204 lines
7.9 KiB
Bash
204 lines
7.9 KiB
Bash
#!/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
|
||
|
||
clickhouse_env="$REMOTE_DIR/runtime/clickhouse.env"
|
||
if [ ! -f "$clickhouse_env" ]; then
|
||
clickhouse_password="$(openssl rand -hex 24)"
|
||
umask 077
|
||
{
|
||
printf '%s\n' \
|
||
'CLICKHOUSE_DB=caiwu_business_analytics' \
|
||
'CLICKHOUSE_USER=caiwu_app' \
|
||
"CLICKHOUSE_PASSWORD=$clickhouse_password" \
|
||
'CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1' \
|
||
'BUSINESS_CLICKHOUSE_ENABLED=true' \
|
||
'BUSINESS_CLICKHOUSE_URL=http://clickhouse:8123' \
|
||
'BUSINESS_CLICKHOUSE_DATABASE=caiwu_business_analytics' \
|
||
'BUSINESS_CLICKHOUSE_USERNAME=caiwu_app' \
|
||
"BUSINESS_CLICKHOUSE_PASSWORD=$clickhouse_password"
|
||
} > "$clickhouse_env"
|
||
chmod 600 "$clickhouse_env"
|
||
fi
|
||
|
||
if ! grep -q '^CLICKHOUSE_PASSWORD=.' "$clickhouse_env" \
|
||
|| ! grep -q '^BUSINESS_CLICKHOUSE_PASSWORD=.' "$clickhouse_env"; then
|
||
echo "Incomplete $clickhouse_env; refusing to overwrite private ClickHouse credentials" >&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
|
||
|
||
# Pull the independent columnar runtime before migrations or app switching.
|
||
# A registry/network failure therefore leaves both the database and current app untouched.
|
||
docker compose -f compose.jiangling.yml pull clickhouse
|
||
|
||
if [ "$RUN_MIGRATIONS" = "1" ]; then
|
||
# 先创建独立业务镜像库并授权应用账号,再启动会执行迁移的新应用。
|
||
# 该脚本幂等;已有数据库只会补齐授权,不会覆盖业务数据。
|
||
docker compose -f compose.jiangling.yml up -d config_db
|
||
config_db_attempt=1
|
||
while [ "$config_db_attempt" -le 24 ]; do
|
||
if [ "$(docker inspect caiwu_config_db --format '{{.State.Health.Status}}' 2>/dev/null || true)" = "healthy" ]; then
|
||
break
|
||
fi
|
||
|
||
sleep 5
|
||
config_db_attempt=$((config_db_attempt + 1))
|
||
done
|
||
if [ "$(docker inspect caiwu_config_db --format '{{.State.Health.Status}}' 2>/dev/null || true)" != "healthy" ]; then
|
||
docker compose -f compose.jiangling.yml logs --tail 80 config_db >&2 || true
|
||
echo "Shared MariaDB did not become healthy before mirror database initialization" >&2
|
||
exit 1
|
||
fi
|
||
docker exec caiwu_config_db sh /docker-entrypoint-initdb.d/10-config-db-security.sh
|
||
fi
|
||
|
||
docker compose -f compose.jiangling.yml up -d --remove-orphans
|
||
|
||
clickhouse_healthy=0
|
||
clickhouse_attempt=1
|
||
while [ "$clickhouse_attempt" -le 24 ]; do
|
||
if [ "$(docker inspect caiwu_clickhouse --format '{{.State.Health.Status}}' 2>/dev/null || true)" = "healthy" ]; then
|
||
clickhouse_healthy=1
|
||
break
|
||
fi
|
||
|
||
sleep 5
|
||
clickhouse_attempt=$((clickhouse_attempt + 1))
|
||
done
|
||
|
||
if [ "$clickhouse_healthy" -ne 1 ]; then
|
||
docker compose -f compose.jiangling.yml logs --tail 120 clickhouse >&2 || true
|
||
restore_previous_image
|
||
echo "ClickHouse did not become healthy; previous application image was restored when available" >&2
|
||
exit 1
|
||
fi
|
||
|
||
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"
|
||
|
||
# 只运行已登记的上游后台读取和 E 签宝状态轮询,不启用整个 Laravel
|
||
# 调度器,避免意外启动账单提醒等既有任务。上游读取共用一把锁,E 签宝单独加锁。
|
||
# 普通商品优先高频增量,并在配置的每周低峰窗口自动继续全量校准。
|
||
cat > /etc/cron.d/caiwu-jiansun-snapshots <<'EOF'
|
||
CRON_TZ=Asia/Shanghai
|
||
* * * * * root /usr/bin/flock -n /run/caiwu-upstream-sync.lock /bin/sh -c '/usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-product-catalog --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-product-purchase-prices --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-store-report --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan upstream:snapshots:refresh-all --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-product-prices --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-tables --quiet' >/dev/null 2>&1
|
||
0 0 * * * root /usr/bin/flock -w 3600 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-third-party-warehouse-storage --max-batches=0 --quiet >/dev/null 2>&1
|
||
*/2 * * * * root /usr/bin/flock -n /run/caiwu-esign-polling.lock /usr/bin/docker exec -u www-data caiwu_app php artisan esign:poll-signatures --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"
|