Skip to content

Repository files navigation

✨ etincelle

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.

Services

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

Build

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-selinux installed. The build runs rootless (no sudo) using bootc-image-builder's --in-vm KVM mode, so /dev/kvm must be accessible to the invoking user.

  • macOS: rootful podman machine is required (rootless --in-vm cannot 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 bake

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

Deploy

One-time install on a fresh VM:

  1. (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
  2. 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.

  3. SSH in as the user defined in config.toml (currently etincelle). The key in config.toml is the only authorized credential; there is no console login or password.

    ssh etincelle@<vm-ip>
  4. Provision secrets and join Tailscale. Requires the 1Password CLI signed in to the kantai vault 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.env on the VM, starts caddy.service, image-factory.service, beszel-agent.service, and trove-agent.service, then prompts for a Tailscale auth key and runs tailscale up. Pass the key non-interactively with TS_AUTHKEY=tskey-...; submit an empty key to skip.

Updates

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 it

Other 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 pulls

Secrets and host state

Provisioned 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 agent TOKEN
  • /etc/etincelle/secrets/trove-agent.env — Trove agent TROVE_TOKEN
  • /var/lib/tailscale/ — Tailscale node identity (created on first tailscale 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.

About

We're going to light a fire.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages