chore: add production deploy script
Adds deploy.sh for the Crewli VPS: locks against concurrent deploys, conditionally runs composer/npm install, builds both SPAs, migrates, rebuilds caches, and health-checks the API before going live. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
187
deploy.sh
Executable file
187
deploy.sh
Executable file
@@ -0,0 +1,187 @@
|
||||
#!/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 + apps/portal)..."
|
||||
# Explicit per-workspace build to avoid silent single-app builds
|
||||
npm run build -w apps/app
|
||||
npm run build -w apps/portal
|
||||
|
||||
# Verify both dist folders exist and are non-empty
|
||||
for app in app portal; do
|
||||
if [ ! -f "apps/$app/dist/index.html" ]; then
|
||||
echo "❌ Build failed: apps/$app/dist/index.html missing"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# ──────────────────────────────────────────
|
||||
# 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 "══════════════════════════════════════"
|
||||
Reference in New Issue
Block a user