From 01968d79a7f6e8213a07aa15fa22712b8f3ae27a Mon Sep 17 00:00:00 2001 From: Ochenstarik Date: Thu, 16 Jul 2026 19:08:16 +0700 Subject: [PATCH] Add role-aware update rollback and uninstall --- .github/workflows/bash-syntax.yml | 3 + README.md | 10 ++ docs/readme/README.ru.md | 10 ++ ochenstarik-server-monitor-manager.sh | 139 ++++++++++++++++++++++-- tests/test-monitor-manager-lifecycle.sh | 22 ++++ 5 files changed, 172 insertions(+), 12 deletions(-) create mode 100644 tests/test-monitor-manager-lifecycle.sh diff --git a/.github/workflows/bash-syntax.yml b/.github/workflows/bash-syntax.yml index 306e900..47c0d64 100644 --- a/.github/workflows/bash-syntax.yml +++ b/.github/workflows/bash-syntax.yml @@ -49,3 +49,6 @@ jobs: - name: Test Server Monitor Manager Link policies run: bash tests/test-monitor-manager-links.sh + + - name: Test Server Monitor Manager lifecycle + run: bash tests/test-monitor-manager-lifecycle.sh diff --git a/README.md b/README.md index 042e5e6..529e38b 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,16 @@ sudo ochenstarik-smm links The Node installer creates its private WireGuard key locally and prints an `SMMREQ1` request. In a second terminal run `sudo ochenstarik-smm node-enroll` on the Hub, paste the request at the hidden prompt, and return the resulting `SMMACK1` code to the Node installer. The enrollment code expires after 10 minutes and is consumed by the first successful registration. +Role-aware maintenance commands create a root-only backup before destructive changes: + +```bash +sudo ./ochenstarik-server-monitor-manager.sh update +sudo ./ochenstarik-server-monitor-manager.sh rollback +sudo ./ochenstarik-server-monitor-manager.sh uninstall-node +sudo ./ochenstarik-server-monitor-manager.sh uninstall-hub +sudo ./ochenstarik-server-monitor-manager.sh uninstall-monitor +``` + ## Installation flow 1. Select the wizard dialog language. diff --git a/docs/readme/README.ru.md b/docs/readme/README.ru.md index 549ff84..9a52c6e 100644 --- a/docs/readme/README.ru.md +++ b/docs/readme/README.ru.md @@ -98,6 +98,16 @@ sudo ochenstarik-smm links Установщик Node создаст приватный WireGuard-ключ локально и покажет запрос `SMMREQ1`. Во втором терминале выполните на Hub `sudo ochenstarik-smm node-enroll`, вставьте запрос по скрытому приглашению и верните полученный код `SMMACK1` в установщик Node. Enrollment-код действует 10 минут и погашается при первой успешной регистрации. +Команды обслуживания учитывают установленную роль и создают root-only backup перед изменениями: + +```bash +sudo ./ochenstarik-server-monitor-manager.sh update +sudo ./ochenstarik-server-monitor-manager.sh rollback +sudo ./ochenstarik-server-monitor-manager.sh uninstall-node +sudo ./ochenstarik-server-monitor-manager.sh uninstall-hub +sudo ./ochenstarik-server-monitor-manager.sh uninstall-monitor +``` + ## Требования - Ubuntu Server или совместимая Debian-система с `systemd`; diff --git a/ochenstarik-server-monitor-manager.sh b/ochenstarik-server-monitor-manager.sh index 8fe9188..49c504f 100644 --- a/ochenstarik-server-monitor-manager.sh +++ b/ochenstarik-server-monitor-manager.sh @@ -59,14 +59,14 @@ read_public_key() { install_monitor_dependencies() { apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y \ - ca-certificates openssh-server openssh-client coreutils gawk + ca-certificates openssh-server openssh-client coreutils gawk sudo systemctl enable --now ssh } install_mesh_dependencies() { apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y \ - wireguard-tools nftables iproute2 jq openssl + wireguard-tools nftables iproute2 iputils-ping jq openssl } verify_public_key() { @@ -857,14 +857,84 @@ install_server_part() { show_status } -uninstall_server_part() { - local answer +confirm_action() { + local prompt="$1" answer if [[ -r /dev/tty ]]; then - IFS= read -r -p 'Удалить пользователя мониторинга и forced-command? [y/N]: ' answer < /dev/tty + IFS= read -r -p "$prompt [y/N]: " answer < /dev/tty else - die "Для удаления требуется интерактивный терминал" + die "Для этой операции требуется интерактивный терминал" fi - [[ "$answer" =~ ^[Yy]$ ]] || { log "Отменено"; return 0; } + [[ "$answer" =~ ^[Yy]$ ]] +} + +backup_state() { + local backup_dir="/var/backups/${APP_NAME}" backup_file timestamp path + local -a paths=() + install -d -m 0700 -o root -g root "$backup_dir" + timestamp="$(date -u +%Y%m%dT%H%M%SZ)" + backup_file="${backup_dir}/${timestamp}.tar.gz" + for path in \ + "$MESH_DIR" "$MONITOR_HOME" "$MONITOR_COMMAND" "$HUB_HELPER" "$HUB_CLI" "$WG_CONFIG" \ + /etc/systemd/system/ochenstarik-smm-firewall.service \ + /etc/systemd/system/ochenstarik-smm-firewall.timer \ + /etc/sudoers.d/ochenstarik-smm-hub \ + /etc/sysctl.d/90-ochenstarik-smm-forward.conf; do + [[ -e "$path" || -L "$path" ]] && paths+=("${path#/}") + done + ((${#paths[@]} > 0)) || { warn "Нет установленных файлов для backup"; return 0; } + tar -C / -czf "$backup_file" -- "${paths[@]}" + chmod 0600 "$backup_file" + log "Backup: $backup_file" +} + +rollback_state() { + local backup_dir="/var/backups/${APP_NAME}" latest + latest="$(find "$backup_dir" -maxdepth 1 -type f -name '*.tar.gz' -printf '%T@ %p\n' 2>/dev/null \ + | sort -nr | awk 'NR == 1 { sub(/^[^ ]+ /, ""); print }')" + [[ -n "$latest" && -f "$latest" ]] || die "Backup для rollback не найден" + confirm_action "Восстановить $latest" || { log "Отменено"; return 0; } + tar -C / -xzf "$latest" + systemctl daemon-reload + [[ ! -f "$WG_CONFIG" ]] || systemctl restart "wg-quick@${WG_INTERFACE}.service" + [[ ! -x "$HUB_HELPER" ]] || "$HUB_HELPER" firewall-restore + [[ ! -x "$MONITOR_COMMAND" ]] || systemctl reload ssh + log "Rollback завершён: $latest" +} + +existing_public_key() { + [[ -r "$AUTHORIZED_KEYS" ]] || die "Не найден существующий SSH-ключ мониторинга" + sed -n 's/^.*\(ssh-ed25519 [A-Za-z0-9+\/=]*.*\)$/\1/p' "$AUTHORIZED_KEYS" | head -n 1 +} + +update_installed() { + local role=monitor + backup_state + SERVER_MONITOR_PUBLIC_KEY="$(existing_public_key)" + export SERVER_MONITOR_PUBLIC_KEY + if [[ -r "$HUB_CONFIG" ]]; then + role=hub + SMM_HUB_ENDPOINT="$(awk -F= '$1 == "HUB_ENDPOINT" { print $2; exit }' "$HUB_CONFIG")" + SMM_WG_PORT="$(awk -F= '$1 == "WG_PORT" { print $2; exit }' "$HUB_CONFIG")" + export SMM_HUB_ENDPOINT SMM_WG_PORT + install_hub + elif [[ -r "$MESH_DIR/node.conf" ]]; then + role=node + install_monitoring + install_mesh_dependencies + systemctl enable --now "wg-quick@${WG_INTERFACE}.service" + systemctl restart "wg-quick@${WG_INTERFACE}.service" + else + install_server_part + fi + unset SERVER_MONITOR_PUBLIC_KEY SMM_HUB_ENDPOINT SMM_WG_PORT + log "Обновление роли $role завершено" +} + +uninstall_monitor() { + [[ ! -r "$HUB_CONFIG" && ! -r "$MESH_DIR/node.conf" ]] \ + || die "Сначала удалите роль командой uninstall-hub или uninstall-node" + confirm_action "Удалить пользователя мониторинга и forced-command" || { log "Отменено"; return 0; } + backup_state rm -f -- "$MONITOR_COMMAND" if getent passwd "$MONITOR_USER" >/dev/null; then userdel --remove "$MONITOR_USER" 2>/dev/null || userdel "$MONITOR_USER" @@ -872,18 +942,63 @@ uninstall_server_part() { log "Серверная часть мониторинга удалена" } +uninstall_node() { + local ssh_port + [[ -r "$MESH_DIR/node.conf" ]] || die "Роль Node не установлена" + confirm_action "Отключить и удалить WireGuard Node" || { log "Отменено"; return 0; } + backup_state + systemctl disable --now "wg-quick@${WG_INTERFACE}.service" 2>/dev/null || true + ssh_port="$(sshd -T 2>/dev/null | awk '$1 == "port" { print $2; exit }')" + if command -v ufw >/dev/null 2>&1 && [[ -n "$ssh_port" ]]; then + ufw --force delete allow in on "$WG_INTERFACE" to any port "$ssh_port" proto tcp >/dev/null 2>&1 || true + fi + rm -f -- "$WG_CONFIG" "$MESH_DIR/node.conf" + rmdir "$MESH_DIR" 2>/dev/null || true + log "WireGuard Node удалён; monitoring identity оставлена" +} + +uninstall_hub() { + local wg_port + [[ -r "$HUB_CONFIG" ]] || die "Роль Hub не установлена" + confirm_action "Удалить Hub, все Node identities, Links и аудит" || { log "Отменено"; return 0; } + backup_state + wg_port="$(awk -F= '$1 == "WG_PORT" { print $2; exit }' "$HUB_CONFIG")" + systemctl disable --now ochenstarik-smm-firewall.timer ochenstarik-smm-firewall.service 2>/dev/null || true + systemctl disable --now "wg-quick@${WG_INTERFACE}.service" 2>/dev/null || true + nft delete table inet ochenstarik_smm >/dev/null 2>&1 || true + if command -v ufw >/dev/null 2>&1; then + ufw --force delete allow "$wg_port/udp" >/dev/null 2>&1 || true + ufw --force route delete allow in on "$WG_INTERFACE" out on "$WG_INTERFACE" >/dev/null 2>&1 || true + fi + rm -f -- \ + /etc/systemd/system/ochenstarik-smm-firewall.service \ + /etc/systemd/system/ochenstarik-smm-firewall.timer \ + /etc/sudoers.d/ochenstarik-smm-hub \ + /etc/sysctl.d/90-ochenstarik-smm-forward.conf \ + "$HUB_HELPER" "$HUB_CLI" "$WG_CONFIG" + rm -rf -- "$MESH_DIR" + systemctl daemon-reload + sysctl --system >/dev/null + log "Mesh Hub удалён; monitoring identity оставлена" +} + main() { local action="${1:-install}" require_root detect_system case "$action" in - install) install_server_part ;; - hub) install_hub ;; - node) install_node ;; + install|install-monitor) install_server_part ;; + hub|install-hub) install_hub ;; + node|install-node) install_node ;; hub-code) [[ $# -eq 2 ]] || die "Использование: $0 hub-code NAME"; exec "$HUB_HELPER" node-code "$2" ;; status) show_status ;; - uninstall) uninstall_server_part ;; - *) die "Использование: $0 {install|hub|node|hub-code NAME|status|uninstall}" ;; + update) update_installed ;; + rollback) rollback_state ;; + uninstall-monitor) uninstall_monitor ;; + uninstall-node) uninstall_node ;; + uninstall-hub) uninstall_hub ;; + uninstall) die "Укажите роль: uninstall-monitor, uninstall-node или uninstall-hub" ;; + *) die "Использование: $0 {install-monitor|install-hub|install-node|hub-code NAME|status|update|rollback|uninstall-monitor|uninstall-node|uninstall-hub}" ;; esac } diff --git a/tests/test-monitor-manager-lifecycle.sh b/tests/test-monitor-manager-lifecycle.sh new file mode 100644 index 0000000..c663322 --- /dev/null +++ b/tests/test-monitor-manager-lifecycle.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." +script="ochenstarik-server-monitor-manager.sh" + +[[ -s "$script" ]] +grep -Fq 'sudo' "$script" +grep -Fq 'iputils-ping' "$script" +grep -Fq 'backup_state()' "$script" +grep -Fq 'rollback_state()' "$script" +grep -Fq 'update_installed()' "$script" +grep -Fq 'uninstall_monitor()' "$script" +grep -Fq 'uninstall_node()' "$script" +grep -Fq 'uninstall_hub()' "$script" +grep -Fq '/var/backups/${APP_NAME}' "$script" +grep -Fq 'systemctl disable --now ochenstarik-smm-firewall.timer' "$script" +grep -Fq 'nft delete table inet ochenstarik_smm' "$script" +grep -Fq 'sysctl --system' "$script" +grep -Fq 'uninstall) die "Укажите роль:' "$script" + +printf 'Server Monitor Manager lifecycle checks passed.\n'