montana/Русский/Бот/deploy_nodes.sh

280 lines
11 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# deploy_nodes.sh — Rolling Deploy бота на все узлы Montana
#
# ZERO-DOWNTIME DEPLOYMENT:
# 1. Обновляем узлы по одному (rolling)
# 2. Ждём 20 сек между узлами (failover time)
# 3. Проверяем здоровье после каждого деплоя
# 4. Если узел недоступен — пропускаем, не ломаем остальных
#
# Использование:
# ./deploy_nodes.sh — rolling deploy (по одному)
# ./deploy_nodes.sh --fast — быстрый deploy (все сразу, ОПАСНО)
# ./deploy_nodes.sh --node moscow — deploy только на один узел
set -e
# ═══════════════════════════════════════════════════════════════════════════════
# ЦВЕТА
# ═══════════════════════════════════════════════════════════════════════════════
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# ═══════════════════════════════════════════════════════════════════════════════
# КОНФИГУРАЦИЯ
# ═══════════════════════════════════════════════════════════════════════════════
ROLLING_DELAY=20 # Секунд между узлами (время на failover)
HEALTH_CHECK_DELAY=5 # Секунд ожидания перед health check
NODE_NAMES="amsterdam moscow almaty spb novosibirsk"
NODE_IPS="72.56.102.240 176.124.208.93 91.200.148.93 188.225.58.98 147.45.147.247"
BOT_DIR="/root/bot"
SSH_USER="root"
LOCAL_DIR="$(cd "$(dirname "$0")" && pwd)"
# Файлы для деплоя
FILES="junomontanaagibot.py montana_api.py leader_election.py junona_ai.py junona_agents.py node_crypto.py time_bank.py timechain.py dialogue_coordinator.py hippocampus.py junona_rag.py breathing_sync.py contracts.py council_voting.py montana_db.py sms_gateway.py webrtc_signaling.py wallet_wizard.py event_ledger.py requirements.txt junona.service"
# ═══════════════════════════════════════════════════════════════════════════════
# ПАРСИНГ АРГУМЕНТОВ
# ═══════════════════════════════════════════════════════════════════════════════
FAST_MODE=false
SINGLE_NODE=""
while [[ $# -gt 0 ]]; do
case $1 in
--fast)
FAST_MODE=true
shift
;;
--node)
SINGLE_NODE="$2"
shift 2
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
# ═══════════════════════════════════════════════════════════════════════════════
# ЗАГРУЗКА КЛЮЧЕЙ
# ═══════════════════════════════════════════════════════════════════════════════
echo -e "${BLUE}🔧 Configuring SSH & Keys...${NC}"
# SSH ключи из Keychain (macOS)
if [[ "$OSTYPE" == "darwin"* ]]; then
echo " 🍏 macOS: loading keys from Keychain..."
ssh-add --apple-load-keychain 2>/dev/null || ssh-add -A 2>/dev/null || true
fi
# Функция для чтения из keychain
get_key() {
local env_name="$1"
eval "val=\$$env_name"
if [ -n "$val" ]; then echo "$val"; return; fi
if command -v security >/dev/null; then
val=$(security find-generic-password -a "montana" -s "$env_name" -w 2>/dev/null || echo "")
if [ -n "$val" ]; then echo "$val"; return; fi
fi
echo ""
}
TELEGRAM_TOKEN=$(get_key "TELEGRAM_TOKEN_JUNONA")
OPENAI_KEY=$(get_key "OPENAI_API_KEY")
if [ -z "$TELEGRAM_TOKEN" ]; then
echo -e "${RED}❌ Error: TELEGRAM_TOKEN_JUNONA not found${NC}"
exit 1
fi
echo -e "${GREEN}✅ Keys loaded${NC}"
# ═══════════════════════════════════════════════════════════════════════════════
# ФУНКЦИИ
# ═══════════════════════════════════════════════════════════════════════════════
# Проверка доступности узла
check_node() {
local ip="$1"
ssh -o ConnectTimeout=5 -o BatchMode=yes "$SSH_USER@$ip" "echo ok" >/dev/null 2>&1
}
# Проверка здоровья бота на узле
health_check() {
local ip="$1"
local status=$(ssh -o ConnectTimeout=10 "$SSH_USER@$ip" "systemctl is-active junona 2>/dev/null || echo 'inactive'")
if [ "$status" = "active" ]; then
return 0
fi
return 1
}
# Деплой на один узел
deploy_node() {
local node_name="$1"
local ip="$2"
echo -e "\n${BLUE}📡 Deploying to $node_name ($ip)...${NC}"
# Проверяем доступность
if ! check_node "$ip"; then
echo -e " ${YELLOW}⚠️ SKIP: $node_name unreachable${NC}"
return 1
fi
# Создаём директорию
ssh "$SSH_USER@$ip" "mkdir -p $BOT_DIR $BOT_DIR/data $BOT_DIR/node_crypto"
# Копируем файлы
for file in $FILES; do
if [ -f "$LOCAL_DIR/$file" ]; then
scp -q "$LOCAL_DIR/$file" "$SSH_USER@$ip:$BOT_DIR/$file"
echo -e " ${GREEN}${NC} $file"
fi
done
# Копируем директории
if [ -d "$LOCAL_DIR/node_crypto" ]; then
scp -rq "$LOCAL_DIR/node_crypto" "$SSH_USER@$ip:$BOT_DIR/"
echo -e " ${GREEN}${NC} node_crypto/"
fi
# Создаём systemd service с правильным NODE_NAME
ssh "$SSH_USER@$ip" "cat > /etc/systemd/system/junona.service" << EOF
[Unit]
Description=Junona Montana Protocol Bot - $node_name
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=simple
User=root
WorkingDirectory=$BOT_DIR
ExecStart=/usr/bin/python3 -u junomontanaagibot.py
Environment="MONTANA_NODE_NAME=$node_name"
Environment="TELEGRAM_TOKEN_JUNONA=$TELEGRAM_TOKEN"
Environment="OPENAI_API_KEY=$OPENAI_KEY"
Environment="PYTHONUNBUFFERED=1"
WatchdogSec=60
Restart=always
RestartSec=15
TimeoutStopSec=30
KillMode=mixed
KillSignal=SIGTERM
MemoryMax=512M
MemoryHigh=400M
StandardOutput=journal
StandardError=journal
SyslogIdentifier=junona
[Install]
WantedBy=multi-user.target
EOF
echo -e " ${GREEN}${NC} systemd service (NODE_NAME=$node_name)"
# Перезапускаем сервис
ssh "$SSH_USER@$ip" "systemctl daemon-reload && systemctl enable junona && systemctl restart junona"
echo -e " ${GREEN}${NC} Service restarted"
# Ждём и проверяем здоровье
echo -e " ⏳ Waiting ${HEALTH_CHECK_DELAY}s for health check..."
sleep $HEALTH_CHECK_DELAY
if health_check "$ip"; then
echo -e "${GREEN}$node_name deployed & healthy!${NC}"
return 0
else
echo -e "${YELLOW}⚠️ $node_name deployed but health check failed${NC}"
return 1
fi
}
# ═══════════════════════════════════════════════════════════════════════════════
# MAIN
# ═══════════════════════════════════════════════════════════════════════════════
echo -e "\n${BLUE}🏔 Montana Protocol — Deploy${NC}"
echo "=========================================="
if [ "$FAST_MODE" = true ]; then
echo -e "${YELLOW}⚠️ FAST MODE: deploying all nodes simultaneously${NC}"
echo -e "${YELLOW} This may cause brief downtime!${NC}"
else
echo -e "${GREEN}🔄 ROLLING MODE: deploying one node at a time${NC}"
echo -e " Delay between nodes: ${ROLLING_DELAY}s"
fi
echo ""
# Конвертируем в массивы
names_arr=($NODE_NAMES)
ips_arr=($NODE_IPS)
deployed=0
failed=0
for i in "${!names_arr[@]}"; do
node_name="${names_arr[$i]}"
ip="${ips_arr[$i]}"
# Если указан конкретный узел — деплоим только его
if [ -n "$SINGLE_NODE" ] && [ "$SINGLE_NODE" != "$node_name" ]; then
continue
fi
if deploy_node "$node_name" "$ip"; then
((deployed++))
else
((failed++))
fi
# Rolling delay (кроме последнего узла и fast mode)
if [ "$FAST_MODE" = false ] && [ $i -lt $((${#names_arr[@]} - 1)) ]; then
echo -e "\n${BLUE}⏳ Waiting ${ROLLING_DELAY}s before next node (failover time)...${NC}"
sleep $ROLLING_DELAY
fi
done
# ═══════════════════════════════════════════════════════════════════════════════
# РЕЗУЛЬТАТ
# ═══════════════════════════════════════════════════════════════════════════════
echo ""
echo "=========================================="
if [ $failed -eq 0 ]; then
echo -e "${GREEN}🎉 Deploy complete! $deployed/$((deployed + failed)) nodes updated${NC}"
else
echo -e "${YELLOW}⚠️ Deploy finished with issues: $deployed success, $failed failed${NC}"
fi
echo ""
echo "Check logs:"
for i in "${!names_arr[@]}"; do
node_name="${names_arr[$i]}"
ip="${ips_arr[$i]}"
echo " ssh $SSH_USER@$ip 'journalctl -u junona -n 20 --no-pager'"
done
echo ""
echo "Health check:"
for i in "${!names_arr[@]}"; do
node_name="${names_arr[$i]}"
ip="${ips_arr[$i]}"
if check_node "$ip"; then
if health_check "$ip"; then
echo -e " ${GREEN}🟢${NC} $node_name ($ip) — active"
else
echo -e " ${YELLOW}🟡${NC} $node_name ($ip) — inactive"
fi
else
echo -e " ${RED}🔴${NC} $node_name ($ip) — unreachable"
fi
done