platform/docs/07-repo-layout-gitops.md
Bart Van Geyt 9f819df4d9 Phase 0: architecture docs, ADRs, and repo scaffold
Establish the design foundation for the heleosv2 multi-tenant hosting
platform before any implementation code:

- Monorepo skeleton: docs/, platform-infra/, site-templates/,
  deployments/, control-panel/ with orientation READMEs.
- docs/: roadmap index, architecture + threat model, naming conventions,
  site profiles, provisioning workflow, backup & DR runbook, repo/GitOps
  layout, and the approved architecture plan.
- docs/adr/: 9 ADRs recording the rationale for single-host Compose,
  Traefik edge, nginx+fpm split, shared MariaDB, ZFS-per-customer,
  decoupled backup streams, Forgejo, CLI-first, and SFTP-only.
- Secrets hygiene: .gitignore (only *.enc.* committed) and .gitattributes
  (LF for scripts/Dockerfiles/YAML run on the Linux host).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 12:26:00 +02:00

3.8 KiB

07 — Repository Layout & GitOps

1. Why a monorepo with clear boundaries

Platform code, image templates, per-customer deployment state, and secrets have different lifecycles and audiences. We keep them in one repository (simple to reason about for a small team) but as strictly separated top-level areas, so they can be split into independent repos later without restructuring.

heleosv2/
├── docs/              # this design set (ADRs, runbooks, conventions)
├── platform-infra/    # Ansible + base compose for host & platform services
├── site-templates/    # Dockerfiles + compose templates per site profile
├── deployments/       # rendered per-customer configs (GitOps state)
└── control-panel/     # provisioning CLI now; customer panel later

platform-infra/

Host baseline and platform services as code:

  • Ansible roles: ZFS pool/datasets, Docker, nftables (Docker-aware), SSH hardening, egress filtering, automysqlbackup, ZFS snapshot/send jobs.
  • Base compose projects: Traefik, shared MariaDB, Forgejo + registry, Prometheus/Grafana/Loki, Uptime-Kuma.

site-templates/

The building blocks the CLI renders from:

  • Dockerfiles for standard images (php-fpm non-root, nginx, static base).
  • One compose template per profile (static, redirect, custom-php, wordpress) with placeholders filled from site.yaml.
  • Image builds run through CI with Trivy + gitleaks; images pushed to the Forgejo registry.

deployments/

The GitOps state — one directory per site (see 03 §7). Rendered docker-compose.yml, .env, encrypted secrets.enc.yaml, and the declarative site.yaml. Committing here is the audit trail of what is deployed. Never commit plaintext secrets.

control-panel/

The provisioning CLI (provision/reconfigure/deprovision/backup/restore/ …). Reads/writes site.yaml, renders from site-templates/, writes to deployments/, and drives ZFS/DB/Docker. The future web panel lives here too, calling the same operations.

2. Secrets

  • SOPS + age encrypt secrets at rest; only *.enc.* / *.sops.* files are committed (enforced by .gitignore).
  • Plaintext .env files are git-ignored; a decrypt step materializes runtime env just before compose up (and it stays out of the customer ZFS dataset).
  • Per-site DB/SFTP credentials are unique and rotatable (rotate-secret).
  • The age private key is an operator secret, stored outside the repo and part of DR (without it, encrypted secrets are unrecoverable — back it up offline).

3. GitOps flow

edit site.yaml ─▶ CLI renders ─▶ commit deployments/<slug> ─▶ compose up -d
     (intent)      (from templates)     (audit trail)          (converge)
  • Source of truth: site.yaml per site + the base compose in platform-infra.
  • Change = a commit in deployments/. History shows who deployed what, when.
  • CI (Forgejo Actions): lint/scan templates and images; optionally validate that deployments/ renders cleanly from site.yaml. Deployment stays operator-triggered on the single host initially (no auto-apply agent yet).

4. Branching

  • main is deployable. Platform/template changes go via short-lived branches + PR + CI (scans must pass).
  • deployments/ commits may be direct on main (operational changes) but still run the render/scan checks.

5. When to split into multiple repos

Split when any becomes true: multiple operators needing different access to deployments/ vs platform code; deployments/ history dominating the repo; or open-sourcing site-templates/control-panel while keeping deployments private. The top-level separation above makes that a clean git filter-repo extraction rather than a rewrite.