deploy.sh referenced apps/portal which was deleted in WS-3 PR-B1; the script has been broken in main since that merge (npm run build -w apps/portal would fail). Collapse to a single-app build. Changes: - deploy.sh: replace dual-build block (build app + portal, verify both dist/) with single-app build (build app, verify dist/index.html) - deploy/nginx/csp-portal.conf: deleted (content was identical to csp-spa.conf — verified before removal) - deploy/README.md: replace "Portal (portal.crewli.app)" server-block section with "Legacy portal redirect" — a 301 server block template that redirects portal.crewli.app → crewli.app preserving the request URI. Notes that DNS retirement is a separate ops task Out of scope: actually retiring the portal.crewli.app DNS record (operational, tracked separately). bash -n deploy.sh: clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
182 lines
8.3 KiB
Bash
Executable File
182 lines
8.3 KiB
Bash
Executable File
#!/bin/bash
|
|
set -e
|
|
|
|
# ──────────────────────────────────────────
|
|
# Crewli Deploy Script
|
|
# Run on VPS: ./deploy.sh [tag]
|
|
# Examples:
|
|
# ./deploy.sh → deploys latest main
|
|
# ./deploy.sh v1.2.0 → deploys specific tag
|
|
# ──────────────────────────────────────────
|
|
|
|
APP_DIR="/home/crewli/crewli"
|
|
PHP="/usr/local/php84/bin/php"
|
|
COMPOSER="/usr/local/bin/composer"
|
|
HEALTHCHECK_URL="https://api.crewli.app/up"
|
|
TAG="${1:-}"
|
|
|
|
# Load fnm (Node version manager)
|
|
export PATH="$HOME/.local/share/fnm:$PATH"
|
|
eval "$(fnm env)"
|
|
|
|
echo "══════════════════════════════════════"
|
|
echo " Crewli — Deploy"
|
|
echo "══════════════════════════════════════"
|
|
|
|
cd "$APP_DIR"
|
|
|
|
# ──────────────────────────────────────────
|
|
# 0. Preflight checks
|
|
# ──────────────────────────────────────────
|
|
|
|
# Prevent concurrent deploys (race conditions on git + npm)
|
|
exec 200>/tmp/crewli-deploy.lock
|
|
flock -n 200 || { echo "❌ Another deploy is already running. Aborting."; exit 1; }
|
|
|
|
# Verify .env exists — without it config:cache will crash the app
|
|
if [ ! -f .env ]; then
|
|
echo "❌ .env missing — aborting before any changes"
|
|
exit 1
|
|
fi
|
|
|
|
# Capture current commit for rollback hint on failure
|
|
PREVIOUS_COMMIT=$(git rev-parse HEAD)
|
|
echo "→ Current commit: $PREVIOUS_COMMIT"
|
|
|
|
# Rollback hint on any failure after this point
|
|
trap 'echo ""; echo "❌ Deploy failed. To roll back run:"; echo " cd $APP_DIR && git reset --hard $PREVIOUS_COMMIT && ./deploy.sh"; echo ""; $PHP artisan up 2>/dev/null || true' ERR
|
|
|
|
# ──────────────────────────────────────────
|
|
# 1. Maintenance mode
|
|
# ──────────────────────────────────────────
|
|
echo "→ Enabling maintenance mode..."
|
|
$PHP artisan down --retry=30 || true
|
|
|
|
# ──────────────────────────────────────────
|
|
# 2. Pull latest code
|
|
# ──────────────────────────────────────────
|
|
echo "→ Pulling from Gitea..."
|
|
git fetch --all --tags
|
|
|
|
if [ -n "$TAG" ]; then
|
|
echo "→ Checking out tag: $TAG"
|
|
git checkout "$TAG"
|
|
else
|
|
echo "→ Checking out latest main"
|
|
git checkout main
|
|
git pull origin main
|
|
fi
|
|
|
|
NEW_COMMIT=$(git rev-parse HEAD)
|
|
echo "→ New commit: $NEW_COMMIT"
|
|
|
|
# ──────────────────────────────────────────
|
|
# 3. Install PHP dependencies
|
|
# ──────────────────────────────────────────
|
|
# Only run composer install if composer.lock actually changed
|
|
if ! git diff --quiet "$PREVIOUS_COMMIT" "$NEW_COMMIT" -- composer.lock; then
|
|
echo "→ composer.lock changed — installing Composer dependencies..."
|
|
$PHP $COMPOSER install --no-dev --optimize-autoloader --no-interaction
|
|
else
|
|
echo "→ composer.lock unchanged — skipping composer install"
|
|
# Still regenerate autoloader in case new classes were added
|
|
$PHP $COMPOSER dump-autoload --optimize --no-dev --no-interaction
|
|
fi
|
|
|
|
# ──────────────────────────────────────────
|
|
# 4. Install Node dependencies and build
|
|
# ──────────────────────────────────────────
|
|
if ! git diff --quiet "$PREVIOUS_COMMIT" "$NEW_COMMIT" -- package-lock.json; then
|
|
echo "→ package-lock.json changed — running npm ci..."
|
|
npm ci --production=false
|
|
else
|
|
echo "→ package-lock.json unchanged — skipping npm ci"
|
|
fi
|
|
|
|
echo "→ Building frontend assets (apps/app)..."
|
|
npm run build -w apps/app
|
|
|
|
if [ ! -f "apps/app/dist/index.html" ]; then
|
|
echo "❌ Build failed: apps/app/dist/index.html missing"
|
|
exit 1
|
|
fi
|
|
|
|
# ──────────────────────────────────────────
|
|
# 5. Run migrations
|
|
# ──────────────────────────────────────────
|
|
echo "→ Running migrations..."
|
|
$PHP artisan migrate --force
|
|
|
|
# ──────────────────────────────────────────
|
|
# 6. Clear and rebuild caches
|
|
# ──────────────────────────────────────────
|
|
echo "→ Clearing all caches..."
|
|
$PHP artisan optimize:clear
|
|
|
|
echo "→ Rebuilding caches..."
|
|
$PHP artisan config:cache
|
|
$PHP artisan route:cache
|
|
$PHP artisan event:cache
|
|
$PHP artisan view:cache # Needed for Blade email templates (TransactionalMail)
|
|
|
|
# ──────────────────────────────────────────
|
|
# 7. File permissions
|
|
# ──────────────────────────────────────────
|
|
# Ensure storage/cache dirs are writable by both crewli user and www-data
|
|
echo "→ Fixing permissions..."
|
|
chown -R crewli:crewli storage bootstrap/cache
|
|
chmod -R ug+rwX storage bootstrap/cache
|
|
|
|
# ──────────────────────────────────────────
|
|
# 8. Restart queue (process pending jobs with new code)
|
|
# ──────────────────────────────────────────
|
|
# NB: Supervisor/systemd must be configured to restart workers after signal
|
|
echo "→ Signaling queue workers to restart..."
|
|
$PHP artisan queue:restart
|
|
|
|
# ──────────────────────────────────────────
|
|
# 9. Storage link (idempotent)
|
|
# ──────────────────────────────────────────
|
|
$PHP artisan storage:link 2>/dev/null || true
|
|
|
|
# ──────────────────────────────────────────
|
|
# 10. Disable maintenance mode
|
|
# ──────────────────────────────────────────
|
|
echo "→ Going live!"
|
|
$PHP artisan up
|
|
|
|
# ──────────────────────────────────────────
|
|
# 11. Health check
|
|
# ──────────────────────────────────────────
|
|
echo "→ Running health check..."
|
|
sleep 2 # Give PHP-FPM a moment to pick up new code
|
|
|
|
HEALTH_CODE=$(curl -sf -o /dev/null -w "%{http_code}" --max-time 10 "$HEALTHCHECK_URL" || echo "000")
|
|
|
|
if [ "$HEALTH_CODE" != "200" ]; then
|
|
echo ""
|
|
echo "⚠️ Health check failed — HTTP $HEALTH_CODE on $HEALTHCHECK_URL"
|
|
echo " Site is marked 'up' but may be broken."
|
|
echo " Investigate immediately or roll back:"
|
|
echo " cd $APP_DIR && git reset --hard $PREVIOUS_COMMIT && ./deploy.sh"
|
|
exit 1
|
|
fi
|
|
|
|
echo "✓ Health check passed (HTTP 200)"
|
|
|
|
# ──────────────────────────────────────────
|
|
# Done
|
|
# ──────────────────────────────────────────
|
|
|
|
# Clear the rollback trap — we made it
|
|
trap - ERR
|
|
|
|
echo ""
|
|
echo "══════════════════════════════════════"
|
|
if [ -n "$TAG" ]; then
|
|
echo " ✓ Deployed: $TAG"
|
|
else
|
|
echo " ✓ Deployed: main (latest)"
|
|
fi
|
|
echo " ✓ Commit: $NEW_COMMIT"
|
|
echo "══════════════════════════════════════" |