Files
zarinpal-analytics-deployment/scripts/deploy.sh

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