Files
turnstone/deploy/systemd
Patrick Buckley 04b3a3abe4 feat(deploy): systemd units for a bare-metal turnstone-server node
Hardened service + slice + node-identity drop-in template + a README for
running a turnstone-server outside Docker that joins the compose cluster —
the production-shaped counterpart to the one-liner in docs/docker.md. Secrets
stay in config.toml; per-host identity + cluster URLs go in the drop-in. The
README notes the cross-host mTLS caveat (turnstonelabs/lacme#22).
2026-06-15 03:41:24 -07:00
..

Running a bare-metal turnstone-server under systemd

These units run a turnstone-server outside Docker (e.g. on a box with a local GPU) so it joins an existing cluster — typically the docker-compose stack in compose.yaml. They are the hardened, production-shaped counterpart to the quick turnstone-server … invocation in docs/docker.md ("Join a bare-metal host").

File Purpose
turnstone-server.service The hardened server unit (sandboxed; secrets via config.toml).
turnstone.slice Shared memory/process budget for colocated Turnstone units.
turnstone-server.service.d/node.conf.example Per-host identity + cluster URLs drop-in (no secrets).

Cluster-side prerequisite

The compose stack must publish Postgres, the console's ACME endpoint, and SearxNG on an address the bare-metal host can reach. Start it with TURNSTONE_HOST_IP set to the compose host's LAN IP (default 127.0.0.1 keeps everything host-local):

TURNSTONE_HOST_IP=<compose-host-ip> docker compose up -d

Install (run as root on the bare-metal host)

# 1. A dedicated, unprivileged user.
useradd --system --no-create-home --shell /usr/sbin/nologin turnstone

# 2. Install turnstone into a venv at /opt/turnstone-venv (lacme/mTLS is a core dep).
uv venv /opt/turnstone-venv --python 3.12
uv pip install --python /opt/turnstone-venv 'turnstone @ git+https://github.com/turnstonelabs/turnstone'
#   …or from a local checkout:  uv pip install --python /opt/turnstone-venv /path/to/turnstone

# 3. Secrets — match the cluster's JWT secret + DB credentials (kept out of env).
install -d -m 750 -o turnstone -g turnstone /etc/turnstone
cat > /etc/turnstone/config.toml <<'TOML'
[auth]
jwt_secret = "<same secret as the cluster>"
[database]
backend = "postgresql"
url = "postgresql+psycopg://turnstone:<password>@<compose-host-ip>:5432/turnstone"
[api]
base_url = "http://localhost:8000/v1"   # a real model backend is configured in the console UI
api_key = "dummy"
TOML
chown turnstone:turnstone /etc/turnstone/config.toml
chmod 600 /etc/turnstone/config.toml

# 4. Units + per-host drop-in.
cp turnstone-server.service turnstone.slice /etc/systemd/system/
install -d /etc/systemd/system/turnstone-server.service.d
cp turnstone-server.service.d/node.conf.example \
   /etc/systemd/system/turnstone-server.service.d/node.conf
$EDITOR /etc/systemd/system/turnstone-server.service.d/node.conf   # set the addresses

# 5. Go.
systemctl daemon-reload
systemctl enable --now turnstone-server.service
journalctl -u turnstone-server -f          # watch it register + (if the cluster runs mTLS) enroll

tls.enabled is not set here — a joining node inherits it from the cluster's shared settings (the database). If the cluster runs mTLS, the node auto-enrolls a cert from the console's ACME endpoint and re-advertises itself over https://.

mTLS + cross-host caveat: a node on a different host than the console currently can't complete ACME enrollment — the console advertises an unroutable in-container address in its ACME directory (turnstonelabs/lacme#22). Same-host bare-metal nodes, and any node in a non-mTLS cluster, are unaffected.