82 lines
2.7 KiB
Bash
82 lines
2.7 KiB
Bash
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
DEPLOY_ROOT="${DEPLOY_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
|
BACKEND_ROOT="$DEPLOY_ROOT/backend"
|
|
FRONTEND_ROOT="$DEPLOY_ROOT/frontend"
|
|
COMPOSE=(docker compose --project-directory "$DEPLOY_ROOT" -f "$DEPLOY_ROOT/compose.yaml")
|
|
|
|
fail() {
|
|
printf '[deploy] ERROR: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
log() {
|
|
printf '[deploy] %s\n' "$*"
|
|
}
|
|
|
|
check_repo() {
|
|
local label="$1"
|
|
local path="$2"
|
|
|
|
[[ -d "$path/.git" ]] || fail "$label repository is missing at $path"
|
|
[[ "$(git -C "$path" branch --show-current)" == "main" ]] || fail "$label must be on main"
|
|
[[ -z "$(git -C "$path" status --porcelain)" ]] || fail "$label working tree is not clean"
|
|
git -C "$path" rev-parse --verify origin/main >/dev/null 2>&1 || fail "$label has no origin/main; pull it manually first"
|
|
[[ "$(git -C "$path" rev-parse HEAD)" == "$(git -C "$path" rev-parse origin/main)" ]] || fail "$label does not match origin/main; pull with --ff-only first"
|
|
}
|
|
|
|
wait_healthy() {
|
|
local service="$1"
|
|
local deadline=$((SECONDS + 180))
|
|
local container_id status
|
|
|
|
while (( SECONDS < deadline )); do
|
|
container_id="$("${COMPOSE[@]}" ps -q "$service")"
|
|
if [[ -n "$container_id" ]]; then
|
|
status="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_id")"
|
|
[[ "$status" == "healthy" || "$status" == "running" ]] && return 0
|
|
fi
|
|
sleep 3
|
|
done
|
|
"${COMPOSE[@]}" ps
|
|
fail "$service did not become healthy"
|
|
}
|
|
|
|
cd "$DEPLOY_ROOT"
|
|
[[ -f .env && -f backend/.env && -f frontend/.env ]] || fail "create .env, backend/.env, and frontend/.env first"
|
|
check_repo deployment "$DEPLOY_ROOT"
|
|
check_repo backend "$BACKEND_ROOT"
|
|
check_repo frontend "$FRONTEND_ROOT"
|
|
docker network inspect "${CADDY_NETWORK:-caddy_proxy}" >/dev/null 2>&1 || fail "the external Caddy network does not exist"
|
|
|
|
IMAGE_TAG="$(git -C "$BACKEND_ROOT" rev-parse --short=12 HEAD)-$(git -C "$FRONTEND_ROOT" rev-parse --short=12 HEAD)"
|
|
export IMAGE_TAG
|
|
log "validating production configuration"
|
|
"${COMPOSE[@]}" config --quiet
|
|
|
|
log "building application images"
|
|
"${COMPOSE[@]}" build backend frontend
|
|
|
|
log "starting data services"
|
|
"${COMPOSE[@]}" up -d postgres redis
|
|
wait_healthy postgres
|
|
wait_healthy redis
|
|
|
|
log "applying database migrations"
|
|
"${COMPOSE[@]}" --profile tools run --rm migrate
|
|
|
|
log "starting API, worker, and frontend"
|
|
"${COMPOSE[@]}" up -d backend worker frontend
|
|
wait_healthy backend
|
|
wait_healthy worker
|
|
wait_healthy frontend
|
|
|
|
log "deployment is healthy"
|
|
printf '[deploy] deployment=%s backend=%s frontend=%s image_tag=%s\n' \
|
|
"$(git rev-parse HEAD)" \
|
|
"$(git -C "$BACKEND_ROOT" rev-parse HEAD)" \
|
|
"$(git -C "$FRONTEND_ROOT" rev-parse HEAD)" \
|
|
"$IMAGE_TAG"
|
|
"${COMPOSE[@]}" ps
|