#!/usr/bin/env bash
# scripts/phase-00-teardown-destroy.sh [--apply] [--no-prompt]
#
# FULL-DESTROY teardown (the from-scratch path). Destroys the `openstack` model AND
# lets the MAAS pod-composed openstack0-3 machines DECOMPOSE (returned to the libvirt
# pod, removed from MAAS). After this you MUST reenroll (scripts/reenroll-hosts.sh) and
# re-carve (scripts/carve-host-interfaces.sh) before the next deploy.
#
# WHEN TO USE: when you deliberately want a clean MAAS re-enrollment (e.g. validating
# the reenroll+carve path itself, or recovering from a corrupted machine state). For a
# reuse-in-place teardown that KEEPS the machines, use phase-00-teardown-release.sh.
#
# D-061 (honest behavior note): on this virsh-pod MAAS, `juju destroy-model` decomposes
# the pod-composed machines REGARDLESS of the storage flag. This script EMBRACES that
# (it is the destroy path) rather than fighting it. --destroy-storage is used so OSD
# data is discarded too (a fresh deploy re-wipes vdb anyway). This is NOT the bug that
# bit the project 3x -- the bug was using THIS behavior when machine RETENTION was
# wanted. If you want retention, you are on the wrong script (use -release.sh).
#
# Roster (resolved live):
# PROTECTED (never touched): juju, lxd, tailscale
# HOSTS (decomposed): openstack0-3 -> reenroll+carve after
# ORPHAN (deleted if present): capi-mgmt
#
# DEFAULT = DRY-RUN. --apply executes; typed-approval gate (model name) first.
# --no-prompt skips the gate (tested automation only).
#
# Exit: 0 ok | 1 fatal/unsafe | 2 aborted. ASCII + LF.
set -euo pipefail
shopt -s inherit_errexit 2>/dev/null || true
MAAS_PROFILE="${MAAS_PROFILE:-admin}"
MODEL="${OPENSTACK_MODEL:-openstack}"
HOSTS=(openstack0 openstack1 openstack2 openstack3)
ORPHANS=(capi-mgmt)
PROTECTED=(juju lxd tailscale)
MODE="dryrun"; PROMPT=1
for a in "$@"; do
case "$a" in
--apply) MODE="apply" ;;
--no-prompt) PROMPT=0 ;;
*) echo "unknown arg: $a" >&2; exit 1 ;;
esac
done
FATAL=0
hdr() { echo; echo "=== $* ==="; }
note() { echo " - $*"; }
fail() { echo "FAIL: $*" >&2; FATAL=$((FATAL+1)); }
command -v jq >/dev/null || { echo "FATAL: jq required" >&2; exit 1; }
command -v juju >/dev/null || { echo "FATAL: juju not on PATH" >&2; exit 1; }
maas_json() { local o; o="$(maas "$MAAS_PROFILE" "$@" 2>/dev/null || true)"; printf '%s' "$o" | jq empty 2>/dev/null && printf '%s' "$o" || printf '[]'; }
MACHINES_JSON="$(maas_json machines read)"
sid_of() { printf '%s' "$MACHINES_JSON" | jq -r --arg h "$1" '.[]|select(.hostname==$h)|.system_id' | head -1; }
status_of() { printf '%s' "$MACHINES_JSON" | jq -r --arg h "$1" '.[]|select(.hostname==$h)|.status_name' | head -1; }
hdr "destroy-teardown audit mode=$MODE model=$MODEL"
declare -A PROT_SID
hdr "PROTECTED substrate (never touched)"
for p in "${PROTECTED[@]}"; do
s="$(sid_of "$p")"
if [ -z "$s" ]; then note "$p: not in MAAS -- nothing to protect"; continue; fi
PROT_SID["$s"]="$p"; note "$p = $s (status $(status_of "$p")) -- PROTECTED"
done
hdr "HOSTS (will DECOMPOSE -> reenroll+carve after)"
for h in "${HOSTS[@]}"; do
s="$(sid_of "$h")"
if [ -z "$s" ]; then note "$h: already absent from MAAS"; continue; fi
if [ -n "${PROT_SID[$s]:-}" ]; then fail "$h resolves to PROTECTED sid $s -- ABORT"; continue; fi
note "$h = $s (status $(status_of "$h"))"
done
declare -A OSID
hdr "ORPHANS (deleted)"
for o in "${ORPHANS[@]}"; do
s="$(sid_of "$o")"
if [ -z "$s" ]; then note "$o: absent -- SKIP"; continue; fi
if [ -n "${PROT_SID[$s]:-}" ]; then fail "$o resolves to PROTECTED sid $s -- ABORT"; continue; fi
OSID["$s"]="$o"; note "$o = $s -- DELETE"
done
MODEL_PRESENT=0
if juju models --format=json 2>/dev/null | jq -e --arg m "$MODEL" '.models[]?|select(.name==$m or (.name|endswith("/"+$m)))' >/dev/null 2>&1; then
MODEL_PRESENT=1; note "juju model '$MODEL' PRESENT -- will destroy"
else
note "juju model '$MODEL' not present -- destroy skipped"
fi
[ "$FATAL" -eq 0 ] || { echo; echo "ABORT: $FATAL safety failure(s) -- nothing changed"; exit 1; }
hdr "PLAN"
echo " 1) juju destroy-model $MODEL --destroy-storage --force --no-wait --no-prompt"
echo " (decomposes openstack0-3; discards OSD storage)"
echo " 2) delete orphan MAAS machine(s): ${ORPHANS[*]}"
echo " 3) verify hosts gone + substrate intact"
echo " AFTER: reenroll-hosts.sh -> carve-host-interfaces.sh (x4) -> maas-fabric-prune.sh -> maas-standup.sh"
echo " PROTECTED: ${PROTECTED[*]}"
if [ "$MODE" = dryrun ]; then
echo; echo " re-run with --apply to execute (typed model-name gate)."
echo "OK (dryrun)"; exit 0
fi
if [ "$PROMPT" -eq 1 ] && [ "$MODEL_PRESENT" = 1 ]; then
printf 'Type the model name "%s" to confirm FULL DESTROY (machines WILL decompose): ' "$MODEL" > /dev/tty
read -r ans < /dev/tty
[ "$ans" = "$MODEL" ] || { echo "aborted (got '$ans') -- nothing changed"; exit 2; }
fi
hdr "MUTATE 1: destroy model (machines decompose)"
if [ "$MODEL_PRESENT" = 1 ]; then
echo " DO: juju destroy-model $MODEL --destroy-storage --force --no-wait --no-prompt"
juju destroy-model "$MODEL" --destroy-storage --force --no-wait --no-prompt 2>&1 || fail "destroy-model returned error"
else note "model absent -- skip"; fi
[ "$FATAL" -eq 0 ] || { echo; echo "STOP: destroy-model failed -- not deleting orphans."; exit 1; }
hdr "MUTATE 2: delete orphan machines"
for s in "${!OSID[@]}"; do
echo " DO: delete orphan ${OSID[$s]} ($s)"
maas "$MAAS_PROFILE" machine delete "$s" >/dev/null 2>&1 || note "orphan ${OSID[$s]} delete failed (may already be gone)"
done
hdr "VERIFY (read-only): hosts gone + substrate intact"
MACHINES_JSON="$(maas_json machines read)"
for h in "${HOSTS[@]}"; do
st="$(status_of "$h")"
if [ -z "$st" ]; then note "$h -> decomposed/absent (expected)"; else note "$h -> $st (still present; destroy may be in progress -- re-check)"; fi
done
for p in "${PROTECTED[@]}"; do note "PROTECTED $p -> $(status_of "$p") (unchanged)"; done
echo; echo "next: reenroll-hosts.sh -> carve-host-interfaces.sh x4 -> maas-fabric-prune.sh -> phase-00-maas-standup.sh"
echo "OK (apply)"