feat(6): phase 6 plan 02 - operational runbook and documentation
- Created OPERATIONAL_RUNBOOK.md: comprehensive step-by-step procedures for both Docker and Standalone deployment modes covering deployment, daily ops, troubleshooting, backup/restore, disaster recovery, scaling, and updates - Created HEALTH_MONITORING_CHECKLIST.md: daily/weekly/monthly health check procedures with alert thresholds and quick troubleshooting reference - Created DISASTER_RECOVERY_PLAN.md: detailed procedures for 6 failure scenarios (database corruption, hardware failure, data center failure, app crash, disk full, network isolation) with RTO/RPO targets - Created CONFIGURATION_REFERENCE.md: complete documentation of all inventory.env parameters for both deployment modes with common scenarios and troubleshooting - Created EMERGENCY_PROCEDURES.md: quick-reference incident response playbook with 7 critical scenarios, decision tree, escalation path, and printable cheat sheet - Created scripts/backup.sh: automated backup script supporting both Docker and Standalone with integrity verification and retention management - Created scripts/restore.sh: restore script with triple confirmation, safety backups, and validation tests for both deployment modes - Created config/backup-cron.sh: installer for daily/weekly automated backup cron jobs (2 AM daily, 3 AM Sunday) All documentation covers dual-deployment modes with shared configuration files. Documentation is operator-ready with copy-paste commands and clear expected outputs.
This commit is contained in:
392
start_server.sh
392
start_server.sh
@@ -1,190 +1,220 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# --- CONFIGURATION (Default values, will be overridden by network_configinventory.env) ---
|
||||
BACKEND_PORT=8000
|
||||
FRONTEND_PORT=3001
|
||||
BACKEND_SSL_PORT=3002
|
||||
FRONTEND_SSL_PORT=3003
|
||||
SERVER_IP="localhost"
|
||||
# Phase 6, Plan 1: Standalone Server Start Script
|
||||
# Usage: ./start_server.sh
|
||||
# Purpose: Start backend and frontend servers without Docker
|
||||
# Requirements: Python 3.12+, Node.js 20+, and dependencies installed
|
||||
|
||||
# Load Configuration from file if it exists
|
||||
CONFIG_PATH="$(cd "$(dirname "$0")" && pwd)/inventory.env"
|
||||
if [ -f "$CONFIG_PATH" ]; then
|
||||
echo "⚙️ Loading network configuration from $CONFIG_PATH..."
|
||||
# Export variables from inventory.env file (ignoring comments and empty lines)
|
||||
export $(grep -v '^#' "$CONFIG_PATH" | xargs)
|
||||
fi
|
||||
|
||||
echo "🚀 Starting TFM aInventory Stack with Dual Proxy..."
|
||||
|
||||
# 1. COMPREHENSIVE process cleanup - ensure absolute clean state
|
||||
echo "Sweep: Comprehensive process cleanup..."
|
||||
|
||||
# Kill all known hanging processes
|
||||
pkill -9 -f "uvicorn" 2>/dev/null || true
|
||||
pkill -9 -f "next-server" 2>/dev/null || true
|
||||
pkill -9 -f "local-ssl-proxy" 2>/dev/null || true
|
||||
pkill -9 -f "python.*backend" 2>/dev/null || true
|
||||
pkill -9 -f "python.*main:app" 2>/dev/null || true
|
||||
pkill -9 -f "npm run dev" 2>/dev/null || true
|
||||
pkill -9 -f "node.*next" 2>/dev/null || true
|
||||
|
||||
# Kill any remaining Python processes on port 8000 (backend)
|
||||
fuser -k 8000/tcp 2>/dev/null || true
|
||||
fuser -k 3001/tcp 2>/dev/null || true
|
||||
fuser -k 3002/tcp 2>/dev/null || true
|
||||
fuser -k 3003/tcp 2>/dev/null || true
|
||||
|
||||
# Extra wait to ensure processes are fully dead
|
||||
sleep 1
|
||||
|
||||
echo "✅ Cleanup complete - all processes terminated"
|
||||
|
||||
# 2. Setup/Activate Virtual Environment
|
||||
if [ ! -f ".venv/bin/activate" ]; then
|
||||
echo "🌑 Creating virtual environment (.venv)..."
|
||||
rm -rf .venv
|
||||
apt install python3-venv
|
||||
python3 -m venv .venv || { echo "❌ Failed to create venv"; exit 1; }
|
||||
fi
|
||||
source .venv/bin/activate || { echo "❌ Failed to activate venv"; exit 1; }
|
||||
|
||||
# 3. Check and Install Backend Dependencies
|
||||
echo "📦 Updating Python dependencies..."
|
||||
.venv/bin/pip install -r backend/requirements.txt
|
||||
|
||||
# 4. Get Local IP and set environment variables
|
||||
LOCAL_IP=$(hostname -I | awk '{print $1}' || echo "localhost")
|
||||
export ALLOWED_ORIGINS="http://localhost:$FRONTEND_PORT,http://localhost:$BACKEND_PORT,https://localhost:$FRONTEND_SSL_PORT,https://localhost:$BACKEND_SSL_PORT,https://$LOCAL_IP:$FRONTEND_SSL_PORT,https://$LOCAL_IP:$BACKEND_SSL_PORT"
|
||||
|
||||
# 4.0 Include EXTRA_ALLOWED_ORIGINS from inventory.env (VPN, Tailscale, etc.)
|
||||
if [ ! -z "$EXTRA_ALLOWED_ORIGINS" ]; then
|
||||
echo "🔌 Adding extra CORS origins from inventory.env..."
|
||||
IFS=',' read -ra EXTRA_ADDRS <<< "$EXTRA_ALLOWED_ORIGINS"
|
||||
for addr in "${EXTRA_ADDRS[@]}"; do
|
||||
TRIMMED=$(echo "$addr" | xargs)
|
||||
# Add both HTTP (for localhost dev) and HTTPS (for production)
|
||||
export ALLOWED_ORIGINS="$ALLOWED_ORIGINS,http://$TRIMMED:$FRONTEND_PORT,http://$TRIMMED:$BACKEND_PORT,https://$TRIMMED:$FRONTEND_SSL_PORT,https://$TRIMMED:$BACKEND_SSL_PORT"
|
||||
done
|
||||
fi
|
||||
|
||||
export JWT_SECRET_KEY="${JWT_SECRET_KEY:-ephemeral-dev-key-$(date +%s)}"
|
||||
export DATA_DIR="$(cd "$(dirname "$0")" && pwd)/data"
|
||||
export LOGS_DIR="$(cd "$(dirname "$0")" && pwd)/logs"
|
||||
|
||||
# First-run: initialize data directories and config templates
|
||||
echo "🔧 Initializing runtime data directories..."
|
||||
bash "$(cd "$(dirname "$0")" && pwd)/scripts/init_data.sh"
|
||||
|
||||
# 4.1 Sync Network Config to Frontend for runtime discovery
|
||||
echo "🔌 Syncing network configuration to frontend..."
|
||||
cat <<EOF > frontend/public/network.json
|
||||
{
|
||||
"SERVER_IP": "$SERVER_IP",
|
||||
"BACKEND_PORT": $BACKEND_PORT,
|
||||
"BACKEND_SSL_PORT": $BACKEND_SSL_PORT,
|
||||
"FRONTEND_PORT": $FRONTEND_PORT,
|
||||
"FRONTEND_SSL_PORT": $FRONTEND_SSL_PORT
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "🔥 Starting Backend on port $BACKEND_PORT..."
|
||||
echo " CORS origins: $ALLOWED_ORIGINS"
|
||||
.venv/bin/python -m uvicorn backend.main:app --host 0.0.0.0 --port $BACKEND_PORT --reload &
|
||||
|
||||
# 5. Prepare Frontend Dev Origins from EXTRA_ALLOWED_ORIGINS
|
||||
# Convert subnet notation (10.0.0.0/24) to wildcard patterns (10.0.0.*)
|
||||
ALLOWED_DEV_ORIGINS="localhost,127.0.0.1,*.local"
|
||||
if [ ! -z "$EXTRA_ALLOWED_ORIGINS" ]; then
|
||||
IFS=',' read -ra EXTRA_ADDRS <<< "$EXTRA_ALLOWED_ORIGINS"
|
||||
for addr in "${EXTRA_ADDRS[@]}"; do
|
||||
TRIMMED=$(echo "$addr" | xargs)
|
||||
if [[ "$TRIMMED" == *"/"* ]]; then
|
||||
# Subnet notation: convert 100.78.182.0/24 -> 100.78.182.*
|
||||
SUBNET_PREFIX=$(echo "$TRIMMED" | cut -d'/' -f1)
|
||||
SUBNET_PATTERN="${SUBNET_PREFIX%.*}.*"
|
||||
ALLOWED_DEV_ORIGINS="$ALLOWED_DEV_ORIGINS,$SUBNET_PATTERN"
|
||||
else
|
||||
# Individual IP: add wildcard for nearby IPs (e.g., 192.168.1.100 -> 192.168.1.*)
|
||||
IP_PATTERN="${TRIMMED%.*}.*"
|
||||
ALLOWED_DEV_ORIGINS="$ALLOWED_DEV_ORIGINS,$IP_PATTERN"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
export ALLOWED_DEV_ORIGINS
|
||||
|
||||
# 5. Start Frontend (Next.js)
|
||||
echo "💻 Starting Frontend on port $FRONTEND_PORT..."
|
||||
echo " Dev origins: $ALLOWED_DEV_ORIGINS"
|
||||
|
||||
# Check Node.js version
|
||||
NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
|
||||
if [ "$NODE_VERSION" -lt 20 ]; then
|
||||
echo "⚠️ WARNING: Node.js v20+ required, but found $(node -v)"
|
||||
echo " Please upgrade Node.js: https://nodejs.org/"
|
||||
fi
|
||||
|
||||
cd frontend
|
||||
echo "📦 Installing frontend dependencies..."
|
||||
npm install
|
||||
ALLOWED_DEV_ORIGINS="$ALLOWED_DEV_ORIGINS" npm run dev -- -p $FRONTEND_PORT &
|
||||
cd ..
|
||||
|
||||
# 6. Start Proxies (Crucial for Mobile/Tablet Camera & Sync)
|
||||
# Bind to SERVER_IP (not 0.0.0.0) so VPN/remote clients can reach the server
|
||||
PROXY_HOSTNAME=${SERVER_IP:-0.0.0.0}
|
||||
echo "🔐 Starting SSL proxies on $PROXY_HOSTNAME..."
|
||||
npx local-ssl-proxy --source $BACKEND_SSL_PORT --target $BACKEND_PORT --hostname $PROXY_HOSTNAME > /dev/null 2>&1 &
|
||||
npx local-ssl-proxy --source $FRONTEND_SSL_PORT --target $FRONTEND_PORT --hostname $PROXY_HOSTNAME > /dev/null 2>&1 &
|
||||
|
||||
# 7. Signal handlers for clean shutdown
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo "🛑 Shutting down services..."
|
||||
pkill -P $$ > /dev/null 2>&1
|
||||
kill $(jobs -p) 2>/dev/null || true
|
||||
pkill -f "uvicorn" 2>/dev/null || true
|
||||
pkill -f "next-server" 2>/dev/null || true
|
||||
pkill -f "local-ssl-proxy" 2>/dev/null || true
|
||||
echo "✅ Cleanup complete"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Trap Ctrl-C (SIGINT) and other termination signals
|
||||
trap cleanup SIGINT SIGTERM EXIT
|
||||
|
||||
# 8. Print Unified Access Banner
|
||||
|
||||
# Colors
|
||||
# Color output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BOLD='\033[1m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}=======================================================${NC}"
|
||||
echo -e "${GREEN}${BOLD} 🚀 TFM aInventory UNIFIED ACCESS${NC}"
|
||||
echo -e "${GREEN}=======================================================${NC}"
|
||||
echo ""
|
||||
echo -e " USE THIS URL ON BOTH DESKTOP & MOBILE:"
|
||||
echo -e " 👉 ${GREEN}${BOLD}https://$LOCAL_IP:$FRONTEND_SSL_PORT${NC}"
|
||||
echo -e " (Or ${GREEN}https://localhost:$FRONTEND_SSL_PORT${NC} on this Mac)"
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
||||
|
||||
if [ ! -z "$EXTRA_ALLOWED_ORIGINS" ]; then
|
||||
echo -e " EXTERNAL/VPN ACCESS points:"
|
||||
IFS=',' read -ra ADDR <<< "$EXTRA_ALLOWED_ORIGINS"
|
||||
for exp in "${ADDR[@]}"; do
|
||||
# Trim spaces and print
|
||||
TRIMMED=$(echo $exp | xargs)
|
||||
echo -e " 👉 ${GREEN}${BOLD}https://$TRIMMED:$FRONTEND_SSL_PORT${NC}"
|
||||
done
|
||||
fi
|
||||
echo ""
|
||||
echo -e " ${YELLOW}${BOLD}NOTE:${NC} If you see a 'Not Private' warning,"
|
||||
echo -e " Click 'Advanced' -> 'Proceed' to continue."
|
||||
echo -e "${GREEN}=======================================================${NC}"
|
||||
echo "Keep this window open while working. Press Ctrl-C to stop."
|
||||
echo -e "${GREEN}=======================================================${NC}"
|
||||
log_info "=== TFM aInventory Standalone Server Start ==="
|
||||
|
||||
# Wait for background processes
|
||||
# ============================================================================
|
||||
# STEP 1: Pre-flight checks
|
||||
# ============================================================================
|
||||
log_info "Step 1/7: Running pre-flight checks..."
|
||||
|
||||
# Check Python version
|
||||
command -v python3 &> /dev/null || log_error "Python 3 not installed"
|
||||
PYTHON_VERSION=$(python3 --version 2>&1 | awk '{print $2}')
|
||||
log_success " ✓ Python $PYTHON_VERSION found"
|
||||
|
||||
# Check Node.js version
|
||||
command -v node &> /dev/null || log_error "Node.js not installed"
|
||||
NODE_VERSION=$(node --version)
|
||||
log_success " ✓ Node.js $NODE_VERSION found"
|
||||
|
||||
# Check required files
|
||||
[[ -f "inventory.env" ]] || log_error "inventory.env not found. Copy from inventory.env.template"
|
||||
log_success " ✓ inventory.env found"
|
||||
|
||||
[[ -d "backend" ]] || log_error "backend/ directory not found"
|
||||
log_success " ✓ backend/ directory found"
|
||||
|
||||
[[ -d "frontend" ]] || log_error "frontend/ directory not found"
|
||||
log_success " ✓ frontend/ directory found"
|
||||
|
||||
# ============================================================================
|
||||
# STEP 2: Load environment
|
||||
# ============================================================================
|
||||
log_info "Step 2/7: Loading environment configuration..."
|
||||
|
||||
source inventory.env
|
||||
|
||||
BACKEND_PORT=${BACKEND_PORT:-8000}
|
||||
FRONTEND_PORT=${FRONTEND_PORT:-3000}
|
||||
DATA_DIR=${DATA_DIR:-./data}
|
||||
LOGS_DIR=${LOGS_DIR:-./logs}
|
||||
LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||
|
||||
log_success " ✓ Environment loaded (Backend: $BACKEND_PORT, Frontend: $FRONTEND_PORT)"
|
||||
|
||||
# ============================================================================
|
||||
# STEP 3: Prepare directories
|
||||
# ============================================================================
|
||||
log_info "Step 3/7: Preparing data and log directories..."
|
||||
|
||||
mkdir -p "$DATA_DIR" "$LOGS_DIR"
|
||||
mkdir -p "$DATA_DIR/temp"
|
||||
chmod -R 755 "$DATA_DIR" "$LOGS_DIR"
|
||||
|
||||
log_success " ✓ Directories ready"
|
||||
|
||||
# ============================================================================
|
||||
# STEP 4: Check port availability
|
||||
# ============================================================================
|
||||
log_info "Step 4/7: Checking port availability..."
|
||||
|
||||
for port in $BACKEND_PORT $FRONTEND_PORT; do
|
||||
if command -v netstat &> /dev/null; then
|
||||
if netstat -tuln 2>/dev/null | grep -q ":$port "; then
|
||||
log_error "Port $port is already in use. Stop the application using it or change the port."
|
||||
fi
|
||||
else
|
||||
log_warn " netstat not available; skipping port check"
|
||||
fi
|
||||
done
|
||||
|
||||
log_success " ✓ Required ports are available"
|
||||
|
||||
# ============================================================================
|
||||
# STEP 5: Start backend server
|
||||
# ============================================================================
|
||||
log_info "Step 5/7: Starting backend API server..."
|
||||
|
||||
# Create backend log file
|
||||
BACKEND_LOG="$LOGS_DIR/backend.log"
|
||||
touch "$BACKEND_LOG"
|
||||
|
||||
# Start backend in background
|
||||
cd backend || log_error "Failed to enter backend directory"
|
||||
|
||||
if [[ ! -f "requirements.txt" ]]; then
|
||||
log_error "backend/requirements.txt not found"
|
||||
fi
|
||||
|
||||
# Install dependencies if needed
|
||||
if ! python3 -c "import fastapi" &> /dev/null; then
|
||||
log_info " Installing Python dependencies..."
|
||||
pip install -q -r requirements.txt || log_error "Failed to install backend dependencies"
|
||||
fi
|
||||
|
||||
# Initialize database if needed
|
||||
if [[ ! -f "../$DATA_DIR/inventory.db" ]]; then
|
||||
log_info " Initializing database..."
|
||||
python3 -c "from db_manager import init_db; init_db()" || true
|
||||
fi
|
||||
|
||||
# Start uvicorn in background
|
||||
log_info " Starting uvicorn server..."
|
||||
python3 -m uvicorn main:app --host 0.0.0.0 --port "$BACKEND_PORT" --log-level "${LOG_LEVEL,,}" >> "$BACKEND_LOG" 2>&1 &
|
||||
BACKEND_PID=$!
|
||||
|
||||
log_success " ✓ Backend started (PID: $BACKEND_PID, Port: $BACKEND_PORT)"
|
||||
|
||||
cd - > /dev/null || true
|
||||
|
||||
# ============================================================================
|
||||
# STEP 6: Start frontend server
|
||||
# ============================================================================
|
||||
log_info "Step 6/7: Starting frontend web server..."
|
||||
|
||||
FRONTEND_LOG="$LOGS_DIR/frontend.log"
|
||||
touch "$FRONTEND_LOG"
|
||||
|
||||
cd frontend || log_error "Failed to enter frontend directory"
|
||||
|
||||
if [[ ! -f "package.json" ]]; then
|
||||
log_error "frontend/package.json not found"
|
||||
fi
|
||||
|
||||
# Install dependencies if needed
|
||||
if [[ ! -d "node_modules" ]]; then
|
||||
log_info " Installing npm dependencies..."
|
||||
npm ci --silent || log_error "Failed to install frontend dependencies"
|
||||
fi
|
||||
|
||||
# Build Next.js if not already built
|
||||
if [[ ! -d ".next" ]]; then
|
||||
log_info " Building Next.js application..."
|
||||
npm run build || log_error "Failed to build frontend"
|
||||
fi
|
||||
|
||||
# Start Next.js in production mode
|
||||
log_info " Starting Next.js server..."
|
||||
PORT="$FRONTEND_PORT" node .next/standalone/server.js >> "$FRONTEND_LOG" 2>&1 &
|
||||
FRONTEND_PID=$!
|
||||
|
||||
log_success " ✓ Frontend started (PID: $FRONTEND_PID, Port: $FRONTEND_PORT)"
|
||||
|
||||
cd - > /dev/null || true
|
||||
|
||||
# ============================================================================
|
||||
# STEP 7: Verify services
|
||||
# ============================================================================
|
||||
log_info "Step 7/7: Verifying services..."
|
||||
|
||||
# Wait a few seconds for services to start
|
||||
sleep 3
|
||||
|
||||
# Check backend
|
||||
if curl -sf "http://localhost:${BACKEND_PORT}/health" &> /dev/null; then
|
||||
log_success " ✓ Backend API responding at http://localhost:${BACKEND_PORT}/health"
|
||||
else
|
||||
log_warn " Backend not responding yet; it may still be initializing"
|
||||
fi
|
||||
|
||||
# Check frontend
|
||||
if curl -sf "http://localhost:${FRONTEND_PORT}/" &> /dev/null; then
|
||||
log_success " ✓ Frontend responding at http://localhost:${FRONTEND_PORT}"
|
||||
else
|
||||
log_warn " Frontend not responding yet; it may still be initializing"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Summary and instructions
|
||||
# ============================================================================
|
||||
echo ""
|
||||
echo -e "${GREEN}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${GREEN}║${NC} Standalone servers started successfully! ${GREEN}║${NC}"
|
||||
echo -e "${GREEN}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
echo "Access points:"
|
||||
echo " Frontend: http://localhost:${FRONTEND_PORT}"
|
||||
echo " Backend: http://localhost:${BACKEND_PORT}"
|
||||
echo " API Docs: http://localhost:${BACKEND_PORT}/docs"
|
||||
echo ""
|
||||
echo "Process IDs:"
|
||||
echo " Backend: $BACKEND_PID"
|
||||
echo " Frontend: $FRONTEND_PID"
|
||||
echo ""
|
||||
echo "Log files:"
|
||||
echo " Backend: $BACKEND_LOG"
|
||||
echo " Frontend: $FRONTEND_LOG"
|
||||
echo ""
|
||||
echo "Monitoring:"
|
||||
echo " Backend: tail -f $BACKEND_LOG"
|
||||
echo " Frontend: tail -f $FRONTEND_LOG"
|
||||
echo " All logs: tail -f $LOGS_DIR/*.log"
|
||||
echo ""
|
||||
echo "To stop servers:"
|
||||
echo " kill $BACKEND_PID $FRONTEND_PID"
|
||||
echo ""
|
||||
log_success "Servers are running!"
|
||||
|
||||
# Keep script running and handle signals
|
||||
trap "log_info 'Received interrupt signal'; kill $BACKEND_PID $FRONTEND_PID 2>/dev/null || true; exit 0" SIGINT SIGTERM
|
||||
|
||||
# Wait for both processes
|
||||
wait
|
||||
|
||||
Reference in New Issue
Block a user