#!/bin/bash ## ## Tractatus Unified Deployment Script ## Replaces: deploy-full-project-SAFE.sh and deploy-frontend.sh ## ## Features: ## - Automatic JS/CSS change detection ## - Unified cache versioning (update-cache-version.js) ## - Auto-commit cache version changes ## - Security exclusions via .rsyncignore ## - Frontend-only or full project deployment ## - Optional service restart ## ## Usage: ## ./scripts/deploy.sh # Full deployment with auto-detection ## ./scripts/deploy.sh --frontend-only # Deploy public/ directory only ## ./scripts/deploy.sh --force-cache # Force cache update regardless ## ./scripts/deploy.sh --restart # Restart service after deployment ## ./scripts/deploy.sh --dry-run # Show what would be deployed ## set -e # Colors GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' BLUE='\033[0;34m' NC='\033[0m' # Configuration DEPLOY_KEY="/home/theflow/.ssh/tractatus_deploy" REMOTE_USER="ubuntu" REMOTE_HOST="vps-93a693da.vps.ovh.net" REMOTE_PATH="/var/www/tractatus" PROJECT_ROOT="/home/theflow/projects/tractatus" # Parse flags FRONTEND_ONLY=false FORCE_CACHE=false RESTART_SERVICE=false DRY_RUN=false while [[ $# -gt 0 ]]; do case $1 in --frontend-only) FRONTEND_ONLY=true shift ;; --force-cache) FORCE_CACHE=true shift ;; --restart) RESTART_SERVICE=true shift ;; --dry-run) DRY_RUN=true shift ;; *) echo -e "${RED}Unknown option: $1${NC}" echo "Usage: $0 [--frontend-only] [--force-cache] [--restart] [--dry-run]" exit 1 ;; esac done # Header echo "" echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}" echo -e "${BLUE}║ TRACTATUS UNIFIED DEPLOYMENT ║${NC}" echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}" echo "" if [ "$DRY_RUN" = true ]; then echo -e "${YELLOW}🔍 DRY-RUN MODE - No changes will be made${NC}" echo "" fi if [ "$FRONTEND_ONLY" = true ]; then echo -e "${YELLOW}📁 Mode: Frontend-only (public/ directory)${NC}" else echo -e "${YELLOW}📁 Mode: Full project deployment${NC}" fi echo "" # Step 1: Pre-deployment checks echo -e "${GREEN}[1/7] PRE-DEPLOYMENT CHECKS${NC}" echo "" # Check if we're in the right directory if [ ! -f "$PROJECT_ROOT/package.json" ]; then echo -e "${RED}✗ ERROR: Not in project root directory${NC}" echo "Expected: $PROJECT_ROOT" exit 1 fi echo -e " ✓ Project root verified" # Check .rsyncignore exists (for full deployment) if [ "$FRONTEND_ONLY" = false ] && [ ! -f "$PROJECT_ROOT/.rsyncignore" ]; then echo -e "${RED}✗ ERROR: .rsyncignore not found!${NC}" echo "This file is required to prevent sensitive data deployment." exit 1 fi if [ "$FRONTEND_ONLY" = false ]; then echo -e " ✓ .rsyncignore found" fi # Check for confidential documents (inst_012/inst_015) echo -e " Checking for confidential documents..." if [ "$FRONTEND_ONLY" = true ]; then PUBLIC_FILES=$(find public -type f \( -name "*.md" -o -name "*.html" -o -name "*.txt" \) 2>/dev/null || true) else PUBLIC_FILES=$(find public docs -type f \( -name "*.md" -o -name "*.html" -o -name "*.txt" \) 2>/dev/null || true) fi if [ -n "$PUBLIC_FILES" ]; then if ! node scripts/check-confidential-docs.js $PUBLIC_FILES 2>&1 | grep -q "No confidential"; then echo -e "${RED}✗ ERROR: Confidential documents detected - DEPLOYMENT BLOCKED (inst_012/inst_015)${NC}" echo "" node scripts/check-confidential-docs.js $PUBLIC_FILES exit 1 fi fi echo -e " ✓ No confidential documents" # Check local server is running if ! lsof -i :9000 >/dev/null 2>&1; then echo -e "${YELLOW} ⚠ WARNING: Local server not running on port 9000${NC}" echo " It's recommended to test changes locally before deployment." if [ "$DRY_RUN" = false ]; then read -p " Continue anyway? (yes/NO): " continue_no_server if [ "$continue_no_server" != "yes" ]; then echo "Deployment cancelled. Start local server with: npm start" exit 1 fi fi else echo -e " ✓ Local server running on port 9000" fi # Check for uncommitted changes (excluding cache version files) UNCOMMITTED=$(git status --porcelain | grep -v "public/.*\.html" | grep -v "public/service-worker.js" | grep -v "public/version.json" || true) if [ ! -z "$UNCOMMITTED" ]; then echo -e "${YELLOW} ⚠ WARNING: Uncommitted changes detected:${NC}" echo "$UNCOMMITTED" | sed 's/^/ /' if [ "$DRY_RUN" = false ]; then read -p " Continue with uncommitted changes? (yes/NO): " continue_uncommitted if [ "$continue_uncommitted" != "yes" ]; then echo "Deployment cancelled. Commit changes first." exit 1 fi fi else echo -e " ✓ No uncommitted changes (excluding cache version files)" fi echo "" # Step 2: Cache version update echo -e "${GREEN}[2/7] CACHE VERSION UPDATE${NC}" echo "" NEEDS_CACHE_UPDATE=false # Check if JS/CSS files changed since last commit CHANGED_JS=$(git diff --name-only HEAD~1 2>/dev/null | grep -E "public/.*\.(js|css)$" || true) if [ ! -z "$CHANGED_JS" ]; then NEEDS_CACHE_UPDATE=true echo -e "${YELLOW} ⚠ JavaScript/CSS files changed since last commit:${NC}" echo "$CHANGED_JS" | sed 's/^/ - /' echo "" fi # Force cache update if requested if [ "$FORCE_CACHE" = true ]; then NEEDS_CACHE_UPDATE=true echo -e "${YELLOW} ⚠ Cache update forced via --force-cache flag${NC}" echo "" fi if [ "$NEEDS_CACHE_UPDATE" = true ]; then echo " Running cache version update..." if [ "$DRY_RUN" = false ]; then cd "$PROJECT_ROOT" node scripts/update-cache-version.js # Check if cache version files were modified CACHE_CHANGES=$(git status --porcelain | grep -E "(\.html|service-worker\.js|version\.json)" || true) if [ ! -z "$CACHE_CHANGES" ]; then echo "" echo -e "${GREEN} ✓ Cache version updated${NC}" echo "" echo -e "${YELLOW} Auto-committing cache version changes...${NC}" git add public/*.html public/**/*.html public/service-worker.js public/version.json 2>/dev/null || true git commit -m "chore: bump cache version for deployment" --no-verify echo -e "${GREEN} ✓ Cache version changes committed${NC}" else echo -e "${GREEN} ✓ Cache version already up to date${NC}" fi else echo -e "${BLUE} [DRY-RUN] Would run: node scripts/update-cache-version.js${NC}" echo -e "${BLUE} [DRY-RUN] Would commit cache version changes${NC}" fi else echo -e "${GREEN} ✓ No cache update needed (no JS/CSS changes)${NC}" echo " Use --force-cache to update anyway" fi echo "" # Step 3: Git status echo -e "${GREEN}[3/7] GIT STATUS${NC}" echo "" git log --oneline -5 | sed 's/^/ /' echo "" echo "Current status:" git status -s | sed 's/^/ /' || echo " (working tree clean)" echo "" # Step 4: Security check (full deployment only) if [ "$FRONTEND_ONLY" = false ]; then echo -e "${GREEN}[4/7] SECURITY CHECK${NC}" echo "" echo "Excluded patterns from .rsyncignore:" head -20 "$PROJECT_ROOT/.rsyncignore" | grep -v "^#" | grep -v "^$" | sed 's/^/ - /' || echo " (none)" echo " ... (see .rsyncignore for full list)" else echo -e "${GREEN}[4/7] SECURITY CHECK${NC}" echo "" echo " Frontend-only mode: deploying public/ directory only" fi echo "" # Step 5: Deployment confirmation echo -e "${GREEN}[5/7] DEPLOYMENT CONFIRMATION${NC}" echo "" if [ "$FRONTEND_ONLY" = true ]; then DEPLOY_SOURCE="$PROJECT_ROOT/public/" DEPLOY_DEST="${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/public/" echo -e "${YELLOW}Source:${NC} $DEPLOY_SOURCE" echo -e "${YELLOW}Destination:${NC} $DEPLOY_DEST" else DEPLOY_SOURCE="$PROJECT_ROOT/" DEPLOY_DEST="${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/" echo -e "${YELLOW}Source:${NC} $DEPLOY_SOURCE" echo -e "${YELLOW}Destination:${NC} $DEPLOY_DEST" echo -e "${YELLOW}Exclusions:${NC} via .rsyncignore" fi if [ "$RESTART_SERVICE" = true ]; then echo -e "${YELLOW}Post-deployment:${NC} Restart tractatus service" fi echo "" if [ "$DRY_RUN" = false ]; then read -p "Continue with deployment? (yes/NO): " confirm if [ "$confirm" != "yes" ]; then echo "Deployment cancelled." exit 0 fi fi echo "" # Step 6: Deployment echo -e "${GREEN}[6/7] DEPLOYING TO PRODUCTION${NC}" echo "" # Build rsync command RSYNC_CMD="rsync -avz --delete -e 'ssh -i $DEPLOY_KEY'" if [ "$FRONTEND_ONLY" = false ]; then RSYNC_CMD="$RSYNC_CMD --exclude-from=$PROJECT_ROOT/.rsyncignore" else # Frontend-only exclusions (minimal) RSYNC_CMD="$RSYNC_CMD --exclude=node_modules --exclude=.git" fi # Dry-run preview echo "Dry-run preview..." if [ "$FRONTEND_ONLY" = true ]; then rsync -avzn --delete \ -e "ssh -i $DEPLOY_KEY" \ --exclude=node_modules --exclude=.git \ "$DEPLOY_SOURCE" \ "$DEPLOY_DEST" \ | tail -20 else rsync -avzn --delete \ -e "ssh -i $DEPLOY_KEY" \ --exclude-from="$PROJECT_ROOT/.rsyncignore" \ "$DEPLOY_SOURCE" \ "$DEPLOY_DEST" \ | tail -20 fi echo "" if [ "$DRY_RUN" = true ]; then echo -e "${BLUE}[DRY-RUN] Deployment would proceed here${NC}" echo "" echo -e "${GREEN}Dry-run complete. No changes made.${NC}" exit 0 fi read -p "Proceed with actual deployment? (yes/NO): " confirm2 if [ "$confirm2" != "yes" ]; then echo "Deployment cancelled after dry-run." exit 0 fi echo "" echo "Deploying..." # Actual deployment if [ "$FRONTEND_ONLY" = true ]; then rsync -avz --delete \ -e "ssh -i $DEPLOY_KEY" \ --exclude=node_modules --exclude=.git \ "$DEPLOY_SOURCE" \ "$DEPLOY_DEST" else rsync -avz --delete \ -e "ssh -i $DEPLOY_KEY" \ --exclude-from="$PROJECT_ROOT/.rsyncignore" \ "$DEPLOY_SOURCE" \ "$DEPLOY_DEST" fi echo "" echo -e "${GREEN}✓ Deployment complete${NC}" # Step 7: Post-deployment echo "" echo -e "${GREEN}[7/7] POST-DEPLOYMENT${NC}" echo "" # Restart service if requested if [ "$RESTART_SERVICE" = true ]; then echo "Restarting tractatus service..." ssh -i "$DEPLOY_KEY" "${REMOTE_USER}@${REMOTE_HOST}" "sudo systemctl restart tractatus" sleep 2 ssh -i "$DEPLOY_KEY" "${REMOTE_USER}@${REMOTE_HOST}" "sudo systemctl status tractatus --no-pager" || true echo "" echo -e "${GREEN}✓ Service restarted${NC}" fi # Verification commands echo -e "${YELLOW}Verification commands:${NC}" echo "" echo "1. Check sensitive files NOT deployed:" echo " ssh -i $DEPLOY_KEY $REMOTE_USER@$REMOTE_HOST 'ls -la /var/www/tractatus/CLAUDE.md 2>/dev/null || echo \"NOT FOUND (good)\"'" echo "" echo "2. Check service status:" echo " ssh -i $DEPLOY_KEY $REMOTE_USER@$REMOTE_HOST 'sudo systemctl status tractatus'" echo "" echo "3. Test site:" echo " https://agenticgovernance.digital" echo "" # Summary echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}" echo -e "${BLUE}║ DEPLOYMENT COMPLETE ║${NC}" echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}" echo ""