| 1 |
# Bento app-build controller (bentod) — systemd USER service under the operator. |
| 2 |
# |
| 3 |
# bentod is a user service (not system) because it builds the apps and so needs |
| 4 |
# the operator's environment directly: SSH keys to the tailnet build hosts + the |
| 5 |
# mbp ops-agent, the app checkouts under ~/Code/Apps, and the _private layer for |
| 6 |
# signing secrets (secrets_root). A hardened system user can't reach those. |
| 7 |
# |
| 8 |
# Install (one-time, no sudo except enable-linger): |
| 9 |
# mkdir -p ~/.config/systemd/user |
| 10 |
# install -m 0644 bentod.service ~/.config/systemd/user/ |
| 11 |
# loginctl enable-linger "$USER" # keep it running across logout/reboot |
| 12 |
# systemctl --user daemon-reload |
| 13 |
# systemctl --user enable --now bentod |
| 14 |
# |
| 15 |
# Watch: journalctl --user -u bentod -f |
| 16 |
# Deploy a new bentod: build, copy to ~/.local/bin/bentod, then validate the |
| 17 |
# fresh binary against the live config BEFORE restarting: |
| 18 |
# ~/.local/bin/bentod --check-config && systemctl --user restart bentod |
| 19 |
# (no sudo — that's the point of a user service). The unit also runs the same |
| 20 |
# --check-config as ExecStartPre, so a bad config fails the start rather than |
| 21 |
# crash-looping. |
| 22 |
[Unit] |
| 23 |
Description=Bento app build controller |
| 24 |
After=network-online.target |
| 25 |
Wants=network-online.target |
| 26 |
|
| 27 |
[Service] |
| 28 |
Type=simple |
| 29 |
# Validate the config (daemon file + every app's in-repo manifest) before each |
| 30 |
# start. A binary or schema change that can't parse the live config fails the |
| 31 |
# unit here instead of crash-looping — the guard a per-app-config move needed |
| 32 |
# when it bricked bentod for 20h. Same check runs at install (see README). |
| 33 |
ExecStartPre=%h/.local/bin/bentod --check-config |
| 34 |
ExecStart=%h/.local/bin/bentod |
| 35 |
Restart=on-failure |
| 36 |
RestartSec=5 |
| 37 |
Environment=BENTO_CONFIG=%h/.config/bento/bento-daemon.toml |
| 38 |
# This daemon binds the tailnet, not loopback, and has since 2026-07-29: the |
| 39 |
# ops-viewer panel on astra polls it, and a loopback bind was only ever visible |
| 40 |
# to a viewer on fw13. Two consequences, and neither is optional. |
| 41 |
# |
| 42 |
# bentod REFUSES to start on a non-loopback listen unless BENTO_API_TOKEN is |
| 43 |
# set, so the file below is load-bearing rather than an example. Keep the |
| 44 |
# leading `-` so a fresh machine can start the unit before the token file |
| 45 |
# exists; the daemon will still refuse the tailnet listen until it does. |
| 46 |
# |
| 47 |
# Every mutating call carries the same token as a bearer header. /build and |
| 48 |
# /retry are behind the auth middleware, so a publish is: |
| 49 |
# curl -X POST http://fw13:8765/build -H "authorization: Bearer $TOKEN" |
| 50 |
# -H 'content-type: application/json' -d '{"app":"NAME","version":"X.Y.Z"}' |
| 51 |
# (one command; wrapped here because a trailing backslash in a systemd |
| 52 |
# comment is a line continuation, not punctuation) |
| 53 |
# The read routes (/state, /release, /logs, /events) are open. |
| 54 |
EnvironmentFile=-%h/.config/bento/bento.env |
| 55 |
StandardOutput=journal |
| 56 |
StandardError=journal |
| 57 |
SyslogIdentifier=bentod |
| 58 |
|
| 59 |
[Install] |
| 60 |
WantedBy=default.target |
| 61 |
|