- 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>
399 lines
12 KiB
Bash
Executable file
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 ""
|