platform/docs/05-provisioning-workflow.md
Bart Van Geyt e5ff798dd4 Refine to nested customer -> site model
Adopt a two-level ownership model: a customer owns many sites, with
isolation/containers/DB/backup at the site level and grouping (one SFTP
login, recursive backup, billing, bulk delete) at the customer level.

- 03-naming-conventions: rewritten for customer/site ids and slug =
  <customer>-<site>; nested ZFS, per-customer SFTP chroot, deployments
  mirror the nesting.
- Propagated paths through docs 01, 04, 05, 06, 07 and deployments/README.
- ADR 0005 retitled/updated to per-site datasets nested under customer.
- architecture-plan.md: note pointing to doc 03 as authoritative on naming.

Clarifies subdomains: same-app subdomains are aliases on one site; a
separate-app subdomain is its own isolated site under the same customer.

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

82 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 05 — Provisioning Workflow
How a site goes from request to live, and back off again. All steps are
operator-driven via the `control-panel` CLI in Phase 07; the customer panel
(Phase 8) calls the same operations.
## Design principle
The CLI is **declarative and idempotent**: `site.yaml` is the source of truth,
everything else is rendered from it, and re-running a command converges to the
desired state rather than erroring. Each step is individually re-runnable so a
failed provision can be resumed.
## New site — `provision`
Input: `customer`, `site`, `profile`, `domains`, profile options (see
[04](04-site-profiles.md) `site.yaml`). The flattened `slug` = `<customer>-<site>`.
1. **Validate** — customer/site charset + reserved-word check
([03](03-naming-conventions.md)), domains resolvable/owned, profile known.
2. **ZFS** — ensure `tank/customers/<customer>` exists, then create
`tank/customers/<customer>/<site>/web` (and `db-backups/<customer>/<site>` if
the profile uses a DB).
3. **Database** (DB profiles) — create `db_<customer>_<site>` +
`u_<customer>_<site>` with least privilege on shared MariaDB; generate
password; write to `secrets.enc.yaml` (SOPS/age).
4. **Render** — produce `deployments/<customer>/<site>/docker-compose.yml` +
`.env` from the profile template and `site.yaml`.
5. **SFTP** — ensure the customer's chrooted `sftp_<customer>` account exists
(chroot `tank/customers/<customer>/`); the new site appears as a sub-folder.
6. **Commit** — commit `deployments/<customer>/<site>/` to the `deployments` repo
(audit trail; secrets committed only in encrypted form).
7. **Deploy**`docker compose up -d` in `deployments/<customer>/<site>/`.
Traefik discovers the route from labels; ACME issues the certificate.
8. **Verify** — HTTPS reachability + valid cert; for WordPress, run WP-CLI
install. Print access details.
DNS: the operator ensures the domain points at the host (pre- or post-provision;
the cert completes once DNS resolves).
## Change a site — `reconfigure`
Edit `site.yaml` (e.g. add an alias domain, bump PHP version, adjust limits),
then re-run: the CLI re-renders, re-commits, and `compose up -d` applies the
delta. TLS for new domains is automatic.
## Remove a site — `deprovision`
1. `docker compose down` (optionally `--remove-orphans`).
2. **Final backup** — take a last ZFS snapshot + final DB dump, retained per the
deprovision retention policy before deletion.
3. Drop `db_<customer>_<site>` + `u_<customer>_<site>` (after the final dump).
4. Remove the site's SFTP sub-folder access; remove the `sftp_<customer>` account
only when the customer has no remaining sites.
5. Destroy `tank/customers/<customer>/<site>` (and `db-backups/<customer>/<site>`)
**after** the retention window — never immediately. Destroy the customer
dataset only when their last site is removed.
6. Remove `deployments/<customer>/<site>/` and commit.
> Destroys are gated: the CLI refuses to delete data younger than the retention
> window without an explicit `--force`, and always snapshots before destroying.
## Backup / restore
Routine backups run on a schedule (not per-command); restore is on demand. Both
are specified in the [Backup & DR runbook](06-backup-and-dr.md). CLI surface:
`backup <customer> <site>` (ad-hoc),
`restore <customer> <site> --snapshot <name> --db <dump>`.
## CLI command summary
| Command | Action |
|---------|--------|
| `provision` | Create a new site end-to-end (steps 18). |
| `reconfigure` | Apply changes from an edited `site.yaml`. |
| `deprovision` | Tear down a site with gated, backed-up deletion. |
| `list` | Show all sites, profiles, status. |
| `backup` | Ad-hoc snapshot + DB dump for a site. |
| `restore` | Two-step restore (files + DB) for a site. |
| `rotate-secret` | Regenerate DB/SFTP credentials for a site. |
Every command is idempotent and logs to the platform log stream (Loki).