Skip to main content
  1. Posts/

These are my Memos on IO

·907 words·5 mins
Agent IO
Author
Agent IO
Table of Contents
Here’s how I self-host a gRPC-based web application with IO.

What is Memos?
#

Memos home page

Memos is an open source web application for writing, organizing, and sharing short notes. Here it is running on one of our Digital Ocean droplets in Nomad behind IO.

Memos main screen

Look at this beautiful sign-in screen:

Memos signin screen

As you might guess, Memos is a note-taking app that supports OAuth-based signin (along with optional usernames and passwords). Memos is nicely configurable and can use a small variety of database backends that includes SQLite, which seems perfectly fine for even the busiest self-hosting note taker.

How I Run Memos
#

With Nomad
#

I’m running Memos in a droplet that I manage with Nomad just as described in Droplet Superpowers.

Here’s the job description, which I put in a file named memos.hcl and apply with nomad job run memos.hcl:

job "memos" {
  datacenters = ["dc1"]
  type = "service"
  group "memos" {
    count = 1
    network {
      port "http" { to = 3000 }
    }
    service {
      name = "memos"
      provider = "nomad"
      port  = "http"
    }
    volume "memos" {
      type      = "host"
      read_only = false
      source    = "memos"
    }
    task "memos" {
      driver = "docker"
      config {
        image = "neosmemo/memos:stable"
        ports = ["http"]
        force_pull = true
      }
      env {
        MEMOS_MODE = "prod"
        MEMOS_PORT = 3000
      }
      volume_mount {
        volume = "memos"
        destination = "/var/opt/memos"
        read_only = false
      }
    }
  }
}

This needs a host volume, which I described with an addition to the client block in my /etc/nomad.d/nomad.hcl:

host_volume "memos" {
  path = "/srv/nomad/memos"
  read_only = false
}

If you’ve been doing this, you also know that you need to create the /srv/nomad/memos directory on your droplet and restart Nomad before you apply the job description.

With the job running, this is what I see in the Nomad console:

Nomad job

If I drill down to see the allocation, I can see that it’s running well below the default 300MB memory allocation for Nomad jobs. I’ll probably tighten the memory budget in my job description later.

The Nomad allocation

Behind IO
#

Memos is running a server inside my cluster that listens using HTTP. I publish that to the internet using an IO ingress that serves HTTPS. The configuration for that is really simple:

host "memos.agentio.dev" {
  name    = "memos"
  backend = "nomad:memos"
}

I put that in a file named host-memos.hcl and installed it in my IO with scp:

scp -P 8022 host-memos.hcl agentio.dev:

The HTTPS service needs an SSL certificate, and as discussed elsewhere, IO can get that for me from LetsEncrypt.

Observing Memos Traffic
#

Because I’m running the Memos app behind IO, all of its traffic is captured and viewable in the IO console. Here I’ve connected to the IO running this ingress and expanded the list of traffic to the Memos app.

Memos traffic

That list shows lots of traffic that appears to be API calls. Picking one with ListMemos in the path, I expand the traffic item to see the request and response headers.

The headers for a Memos API request

We see from the content type that this is gRPC Web traffic, and this is how I learned that Memos uses gRPC! The messages are binary-encoded protocol buffers, which can’t be parsed without a schema description, but there’s an easy way to provide that with IO. First I need the descriptors, which I can get by compiling the protos in the usememos/memos repo. I run this script in the protos directory.

#!/bin/sh

protoc -I . -I ~/Desktop/googleapis/googleapis \
        --include_imports \
        api/v1/activity_service.proto \
        api/v1/auth_service.proto \
        api/v1/attachment_service.proto \
        api/v1/user_service.proto \
        api/v1/idp_service.proto \
        api/v1/common.proto \
        api/v1/markdown_service.proto \
        api/v1/workspace_service.proto \
        api/v1/shortcut_service.proto \
        api/v1/inbox_service.proto \
        api/v1/memo_service.proto \
        -odescriptor.pb

Then I upload the descriptors to my IO with scp.

scp -P 8022 descriptor.pb agentio.dev:

That puts the Memos type descriptions in IO’s internal protobuf registry and allows IO to display the contents of Memos API requests and responses. Here’s the response body of that ListMemos request:

The response body for a Memos API request

Going Further
#

Needless to say, I’m impressed by this note-taking project. It has lots of qualities that I find appealing:

  • The code is available at usememos/memos with the MIT License.
  • It’s got a good and familiar base for me (gRPC, Go).
  • It’s mature and up to v0.25.0.
  • It runs easily in a container.
  • It can use SQLite.
  • It stores attachments in nice (and configurable) ways.
  • It could provide good patterns for similar apps (e.g. photo-sharing).

There are just a few things that I don’t love:

  • The main Memos authors are pseudonymous.
  • The Memos API is unstable (the mobile apps are currently broken because some method names changed for v0.25).
  • Memos is a rabbit hole. I could really get drawn into work on it!
  • Now that I’ve tasted ATProto auth, I hate having to register OAuth clients to use providers like GitHub. Memo needs some work to support ATProto auth.
  • The Memos build process depends on Buf and the protos are tricky to compile without Buf.

I’ll keep using my Memos deployment for a while as a scratchpad for work in progress related to IO. Posts are generally “protected” (visible only to signed-in users), but I’ve left the instance open for anyone to sign in with GitHub OAuth. Come see us at memos.agentio.dev.

Appendix: Tweaking the Memos Styling
#

The default Memos header sizes seemed a bit aggressive to me, so I toned them down by adding these style overrides to my Memos configuration:

.text-5xl {font-size: 1.75rem;}
.text-4xl {font-size: 1.60rem;}
.text-3xl {font-size: 1.45rem;}
.text-2xl {font-size: 1.30rem;}
.text-xl  {font-size: 1.15rem;}

This is easy to do in a Memos configuration screen, shown here: Memos configuration

Comments
#