Hermes Agent 883e264c42
Some checks failed
build-and-deploy / test (push) Failing after 2m36s
build-and-deploy / build-deploy (push) Has been skipped
chore: seed gitea-repo-template cookie-cutter
Reusable repo template for homelab services on git.aridgwayweb.com.

Bakes in (all verified live on armistace/wedding-photos):
- Hard commit guard: shared pre-commit hook (git config core.hooksPath
  ~/dev/git-hooks) + master branch protection (push whitelist [armistace],
  merge whitelist [hermes, armistace]).
- Gitea Actions CI (.gitea/workflows/build_push.yml): test + build-deploy,
  persistent remote buildkit cache, registry push, idempotent deploy that
  preserves hand-provisioned Secrets, cluster injection from repo secrets/vars
  via scripts/reconcile-cluster-inject.sh.
- Persistent buildkit cache (ci/buildkit/): single-replica Longhorn backing.
- scripts/reconcile-cluster-inject.sh: reconcile live Secret/ConfigMap from
  Gitea secrets/vars without clobbering hand-provisioned values.
- RUNBOOK.md: handoff-complete ops doc.

Placeholders (<APP> <OWNER> <NS> <KEY_*>) are filled per-service on repo creation.
2026-09-24 11:45:57 +10:00

141 lines
6.0 KiB
Markdown

