Persys Ignite is the external bootstrap and infrastructure reconciliation engine for Persys Cloud. It creates and maintains the foundational infrastructure (control plane and compute nodes) before the cluster can self-manage, and then keeps it converged, healthy, and up to date via GitOps.
Ignite ships as a single static binary (ignite) with no runtime
dependencies beyond git and, depending on your chosen host mode,
systemctl or docker compose.
- Design reference: see the original design doc this implementation follows.
- Upstream repo Ignite watches for cluster specs and, indirectly, component versions: github.com/persys-dev/persys-cloud.
This is a working v1 implementation of the design doc, with a few deliberate, documented deviations from a "day 100" version of the system:
| Design doc component | Status in this release |
|---|---|
| Git Controller | ✅ Implemented (shells out to git; supports commit signature verification via git verify-commit) |
| Declarative Config Parser + validation | ✅ Implemented (internal/config) |
| State Manager | ✅ Implemented, embedded JSON store instead of SQLite (see ARCHITECTURE.md) |
| Reconciler | ✅ Implemented (internal/reconciler) |
| Service catalog (control plane + backing services) | ✅ Implemented: parses persys-cloud's own infra/docker/docker-compose.yml dynamically (internal/catalog) — no service list is hardcoded in Ignite; adding a service to persys-cloud requires zero Ignite code changes. See CONTROL_PLANE.md. |
| Host Adapter — systemd | ✅ Implemented: every catalog service (built-from-source or third-party image) becomes its own systemd unit, no docker-compose involved |
| Host Adapter — container | ✅ Implemented: runs docker compose -f infra/docker/docker-compose.yml up -d --build exactly, unmodified |
| Host Adapter — VM | ✅ Implemented: provisions a libvirt VM via cloud-init, then deploys the same compose file inside it via Ansible |
| Provider Engine — bare-metal (compute pool scaling, separate from the local control plane above) | anvil-mock (in-memory simulation, for dev/test) and ansible (drives statically-declared hosts). See ANSIBLE_PROVISIONING.md. |
| Provider Engine — libvirt (compute-pool VM provisioning) | |
| Secret Manager | ✅ Implemented: local file-backed store, plus a minimal Vault KV-v2 HTTP client (no Vault SDK dependency) |
| Recovery Manager | ✅ Implemented: tar+gzip backup/restore, with automatic use of the external backupeer tool if present in PATH |
| Webhook Server | ✅ Implemented (HMAC-SHA256, GitHub/GitLab compatible) |
HTTP API (/healthz, /status, /metrics, ...) |
✅ Implemented, including per-service health regardless of host mode |
# 1. Build
make build # -> bin/ignite
# 2. Check the host and pick a deployment mode -- writes .ignite-plan.yaml
bin/ignite plan
# 3. Scaffold a cluster (creates ./persys-cloud, cloning --repo-url into it
# since nothing's checked out yet, and uses the plan's recommended
# spec.host.mode)
bin/ignite init --name my-cluster --repo-url git@github.com:you/persys-cloud.git
cd persys-cloud && git add cluster.yaml && git commit -m "add ignite cluster spec" && git push
cd ..
# 4. Install on the bootstrap host, reusing the ignite.yaml init just wrote
sudo install -m 0755 bin/ignite /usr/local/bin/ignite
sudo mkdir -p /etc/persys-ignite
sudo cp persys-cloud/ignite.yaml /etc/persys-ignite/ignite.yaml
# 5. Bootstrap, then run the daemon
sudo ignite bootstrap
sudo ignite serveignite serve reconciles continuously (default every 60s, configurable via
spec.gitops.reconcileInterval), and immediately when a GitOps webhook
fires against /webhook. Check convergence with:
curl http://127.0.0.1:9091/healthz # liveness
curl http://127.0.0.1:9091/readyz # readiness: has it converged at least once?
curl http://127.0.0.1:9091/status # desired vs actual, per compute pool
curl http://127.0.0.1:9091/metrics # Prometheus-format metricsSee GETTING_STARTED.md for a fuller walkthrough, including running as a systemd service.
Ignite reads persys-cloud's own infra/docker/docker-compose.yml from the
cloned repo as a dynamic service catalog and deploys whatever it
declares — persys-gateway, persys-scheduler, compute-agent, vault,
etcd, redis, mongodb, coredns, or anything added later — with zero
Ignite code changes required when persys-cloud adds a service.
spec.host.mode picks how:
systemd— every service becomes its own systemd unit (built from source for Go services,docker runsupervised directly by systemd for third-party images like Vault/etcd/Mongo). No docker-compose involved.container— runsdocker compose -f infra/docker/docker-compose.yml up -d --buildexactly, unmodified.vm— provisions a local VM and runs the same compose file inside it.
These are strictly separate — picking one never mixes in another's mechanism. Full details: docs/CONTROL_PLANE.md.
The above covers the control plane on a single host. Separately, scaling
compute-agent across many additional bare-metal/VM nodes uses the
Provider Engine, selected via spec.providers.baremetal:
anvil-mock(default) — an in-memory simulated Anvil. Nodes move throughDiscovered → Provisioning → Readyautomatically. Zero external dependencies; ideal for local development, demos, and CI. It never touches real infrastructure.ansible— the recommended path for real clusters today. You declare real, already-provisioned hosts underspec.computePools[].nodes(hostname/IP + SSH details); Ignite checks reachability and runs the bundled Ansible playbook (ansible/playbook-compute-agent.yml) to install, configure, and startcompute-agenton them. See docs/ANSIBLE_PROVISIONING.md for the full workflow, including a fully-manual (no-Ansible) alternative.
Neither backend does hardware discovery or zero-touch PXE provisioning —
that's genuinely Anvil's job, and provisioning the physical/virtual
machines themselves (bare metal, cloud VM, whatever) remains the operator's
responsibility until Anvil ships. What Ignite owns today is everything
after a machine exists: getting compute-agent installed, configured,
running, and kept up to date on it.
ignite plan Preflight-check this host and recommend a spec.host.mode
ignite init Scaffold a new Git repo with example manifests
ignite bootstrap Run the initial cluster bootstrap (idempotent)
ignite serve Run the reconciliation + webhook daemon
ignite apply Run a single manual reconciliation pass
ignite status Show desired vs actual state
ignite restore Disaster recovery: restore from a backup archive
ignite upgrade Trigger a GitOps-driven component upgrade check
ignite backup Take an on-demand state backup
ignite version Print version information
Full flag reference: docs/CLI.md.
- ARCHITECTURE.md — component-by-component mapping to the design doc, and where this implementation deviates and why.
- CONTROL_PLANE.md — how the dynamic service catalog works and how each host mode (systemd/container/vm) deploys it.
- GETTING_STARTED.md — install, bootstrap, and operate a cluster end to end.
- CONFIGURATION.md — full
cluster.yaml/ignite.yamlfield reference. - ANSIBLE_PROVISIONING.md — the interim bare-metal workflow (Ansible-driven and fully-manual options).
- API.md — HTTP API reference.
- CLI.md — CLI flag reference.
Requires Go 1.22+.
make build # bin/ignite
make test
make vetThe only external Go dependency is github.com/goccy/go-yaml. This is
intentional: heavier alternatives (Cobra, go-git, a SQLite driver) pull in
golang.org/x/* and other vanity-import dependency chains that some
locked-down build environments can't fetch, so the CLI, git plumbing, and
state store are built on the standard library plus a few os/exec calls to
git/systemctl/docker compose/ansible-playbook instead.
Same license as the parent persys-cloud project.