Using systemd as user-level inetd

Sun 03 December 2023 by pj Tagged as linux systemd

So, I wanted to jump on the AI-dev-assistant bandwagon. Preferably with an open-source self-hosted system so I don't have to worry about usage limits or the system going away, etc.

A bit of research later, and my dev-assistant of choice is currently Tabby, which is happy to run under docker and then talk to neovim via a socket. So now I just have to set it up to auto-start somehow.

Systemd has socket activation, which is almost the same as inetd, but docker isn't nice enough to accept filehandles via a socket or pipe (which is what inetd - and thus systemd - wants)... but the systemd guys realized that lots of things wouldn't do so and thus wrote systemd-socket-proxyd to bridge the gap.

I mostly followed the method and details as (well) written up in a blog post by an atlassian dev, resulting in:

tabby-docker.sh

#!/bin/bash
exec docker run -t --rm \
  --name tabby \
  --pull always \
  --gpus all \
  -p 8151:8080 \
  -v $HOME/.tabby:/data \
  tabbyml/tabby \
  serve --model TabbyML/StarCoder-1B --device cuda

~/.config/systemd/user/tabby-docker.service

[Unit]
Description=TabbyML/tabby container

[Service]
ExecStart=/home/pj/bin/tabby-docker.sh
ExecStartPost=/home/pj/bin/waitforport localhost 8151
ExecStop=/usr/bin/docker stop tabby

~/.config/systemd/user/tabby-docker-proxy.service

[Unit]
Description=unix socket proxy to tabby-docker service

BindsTo=tabby-docker.service
After=tabby-docker.service

[Service]
ExecStart=/lib/systemd/systemd-socket-proxyd 127.0.0.1:8151

~/.config/systemd/user/tabby-docker-proxy.socket

[Socket]
ListenStream=8150

[Install]
WantedBy=sockets.target

The only tweak I found necessary was to change the Requires= in the myservice-proxy.service file to be BindsTo= so that if I manually stop tabby (via docker stop or systemctl --user stop tabb-docker), it will be restarted when a connection is next made.

The only annoyance factor is the use of two sockets: one for systemd to listen on and one for the proxy to talk to. This has two potential fixes: 1. Docker should be able to accept a connection from systemd somehow: a unix socket, a file descriptor... something. 2. Systemd should have a shortcut syntax to do all of the above. inetd and xinetd had redirect; something similar in the .socket config could obviate the need for the proxy service.

Hmm. Now that I think about it, I wonder if there's a way to wrap the proxy inside the docker container so that the unix socket could be passed via a filesystem mapping. Maybe I'll experiment another time.