#!/usr/bin/env bash set -Eeuo pipefail deploy_root="${1:?deployment checkout is required}" revision="${2:?commit SHA is required}" [[ "$deploy_root" == /* && "$deploy_root" != / ]] || { echo 'Use an absolute DEPLOY_PATH.' >&2; exit 2; } [[ "$revision" =~ ^[0-9a-f]{40,64}$ ]] || { echo 'Invalid commit SHA.' >&2; exit 2; } cd -- "$deploy_root" # Serialize manual and CI deployments; old queued runs cannot replace newer code. exec 9>.deploy.lock flock -x 9 [[ -f .env ]] || { echo 'Fill in .env before deploying.' >&2; exit 1; } git diff --quiet && git diff --cached --quiet || { echo 'Commit or remove server checkout edits first.' >&2; exit 1; } [[ "$(git symbolic-ref --short HEAD)" == main ]] || { echo 'The deployment checkout must be on main.' >&2; exit 1; } git fetch origin main if [[ "$(git rev-parse origin/main)" != "$revision" ]]; then echo 'Skipping an outdated deployment; a newer main commit exists.' exit 0 fi git merge --ff-only "$revision" export PORTFOLIO_IMAGE="portfolio:$revision" # Keep a consistent backup before migrations can run in the new container. container="$(docker compose ps -q portfolio-app)" if [[ -n "$container" ]]; then backup_name="before-$revision-$(date +%s)" mkdir -p backups docker compose exec -T portfolio-app node maintenance.mjs backup "/tmp/$backup_name" docker cp "$container:/tmp/$backup_name" "backups/$backup_name" fi # Build once; Next's production compilation includes the type check. docker compose up --build -d --remove-orphans --wait --wait-timeout 60