prole/install.sh
chrisfu 500c9b1317 fix(installer): env-contamination guard against shell-context / config mismatch
Filed in response to the 2026-04-28 14:00 UTC backup outage. An
`install.sh --mode k3d` run with the shell pointed at GKE silently
overwrote the GKE cluster's GCS-backed ObjectStore + ScheduledBackup
with k3d-mode defaults; Garage filled up and CNPG backups failed for
hours before the next manual check. The class of bug is "config says
target cluster A, shell context says target cluster B, installer
proceeds against B without warning."

New shared bash helper at etc/preflight_kubecontext.sh with two
functions:

  - verify_kubecontext_matches_config <cfg-path>
      Strict gate. Reads [Global] APP_CLUSTER_KUBECONTEXT from the
      config and exits 1 if `kubectl config current-context` differs.
      Skipped silently when the config has no baked APP_CLUSTER_KUBECONTEXT
      (e.g. fresh k3d.cfg) or when there's no live current-context.

  - print_kubecontext_notice
      Informational. Prints what's about to be inherited so the user
      can abort before the TUI launches if it looks wrong. Never fails.

Wiring:

  - deploy.sh sources the helper and calls the strict gate against
    ${PROLE_DEPLOY_CFG:-conf/gke.cfg} before invoking Python.
    Unattended path -> hard refusal on mismatch.

  - install.sh sources the helper and calls the informational notice
    (gated on not-`--min`) right after entering the local-checkout
    branch. The TUI is interactive, so the strict mode-aware gate is
    a follow-up once the welcome screen records a mode in
    state.inputs.

Bypass for deliberate cross-cluster maintenance:
    KNOE_SKIP_KUBECONTEXT_GUARD=true ./deploy.sh

End-to-end verified:
  - deploy.sh with current=cnpg-0, gke.cfg=app-0   -> exit 1, clear msg
  - deploy.sh with KNOE_SKIP_...=true              -> bypasses, prints
                                                     "skipping check"
  - install.sh --min                               -> notice skipped
  - install.sh (no flag) and install.sh --silent   -> notice printed

