Bootstrap utility server for the flatops Kubernetes cluster. Runs Fedora image-mode (bootc), managed via GitOps — push to main builds and publishes a new OS image to ghcr.io.
| Service | Purpose |
|---|---|
| Talos discovery service | Node discovery for Talos cluster bootstrap (ds.etincelle.cloud) |
| Talos image factory | Builds custom Talos OS images for cluster nodes (tif.etincelle.cloud) |
| Distribution registry | OCI registry used as image factory artifact cache (registry.etincelle.cloud) |
| Caddy | Reverse proxy with automatic TLS via Cloudflare DNS-01 |
| Beszel agent | System and container metrics agent reporting to an external hub |
| Prometheus node exporter | Host metrics on :9100 (default collectors) |
| Trove agent | Read-only container inventory agent reporting to an external server |
Produces a bootable qcow2 from the current Containerfile via bootc-image-builder. Output lands at output/qcow2/disk.qcow2 (10 GiB virtual, ~1.2 GiB sparse).
Prerequisites:
-
podman,qemu-img,go-task -
Linux: SELinux-enforcing hosts need
osbuild-selinuxinstalled. The build runs rootless (nosudo) using bootc-image-builder's--in-vmKVM mode, so/dev/kvmmust be accessible to the invoking user. -
macOS: rootful
podman machineis required (rootless--in-vmcannot reach KVM inside Apple's hypervisor):podman machine init --cpus 2 --memory 2048 --disk-size 40 podman machine set --rootful podman machine start
Build:
task bakeThe bake task pulls ghcr.io/jfroy/etincelle:latest for the host architecture (amd64 or arm64; the GitHub Actions workflow publishes both) and builds a matching qcow2.
One-time install on a fresh VM:
-
(Optional) Resize the qcow2 before first boot. The image ships at 10 GiB; the root partition auto-grows to fill the disk on first boot via
systemd-growfs, but the underlying disk must be enlarged first:qemu-img resize output/qcow2/disk.qcow2 100G
-
Boot the VM. Any UEFI-capable hypervisor works (UTM on Apple Silicon,
virt-install/libvirt on Linux, etc.). The image has no BIOS fallback — UEFI is required. -
SSH in as the user defined in
config.toml(currentlyetincelle). The key inconfig.tomlis the only authorized credential; there is no console login or password.ssh etincelle@<vm-ip>
-
Provision secrets and join Tailscale. Requires the 1Password CLI signed in to the
kantaivault on the workstation running the task:task provision HOST=<vm-ip>
This installs
/etc/image-factory/keys/*,/etc/etincelle/secrets/caddy.env,/etc/etincelle/secrets/beszel-agent.env, and/etc/etincelle/secrets/trove-agent.envon the VM, startscaddy.service,image-factory.service,beszel-agent.service, andtrove-agent.service, then prompts for a Tailscale auth key and runstailscale up. Pass the key non-interactively withTS_AUTHKEY=tskey-...; submit an empty key to skip.
Ongoing updates are automatic: pushes to main build a new image via GitHub Actions, and bootc-fetch-apply-updates.timer on the VM applies it on the next interval (reboots into the new deployment).
Two independent mechanisms are at work:
| Timer | Scope | Default schedule |
|---|---|---|
bootc-fetch-apply-updates.timer |
The OS image itself (ghcr.io/jfroy/etincelle), including quadlet units and baked-in service configs |
OnBootSec=1h, then every 8h with up to 2h of jitter — worst case ~10h between checks |
podman-auto-update.timer |
Container images referenced by quadlets marked AutoUpdate=registry |
OnCalendar=daily with 15m jitter |
Because quadlet units live in the OS image at /etc/containers/systemd/, adding or changing a service requires a bootc update and reboot, not just a container image pull.
To apply a freshly built image immediately instead of waiting for the timer:
sudo bootc upgrade --check # is a newer image available?
sudo bootc upgrade --apply # fetch, stage, and reboot into itOther useful commands on the host:
sudo bootc status # booted, staged, and rollback deployments
sudo bootc upgrade # stage without rebooting (applies on next boot)
sudo bootc rollback && sudo systemctl reboot # boot the previous deployment
sudo podman auto-update # pull updated container images now
systemctl list-timers 'bootc-*' 'podman-auto-update*'
journalctl -u bootc-fetch-apply-updates.service # diagnose failed pullsProvisioned post-install by scripts/provision-secrets.sh, never committed to this repo:
/etc/image-factory/keys/— Talos image factory signing keys/etc/etincelle/secrets/caddy.env— Cloudflare API token for ACME DNS challenge/etc/etincelle/secrets/beszel-agent.env— Beszel agentTOKEN/etc/etincelle/secrets/trove-agent.env— Trove agentTROVE_TOKEN/var/lib/tailscale/— Tailscale node identity (created on firsttailscale up)
The image grants passwordless sudo to the wheel group via /etc/sudoers.d/wheel-nopasswd, so the user defined in config.toml (currently etincelle) can run privileged commands without a password.