# control-panel — heleosctl Provisioning CLI for the platform. Reads a site's `site.yaml`, renders the [site-templates](../site-templates/README.md) into [deployments/](../deployments/README.md), creates the ZFS dataset + database + SFTP account, and runs `docker compose up`. Implements the flow in [docs/05-provisioning-workflow.md](../docs/05-provisioning-workflow.md). > Runs **on the host** (Linux), typically as root, since it drives `zfs`, > `docker`, `useradd`, and `systemctl`. Use `--dry-run` to preview every action > first — nothing touches the system until you drop the flag. ## Install ```bash cd control-panel python -m venv .venv && . .venv/bin/activate pip install -e ".[dev]" # installs the `heleosctl` command + pytest cp config.example.yaml config.yaml && $EDITOR config.yaml ``` Config is found via `--config`, `$HELEOS_CONFIG`, `./config.yaml`, or `/etc/heleos/config.yaml`. ## Usage ```bash # Preview everything (no changes) heleosctl --dry-run provision -f examples/acme-shop.site.yaml # Provision for real heleosctl provision -f examples/acme-shop.site.yaml # Inspect / operate on an existing site (by customer+site or by file) heleosctl list heleosctl render -c acme -s shop # print rendered config heleosctl backup -c acme -s shop # snapshot + DB dump heleosctl restore -c acme -s shop --snapshot auto-20260101-0300 --db /path/dump.sql heleosctl deprovision -c acme -s shop # stop + drop DB, KEEP data heleosctl deprovision -c acme -s shop --purge # also destroy the dataset (after a final backup) ``` ## What each command does | Command | Actions | |---------|---------| | `provision` | ZFS web dataset + ownership → DB + user (if any) → `.env` + SOPS encrypt → render compose/nginx/site.yaml → per-customer SFTP account → `compose up -d`. | | `deprovision` | `compose down` → final backup → drop DB/user → (with `--purge`) destroy dataset + remove config. | | `render` | Print the rendered files without writing (debugging). | | `backup` | ZFS snapshot + `mariadb-dump` into `db-backups//`. | | `restore` | ZFS rollback + import a DB dump. | | `list` | Table of provisioned sites found under the deployments dir. | ## Design notes - **Idempotent & declarative:** `site.yaml` is the source of truth; steps use `zfs create -p`, `CREATE ... IF NOT EXISTS`, and existence checks so re-running converges. A customer's SFTP password is set only when the account is first created. - **Secrets:** the DB password is generated per site, written to a git-ignored `.env`, and encrypted to `secrets.enc.yaml` via SOPS/age (set `sops_age_recipient`). Passwords never appear in command logs (`--redacted`). - **Safety:** all host mutations go through one runner supporting `--dry-run`; data is destroyed only with `--purge`, always after a final backup. ## Layout ``` src/heleos/ cli.py # click commands config.py # Config + Site loading/validation naming.py # slug / db / sftp identifier rules (docs/03) context.py # Site -> template variables render.py # Jinja2 render of a profile runner.py # command + file runner with --dry-run zfs.py database.py secrets.py sftp.py compose.py # host ops provision.py # provision / deprovision orchestration backup.py # snapshot/dump + restore tests/ # pure-logic unit tests (naming, config, render) ``` ## Test ```bash pip install -e ".[dev]" && pytest # or: PYTHONPATH=src pytest ``` ## Known follow-ups - **Web-root umask for php-fpm:** SFTP writes with umask 0002 (group-writable); php-fpm-created files may need the same for two-way SFTP editing. See the note in [site-templates/README.md](../site-templates/README.md). - **git commit of `deployments/`** on provision (GitOps audit trail) — currently left to the operator; wire into the flow when the deployments repo is set up. - **`reconfigure` / `rotate-secret`** commands (documented in docs/05) — to add.