Python control-panel package driving the full provisioning flow from a site's site.yaml (docs/05): - provision: ZFS web dataset + ownership, per-site DB + least-priv user, generated .env encrypted to secrets.enc.yaml (SOPS/age), render the profile templates + persist site.yaml, per-customer chrooted SFTP account, docker compose up. - deprovision (gated: data destroyed only with --purge, after a final backup), backup (ZFS snapshot + mariadb-dump), restore (rollback + import), render (preview), list. Design: one command/file runner with a real --dry-run (prints every action, redacts secrets); idempotent steps; Config + Site validation mirroring docs/03; passwords never logged. Modules: cli, config, naming, context, render, runner, zfs, database, secrets, sftp, compose, provision, backup. Plus pyproject (heleosctl entry point), config.example.yaml, an example site, and a README. Tests: 22 pure-logic unit tests (naming, config validation, template render across all profiles + db on/off) — all passing. Full provision and deprovision verified end-to-end in --dry-run. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4 KiB
4 KiB
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. This file is just the orientation map so a fresh session gets up to speed cheaply.
Core philosophy
- The customer is the boundary — isolation, backup, restore align on it.
- Two levels: a
customerowns manysites. Isolation/containers/DB/network are per site; grouping (one SFTP login, recursive backup, billing) is per customer. See docs/03-naming-conventions.md. - Containers isolate, they don't secure by themselves — non-root FPM, read-only rootfs, per-site networks, egress filtering, least-priv DB users.
- 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). - CLI/templates before UI. Prove the platform, then wrap it in a panel.
Status
- ✅ Phase 0 — design docs, ADRs, scaffold (docs/)
- ✅ Phase 1 — host baseline Ansible (platform-infra/ansible/)
- ✅ Phase 2 — platform services (Traefik, MariaDB, Forgejo) in platform-infra/stacks/
- ✅ Phase 3 — site templates & base images in site-templates/
- ✅ Phase 4 — provisioning CLI
heleosctlin control-panel/ (provision/deprovision/list/render/backup/restore;--dry-run; 22 tests) - 🚧 Phase 5 — backup/DR automation (ZFS snapshot/
send+ automysqlbackup jobs, offsite, restore drill) - ⬜ Phases 6–8 — observability, migration, panel
Phase 4 follow-ups (not yet done)
reconfigure+rotate-secretcommands; git-commit ofdeployments/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 <customer>/<site>/ (GitOps state). |
control-panel/ |
Provisioning CLI (Phase 4), panel later. |
Naming quick-ref (see docs/03)
customer+siteids;slug = <customer>-<site>(Docker/DB key).- ZFS:
tank/customers/<customer>/<site>/web; platform ontank/platform/*. - DB:
db_<customer>_<site>+ useru_<customer>_<site>on shared MariaDB. - Networks: shared
proxy(edge) +platform(DB); per-site<slug>_net.
Common commands
# 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/<svc> && 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 indocs/adr/. - Never commit plaintext secrets. Only
*.enc.*(SOPS/age) are allowed;.envis git-ignored (.env.exampleis 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.