#!/usr/bin/env sh set -eu if [ "$#" -ne 4 ]; then echo "用法: $0 <服务名> <镜像仓库> <镜像标签> " >&2 exit 2 fi SERVICE="$1" IMAGE_REPOSITORY="$2" IMAGE_TAG="$3" COMPOSE_FILE="$4" IMAGE="$IMAGE_REPOSITORY:$IMAGE_TAG" REGISTRY_HOST="${IMAGE_REPOSITORY%%/*}" SSH_TARGET="${TARGET_SSH_TARGET:?TARGET_SSH_TARGET 未设置}" SSH_KEY="${TARGET_SSH_KEY:?TARGET_SSH_KEY 未设置}" SSH_KNOWN_HOSTS="${TARGET_SSH_KNOWN_HOSTS:?TARGET_SSH_KNOWN_HOSTS 未设置}" REMOTE_DIR="${TARGET_DEPLOY_DIR:?TARGET_DEPLOY_DIR 未设置}" : "${REGISTRY_USER:?REGISTRY_USER 未设置}" : "${REGISTRY_PASSWORD:?REGISTRY_PASSWORD 未设置}" run_ssh() { ssh -i "$SSH_KEY" -o BatchMode=yes -o StrictHostKeyChecking=yes \ -o UserKnownHostsFile="$SSH_KNOWN_HOSTS" -o ConnectTimeout=10 "$SSH_TARGET" "$@" } run_scp() { scp -i "$SSH_KEY" -o BatchMode=yes -o StrictHostKeyChecking=yes \ -o UserKnownHostsFile="$SSH_KNOWN_HOSTS" -o ConnectTimeout=10 "$@" } run_ssh install -d -m 750 "$REMOTE_DIR" REMOTE_COMPOSE="$REMOTE_DIR/docker-compose.$SERVICE.yml" run_scp "$COMPOSE_FILE" "$SSH_TARGET:$REMOTE_COMPOSE.tmp" run_ssh sh -s -- "$REMOTE_COMPOSE.tmp" "$REMOTE_COMPOSE" <<'REMOTE_PREPARE' set -eu compose_tmp="$1" compose_target="$2" chmod 644 "$compose_tmp" mv -f "$compose_tmp" "$compose_target" REMOTE_PREPARE printf '%s' "$REGISTRY_PASSWORD" | run_ssh docker login -u "$REGISTRY_USER" --password-stdin "$REGISTRY_HOST" trap 'run_ssh docker logout "$REGISTRY_HOST" >/dev/null 2>&1 || true' EXIT HUP INT TERM run_ssh docker pull "$IMAGE" run_ssh sh -s -- "$REMOTE_DIR" "$SERVICE" "$IMAGE_REPOSITORY" "$IMAGE_TAG" <<'REMOTE_DEPLOY' set -eu remote_dir="$1" service="$2" image_repository="$3" image_tag="$4" compose_file="docker-compose.$service.yml" project_name="firstleg-prod-$service" cd "$remote_dir" export IMAGE_REPOSITORY="$image_repository" IMAGE_TAG="$image_tag" compose() { docker compose --project-name "$project_name" -f "$compose_file" "$@" } container_id="$(compose ps --all --quiet "$service" | head -n 1)" previous_image="" if [ -n "$container_id" ]; then previous_image="$(docker inspect --format '{{.Config.Image}}' "$container_id")" fi if compose up -d --no-deps --pull never "$service" && \ compose exec -T "$service" wget -qO- http://127.0.0.1/healthz >/dev/null; then echo "生产前端部署成功: $image_repository:$image_tag" compose ps "$service" exit 0 fi echo "生产前端健康检查失败,准备回滚" >&2 compose logs --tail=100 "$service" >&2 || true case "$previous_image" in "$image_repository:"*) ;; '') echo "首次部署无旧镜像可回滚" >&2; exit 1 ;; *) echo "旧镜像不属于当前应用,拒绝回滚: $previous_image" >&2; exit 1 ;; esac previous_tag="${previous_image#"$image_repository:"}" if [ "$previous_tag" = "$image_tag" ]; then echo "旧镜像与新镜像一致,无法回滚" >&2 exit 1 fi docker pull "$previous_image" export IMAGE_TAG="$previous_tag" compose up -d --no-deps --pull never "$service" compose exec -T "$service" wget -qO- http://127.0.0.1/healthz >/dev/null || true echo "已回滚至: $previous_image" >&2 exit 1 REMOTE_DEPLOY