Files
crewli/deploy.sh
bert.hausmans 17373da1a5 feat: sourcemap upload to GlitchTip in deploy.sh
WS-7 PR-3 commit 3, RFC §3.5.

- deploy.sh: export VITE_SENTRY_RELEASE=crewli-app@<short-sha> before
  the Vite build so the release identifier is inlined into the bundle
  via import.meta.env.
- New step 4a after the build: when SENTRY_AUTH_TOKEN and
  VITE_SENTRY_DSN_FRONTEND are present, upload sourcemaps via
  `npx @sentry/cli@latest sourcemaps upload` to project crewli-app
  with --url-prefix=~/assets/ matching Vite's default asset path.
  Soft-fails with a warning so deploy can still succeed if GlitchTip
  is unreachable.
- Always run `find apps/app/dist -name '*.map' -delete` after upload
  (or after skipped upload). No public-mapped sources reach nginx —
  RFC §3.5 invariant.
- .gitignore: defensive `apps/app/dist/**/*.map` exclusion (dist/ is
  already broadly ignored; this is belt-and-suspenders against
  accidental commits of build output).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 17:59:58 +02:00

207 lines
9.8 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)..."
# WS-7 RFC §3.4 — release identifier is injected at build-time so Vite
# inlines it into import.meta.env.VITE_SENTRY_RELEASE for the bundle.
export VITE_SENTRY_RELEASE="crewli-app@$(git rev-parse --short HEAD)"
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
# ──────────────────────────────────────────
# 4a. Sourcemap upload to GlitchTip + scrub from dist/
# ──────────────────────────────────────────
# WS-7 RFC §3.5: maps generated by Vite, uploaded to GlitchTip so stack
# traces are readable in the UI, then DELETED from dist/ before nginx
# serves them. No public-mapped sources on production.
if [ -n "${SENTRY_AUTH_TOKEN:-}" ] && [ -n "${VITE_SENTRY_DSN_FRONTEND:-}" ]; then
SENTRY_ORG_VAL="${SENTRY_ORG:-crewli}"
echo "→ Uploading sourcemaps for release ${VITE_SENTRY_RELEASE} to project crewli-app..."
npx --yes @sentry/cli@latest sourcemaps upload \
--org "$SENTRY_ORG_VAL" \
--project crewli-app \
--release "$VITE_SENTRY_RELEASE" \
--url-prefix "~/assets/" \
apps/app/dist/assets || echo "⚠️ Sourcemap upload failed; continuing deploy."
else
echo "→ SENTRY_AUTH_TOKEN or VITE_SENTRY_DSN_FRONTEND unset — skipping sourcemap upload."
fi
echo "→ Stripping *.map files from dist/ (RFC §3.5: no public-mapped sources)..."
find apps/app/dist -name '*.map' -type f -delete
# ──────────────────────────────────────────
# 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 "══════════════════════════════════════"