From 97e380b138572b5cc584d1deec2479c2654ece8e Mon Sep 17 00:00:00 2001 From: WuYongJie Date: Wed, 15 Jul 2026 20:51:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=80=9A=E7=9F=A5=E4=B8=AD?= =?UTF-8?q?=E5=BF=83=E6=9E=84=E5=BB=BA=E6=9C=BA=E9=83=A8=E7=BD=B2=E6=B5=81?= =?UTF-8?q?=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/git-build-deploy.ps1 | 158 +++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 scripts/git-build-deploy.ps1 diff --git a/scripts/git-build-deploy.ps1 b/scripts/git-build-deploy.ps1 new file mode 100644 index 0000000..b79d469 --- /dev/null +++ b/scripts/git-build-deploy.ps1 @@ -0,0 +1,158 @@ +#Requires -Version 5.1 + +param( + [string]$RepoUrl = 'http://git.1080p.hk/WuYongJie/YouYiDianNotificationCenter.git', + [string]$Branch = 'main', + [string]$Commit = '', + [string]$BuildServer = 'root@192.168.40.206', + [string]$ProductionServer = 'root@192.168.58.20', + [string]$RemoteBuildDir = '/root/youyidian-notification-center-build', + [string]$RegistryHost = '192.168.40.206:5000', + [string]$ImageName = 'youyidian-notification-center', + [switch]$DryRun +) + +$ErrorActionPreference = 'Stop' + +function ConvertTo-ShellSingleQuoted([string]$Value) { + return "'" + $Value.Replace("'", "'\''") + "'" +} + +$remoteScript = @' +set -Eeuo pipefail + +REPO_URL=__REPO_URL__ +BRANCH=__BRANCH__ +COMMIT_INPUT=__COMMIT__ +BUILD_DIR=__BUILD_DIR__ +PRODUCTION_SERVER=__PRODUCTION_SERVER__ +REGISTRY_HOST=__REGISTRY_HOST__ +IMAGE_NAME=__IMAGE_NAME__ +DRY_RUN=__DRY_RUN__ + +log() { printf '[INFO] %s\n' "$*"; } +ok() { printf '[OK] %s\n' "$*"; } +fail() { printf '[ERR] %s\n' "$*" >&2; exit 1; } + +for command in git docker curl ssh; do + command -v "$command" >/dev/null 2>&1 || fail "构建机缺少命令: $command" +done +curl -fsS "http://127.0.0.1:${REGISTRY_HOST##*:}/v2/" >/dev/null \ + || fail '构建机内部镜像仓库不可用' +ssh -o BatchMode=yes -o ConnectTimeout=10 "$PRODUCTION_SERVER" 'true' \ + || fail '构建机无法连接生产机' + +if [ ! -d "$BUILD_DIR/.git" ]; then + rm -rf "$BUILD_DIR" + git clone "$REPO_URL" "$BUILD_DIR" +fi + +cd "$BUILD_DIR" +git remote set-url origin "$REPO_URL" +git fetch --prune origin "$BRANCH" +target="origin/$BRANCH" +if [ -n "$COMMIT_INPUT" ]; then + target="$COMMIT_INPUT" +fi +git rev-parse --verify "${target}^{commit}" >/dev/null +if [ "$DRY_RUN" = 1 ]; then + ok "预检通过,目标提交: $(git rev-parse "${target}^{commit}")" + exit 0 +fi +git checkout --detach "$target" +git reset --hard "$target" +git clean -fdx + +commit_sha="$(git rev-parse HEAD)" +short_sha="$(git rev-parse --short=12 HEAD)" +image_tag="production-${short_sha}" +local_commit="localhost:${REGISTRY_HOST##*:}/${IMAGE_NAME}:${image_tag}" +local_latest="localhost:${REGISTRY_HOST##*:}/${IMAGE_NAME}:production-latest" +remote_commit="${REGISTRY_HOST}/${IMAGE_NAME}:${image_tag}" + +log "构建提交 ${commit_sha}" +docker pull "$local_latest" >/dev/null 2>&1 || true +build_args=() +if docker image inspect "$local_latest" >/dev/null 2>&1; then + build_args+=(--cache-from "$local_latest") +fi +DOCKER_BUILDKIT=1 docker build "${build_args[@]}" \ + -t "$local_commit" \ + -t "$local_latest" \ + . +docker push "$local_commit" +docker push "$local_latest" +ok "镜像已推送: $remote_commit" + +if ! ssh -o BatchMode=yes -o ConnectTimeout=10 "$PRODUCTION_SERVER" \ + "docker pull '$remote_commit'"; then + log '生产机无法直接拉取,改用 SSH 传输镜像' + docker save "$local_commit" | ssh -o BatchMode=yes "$PRODUCTION_SERVER" docker load + ssh -o BatchMode=yes "$PRODUCTION_SERVER" \ + "docker tag '$local_commit' '$remote_commit'" +fi + +ssh -o BatchMode=yes "$PRODUCTION_SERVER" \ + "REMOTE_IMAGE='$remote_commit' IMAGE_TAG='$image_tag' COMMIT_SHA='$commit_sha' bash -s" <<'PRODUCTION' +set -Eeuo pipefail + +target=/mnt/PoolOne/apps/youyidian-notification-center +cd "$target" +test -f .env +test -f docker-compose.yml + +old_image="$(docker inspect -f '{{.Config.Image}}' youyidian-notification-center 2>/dev/null || true)" +if [ -n "$old_image" ] && docker image inspect "$old_image" >/dev/null 2>&1; then + docker tag "$old_image" youyidian-notification-center:rollback-before-release +fi + +docker tag "$REMOTE_IMAGE" "youyidian-notification-center:$IMAGE_TAG" +YYD_NOTIFICATION_IMAGE_TAG="$IMAGE_TAG" docker compose up -d --no-build --force-recreate + +healthy=0 +for _ in $(seq 1 30); do + if curl -fsS --max-time 3 http://127.0.0.1:3088/health >/tmp/yyd-notification-health.json; then + healthy=1 + break + fi + sleep 2 +done + +if [ "$healthy" != 1 ]; then + docker compose logs --tail 120 + if docker image inspect youyidian-notification-center:rollback-before-release >/dev/null 2>&1; then + YYD_NOTIFICATION_IMAGE_TAG=rollback-before-release docker compose up -d --no-build --force-recreate + fi + exit 1 +fi + +printf '%s' "$COMMIT_SHA" > DEPLOYED_COMMIT +curl -fsS --max-time 10 https://sc.jiansun.vip/notification-center/health +printf '\nDEPLOYED_COMMIT=' +cat DEPLOYED_COMMIT +printf '\nCONTAINER_IMAGE=' +docker inspect -f '{{.Config.Image}}' youyidian-notification-center +printf '\n' +PRODUCTION + +ok "通知中心已部署到 ${commit_sha}" +'@ + +$replacements = @{ + '__REPO_URL__' = ConvertTo-ShellSingleQuoted $RepoUrl + '__BRANCH__' = ConvertTo-ShellSingleQuoted $Branch + '__COMMIT__' = ConvertTo-ShellSingleQuoted $Commit + '__BUILD_DIR__' = ConvertTo-ShellSingleQuoted $RemoteBuildDir + '__PRODUCTION_SERVER__' = ConvertTo-ShellSingleQuoted $ProductionServer + '__REGISTRY_HOST__' = ConvertTo-ShellSingleQuoted $RegistryHost + '__IMAGE_NAME__' = ConvertTo-ShellSingleQuoted $ImageName + '__DRY_RUN__' = if ($DryRun) { '1' } else { '0' } +} +foreach ($entry in $replacements.GetEnumerator()) { + $remoteScript = $remoteScript.Replace($entry.Key, $entry.Value) +} + +$remoteScript | ssh -o BatchMode=yes -o ConnectTimeout=10 $BuildServer 'bash -s' +if ($LASTEXITCODE -ne 0) { + throw "通知中心构建机发布失败,退出码: $LASTEXITCODE" +}