# CB-504 — systemd unit for fleetd (Linux). # # The macOS launchd agent (deploy/dev.ltms.fleetd.plist) is the supervision target for the # current single-host deployment. This unit exists for the per-host gateways CB-308 introduces, # which will run on Linux. # # Install (user service — fleetd drives the user's herdr, not a system daemon): # mkdir -p ~/.config/systemd/user # cp deploy/fleetd.service ~/.config/systemd/user/ # # edit ExecStart / WorkingDirectory / Environment below, then: # systemctl --user daemon-reload # systemctl --user enable --now fleetd # journalctl --user -u fleetd -f [Unit] Description=fleetd — claude-bridge message server Documentation=https://git.ltms.dev/fleet/fleetd/wiki # Ordering only: herdr is a user process and its socket may appear after us. This is advisory — # fleetd retries the herdr socket rather than exiting, which is what actually makes a late # socket survivable. Do NOT add Requires=: a herdr restart must not take fleetd down with it. After=herdr.service Wants=herdr.service [Service] Type=simple WorkingDirectory=%h/src/claude-bridge/fleetd ExecStart=/usr/lib/jvm/temurin-25-jdk/bin/java -jar target/fleetd.jar fleetd.yaml Environment=HERDR_SOCKET_PATH=%h/.config/herdr/herdr.sock # PATH matters more than it looks (CB-511): fleetd propagates its own PATH to every worker it # spawns, so this line decides whether the fleet can run a build at all. systemd does not source a # login shell, so without it the daemon — and every worker — gets a bare default with no JDK/Maven. Environment=PATH=/usr/lib/jvm/temurin-25-jdk/bin:/usr/share/maven/bin:/usr/local/bin:/usr/bin:/bin # Secrets are NOT set here — this file is committed. Put ALL three tokens in a private drop-in # that systemd reads with restrictive permissions. In `systemctl --user edit fleetd`, add: # [Service] # Environment=FLEETD_API_TOKEN=... # Environment=WORKER_GITEA_TOKEN=... # Environment=AI_GATEWAY_TOKEN=... # FLEETD_API_TOKEN protects fleetd's API. WORKER_GITEA_TOKEN lets members open pull requests; if # it is missing, fleetd still starts, but a member fails when it later tries to open a pull request. # AI_GATEWAY_TOKEN authenticates gateway profiles; if it is missing, fleetd still starts, but a # gateway profile later returns HTTP 401. Or, put the same three variables in a 0600 file and add: # EnvironmentFile=%h/.config/fleetd/env # After starting, check which of them actually resolved. The daemon reports every secret a # configured profile references, by name, never by value: # journalctl --user -u fleetd | grep 'startup secret' # A resolved one logs "startup secret NAME: set (profile 'x' tokenEnv)". A missing one logs # "startup secret NAME: MISSING" at WARN — and the daemon starts anyway, which is the whole # problem: without this grep the first sign is a member that cannot open a pull request, hours # later and in a different component. # Note what the report can and cannot tell you. It lists only names some profile actually # references (tokenEnv, gitTokenEnv, and the broker uriEnv). A secret nothing references is never # reported, because nothing needs it. Restart=on-failure RestartSec=10s # A bad config (e.g. a non-loopback bind without token auth) makes fleetd fail fast by design. # Give up rather than restart-loop on a permanent error. StartLimitBurst=5 StartLimitIntervalSec=120 # The daemon reads the repo, writes worktrees, and talks to a Unix socket — it needs no more. NoNewPrivileges=true PrivateTmp=true ProtectSystem=strict ProtectHome=read-write ProtectKernelTunables=true ProtectControlGroups=true RestrictSUIDSGID=true StandardOutput=journal StandardError=journal SyslogIdentifier=fleetd [Install] WantedBy=default.target