Compare commits

...

1 Commits

Author SHA1 Message Date
chenpei
293939000d fix: 自动修复本地代理上游漂移 2026-07-30 18:16:14 +08:00
2 changed files with 143 additions and 1 deletions

View File

@ -446,6 +446,9 @@ test -f Dockerfile || fail "Dockerfile missing after Git sync"
test -f package.json || fail "package.json missing after Git sync"
test -f "app/api/uploads/[...path]/route.ts" || fail "app/api/uploads fallback route missing"
test -f "app/uploads/[...path]/route.ts" || fail "app/uploads fallback route missing"
test -f "scripts/local-proxy-reconcile.sh" || fail "local proxy reconciler missing"
PROXY_RECONCILE_SCRIPT_B64="$(base64 -w 0 scripts/local-proxy-reconcile.sh)"
ok "Synced to commit $COMMIT_SHA"
log "Image tag: $COMMIT_TAG"
@ -573,6 +576,9 @@ PROXY_CONF="${PROXY_ROOT}/nginx.conf"
PROXY_PID="${PROXY_ROOT}/nginx.pid"
PROXY_SERVICE="${CONTAINER_NAME}-local-proxy.service"
CURRENT_FILE="${PROXY_ROOT}/current-container"
PROXY_RECONCILE_SCRIPT="${PROXY_ROOT}/reconcile-upstream.sh"
PROXY_RECONCILE_SERVICE="${CONTAINER_NAME}-local-proxy-reconcile.service"
PROXY_RECONCILE_TIMER="${CONTAINER_NAME}-local-proxy-reconcile.timer"
BLUE_CONTAINER="${CONTAINER_NAME}-blue"
GREEN_CONTAINER="${CONTAINER_NAME}-green"
PUBLIC_TRAFFIC_SYNC_ROOT="/mnt/PoolOne/apps/youyidian-3d/public-traffic-sync"
@ -802,6 +808,46 @@ EOF
systemctl enable "$PROXY_SERVICE" >/dev/null 2>&1 || true
}
install_proxy_reconciler() {
mkdir -p "$PROXY_ROOT"
printf '%s' "$PROXY_RECONCILE_SCRIPT_B64" | base64 -d > "$PROXY_RECONCILE_SCRIPT"
chmod 700 "$PROXY_RECONCILE_SCRIPT"
cat > "/etc/systemd/system/${PROXY_RECONCILE_SERVICE}" <<EOF
[Unit]
Description=Reconcile ${CONTAINER_NAME} local proxy upstream
After=docker.service ${PROXY_SERVICE}
Requires=docker.service
[Service]
Type=oneshot
Environment=CONTAINER_NAME=${CONTAINER_NAME}
Environment=PORT=${PORT}
Environment=PROXY_ROOT=${PROXY_ROOT}
Environment=PROXY_SERVICE=${PROXY_SERVICE}
ExecStart=${PROXY_RECONCILE_SCRIPT}
TimeoutStartSec=30
EOF
cat > "/etc/systemd/system/${PROXY_RECONCILE_TIMER}" <<EOF
[Unit]
Description=Check ${CONTAINER_NAME} local proxy upstream
[Timer]
OnBootSec=20s
OnUnitActiveSec=15s
AccuracySec=1s
Persistent=true
[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
systemctl enable "$PROXY_RECONCILE_TIMER" >/dev/null 2>&1 || true
systemctl restart "$PROXY_RECONCILE_TIMER"
}
switch_proxy() {
ensure_proxy_service
if is_proxy_running; then
@ -913,6 +959,7 @@ if ! curl -fsS --max-time 8 "http://127.0.0.1:${PORT}/api/health" >/tmp/git-depl
fi
printf '%s\n' "$NEXT_CONTAINER" > "$CURRENT_FILE"
install_proxy_reconciler
ok "Stable local entry is healthy on port $PORT"
docker logs --tail 20 "$NEXT_CONTAINER" 2>&1 || true
@ -950,7 +997,7 @@ PROD_SCRIPT
export_script="$(printf '%s' "$PROD_PAYLOAD" | base64 -w 0)"
ssh -o StrictHostKeyChecking=no "$PROD_SERVER_USER@$PROD_SERVER_IP" \
"TARGET='$TARGET' CONTAINER_NAME='$CONTAINER_NAME' PORT='$PORT' REGISTRY_COMMIT='$REGISTRY_COMMIT' COMMIT_SHA='$COMMIT_SHA' ENV_FILE_PATH='/mnt/PoolOne/apps/youyidian-3d/$ENV_FILE' PUBLIC_APP_URL='$PUBLIC_APP_URL' CORS_ALLOWED_ORIGINS='$CORS_ALLOWED_ORIGINS' DINGTALK_CALLBACK_ORIGIN='$DINGTALK_CALLBACK_ORIGIN' DINGTALK_INTERNAL_RETURN_ORIGINS='$DINGTALK_INTERNAL_RETURN_ORIGINS' S3_PUBLIC_DOMAIN='$S3_PUBLIC_DOMAIN' DOWNLOAD_TOKEN_SECRET='$DOWNLOAD_TOKEN_SECRET' NETWORK_NAME='youyidian-3d_app-network' UPLOADS_MOUNT='/mnt/PoolOne/apps/youyidian-3d/uploads' ROLLBACK_ON_FAILURE='$ROLLBACK_ON_FAILURE' bash -lc \"printf '%s' '$export_script' | base64 -d | bash\""
"TARGET='$TARGET' CONTAINER_NAME='$CONTAINER_NAME' PORT='$PORT' REGISTRY_COMMIT='$REGISTRY_COMMIT' COMMIT_SHA='$COMMIT_SHA' ENV_FILE_PATH='/mnt/PoolOne/apps/youyidian-3d/$ENV_FILE' PUBLIC_APP_URL='$PUBLIC_APP_URL' CORS_ALLOWED_ORIGINS='$CORS_ALLOWED_ORIGINS' DINGTALK_CALLBACK_ORIGIN='$DINGTALK_CALLBACK_ORIGIN' DINGTALK_INTERNAL_RETURN_ORIGINS='$DINGTALK_INTERNAL_RETURN_ORIGINS' S3_PUBLIC_DOMAIN='$S3_PUBLIC_DOMAIN' DOWNLOAD_TOKEN_SECRET='$DOWNLOAD_TOKEN_SECRET' PROXY_RECONCILE_SCRIPT_B64='$PROXY_RECONCILE_SCRIPT_B64' NETWORK_NAME='youyidian-3d_app-network' UPLOADS_MOUNT='/mnt/PoolOne/apps/youyidian-3d/uploads' ROLLBACK_ON_FAILURE='$ROLLBACK_ON_FAILURE' bash -lc \"printf '%s' '$export_script' | base64 -d | bash\""
ok "Production deployment command completed"
if [ "$TARGET" = "production" ]; then

View File

@ -0,0 +1,95 @@
#!/usr/bin/env bash
set -euo pipefail
CONTAINER_NAME="${CONTAINER_NAME:?CONTAINER_NAME is required}"
PORT="${PORT:?PORT is required}"
PROXY_ROOT="${PROXY_ROOT:?PROXY_ROOT is required}"
PROXY_SERVICE="${PROXY_SERVICE:?PROXY_SERVICE is required}"
CURRENT_FILE="${PROXY_ROOT}/current-container"
PROXY_CONF="${PROXY_ROOT}/nginx.conf"
PROXY_PID="${PROXY_ROOT}/nginx.pid"
LOCK_FILE="/run/${CONTAINER_NAME}-local-proxy-reconcile.lock"
log() {
printf '%s [%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" "$2"
}
exec 9>"$LOCK_FILE"
if command -v flock >/dev/null 2>&1; then
flock -n 9 || exit 0
fi
if [ ! -s "$CURRENT_FILE" ] || [ ! -s "$PROXY_CONF" ]; then
log "WARN" "proxy state files are missing"
exit 1
fi
current_container="$(tr -d '\r\n' < "$CURRENT_FILE")"
if [ -z "$current_container" ]; then
log "WARN" "current container is empty"
exit 1
fi
running="$(docker inspect -f '{{.State.Running}}' "$current_container" 2>/dev/null || true)"
current_ip="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$current_container" 2>/dev/null || true)"
if [ "$running" != "true" ] || [ -z "$current_ip" ]; then
log "WARN" "current container is unavailable: ${current_container}"
exit 1
fi
if ! curl -fsS --max-time 8 "http://${current_ip}:3000/api/health" >/dev/null; then
log "WARN" "current container health check failed: ${current_container} ${current_ip}:3000"
exit 1
fi
configured_ip="$(
sed -nE 's/^[[:space:]]*server ([0-9.]+):3000 .*/\1/p' "$PROXY_CONF" |
head -n 1
)"
entry_ok=0
if curl -fsS --max-time 8 "http://127.0.0.1:${PORT}/api/health" >/dev/null 2>&1; then
entry_ok=1
fi
if [ "$configured_ip" = "$current_ip" ] && [ "$entry_ok" = "1" ]; then
exit 0
fi
backup="${PROXY_CONF}.bak.reconcile.$(date +%Y%m%d-%H%M%S)"
temp_conf="$(mktemp "${PROXY_CONF}.tmp.XXXXXX")"
trap 'rm -f "$temp_conf"' EXIT
if [ "$configured_ip" != "$current_ip" ]; then
cp -a "$PROXY_CONF" "$backup"
sed -E \
"0,/server [0-9.]+:3000 /s//server ${current_ip}:3000 /" \
"$PROXY_CONF" > "$temp_conf"
if ! grep -q "server ${current_ip}:3000 " "$temp_conf"; then
log "ERROR" "failed to update nginx upstream"
exit 1
fi
/usr/sbin/nginx -t -p "${PROXY_ROOT}/" -c "$temp_conf"
mv "$temp_conf" "$PROXY_CONF"
fi
if ! systemctl reload "$PROXY_SERVICE"; then
if [ -f "$backup" ]; then
cp -a "$backup" "$PROXY_CONF"
/usr/sbin/nginx -t -p "${PROXY_ROOT}/" -c "$PROXY_CONF" >/dev/null
systemctl reload "$PROXY_SERVICE" || true
fi
log "ERROR" "failed to reload ${PROXY_SERVICE}"
exit 1
fi
sleep 1
if ! curl -fsS --max-time 8 "http://127.0.0.1:${PORT}/api/health" >/dev/null; then
log "ERROR" "stable local entry is still unhealthy after reconciliation"
exit 1
fi
log "OK" "upstream reconciled: ${configured_ip:-missing} -> ${current_ip}"