153 lines
5.3 KiB
Bash
Executable file
153 lines
5.3 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
IFS=$'\n\t'
|
|
umask 077
|
|
|
|
readonly PROGRAM="ochenstarik-smm-emergency"
|
|
readonly STATE_DIR="/var/lib/ochenstarik-server-monitor-manager"
|
|
readonly ETC_DIR="/etc/ochenstarik-server-monitor-manager"
|
|
readonly MARKER="$STATE_DIR/mesh/emergency-disabled"
|
|
readonly RECONCILE_MARKER="$STATE_DIR/mesh/reconcile-requested"
|
|
readonly RECONCILE_LOCK="$STATE_DIR/mesh/reconcile-requested.lock"
|
|
readonly CONTROL_UNIT="ochenstarik-smm-control.service"
|
|
readonly AGENT_UNIT="ochenstarik-smm-agent.service"
|
|
readonly FIREWALL_UNIT="ochenstarik-smm-firewall.service"
|
|
readonly WIREGUARD_UNIT="wg-quick@smm0.service"
|
|
readonly NFT_FAMILY="inet"
|
|
readonly NFT_TABLE="ochenstarik_smm"
|
|
|
|
fail() { printf '%s: %s\n' "$PROGRAM" "$*" >&2; exit 78; }
|
|
log() { printf '%s: %s\n' "$PROGRAM" "$*"; }
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Local Server Monitor Manager emergency recovery
|
|
|
|
Usage:
|
|
ochenstarik-smm-emergency status
|
|
ochenstarik-smm-emergency mesh-disable
|
|
ochenstarik-smm-emergency mesh-enable
|
|
ochenstarik-smm-emergency firewall-restore
|
|
|
|
Commands use only Server Monitor Manager-owned units, interface and nftables
|
|
table. They do not modify the host SSH service or unrelated firewall rules.
|
|
EOF
|
|
}
|
|
|
|
require_root() {
|
|
[[ ${EUID:-$(id -u)} -eq 0 ]] || fail "this action must run as root (use sudo)"
|
|
}
|
|
|
|
unit_state() {
|
|
local unit="$1"
|
|
if systemctl list-unit-files "$unit" --no-legend 2>/dev/null | grep -q "^$unit"; then
|
|
systemctl is-active "$unit" 2>/dev/null || true
|
|
else
|
|
printf '%s\n' "not-installed"
|
|
fi
|
|
}
|
|
|
|
show_status() {
|
|
local unit
|
|
for unit in "$CONTROL_UNIT" "$AGENT_UNIT" "$FIREWALL_UNIT" "$WIREGUARD_UNIT"; do
|
|
printf '%s: %s\n' "$unit" "$(unit_state "$unit")"
|
|
done
|
|
if command -v ip >/dev/null 2>&1 && ip link show smm0 >/dev/null 2>&1; then
|
|
printf '%s\n' "mesh-interface: present"
|
|
else
|
|
printf '%s\n' "mesh-interface: absent"
|
|
fi
|
|
if command -v nft >/dev/null 2>&1 && nft list table "$NFT_FAMILY" "$NFT_TABLE" >/dev/null 2>&1; then
|
|
printf '%s\n' "mesh-firewall: loaded"
|
|
else
|
|
printf '%s\n' "mesh-firewall: absent"
|
|
fi
|
|
[[ -f "$MARKER" ]] && printf '%s\n' "emergency-lock: active" || printf '%s\n' "emergency-lock: inactive"
|
|
printf '%s\n' "backups:"
|
|
find "$STATE_DIR/bootstrap-backups" -maxdepth 1 -type f \
|
|
\( -name '*.tar.gz' -o -name '*.empty' \) -printf ' %f\n' 2>/dev/null | sort -r | head -n 10 || true
|
|
}
|
|
|
|
delete_project_firewall() {
|
|
if command -v nft >/dev/null 2>&1; then
|
|
nft delete table "$NFT_FAMILY" "$NFT_TABLE" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
request_reconciliation() {
|
|
local marker_directory temporary_marker generation
|
|
marker_directory="$(dirname "$RECONCILE_MARKER")"
|
|
install -d -o root -g root -m 0700 "$marker_directory"
|
|
exec 9>"$RECONCILE_LOCK"
|
|
chown root:root "$RECONCILE_LOCK"
|
|
chmod 0600 "$RECONCILE_LOCK"
|
|
/usr/bin/flock -x 9
|
|
generation="$(</proc/sys/kernel/random/uuid)"
|
|
temporary_marker="$(mktemp "$marker_directory/.reconcile-requested.XXXXXXXX")"
|
|
if ! printf '%s\n' "$generation" >"$temporary_marker" \
|
|
|| ! chown root:root "$temporary_marker" \
|
|
|| ! chmod 0600 "$temporary_marker" \
|
|
|| ! mv -f -- "$temporary_marker" "$RECONCILE_MARKER"; then
|
|
rm -f -- "$temporary_marker"
|
|
fail "could not create the Link reconciliation request"
|
|
fi
|
|
}
|
|
|
|
mesh_disable() {
|
|
require_root
|
|
systemctl disable --now "$WIREGUARD_UNIT" 2>/dev/null || true
|
|
systemctl disable --now "$FIREWALL_UNIT" 2>/dev/null || true
|
|
delete_project_firewall
|
|
install -d -m 0700 "$(dirname "$MARKER")"
|
|
printf '%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >"$MARKER"
|
|
chmod 0600 "$MARKER"
|
|
log "Mesh disabled locally; Control and SSH were not changed."
|
|
}
|
|
|
|
restore_project_firewall() {
|
|
require_root
|
|
[[ -f "$ETC_DIR/mesh.nft" ]] || fail "managed firewall configuration is missing"
|
|
command -v nft >/dev/null 2>&1 || fail "nft is unavailable"
|
|
if ! nft list table "$NFT_FAMILY" "$NFT_TABLE" >/dev/null 2>&1; then
|
|
nft --check -f "$ETC_DIR/mesh.nft"
|
|
fi
|
|
delete_project_firewall
|
|
if ! nft -f "$ETC_DIR/mesh.nft"; then
|
|
systemctl disable --now "$WIREGUARD_UNIT" 2>/dev/null || true
|
|
fail "managed firewall restore failed; Mesh was disabled to fail closed"
|
|
fi
|
|
if systemctl list-unit-files "$FIREWALL_UNIT" --no-legend 2>/dev/null | grep -q "^$FIREWALL_UNIT"; then
|
|
systemctl enable "$FIREWALL_UNIT" >/dev/null
|
|
fi
|
|
request_reconciliation
|
|
log "Base deny-by-default Mesh firewall restored; Control must reconcile active Links."
|
|
}
|
|
|
|
mesh_enable() {
|
|
require_root
|
|
[[ -f /etc/wireguard/smm0.conf ]] || fail "WireGuard smm0 configuration is missing"
|
|
if [[ -f "$ETC_DIR/mesh.nft" ]]; then
|
|
restore_project_firewall
|
|
fi
|
|
systemctl enable "$WIREGUARD_UNIT" >/dev/null
|
|
systemctl restart "$WIREGUARD_UNIT"
|
|
request_reconciliation
|
|
rm -f -- "$MARKER"
|
|
log "Mesh enabled locally."
|
|
}
|
|
|
|
main() {
|
|
local action="${1:-help}"
|
|
shift || true
|
|
[[ $# -eq 0 ]] || fail "unexpected arguments"
|
|
case "$action" in
|
|
help|-h|--help) usage ;;
|
|
status) show_status ;;
|
|
mesh-disable) mesh_disable ;;
|
|
mesh-enable) mesh_enable ;;
|
|
firewall-restore) restore_project_firewall ;;
|
|
*) fail "unknown action: $action" ;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|