Merge pull request #60 from ochenstarik-ui/codex/uninstall-mode

feat: add complete server uninstall mode
This commit is contained in:
ochenstarik-ui 2026-08-18 11:28:24 +07:00 committed by GitHub
commit 0bbee800dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 470 additions and 19 deletions

View file

@ -11,13 +11,18 @@ readonly ENROLLMENT_DIR="${STATE_DIR}-enrollment"
readonly BACKUP_DIR="${STATE_DIR}/bootstrap-backups"
readonly CONTROL_USER="ochenstarik-smm-control"
readonly AGENT_USER="ochenstarik-smm-agent"
readonly MONITOR_USER="ochenstarik-monitor"
readonly MONITOR_HOME="/var/lib/ochenstarik-monitor"
readonly CONTROL_UNIT="ochenstarik-smm-control.service"
readonly AGENT_UNIT="ochenstarik-smm-agent.service"
readonly PROVISIONING_HELPER_UNIT="ochenstarik-smm-provisioning-helper.service"
readonly POLICY_HELPER="/usr/local/libexec/ochenstarik-smm-policy-apply"
readonly EMERGENCY_COMMAND="/usr/local/sbin/ochenstarik-smm-emergency"
readonly BOOTSTRAP_COMMAND="/usr/local/sbin/ochenstarik-server-monitor-manager.sh"
readonly METRICS_SCRIPT="/usr/local/libexec/ochenstarik-smm-metrics"
readonly SUDOERS_FILE="/etc/sudoers.d/ochenstarik-smm-control"
readonly SYSCTL_FILE="/etc/sysctl.d/90-ochenstarik-smm-mesh.conf"
readonly SYSTEM_WG_CONFIG="/etc/wireguard/smm0.conf"
readonly MESH_DIR="${STATE_DIR}/mesh"
readonly WG_DIR="${ETC_DIR}/wireguard"
readonly FIREWALL_UNIT="ochenstarik-smm-firewall.service"
@ -91,6 +96,8 @@ Usage:
ochenstarik-server-monitor-manager.sh status
ochenstarik-server-monitor-manager.sh uninstall-agent [--purge]
ochenstarik-server-monitor-manager.sh uninstall-control --confirm-destroy-control
ochenstarik-server-monitor-manager.sh uninstall-system-plan [--purge-data]
ochenstarik-server-monitor-manager.sh uninstall-system --confirm-uninstall [--purge-data --confirm-destroy-data]
ochenstarik-server-monitor-manager.sh version
ARCHIVE must have a matching ARCHIVE.sha256 file. Agent enrollment reads the
@ -1438,10 +1445,9 @@ install_monitor() {
local public_key="$1"
require_root
validate_platform
local metrics_script="/usr/local/libexec/ochenstarik-smm-metrics"
local monitor_user="ochenstarik-monitor"
local monitor_home="/var/lib/ochenstarik-monitor"
local metrics_script="$METRICS_SCRIPT"
local monitor_user="$MONITOR_USER"
local monitor_home="$MONITOR_HOME"
if ! id -u "$monitor_user" >/dev/null 2>&1; then
useradd -r -s /usr/sbin/nologin -d "$monitor_home" -M "$monitor_user"
@ -1516,9 +1522,9 @@ EOF
uninstall_monitor() {
require_root
local monitor_user="ochenstarik-monitor"
local monitor_home="/var/lib/ochenstarik-monitor"
local metrics_script="/usr/local/libexec/ochenstarik-smm-metrics"
local monitor_user="$MONITOR_USER"
local monitor_home="$MONITOR_HOME"
local metrics_script="$METRICS_SCRIPT"
if id -u "$monitor_user" >/dev/null 2>&1; then
userdel -f "$monitor_user" || true
@ -1543,6 +1549,215 @@ uninstall_control() {
log "Control role and its state were removed."
}
owned_unit_exists() {
local unit="$1"
[[ -e "/etc/systemd/system/$unit" || -L "/etc/systemd/system/$unit" ]] && return 0
systemctl list-unit-files "$unit" --no-legend 2>/dev/null | grep -q "^${unit}[[:space:]]"
}
path_has_content() {
local path="$1"
[[ -f "$path" || -L "$path" ]] && return 0
[[ -d "$path" ]] || return 1
find "$path" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null | grep -q .
}
uninstall_system_plan() {
local purge_data="${1:-}" found=0 unit user path
local -a units=(
"$CONTROL_UNIT" "$AGENT_UNIT" "$PROVISIONING_HELPER_UNIT"
"$FIREWALL_UNIT" wg-quick@smm0.service
)
local -a users=("$CONTROL_USER" "$AGENT_USER" "$MONITOR_USER")
local -a files=(
"$POLICY_HELPER" "$METRICS_SCRIPT" "$EMERGENCY_COMMAND" "$BOOTSTRAP_COMMAND"
"$SUDOERS_FILE" "$SYSCTL_FILE" "$SYSTEM_WG_CONFIG"
)
local -a program_paths=("$LIB_DIR" "$MONITOR_HOME" "$ENROLLMENT_DIR")
printf '%s\n' 'Server Monitor Manager objects found on this machine:'
for unit in "${units[@]}"; do
if owned_unit_exists "$unit"; then
printf ' unit: %s\n' "$unit"
found=1
fi
done
for user in "${users[@]}"; do
if id -u "$user" >/dev/null 2>&1; then
printf ' user/group: %s\n' "$user"
found=1
fi
done
if command -v nft >/dev/null 2>&1 \
&& nft list table inet ochenstarik_smm >/dev/null 2>&1; then
printf '%s\n' ' nftables table: inet ochenstarik_smm'
found=1
fi
if command -v ip >/dev/null 2>&1 && ip link show smm0 >/dev/null 2>&1; then
printf '%s\n' ' network interface: smm0'
found=1
fi
for path in "${files[@]}" "${program_paths[@]}"; do
if path_has_content "$path"; then
printf ' path: %s\n' "$path"
found=1
fi
done
for path in "$ETC_DIR" "$STATE_DIR"; do
if path_has_content "$path"; then
if [[ "$purge_data" == "--purge-data" ]]; then
printf ' data path: %s\n' "$path"
else
printf ' program files under: %s (Control database, backups, and CA are preserved)\n' "$path"
fi
found=1
fi
done
if [[ "$found" == "0" ]]; then
printf '%s\n' ' nothing installed'
fi
}
stop_owned_services() {
local unit
local -a units=(
"$CONTROL_UNIT" "$AGENT_UNIT" "$PROVISIONING_HELPER_UNIT"
"$FIREWALL_UNIT" wg-quick@smm0.service
)
for unit in "${units[@]}"; do
systemctl disable --now "$unit" >/dev/null 2>&1 || true
done
}
terminate_owned_processes() {
local user running=0
local -a users=("$CONTROL_USER" "$AGENT_USER" "$MONITOR_USER")
for user in "${users[@]}"; do
if id -u "$user" >/dev/null 2>&1 && pgrep -u "$user" >/dev/null 2>&1; then
pkill -TERM -u "$user" || true
running=1
fi
done
[[ "$running" == "0" ]] || sleep 1
for user in "${users[@]}"; do
if id -u "$user" >/dev/null 2>&1 && pgrep -u "$user" >/dev/null 2>&1; then
pkill -KILL -u "$user" || true
fi
done
for user in "${users[@]}"; do
if id -u "$user" >/dev/null 2>&1 && pgrep -u "$user" >/dev/null 2>&1; then
fail "Processes owned by $user are still running."
fi
done
}
remove_owned_accounts() {
local user
local -a users=("$CONTROL_USER" "$AGENT_USER" "$MONITOR_USER")
for user in "${users[@]}"; do
if id -u "$user" >/dev/null 2>&1; then
userdel "$user"
fi
done
for user in "${users[@]}"; do
if getent group "$user" >/dev/null 2>&1; then
groupdel "$user" || log "Group $user remains because it is still in use."
fi
done
}
remove_owned_network() {
if command -v ip >/dev/null 2>&1 && ip link show smm0 >/dev/null 2>&1; then
ip link delete smm0
fi
if command -v nft >/dev/null 2>&1 \
&& nft list table inet ochenstarik_smm >/dev/null 2>&1; then
nft delete table inet ochenstarik_smm
fi
}
remove_owned_files() {
local purge_data="$1"
rm -f -- \
"/etc/systemd/system/$CONTROL_UNIT" \
"/etc/systemd/system/$AGENT_UNIT" \
"/etc/systemd/system/$PROVISIONING_HELPER_UNIT" \
"/etc/systemd/system/$FIREWALL_UNIT" \
"$POLICY_HELPER" "$METRICS_SCRIPT" "$EMERGENCY_COMMAND" \
"$SUDOERS_FILE" "$SYSCTL_FILE" "$SYSTEM_WG_CONFIG"
rm -rf -- "$LIB_DIR" "$MONITOR_HOME" "$ENROLLMENT_DIR"
if [[ "$purge_data" == "--purge-data" ]]; then
rm -rf -- "$ETC_DIR" "$STATE_DIR"
else
rm -f -- \
"$ETC_DIR/agent.env" "$ETC_DIR/control.env" \
"$ETC_DIR/control-public-url" "$ETC_DIR/control-server.pfx" \
"$ETC_DIR/mesh.env" "$ETC_DIR/mesh.nft" \
"$ETC_DIR/installed-version-agent" "$ETC_DIR/installed-version-control"
rm -rf -- "$WG_DIR" "$STATE_DIR/agent" "$STATE_DIR/mesh" "$STATE_DIR/provisioning"
fi
rm -f -- "$BOOTSTRAP_COMMAND"
rmdir "$ETC_DIR" "$STATE_DIR" 2>/dev/null || true
systemctl daemon-reload
systemctl reset-failed >/dev/null 2>&1 || true
}
port_state() {
local port="$1"
if command -v ss >/dev/null 2>&1 \
&& ss -H -lntu "sport = :$port" 2>/dev/null | grep -q .; then
printf '%s' 'in-use'
else
printf '%s' 'free'
fi
}
show_uninstall_result() {
local unit user units=() users=()
printf 'port-7443: %s\n' "$(port_state 7443)"
printf 'port-51820: %s\n' "$(port_state 51820)"
if command -v ip >/dev/null 2>&1 \
&& ip route show 2>/dev/null | grep -Fq '10.77.0.'; then
printf '%s\n' 'mesh-routes: present'
else
printf '%s\n' 'mesh-routes: absent'
fi
while IFS= read -r unit; do
[[ -n "$unit" ]] && units+=("${unit%%[[:space:]]*}")
done < <(systemctl list-unit-files 'ochenstarik-smm-*' --no-legend 2>/dev/null || true)
if (( ${#units[@]} == 0 )); then
printf '%s\n' 'owned-units: absent'
else
printf 'owned-units: present (%s)\n' "${units[*]}"
fi
for user in "$CONTROL_USER" "$AGENT_USER" "$MONITOR_USER"; do
id -u "$user" >/dev/null 2>&1 && users+=("$user")
done
if (( ${#users[@]} == 0 )); then
printf '%s\n' 'owned-users: absent'
else
printf 'owned-users: present (%s)\n' "${users[*]}"
fi
}
uninstall_system() {
local confirmation="${1:-}" purge_data="${2:-}" data_confirmation="${3:-}"
[[ "$confirmation" == "--confirm-uninstall" ]] \
|| fail "System removal requires --confirm-uninstall"
if [[ -n "$purge_data" ]]; then
[[ "$purge_data" == "--purge-data" && "$data_confirmation" == "--confirm-destroy-data" ]] \
|| fail "Permanent data removal requires --purge-data --confirm-destroy-data"
fi
require_root
uninstall_system_plan "$purge_data"
stop_owned_services
terminate_owned_processes
remove_owned_network
remove_owned_files "$purge_data"
remove_owned_accounts
show_uninstall_result
log "System removal completed${purge_data:+, including Control data and CA}."
}
preflight() {
validate_platform
local command_name
@ -1580,6 +1795,8 @@ main() {
status) [[ $# -eq 0 ]] || fail "status takes no arguments"; show_status ;;
uninstall-agent) [[ $# -eq 0 || ( $# -eq 1 && "$1" == "--purge" ) ]] || fail "uninstall-agent accepts only [--purge]"; uninstall_agent "${1:-}" ;;
uninstall-control) [[ $# -eq 1 ]] || fail "uninstall-control requires confirmation"; uninstall_control "$1" ;;
uninstall-system-plan) [[ $# -eq 0 || ( $# -eq 1 && "$1" == "--purge-data" ) ]] || fail "uninstall-system-plan accepts only [--purge-data]"; uninstall_system_plan "${1:-}" ;;
uninstall-system) [[ $# -eq 1 || $# -eq 3 ]] || fail "uninstall-system requires --confirm-uninstall [--purge-data --confirm-destroy-data]"; uninstall_system "$@" ;;
*) fail "Unknown action: $action (run with --help)" ;;
esac
}

View file

@ -23,9 +23,10 @@ Usage:
smm-setup.sh [--tag TAG] [--repository OWNER/REPO] COMMAND [ARG...]
With no arguments, an interactive terminal guides a complete Hub or Node
installation. Non-interactive commands remain available:
installation or a full removal. Non-interactive commands remain available:
install-hub PUBLIC_HOST [HTTPS_PORT] [WG_PORT]
install-node
uninstall-system --confirm-uninstall [--purge-data --confirm-destroy-data]
Other commands are passed to the verified ochenstarik-server-monitor-manager.sh
asset. Use -- before a command to force pass-through. Common commands:
@ -45,6 +46,7 @@ smm-setup: interactive installation requires a terminal on stdin and stdout.
For automation, choose an explicit command:
sudo ./smm-setup.sh install-hub PUBLIC_HOST [HTTPS_PORT] [WG_PORT]
sudo ./smm-setup.sh install-node
sudo ./smm-setup.sh uninstall-system --confirm-uninstall
Run ./smm-setup.sh --help for pass-through commands.
HELP
}
@ -168,7 +170,7 @@ inspect_node_code() {
choose_interactive_action() {
local role host https_port wg_port existing default_host
show_machine
read -r -p 'Choose role: 1) Hub 2) Node: ' role
read -r -p 'Choose role: 1) Hub 2) Node 3) Uninstall: ' role
case "${role,,}" in
1|hub)
action=install-hub; existing="$(role_status Hub)"
@ -186,7 +188,12 @@ choose_interactive_action() {
[[ -n "$node_code" ]] || die "SMMNODE2 code is empty"
action_args=()
;;
*) die "choose Hub (1) or Node (2)" ;;
3|uninstall|remove)
action=uninstall-system
action_args=()
return
;;
*) die "choose Hub (1), Node (2), or Uninstall (3)" ;;
esac
if [[ "$existing" == "installed" ]] && ! confirm "This role is already installed. Reinstall or update it?"; then
printf 'No changes were made.\n'; exit 0
@ -201,6 +208,31 @@ choose_interactive_action() {
fi
}
run_interactive_uninstall() {
local depth answer
local -a plan_args=()
printf '\nRemoval depth:\n'
printf '%s\n' ' 1) Remove programs and accounts; preserve Control database, backups, and CA'
printf '%s\n' ' 2) Permanently remove everything, including Control database, backups, and CA'
read -r -p 'Choose removal depth [1]: ' depth
case "${depth:-1}" in
1|preserve) ;;
2|purge) plan_args=(--purge-data) ;;
*) die "choose removal depth 1 or 2" ;;
esac
printf '\n'
"$cached_script" uninstall-system-plan "${plan_args[@]}"
read -r -p 'Type UNINSTALL to remove the objects listed above: ' answer
[[ "$answer" == "UNINSTALL" ]] || die "removal cancelled: confirmation word did not match"
if (( ${#plan_args[@]} > 0 )); then
read -r -p 'Type DESTROY-DATA to permanently delete Control data, backups, and CA: ' answer
[[ "$answer" == "DESTROY-DATA" ]] || die "removal cancelled: data-destruction confirmation did not match"
exec "$cached_script" uninstall-system --confirm-uninstall --purge-data --confirm-destroy-data
fi
exec "$cached_script" uninstall-system --confirm-uninstall
}
original_count=$#
pass_through=0
while [[ $# -gt 0 ]]; do
@ -232,6 +264,15 @@ if (( pass_through == 0 )); then
case "$action" in
install-hub) [[ ${#action_args[@]} -ge 1 && ${#action_args[@]} -le 3 ]] || die "install-hub requires PUBLIC_HOST [HTTPS_PORT] [WG_PORT]" ;;
install-node) [[ ${#action_args[@]} -eq 0 ]] || die "install-node takes no arguments" ;;
uninstall-system)
if (( interactive == 0 )); then
[[ ( ${#action_args[@]} -eq 1 && "${action_args[0]}" == "--confirm-uninstall" ) \
|| ( ${#action_args[@]} -eq 3 && "${action_args[0]}" == "--confirm-uninstall" \
&& "${action_args[1]}" == "--purge-data" \
&& "${action_args[2]}" == "--confirm-destroy-data" ) ]] \
|| die "uninstall-system requires --confirm-uninstall [--purge-data --confirm-destroy-data]"
fi
;;
esac
fi
@ -311,5 +352,9 @@ case "$action" in
exec "$cached_script" install-node "$archive"
fi
;;
uninstall-system)
if (( interactive == 1 )); then run_interactive_uninstall; fi
exec "$cached_script" "$action" "${action_args[@]}"
;;
*) exec "$cached_script" "$action" "${action_args[@]}" ;;
esac

View file

@ -21,7 +21,7 @@ chmod 700 smm-setup.sh
sudo ./smm-setup.sh
```
Интерактивный режим показывает дистрибутив, архитектуру, определённый публичный адрес и уже установленные роли, затем предлагает выбрать Hub или Node. Для Hub он предлагает публичный адрес и порты HTTPS `7443`/WireGuard `51820`. Для Node он принимает готовый `SMMNODE2`, показывает извлечённые адрес Hub и SHA-256 fingerprint CA и продолжает только после подтверждения оператора. Загрузки сопровождаются индикатором; системные зависимости и закреплённый `cosign` устанавливаются и проверяются до использования.
Интерактивный режим показывает дистрибутив, архитектуру, определённый публичный адрес и уже установленные роли, затем предлагает выбрать Hub, Node или полное удаление. Для Hub он предлагает публичный адрес и порты HTTPS `7443`/WireGuard `51820`. Для Node он принимает готовый `SMMNODE2`, показывает извлечённые адрес Hub и SHA-256 fingerprint CA и продолжает только после подтверждения оператора. Загрузки сопровождаются индикатором; системные зависимости и закреплённый `cosign` устанавливаются и проверяются до использования.
После установки Hub установщик печатает код регистрации устройства `SMMDEV1-...` и fingerprint CA — оба значения предназначены для подключения приложения оператора. После установки Node он печатает `SMMPEER1...`; вставьте этот код в карточку соответствующего Node в приложении оператора.
@ -146,6 +146,25 @@ sudo ./ochenstarik-server-monitor-manager.sh uninstall-agent --purge
sudo ./ochenstarik-server-monitor-manager.sh uninstall-control --confirm-destroy-control
```
## Полное удаление с сервера
Запустите `sudo ./smm-setup.sh`, выберите `3) Uninstall`, затем глубину очистки. Перед изменениями установщик показывает найденные units, пользователей, сетевые объекты и каталоги проекта. Удаление начинается только после ввода слова `UNINSTALL`, а необратимое удаление данных — после дополнительного ввода `DESTROY-DATA`.
Обычная глубина останавливает сервисы, завершает процессы системных пользователей проекта и удаляет программы, accounts, интерфейс `smm0` и только nftables-таблицу `inet ochenstarik_smm`. При этом сохраняются база Control, резервные копии bootstrap и локальный Control CA. Полная глубина удаляет также эти данные без возможности восстановления. Объекты `3x-ui` и другие посторонние users, units, интерфейсы, таблицы и файлы не затрагиваются.
Для автоматизации интерактивных вопросов нет; обязательные флаги являются явным подтверждением:
```bash
# Удалить программу, сохранив базу Control, backups и CA
sudo ./smm-setup.sh uninstall-system --confirm-uninstall
# Необратимо удалить также базу Control, backups и CA
sudo ./smm-setup.sh uninstall-system --confirm-uninstall \
--purge-data --confirm-destroy-data
```
Команда без `--confirm-uninstall`, а полная очистка без `--confirm-destroy-data`, завершается отказом до удаления. Повторный запуск на уже очищенной машине безопасен и возвращает успешный результат. В конце выводится состояние портов `7443` и `51820`, маршрутов `10.77.0.*`, units и пользователей проекта.
Update создаёт root-only backup перед заменой binaries и автоматически восстанавливает предыдущую версию, если сервис не запускается. Перед alpha-тестом на реальных серверах обязательно сохраните отдельную консольную/SSH-сессию и не закрывайте основной административный доступ firewall-правилами проекта.
## Локальное аварийное восстановление

View file

@ -2,11 +2,13 @@
set -Eeuo pipefail
IFS=$'\n\t'
archive="${1:?usage: run-native-systemd-smoke.sh ARCHIVE BOOTSTRAP}"
bootstrap="${2:?usage: run-native-systemd-smoke.sh ARCHIVE BOOTSTRAP}"
archive="$(realpath "${1:?usage: run-native-systemd-smoke.sh ARCHIVE BOOTSTRAP}")"
bootstrap="$(realpath "${2:?usage: run-native-systemd-smoke.sh ARCHIVE BOOTSTRAP}")"
port="${SMM_SMOKE_PORT:-17443}"
system_bootstrap="/usr/local/sbin/ochenstarik-server-monitor-manager.sh"
probe_dir=""
foreign_user="smm-uninstall-foreign"
foreign_unit="smm-uninstall-foreign.service"
# If manifest+sig are not shipped alongside the archive (CI-only builds),
# allow unsigned verification via .sha256 fallback.
@ -19,8 +21,11 @@ cleanup() {
if [[ -n "$probe_dir" ]]; then
rm -rf -- "$probe_dir"
fi
sudo "$system_bootstrap" uninstall-agent --purge >/dev/null 2>&1 || true
sudo "$system_bootstrap" uninstall-control --confirm-destroy-control >/dev/null 2>&1 || true
sudo "$bootstrap" uninstall-system --confirm-uninstall --purge-data \
--confirm-destroy-data >/dev/null 2>&1 || true
sudo rm -f -- "/etc/systemd/system/$foreign_unit"
sudo userdel "$foreign_user" >/dev/null 2>&1 || true
sudo systemctl daemon-reload >/dev/null 2>&1 || true
}
trap cleanup EXIT
@ -116,4 +121,30 @@ sudo curl --fail --silent --show-error --retry 15 --retry-all-errors --retry-del
--cacert /etc/ochenstarik-server-monitor-manager/control-ca.crt \
"https://127.0.0.1:$port/healthz"
sudo install -d -m 0700 /var/lib/ochenstarik-server-monitor-manager/bootstrap-backups
sudo touch /var/lib/ochenstarik-server-monitor-manager/bootstrap-backups/smoke-preserve
sudo useradd --system --no-create-home "$foreign_user"
printf '[Unit]\nDescription=Foreign smoke unit\n' | sudo tee "/etc/systemd/system/$foreign_unit" >/dev/null
sudo "$system_bootstrap" uninstall-system --confirm-uninstall
sudo test -s /var/lib/ochenstarik-server-monitor-manager/control/control.db
sudo test -f /var/lib/ochenstarik-server-monitor-manager/bootstrap-backups/smoke-preserve
sudo test -f /etc/ochenstarik-server-monitor-manager/control-ca.pfx
sudo test -f /etc/ochenstarik-server-monitor-manager/control-ca.crt
sudo id "$foreign_user"
sudo test -f "/etc/systemd/system/$foreign_unit"
if sudo ss -H -ltn "sport = :$port" | grep -q .; then
printf 'Control port survived uninstall: %s\n' "$port" >&2
exit 1
fi
sudo "$bootstrap" uninstall-system --confirm-uninstall --purge-data --confirm-destroy-data
sudo test ! -e /etc/ochenstarik-server-monitor-manager
sudo test ! -e /var/lib/ochenstarik-server-monitor-manager
sudo test ! -e /var/lib/ochenstarik-server-monitor-manager-enrollment
sudo id "$foreign_user"
sudo test -f "/etc/systemd/system/$foreign_unit"
empty_uninstall_output="$(sudo "$bootstrap" uninstall-system --confirm-uninstall \
--purge-data --confirm-destroy-data)"
grep -Fq 'nothing installed' <<<"$empty_uninstall_output"
printf '%s\n' "NATIVE_SYSTEMD_SMOKE=PASS"

View file

@ -12,6 +12,7 @@ port="17443"
smoke_dir="/root/smm-smoke"
remote_archive="$smoke_dir/release.tar.gz"
remote_bootstrap="$smoke_dir/ochenstarik-server-monitor-manager.sh"
system_bootstrap="/usr/local/sbin/ochenstarik-server-monitor-manager.sh"
cleanup() {
docker rm -f "$name" >/dev/null 2>&1 || true
@ -78,4 +79,63 @@ docker exec "$name" curl --fail --silent --show-error --retry 15 --retry-all-err
"https://127.0.0.1:$port/healthz"
docker exec "$name" /usr/local/sbin/ochenstarik-smm-emergency status
# Exercise both removal depths with live services, owned network objects, and
# similarly placed foreign objects that must survive.
docker exec "$name" install -d -m 0700 /var/lib/ochenstarik-server-monitor-manager/bootstrap-backups
docker exec "$name" touch /var/lib/ochenstarik-server-monitor-manager/bootstrap-backups/smoke-preserve
docker exec "$name" useradd --system --user-group --no-create-home ochenstarik-smm-agent
docker exec "$name" useradd --system --user-group --no-create-home ochenstarik-monitor
docker exec "$name" install -d -m 0700 -o ochenstarik-monitor -g ochenstarik-monitor /var/lib/ochenstarik-monitor
docker exec "$name" useradd --system --no-create-home smm-uninstall-foreign
docker exec "$name" sh -c "printf '[Unit]\nDescription=Foreign smoke unit\n' >/etc/systemd/system/smm-uninstall-foreign.service"
docker exec "$name" ip link add smm0 type dummy
docker exec "$name" nft add table inet ochenstarik_smm
docker exec "$name" "$system_bootstrap" uninstall-system --confirm-uninstall
docker exec "$name" test -s /var/lib/ochenstarik-server-monitor-manager/control/control.db
docker exec "$name" test -f /var/lib/ochenstarik-server-monitor-manager/bootstrap-backups/smoke-preserve
docker exec "$name" test -f /etc/ochenstarik-server-monitor-manager/control-ca.pfx
docker exec "$name" test -f /etc/ochenstarik-server-monitor-manager/control-ca.crt
docker exec "$name" id smm-uninstall-foreign
docker exec "$name" test -f /etc/systemd/system/smm-uninstall-foreign.service
docker exec "$name" sh -c "! ss -H -ltn 'sport = :$port' | grep -q ."
for owned_user in ochenstarik-smm-control ochenstarik-smm-agent ochenstarik-monitor; do
if docker exec "$name" id "$owned_user" >/dev/null 2>&1; then
printf 'owned user survived uninstall: %s\n' "$owned_user" >&2
exit 1
fi
done
for owned_unit in ochenstarik-smm-control.service ochenstarik-smm-agent.service \
ochenstarik-smm-provisioning-helper.service ochenstarik-smm-firewall.service; do
if docker exec "$name" test -e "/etc/systemd/system/$owned_unit"; then
printf 'owned unit survived uninstall: %s\n' "$owned_unit" >&2
exit 1
fi
done
if docker exec "$name" ip link show smm0 >/dev/null 2>&1; then
printf '%s\n' 'owned smm0 interface survived uninstall' >&2
exit 1
fi
if docker exec "$name" nft list table inet ochenstarik_smm >/dev/null 2>&1; then
printf '%s\n' 'owned nftables table survived uninstall' >&2
exit 1
fi
docker exec "$name" "$remote_bootstrap" uninstall-system --confirm-uninstall \
--purge-data --confirm-destroy-data
for owned_path in /etc/ochenstarik-server-monitor-manager \
/var/lib/ochenstarik-server-monitor-manager \
/var/lib/ochenstarik-server-monitor-manager-enrollment \
/var/lib/ochenstarik-monitor; do
if docker exec "$name" test -e "$owned_path"; then
printf 'owned path survived purge: %s\n' "$owned_path" >&2
exit 1
fi
done
docker exec "$name" id smm-uninstall-foreign
docker exec "$name" test -f /etc/systemd/system/smm-uninstall-foreign.service
empty_uninstall_output="$(docker exec "$name" "$remote_bootstrap" uninstall-system \
--confirm-uninstall --purge-data --confirm-destroy-data)"
grep -Fq 'nothing installed' <<<"$empty_uninstall_output"
printf '%s\n' "SYSTEMD_CONTAINER_SMOKE=PASS image=$base_image"

View file

@ -6,7 +6,7 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates curl iproute2 openssl procps sudo systemd systemd-sysv \
ca-certificates curl iproute2 nftables openssl procps sudo systemd systemd-sysv \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View file

@ -9,13 +9,15 @@ emergency="$root/deploy/ochenstarik-smm-emergency"
acceptance="$root/tests/acceptance/three-server-mesh.sh"
unified_setup="$root/deploy/smm-setup.sh"
unified_test="$root/tests/bootstrap/test-unified-installer.sh"
uninstall_test="$root/tests/bootstrap/test-uninstall-mode.sh"
bash -n "$unified_setup" "$unified_test"
bash -n "$unified_setup" "$unified_test" "$uninstall_test"
if command -v shellcheck >/dev/null 2>&1; then
shellcheck --severity=error "$unified_setup" "$unified_test"
shellcheck --severity=error "$unified_setup" "$unified_test" "$uninstall_test"
fi
if [[ "$(uname -s)" != MINGW* ]]; then
bash "$unified_test"
bash "$uninstall_test"
fi
grep -Fq 'listing="$(/usr/sbin/nft -a list chain' "$helper" || {
@ -147,7 +149,8 @@ if ! bash "$bootstrap" >/dev/null 2>&1; then
exit 1
fi
for action in verify-release install-control install-agent install-node mesh-init peer-add \
update-control update-agent rollback node-code control-device-code node-token uninstall-control; do
update-control update-agent rollback node-code control-device-code node-token uninstall-control \
uninstall-system; do
if bash "$bootstrap" "$action" >/dev/null 2>&1; then
printf 'bootstrap action accepted missing arguments: %s\n' "$action" >&2
exit 1

View file

@ -20,6 +20,12 @@ case "$1" in
[[ -n "${SMM_ENROLL_CODE:-}" && "${SMM_ACCEPT_CA_FINGERPRINT:-}" == 1 ]]
printf '%s\n' 'SMMPEER1.test-node.test-address.test-key'
;;
uninstall-system-plan)
printf '%s\n' 'Server Monitor Manager objects found on this machine:'
printf '%s\n' ' unit: ochenstarik-smm-control.service'
[[ "${2:-}" != "--purge-data" ]] || printf '%s\n' ' data path: /var/lib/ochenstarik-server-monitor-manager'
;;
uninstall-system) printf 'FAKE_UNINSTALL=%s\n' "$*" ;;
*) printf 'PASSTHROUGH=%s\n' "$*" ;;
esac
INNER
@ -101,6 +107,21 @@ grep -Fq 'CA SHA-256:' "$fixture/node.out"
grep -Fq 'SMMPEER1.test-node.test-address.test-key' "$fixture/node.out"
grep -Fq 'operator application' "$fixture/node.out"
run_tty $'3\n1\nUNINSTALL\n' "$fixture/uninstall-preserve.out"
grep -Fq 'unit: ochenstarik-smm-control.service' "$fixture/uninstall-preserve.out"
grep -Fq 'FAKE_UNINSTALL=uninstall-system --confirm-uninstall' "$fixture/uninstall-preserve.out"
run_tty $'3\n2\nUNINSTALL\nDESTROY-DATA\n' "$fixture/uninstall-purge.out"
grep -Fq 'data path: /var/lib/ochenstarik-server-monitor-manager' "$fixture/uninstall-purge.out"
grep -Fq 'FAKE_UNINSTALL=uninstall-system --confirm-uninstall --purge-data --confirm-destroy-data' "$fixture/uninstall-purge.out"
if PATH="$fixture/bin:$PATH" FIXTURE_RELEASE="$fixture/release" SMM_CACHE_DIR="$fixture/cache" \
bash "$setup" uninstall-system >"$fixture/uninstall-refused.out" 2>&1; then
printf '%s\n' 'uninstall without confirmation flag unexpectedly succeeded' >&2
exit 1
fi
grep -Fq 'requires --confirm-uninstall' "$fixture/uninstall-refused.out"
tampered_ca_part="$(printf '%s' 'not-a-certificate' | b64url)"
tampered_code="SMMNODE2.$control_part.$tampered_ca_part.$node_part.$token_part.$endpoint_part.$hub_key_part.$address_part.$network_part"
for bad_code in "${valid_code%.*}" "$valid_code.extra" "$tampered_code"; do

View file

@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)"
bootstrap="$root/deploy/ochenstarik-server-monitor-manager.sh"
setup="$root/deploy/smm-setup.sh"
refusal_output="$(mktemp -t smm-uninstall-refused.XXXXXXXX)"
trap 'rm -f -- "$refusal_output"' EXIT
bash -n "$bootstrap" "$setup"
if bash "$bootstrap" uninstall-system >/dev/null 2>"$refusal_output"; then
printf '%s\n' 'uninstall-system accepted a request without confirmation' >&2
exit 1
fi
grep -Fq -- '--confirm-uninstall' "$refusal_output"
definition="$(awk '
/^uninstall_system\(\) \{/ { active=1 }
active { print }
active && /^}$/ { exit }
' "$bootstrap")"
stop_line="$(grep -Fn ' stop_owned_services' <<<"$definition" | cut -d: -f1)"
term_line="$(grep -Fn ' terminate_owned_processes' <<<"$definition" | cut -d: -f1)"
network_line="$(grep -Fn ' remove_owned_network' <<<"$definition" | cut -d: -f1)"
files_line="$(grep -Fn ' remove_owned_files' <<<"$definition" | cut -d: -f1)"
accounts_line="$(grep -Fn ' remove_owned_accounts' <<<"$definition" | cut -d: -f1)"
(( stop_line < term_line && term_line < network_line && network_line < files_line && files_line < accounts_line ))
grep -Fq 'pkill -TERM -u "$user"' "$bootstrap"
grep -Fq 'pkill -KILL -u "$user"' "$bootstrap"
grep -Fq 'nft delete table inet ochenstarik_smm' "$bootstrap"
grep -Fq 'ip link delete smm0' "$bootstrap"
grep -Fq 'port-7443:' "$bootstrap"
grep -Fq 'port-51820:' "$bootstrap"
grep -Fq 'mesh-routes:' "$bootstrap"
grep -Fq 'owned-units:' "$bootstrap"
grep -Fq 'owned-users:' "$bootstrap"
grep -Fq 'control-ca.pfx' "$bootstrap"
grep -Fq 'bootstrap-backups' "$bootstrap"
grep -Fq 'Type UNINSTALL' "$setup"
grep -Fq 'Type DESTROY-DATA' "$setup"
grep -Fq '3) Uninstall' "$setup"
if grep -Eiq '3x-ui|x-ui' "$bootstrap" "$setup"; then
printf '%s\n' 'uninstall implementation references protected 3x-ui objects' >&2
exit 1
fi
if grep -Eq "rm -rf --? [\"']?/(etc|var|usr)(/|[\"']|$)" "$bootstrap"; then
printf '%s\n' 'uninstall implementation contains a broad recursive deletion' >&2
exit 1
fi
printf '%s\n' 'UNINSTALL_MODE=PASS'