Install image-reflector-controller + image-automation-controller as part of the flux-system install (bootstrap.sh --components-extra) so the image.toolkit CRDs pre-exist before reconcile — no F-MON1 split needed for the ImageUpdateAutomation CR. Refine the cluster-wide ImageUpdateAutomation for machine-authored git write-back (setter strategy, path ./apps) reusing the flux-system GitRepository secretRef, and document the operator git-write-credential hand-off + SOPS secret template (gitea-image-automation-credentials). image-automation stays deferred in platform/kustomization.yaml until that credential is provisioned. Co-Authored-By: Claude Opus 4.8 <[email protected]>
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),
docs/05 · 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):
bootstrap/sops/age-keygen.sh(operator step — generates key outside repo).- Install the
sops-ageSecret perbootstrap/sops/age-key-secret.md(thekubectl create secrettemplate).bootstrap.shfails fast if this Secret is absent. 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:):
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):
# ILLUSTRATIVE — Phase 6, not run in Phase 1.
flux bootstrap git \
--url=https://gitea.home.keithjackson.dev/<org>/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").