In the last few days, I've needed to set up several long-running services and I just wanted to take a minute to talk about how helpful systemd's user services have been.

The things I wanted to run are:

  • A Node.js server which is started with npm run
  • A Node.js client (for testing)
  • MoinMoin (a wiki written in Python)
  • Ghost (blog software for Node.js)
  • SyncThing (file sync like DropBox)

For the first one, I figured systemd would be useful, and I figured I might as well try running in user mode. The server isn't particularly stable, so systemd's automatic-restarting is helpful, and the sandboxing is also nice to have.

Once I had the config file, I realized that it's trivial to adapt it to any other long-running process. I also found another advantage: Since these run in user-mode, it's trivial to create a user for each service, do whatever I need to in their home directory, and then lock things down. I can log in as those users and run updates, and the only thing I need root access for is to give them permission to run services when not logged in ("lingering").

Users

Creating single-use users is easy:

sudo adduser moin

To be able to ssh in as that user using the same keys as the current user:

sudo -u moin mkdir ~moin/.ssh
sudo cp ~/.ssh/authorized_keys ~moin/.ssh/authorized_keys
sudo chown moin:moin ~/.ssh/authorized_keys

If multiple people need access, you might want to generate the authorized_keys file from their public keys rather than just copying your own.

Unit files

Here's what my Ghost config looks like (in ~/.config/systemd/user/ghost.service):

[Unit]
AssertPathExists=/home/ghost/ghost

[Service]
WorkingDirectory=/home/ghost/ghost
Environment=GHOST_NODE_VERSION_CHECK=false
ExecStart=/usr/bin/npm start --production
Restart=always
PrivateTmp=true
NoNewPrivileges=true

[Install]
WantedBy=default.target

The nice bits are that if it crashes, it will restart, it has its own /tmp, and even if it was hacked, it can't gain root priviledges (although, I'm not sure if these options work right in user mode).

With barely any changes, it works for MoinMoin too:

[Unit]
AssertPathExists=/home/moin/moin

[Service]
WorkingDirectory=/home/moin/moin
ExecStart=/usr/bin/python2 /home/moin/moin/wikiserver.py
Restart=always
PrivateTmp=true
NoNewPrivileges=true

[Install]
WantedBy=default.target

For my custom Node.js server, I could even have systemd provide the socket and then lock things down even more.

Auto-start and lingering

To enable autostart, I just had to run:

systemctl --user enable moin # or ghost or whatever

Then:

loginctl enable-linger moin # this will need authentication from a sudoer

The end

I just want to share how easy it was to do this, since separating services to each be run by a different user is good for security, and apparently it's super easy these days.