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>
82 lines
3.9 KiB
Markdown
82 lines
3.9 KiB
Markdown
# 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 0–7; 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 1–8). |
|
||
| `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).
|