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>
83 lines
3.8 KiB
Markdown
83 lines
3.8 KiB
Markdown
# 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](03-naming-conventions.md) §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`](../.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.
|