Files
2026-09-14 00:54:48 +08:00

213 lines
8.8 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: 构建并部署
on:
push:
tags: ['v*']
workflow_dispatch:
concurrency:
group: deploy
cancel-in-progress: false
env:
REGISTRY: git.lzcwy.cn
IMAGE: git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: 记录开始时间
run: echo "JOB_START=$(date +%s)" >> "$GITHUB_ENV"
- name: 拉取代码
uses: https://gitee.com/liu_zhengchi/checkout@v4
- name: 确认 docker 可用
run: |
docker version --format 'Server: {{.Server.Version}}'
- name: 计算镜像标签
id: meta
run: |
case "$GITEA_REF" in
refs/tags/*) TAG="${GITEA_REF#refs/tags/}" ;;
*) TAG="sha-$(echo "$GITEA_SHA" | cut -c1-7)" ;;
esac
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "镜像标签: $TAG"
- name: 登录 Gitea 镜像仓库
env:
REGISTRY_USER: ${{ secrets.ZT_CLOSET_REGISTRY_USER }}
REGISTRY_TOKEN: ${{ secrets.ZT_CLOSET_REGISTRY_TOKEN }}
run: |
echo "$REGISTRY_TOKEN" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin
- name: 构建并推送
run: |
# 生产机若是 ARM(如甲骨文 ARM 机),去掉下一行注释并改成 linux/arm64
# PLATFORM="--platform linux/arm64"
docker build \
--build-arg GOPROXY=https://goproxy.cn,direct \
-f scripts/dockerfile/Dockerfile \
-t "$IMAGE:${{ steps.meta.outputs.tag }}" \
-t "$IMAGE:latest" \
.
docker push "$IMAGE:${{ steps.meta.outputs.tag }}"
docker push "$IMAGE:latest"
- name: SSH 部署到生产机
env:
SSH_KEY: ${{ secrets.ZT_CLOSET_PROD_SSH_KEY }}
SSH_HOST: ${{ vars.ZT_CLOSET_PROD_SSH_HOST }}
SSH_USER: ${{ vars.ZT_CLOSET_PROD_SSH_USER }}
run: |
mkdir -p ~/.ssh && chmod 700 ~/.ssh
printf '%s\n' "$SSH_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
SSHOPT="-i ~/.ssh/deploy_key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
ssh $SSHOPT "$SSH_USER@$SSH_HOST" 'bash -s' <<'REMOTE'
set -e
APP=zt-closet-manage-backend
IMAGE=git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend
DIR=/opt/zt-closet-manage
mkdir -p "$DIR/configs" "$DIR/storage/logs"
# 容器内进程是 uid 10001,日志目录要给它写权限
chown -R 10001:10001 "$DIR/storage"
# 配置文件含密钥,不打入镜像,由运维手动放置。缺失或不可读会导致容器启动即 panic,
# 此处提前拦截,避免「容器起不来 → 健康检查失败 → 回滚」这种误导性报错。
CONFIG_FILE="$DIR/configs/config.yaml"
if [ ! -f "$CONFIG_FILE" ]; then
echo "缺少配置文件:$CONFIG_FILE" >&2
echo "请参考仓库 configs/config.example.yaml 在生产机放置该文件后重试。" >&2
exit 1
fi
# 容器内为 uid 10001,需 other 可读(推荐 644):权限位末位为 4~7 才可读
case "$(stat -c '%a' "$CONFIG_FILE" 2>/dev/null || echo 644)" in
*4|*5|*6|*7) echo "配置文件就绪:$CONFIG_FILE" ;;
*)
echo "配置文件权限过严:$CONFIG_FILE 需对 uid 10001 可读(chmod 644)" >&2
exit 1
;;
esac
# 记住旧镜像,用于失败回滚
OLD_IMAGE=$(docker inspect -f '{{.Image}}' $APP 2>/dev/null || echo "")
docker pull $IMAGE:latest
docker rm -f $APP 2>/dev/null || true
docker run -d \
--name $APP \
--network host \
--restart unless-stopped \
-v $DIR/configs:/app/configs:ro \
-v $DIR/storage/logs:/app/storage/logs \
-e TZ=Asia/Shanghai \
--log-opt max-size=50m \
--log-opt max-file=5 \
$IMAGE:latest
sleep 5
# 健康检查端口须与生产 configs/config.yaml 的 Server.HttpPort 一致(当前 8081)
if (command -v curl >/dev/null && curl -fsS http://127.0.0.1:8081/ping) \
|| (command -v wget >/dev/null && wget -qO- http://127.0.0.1:8081/ping); then
echo " 健康检查通过"
docker image prune -f
else
echo " 健康检查失败,回滚"
docker logs --tail 50 $APP
docker rm -f $APP
if [ -n "$OLD_IMAGE" ]; then
docker run -d \
--name $APP \
--network host \
--restart unless-stopped \
-v $DIR/configs:/app/configs:ro \
-v $DIR/storage/logs:/app/storage/logs \
-e TZ=Asia/Shanghai \
--log-opt max-size=50m \
--log-opt max-file=5 \
"$OLD_IMAGE"
fi
exit 1
fi
REMOTE
- name: 部署结果
if: always()
run: echo "镜像 ${{ env.IMAGE }}:${{ steps.meta.outputs.tag }} 部署流程结束"
- name: 钉钉通知(成功)
if: success()
continue-on-error: true
env:
WEBHOOK: ${{ secrets.ZT_CLOSET_DINGTALK_WEBHOOK }}
REPO: ${{ gitea.repository }}
REF: ${{ gitea.ref }}
SHA: ${{ gitea.sha }}
ACTOR: ${{ gitea.actor }}
RUN_URL: ${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_id }}
IMAGE_REF: ${{ env.IMAGE }}:${{ steps.meta.outputs.tag }}
run: |
set -uo pipefail
if [ -z "${WEBHOOK:-}" ]; then
echo "未配置 ZT_CLOSET_DINGTALK_WEBHOOK,跳过钉钉通知"
exit 0
fi
REF_NAME="${REF#refs/heads/}"; REF_NAME="${REF_NAME#refs/tags/}"
SHORT_SHA="$(printf '%s' "$SHA" | cut -c1-7)"
COST=$(( $(date +%s) - ${JOB_START:-$(date +%s)} ))
TEXT="### ✅ 构建并部署成功\n\n"
TEXT="${TEXT}> **zt-closet-manage-backend** · 构建并部署\n\n"
TEXT="${TEXT}- **📦 仓库**:$REPO\n"
TEXT="${TEXT}- **🌿 分支/标签**:$REF_NAME\n"
TEXT="${TEXT}- **🔖 提交**:$SHORT_SHA\n"
TEXT="${TEXT}- **👤 触发者**:$ACTOR\n"
TEXT="${TEXT}- **🐳 镜像**:$IMAGE_REF\n"
TEXT="${TEXT}- **⏱️ 耗时**:$((COST/60)) 分 $((COST%60)) 秒\n"
TEXT="${TEXT}- **🕐 时间**:$(date '+%Y-%m-%d %H:%M:%S')\n\n"
TEXT="${TEXT}[查看运行详情]($RUN_URL)"
printf '%s' "{\"msgtype\":\"markdown\",\"markdown\":{\"title\":\"✅ 构建并部署成功 · zt-closet-manage-backend\",\"text\":\"$TEXT\"}}" > /tmp/dingtalk.json
RESP="$(curl -sS -X POST "$WEBHOOK" -H 'Content-Type: application/json' -d @/tmp/dingtalk.json)"
echo "钉钉响应: $RESP"
- name: 钉钉通知(失败)
if: failure()
continue-on-error: true
env:
WEBHOOK: ${{ secrets.ZT_CLOSET_DINGTALK_WEBHOOK }}
REPO: ${{ gitea.repository }}
REF: ${{ gitea.ref }}
SHA: ${{ gitea.sha }}
ACTOR: ${{ gitea.actor }}
RUN_URL: ${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_id }}
IMAGE_REF: ${{ env.IMAGE }}:${{ steps.meta.outputs.tag }}
run: |
set -uo pipefail
if [ -z "${WEBHOOK:-}" ]; then
echo "未配置 ZT_CLOSET_DINGTALK_WEBHOOK,跳过钉钉通知"
exit 0
fi
REF_NAME="${REF#refs/heads/}"; REF_NAME="${REF_NAME#refs/tags/}"
SHORT_SHA="$(printf '%s' "$SHA" | cut -c1-7)"
COST=$(( $(date +%s) - ${JOB_START:-$(date +%s)} ))
TEXT="### ❌ 构建并部署失败\n\n"
TEXT="${TEXT}> **zt-closet-manage-backend** · 构建并部署\n\n"
TEXT="${TEXT}- **📦 仓库**:$REPO\n"
TEXT="${TEXT}- **🌿 分支/标签**:$REF_NAME\n"
TEXT="${TEXT}- **🔖 提交**:$SHORT_SHA\n"
TEXT="${TEXT}- **👤 触发者**:$ACTOR\n"
TEXT="${TEXT}- **🐳 镜像**:$IMAGE_REF\n"
TEXT="${TEXT}- **⏱️ 耗时**:$((COST/60)) 分 $((COST%60)) 秒\n"
TEXT="${TEXT}- **🕐 时间**:$(date '+%Y-%m-%d %H:%M:%S')\n"
TEXT="${TEXT}- **🧭 失败定位**:请点击下方链接查看失败步骤与日志\n\n"
TEXT="${TEXT}[查看失败日志]($RUN_URL)"
printf '%s' "{\"msgtype\":\"markdown\",\"markdown\":{\"title\":\"❌ 构建并部署失败 · zt-closet-manage-backend\",\"text\":\"$TEXT\"}}" > /tmp/dingtalk.json
RESP="$(curl -sS -X POST "$WEBHOOK" -H 'Content-Type: application/json' -d @/tmp/dingtalk.json)"
echo "钉钉响应: $RESP"