# bootstrap/flux — Flux bootstrap → `clusters/dev` (Phase 1, T-1.4) Points Flux at the authored **`clusters/dev`** entrypoint and turns on SOPS decryption. Spec: [ADR-0004 (Flux)](../../../home-network-k3s/decisions/adr-0004-gitops-flux.md), [docs/05](../../../home-network-k3s/docs/05-gitops-and-delivery.md) · Requirements: **R-GITOPS-01** (pull-based reconcile), **R-GITOPS-03** (drift correction), **R-SEC-01** (SOPS decryption). > **AUTHOR-ONLY. Nothing in this directory has been executed.** No `flux`, `kubectl`, key-gen, or > cluster contact was run. These are files + runbooks the PM/operator validates and runs. ## Files | File | Purpose | |---|---| | `bootstrap.sh` | **The single bootstrap entrypoint** the Infra worker's final Ansible play calls. `flux install` → verify `sops-age` Secret → `kubectl apply -k clusters/dev`. | | `patches/platform-sops-decryption.yaml` | Strategic-merge patch enabling `decryption.provider: sops` + `secretRef: sops-age` on the `platform` Kustomization (realises the base TODO). | | `patches/apps-sops-decryption.yaml` | Same, for the `apps` Kustomization. | | `patches/flux-system-source-url.yaml` | Overrides the `flux-system` GitRepository `url` placeholder with the real bootstrap source (template — fill at bootstrap). | ## Why `flux install` + apply, not `flux bootstrap git` (Phase 1) The reconciliation graph is **already authored** in `clusters/base` (read-only to this worker): `flux-system.yaml` is the `flux-system` **GitRepository**; `platform.yaml`/`apps.yaml` are the Flux **Kustomizations**; `clusters/dev` is the overlay that patches `cluster-vars` (R-ENV-02). `flux bootstrap git` **generates and owns its own** `gotk-sync` GitRepository + Kustomization named `flux-system` and commits them into the repo — which would **duplicate/conflict** with the authored base. It also requires a **writable remote git host**, which does not exist in Phase 1 (spec docs/05 "Source-of-truth", CONTRIBUTING.md: bring-up is from a local/out-of-band bootstrap source; Gitea arrives in Phase 6). So Phase 1 uses `flux install` (controllers only) + `kubectl apply -k clusters/dev` (materialise the authored graph). This **reconciles with** `clusters/base` instead of regenerating it. ## Bootstrap order (what runs, in sequence) ``` tofu apply → cloud-init → Ansible (k3s + Cilium) [Infra worker, T-1.1..1.3] │ ▼ (final Ansible play calls the entrypoints below) 1. bootstrap/sops/age-keygen.sh # operator generates age key OUTSIDE repo 2. bootstrap/sops/age-key-secret.md # operator installs private key as sops-age Secret 3. bootstrap/flux/bootstrap.sh # flux install → verify sops-age → apply -k clusters/dev │ ▼ Flux reconciles: GitRepository(flux-system) → Kustomization(platform) → Kustomization(apps), with SOPS decryption enabled (patches/) and cluster-vars overlay (R-ENV-02). ``` ## Interface note — for the Infra worker (T-1.3 Ansible) **Single entrypoint your final play calls:** `bootstrap/flux/bootstrap.sh` (run inside the `infra/devcontainer` toolchain so pinned `flux`/`kubectl` are on PATH; `FLUX_VERSION`/`SOPS_VERSION` live in `infra/devcontainer/versions.env`). **Order your play must enforce** (the age key is a hard dependency of a working decrypt): 1. `bootstrap/sops/age-keygen.sh` (operator step — generates key outside repo). 2. Install the `sops-age` Secret per `bootstrap/sops/age-key-secret.md` (the `kubectl create secret` template). `bootstrap.sh` **fails fast** if this Secret is absent. 3. `bootstrap/flux/bootstrap.sh` (this entrypoint). `bootstrap.sh` is idempotent-friendly (`flux install` and `kubectl apply -k` re-run safely) and never handles key material — it only *verifies* the Secret exists. ## Handoff — clusters owner / PM (files this worker may not edit) `clusters/` is out of this worker's write scope, so three wirings are delivered as patches here and must be applied into the `clusters/dev` overlay (`clusters/dev/kustomization.yaml` `patches:`): ```yaml patches: - path: ../../bootstrap/flux/patches/flux-system-source-url.yaml # real source URL - path: ../../bootstrap/flux/patches/platform-sops-decryption.yaml # SOPS on platform - path: ../../bootstrap/flux/patches/apps-sops-decryption.yaml # SOPS on apps ``` Alternatively the base TODOs in `clusters/base/{platform,apps}.yaml` and the `url` in `clusters/base/flux-system.yaml` can be filled in directly (same effect) — the clusters owner's call. Either way the `sops-age` `secretRef` name and `flux-system` source name are kept in lockstep with the authored base. ## Phase-6 cutover (WS-GITOPS — NOT now) Once Gitea is stable, migrate the source to Gitea (docs/05, CONTRIBUTING.md). At that point `flux bootstrap git` becomes appropriate (there is a writable remote to own): ```bash # ILLUSTRATIVE — Phase 6, not run in Phase 1. flux bootstrap git \ --url=https://gitea.home.keithjackson.dev//home-network-k3s-impl.git \ --branch=main \ --path=clusters/dev ``` Keep an off-cluster mirror so a source-of-truth outlives the cluster (docs/05 "Circularity & DR").