mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 17:11:30 +00:00
228 lines
9.2 KiB
Bash
Executable File
228 lines
9.2 KiB
Bash
Executable File
#!/bin/bash
|
|
# node-reset.sh — return one Spinifex node to a freshly-installed state.
|
|
#
|
|
# Teardown only: it stops services, removes state, and exits. It installs
|
|
# nothing, starts nothing and decides nothing. Callers do the rebuilding —
|
|
# reset-dev-env.sh locally, install-node.sh --wipe over SSH.
|
|
#
|
|
# Usage:
|
|
# sudo scripts/node-reset.sh [--keep-data] [--yes] [--dry-run]
|
|
#
|
|
# Options:
|
|
# --keep-data Leave /var/lib/spinifex alone: volumes, S3 objects and
|
|
# JetStream survive. For when only the control plane is suspect.
|
|
# --yes Skip the confirmation prompt
|
|
# --dry-run Print what would be removed and touch nothing
|
|
#
|
|
# WHAT THIS DESTROYS
|
|
#
|
|
# Every instance on this node and every volume backing them, the node's CA and
|
|
# master key, all OVN logical network state, and the S3 objects held here. None
|
|
# of it is recoverable. Data sealed under the master key cannot be read back
|
|
# even if the bytes are restored from elsewhere.
|
|
set -euo pipefail
|
|
|
|
ETC_DIR=/etc/spinifex
|
|
DATA_DIR=/var/lib/spinifex
|
|
LOG_DIR=/var/log/spinifex
|
|
RUN_DIR=/run/spinifex
|
|
|
|
KEEP_DATA=false
|
|
ASSUME_YES=false
|
|
DRY_RUN=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--keep-data) KEEP_DATA=true ;;
|
|
--yes | -y) ASSUME_YES=true ;;
|
|
--dry-run) DRY_RUN=true ;;
|
|
-h | --help)
|
|
sed -n '2,23p' "$0" | sed 's/^# \?//'
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "ERROR: unknown option: $1" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
log() { echo "[node-reset] $*"; }
|
|
run() {
|
|
if $DRY_RUN; then
|
|
echo " would run: $*"
|
|
return 0
|
|
fi
|
|
"$@"
|
|
}
|
|
|
|
WIPE_DIRS=("$ETC_DIR" "$LOG_DIR" "$RUN_DIR")
|
|
$KEEP_DATA || WIPE_DIRS+=("$DATA_DIR")
|
|
|
|
# Directories and symlinks are structure, not state: setup.sh builds both and
|
|
# nothing in a cluster is stored as either. Two of those symlinks are load
|
|
# bearing and easy to miss — /var/lib/spinifex/config and awsgw/config both
|
|
# point at /etc/spinifex, the second nested a level down, and awsgw reads its
|
|
# IAM master key through it. Keeping every symlink covers them without this
|
|
# script having to know where setup.sh put them.
|
|
#
|
|
# keep_args adds the regular files worth keeping: the service helper scripts
|
|
# and the env files the units load, at the top level of $1 only. A *.sh deeper
|
|
# in the tree is cluster state, not an installed helper.
|
|
KEEP_TOP=()
|
|
keep_args() {
|
|
KEEP_TOP=(-path "$1/*" ! -path "$1/*/*" '(' -name '*.sh' -o -name '*.env' ')')
|
|
}
|
|
|
|
# Report what is at stake in figures rather than adjectives. An operator who
|
|
# reads "3 instances, 12 volumes, 840G" makes a better decision than one who
|
|
# reads a warning banner.
|
|
# `|| true` on each: under pipefail a find over a directory that was never
|
|
# created fails the whole pipeline, and a node with nothing to report is the
|
|
# most ordinary case there is.
|
|
if [ -d "$DATA_DIR" ]; then
|
|
instances=$(sudo find "$DATA_DIR/instances" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | wc -l || true)
|
|
volumes=$(sudo find "$DATA_DIR/volumes" -maxdepth 1 -mindepth 1 2>/dev/null | wc -l || true)
|
|
size=$(sudo du -sh "$DATA_DIR" 2>/dev/null | cut -f1 || true)
|
|
log "on $(hostname): $instances instance(s), $volumes volume(s), ${size:-unknown} under $DATA_DIR"
|
|
$KEEP_DATA && log " --keep-data: $DATA_DIR will be preserved"
|
|
fi
|
|
log "removing: ${WIPE_DIRS[*]}"
|
|
|
|
if ! $ASSUME_YES && ! $DRY_RUN; then
|
|
read -r -p "Destroy this node's state? Type 'destroy' to continue: " reply
|
|
[ "$reply" = "destroy" ] || { log "aborted"; exit 1; }
|
|
fi
|
|
|
|
log "stopping services"
|
|
run sudo systemctl stop spinifex.target 2>/dev/null || true
|
|
run sudo systemctl reset-failed 'spinifex-*' 2>/dev/null || true
|
|
run sudo pkill -x qemu-system-x86_64 2>/dev/null || true
|
|
run sudo pkill -x qemu-system-aarch64 2>/dev/null || true
|
|
|
|
# Viperblock state must not be torn out from under a live guest, so wait for
|
|
# QEMU to actually exit rather than assuming the signal was enough.
|
|
if ! $DRY_RUN; then
|
|
elapsed=0
|
|
while pgrep -x 'qemu-system-x86_64|qemu-system-aarch64' >/dev/null 2>&1; do
|
|
if [ "$elapsed" -ge 30 ]; then
|
|
echo "ERROR: QEMU still running after 30s:" >&2
|
|
pgrep -af 'qemu-system-' >&2 || true
|
|
echo " Kill them manually and re-run." >&2
|
|
exit 1
|
|
fi
|
|
sleep 1
|
|
elapsed=$((elapsed + 1))
|
|
done
|
|
fi
|
|
|
|
# Clearing external_ids drops system-id along with it. That is the point: a
|
|
# node keeping its old system-id across a reset re-registers under the old
|
|
# chassis name and collides with the new one over the encap IP.
|
|
log "removing OVS bridges and identity"
|
|
if command -v ovs-vsctl >/dev/null 2>&1; then
|
|
run sudo systemctl start openvswitch-switch 2>/dev/null || true
|
|
$DRY_RUN || sleep 1
|
|
# Listed even under --dry-run: "which bridges" is the question an operator
|
|
# actually has here, and br-wan is a Linux bridge so it is never in this set.
|
|
for br in $(sudo ovs-vsctl list-br 2>/dev/null || true); do
|
|
log " deleting bridge: $br"
|
|
run sudo ovs-vsctl --if-exists del-br "$br"
|
|
done
|
|
run sudo ovs-vsctl --if-exists clear Open_vSwitch . external_ids 2>/dev/null || true
|
|
run sudo systemctl stop openvswitch-switch 2>/dev/null || true
|
|
fi
|
|
run sudo rm -f /etc/openvswitch/system-id.conf
|
|
|
|
# Delete the OVN DBs outright — a caller re-running setup-ovn.sh gets fresh
|
|
# empty ones. This is what clears stale chassis rows and port bindings, which
|
|
# otherwise accumulate across resets and wedge ovn-controller in a commit loop.
|
|
log "removing OVN databases"
|
|
run sudo systemctl stop ovn-central 2>/dev/null || true
|
|
run sudo systemctl stop ovn-controller 2>/dev/null || true
|
|
run sudo rm -f /var/lib/ovn/ovnnb_db.db /var/lib/ovn/ovnsb_db.db
|
|
|
|
if ip link show veth-wan-br >/dev/null 2>&1; then
|
|
log " deleting veth pair: veth-wan-br <-> veth-wan-ovs"
|
|
run sudo ip link del veth-wan-br 2>/dev/null || true
|
|
fi
|
|
|
|
# Without this, systemd-networkd recreates the veth on the next reboot even
|
|
# after a full reset.
|
|
if [ -e /etc/systemd/network/15-spinifex-veth-wan.netdev ] ||
|
|
[ -e /etc/systemd/network/15-spinifex-veth-wan.network ] ||
|
|
[ -e /etc/systemd/network/16-spinifex-veth-wan-ovs.network ]; then
|
|
log " deleting veth persistence units"
|
|
run sudo rm -f /etc/systemd/network/15-spinifex-veth-wan.netdev \
|
|
/etc/systemd/network/15-spinifex-veth-wan.network \
|
|
/etc/systemd/network/16-spinifex-veth-wan-ovs.network
|
|
run sudo networkctl reload 2>/dev/null || true
|
|
fi
|
|
|
|
# `rm -rf` is wrong here on two counts. On an ISO-installed node several of
|
|
# these are separate ZFS datasets — rpool/log and rpool/data/{nats,viperblock,
|
|
# predastore,predastore-db,predastore-nodes} — and a mountpoint cannot be
|
|
# removed at all. And the directory tree is itself install state worth keeping:
|
|
# it carries the ownership setup.sh assigned per service.
|
|
log "wiping ${WIPE_DIRS[*]}"
|
|
for dir in "${WIPE_DIRS[@]}"; do
|
|
[ -d "$dir" ] || continue
|
|
if $DRY_RUN; then
|
|
echo " would empty: $dir"
|
|
continue
|
|
fi
|
|
keep_args "$dir"
|
|
# Regular files only, so directories and symlinks survive along with the
|
|
# per-service ownership setup.sh assigned. Nothing then has to be
|
|
# reinstalled afterwards, which is what lets this script install nothing
|
|
# and leave a node on the exact build it was already running.
|
|
sudo find "$dir" -mindepth 1 \( -type f -o -type s -o -type p \) \
|
|
! \( "${KEEP_TOP[@]}" \) -delete 2>/dev/null || true
|
|
done
|
|
|
|
# Directories may survive — a mountpoint cannot be removed, and neither can the
|
|
# path leading down to one. Files may not: a surviving file is state that would
|
|
# carry into the new cluster, which is the exact failure this script exists to
|
|
# prevent, so refuse rather than hand back a node that looks clean.
|
|
if ! $DRY_RUN; then
|
|
for dir in "${WIPE_DIRS[@]}"; do
|
|
[ -d "$dir" ] || continue
|
|
keep_args "$dir"
|
|
left=$(sudo find "$dir" -mindepth 1 ! -type d ! -type l \
|
|
! \( "${KEEP_TOP[@]}" \) 2>/dev/null | head -5)
|
|
if [ -n "$left" ]; then
|
|
echo "ERROR: $dir still holds files after the wipe:" >&2
|
|
echo "$left" | sed 's/^/ /' >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [ ! -d /etc/spinifex ]; then
|
|
echo "ERROR: /etc/spinifex is missing and could not be restored." >&2
|
|
echo " spx would treat this node as a dev install and build it under ~/spinifex," >&2
|
|
echo " forming a cluster whose services can never start. Re-run the installer." >&2
|
|
$DRY_RUN || exit 1
|
|
fi
|
|
|
|
# A node that has already been through that fallback carries a stray dev-layout
|
|
# tree holding its own keys and configs. It is not user data — it is wreckage
|
|
# from a misdetected layout — and leaving it invites a later dev-mode run to
|
|
# adopt it.
|
|
DEV_ROOT="$(getent passwd spinifex | cut -d: -f6)/spinifex"
|
|
if [ -n "${DEV_ROOT#/spinifex}" ] && [ -d "$DEV_ROOT" ]; then
|
|
log "removing the dev-layout fallback tree at $DEV_ROOT"
|
|
run sudo rm -rf "$DEV_ROOT"
|
|
fi
|
|
|
|
# The next init writes a fresh CA. Leaving the old one trusted means the host
|
|
# trusts a CA nobody holds the key for.
|
|
if [ -f /usr/local/share/ca-certificates/spinifex-ca.crt ]; then
|
|
log "removing the stale CA from the trust store"
|
|
run sudo rm -f /usr/local/share/ca-certificates/spinifex-ca.crt
|
|
run sudo update-ca-certificates
|
|
fi
|
|
|
|
log "done — node is installed but uninitialized, ready to init or join"
|