#!/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 "══════════════════════════════════════"