# Эксплуатационный runbook Версия: **2026-05-18** ## Добавление нового VPN-узла ### 1. Подготовка сервера ``` # Минимум Ubuntu 22.04+, root, IPv4 публичный, открытые :22 :443 :8444 ``` ### 2. Получить секреты у admin - `VPN_PRIVKEY` — Reality privateKey (общий для всех узлов) - `TOKEN` — admin token orchestrator Передача — через зашифрованный канал (encrypted age/gpg, signal, encrypted email). НЕ через github/telegram cleartext. ### 3. Запустить join.sh ``` ROLE=vpn-backend \ TOKEN= \ VPN_PRIVKEY= \ ALIAS=cologne LABEL=Köln COUNTRY=DE HOSTING=Hetzner \ COORDS="50.94,6.96" \ bash <(curl -sL https://hub.montana.quest/efir369999/montana/raw/branch/main/Node/join.sh) ``` Скрипт: 1. ставит зависимости, fail2ban 2. ufw: 22, 80, 443, 8444 → ALLOW; остальное DENY 3. ставит pinned версию xray, dedicated user `xray:xray` 4. пишет config.json с UUID/PBK/SID = универсальными, privateKey из env (никогда в скрипт) 5. drop-in `Restart=always StartLimitBurst=10` 6. `ExecStopPost=/usr/local/bin/montana-vpn-deregister` — при штатной остановке узла дёргает `/vpn/node/deregister` 7. ставит montana-node :8444 (TimeChain p2p) 8. POST `https://montana.quest/vpn/node/register` с admin TOKEN → CF добавляет IP в multi-A ### 4. Проверка ``` ssh new-node 'systemctl is-active xray fail2ban montana-node' curl https://montana.quest/vpn/node/pool | jq '.records[].ip' # должен видеть наш IP openssl s_client -connect :443 -servername www.googletagmanager.com -tls1_3 &1 | grep Verification ``` ## Удаление узла ``` # 1. На самом узле: systemctl stop xray # триггерит ExecStopPost → /deregister # 2. Если узел уже мёртв — admin вручную: TOKEN=$(security find-generic-password -s montana-orchestrator-admin -a token -w) curl -X POST -H 'Content-Type: application/json' \ https://montana.quest/vpn/node/deregister \ --data "{\"ip\":\"\",\"secret\":\"$TOKEN\"}" # 3. Если orchestrator недоступен — напрямую через CF API: CF=$(security find-generic-password -s cloudflare-api-token -a montana-quest -w) ZONE=2bc47161267258960d48bedfdf476f1a REC=$(curl -sH "Authorization: Bearer $CF" \ "https://api.cloudflare.com/client/v4/zones/$ZONE/dns_records?type=A&name=cdn.montana.quest" \ | jq -r '.result[] | select(.content=="") | .id') curl -X DELETE -H "Authorization: Bearer $CF" \ "https://api.cloudflare.com/client/v4/zones/$ZONE/dns_records/$REC" ``` ## Ротация ключей Reality Когда нужно: подозрение на утечку privateKey, plan-rotation каждые 6 месяцев. ``` # 1. Сгенерировать новый keypair (на любом узле): ssh montana-finland 'xray x25519' # → PrivateKey: # Password: (этот идёт в URL как pbk=) # Hash32: ... # 2. Сгенерировать новый shortId: openssl rand -hex 8 # → # 3. Раскатать NEW_PRIV/SID на каждый узел одной командой: for n in montana-finland montana-frankfurt montana-us; do ssh $n "python3 -c \" import json p='/usr/local/etc/xray/config.json' c=json.load(open(p)) ib=c['inbounds'][0] ib['streamSettings']['realitySettings']['privateKey']='' ib['streamSettings']['realitySettings']['shortIds']=[''] json.dump(c,open(p,'w'),indent=2)\" && systemctl restart xray" done # 4. Обновить /vpn/sub (отдаётся с Moscow :5008) — поменять pbk= и sid= в источнике подписки. # 5. Уведомить пользователей — у них в Happ профиль больше не работает (другой pbk). ``` Стоимость ротации: все клиенты должны переустановить профиль. Делать только при необходимости. ## Падение orchestrator ``` ssh montana-moscow 'systemctl status montana-orchestrator' # если crashed: ssh montana-moscow 'journalctl -u montana-orchestrator -n 50 --no-pager' ssh montana-moscow 'systemctl restart montana-orchestrator' # multi-A остаётся как есть → существующие подключения продолжают работать # (deg-graceful: только новых узлов нельзя зарегистрировать пока orch down) ``` ## Падение CF API ``` # DNS остаётся как есть (CF resolvers независимы от API). # Невозможно add/remove IP. Существующий pool работает. # При длительном outage — ждать восстановления CF. ``` ## Доступ к секретам | Секрет | Где | |---|---| | Reality privateKey | macOS Keychain `montana-vpn-privkey` (служба), и `/etc/montana/vpn-privkey` (0600) на узле | | Admin token | macOS Keychain `montana-orchestrator-admin` / `token`; `/etc/montana/orchestrator-admin-token` (0600) на Moscow | | CF API token | macOS Keychain `cloudflare-api-token` / `montana-quest`; `/etc/montana/cf-api-token` (0600) на Moscow | | SSH ключи к узлам | macOS Keychain (через ssh-add) + `~/.ssh/montana-*` (0600) | ## Аптайм-критерии - xray на узле: `Restart=always`, StartLimitBurst=10/5min — выживает все аварии кроме битого конфига - orchestrator: `Restart=on-failure`, RestartSec=10 - fail2ban: `Restart=on-failure` - nginx (Moscow): `Restart=on-failure` - Все critical units `enabled` — переживают reboot