#!/usr/bin/env bash # infra/devcontainer/dev.sh — build & enter the pinned dev toolbox. # # Mounts the repo so OpenTofu (bpg/proxmox) and Ansible drive the Proxmox-hosted k3s cluster # from inside the container. Rootless podman by default (ENGINE=docker to switch); SELinux # label is disabled for the run. kubectl/flux/helm reach the kube-API DIRECTLY via the pfSense # NAT (192.168.2.50:6443 -> kube-vip VIP 192.168.1.49:6443) — no SSH tunnel; the kubeconfig # validates TLS against the VIP via tls-server-name. The pfSense SSH NAT (2250/2251/2252) is # retained ONLY for Ansible node access, not for the kube-API. # # ./dev.sh build # build the image only # ./dev.sh # build (if needed) + open a shell # ./dev.sh run tofu -version # run one command in the toolbox # ./dev.sh run kubectl get nodes # talks to the Proxmox k3s cluster directly (no tunnel) set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${HERE}/../.." && pwd)" IMAGE="${IMAGE:-home-k3s-dev:local}" ENGINE="${ENGINE:-podman}" # shellcheck disable=SC1091 source "${HERE}/versions.env" # k3s API access: the dev cluster runs on Proxmox, and its kube-API is reached DIRECTLY over # the pfSense NAT (192.168.2.50:6443 -> kube-vip VIP 192.168.1.49:6443). No SSH tunnel is # opened. infra/ansible/.cache/dev.kubeconfig targets https://192.168.2.50:6443 with # tls-server-name=192.168.1.49 so TLS validates against the VIP's SAN through the NAT. build() { "$ENGINE" build \ --build-arg FEDORA_VERSION="$FEDORA_VERSION" \ --build-arg OPENTOFU_VERSION="$OPENTOFU_VERSION" \ --build-arg AGE_VERSION="$AGE_VERSION" \ --build-arg SOPS_VERSION="$SOPS_VERSION" \ --build-arg FLUX_VERSION="$FLUX_VERSION" \ --build-arg KUBECTL_VERSION="$KUBECTL_VERSION" \ --build-arg HELM_VERSION="$HELM_VERSION" \ --build-arg KYVERNO_VERSION="$KYVERNO_VERSION" \ --build-arg KYVERNO_JSON_VERSION="$KYVERNO_JSON_VERSION" \ --build-arg CHAINSAW_VERSION="$CHAINSAW_VERSION" \ --build-arg YAMLLINT_VERSION="$YAMLLINT_VERSION" \ --build-arg YQ_VERSION="$YQ_VERSION" \ --build-arg CHECK_JSONSCHEMA_VERSION="$CHECK_JSONSCHEMA_VERSION" \ -t "$IMAGE" -f "${HERE}/Containerfile" "${HERE}" } run() { # Persist the SOPS age keyring on the HOST so the age PRIVATE key generated by # bootstrap/sops/age-keygen.sh survives this --rm container (otherwise it lives only # in the ephemeral container fs and is lost on exit — you'd be locked out of every # SOPS-encrypted secret). SOPS_AGE_KEY_FILE defaults to ~/.config/sops/age/keys.txt, # which is this mount. Create it first so the bind source exists. mkdir -p "${HOME}/.config/sops/age" # Spec repo (home-network-k3s) mount, read-only, for ci/spec-immutability.sh (R-TEST-06). # That script reads SPEC_REPO (default ${REPO_ROOT}/../home-network-k3s); inside the # container REPO_ROOT is /work, so the default resolves to /home-network-k3s. Mount the # sibling spec repo there RO when it exists; skip the mount otherwise so the container # still runs (the gate then honestly SKIPS immutability — exit 3, never faked green). local -a spec_mount=() local spec_host spec_host="$(cd "${REPO_ROOT}/../home-network-k3s" 2>/dev/null && pwd || true)" if [ -n "${spec_host}" ] && [ -d "${spec_host}/.git" ]; then spec_mount=(-v "${spec_host}:/home-network-k3s:ro" -e SPEC_REPO=/home-network-k3s) fi "$ENGINE" run --rm -it \ --network host \ --security-opt label=disable \ -v "${REPO_ROOT}:/work" -w /work \ "${spec_mount[@]}" \ -v "${HOME}/.ssh:/root/.ssh:ro" \ -v "${HOME}/.config/sops:/root/.config/sops" \ -v "${HOME}/.config/home-k3s/ansible-vault-pass:/root/.config/home-k3s/ansible-vault-pass:ro" \ -e KUBECONFIG="${DEV_KUBECONFIG:-/work/infra/ansible/.cache/dev.kubeconfig}" \ "$IMAGE" bash -lc 'if [ "$#" -gt 0 ]; then exec "$@"; else exec bash -l; fi' dev.sh "$@" } case "${1:-shell}" in build) build ;; run) shift; run "$@" ;; shell) build; shift 2>/dev/null || true; run ;; *) build; run "$@" ;; esac