Add zfs_pool_mode: file|single|mirror. - file (default): loopback disk image at zfs_pool_file_path — real ZFS with no spare disk, ideal for a cost-optimized test VM; wipes nothing. - single/mirror: whole spare disk(s); mirror for production redundancy. Guard/probe now loops over zfs_pool_disks. Update group_vars, README (modes, prerequisites, safety), and CLAUDE.md current-focus note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
80 lines
4.6 KiB
Markdown
80 lines
4.6 KiB
Markdown
# 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 `<customer>/<site>/` (GitOps state). |
|
||
| `control-panel/` | Provisioning CLI (Phase 4), panel later. |
|
||
|
||
## Naming quick-ref (see docs/03)
|
||
- `customer` + `site` ids; `slug = <customer>-<site>` (Docker/DB key).
|
||
- ZFS: `tank/customers/<customer>/<site>/web`; platform on `tank/platform/*`.
|
||
- DB: `db_<customer>_<site>` + user `u_<customer>_<site>` on shared MariaDB.
|
||
- Networks: shared `proxy` (edge) + `platform` (DB); per-site `<slug>_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/<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 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.
|