From de26a53d4b449df5413a9ca0eff4d0a286f02eb4 Mon Sep 17 00:00:00 2001 From: Keith Jackson Date: Sat, 15 Aug 2026 22:40:11 -0500 Subject: [PATCH] =?UTF-8?q?Establish=20dev=20workflow=20(feature-branch?= =?UTF-8?q?=E2=86=92main)=20and=20pull=20Gitea=20bootstrap=20earlier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add CONTRIBUTING.md: interim local-git rules (no work on main; every change a feature branch merged into main) and the target state (Gitea-hosted repos, GitOps source cutover, CI/CD via Gitea Actions running the Kyverno gate + Chainsaw conformance Job, PR merge gate). Note the self-hosted-git bootstrapping circularity + off-cluster mirror/backup mitigation. - AGENTS.md: add section 12 (version-control workflow). - docs/05: source-of-truth migration (local bootstrap -> self-hosted Gitea). - docs/13 + roadmap: reprioritize Gitea as bootstrapping-critical, onboarded early (WS-APPS step 2); nextcloud onboarding split out. - task-board: T-6.1a (gitea early), T-6.1b (source+CI migration to Gitea). Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 13 +++++++ CONTRIBUTING.md | 64 ++++++++++++++++++++++++++++++++++ README.md | 1 + agents/task-board.md | 4 ++- docs/05-gitops-and-delivery.md | 22 ++++++++++++ docs/13-service-catalog.md | 8 ++++- roadmap/phases.md | 3 ++ roadmap/workstreams.md | 17 ++++++--- 8 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/AGENTS.md b/AGENTS.md index 5c526e5..1bbbf63 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -146,3 +146,16 @@ This is a hard boundary (R-TEST-06): The point: the specification is the fixed target. Agents move the implementation and its tests toward the target; they never move the target. + +## 12. Version-control workflow + +Full detail in [`CONTRIBUTING.md`](CONTRIBUTING.md). The rules that bind every change: + +- **No work on `main`.** `main` holds only completed, reviewed work. +- **Every change is a feature branch** (`feature/…`, `fix/…`, `docs/…`, `chore/…`), **merged into + `main`** when complete (and, for implementation, when its conformance tests pass). +- **Local git for now; Gitea soon.** There is no remote host on this network yet — the prior + `home-network` Gitea is gone from this network. **Gitea is one of the first services brought online + on the cluster**, after which repos, the GitOps source, and CI/CD move to Gitea (Gitea Actions + running the Kyverno gate + Chainsaw conformance Job, with PRs as the enforced merge gate). +- **Never commit plaintext secrets** (SOPS-encrypted only, [`docs/07-security-and-secrets.md`](docs/07-security-and-secrets.md)). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2df3cc6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,64 @@ +# Contributing & Development Workflow + +How work is version-controlled for this repository. This complements the governance rules in +[`AGENTS.md`](AGENTS.md) (what agents may/may not change) — this file covers *how changes land in +git*. + +## Interim state (now): local git, feature branches → main + +There is **no remote git host on this network today**. (The previous `home-network` Gitea instance is +gone from this network; the infrastructure it built still runs but is not reachable here.) So for now: + +- **Local git only.** The repository lives on the workstation; there is no push target yet. +- **No work on `main`.** `main` holds only reviewed, completed work. Do **not** commit work-in- + progress directly to `main`. +- **Every change is a feature branch.** Create `feature/` (or `fix/…`, `docs/…`) + for any unit of work, commit there, then **merge into `main`** when the work is complete and — for + implementation work — its conformance tests pass ([`agents/definition-of-done.md`](agents/definition-of-done.md)). +- **Merge to `main` = "done".** Keep `main` always in a coherent, mergeable state. + +### Branch naming +- `feature/` — new capability or spec area +- `fix/` — corrections +- `docs/` — documentation-only changes +- `chore/` — tooling/repo housekeeping + +### Commit hygiene +- Small, single-responsibility commits; imperative subject lines. +- Reference requirement IDs / ADR numbers / task-board IDs where relevant (they're stable across + file moves). +- **Never commit plaintext secrets** — secrets are SOPS-encrypted only ([`docs/07-security-and-secrets.md`](docs/07-security-and-secrets.md)); + the [`.gitignore`](.gitignore) guards common leak paths but is not a substitute for care. + +## Target state (soon): Gitea-hosted, CI/CD-driven GitOps + +The platform will **host its own git**. **Gitea is one of the first services brought online on the +cluster** ([`docs/13-service-catalog.md`](docs/13-service-catalog.md), roadmap Phase 6 is pulled +early — see [`roadmap/phases.md`](roadmap/phases.md)). Once Gitea is up and stable, the workflow +shifts: + +1. **Repos move to Gitea** (`gitea.home.keithjackson.dev`). This local repo is pushed up as the + origin of record. +2. **GitOps source migrates** from local/bootstrap to Gitea: Flux reconciles the cluster from Gitea + ([`docs/05-gitops-and-delivery.md`](docs/05-gitops-and-delivery.md)). +3. **CI/CD runs in Gitea** (Gitea Actions): the Kyverno L0 policy gate and the trigger for the + in-cluster Chainsaw conformance Job run as pipelines on push/PR + ([`docs/14-testing-and-conformance.md`](docs/14-testing-and-conformance.md)); Renovate opens + update PRs ([`docs/11-automated-updates.md`](docs/11-automated-updates.md)). +4. **Pull requests become the merge gate** into `main`, with CI (policy + conformance) required + green before merge — the same no-work-in-main rule, now enforced by the platform instead of by + convention. + +### The bootstrapping caveat (self-hosted git is circular) +Gitea runs *on* the cluster that Flux reconciles *from* Gitea — a chicken-and-egg. Handling: +- Bring the cluster up from a **bootstrap source** (this local repo / an out-of-band source), stand + up Gitea, then **cut Flux's source over to Gitea**. +- Because a cluster loss would take its own git host with it, **Gitea is DR-critical**: its repos are + backed up **off-cluster** ([`docs/04-storage-and-state.md`](docs/04-storage-and-state.md)), and an + off-cluster mirror of the platform repo is kept so the platform can always be rebuilt from a source + that outlives the cluster. + +## What does not change +- Specs remain **human-owned and immutable to agents** ([`AGENTS.md` §11](AGENTS.md)) regardless of + where git is hosted. +- "Done" remains a **green, reproducible conformance test**, not "merged." diff --git a/README.md b/README.md index 47144bd..b4d9959 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ GitOps configuration promotes to production with only environment-scoped overrid home-network-k3s/ ├── README.md ← you are here ├── AGENTS.md ← rules of engagement for AI agents +├── CONTRIBUTING.md ← git workflow (feature branches → main; Gitea/CI-CD to come) ├── GLOSSARY.md ← shared vocabulary (read to avoid ambiguity) ├── docs/ ← the specification, by domain │ ├── 00-vision-and-scope.md diff --git a/agents/task-board.md b/agents/task-board.md index 72552cd..803dffa 100644 --- a/agents/task-board.md +++ b/agents/task-board.md @@ -74,11 +74,13 @@ Seeded from [`../roadmap/phases.md`](../roadmap/phases.md) + [`../roadmap/workst | ID | Task | WS | Serves | Blocked-by | Owner | Status | |---|---|---|---|---|---|---| | T-6.1 | Onboard **pihole** (DNS underpinning) | WS-APPS | R-DNS-03 | T-2.5 | — | todo | +| T-6.1a | Onboard **gitea** early (bootstrapping-critical; self-hosts GitOps source + CI/CD) | WS-APPS | R-STATE-03 | T-4.4 | — | todo | +| T-6.1b | Migrate GitOps source local→Gitea; move CI to **Gitea Actions** (Kyverno gate + Chainsaw Job trigger; PR merge gate); off-cluster repo mirror | WS-GITOPS | R-GITOPS-01, R-TEST-03 | T-6.1a | — | todo | | T-6.2 | Onboard **plex** (bulk tier + public exposure) | WS-APPS | R-STATE-04 | T-2.6, T-6.1 | — | todo | | T-6.3 | Onboard **qbittorrent, jackett** (VPN + kill-switch) | WS-APPS | R-NET-05/06 | T-3.4 | — | todo | | T-6.4 | Onboard **sonarr, radarr** (VPN + arr↔downloader allows) | WS-APPS | R-NET-02/05 | T-6.3 | — | todo | | T-6.5 | Onboard **seerr** (allows to arr + plex) | WS-APPS | R-NET-02 | T-6.2, T-6.4 | — | todo | -| T-6.6 | Onboard **nextcloud, gitea** (DR-critical + backups) | WS-APPS | R-STATE-03 | T-4.4 | — | todo | +| T-6.6 | Onboard **nextcloud** (DR-critical + backups) | WS-APPS | R-STATE-03 | T-4.4 | — | todo | | T-6.7 | Onboard **minecraft, unifi, semaphore, kasm** | WS-APPS | app reqs | T-2.6 | — | todo | | T-6.8 | C2 UI: Homepage root + Grafana + Headlamp + Flux status + auth | WS-C2 | R-C2-01/02 | T-4.2, T-6.* | — | todo | diff --git a/docs/05-gitops-and-delivery.md b/docs/05-gitops-and-delivery.md index 2f30849..e035119 100644 --- a/docs/05-gitops-and-delivery.md +++ b/docs/05-gitops-and-delivery.md @@ -80,6 +80,28 @@ Full detail and the "is an automated commit a GitOps change?" nuance is in - Because desired state is in git and data is on Longhorn/backups, a rebuilt cluster reconverges by re-bootstrapping Flux and restoring volumes ([04](04-storage-and-state.md)). +## Source-of-truth: local bootstrap → self-hosted Gitea + +The platform hosts its own git, which creates a deliberate bootstrapping sequence +([`../CONTRIBUTING.md`](../CONTRIBUTING.md)): + +1. **Bootstrap source.** Initially there is no remote git on this network (the prior `home-network` + Gitea is gone from here). Development is local; the cluster is first reconciled from a + **bootstrap source** (this local repo / an out-of-band source). +2. **Gitea comes online early.** **Gitea is one of the first services onboarded** ([13](13-service-catalog.md)) + so the platform gains its own git host at `gitea.home.keithjackson.dev`. +3. **Cut Flux over to Gitea.** Once Gitea is stable, Flux's `GitRepository` source is migrated from + the bootstrap source to Gitea; the platform then reconciles from its own git. +4. **CI/CD moves to Gitea Actions.** The Kyverno L0 policy gate and the trigger for the in-cluster + Chainsaw conformance Job ([14](14-testing-and-conformance.md)) run as Gitea Actions pipelines on + push/PR; PRs become the enforced merge gate into `main`. + +**Circularity & DR.** Gitea runs *on* the cluster Flux reconciles *from* Gitea. Mitigations: +- Keep an **off-cluster mirror** of the platform repo so a source-of-truth exists that outlives the + cluster. +- Treat **Gitea as DR-critical**: its repos/DB are backed up off-cluster ([04](04-storage-and-state.md)), + so a cluster loss never loses the git history needed to rebuild the platform. + ## Guardrails - No `kubectl apply` of app state by hand — everything flows through git/Flux (R-GITOPS-01, click- ops-never per [AGENTS.md](../AGENTS.md)). diff --git a/docs/13-service-catalog.md b/docs/13-service-catalog.md index 2509540..0124969 100644 --- a/docs/13-service-catalog.md +++ b/docs/13-service-catalog.md @@ -75,7 +75,7 @@ Egress and dependencies below are **normative intent**; implementers render the | 9 | **unifi** | container | internal | internet | config: longhorn | — | UniFi network controller | | 10 | **semaphore** | container | internal | internet | config/db: longhorn | — | Ansible UI | | 11 | **kasm** | container | internal | internet | config: longhorn | — | Kasm Workspaces (may need more resources; container-first, VM if required) | -| 12 | **gitea** | container | public | internet | repos/db: longhorn (irreplaceable → backed up) | — | Git hosting; DR-critical | +| 12 | **gitea** | container | public | internet | repos/db: longhorn (irreplaceable → backed up) | — | Git hosting; **bootstrapping-critical** — self-hosts the platform's GitOps source + CI/CD; onboarded **early** (see below); DR-critical (off-cluster backup + mirror) | | 13 | **qbittorrent** | container | internal | **vpn** | config: longhorn; downloads: **bulk** | ← sonarr, ← radarr | VPN + kill-switch; download client | **VPN-class set (R-NET-05/06):** sonarr, radarr, jackett, qbittorrent — all egress via VPN with an @@ -103,6 +103,12 @@ and inherit all guarantees automatically — that is the point of the abstractio ([09](09-dns-and-ingress.md)); everything else stays `internal`. - **Kasm** is resource-heavy and may be a candidate for `kind: vm` if container mode proves insufficient — the ServiceDefinition makes that a one-field change. +- **Gitea is bootstrapping-critical.** The platform self-hosts its git and CI/CD, so Gitea is brought + online **early** (WS-APPS step 2, right after PiHole). Once stable, the GitOps source and CI move + from the local bootstrap source to Gitea + Gitea Actions ([05](05-gitops-and-delivery.md), + [`../CONTRIBUTING.md`](../CONTRIBUTING.md)). Because a cluster loss would take its own git host with + it, Gitea's repos/DB are backed up **off-cluster** and mirrored, so the platform can always be + rebuilt from a source that outlives the cluster. ## Acceptance gates - Each service is fully wired from its ServiceDefinition alone (compute, storage, policy, egress, diff --git a/roadmap/phases.md b/roadmap/phases.md index 236012e..8720e19 100644 --- a/roadmap/phases.md +++ b/roadmap/phases.md @@ -77,6 +77,9 @@ survives rebuild; default-deny + `egress` apply to the VM; guest auto-updates. ## Phase 6 — Service Catalog Onboarding + C2 UI **Entry:** Phases 2–4 done. **Do:** onboard the 13 services via ServiceDefinitions in dependency order (see workstream WS-APPS); +**bring Gitea online early in this phase** and cut the GitOps source + CI/CD over to it (Gitea +Actions running the Kyverno gate + Chainsaw conformance Job; PRs as the enforced merge gate — +[`../docs/05-gitops-and-delivery.md`](../docs/05-gitops-and-delivery.md), [`../CONTRIBUTING.md`](../CONTRIBUTING.md)); stand up the C2 UI (Homepage root + Grafana + Headlamp + Flux status). **Exit (demonstrate):** each service fully wired from its ServiceDefinition (compute/storage/policy/ egress/TLS/DNS/monitoring/updates) with default-deny + correct egress; the arr↔qbittorrent↔jackett↔ diff --git a/roadmap/workstreams.md b/roadmap/workstreams.md index 33a677c..9ab3938 100644 --- a/roadmap/workstreams.md +++ b/roadmap/workstreams.md @@ -50,10 +50,19 @@ only via [`../agents/coordination-protocol.md`](../agents/coordination-protocol. - WS-C2 is last-ish (Phase 6) since it aggregates everything. ## WS-APPS onboarding order (respect dependencies from [13](../docs/13-service-catalog.md)) -1. **pihole** (DNS underpinning) → 2. **plex** (largest; validates bulk tier + public exposure) → -3. **qbittorrent, jackett** (VPN + kill-switch proof) → 4. **sonarr, radarr** (VPN + arr↔downloader -allows) → 5. **seerr** (cross-service allows to arr + plex) → 6. **nextcloud, gitea** (DR-critical -data + backups) → 7. **minecraft, unifi, semaphore, kasm** (remaining). +1. **pihole** (DNS underpinning) → +2. **gitea** — **bootstrapping-critical; brought online early** so the platform self-hosts its git + and CI/CD; after it is stable, WS-GITOPS cuts Flux's source over to Gitea and moves CI to Gitea + Actions ([`../docs/05-gitops-and-delivery.md`](../docs/05-gitops-and-delivery.md), + [`../CONTRIBUTING.md`](../CONTRIBUTING.md)). DR-critical → off-cluster backup + mirror. → +3. **plex** (largest; validates bulk tier + public exposure) → +4. **qbittorrent, jackett** (VPN + kill-switch proof) → 5. **sonarr, radarr** (VPN + arr↔downloader +allows) → 6. **seerr** (cross-service allows to arr + plex) → 7. **nextcloud** (DR-critical data + +backups) → 8. **minecraft, unifi, semaphore, kasm** (remaining). + +**GitOps source migration** (WS-GITOPS, tied to step 2): local bootstrap source → self-hosted Gitea; +CI (Kyverno gate + Chainsaw conformance Job trigger) moves to Gitea Actions; PRs become the enforced +merge gate into `main`. Each onboarding is a full acceptance-gate pass ([`../agents/definition-of-done.md`](../agents/definition-of-done.md)), not just a merged manifest.