diff --git a/deploy/docker/README.md b/deploy/docker/README.md index 9710294..c7112ff 100644 --- a/deploy/docker/README.md +++ b/deploy/docker/README.md @@ -1,6 +1,6 @@ # sw2 Docker 部署 -前端镜像统一发布至 `harbor.fuhua91.com/game/sw2:<版本>`。发布服务器需先通过 `docker login harbor.fuhua91.com` 配置 push/pull 权限;脚本在替换容器前完成推送、拉取和摘要校验。前端独立发布入口位于前端仓库 `deploy/`,也支持直接指定 Harbor 版本拉取部署。 +后端镜像发布至 `harbor.fuhua91.com/game/sw2-backend:<版本>`,前端镜像发布至 `harbor.fuhua91.com/game/sw2:<版本>`。发布服务器需先通过 `docker login harbor.fuhua91.com` 配置两个镜像仓库的 push/pull 权限;也可将本应用的 Docker 登录配置放在部署根目录的 `harbor-docker/` 下,脚本优先使用该配置。脚本在替换容器前完成两个镜像的推送、拉取和摘要校验,并使用 Harbor 返回的摘要启动容器。前端独立发布入口位于前端仓库 `deploy/`,也支持直接指定 Harbor 版本拉取部署。 沿用 226 服务器 `/www/wwwroot/live.fuhua91.com` 的 Docker 网络、持久化目录和宝塔反向代理结构。使用独立的 `sw2-backend`、`sw2-frontend`、`sw2-net`,兼容服务器现有 Docker 18.06,不依赖 Compose。 diff --git a/deploy/docker/deploy.sh b/deploy/docker/deploy.sh index 4531f66..38c351c 100644 --- a/deploy/docker/deploy.sh +++ b/deploy/docker/deploy.sh @@ -5,10 +5,12 @@ TAG="${1:?Usage: deploy.sh TAG [--build-only]}" MODE="${2:-}" [[ "$TAG" =~ ^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,100}$ ]] || { echo 'Invalid release tag' >&2; exit 1; } [[ -z "$MODE" || "$MODE" == '--build-only' ]] || { echo 'Unknown mode' >&2; exit 1; } -BASE_DIR=/www/wwwroot/sw2.fuhua91.com +BASE_DIR="${SW_DEPLOY_BASE:-/www/wwwroot/sw2.fuhua91.com}" RELEASE="$BASE_DIR/releases/$TAG" -BACKEND_IMAGE="sw2/backend:$TAG" -FRONTEND_IMAGE="harbor.fuhua91.com/game/sw2:$TAG" +BACKEND_REPOSITORY=harbor.fuhua91.com/game/sw2-backend +FRONTEND_REPOSITORY=harbor.fuhua91.com/game/sw2 +BACKEND_IMAGE="$BACKEND_REPOSITORY:$TAG" +FRONTEND_IMAGE="$FRONTEND_REPOSITORY:$TAG" NETWORK=sw2-net NAMES=(sw2-backend sw2-frontend) @@ -19,17 +21,26 @@ test -f "$RELEASE/frontend/Dockerfile" docker build --build-arg "VERSION=$TAG" -t "$BACKEND_IMAGE" "$RELEASE/backend" docker build --build-arg "VERSION=$TAG" -t "$FRONTEND_IMAGE" "$RELEASE/frontend" -registry_args=() -if [[ -s "$BASE_DIR/harbor-docker/config.json" ]]; then registry_args=(--config "$BASE_DIR/harbor-docker"); fi -docker "${registry_args[@]}" push "$FRONTEND_IMAGE" -docker "${registry_args[@]}" pull "$FRONTEND_IMAGE" -frontend_digests=$(docker inspect --format '{{range .RepoDigests}}{{println .}}{{end}}' "$FRONTEND_IMAGE") -frontend_ref='' -while IFS= read -r reference; do - if [[ "$reference" == 'harbor.fuhua91.com/game/sw2@sha256:'* && "${reference##*@}" =~ ^sha256:[a-f0-9]{64}$ ]]; then frontend_ref="$reference"; break; fi -done <<< "$frontend_digests" -[[ -n "$frontend_ref" ]] || { echo 'Harbor frontend digest is missing' >&2; exit 1; } -FRONTEND_IMAGE="$frontend_ref" +if [[ -s "$BASE_DIR/harbor-docker/config.json" ]]; then export DOCKER_CONFIG="$BASE_DIR/harbor-docker"; fi +publish_image() { + local repository=$1 + local image="$repository:$TAG" + local digests reference + # Explicitly propagate failures inside command substitutions on Bash 4.2. + docker push "$image" >&2 || return 1 + docker pull "$image" >&2 || return 1 + digests=$(docker inspect --format '{{range .RepoDigests}}{{println .}}{{end}}' "$image") || return 1 + while IFS= read -r reference; do + if [[ "$reference" == "$repository@sha256:"* && "${reference##*@}" =~ ^sha256:[a-f0-9]{64}$ ]]; then + printf '%s\n' "$reference" + return 0 + fi + done <<< "$digests" + echo "Harbor image digest is missing: $image" >&2 + return 1 +} +BACKEND_IMAGE=$(publish_image "$BACKEND_REPOSITORY") +FRONTEND_IMAGE=$(publish_image "$FRONTEND_REPOSITORY") docker run --rm "$BACKEND_IMAGE" /app/kratos-admin -h >/dev/null docker run --rm --add-host sw2-backend:127.0.0.1 "$FRONTEND_IMAGE" nginx -t if [[ "$MODE" == '--build-only' ]]; then exit 0; fi diff --git a/deploy/docker/deploy.test.sh b/deploy/docker/deploy.test.sh new file mode 100644 index 0000000..84669a2 --- /dev/null +++ b/deploy/docker/deploy.test.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +set -euo pipefail +SCRIPT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)/deploy.sh +TEST_TMP=$(cd -- "${TMPDIR:-/tmp}" && pwd -P) +TEST_ROOT=$(mktemp -d "$TEST_TMP/sw-joint-deploy-test.XXXXXX") +cleanup() { + [[ "$TEST_ROOT" == "$TEST_TMP/sw-joint-deploy-test."* ]] || exit 1 + rm -rf -- "$TEST_ROOT" +} +trap cleanup EXIT +mkdir "$TEST_ROOT/bin" +cat > "$TEST_ROOT/bin/docker" <<'DOCKER' +#!/usr/bin/env bash +set -euo pipefail +printf '%s\n' "$*" >> "$TEST_STATE/calls" +if [[ "$TEST_MODE" == isolated-config ]]; then + [[ "${DOCKER_CONFIG:-}" == "$SW_DEPLOY_BASE/harbor-docker" || "$1" == build ]] || exit 1 +else + [[ "${DOCKER_CONFIG:-}" == "$TEST_ROOT/default-config" ]] || exit 1 +fi +component=frontend +image="${!#}" +if [[ "$image" == *sw2-backend:* ]]; then component=backend; fi +case "$1" in + build) exit 0 ;; + push|pull) + [[ "$TEST_MODE" != "$component-$1-fail" ]] || exit 1 + echo 'registry progress output' ;; + inspect) + [[ "$TEST_MODE" != "$component-inspect-fail" ]] || exit 1 + case "$TEST_MODE" in + "$component-digest-missing") exit 0 ;; + "$component-digest-wrong-repo") printf 'example.com/wrong@sha256:%064d\n' 1 ;; + "$component-digest-malformed") printf '%s@sha256:invalid\n' "${image%:*}" ;; + *) + # An unrelated digest may precede the correct registry digest. + printf 'example.com/old@sha256:%064d\n' 1 + printf '%s@sha256:%064d\n' "${image%:*}" 1 ;; + esac ;; + run) [[ "$*" == *'@sha256:'* && "$*" == *'--rm'* ]] ;; + *) echo "Unexpected Docker command: $*" >&2; exit 1 ;; +esac +DOCKER +printf '#!/usr/bin/env bash\nexit 0\n' > "$TEST_ROOT/bin/curl" +chmod +x "$TEST_ROOT/bin/"* +export TEST_ROOT PATH="$TEST_ROOT/bin:$PATH" + +for scenario in success isolated-config \ + backend-push-fail backend-pull-fail backend-inspect-fail backend-digest-missing backend-digest-wrong-repo backend-digest-malformed \ + frontend-push-fail frontend-pull-fail frontend-inspect-fail frontend-digest-missing frontend-digest-wrong-repo frontend-digest-malformed; do + export TEST_MODE="$scenario" TEST_STATE="$TEST_ROOT/$scenario" + export SW_DEPLOY_BASE="$TEST_STATE/base" DOCKER_CONFIG="$TEST_ROOT/default-config" + mkdir -p "$SW_DEPLOY_BASE/releases/new/backend" "$SW_DEPLOY_BASE/releases/new/frontend" + touch "$SW_DEPLOY_BASE/releases/new/backend/Dockerfile" "$SW_DEPLOY_BASE/releases/new/frontend/Dockerfile" + if [[ "$scenario" == isolated-config ]]; then + mkdir "$SW_DEPLOY_BASE/harbor-docker" + printf '{}\n' > "$SW_DEPLOY_BASE/harbor-docker/config.json" + fi + result=0 + bash "$SCRIPT" new --build-only > "$TEST_STATE/output" 2>&1 || result=$? + case "$scenario" in + success|isolated-config) + [[ "$result" == 0 ]] || { cat "$TEST_STATE/output" >&2; exit 1; } + for repository in harbor.fuhua91.com/game/sw2-backend harbor.fuhua91.com/game/sw2; do + grep -qFx "push $repository:new" "$TEST_STATE/calls" + grep -qFx "pull $repository:new" "$TEST_STATE/calls" + grep -qE "^run .* $repository@sha256:[a-f0-9]{64} " "$TEST_STATE/calls" + done ;; + *) + [[ "$result" != 0 ]] + ! grep -qE '^(run|stop|rename|rm) ' "$TEST_STATE/calls" ;; + esac + ! grep -qE 'sw2/(backend|frontend):' "$TEST_STATE/calls" + printf 'passed: %s\n' "$scenario" +done