From 37cece9dbd4ece0195890fe43ea84586e736b941 Mon Sep 17 00:00:00 2001 From: sjat Date: Sat, 30 May 2026 21:34:07 +0200 Subject: [PATCH] Add ADR-010 (Forgejo integration) and rbw-unlocked pre-flight convention ADR-010: API tokens as least-privilege managed secrets, declarative-first (no click-ops), automation boundary, planned trunk-based CI. CLAUDE.md/AGENTS.md: check 'rbw unlocked' before vault-dependent tasks (incl. commits) rather than failing partway. Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 3 ++ CLAUDE.md | 5 +++ docs/decisions/010-forgejo-ci.md | 76 ++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 docs/decisions/010-forgejo-ci.md diff --git a/AGENTS.md b/AGENTS.md index 66eacac..649c13c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,3 +21,6 @@ only designed — much of the ADR-described design is not built yet. - **Check `STATUS.md`** before assuming a role, provider, or pipeline exists. - **Git**: `main` must always work; branch for sweeping changes. Commit your work in logical units with imperative ≤72-char subjects and a `Co-Authored-By` trailer. +- **Vault access**: before a task needing a Vaultwarden secret (`make + deploy/check/encrypt/decrypt`, or any `git commit` — the hook decrypts `vault.yml`), + run `rbw unlocked`; if locked, ask the user to `rbw unlock` first, don't fail partway. diff --git a/CLAUDE.md b/CLAUDE.md index 0cc493f..f3f37c6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -66,6 +66,10 @@ Full design rationale: `docs/decisions/` `vault.grafana.admin_password`); reference as `{{ vault.grafana.admin_password }}` - Vault password comes from Vaultwarden via `rbw` (`scripts/vault-pass-client.sh`, wired as `vault_password_file`). Unlock once per session: `rbw unlock` +- **Before any vault-dependent task** (`make deploy/check/encrypt/decrypt`, or **any + git commit** — the pre-commit ansible-lint hook decrypts `vault.yml`), run `rbw + unlocked`; if it exits non-zero, ask the user to `rbw unlock` and wait rather than + starting and failing partway. The agent stays unlocked 5h. - To edit a vault file: `make decrypt FILE=`, edit, `make encrypt FILE=` --- @@ -165,6 +169,7 @@ Single-contributor, trunk-based (no merge requests / approval gates): | Network topology | `docs/decisions/007-network.md` | | Testing methodology | `docs/decisions/008-testing.md` | | TF ↔ Ansible handoff | `docs/decisions/009-provisioning-handoff.md` | +| Forgejo & CI | `docs/decisions/010-forgejo-ci.md` | | Adding a new role | `docs/runbooks/new-role.md` | | Adding a new host | `docs/runbooks/new-host.md` | | Rotating vault secrets | `docs/runbooks/rotate-secrets.md` | diff --git a/docs/decisions/010-forgejo-ci.md b/docs/decisions/010-forgejo-ci.md new file mode 100644 index 0000000..edeef00 --- /dev/null +++ b/docs/decisions/010-forgejo-ci.md @@ -0,0 +1,76 @@ +# ADR-010 — Forgejo integration and CI + +## Context + +boma's git host, container registry, and (planned) CI all run on a self-hosted +Forgejo instance at `forgejo.nyumbani.baobab.band` (SSH on 7577, HTTPS on 443). Both +humans and AI/automation interact with it. This ADR sets the principles so the +instance does not become undocumented click-ops drift, and so its credentials are +held to the same standard as the rest of the repo's secrets. + +--- + +## What Forgejo provides here + +- **Git hosting** — `origin` (push/pull over SSH; key-based, not token). +- **Container registry** — hosts the Molecule test image + (`forgejo.nyumbani.baobab.band/sjat/molecule-debian13`); see ADR-008. +- **Actions CI** — planned, not yet enabled (`has_actions: false`); trunk-based + pipeline per ADR-003 / ADR-008. + +--- + +## Decisions + +### 1. API tokens are managed secrets, least-privilege + +A Forgejo API token (PAT) is a secret and follows ADR-002: stored in **Vaultwarden**, +fetched via `rbw`/env, **never** written to a file or pasted into chat. Tokens are +**least-privilege** — scoped to their purpose, never admin. + +Note what does *not* need a token: git push/pull (SSH key), and Terraform state +(local — ADR-006). A token for CI / registry use needs only: + +- `read:repository` +- `read:package`, `write:package` (pull/push the Molecule image) + +### 2. Declarative-first, not click-ops + +Forgejo configuration lives in the repo where possible: + +- Actions workflows in `.forgejo/workflows/` (version-controlled). +- Repo/instance settings are codified or documented; changing them ad-hoc via the + API or UI is a **documented exception**, recorded when done — not the norm. + +The point: the Forgejo instance must not become the kind of undocumented drift that +`/review-repo` exists to catch. + +### 3. Automation boundary + +Automation / AI **may**, via the API or CLI: read repo and CI state, manage the +container registry, run and inspect CI. It must **not**, without explicit human +direction: change instance/admin settings, delete repos or packages, or rotate +credentials. + +--- + +## CI pipeline (planned) + +Trunk-based, matching ADR-003 / ADR-008: + +``` +push to main → lint + Molecule → deploy staging → [manual gate] → deploy production +``` + +Runner: `act_runner` on the control node or a dedicated runner VM. Actions is not +yet enabled — see STATUS.md. + +--- + +## What was ruled out + +| Option | Reason | +|---|---| +| Terraform Forgejo HTTP state backend | Forgejo's `/raw/` API is read-only; state can't be written there. Local state instead (ADR-006). | +| Admin-scoped automation tokens | Unnecessary privilege; scope to `read:repository` + `read`/`write:package`. | +| Ad-hoc UI/API configuration as the norm | Becomes undocumented drift; codify or document instead. |