ea1de71146
Build docker and publish / build (20.15.1) (pull_request) Successful in 8m3s
Co-authored-by: multica-agent <github@multica.ai>
338 lines
13 KiB
YAML
338 lines
13 KiB
YAML
name: Build docker and publish
|
||
run-name: 简化的Docker构建和部署流程
|
||
|
||
on:
|
||
push:
|
||
branches:
|
||
- main
|
||
- internal
|
||
pull_request:
|
||
branches:
|
||
- main
|
||
- internal
|
||
|
||
env:
|
||
# Docker镜像仓库
|
||
REPO: ${{ vars.REPO || 'registry.kxsw.us/vpn-server' }}
|
||
# SSH连接信息 (根据分支自动选择服务器和用户)
|
||
SSH_HOST: ${{ github.ref_name == 'main' && vars.SSH_HOST || vars.DEV_SSH_HOST }}
|
||
SSH_PORT: ${{ vars.SSH_PORT }}
|
||
SSH_USER: ${{ github.ref_name == 'main' && 'ubuntu' || 'root' }}
|
||
# SSH私钥(Gitea Secret 名称:AWS)
|
||
SSH_KEY: ${{ secrets.AWS }}
|
||
# TG通知
|
||
TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }}
|
||
TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
|
||
# Go构建变量
|
||
SERVICE: vpn
|
||
SERVICE_STYLE: vpn
|
||
VERSION: ${{ github.sha }}
|
||
BUILDTIME: ${{ github.event.head_commit.timestamp }}
|
||
GOARCH: amd64
|
||
|
||
jobs:
|
||
build:
|
||
runs-on: ario-server
|
||
container:
|
||
image: node:20
|
||
strategy:
|
||
matrix:
|
||
# 只有node支持版本号别名
|
||
node: ['20.15.1']
|
||
steps:
|
||
# 步骤1: 下载代码
|
||
- name: 📥 下载代码
|
||
uses: actions/checkout@v4
|
||
|
||
# 步骤2: 设置动态环境变量
|
||
- name: ⚙️ 设置动态环境变量
|
||
run: |
|
||
if [ "${{ github.ref_name }}" = "main" ]; then
|
||
echo "DOCKER_TAG_SUFFIX=latest" >> $GITHUB_ENV
|
||
echo "CONTAINER_NAME=ppanel-server" >> $GITHUB_ENV
|
||
echo "DEPLOY_PATH=/opt/ppanel" >> $GITHUB_ENV
|
||
echo "DEPLOY_ENV_LABEL=🚀 服务已成功部署到生产环境" >> $GITHUB_ENV
|
||
echo "为 main 分支设置生产环境变量"
|
||
elif [ "${{ github.ref_name }}" = "internal" ]; then
|
||
echo "DOCKER_TAG_SUFFIX=internal" >> $GITHUB_ENV
|
||
echo "CONTAINER_NAME=ppanel-server-internal" >> $GITHUB_ENV
|
||
echo "DEPLOY_PATH=/root/bindbox" >> $GITHUB_ENV
|
||
echo "DEPLOY_ENV_LABEL=🧪 服务已成功部署到测试环境" >> $GITHUB_ENV
|
||
echo "为 internal 分支设置开发环境变量"
|
||
else
|
||
echo "DOCKER_TAG_SUFFIX=${{ github.ref_name }}" >> $GITHUB_ENV
|
||
echo "CONTAINER_NAME=ppanel-server-${{ github.ref_name }}" >> $GITHUB_ENV
|
||
echo "DEPLOY_PATH=/root/vpn_server_other" >> $GITHUB_ENV
|
||
echo "DEPLOY_ENV_LABEL=🔧 服务已成功部署到其他环境" >> $GITHUB_ENV
|
||
echo "为其他分支 (${{ github.ref_name }}) 设置环境变量"
|
||
fi
|
||
|
||
# 步骤3: 安装系统工具 (curl, jq) 并升级 Docker CLI 到 1.44+
|
||
- name: 🔧 安装系统工具并升级 Docker CLI
|
||
run: |
|
||
set -e
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
echo "等待 apt/dpkg 锁释放 (unattended-upgrades)..."
|
||
end=$((SECONDS+300))
|
||
while true; do
|
||
LOCKS_BUSY=0
|
||
if pgrep -x unattended-upgrades >/dev/null 2>&1; then LOCKS_BUSY=1; fi
|
||
if command -v fuser >/dev/null 2>&1; then
|
||
if fuser /var/lib/dpkg/lock >/dev/null 2>&1 \
|
||
|| fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 \
|
||
|| fuser /var/lib/apt/lists/lock >/dev/null 2>&1; then
|
||
LOCKS_BUSY=1
|
||
fi
|
||
fi
|
||
if [ "$LOCKS_BUSY" -eq 0 ]; then break; fi
|
||
if [ $SECONDS -ge $end ]; then
|
||
echo "等待 apt/dpkg 锁超时,使用 Dpkg::Lock::Timeout 继续..."
|
||
break
|
||
fi
|
||
echo "仍在等待锁释放..."; sleep 5
|
||
done
|
||
|
||
# 基础工具
|
||
apt-get update -y -o Dpkg::Lock::Timeout=600
|
||
apt-get install -y -o Dpkg::Lock::Timeout=600 jq curl ca-certificates gnupg lsb-release
|
||
|
||
# 移除旧版 docker.io,避免客户端过旧 (API 1.41)
|
||
if dpkg -s docker.io >/dev/null 2>&1; then
|
||
apt-get remove -y docker.io || true
|
||
fi
|
||
|
||
# 安装 Docker 官方仓库的 CLI (确保 API >= 1.44)
|
||
distro_codename=$(. /etc/os-release && echo "$VERSION_CODENAME")
|
||
install_repo="deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian ${distro_codename} stable"
|
||
mkdir -p /etc/apt/keyrings
|
||
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||
echo "$install_repo" > /etc/apt/sources.list.d/docker.list
|
||
apt-get update -y -o Dpkg::Lock::Timeout=600
|
||
apt-get install -y -o Dpkg::Lock::Timeout=600 docker-ce-cli docker-buildx-plugin
|
||
|
||
# 版本检查
|
||
docker --version || true
|
||
docker version || true
|
||
echo "客户端 API 版本:" $(docker version --format '{{.Client.APIVersion}}')
|
||
|
||
# 步骤4: 构建镜像
|
||
- name: 🏗️ 构建镜像
|
||
run: |
|
||
echo "开始构建镜像..."
|
||
echo "仓库: ${{ env.REPO }}"
|
||
echo "版本标签: ${{ env.VERSION }}"
|
||
echo "分支标签: ${{ env.DOCKER_TAG_SUFFIX }}"
|
||
|
||
BUILD_TAG_ARGS="-t ${{ env.REPO }}:${{ env.VERSION }}"
|
||
if [ "${{ github.event_name }}" = "push" ]; then
|
||
BUILD_TAG_ARGS="$BUILD_TAG_ARGS -t ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }}"
|
||
else
|
||
echo "PR事件仅构建版本标签,不推送镜像、不部署"
|
||
fi
|
||
|
||
docker build -f Dockerfile \
|
||
--platform linux/amd64 \
|
||
--build-arg TARGETARCH=amd64 \
|
||
--build-arg VERSION=${{ env.VERSION }} \
|
||
--build-arg BUILDTIME=${{ env.BUILDTIME }} \
|
||
$BUILD_TAG_ARGS \
|
||
.
|
||
|
||
echo "镜像构建完成"
|
||
|
||
# 步骤5: 发布到镜像仓库
|
||
- name: 📤 发布到镜像仓库
|
||
if: github.event_name == 'push'
|
||
run: |
|
||
echo "开始推送镜像..."
|
||
|
||
echo "推送版本标签镜像: ${{ env.REPO }}:${{ env.VERSION }}"
|
||
docker push ${{ env.REPO }}:${{ env.VERSION }}
|
||
|
||
echo "推送分支标签镜像: ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }}"
|
||
docker push ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }}
|
||
|
||
echo "镜像推送完成"
|
||
|
||
# 步骤6: 调试 - 打印部署目标(不输出敏感信息)
|
||
- name: 🔍 调试 - 打印部署目标
|
||
if: github.event_name == 'push'
|
||
run: |
|
||
echo "========== 部署目标调试 =========="
|
||
echo "当前分支: ${{ github.ref_name }}"
|
||
echo "SSH_HOST: ${{ env.SSH_HOST }}"
|
||
echo "SSH_PORT: ${{ env.SSH_PORT }}"
|
||
echo "SSH_USER: ${{ env.SSH_USER }}"
|
||
echo "SSH认证方式: 私钥 (AWS)"
|
||
echo "DEPLOY_PATH: ${{ env.DEPLOY_PATH }}"
|
||
echo "====================================="
|
||
|
||
# 步骤7: 传输配置文件
|
||
- name: 📂 传输配置文件
|
||
if: github.event_name == 'push'
|
||
uses: appleboy/scp-action@v0.1.7
|
||
with:
|
||
host: ${{ env.SSH_HOST }}
|
||
username: ${{ env.SSH_USER }}
|
||
key: ${{ env.SSH_KEY }}
|
||
port: ${{ env.SSH_PORT }}
|
||
source: "docker-compose.cloud.yml"
|
||
target: "/tmp/ppanel-deploy/"
|
||
|
||
# 步骤8: 连接服务器更新、健康检查并按需回滚
|
||
- name: 🚀 连接服务器更新并启动
|
||
if: github.event_name == 'push'
|
||
uses: appleboy/ssh-action@v1.0.3
|
||
with:
|
||
host: ${{ env.SSH_HOST }}
|
||
username: ${{ env.SSH_USER }}
|
||
key: ${{ env.SSH_KEY }}
|
||
port: ${{ env.SSH_PORT }}
|
||
timeout: 300s
|
||
command_timeout: 600s
|
||
script: |
|
||
set -e
|
||
|
||
echo "连接服务器成功,开始部署..."
|
||
echo "部署目录: ${{ env.DEPLOY_PATH }}"
|
||
echo "部署标签: ${{ env.DOCKER_TAG_SUFFIX }}"
|
||
echo "登录用户: ${{ env.SSH_USER }}"
|
||
|
||
HEALTHCHECK_URL="http://127.0.0.1:8080/v1/common/heartbeat"
|
||
NEW_TAG="${{ env.DOCKER_TAG_SUFFIX }}"
|
||
ROLLBACK_TAG="rollback-${{ env.VERSION }}"
|
||
SUDO=""
|
||
if [ "${{ github.ref_name }}" = "main" ]; then
|
||
SUDO="sudo"
|
||
fi
|
||
|
||
docker_cmd() {
|
||
if [ -n "$SUDO" ]; then
|
||
sudo docker "$@"
|
||
else
|
||
docker "$@"
|
||
fi
|
||
}
|
||
|
||
compose_with_tag() {
|
||
tag="$1"
|
||
shift
|
||
if [ -n "$SUDO" ]; then
|
||
sudo env PPANEL_SERVER_TAG="$tag" docker-compose -f docker-compose.cloud.yml "$@"
|
||
else
|
||
PPANEL_SERVER_TAG="$tag" docker-compose -f docker-compose.cloud.yml "$@"
|
||
fi
|
||
}
|
||
|
||
write_previous_tag() {
|
||
if [ -n "$SUDO" ]; then
|
||
printf '%s\n' "${PREVIOUS_IMAGE_TAG:-}" | sudo tee .previous-ppanel-image-tag >/dev/null
|
||
else
|
||
printf '%s\n' "${PREVIOUS_IMAGE_TAG:-}" > .previous-ppanel-image-tag
|
||
fi
|
||
}
|
||
|
||
health_check() {
|
||
attempt=1
|
||
while [ "$attempt" -le 3 ]; do
|
||
if curl -sf "$HEALTHCHECK_URL"; then
|
||
echo
|
||
return 0
|
||
fi
|
||
|
||
echo "健康检查第 ${attempt}/3 次失败,10s 后重试..."
|
||
attempt=$((attempt + 1))
|
||
sleep 10
|
||
done
|
||
|
||
return 1
|
||
}
|
||
|
||
if [ "${{ github.ref_name }}" = "main" ]; then
|
||
sudo mkdir -p ${{ env.DEPLOY_PATH }}
|
||
sudo cp /tmp/ppanel-deploy/docker-compose.cloud.yml ${{ env.DEPLOY_PATH }}/docker-compose.cloud.yml
|
||
else
|
||
mkdir -p ${{ env.DEPLOY_PATH }}
|
||
cp /tmp/ppanel-deploy/docker-compose.cloud.yml ${{ env.DEPLOY_PATH }}/docker-compose.cloud.yml
|
||
fi
|
||
|
||
cd ${{ env.DEPLOY_PATH }}
|
||
|
||
PREVIOUS_IMAGE_TAG="$(docker_cmd inspect --format '{{.Config.Image}}' ppanel-server 2>/dev/null || true)"
|
||
PREVIOUS_IMAGE_ID="$(docker_cmd inspect --format '{{.Image}}' ppanel-server 2>/dev/null || true)"
|
||
echo "上一版本镜像tag: ${PREVIOUS_IMAGE_TAG:-未发现}"
|
||
echo "上一版本镜像ID: ${PREVIOUS_IMAGE_ID:-未发现}"
|
||
write_previous_tag
|
||
|
||
echo "📥 拉取镜像: ${{ env.REPO }}:${NEW_TAG}"
|
||
compose_with_tag "$NEW_TAG" pull ppanel-server
|
||
echo "🚀 启动服务..."
|
||
compose_with_tag "$NEW_TAG" up -d ppanel-server
|
||
|
||
echo "🩺 部署后健康检查: ${HEALTHCHECK_URL}"
|
||
if health_check; then
|
||
docker_cmd image prune -f || true
|
||
echo "✅ 部署后健康检查通过"
|
||
echo "✅ 部署命令执行完成"
|
||
exit 0
|
||
fi
|
||
|
||
echo "❌ 部署后健康检查连续 3 次失败,开始回滚..."
|
||
if [ -n "$PREVIOUS_IMAGE_ID" ]; then
|
||
docker_cmd tag "$PREVIOUS_IMAGE_ID" "${{ env.REPO }}:${ROLLBACK_TAG}"
|
||
echo "回滚镜像tag: ${{ env.REPO }}:${ROLLBACK_TAG}"
|
||
compose_with_tag "$ROLLBACK_TAG" up -d ppanel-server
|
||
|
||
echo "🩺 回滚后健康检查: ${HEALTHCHECK_URL}"
|
||
if health_check; then
|
||
echo "✅ 回滚后健康检查通过"
|
||
else
|
||
echo "❌ 回滚后健康检查仍失败"
|
||
fi
|
||
else
|
||
echo "未找到上一版本镜像ID,无法自动回滚"
|
||
fi
|
||
|
||
docker_cmd image prune -f || true
|
||
exit 1
|
||
|
||
# 步骤9: TG通知 (成功)
|
||
- name: 📱 发送成功通知到Telegram
|
||
if: success() && github.event_name == 'push'
|
||
uses: appleboy/telegram-action@master
|
||
with:
|
||
token: ${{ env.TG_BOT_TOKEN }}
|
||
to: ${{ env.TG_CHAT_ID }}
|
||
message: |
|
||
✅ 部署成功!
|
||
|
||
📦 项目: ${{ github.repository }}
|
||
🌿 分支: ${{ github.ref_name }}
|
||
📝 提交: ${{ github.sha }}
|
||
👤 提交者: ${{ github.actor }}
|
||
🕐 时间: ${{ github.event.head_commit.timestamp }}
|
||
|
||
${{ env.DEPLOY_ENV_LABEL }}
|
||
🩺 健康检查: 通过 (http://127.0.0.1:8080/v1/common/heartbeat)
|
||
parse_mode: Markdown
|
||
|
||
# 步骤10: TG通知 (失败)
|
||
- name: 📱 发送失败通知到Telegram
|
||
if: failure() && github.event_name == 'push'
|
||
uses: appleboy/telegram-action@master
|
||
with:
|
||
token: ${{ env.TG_BOT_TOKEN }}
|
||
to: ${{ env.TG_CHAT_ID }}
|
||
message: |
|
||
❌ 部署失败!
|
||
|
||
📦 项目: ${{ github.repository }}
|
||
🌿 分支: ${{ github.ref_name }}
|
||
📝 提交: ${{ github.sha }}
|
||
👤 提交者: ${{ github.actor }}
|
||
🕐 时间: ${{ github.event.head_commit.timestamp }}
|
||
|
||
🩺 健康检查: 失败或未完成;若新版本健康检查连续 3 次失败,已自动尝试回滚并重新检查
|
||
⚠️ 请检查构建日志获取详细信息
|
||
parse_mode: Markdown
|