Files
paul 00195ee6d5 Add Gitea integration for agent to push branches and open pull requests
- 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`.
2026-06-29 20:46:33 +12:00

82 lines
3.9 KiB
Markdown

# semprini-maintainer — Claude Instructions
## What this is
A standalone, always-on **autonomous maintenance agent** for the semprini.me
container stack, deployed as a systemd service (`semprini-maintainer`). It keeps
the stack healthy and current and talks to the operator over Matrix
(chat.semprini.me).
It is the *unattended driver* for the canonical **stack-support agent**, which
lives in the separate **semprini-core** repo. This project does not reimplement
that logic — it gathers state and invokes the stack-support agent headless via
Claude Code, then relays/acts on the agent's JSON result.
## Relationship to other projects
- **semprini-core** (`../semprini-core`) — the Enterprise Landing Zone this agent
maintains. At runtime the daemon reads, from the path in `config.json`
`repo_dir`:
- `agents/stack-support.agent.md` (injected as system prompt)
- `docs/stack-support-runbook.md`
- `core_stack/compose*.yml`
Keep the stack-support agent definition canonical **in semprini-core** — do not
fork a copy here.
- **Other projects** (e.g. `../semprini-blog`) that register Uptime Kuma monitors
are also maintained; their repos are exposed to the agent via `extra_dirs`.
## How it works (one screen)
- **Scope** = whatever **Uptime Kuma** tracks at check time, re-read every cycle
(never hard-coded). Kuma's heartbeat is the health signal — the daemon does not
re-probe. Kuma's root-owned `kuma.db` is read via a throwaway `keinos/sqlite3`
container mounting the `uptime-kuma-data` volume read-only.
- **Health loop** (5 min): down services confirmed over `down_confirmations`
cycles → invoke stack-support agent to remediate → report to Matrix. Also
reads Kuma's `important=1` heartbeat transitions since the last cycle (tracked
by heartbeat id) to catch services that blipped DOWN→UP on their own, and
hands those self-recovered windows to the agent for a log post-mortem.
- **Upgrade loop** (7 days): resolve containers behind tracked services → agent
researches + applies safe upgrades (test, pin, rollback); escalates
major/breaking ones to Matrix.
- **Matrix**: bot `@maintainer-bot:semprini.me` DMs `@paul:semprini.me`. Escalations
are numbered questions; the reply is fed back to the agent to carry out.
- **Git/PRs**: when the agent changes tracked files it branches, pushes, and opens
a **pull request** on git.semprini.me (Gitea) as the `claude-code` user (never
commits to `main`); live remediation still happens immediately and directly.
The daemon injects claude-code's git identity + credential and the Gitea API
env into the headless agent (see `agent_git_env` / `GIT_PR_POLICY` in
`maintainer.py`); token provisioned by `register-gitea-bot.sh`. Empty
`gitea.token` disables the workflow.
- **Autonomy** (`config.json``autonomy`): `full` (default) | `fix-only` |
`notify`.
## Conventions / guardrails
- Runs as host user **`paul`** (in `docker` group), **not root** — Claude Code
refuses `--permission-mode bypassPermissions` under root.
- `maintainer.py` is **stdlib-only** — keep it dependency-free so it deploys
without a venv.
- Secrets live in `config.json` and `.bot-secrets`**never commit** (gitignored).
- Prefer editing existing files over adding new ones.
## Files
| File | Purpose |
|---|---|
| `maintainer.py` | The daemon (stdlib only). |
| `config.example.json` | Template — copy to `config.json`. |
| `register-matrix-bot.sh` | Creates `@maintainer-bot` account + DM room; writes token to config. |
| `register-gitea-bot.sh` | Mints the `claude-code` Gitea token (push + PRs); writes it to config. |
| `install.sh` | Syntax-check, install unit, enable + start. |
| `semprini-maintainer.service` | systemd unit (runs as `paul`). |
| `docs/architecture.md` | Architecture decision record. |
## Test without the service
```bash
python3 maintainer.py --config config.json --once health
python3 maintainer.py --config config.json --once upgrade
python3 maintainer.py --config config.json --once replies
```