Files
client-web/scripts/deploy-production.sh
T

94 lines
3.1 KiB
Bash
Raw Normal View History

2026-09-17 11:49:01 +08:00
#!/usr/bin/env sh
set -eu
if [ "$#" -ne 4 ]; then
echo "用法: $0 <服务名> <镜像仓库> <镜像标签> <Compose 文件>" >&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