Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

futhark

A GitOps-driven homelab. Two Fedora nodes joined over a Tailscale mesh run one k0s cluster; everything inside that cluster is reconciled by Flux from this repository, and everything that cannot live inside it is managed by OpenTofu.

The tree splits along three planes, and almost every question about the repo resolves to “which plane owns this?”:

PlaneOwnsToolWhere
HostThe machines: users, SSH, firewall, mesh join, the k0s install itselfAnsible + k0sctlansible/
ClusterEverything reconcilable from git: controllers, apps, namespaces, policyFluxflux/, infra/, nodes/
CloudProvider APIs no Kustomization can express: DNS, OIDC clients, the tailnet policyOpenTofutofu/

If you are setting a machine up from nothing, start at Cold bootstrap. If something is broken, start at Troubleshooting. If you are adding to the tree, the rules you have to follow are under Conventions.

Everything an operator runs goes through task:

task --list

What runs where

kenaz is the k0s controller+worker and the only node with public ingress. ogma is a worker; OpenBao and Pocket ID are pinned to it with a nodeSelector. Both are on the mesh and are addressed by their MagicDNS name, never by a stored address — see Nodes.

The pieces, roughly in dependency order: OpenBao (secrets, sealed by an external KMIP service), External Secrets Operator (which pulls from OpenBao into Kubernetes Secrets), Pocket ID (OIDC), cert-manager (Let’s Encrypt over DNS-01), two Traefiks — one public, one mesh-only — and a VictoriaMetrics/VictoriaLogs/Grafana stack. Each is described in Cluster infrastructure, and the order they must come up in is Startup ordering.

Secrets, in one paragraph

No credential is ever committed. Bootstrap secrets are pass:// pointers to a Proton Pass vault, resolved at runtime; runtime secrets live in OpenBao and reach pods through External Secrets Operator. The same rule covers identifying non-credentials — a public IP, the tailnet name — because this repository is public. The full rule, and what to do when you need a new one, is in Secrets.