Add SSH monitoring server installer #3

Open
ochenstarik-ui wants to merge 15 commits from agent/server-monitor-installer into main
5 changed files with 172 additions and 12 deletions
Showing only changes of commit 01968d79a7 - Show all commits

View file

@ -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

View file

@ -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.

View file

@ -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`;

View file

@ -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
}

View file

@ -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'