The official WordPress php-fpm images tag PHP as `phpX.Y` (e.g.
wordpress:php8.3-fpm-alpine), not `X.Y`. The images.wordpress default rendered
`wordpress:8.3-fpm-alpine`, which does not exist ("manifest unknown"). Add the
`php` prefix so the wordpress profile pulls a real image.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| examples | ||
| src/heleos | ||
| tests | ||
| config.example.yaml | ||
| pyproject.toml | ||
| README.md | ||
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, andsystemctl. Use--dry-runto 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.yamlis the source of truth; steps usezfs 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 tosecrets.enc.yamlvia SOPS/age (setsops_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-secretcommands (documented in docs/05) — to add.