- Implemented `register-gitea-bot.sh` to provision Gitea access token. - Updated `maintainer.py` to support Gitea API for push and PR operations. - Modified `install.sh` to warn if Gitea token is not provisioned. - Enhanced documentation in `README.md`, `CLAUDE.md`, and `architecture.md` to reflect new Gitea functionality. - Added Gitea configuration to `config.example.json`.
76 lines
3.9 KiB
Markdown
76 lines
3.9 KiB
Markdown
# Maintenance Agent (semprini-maintainer)
|
|
|
|
Autonomous, always-on agent that keeps the container stack healthy and current.
|
|
Implementation: this repository — see [`README.md`](../README.md). It maintains
|
|
the **semprini-core** stack (a separate repo) referenced via `repo_dir`.
|
|
|
|
## Purpose
|
|
|
|
Continuously maintain the running stack without an operator at the keyboard:
|
|
|
|
1. Detect and **remediate** service outages.
|
|
2. Periodically **check for and apply container upgrades** (with test + rollback).
|
|
3. **Communicate with the operator over Matrix** (chat.semprini.me), including
|
|
asking questions when an upgrade needs a human decision.
|
|
|
|
It is the unattended driver for the canonical stack-support agent
|
|
(`agents/stack-support.agent.md`) — it does not re-implement that logic, it
|
|
feeds the agent state and relays its results.
|
|
|
|
## Key decisions
|
|
|
|
- **Scope = Uptime Kuma's live monitor list.** Read every health cycle, never
|
|
hard-coded. Anything Kuma tracks is in scope — including services owned by
|
|
other projects (e.g. `semprini-blog`) that register Kuma monitors. This keeps
|
|
the maintained set and the monitored set identical by construction.
|
|
- **Health signal = Kuma heartbeat.** The daemon reads Kuma's own up/down
|
|
result rather than re-probing, reusing the stack's existing monitoring.
|
|
- **Kuma data access** via a throwaway `keinos/sqlite3` container mounting the
|
|
`uptime-kuma-data` volume read-only (the daemon user has Docker but not host
|
|
access to root-owned volume files).
|
|
- **Runner = Claude Code headless** (`claude -p`), with
|
|
`agents/stack-support.agent.md` injected as the system prompt. The agent
|
|
returns a JSON envelope (`status/summary/question/options`) the daemon acts on.
|
|
- **Runs as `paul`** (in the `docker` group), not root — Claude Code refuses
|
|
`bypassPermissions` as root.
|
|
- **Autonomy = full** by default: remediate and apply safe upgrades; escalate
|
|
only major/breaking upgrades to Matrix and resume on reply. Configurable to
|
|
`fix-only` or `notify`.
|
|
- **Matrix transport**: dedicated bot `@maintainer:semprini.me` (created via the
|
|
Synapse registration shared secret) in a private room with `@paul:semprini.me`.
|
|
- **Code changes go through pull requests.** When the agent edits tracked files
|
|
it branches, pushes, and opens a PR on Gitea (`git.semprini.me`) as the
|
|
`claude-code` user rather than committing to `main`, giving the operator a
|
|
review gate. Live remediation (restart/redeploy) is *not* gated — only the git
|
|
change is. claude-code is an OIDC-linked, push-capable collaborator; since
|
|
git.semprini.me disables password login + HTTP basic auth, `register-gitea-bot.sh`
|
|
mints a personal access token via the gitea admin CLI inside the `user-gitea`
|
|
container (mirroring the Matrix bot's shared-secret pattern). A PAT still works
|
|
for git-over-HTTPS and the API, so no SSH exposure or core-stack change is
|
|
required. The daemon injects the bot's git identity + credential (`GIT_CONFIG_*`,
|
|
scoped to the Gitea host so it overrides the repo's own `user.*`) and the API
|
|
coordinates into the headless agent; the agent opens the PR itself.
|
|
|
|
## Cadence
|
|
|
|
| Loop | Default interval | Config key |
|
|
|---|---|---|
|
|
| Health check + remediation | 5 min | `health_interval_seconds` |
|
|
| Upgrade review | 7 days | `upgrade_interval_seconds` |
|
|
| Matrix reply polling | 30 s | `matrix_poll_seconds` |
|
|
| Quiet health heartbeat | daily | `heartbeat_interval_seconds` |
|
|
|
|
## Escalation flow
|
|
|
|
1. Agent returns `status=escalate` with a `question` + `options`.
|
|
2. Daemon posts a numbered question to Matrix and pauses upgrade cycles.
|
|
3. Admin replies (a number or free text).
|
|
4. Daemon feeds the decision back to the agent, which carries it out and reports.
|
|
|
|
## Operational notes
|
|
|
|
- The bot DM room invite must be accepted once by `@paul` in Element.
|
|
- Secrets (`config.json`, `.bot-secrets`) are gitignored.
|
|
- A single unanswered escalation blocks new upgrade cycles (health remediation
|
|
continues regardless).
|