Subsystem Search

Part 6 of 6 Running one for other people

An account, an install and a first run

By the end of this chapter, Subroutine is installed on Laurence's server under an account of its own, its instance is made in directories the service will use, and its settings read back to say where each one came from.

This is the first chapter of running an instance for other people. Nothing is served yet and nobody else can reach it: what it gets right is which account owns the data and where that data lives, which is the thing everything afterwards depends on.

How the commands in this part are written

Everything here is typed on the server, by somebody who can sudo. Your own terminal will ask for your password the first time; these pages never show that prompt, because there is nobody at the keyboard to answer it.

Every command that acts on the instance's data is written out in full, as five lines:

Example not checked: it is not run when this site is built.

$ sudo -u subroutine env \
    XDG_CONFIG_HOME=/var/lib/subroutine/config \
    XDG_DATA_HOME=/var/lib/subroutine/data \
    XDG_STATE_HOME=/var/lib/subroutine/state \
    /opt/subroutine/bin/subroutine <command>

It is long, and the length is the point. It says on the line you are reading who it runs as, which configuration and data it uses, and which copy of the program, and those three things decide which database you are about to change.

A bare subroutine … on this machine reads your configuration, finds whichever instance your own account has, and answers confidently about that one. It does not fail. That is why nothing here is shortened, and why no alias or PATH change is offered: a second name for the command is a step you can forget, and forgetting it does not stop you, it addresses a different database.

An account and an install

A service account with no login and no home, and a virtual environment it does not own:

$ sudo useradd --system --no-create-home --shell /usr/sbin/nologin subroutine
$ sudo python3 -m venv /opt/subroutine
$ sudo /opt/subroutine/bin/pip install "subroutine[postgres]"
…
Successfully installed …

[postgres] installs the database driver, not the database: it is harmless with no server anywhere, and a server is the next chapter's subject. Leave it off if you are staying on SQLite.

Subroutine keeps its files under the XDG directories: configuration in $XDG_CONFIG_HOME/subroutine, the database in $XDG_DATA_HOME/subroutine, and what it is working on in $XDG_STATE_HOME/subroutine. The systemd unit, later in this part, points all three inside /var/lib/subroutine, so the service never depends on its account having a home directory.

Make the state directory first

The service account cannot make its own directory, because /var/lib belongs to root. So init stops before it has started:

$ sudo -u subroutine env \
    XDG_CONFIG_HOME=/var/lib/subroutine/config \
    XDG_DATA_HOME=/var/lib/subroutine/data \
    XDG_STATE_HOME=/var/lib/subroutine/state \
    /opt/subroutine/bin/subroutine init --workspace metacortex --instance-name MetaCortex
Cannot create the configuration directory /var/lib/subroutine/config/subroutine: Permission denied.

/var/lib/subroutine does not exist, and this account cannot create it. Make it as root and hand it to this account, or point the XDG directories somewhere this account can already write.

Take the first of the two:

$ sudo install -d -o subroutine -g subroutine -m 0755 /var/lib/subroutine

This is the only time you make that directory by hand. The systemd unit later carries StateDirectory=subroutine, which makes it and hands it to the service account, with exactly the owner and mode above. But systemd only does so when the service first starts, and the service cannot start until init has run, so the first one is yours.

The first run

Now run init as the service account, with the same environment the service will have, so that the database and the signing key land where the service will look for them:

$ sudo -u subroutine env \
    XDG_CONFIG_HOME=/var/lib/subroutine/config \
    XDG_DATA_HOME=/var/lib/subroutine/data \
    XDG_STATE_HOME=/var/lib/subroutine/state \
    /opt/subroutine/bin/subroutine init --workspace metacortex --instance-name MetaCortex
Ready. Try: subroutine add "something to do"

One line, because subroutine init is written for somebody setting up a list. It has made the database, the first workspace, an Inbox and the first account, which administers this instance, and it has written exactly one setting into config.toml: secret_key. That file is created 0600, and so is credentials.toml beside it.

The two options name the team's workspace and this installation, both of which have defaults meant for somebody setting up a list of their own. metacortex is the short name every command and address writes, and MetaCortex is what a terminal connecting from another machine reports back. Either can be changed afterwards, but the short name ends up in addresses people have written down, so it is cheaper to choose it now.

The key signs pagination cursors and nothing else. It is deliberately not mixed into stored token hashes, so rotating it costs a page of results somebody was in the middle of, rather than every credential on the instance.

What it decided, and where each value came from

Everything else in config.toml you add yourself. When a value surprises you, ask where it came from rather than guessing:

$ sudo -u subroutine env \
    XDG_CONFIG_HOME=/var/lib/subroutine/config \
    XDG_DATA_HOME=/var/lib/subroutine/data \
    XDG_STATE_HOME=/var/lib/subroutine/state \
    /opt/subroutine/bin/subroutine config show
…
database_url                    sqlite:////var/lib/subroutine/data/subroutine/subroutine.db  [default]
…
host                            127.0.0.1  [default]
…
port                            8471  [default]
…
secret_key                      (set)  [/var/lib/subroutine/config/subroutine/config.toml]
…

subroutine config show lists every setting rather than the ones you have changed, which is how you find out what there is to change. The column at the end says which source won: a flag beats the environment, the environment beats the file, and the file beats the default.

Every setting can also be given as SUBROUTINE_<NAME> in the environment, which is what you want for database_url when the credential comes from a secrets manager rather than a file on disk.

The process reads its configuration once, when it starts. Change config.toml and restart the service; serve does not reload. A setting that appears not to have taken effect is nearly always this.

A small always-on machine

A Raspberry Pi or a NAS is a Linux server, and everything in this part applies to it unchanged. Two things are worth deciding before you start:

Where the database lives. SQLite needs proper file locking, and a network filesystem cannot give it: serve refuses to start on one rather than corrupting the file quietly. On a NAS that means a local volume rather than a share, and on a Pi it means the disk rather than a mount.

What the backups are for. Kept beside the database, they protect you from a bad migration or a delete nobody meant, and not from losing the device. Backups have a chapter of their own later in this part, and where they are written is one line of configuration.