#!/bin/bash # ───────────────────────────────────────────────────────────── # deploy-platform.sh # Deploys the latest locally-pulled platform backup. # Self-healing: installs all required system dependencies. # Only redeploys if the backup is newer than the running version. # Run every hour (offset ~5min after pull) via cron. # ───────────────────────────────────────────────────────────── LOCAL_BACKUP_DIR="/backups/platform" DEPLOY_DIR="/root/management-platform" OLD_DEPLOY_DIR="/backups/platform/old-deploys" STAMP_FILE="/backups/platform/.last-deployed" SERVICE_FILE="/etc/systemd/system/management-platform.service" LOG="/root/deploy-platform.log" log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG"; } mkdir -p "$OLD_DEPLOY_DIR" # ════════════════════════════════════════════════════════════ # STEP 1 — Check & install system dependencies # ════════════════════════════════════════════════════════════ log "🔍 Checking system dependencies..." MISSING_PKGS="" # python3 if ! command -v python3 &>/dev/null; then MISSING_PKGS="$MISSING_PKGS python3" fi # python3-venv (needed for venv creation) PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null) if ! python3 -c "import ensurepip" &>/dev/null; then MISSING_PKGS="$MISSING_PKGS python${PYTHON_VERSION}-venv" fi # sshpass (used by restore feature) if ! command -v sshpass &>/dev/null; then MISSING_PKGS="$MISSING_PKGS sshpass" fi if [ -n "$MISSING_PKGS" ]; then log "📦 Installing missing packages:$MISSING_PKGS" apt-get update -qq apt-get install -y --quiet $MISSING_PKGS if [ $? -ne 0 ]; then log "❌ apt install failed — check internet/apt on this machine" exit 1 fi log "✅ System packages installed" else log "✅ System dependencies OK" fi # ════════════════════════════════════════════════════════════ # STEP 2 — Find latest local backup # ════════════════════════════════════════════════════════════ LATEST=$(ls -t "$LOCAL_BACKUP_DIR"/platform-backup-*.tar.gz 2>/dev/null | head -1) if [ -z "$LATEST" ]; then log "❌ No local platform backups found in $LOCAL_BACKUP_DIR" log " Run pull-platform-backup.sh first" exit 1 fi BACKUP_NAME=$(basename "$LATEST") # ── Check if already deployed ───────────────────────────────── if [ -f "$STAMP_FILE" ] && [ "$(cat $STAMP_FILE)" = "$BACKUP_NAME" ]; then log "✅ Already running latest: $BACKUP_NAME — nothing to do" exit 0 fi log "🚀 New backup detected: $BACKUP_NAME — deploying..." # ════════════════════════════════════════════════════════════ # STEP 3 — Extract & validate # ════════════════════════════════════════════════════════════ EXTRACT_DIR=$(mktemp -d /tmp/platform-deploy-XXXXXX) log "📂 Extracting to $EXTRACT_DIR ..." tar -xzf "$LATEST" -C "$EXTRACT_DIR" if [ $? -ne 0 ]; then log "❌ tar extraction failed — backup may be corrupt: $BACKUP_NAME" rm -rf "$EXTRACT_DIR" exit 1 fi # Find management-platform/ inside archive (handles any nesting level) PLATFORM_SRC=$(find "$EXTRACT_DIR" -maxdepth 3 -type d -name "management-platform" | head -1) if [ -z "$PLATFORM_SRC" ]; then log "❌ Could not find management-platform/ inside archive" log " Archive contents:" find "$EXTRACT_DIR" -maxdepth 3 | head -20 | tee -a "$LOG" rm -rf "$EXTRACT_DIR" exit 1 fi if [ ! -f "$PLATFORM_SRC/app.py" ]; then log "❌ app.py not found in extracted platform — archive looks wrong" rm -rf "$EXTRACT_DIR" exit 1 fi log "✅ Found platform at: $PLATFORM_SRC" # Strip venv and cache from source — always rebuild locally rm -rf "$PLATFORM_SRC/venv" "$PLATFORM_SRC/__pycache__" find "$PLATFORM_SRC" -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null # ════════════════════════════════════════════════════════════ # STEP 4 — Stop service & kill strays # ════════════════════════════════════════════════════════════ log "🛑 Stopping management-platform service..." systemctl stop management-platform 2>/dev/null sleep 2 pkill -f "python.*app.py" 2>/dev/null sleep 1 # ════════════════════════════════════════════════════════════ # STEP 5 — Archive current deploy, swap in new one # ════════════════════════════════════════════════════════════ if [ -d "$DEPLOY_DIR" ] && [ "$(ls -A $DEPLOY_DIR 2>/dev/null)" ]; then OLD_NAME="management-platform.old.$(date +%Y%m%d_%H%M%S)" log "📦 Archiving current deploy → $OLD_DEPLOY_DIR/$OLD_NAME.tar.gz" tar -czf "$OLD_DEPLOY_DIR/$OLD_NAME.tar.gz" -C /root management-platform 2>/dev/null rm -rf "$DEPLOY_DIR" # Keep only last 2 old deploys ls -t "$OLD_DEPLOY_DIR"/management-platform.old.*.tar.gz 2>/dev/null \ | tail -n +3 | xargs rm -f 2>/dev/null fi # Remove any leftover .old directories in /root from old scripts rm -rf /root/management-platform.old.* 2>/dev/null log "📁 Deploying new version..." cp -r "$PLATFORM_SRC" "$DEPLOY_DIR" chmod +x "$DEPLOY_DIR"/*.sh 2>/dev/null chmod +x "$DEPLOY_DIR"/*.py 2>/dev/null # ════════════════════════════════════════════════════════════ # STEP 6 — Build fresh venv & install flask # ════════════════════════════════════════════════════════════ log "🐍 Building fresh Python venv..." cd "$DEPLOY_DIR" python3 -m venv venv if [ $? -ne 0 ]; then log "❌ venv creation failed" rm -rf "$EXTRACT_DIR" exit 1 fi # Bootstrap pip in case ensurepip is incomplete venv/bin/python -m ensurepip --upgrade 2>/dev/null || true venv/bin/python -m pip install --upgrade pip --quiet 2>/dev/null || true log "📦 Installing Flask..." venv/bin/pip install flask --quiet if [ $? -ne 0 ]; then log "❌ Flask install failed" rm -rf "$EXTRACT_DIR" exit 1 fi log "✅ Venv ready ($(venv/bin/python --version), flask $(venv/bin/pip show flask | grep Version | cut -d' ' -f2))" # ════════════════════════════════════════════════════════════ # STEP 7 — Write correct systemd service file # ════════════════════════════════════════════════════════════ log "🔧 Writing systemd service file..." cat > "$SERVICE_FILE" << 'SVCEOF' [Unit] Description=Navitrends Management Platform After=network.target docker.target Wants=network.target [Service] Type=simple User=root WorkingDirectory=/root/management-platform ExecStart=/root/management-platform/venv/bin/python /root/management-platform/app.py Restart=on-failure RestartSec=10 StartLimitIntervalSec=60 StartLimitBurst=3 StandardOutput=append:/root/management-platform/app.log StandardError=append:/root/management-platform/app.log [Install] WantedBy=multi-user.target SVCEOF systemctl daemon-reload # ════════════════════════════════════════════════════════════ # STEP 8 — Clean up & start # ════════════════════════════════════════════════════════════ rm -rf "$EXTRACT_DIR" log "▶️ Starting management-platform service..." systemctl start management-platform sleep 4 if systemctl is-active --quiet management-platform; then log "✅ Service is running" echo "$BACKUP_NAME" > "$STAMP_FILE" log "🎉 Deploy complete: $BACKUP_NAME" else log "❌ Service failed to start. Last app.log lines:" tail -20 "$DEPLOY_DIR/app.log" | tee -a "$LOG" log " Full check: journalctl -u management-platform -n 30" exit 1 fi