2026-05-26 03:42:48 +08:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
set -euo pipefail
|
2026-05-19 13:19:03 +08:00
|
|
|
|
2026-05-26 03:42:48 +08:00
|
|
|
GHCR_PAT="$1"
|
|
|
|
|
NEW_TAG="${2:-latest}"
|
|
|
|
|
TAG_FILE="/var/lib/polyweather/.current_tag"
|
|
|
|
|
COMPOSE_DIR="/root/PolyWeather"
|
2026-05-30 21:21:30 +08:00
|
|
|
LOCK_FILE="${POLYWEATHER_DEPLOY_LOCK_FILE:-/var/lock/polyweather-deploy.lock}"
|
|
|
|
|
|
|
|
|
|
mkdir -p "$(dirname "$LOCK_FILE")"
|
|
|
|
|
exec 9>"$LOCK_FILE"
|
|
|
|
|
if ! flock -n 9; then
|
|
|
|
|
echo "❌ Another PolyWeather deploy is already running"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
2026-05-19 13:19:03 +08:00
|
|
|
|
2026-05-26 03:42:48 +08:00
|
|
|
echo "$GHCR_PAT" | docker login ghcr.io -u yangyuan-zhen --password-stdin
|
2026-05-19 13:19:03 +08:00
|
|
|
|
2026-05-26 03:42:48 +08:00
|
|
|
cd "$COMPOSE_DIR"
|
|
|
|
|
git fetch origin main && git reset --hard origin/main
|
|
|
|
|
|
|
|
|
|
PREVIOUS_TAG=""
|
|
|
|
|
if [ -f "$TAG_FILE" ]; then
|
|
|
|
|
PREVIOUS_TAG=$(cat "$TAG_FILE")
|
|
|
|
|
echo "Previous tag: $PREVIOUS_TAG"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-05-30 21:21:30 +08:00
|
|
|
rollback_to_previous() {
|
|
|
|
|
if [ -n "$PREVIOUS_TAG" ]; then
|
|
|
|
|
echo "Rolling back to $PREVIOUS_TAG..."
|
|
|
|
|
export IMAGE_TAG="$PREVIOUS_TAG"
|
|
|
|
|
docker compose pull
|
|
|
|
|
docker compose up -d
|
|
|
|
|
echo "✅ Rolled back to $PREVIOUS_TAG"
|
|
|
|
|
else
|
|
|
|
|
echo "⚠️ No previous tag to rollback to"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-26 03:42:48 +08:00
|
|
|
export IMAGE_TAG="$NEW_TAG"
|
2026-05-28 10:16:31 +08:00
|
|
|
pull_ok=0
|
|
|
|
|
for pull_attempt in $(seq 1 6); do
|
|
|
|
|
docker compose pull && pull_ok=1 && break
|
|
|
|
|
echo "Image pull failed or tag not ready, retry ${pull_attempt}/6..."
|
|
|
|
|
sleep 10
|
|
|
|
|
done
|
|
|
|
|
if [ "$pull_ok" != "1" ]; then
|
|
|
|
|
echo "❌ Image pull failed after retries"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
2026-05-26 03:55:21 +08:00
|
|
|
|
2026-05-30 14:24:02 +08:00
|
|
|
smoke_check() {
|
|
|
|
|
local name="$1"
|
|
|
|
|
local url="$2"
|
|
|
|
|
local timeout="$3"
|
|
|
|
|
local attempts="${4:-6}"
|
|
|
|
|
local delay="${5:-5}"
|
2026-05-30 22:51:25 +08:00
|
|
|
local output=""
|
2026-05-30 14:24:02 +08:00
|
|
|
|
|
|
|
|
for i in $(seq 1 "$attempts"); do
|
2026-05-30 22:51:25 +08:00
|
|
|
if output=$(curl -fsS -w "http=%{http_code} time=%{time_total}" -o /dev/null --max-time "$timeout" "$url" 2>&1); then
|
|
|
|
|
echo "✅ $name ($output)"
|
2026-05-30 14:24:02 +08:00
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
if [ "$i" != "$attempts" ]; then
|
2026-05-30 22:51:25 +08:00
|
|
|
echo " $name retry $i/$attempts... ($output)"
|
2026-05-30 14:24:02 +08:00
|
|
|
sleep "$delay"
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
2026-05-30 22:51:25 +08:00
|
|
|
echo "❌ $name ($output)"
|
2026-05-30 14:24:02 +08:00
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 21:21:30 +08:00
|
|
|
wait_for_local_service() {
|
|
|
|
|
local name="$1"
|
|
|
|
|
local url="$2"
|
|
|
|
|
local timeout="${3:-5}"
|
|
|
|
|
local attempts="${4:-30}"
|
|
|
|
|
local delay="${5:-2}"
|
|
|
|
|
|
|
|
|
|
for i in $(seq 1 "$attempts"); do
|
|
|
|
|
if curl -fsSo /dev/null --max-time "$timeout" "$url"; then
|
|
|
|
|
echo "✅ $name ready after attempt $i/$attempts"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
if [ "$i" != "$attempts" ]; then
|
|
|
|
|
echo " $name warming $i/$attempts..."
|
|
|
|
|
sleep "$delay"
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
echo "❌ $name did not become ready"
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
warm_public_route() {
|
|
|
|
|
local name="$1"
|
|
|
|
|
local url="$2"
|
|
|
|
|
local timeout="${3:-15}"
|
|
|
|
|
local attempts="${4:-3}"
|
|
|
|
|
local delay="${5:-2}"
|
|
|
|
|
|
|
|
|
|
for i in $(seq 1 "$attempts"); do
|
|
|
|
|
if curl -fsSo /dev/null --max-time "$timeout" "$url"; then
|
|
|
|
|
echo "✅ warmed $name"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
if [ "$i" != "$attempts" ]; then
|
|
|
|
|
echo " warm $name retry $i/$attempts..."
|
|
|
|
|
sleep "$delay"
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
echo "⚠️ warm $name failed"
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
echo "Updating Redis dependency..."
|
|
|
|
|
docker compose up -d polyweather_redis
|
|
|
|
|
|
|
|
|
|
echo "Updating backend services..."
|
|
|
|
|
docker compose up -d --no-deps polyweather_web polyweather
|
|
|
|
|
|
2026-05-26 03:55:21 +08:00
|
|
|
echo "Waiting for backend..."
|
2026-05-30 21:21:30 +08:00
|
|
|
wait_for_local_service "backend healthz" "http://127.0.0.1:8000/healthz" 5 30 5 || FAILED_BACKEND=1
|
|
|
|
|
FAILED_BACKEND="${FAILED_BACKEND:-0}"
|
|
|
|
|
if [ "$FAILED_BACKEND" = "1" ]; then
|
|
|
|
|
echo "❌ Backend did not become healthy"
|
|
|
|
|
rollback_to_previous
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo "Updating frontend..."
|
|
|
|
|
docker compose up -d --no-deps polyweather_frontend
|
|
|
|
|
|
|
|
|
|
echo "Waiting for frontend..."
|
|
|
|
|
wait_for_local_service "frontend root" "http://127.0.0.1:3001/" 5 40 2 || FAILED_FRONTEND=1
|
|
|
|
|
wait_for_local_service "frontend terminal" "http://127.0.0.1:3001/terminal" 10 20 2 || FAILED_FRONTEND=1
|
|
|
|
|
FAILED_FRONTEND="${FAILED_FRONTEND:-0}"
|
|
|
|
|
if [ "$FAILED_FRONTEND" = "1" ]; then
|
|
|
|
|
echo "❌ Frontend did not become healthy"
|
|
|
|
|
rollback_to_previous
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
warm_public_route "terminal" "https://polyweather.top/terminal" 20 4 3
|
|
|
|
|
warm_public_route "auth snapshot" "https://polyweather.top/api/auth/me?prefer_snapshot=1" 10 3 2
|
|
|
|
|
warm_public_route "cities" "https://polyweather.top/api/cities" 20 3 2
|
2026-05-26 03:42:48 +08:00
|
|
|
|
|
|
|
|
FAILED=0
|
2026-05-30 14:24:02 +08:00
|
|
|
smoke_check "healthz" "https://api.polyweather.top/healthz" 15 3 5 || FAILED=1
|
2026-05-30 22:51:25 +08:00
|
|
|
smoke_check "local cities" "http://127.0.0.1:8000/api/cities" 10 6 3 || FAILED=1
|
|
|
|
|
smoke_check "frontend cities" "https://polyweather.top/api/cities" 20 5 5 || FAILED=1
|
2026-05-30 14:24:02 +08:00
|
|
|
smoke_check "frontend" "https://www.polyweather.top/" 15 3 5 || FAILED=1
|
2026-05-26 03:42:48 +08:00
|
|
|
|
|
|
|
|
if [ "$FAILED" = "1" ]; then
|
|
|
|
|
echo "❌ Smoke tests failed. Rolling back..."
|
2026-05-30 21:21:30 +08:00
|
|
|
rollback_to_previous
|
2026-05-26 03:42:48 +08:00
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
mkdir -p "$(dirname "$TAG_FILE")"
|
|
|
|
|
echo "$NEW_TAG" > "$TAG_FILE"
|
2026-05-26 04:36:43 +08:00
|
|
|
docker image prune -af
|
2026-05-26 03:42:48 +08:00
|
|
|
echo "✅ Deployed $NEW_TAG"
|