fix(deploy): make config readable by the service account, and assert modes
Some checks failed
deploy / deploy (push) Failing after 7m11s

Run 5 shipped everything and started all three units, which then failed
identically:

  configuration is invalid: /etc/tireless/config.toml: Permission denied

The config went out 0640 root:root, but the services run as `tireless`, which
is not in the root group. `--chown root:tireless` is not available as a fix:
on a fresh host that group does not exist until systemd-sysusers runs later in
the same deploy — the same ordering trap that produced the StateDirectory
change. So 0644 root:root, which is defensible precisely because this file
carries no secrets by design; the one that does, tireless.env, stays
0640 root:tireless and is installed by hand.

Testing that on the host turned up a second, quieter fault: `--chmod` only
applies to files rsync actually transfers, so a redeploy whose config is
byte-identical leaves the old mode in place. A mode fix would have appeared to
work on the deploy that introduced it and silently not applied afterwards. Add
-p to the shared options and switch to --chmod=F644/F755 so every push asserts
the mode it wants rather than hoping the content changed.

Also give tireless-runner an explicit start limit. Its preflight fails
permanently without an agent login, and Restart=on-failure with RestartSec=30s
never trips the default 5-starts-per-10s limiter, so it would have retried
forever. Three attempts in ten minutes, then failed, where systemctl status
says why.

Verified on bob: api and poller active, /v1/ready returns
{"config":"ok","database":"not_implemented","forge":"not_implemented"}, and
`tireless preflight` reports Subscription billing with the OpenCode lane
asserted non-Anthropic.

Refs #9

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013TxK1CWPkFXqdcXMJ4hVe6
This commit is contained in:
rob thijssen
2026-08-07 16:41:12 +03:00
parent 267cb3314d
commit a5efa1e096
2 changed files with 28 additions and 5 deletions

View File

@@ -105,15 +105,29 @@ jobs:
# directory for a single-file copy, and Fedora ships neither
# /etc/sysusers.d nor /etc/firewalld/services
# (architecture/deployment-gitea-actions.md §6).
R=(--rsync-path="sudo rsync --mkpath")
# -p so --chmod actually applies. Without it, --chmod only affects
# files rsync transfers, so a redeploy whose config is byte-identical
# leaves whatever mode the file already had — including a wrong one.
R=(-p --rsync-path="sudo rsync --mkpath")
B=target/x86_64-unknown-linux-musl/release
rsync "${R[@]}" --chmod 0755 "$B/tireless-api" \
rsync "${R[@]}" --chmod=F755 "$B/tireless-api" \
gitea_ci@"$API_HOST":/usr/local/bin/tireless-api
rsync "${R[@]}" --chmod 0755 "$B/tireless-worker" \
rsync "${R[@]}" --chmod=F755 "$B/tireless-worker" \
gitea_ci@"$API_HOST":/usr/local/bin/tireless-worker
rsync "${R[@]}" --chmod 0755 "$B/tireless" \
rsync "${R[@]}" --chmod=F755 "$B/tireless" \
gitea_ci@"$API_HOST":/usr/local/bin/tireless
rsync "${R[@]}" --chmod 0640 config.toml \
# 0644 root:root, not 0640: the services run as `tireless`, which is
# not in the root group, so 0640 root:root is unreadable to them —
# and `--chown root:tireless` cannot be used here because on a fresh
# host the group does not exist until systemd-sysusers runs, later in
# this same deploy.
#
# World-readable is acceptable because this file carries no secrets by
# design: tokens are named by environment variable rather than written
# down, and there is a test asserting no api-key field can appear in it.
# The file that does carry secrets is /etc/tireless/tireless.env, which
# stays 0640 root:tireless and is installed by hand, never by CI.
rsync "${R[@]}" --chmod=F644 config.toml \
gitea_ci@"$API_HOST":/etc/tireless/config.toml
rsync "${R[@]}" asset/systemd/tireless.sysusers.conf \
gitea_ci@"$API_HOST":/etc/sysusers.d/tireless.conf

View File

@@ -4,6 +4,15 @@ Documentation=https://git.lair.cafe/lair/tireless
After=network-online.target
Wants=network-online.target
# Give up rather than loop. The runner's preflight fails permanently when the
# service account has no agent login, and that needs a human — so `Restart=on-failure`
# with a 30s backoff would retry forever without ever getting closer. The default
# rate limit (5 starts / 10s) never trips at that interval, so state it explicitly:
# three attempts in ten minutes, then sit in `failed` where `systemctl status`
# reports the reason.
StartLimitIntervalSec=600
StartLimitBurst=3
[Service]
Type=exec
User=tireless