platform/control-panel
Bart Van Geyt fe4cbbe36a Phase 4: provisioning CLI (heleosctl)
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>
2026-07-07 17:18:36 +02:00
..
examples Phase 4: provisioning CLI (heleosctl) 2026-07-07 17:18:36 +02:00
src/heleos Phase 4: provisioning CLI (heleosctl) 2026-07-07 17:18:36 +02:00
tests Phase 4: provisioning CLI (heleosctl) 2026-07-07 17:18:36 +02:00
config.example.yaml Phase 4: provisioning CLI (heleosctl) 2026-07-07 17:18:36 +02:00
pyproject.toml Phase 4: provisioning CLI (heleosctl) 2026-07-07 17:18:36 +02:00
README.md Phase 4: provisioning CLI (heleosctl) 2026-07-07 17:18:36 +02:00

control-panel — heleosctl

Provisioning CLI for the platform. Reads a site's site.yaml, renders the site-templates into deployments/, creates the ZFS dataset + database + SFTP account, and runs docker compose up. Implements the flow in 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

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

# 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/<customer>/<site>.
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

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.
  • 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.