280 lines
11 KiB
Bash
280 lines
11 KiB
Bash
|
|
#!/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
|