docs: extract best-effort oneshot service contract + matrix-avatar (#718 batch 6)
Two new sections housing rationale that was inline in `harness-base.nix`: - `docs/conventions.md::Best-effort oneshot services` — shape contract shared by `tea-login`, `forge-avatar-sync`, and `matrix-avatar-sync`: always exit 0, no `set -e`, skip silently on missing prerequisites, wired to multi-user.target, re-runnable. Plus the artefact-under-agent-home + service-stays- root posture and the operator-visible-via-journalctl trade-off. - `docs/persistence.md::matrix-avatar-sync` — the two-step `media upload` → `set avatar_url` matrix-spec dance + why `RemainAfterExit = false` (so the `.path` watcher's re-fire on token appearance actually re-executes the unit). In-code comments trim to one-liner purpose + pointers; the script bodies stay (they're the actual implementation) but their inline `#` rationale comments collapse where the docs cover them. `description = ''…''` blocks (operator-facing options docs) preserved per iris #718. `nix flake check` clean; `nix fmt` clean.
This commit is contained in:
parent
fe6f96397e
commit
a043c61828
3 changed files with 94 additions and 56 deletions
|
|
@ -206,3 +206,53 @@ Stage and commit when work *looks* ready, then run validation
|
|||
(`cargo check`, `nix flake check`, real deploy). Failures get a
|
||||
follow-up commit rather than an amend. The commit history is the
|
||||
work log; rewriting it loses signal.
|
||||
|
||||
## Best-effort oneshot services
|
||||
|
||||
The harness ships a family of one-shot systemd services that
|
||||
configure agent-side surfaces from values hive-c0re writes into
|
||||
the state dir at provisioning time:
|
||||
|
||||
- `tea-login` — writes `~/.config/tea/config.yml` from the
|
||||
`forge-token` written by `hive-c0re::forge::ensure_user_for`,
|
||||
so `tea repos create` / `tea pulls create` work without
|
||||
interactive prompts.
|
||||
- `forge-avatar-sync` — uploads `hyperhive.icon` SVG to the
|
||||
agent's Forgejo profile, so the icon shows up on commits / PRs /
|
||||
issue comments.
|
||||
- `matrix-avatar-sync` — same idea for matrix profile avatars
|
||||
(two-step `media upload` → `set avatar_url` dance — see
|
||||
`docs/persistence.md::matrix-avatar-sync` for the protocol
|
||||
detail).
|
||||
|
||||
Shape contract — every one of these:
|
||||
|
||||
1. **Always `exit 0`**, even on internal failure. A non-zero exit
|
||||
would mark the unit `failed`, which in turn aborts
|
||||
`nixos-container update` and blocks rebuilds. The agent's
|
||||
capability surface is not allowed to gate the container
|
||||
build.
|
||||
2. **No `set -e`** in the script body. Subshell failures must not
|
||||
propagate. Use `... || true` on every external call that can
|
||||
fail (forge unreachable, missing icon, parse error, etc.)
|
||||
3. **Skip silently when prerequisites are missing**: no token
|
||||
file, no icon, no reachable upstream → `echo` a short skip
|
||||
line + `exit 0`. The next boot tries again.
|
||||
4. **Wired to `multi-user.target`** so they run on every boot
|
||||
(lets a rotated token / new icon take effect without
|
||||
`systemctl restart` gymnastics).
|
||||
5. **Re-runnable**: a second invocation produces the same final
|
||||
state (idempotent uploads, idempotent config rewrites). Used
|
||||
by the `.path` watchers that re-fire on token appearance
|
||||
(#571 — see `docs/persistence.md::matrix-avatar-sync`).
|
||||
|
||||
The artefact lives under the agent user's home where applicable
|
||||
(`~/.config/tea/config.yml`) and is chown'd to that user, but the
|
||||
service itself stays root-owned so the bootstrap ordering doesn't
|
||||
need a user-existence check before each fire.
|
||||
|
||||
This pattern keeps the rebuild path resilient: any failure inside
|
||||
these services degrades the corresponding surface (no tea config,
|
||||
no avatar) but never blocks the container from coming up. The
|
||||
operator notices through `journalctl -u <unit>` rather than a
|
||||
broken switch-to-configuration.
|
||||
|
|
|
|||
|
|
@ -259,3 +259,25 @@ restart. The `.path` unit makes the appearance of the token re-fire
|
|||
the service so the daemon comes alive in the same boot cycle as
|
||||
provisioning. `matrix-avatar-sync.path` uses the same pattern for
|
||||
the icon-upload oneshot.
|
||||
|
||||
### matrix-avatar-sync (two-step media + profile dance)
|
||||
|
||||
Mirrors the forge-avatar oneshot's shape (`docs/conventions.md::
|
||||
Best-effort oneshot services`) but differs in protocol: matrix
|
||||
avatars are a two-step `POST /media/r0/upload` → `PUT
|
||||
/profile/<user_id>/avatar_url` dance, both authenticated by the
|
||||
`access_token` written by `hive-c0re::matrix::ensure_user_for` to
|
||||
`<state>/matrix-token`.
|
||||
|
||||
Triggered by EITHER boot (`wantedBy = multi-user.target`) OR the
|
||||
sibling `matrix-avatar-sync.path` firing on token appearance. Both
|
||||
paths re-run the oneshot idempotently — running the avatar set
|
||||
twice is harmless.
|
||||
|
||||
Critically: **`RemainAfterExit = false`** (not the more common
|
||||
`true` for oneshots). systemd treats `RemainAfterExit = true`
|
||||
oneshots as "still running" after the first exit, so the second
|
||||
trigger from the `.path` watcher becomes a no-op. Setting it to
|
||||
`false` lets re-fires actually re-execute. The trade-off is the
|
||||
service unit shows `inactive (dead)` between fires — visible in
|
||||
`journalctl` but harmless; the `.path` unit drives the lifecycle.
|
||||
|
|
|
|||
Loading…
Reference in a new issue