Doc updates:
  - CLAUDE.md §"Env-contamination warning" rewritten to describe the
    live guard (was a forward-looking TODO).
  - CLAUDE.md drift table row R4 removed; "Closed 2026-05-01" line added.
  - docs/TODO.md queue item #1 archived to Done; R4 dropped from the
    reality-vs-intent table. Queue numbering retained (no #1 placeholder)
    so the docs/plans/junie/<NN>-...md filenames still match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 16:14:30 -07:00

127 lines
4.8 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Thin launch vehicle for the Knoe installer.
#
# Local execution (from a repo checkout):
# ./install.sh [args...]
#
# Bootstrap via curl (first-time install):
# curl -fsSL https://knoey.com/install.sh | bash
#
# ---------------------------------------------------------------------------
# Dual-cluster GKE architecture (prod / k8s mode)
# ---------------------------------------------------------------------------
# knoe-dev-0 — Autopilot cluster: app workloads (GitLab, platform), service mesh knoe-0
# knoe-cnpg-0 — Standard GKE cluster: CloudNative-PG only (rw/ro/r pod separation)
# 3 nodes × e2-standard-2, node pool: cnpg-db-pool
#
# GCP quota requirements (project: plenary-truck-485623-p7, region: us-west3):
# pd-standard : node boot disks (3 × 50 GB = 150 GB — does NOT count against pd-ssd quota)
# pd-ssd : PGDATA PVCs (3 × 50 Gi = 150 Gi)
# WAL PVCs (3 × 50 Gi = 150 Gi)
# Total pd-ssd = 300 Gi (requires 300 GB pd-ssd regional quota)
#
# To patch an existing knoe-cnpg-0 cluster with wrong boot disk type:
# ./scripts/patch_clusters.sh # dry-run first: DRY_RUN=true ./scripts/patch_clusters.sh
# ---------------------------------------------------------------------------
set -euo pipefail
# Repo URL used only during bootstrap (curl-pipe) installs.
KNOE_REPO_URL="${KNOE_REPO_URL:-https://gitlab.knoey.com/knoe/knoe.git}"
# Default install directory for bootstrap installs.
_INSTALL_DIR="${KNOE_HOME:-$HOME/knoe}"
_check_gcp_tools() {
# Skip GCP tool check if --min flag is present
for arg in "$@"; do
if [[ "$arg" == "--min" ]]; then
return 0
fi
done
local missing=0
if ! command -v gcloud >/dev/null 2>&1; then
echo "Error: gcloud CLI is required for GKE cluster management." >&2
echo " Install: https://cloud.google.com/sdk/docs/install" >&2
missing=1
fi
if ! command -v kubectl >/dev/null 2>&1; then
echo "Error: kubectl is required for Kubernetes cluster operations." >&2
echo " Install: https://kubernetes.io/docs/tasks/tools/" >&2
missing=1
fi
if [[ "$missing" -ne 0 ]]; then
echo "" >&2
echo "Install missing tools and re-run install.sh." >&2
exit 1
fi
}
_bootstrap() {
echo "==> Bootstrapping Knoe installer..."
command -v git >/dev/null 2>&1 || { echo "Error: git is required." >&2; exit 1; }
command -v python3 >/dev/null 2>&1 || { echo "Error: python3 is required." >&2; exit 1; }
if [[ -d "${_INSTALL_DIR}/.git" ]]; then
echo "==> Updating existing checkout at ${_INSTALL_DIR} ..."
git -C "${_INSTALL_DIR}" pull --ff-only
else
echo "==> Cloning to ${_INSTALL_DIR} ..."
git clone "${KNOE_REPO_URL}" "${_INSTALL_DIR}"
fi
exec "${_INSTALL_DIR}/install.sh" "$@"
}
# Resolve script directory; empty when running via curl pipe.
_src="${BASH_SOURCE[0]:-}"
_script_dir="$(cd "$(dirname "${_src:-/}")" 2>/dev/null && pwd || echo "")"
if [[ -n "${_script_dir}" && -d "${_script_dir}/knoe" ]]; then
# Local checkout: delegate to the Python installer module.
cd "${_script_dir}"
# kubectl-context notice. install.sh defers mode selection to the Python
# TUI, so we can't strictly verify here — but printing the inherited
# context up-front gives the user a chance to abort before the TUI
# launches if it looks wrong (e.g. shell on GKE while planning a k3d
# install). Filed against docs/TODO.md queue item #1 (drift R4); the
# mode-aware strict guard runs in deploy.sh (which knows its config) and
# should also run Python-side once the welcome screen records a mode.
if [[ -f "${_script_dir}/etc/preflight_kubecontext.sh" ]]; then
_is_min=0
for arg in "$@"; do
if [[ "$arg" == "--min" ]]; then _is_min=1; break; fi
done
if [[ "$_is_min" -eq 0 ]]; then
# shellcheck source=etc/preflight_kubecontext.sh
source "${_script_dir}/etc/preflight_kubecontext.sh"
print_kubecontext_notice
fi
unset _is_min
fi
# 1Password preflight — sign in, create 'knoey' vault, ensure 'administrator' item.
# Skipped in --min mode (init_1password.sh handles the flag check itself).
if [[ -f "${_script_dir}/etc/init_1password.sh" ]]; then
bash "${_script_dir}/etc/init_1password.sh" "$@" || true
fi
# If a built binary exists, prefer it.
if [[ -x "dist/knoe" ]]; then
exec "./dist/knoe" "$@"
fi
# Prefer the venv Python at KNOE_HOME (or the repo root) when available;
# it carries all knoe_requirements.txt dependencies.
_VENV_PYTHON="${KNOE_HOME:-${_script_dir}}/bin/python3"
_check_gcp_tools "$@"
if [[ -x "${_VENV_PYTHON}" ]]; then
exec "${_VENV_PYTHON}" -m knoe.ui.screens "$@"
fi
exec python3 -m knoe.ui.screens "$@"
else
# No local checkout found — bootstrap from the remote repo.
_bootstrap "$@"
fi