tractatus/scripts/deploy.sh
TheFlow 3cb643ddeb fix(deployment): frontend-only mode now only checks public/ for uncommitted changes
- Frontend-only deployments no longer blocked by .claude/ session files
- Full deployments still check all files for uncommitted changes
- Allows deploying public/ changes without committing backend session state

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 23:22:40 +13:00

399 lines
12 KiB
Bash
Executable file

#!/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"
# Verify deployment structure (inst_025)
echo -e " Verifying deployment structure..."
if ! node scripts/verify-deployment-structure.js > /dev/null 2>&1; then
echo -e "${YELLOW} ⚠ WARNING: Deployment structure verification failed${NC}"
fi
echo -e " ✓ Deployment structure validated"
# Check file permissions (inst_020_CONSOLIDATED)
echo -e " Checking file permissions..."
if ! node scripts/check-file-permissions.js public > /dev/null 2>&1; then
echo -e "${YELLOW} ⚠ WARNING: Some file permissions may need correction${NC}"
fi
echo -e " ✓ File permissions checked"
# 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)
if [ "$FRONTEND_ONLY" = true ]; then
# Frontend-only: only check public/ directory for uncommitted changes
UNCOMMITTED=$(git status --porcelain public/ | grep -v "public/.*\.html" | grep -v "public/service-worker.js" | grep -v "public/version.json" || true)
else
# Full deployment: check all files
UNCOMMITTED=$(git status --porcelain | grep -v "public/.*\.html" | grep -v "public/service-worker.js" | grep -v "public/version.json" || true)
fi
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 ""