#!/bin/sh set -eu IMAGE_ARCHIVE="${1:?IMAGE_ARCHIVE is required}" IMAGE_TAG="${2:?IMAGE_TAG is required}" REMOTE_DIR="${3:-/opt/caiwuxitong}" DEPLOY_SHA="${4:?DEPLOY_SHA is required}" RUN_MIGRATIONS="${5:-0}" KEEP_IMAGES="${6:-2}" IMAGE_NAME="caiwu_akaunting" LOCK_FILE="$REMOTE_DIR/.deploy.lock" cleanup() { rm -f "$IMAGE_ARCHIVE" } trap cleanup EXIT INT TERM if [ "$(id -u)" -ne 0 ]; then echo "activate-release.sh must run as root" >&2 exit 1 fi if [ ! -f "$REMOTE_DIR/runtime/.env" ]; then echo "Missing $REMOTE_DIR/runtime/.env; run bootstrap-server.sh first" >&2 exit 1 fi if [ ! -f "$REMOTE_DIR/runtime/config-db.env" ]; then echo "Missing $REMOTE_DIR/runtime/config-db.env; run bootstrap-server.sh first" >&2 exit 1 fi if ! grep -q '^BUSINESS_STORAGE_DRIVER=qiniu$' "$REMOTE_DIR/runtime/qiniu-storage.env" 2>/dev/null \ || ! grep -q '^QINIU_ACCESS_KEY=.' "$REMOTE_DIR/runtime/qiniu-storage.env" 2>/dev/null \ || ! grep -q '^QINIU_SECRET_KEY=.' "$REMOTE_DIR/runtime/qiniu-storage.env" 2>/dev/null; then echo "Private business storage runtime is incomplete" >&2 exit 1 fi exec 9>"$LOCK_FILE" if ! flock -n 9; then echo "Another CaiWuXiTong deployment is already running" >&2 exit 1 fi PREVIOUS_IMAGE="$(docker inspect caiwu_app --format '{{.Config.Image}}' 2>/dev/null || true)" PREVIOUS_SHA="$(cat "$REMOTE_DIR/.deployed-sha" 2>/dev/null || true)" restore_previous_image() { case "$PREVIOUS_IMAGE" in "$IMAGE_NAME":*) IMAGE_TAG="${PREVIOUS_IMAGE#*:}" RUN_MIGRATIONS=0 export IMAGE_TAG RUN_MIGRATIONS docker compose -f compose.jiangling.yml up -d app || true ;; esac } docker load --input "$IMAGE_ARCHIVE" rm -f "$IMAGE_ARCHIVE" LOADED_SHA="$(docker image inspect "$IMAGE_NAME:$IMAGE_TAG" --format '{{index .Config.Labels "org.opencontainers.image.revision"}}')" if [ "$LOADED_SHA" != "$DEPLOY_SHA" ]; then echo "Image revision mismatch: expected $DEPLOY_SHA, got $LOADED_SHA" >&2 exit 1 fi cd "$REMOTE_DIR" export IMAGE_TAG RUN_MIGRATIONS docker compose -f compose.jiangling.yml up -d --remove-orphans healthy=0 attempt=1 while [ "$attempt" -le 30 ]; do if curl -fsS -o /dev/null http://127.0.0.1:18088/; then healthy=1 break fi sleep 5 attempt=$((attempt + 1)) done if [ "$healthy" -ne 1 ]; then docker compose -f compose.jiangling.yml logs --tail 120 app >&2 || true restore_previous_image echo "Release failed health check; previous image was restored when available" >&2 exit 1 fi if ! docker exec -u www-data caiwu_app php artisan storage:business-files:sync --require-qiniu; then docker compose -f compose.jiangling.yml logs --tail 120 app >&2 || true restore_previous_image echo "Release failed private business storage verification; previous image was restored when available" >&2 exit 1 fi if ! curl -fsS -o /dev/null http://127.0.0.1:18088/; then restore_previous_image echo "Release failed after business storage activation; previous image was restored when available" >&2 exit 1 fi printf '%s\n' "$DEPLOY_SHA" > "$REMOTE_DIR/.deployed-sha.tmp" mv "$REMOTE_DIR/.deployed-sha.tmp" "$REMOTE_DIR/.deployed-sha" # 只运行已登记来源的共享快照刷新命令,不启用整个 Laravel 调度器, # 避免意外启动账单提醒等既有任务。共享锁保证多环境不会重复访问上游。 cat > /etc/cron.d/caiwu-jiansun-snapshots <<'EOF' * * * * * root /usr/bin/flock -n /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan upstream:snapshots:refresh-all --quiet >/dev/null 2>&1 EOF chmod 644 /etc/cron.d/caiwu-jiansun-snapshots docker image ls "$IMAGE_NAME" --format '{{.Repository}}:{{.Tag}}' \ | awk '!seen[$0]++' \ | tail -n "+$((KEEP_IMAGES + 1))" \ | while IFS= read -r old_image; do [ -n "$old_image" ] && docker image rm "$old_image" >/dev/null 2>&1 || true done docker image prune -f --filter 'label=com.caiwu.project=CaiWuXiTong' >/dev/null || true docker compose -f compose.jiangling.yml ps echo "DEPLOYED_SHA=$DEPLOY_SHA" # Keep the variable for rollback diagnostics without printing any secret. : "$PREVIOUS_SHA"