72b6879f4b
- Refactored max_bot from nested packages to flat module structure - Q2: Extended BotUser model (patronymic, email, org, address, vcf_raw, contact_hash, phone_verified, email_verified, last_interaction, total_conversations, total_tickets) - Q2: VCF parser (FN, N, TEL, EMAIL, ORG, ADR), upsert on re-contact, NLP history context (_get_user_history_context -> YandexGPT) - Q1: Broadcast preview modal with 10s confirmation timer - Q3: CSS var(--white)->var(--bg-card), var(--text)->var(--text-primary) - Q4: bot_settings showNotification(), editable max_bot_id - Q5: Webhook secret passthrough via X-Max-Bot-Api-Secret - Masking sensitive keys, dialog_cleared handler, migrate via _add_column_if_not_exists() - Rate limit (asyncio.sleep 0.5 per 10), dead code removed, conv.intent context in contact.py - Portal pages: bot_consent, bot_kb (edit), bot_settings, bot_test, bot_tickets, portal_settings - Tests: 21/21 passing, added test_yandex_gpt.py, test_email_sender.py - Deploy: deploy_full.sh, schema.sql, seed_knowledge_base.sql
223 lines
7.9 KiB
Bash
223 lines
7.9 KiB
Bash
#!/bin/bash
|
||
# ==========================================
|
||
# AegisOne Engineering — Full Deployment Script
|
||
# ==========================================
|
||
# Usage: bash deploy.sh [--skip-ssl]
|
||
# Run on VPS as angel user
|
||
set -e
|
||
|
||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
||
log() { echo -e "${CYAN}[$(date +%H:%M:%S)]${NC} $1"; }
|
||
ok() { echo -e " ${GREEN}✅ $1${NC}"; }
|
||
info() { echo -e " ${YELLOW}ℹ️ $1${NC}"; }
|
||
fail() { echo -e " ${RED}❌ $1${NC}"; exit 1; }
|
||
|
||
SKIP_SSL=false
|
||
for arg in "$@"; do [ "$arg" = "--skip-ssl" ] && SKIP_SSL=true; done
|
||
|
||
cd "$(dirname "$0")"
|
||
|
||
echo -e "${CYAN}"
|
||
echo "=========================================="
|
||
echo " AegisOne Engineering — Deploy v2.0"
|
||
echo "=========================================="
|
||
echo -e "${NC}"
|
||
|
||
# ==========================================
|
||
# PART 1: Server Preparation
|
||
# ==========================================
|
||
log "PART 1: Server preparation"
|
||
|
||
if ! command -v docker &>/dev/null; then
|
||
log "Installing Docker..."
|
||
curl -fsSL https://get.docker.com | sh
|
||
sudo usermod -aG docker "$USER"
|
||
ok "Docker installed. Re-login required for docker group"
|
||
newgrp docker
|
||
else
|
||
ok "Docker already installed: $(docker --version)"
|
||
fi
|
||
|
||
# Create project structure
|
||
log "Creating /opt/projects/ structure..."
|
||
sudo mkdir -p /opt/projects/{nginx-proxy,gitea,aegisone-php,aegisone-py,portainer,project2,backups,certs,data}
|
||
sudo chown -R "$USER":"$USER" /opt/projects
|
||
ok "Structure created"
|
||
|
||
# Create Docker networks
|
||
log "Creating Docker networks..."
|
||
docker network inspect proxy-net &>/dev/null || docker network create proxy-net && ok "proxy-net"
|
||
docker network inspect aegisone-net &>/dev/null || docker network create aegisone-net && ok "aegisone-net"
|
||
docker network inspect gitea-net &>/dev/null || docker network create gitea-net && ok "gitea-net"
|
||
|
||
# ==========================================
|
||
# PART 2: Deploy nginx-proxy
|
||
# ==========================================
|
||
log "PART 2: Deploy nginx-proxy"
|
||
NGX="/opt/projects/nginx-proxy"
|
||
|
||
cp nginx/nginx.conf "$NGX/"
|
||
cp -r nginx/conf.d/* "$NGX/conf.d/"
|
||
|
||
# Generate self-signed cert as placeholder
|
||
mkdir -p "$NGX/certs/live/aegisone.ru" "$NGX/html/.well-known/acme-challenge"
|
||
cat > "$NGX/html/index.html" <<< "<!DOCTYPE html><html><head><title>AegisOne</title></head><body><h1>AegisOne Engineering</h1></body></html>"
|
||
|
||
if [ ! -f "$NGX/certs/live/aegisone.ru/fullchain.pem" ]; then
|
||
log "Generating self-signed SSL cert..."
|
||
openssl req -x509 -nodes -days 30 -newkey rsa:2048 \
|
||
-keyout "$NGX/certs/live/aegisone.ru/privkey.pem" \
|
||
-out "$NGX/certs/live/aegisone.ru/fullchain.pem" \
|
||
-subj "/CN=aegisone.ru" 2>/dev/null || {
|
||
docker run --rm -v "$NGX/certs:/certs" alpine:latest sh -c "
|
||
apk add openssl >/dev/null 2>&1
|
||
mkdir -p /certs/live/aegisone.ru
|
||
openssl req -x509 -nodes -days 30 -newkey rsa:2048 \
|
||
-keyout /certs/live/aegisone.ru/privkey.pem \
|
||
-out /certs/live/aegisone.ru/fullchain.pem \
|
||
-subj '/CN=aegisone.ru'
|
||
"
|
||
}
|
||
ok "Self-signed cert created"
|
||
fi
|
||
|
||
cp nginx/docker-compose.yml "$NGX/"
|
||
cd "$NGX" && docker compose up -d
|
||
ok "nginx-proxy started"
|
||
|
||
# ==========================================
|
||
# PART 3: Deploy aegisone-py (FastAPI + PostgreSQL)
|
||
# ==========================================
|
||
log "PART 3: Deploy aegisone-py (FastAPI + PostgreSQL)"
|
||
PY="/opt/projects/aegisone-py"
|
||
|
||
# Copy all py_service files
|
||
rsync -a --delete py_service/ "$PY/" 2>/dev/null || cp -r py_service/* "$PY/"
|
||
# Override docker-compose.yml with updated network config
|
||
cp py_service/docker-compose.yml "$PY/"
|
||
|
||
cd "$PY" && docker compose up -d --build
|
||
ok "aegisone-py started"
|
||
|
||
# ==========================================
|
||
# PART 3B: Deploy max_bot (MAX messenger bot)
|
||
# ==========================================
|
||
log "PART 3B: Deploy max_bot (MAX messenger bot on port 8002)"
|
||
BOT="/opt/projects/aegisone-py/max_bot"
|
||
|
||
mkdir -p "$BOT"
|
||
rsync -a --delete max_bot/ "$BOT/" 2>/dev/null || cp -r max_bot/* "$BOT/"
|
||
|
||
# Copy nginx config for max.aegisone.ru
|
||
cp max_bot/nginx/max-aegisone.conf "$NGX/conf.d/max-aegisone.conf"
|
||
docker compose -f "$NGX/docker-compose.yml" exec nginx nginx -s reload 2>/dev/null || true
|
||
ok "max_bot nginx config deployed"
|
||
|
||
cd "$BOT" && docker compose up -d --build
|
||
ok "max_bot started on port 8002"
|
||
|
||
# ==========================================
|
||
# PART 4: Deploy aegisone-php (Public site)
|
||
# ==========================================
|
||
log "PART 4: Deploy aegisone-php (Public site)"
|
||
PHP="/opt/projects/aegisone-php"
|
||
|
||
# Copy PHP source files from repo root (excluding certain dirs)
|
||
mkdir -p "$PHP/src"
|
||
rsync -a --delete \
|
||
--exclude='.git' --exclude='node_modules' --exclude='*.zip' \
|
||
--exclude='py_service' --exclude='aegisone-php' --exclude='nginx' \
|
||
--exclude='gitea' --exclude='portainer' --exclude='opencode.json' \
|
||
--exclude='site_aegisone.zip' \
|
||
./ "$PHP/src/"
|
||
|
||
# Copy Docker infrastructure
|
||
cp aegisone-php/Dockerfile "$PHP/"
|
||
cp aegisone-php/nginx.conf "$PHP/"
|
||
cp aegisone-php/docker-compose.yml "$PHP/"
|
||
|
||
cd "$PHP" && docker compose up -d --build
|
||
ok "aegisone-php started"
|
||
|
||
# ==========================================
|
||
# PART 5: Deploy Gitea
|
||
# ==========================================
|
||
log "PART 5: Deploy Gitea"
|
||
GITEA="/opt/projects/gitea"
|
||
cp gitea/docker-compose.yml "$GITEA/"
|
||
cd "$GITEA" && docker compose up -d
|
||
ok "Gitea started"
|
||
|
||
# ==========================================
|
||
# PART 6: Deploy Portainer (port 9000)
|
||
# ==========================================
|
||
log "PART 6: Deploy Portainer"
|
||
P="/opt/projects/portainer"
|
||
cp portainer/docker-compose.yml "$P/"
|
||
cd "$P" && docker compose up -d
|
||
ok "Portainer started on port 9000"
|
||
|
||
# ==========================================
|
||
# PART 7: Configure UFW
|
||
# ==========================================
|
||
log "PART 7: Configure UFW"
|
||
if ! sudo ufw status | grep -q "Status: active"; then
|
||
sudo ufw --force disable 2>/dev/null || true
|
||
sudo ufw default deny incoming
|
||
sudo ufw default allow outgoing
|
||
sudo ufw allow 22/tcp comment 'SSH'
|
||
sudo ufw allow 80/tcp comment 'HTTP'
|
||
sudo ufw allow 443/tcp comment 'HTTPS'
|
||
sudo ufw allow 9000/tcp comment 'Portainer'
|
||
sudo ufw --force enable
|
||
ok "UFW configured"
|
||
else
|
||
ok "UFW already active"
|
||
fi
|
||
|
||
# ==========================================
|
||
# PART 8: Get SSL certificates
|
||
# ==========================================
|
||
if [ "$SKIP_SSL" = false ]; then
|
||
log "PART 8: Obtaining Let's Encrypt SSL certificates"
|
||
cd "$NGX"
|
||
docker compose run --rm certbot certonly --webroot \
|
||
--webroot-path=/var/www/certbot \
|
||
-d aegisone.ru -d www.aegisone.ru \
|
||
-d service.aegisone.ru \
|
||
-d max.aegisone.ru \
|
||
-d git.aegisone.ru \
|
||
--email admin@aegisone.ru \
|
||
--agree-tos --non-interactive 2>/dev/null && {
|
||
ok "SSL certificates obtained"
|
||
docker compose exec nginx nginx -s reload
|
||
ok "Nginx reloaded with real certs"
|
||
} || info "Certbot failed — run manually after DNS propagation"
|
||
else
|
||
info "SSL step skipped (--skip-ssl)"
|
||
fi
|
||
|
||
# ==========================================
|
||
# Done
|
||
# ==========================================
|
||
echo ""
|
||
echo -e "${GREEN}=========================================="
|
||
echo " Deploy complete!"
|
||
echo "==========================================${NC}"
|
||
echo ""
|
||
echo " nginx-proxy: http://81.177.141.34 → aegisone.ru"
|
||
echo " Portainer: http://81.177.141.34:9000"
|
||
echo " FastAPI (direct): http://81.177.141.34:8000"
|
||
echo " Gitea (direct): http://81.177.141.34:3000"
|
||
echo ""
|
||
echo " After DNS propagation, run certbot:"
|
||
echo " cd /opt/projects/nginx-proxy"
|
||
echo " docker compose run --rm certbot certonly --webroot \\"
|
||
echo " --webroot-path=/var/www/certbot \\"
|
||
echo " -d aegisone.ru -d www.aegisone.ru \\"
|
||
echo " -d service.aegisone.ru -d max.aegisone.ru \\"
|
||
echo " -d git.aegisone.ru \\"
|
||
echo " --email admin@aegisone.ru --agree-tos --non-interactive"
|
||
echo " docker compose exec nginx nginx -s reload"
|
||
echo ""
|