# CLAUDE.md — heleosv2 platform Multi-tenant web hosting platform: **container-per-site + ZFS-dataset-per-site**, CLI-driven provisioning, decoupled per-site backup/restore. Single bare-metal host, Docker Compose per site, no orchestrator. > **Read `docs/` first — it is the source of truth.** Start with > [docs/00-roadmap.md](docs/00-roadmap.md). This file is just the orientation > map so a fresh session gets up to speed cheaply. ## Core philosophy 1. **The customer is the boundary** — isolation, backup, restore align on it. 2. **Two levels:** a `customer` owns many `site`s. Isolation/containers/DB/network are per **site**; grouping (one SFTP login, recursive backup, billing) is per **customer**. See [docs/03-naming-conventions.md](docs/03-naming-conventions.md). 3. **Containers isolate, they don't secure by themselves** — non-root FPM, read-only rootfs, per-site networks, egress filtering, least-priv DB users. 4. **Each backup stream matches its change pattern** — web files via ZFS snapshot/`send`; DB via automysqlbackup + rsync; logs via Loki. Never fold the DB dump into the web dataset (it bloats every incremental). 5. **CLI/templates before UI.** Prove the platform, then wrap it in a panel. ## Status - ✅ Phase 0 — design docs, ADRs, scaffold ([docs/](docs/00-roadmap.md)) - ✅ Phase 1 — host baseline Ansible ([platform-infra/ansible/](platform-infra/ansible/README.md)) - ✅ Phase 2 — platform services (Traefik, MariaDB, Forgejo) in [platform-infra/stacks/](platform-infra/stacks/README.md) - ✅ Phase 3 — site templates & base images in [site-templates/](site-templates/README.md) - ✅ Phase 4 — provisioning CLI `heleosctl` in [control-panel/](control-panel/README.md) (provision/deprovision/list/render/backup/restore; `--dry-run`; 22 tests) - ✅ Phase 5 — backup/DR automation in the `backup` Ansible role (sanoid snapshots, per-DB dump timer, optional syncoid/rsync offsite) - 🚧 Phase 6 — observability (Prometheus/Grafana + cAdvisor/node_exporter/Traefik metrics, Loki/Promtail, Uptime-Kuma) - ⬜ Phases 7–8 — migration, customer panel ### Current focus (Aug 2026) Bringing up Phases 1–5 on a **cost-optimized test VM** (Ubuntu). ZFS is **simulated via a file-backed pool** (`zfs_pool_mode: file`, default) since the VM has no spare disk; the `zfs` role also supports `single`/`mirror` for a real host later. Existing box `korat` (Hetzner, in prod) is NOT the target — a fresh VM is. Next: finish the VM playbook run, then Phase 2 stacks on the VM (or author Phase 6 observability). ### Phase 4 follow-ups (not yet done) - `reconfigure` + `rotate-secret` commands; git-commit of `deployments/` on provision; php-fpm umask for two-way SFTP editing (see control-panel/README.md). ## Repo map | Path | Purpose | |------|---------| | `docs/` | Architecture, ADRs, runbooks, conventions (**authoritative**). | | `platform-infra/ansible/` | Phase 1 host baseline (ZFS, Docker, firewall, SSH). | | `platform-infra/stacks/` | Phase 2 base compose projects (Traefik/MariaDB/Forgejo). | | `site-templates/` | Dockerfiles + per-profile compose templates (Phase 3). | | `deployments/` | Rendered per-site configs `//` (GitOps state). | | `control-panel/` | Provisioning CLI (Phase 4), panel later. | ## Naming quick-ref (see docs/03) - `customer` + `site` ids; `slug = -` (Docker/DB key). - ZFS: `tank/customers///web`; platform on `tank/platform/*`. - DB: `db__` + user `u__` on shared MariaDB. - Networks: shared `proxy` (edge) + `platform` (DB); per-site `_net`. ## Common commands ```bash # Phase 1 — host baseline (from platform-infra/ansible, against the VM) ansible-galaxy collection install -r requirements.yml ansible-playbook site.yml # tags: base|zfs|docker|firewall|ssh # Phase 2 — platform services (on the host, once Phase 1 is applied) platform-infra/stacks/bootstrap-networks.sh cd platform-infra/stacks/ && cp .env.example .env && docker compose up -d ``` ## Working agreements - **Docs are source of truth.** When a design decision changes, update the relevant `docs/` file and add/adjust an ADR in `docs/adr/`. - **Never commit plaintext secrets.** Only `*.enc.*` (SOPS/age) are allowed; `.env` is git-ignored (`.env.example` is committed). See `.gitignore`. - **Line endings:** LF for scripts/Dockerfiles/YAML (enforced by `.gitattributes`) — these run on Linux. - **Commit per phase/step** with a descriptive message; the repo is the memory. - Environment is Windows (authoring) → Linux host (runtime). Prefer the Bash tool for POSIX one-offs.