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]>
100 lines
6.4 KiB
Bash
Executable File
100 lines
6.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# bootstrap.sh — the SINGLE Flux + SOPS bootstrap entrypoint for the dev cluster.
|
|
#
|
|
# Phase 1, T-1.4 · ADR-0004 (Flux) · ADR-0007 (SOPS+age) · R-GITOPS-01 · R-SEC-01.
|
|
# =============================================================================
|
|
# *** AUTHORED, NOT RUN. The Infra worker's FINAL Ansible play invokes this ***
|
|
# *** (interface contract, see ../README.md). Agents must not execute it. ***
|
|
# =============================================================================
|
|
#
|
|
# WHAT THIS DOES (Phase 1, bootstrap-source method — reconciles from the LOCAL repo, no remote
|
|
# git host exists yet per spec docs/05 + CONTRIBUTING.md):
|
|
#
|
|
# 1. `flux install` — deploy the Flux controllers. Besides the default set (source, kustomize,
|
|
# helm, notification) we ALSO install the two IMAGE controllers via
|
|
# `--components-extra` (see FLUX_COMPONENTS_EXTRA below): image-reflector-
|
|
# controller + image-automation-controller. These register the
|
|
# image.toolkit.fluxcd.io CRDs (ImageRepository/ImagePolicy/ImageUpdateAutomation)
|
|
# that Phase-4 hands-off updates need (ADR-0009, R-UPD-02). They are part of the
|
|
# flux-system install exactly like the other 4 controllers — NOT a reconciled
|
|
# HelmRelease — so their CRDs pre-exist before the first reconcile (this is why
|
|
# platform/image-automation can carry the ImageUpdateAutomation CR without the
|
|
# F-MON1 controllers/configs split: the CRD is present at bootstrap, like
|
|
# GitRepository/Kustomization are). WS-UPDATE (T-4.5).
|
|
# We do NOT use `flux bootstrap git` in Phase 1 because that command commits
|
|
# and OWNS its own gotk-sync GitRepository+Kustomization named `flux-system`.
|
|
# Our entrypoints are ALREADY authored in clusters/base (flux-system.yaml =
|
|
# the GitRepository; platform.yaml/apps.yaml = the Kustomizations). Letting
|
|
# bootstrap regenerate them would DUPLICATE/conflict. `flux install` +
|
|
# applying the authored clusters/dev overlay is the reconciling path.
|
|
# 2. sops-age Secret — the operator installs the age PRIVATE key out-of-band BEFORE reconcile so
|
|
# decryption works on first pass (../sops/age-key-secret.md). This script
|
|
# only VERIFIES it exists; it never handles key material.
|
|
# 3. `kubectl apply -k clusters/dev` — materialises the authored reconciliation graph:
|
|
# GitRepository `flux-system` (source) -> Kustomization `platform` ->
|
|
# Kustomization `apps` (dependsOn platform), with cluster-vars overlay
|
|
# (R-ENV-02) and SOPS decryption enabled via the patches in ./patches/.
|
|
#
|
|
# PREREQUISITES the operator/PM resolves before this runs (see ../README.md "Handoff"):
|
|
# - clusters/base/flux-system.yaml url placeholder is overridden — wire ./patches/flux-system-
|
|
# source-url.yaml into clusters/dev/kustomization.yaml (or set the URL) so the source is real.
|
|
# - ./patches/platform-sops-decryption.yaml + apps-sops-decryption.yaml are wired into
|
|
# clusters/dev/kustomization.yaml `patches:` so decryption is enabled at reconcile.
|
|
# - the repo-root .sops.yaml carries the real age recipient (single canonical file; no copy under ../sops).
|
|
#
|
|
# PHASE 6 (WS-GITOPS, later — NOT Phase 1): once Gitea is up, cut the source over with
|
|
# `flux bootstrap git` against gitea.home.keithjackson.dev — see ../README.md "Phase-6 cutover".
|
|
#
|
|
set -euo pipefail
|
|
|
|
# --- config (env-overridable) ------------------------------------------------
|
|
FLUX_ENV_PATH="${FLUX_ENV_PATH:-clusters/dev}" # dev is the ONLY Phase-1 build target
|
|
FLUX_NAMESPACE="${FLUX_NAMESPACE:-flux-system}"
|
|
SOPS_SECRET_NAME="${SOPS_SECRET_NAME:-sops-age}"
|
|
# The two Phase-4 image-automation controllers are installed alongside the default set so their CRDs
|
|
# exist before reconcile (ADR-0009, R-UPD-02). Override to "" to build a cluster without them.
|
|
FLUX_COMPONENTS_EXTRA="${FLUX_COMPONENTS_EXTRA:-image-reflector-controller,image-automation-controller}"
|
|
|
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
repo_root="$(git -C "${script_dir}" rev-parse --show-toplevel 2>/dev/null || cd "${script_dir}/../.." && pwd)"
|
|
|
|
echo "==> Flux + SOPS bootstrap (dev) — path=${FLUX_ENV_PATH}"
|
|
|
|
# --- 0. tooling present (from infra/devcontainer; versions pinned there) ------
|
|
for bin in flux kubectl; do
|
|
command -v "$bin" >/dev/null 2>&1 || { echo "ERROR: '$bin' not found (use infra/devcontainer)." >&2; exit 1; }
|
|
done
|
|
|
|
# --- 1. install Flux controllers ---------------------------------------------
|
|
# Default controllers + the image-reflector/image-automation pair (Phase-4 hands-off updates,
|
|
# ADR-0009). `--components-extra` regenerates flux-system with all controllers and registers the
|
|
# image.toolkit.fluxcd.io CRDs before any reconcile — so platform/image-automation's CR applies cleanly.
|
|
if [ -n "${FLUX_COMPONENTS_EXTRA}" ]; then
|
|
echo "==> [1/3] flux install (default controllers + extra: ${FLUX_COMPONENTS_EXTRA})"
|
|
flux install --namespace="${FLUX_NAMESPACE}" --components-extra="${FLUX_COMPONENTS_EXTRA}"
|
|
else
|
|
echo "==> [1/3] flux install (default controllers only)"
|
|
flux install --namespace="${FLUX_NAMESPACE}"
|
|
fi
|
|
|
|
# --- 2. verify the out-of-band age key Secret exists -------------------------
|
|
# We DO NOT create it here (it holds the private key). The operator installs it per
|
|
# ../sops/age-key-secret.md. Fail fast if missing so decryption can't silently no-op.
|
|
echo "==> [2/3] verifying ${SOPS_SECRET_NAME} Secret exists in ${FLUX_NAMESPACE}"
|
|
if ! kubectl -n "${FLUX_NAMESPACE}" get secret "${SOPS_SECRET_NAME}" >/dev/null 2>&1; then
|
|
echo "ERROR: Secret ${FLUX_NAMESPACE}/${SOPS_SECRET_NAME} not found." >&2
|
|
echo " Install the age private key first: bootstrap/sops/age-key-secret.md" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# --- 3. apply the authored dev entrypoint ------------------------------------
|
|
echo "==> [3/3] kubectl apply -k ${FLUX_ENV_PATH}"
|
|
kubectl apply -k "${repo_root}/${FLUX_ENV_PATH}"
|
|
|
|
echo
|
|
echo "==> Bootstrap applied. Watch reconciliation with (read-only):"
|
|
echo " flux get kustomizations --watch"
|
|
echo " flux get sources git -n ${FLUX_NAMESPACE}"
|
|
echo " 'platform' should go Ready, then 'apps' (dependsOn platform)."
|