#!/usr/bin/env bash # Sync Git-tracked web/app and web/app-v2 into the running Nexus Docker container. # # Root cause: Dockerfile.prod runs vite build inside the image; Docker layer cache # can produce asset hashes that differ from committed web/app on the host. # # Usage (on production host after git pull / upgrade): # NEXUS_ROOT=/opt/nexus bash deploy/sync_webapp_to_container.sh # # Called automatically from deploy/nexus-1panel.sh cmd_upgrade and deploy-production.sh. set -euo pipefail NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}" CONTAINER="${NEXUS_CONTAINER:-}" NEXUS_PUBLISH_PORT="${NEXUS_PUBLISH_PORT:-8600}" info() { echo -e "\033[0;32m[INFO]\033[0m $*"; } warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; } error() { echo -e "\033[0;31m[ERROR]\033[0m $*" >&2; } docker_cmd() { if docker "$@" 2>/dev/null; then return 0; fi sudo docker "$@" 2>/dev/null } resolve_container() { if [[ -n "$CONTAINER" ]]; then return 0 fi CONTAINER="$(docker_cmd ps --format '{{.Names}}' | grep -E 'nexus-prod-nexus|nexus-nexus-1' | head -1 || true)" if [[ -z "$CONTAINER" ]]; then error "未找到运行中的 Nexus 容器(可设 NEXUS_CONTAINER=名称)" exit 1 fi info "容器: $CONTAINER" } verify_entry_bundle() { local html bundle_path code html="$(curl -sf "http://127.0.0.1:${NEXUS_PUBLISH_PORT}/app/" 2>/dev/null || true)" bundle_path="$(printf '%s' "$html" | sed -n 's/.*src="\(\/app\/assets\/index-[^"]*\.js\)".*/\1/p' | head -1)" if [[ -z "$bundle_path" ]]; then error "/app/ index.html 未解析到主 bundle" exit 1 fi code="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${NEXUS_PUBLISH_PORT}${bundle_path}" 2>/dev/null || echo 000)" if [[ "$code" != "200" ]]; then error "主 bundle ${bundle_path} → HTTP ${code}" exit 1 fi info "入口校验 OK: ${bundle_path} → HTTP 200" } verify_app_v2_entry_bundle() { local html bundle_path code html="$(curl -sf "http://127.0.0.1:${NEXUS_PUBLISH_PORT}/app-v2/" 2>/dev/null || true)" bundle_path="$(printf '%s' "$html" | sed -n 's/.*src="\(\/app-v2\/assets\/[^"]*\.js\)".*/\1/p' | head -1)" if [[ -z "$bundle_path" ]]; then error "/app-v2/ index.html 未解析到主 bundle" exit 1 fi code="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${NEXUS_PUBLISH_PORT}${bundle_path}" 2>/dev/null || echo 000)" if [[ "$code" != "200" ]]; then error "V2 主 bundle ${bundle_path} → HTTP ${code}" exit 1 fi info "V2 入口校验 OK: ${bundle_path} → HTTP 200" } main() { local app="${NEXUS_ROOT}/web/app" local app_v2="${NEXUS_ROOT}/web/app-v2" if [[ ! -f "${app}/index.html" ]]; then error "缺少 ${app}/index.html,请先 git pull" exit 1 fi if [[ ! -d "${app}/assets" ]]; then error "缺少 ${app}/assets" exit 1 fi if [[ ! -f "${app_v2}/index.html" ]]; then error "缺少 ${app_v2}/index.html,请先构建 frontend-v2" exit 1 fi if [[ ! -d "${app_v2}/assets" ]]; then error "缺少 ${app_v2}/assets" exit 1 fi resolve_container info "同步 web/app → 容器 /app/web/app ..." docker_cmd exec "$CONTAINER" rm -rf /app/web/app/assets docker_cmd exec "$CONTAINER" mkdir -p /app/web/app docker_cmd cp "${app}/index.html" "${CONTAINER}:/app/web/app/index.html" docker_cmd cp "${app}/assets" "${CONTAINER}:/app/web/app/assets" if [[ -f "${app}/favicon.ico" ]]; then docker_cmd cp "${app}/favicon.ico" "${CONTAINER}:/app/web/app/favicon.ico" 2>/dev/null || true fi info "同步 web/app-v2 → 容器 /app/web/app-v2 ..." docker_cmd exec "$CONTAINER" rm -rf /app/web/app-v2/assets docker_cmd exec "$CONTAINER" mkdir -p /app/web/app-v2 docker_cmd cp "${app_v2}/index.html" "${CONTAINER}:/app/web/app-v2/index.html" docker_cmd cp "${app_v2}/assets" "${CONTAINER}:/app/web/app-v2/assets" if [[ -x "${NEXUS_ROOT}/deploy/prune_frontend_assets.py" ]]; then info "容器内 prune 未引用 assets(可选)..." docker_cmd exec "$CONTAINER" python3 /app/deploy/prune_frontend_assets.py --dry-run 2>/dev/null || \ warn "容器内 prune 跳过(脚本或路径不可用)" fi verify_entry_bundle verify_app_v2_entry_bundle info "web/app 与 web/app-v2 同步完成" } main "$@"