# Gitea Repo Template — Operations Runbook
This is the **cookie-cutter** for new homelab repos. On Gitea you create a new
repo **from this template** and it copies `.gitea/`, `kube/`, `ci/buildkit/`,
`scripts/`, and this RUNBOOK. Fill in the `<PLACEHOLDERS>` per service, then
follow the bootstrap flow below. Everything documented here is proven live on
`armistace/wedding-photos`.
---
## 1. Create a repo from this template
- Gitea UI: `+` → *New Repository* → **"Copy to new repository"** source = `hermes/gitea-repo-template`.
- Bot/automation: `POST /api/v1/repos/{owner}/{repo}/generate` (See `~/.hermes/scripts/gitea-bootstrap-new-repo.sh`).
After creation, commit+push a branch filling in:
- `.gitea/workflows/build_push.yml` → `<APP>`, `<OWNER>`, `<NS>`, `<KEY_*>`
- `scripts/reconcile-cluster-inject.sh` → same placeholders
- `kube/*` , `ci/buildkit/*` → your image/service names
- This RUNBOOK's placeholders → actual namespace/app
---
## 2. One-time per-repo setup (do this once, after the first commit)
### 2.1 Repo secrets + vars (Gitea Settings → Actions/secrets)
Set on the repo (or reuse the global `armistace`-scoped ones):
- `KUBEC_CONFIG_BUILDX_NEW_2` — kubeconfig for the cluster (buildx secret)
- `REG_PASSWORD` — Gitea registry password for `<OWNER>`
- every `<KEY_*>` referenced by `reconcile-cluster-inject.sh` SECRET_KEYS
- vars for CFG: e.g. `DB_HOST`, `S3_ENDPOINT_URL`, etc.
### 2.2 Branch protection (hard guard, server-side)
Created automatically by the bootstrap script (agent runs it with the bot token
on `hermes/`-owned repos). Rule on `master`:
- `enable_push + enable_push_whitelist=true`, push whitelist `[armistace]`
→ only Andrew can DIRECT-push master; agent direct push is blocked even with `--no-verify`
- `enable_merge_whitelist=true`, merge whitelist `[hermes, armistace]`
→ agent can still MERGE PRs (merge ≠ direct push)
### 2.3 Local pre-commit guard
Point the repo at the shared git hook so it blocks direct-to-master commits:
```bash
git config core.hooksPath ~/dev/git-hooks
```
(Shared hook lives at `~/dev/git-hooks/pre-commit` on the control host; the
master-guard binary is bundled in the `gitea-pr-workflow` skill.)
---
## 3. Architecture & storage
### 3.1 Cluster injection via Gitea repo secrets + vars
`scripts/reconcile-cluster-inject.sh` reconciles the live Secret/ConfigMap each
deploy with three rules (priority order):
1. Gitea supplies a **non-empty** value → that value wins (seed / rotate).
2. Key **missing** from the live resource → write placeholder (env ref always resolves).
3. Otherwise → **preserve** the live value (hand-provisioned creds never clobbered).
The patch is built by Python `json.dumps` (no shell interpolation). Add every
key the deployments reference to `SECRET_KEYS` / `CM_KEYS`; list new repo
secrets/vars here so a handoff never loses them.
### 3.2 Persistent buildkit cache (optional, durable CI cache)
`ci/buildkit/` installs a single-replica Longhorn-backed buildkit daemon so CI
image builds reuse cache. Manifests are shipped but NOT auto-applied — apply
once:
```bash
kubectl apply -f ci/buildkit/01-storageclass.yaml
kubectl apply -f ci/buildkit/02-statefulset.yaml
kubectl apply -f ci/buildkit/03-service.yaml
```
The remote-driver workflow points at `buildkit.gitea-runner.svc:1234`.
Disk watchdog + revert scripts live on the control host
(`~/.hermes/scripts/buildkit-cache-monitor.sh`, `buildkit-re-enable.sh`) — NOT
in this repo (ops tooling doesn't go in app repos).
---
## 4. Deploying / re-deploying from scratch
### 4.1 Provision the namespace + live Secret ONCE by hand
CI will `kubectl apply` manifests and reconcile the Secret, but the FIRST seed
of real credentials is manual (CI only writes placeholders when a key is absent):
```bash
kubectl create namespace <NS>
kubectl create secret docker-registry regcred \
--docker-server=git.aridgwayweb.com --docker-username=<OWNER> \
--docker-password='<REG_PASSWORD>' --docker-email=<OWNER>@aridgwayweb.com \
-n <NS> --dry-run=client -o yaml | kubectl apply -f -
kubectl -n <NS> create secret generic <NS>-secret \
--from-literal=<KEY_1>='<real-value>' \
--from-literal=<KEY_2>='<real-value>'
```
### 4.2 Set the ConfigMap
```bash
kubectl -n <NS> apply -f kube/<NS>_configmap.yaml
```
### 4.3 After CI deploy — verify
```bash
kubectl -n <NS> get pods
kubectl -n <NS> rollout status deployment/<APP>-backend --timeout=180s
```
---
## 5. Common operations
- **Rotate a secret**: update the Gitea repo secret → push an empty commit or
re-run `workflow_dispatch` → reconcile applies the new value → `kubectl -n <NS> rollout restart deployment/<APP>-backend`.
- **If pods CrashLoopBackOff after a secret change**: the env ref is likely
unresolvable — check `kubectl get event` / secret keys; add the missing key to
`reconcile-cluster-inject.sh` SECRET_KEYS and the workflow export block.
- **Never delete the namespace** on deploy (CI does not; don't start) — it
destroys the live Secret.
---
## 6. Troubleshooting
- **Build-deploy fails ~36s, "no remote endpoint provided"**: the buildx
`endpoint` was put under `driver-opts`. Move it to a top-level `with:` input.
- **Multi-arch build OOMs/crashes the buildkit pod**: build `linux/amd64` only.
- **`COPY --from=ghcr.io/...` TLS timeout**: buildkit can't reach ghcr.io. Use
an ECR base + `pip install uv`.
- **PR files API returns empty `patch` fields**: build review payload from local
`git diff` — never trust the API `patch`.
---
## 7. Handoff checklist
- [ ] All `<PLACEHOLDERS>` filled (workflow, reconcile script, kube/, this RUNBOOK)
- [ ] Repo secrets + vars set; keys match `reconcile-cluster-inject.sh` + workflow `export` block
- [ ] Branch protection on `master` enabled (push whitelist `[armistace]`, merge whitelist `[hermes, armistace]`)
- [ ] `git config core.hooksPath ~/dev/git-hooks` set in the new repo
- [ ] First deploy: namespace seeded with a real Secret (RUNBOOK §4.1) before relying on CI reconcile
- [ ] Any new env key added to §3.1 table + §2.1 list so a handoff doesn't lose it