#!/bin/sh set -eu umask 077 root=$1 upload=$2 commit=$3 build=$4 case "$root" in /opt/*|/srv/*|/home/*/*) ;; *) echo "Use a dedicated absolute deployment directory" >&2; exit 1;; esac case "$root" in *..*|*[!a-zA-Z0-9_./-]*) echo "Invalid deployment path" >&2; exit 1;; esac mkdir -p "$root/releases" "$root/backups" root=$(cd "$root" && pwd -P) exec 9>"$root/deploy.lock" flock -w 600 9 if [ -f "$root/last-build" ] && [ "$build" -le "$(cat "$root/last-build")" ]; then echo "A newer or identical build is already deployed" exit 0 fi release="$root/releases/$commit-$build" mkdir -p "$release" tar -xzf "$upload/lottery.tar.gz" -C "$release" install -m 600 "$upload/runtime.env" "$release/.env.prod" cd "$release" export BOT_IMAGE="new_lottery_bot:$commit" compose() { docker compose --project-name new_lottery_bot --env-file .env.prod "$@"; } compose config --quiet compose build --pull bot old_image=$(docker inspect --format '{{.Image}}' lottery_bot 2>/dev/null || true) old_project=$(docker inspect --format '{{index .Config.Labels "com.docker.compose.project"}}' lottery_bot 2>/dev/null || true) if [ -n "$old_image" ] && [ "$old_project" != "new_lottery_bot" ]; then echo "Existing lottery_bot belongs to another stack; align the Compose project before the first deployment" >&2 exit 1 fi services=$(compose config --services | grep -v '^bot$') # Service names come from this repository's trusted Compose file. compose up -d --wait $services compose run --rm --no-deps --user "$(id -u):$(id -g)" \ -v "$root/backups:/backups" -e BACKUP_FILE="/backups/$commit-$build-$(date -u +%Y%m%dT%H%M%SZ).dump" \ bot python scripts/backup_database.py rollback() { code=$? trap - EXIT if [ "$code" -ne 0 ] && [ -n "$old_image" ]; then echo "Deployment failed; restoring previous application image" >&2 BOT_IMAGE="$old_image" compose up -d --no-build --no-deps bot || true fi exit "$code" } trap rollback EXIT compose stop bot compose run --rm --no-deps bot python -m alembic upgrade head compose run --rm --no-deps bot python scripts/check_schema.py compose up -d --no-build --wait --wait-timeout 180 bot docker exec lottery_bot python -m src.core.health ln -s "$release" "$root/current.$build" mv -Tf "$root/current.$build" "$root/current" printf '%s\n' "$build" > "$root/last-build" trap - EXIT echo "Deployment verified: $commit (build $build)"