diff --git a/CLAUDE.md b/CLAUDE.md index 70ef6859..e1d12d2d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -108,8 +108,13 @@ node scripts/check-session-pressure.js # Check context pressure # Local development npm start # Start local server (port 9000) -# Production deployment -./scripts/deploy-full-project-SAFE.sh # Deploy to production (safe) +# Production deployment (unified workflow) +./scripts/deploy.sh # Full deployment (auto-detects changes, auto-commits cache) +./scripts/deploy.sh --frontend-only # Frontend-only (public/ directory) +./scripts/deploy.sh --force-cache --restart # Force cache update + restart service +./scripts/deploy.sh --dry-run # Preview without deploying + +# Service management (remote) ssh -i ~/.ssh/tractatus_deploy ubuntu@vps-93a693da.vps.ovh.net "sudo systemctl status tractatus" ssh -i ~/.ssh/tractatus_deploy ubuntu@vps-93a693da.vps.ovh.net "sudo systemctl restart tractatus" @@ -141,5 +146,5 @@ node scripts/generate-single-pdf.js --- -**Last Updated**: 2025-10-24 (Added session closedown script; session management now fully automated) +**Last Updated**: 2025-10-25 (Added unified deployment script; cache versioning now auto-committed) **Philosophy**: If it can be enforced in code, it should not be documented here. diff --git a/scripts/deploy-frontend.sh b/scripts/deploy-frontend.sh index f4905f3f..9fc07b6a 100755 --- a/scripts/deploy-frontend.sh +++ b/scripts/deploy-frontend.sh @@ -1,5 +1,31 @@ #!/bin/bash +## +## ⚠️ DEPRECATED - Use scripts/deploy.sh instead +## +## This script is deprecated in favor of the unified deployment script. +## The new script provides: +## - Automatic cache versioning (update-cache-version.js) +## - Auto-commit of cache changes (no more manual loops) +## - Frontend-only mode (--frontend-only flag) +## - Better security and validation +## +## Migration: +## OLD: ./scripts/deploy-frontend.sh +## NEW: ./scripts/deploy.sh --frontend-only +## +## See: scripts/deploy.sh for full documentation +## + +echo "⚠️ WARNING: This script is deprecated" +echo "Please use: ./scripts/deploy.sh --frontend-only" +echo "" +read -p "Continue with deprecated script anyway? (yes/NO): " continue_deprecated +if [ "$continue_deprecated" != "yes" ]; then + echo "Cancelled. Use ./scripts/deploy.sh --frontend-only instead." + exit 1 +fi + # Deploy Frontend with Automatic Cache Busting # Ensures users always get latest JS/CSS without manual cache clearing diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 00000000..033ae85f --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,360 @@ +#!/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 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 ""