shanshanzhong 38f64f770d
Some checks failed
CI / build (20.15.1) (push) Failing after 23m2s
in1
2025-09-26 09:48:00 -07:00

779 lines
31 KiB
YAML
Raw 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: CI
on:
push:
branches:
- main
- dev
- cicd
pull_request:
branches:
- main
- dev
- cicd
env:
DOMAIN_URL: git.kxsw.us #*修改为你12
REPO: ${{ vars.REPO }}
TELEGRAM_BOT_TOKEN: 8114337882:AAHkEx03HSu7RxN4IHBJJEnsK9aPPzNLIk0
TELEGRAM_CHAT_ID: "-4940243803"
DOCKER_REGISTRY: registry.kxsw.us
DOCKER_BUILDKIT: 1
# Host SSH
SSH_HOST: ${{ vars.SSH_HOST }}
SSH_PORT: ${{ vars.SSH_PORT }}
SSH_USER: ${{ vars.SSH_USER }}
SSH_PASSWORD: ${{ vars.SSH_PASSWORD }}
jobs:
build:
runs-on: ppanel-web02
container:
image: node:20
strategy:
matrix:
# 只有node支持版本号别名
node: ['20.15.1']
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: 缓存服务健康检查
id: cache-health
continue-on-error: true
run: |
echo "检查缓存服务可用性..."
# 设置缓存可用性标志
CACHE_AVAILABLE=true
# 测试GitHub Actions缓存API
if ! curl -s --connect-timeout 10 --max-time 30 "https://api.github.com/repos/${{ github.repository }}/actions/caches" > /dev/null 2>&1; then
echo "⚠️ GitHub Actions缓存服务不可用将跳过缓存步骤"
CACHE_AVAILABLE=false
else
echo "✅ 缓存服务可用"
fi
echo "CACHE_AVAILABLE=$CACHE_AVAILABLE" >> $GITHUB_ENV
echo "cache-available=$CACHE_AVAILABLE" >> $GITHUB_OUTPUT
- name: 缓存降级提示
if: env.CACHE_AVAILABLE == 'false'
run: |
echo "🔄 缓存服务不可用,构建将在无缓存模式下进行"
echo "⏱️ 这可能会增加构建时间,但不会影响构建结果"
echo "📦 所有依赖将重新下载和安装"
- name: Install system tools (jq, docker, curl)
run: |
set -e
export DEBIAN_FRONTEND=noninteractive
echo "Waiting for apt/dpkg locks (unattended-upgrades) to release..."
# Wait up to 300s for unattended-upgrades/apt/dpkg locks
end=$((SECONDS+300))
while true; do
LOCKS_BUSY=0
# If unattended-upgrades is running, mark busy
if pgrep -x unattended-upgrades >/dev/null 2>&1; then LOCKS_BUSY=1; fi
# If fuser exists, check common lock files
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
# Break if not busy
if [ "$LOCKS_BUSY" -eq 0 ]; then break; fi
# Timeout after ~5 minutes
if [ $SECONDS -ge $end ]; then
echo "Timeout waiting for apt/dpkg locks, proceeding with Dpkg::Lock::Timeout..."
break
fi
echo "Still waiting for locks..."; 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 docker.io
docker --version
jq --version
curl --version
- name: Set up Docker Buildx
run: |
# Check if buildx is available
if docker buildx version >/dev/null 2>&1; then
echo "Docker Buildx is available"
# Create builder if it doesn't exist
if ! docker buildx ls | grep -q "builder"; then
docker buildx create --name builder --driver docker-container
fi
# Use the builder
docker buildx use builder
docker buildx inspect --bootstrap
else
echo "Docker Buildx not available, using regular docker build"
fi
- name: Install Bun
run: |
echo "=== Installing Bun ==="
echo "Current working directory: $(pwd)"
echo "Current user: $(whoami)"
echo "Home directory: $HOME"
# 设置Bun安装路径
export BUN_INSTALL="$HOME/.bun"
echo "BUN_INSTALL=$BUN_INSTALL" >> $GITHUB_ENV
echo "PATH=$BUN_INSTALL/bin:${PATH}" >> $GITHUB_ENV
# 检查缓存是否存在
if [ -d "$BUN_INSTALL" ]; then
echo "✅ Bun cache found at $BUN_INSTALL"
ls -la "$BUN_INSTALL" || true
else
echo "❌ No Bun cache found, will install fresh"
fi
# 安装Bun
curl -fsSL https://bun.sh/install | bash
# 验证安装
"$BUN_INSTALL/bin/bun" --version
echo "✅ Bun installed successfully"
- name: Configure npm registry (npmmirror) and canvas mirror
run: |
echo "registry=https://registry.npmmirror.com" >> .npmrc
echo "canvas_binary_host_mirror=https://registry.npmmirror.com/-/binary/canvas" >> .npmrc
- name: Install dependencies cache (Bun)
if: env.CACHE_AVAILABLE == 'true'
uses: actions/cache@v4
continue-on-error: true
with:
path: ~/.bun
key: bun-${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-${{ runner.os }}-${{ matrix.node }}-
bun-${{ runner.os }}-
- name: Install dependencies cache (node_modules)
if: env.CACHE_AVAILABLE == 'true'
uses: actions/cache@v4
continue-on-error: true
with:
path: |
node_modules
apps/*/node_modules
packages/*/node_modules
key: node-modules-${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('bun.lock', 'package.json', 'apps/*/package.json', 'packages/*/package.json') }}
restore-keys: |
node-modules-${{ runner.os }}-${{ matrix.node }}-
node-modules-${{ runner.os }}-
- name: 缓存状态检查和设置
run: |
echo "=== 缓存状态检查 ==="
echo "检查缓存恢复状态..."
# 检查各种缓存目录
echo "Bun缓存: $([ -d ~/.bun ] && echo '✅ 已发现' || echo '❌ 缺失')"
echo "node_modules: $([ -d node_modules ] && echo '✅ 已发现' || echo '❌ 缺失')"
echo "Turbo缓存: $([ -d .turbo ] && echo '✅ 已发现' || echo '❌ 缺失')"
# 显示缓存大小
if [ -d ~/.bun ]; then
echo "Bun缓存大小: $(du -sh ~/.bun 2>/dev/null || echo '未知')"
fi
if [ -d node_modules ]; then
echo "node_modules大小: $(du -sh node_modules 2>/dev/null || echo '未知')"
fi
if [ -d .turbo ]; then
echo "Turbo缓存大小: $(du -sh .turbo 2>/dev/null || echo '未知')"
fi
echo "=== 缓存设置 ==="
# 确保缓存目录存在且权限正确
mkdir -p ~/.bun ~/.cache .turbo
chmod -R 755 ~/.bun ~/.cache .turbo 2>/dev/null || true
# 设置Bun环境变量
echo "BUN_INSTALL_CACHE_DIR=$HOME/.cache/bun" >> $GITHUB_ENV
echo "BUN_INSTALL_BIN_DIR=$HOME/.bun/bin" >> $GITHUB_ENV
echo "✅ 缓存目录已准备完成"
- name: Turborepo cache (.turbo)
if: env.CACHE_AVAILABLE == 'true'
uses: actions/cache@v4
continue-on-error: true
with:
path: .turbo
key: turbo-${{ runner.os }}-${{ hashFiles('turbo.json') }}-${{ hashFiles('apps/**/package.json') }}-${{ hashFiles('packages/**/package.json') }}-${{ hashFiles('bun.lock') }}
restore-keys: |
turbo-${{ runner.os }}-${{ hashFiles('turbo.json') }}-${{ hashFiles('apps/**/package.json') }}-${{ hashFiles('packages/**/package.json') }}-
turbo-${{ runner.os }}-${{ hashFiles('turbo.json') }}-
turbo-${{ runner.os }}-
- name: 安装依赖 (bun)
run: |
echo "=== 依赖安装调试信息 ==="
echo "当前目录: $(pwd)"
echo "Bun版本: $(bun --version)"
# 检查node_modules缓存状态
if [ -d "node_modules" ]; then
echo "✅ 发现node_modules缓存"
echo "node_modules大小: $(du -sh node_modules 2>/dev/null || echo '未知')"
else
echo "❌ 未发现node_modules缓存"
fi
# 检查bun.lock文件
if [ -f "bun.lock" ]; then
echo "✅ 发现bun.lock文件"
else
echo "❌ 未发现bun.lock文件"
fi
echo "=== 开始安装依赖 ==="
echo "安装开始时间: $(date)"
bun install --frozen-lockfile
echo "安装完成时间: $(date)"
echo "=== 依赖安装完成 ==="
echo "最终node_modules大小: $(du -sh node_modules 2>/dev/null || echo '未知')"
# 验证缓存效果
echo "=== 缓存效果验证 ==="
if [ -d "node_modules" ]; then
echo "✅ 依赖安装成功"
echo "包数量: $(ls node_modules | wc -l 2>/dev/null || echo '未知')"
else
echo "⚠️ 依赖可能未完全安装"
fi
- name: Decide build target (admin/user/both)
run: |
set -e
COMMIT_MSG="${{ github.event.head_commit.message }}"
BUILD_TARGET="both"
if echo "$COMMIT_MSG" | grep -qi "\[admin-only\]"; then
BUILD_TARGET="admin"
elif echo "$COMMIT_MSG" | grep -qi "\[user-only\]"; then
BUILD_TARGET="user"
else
if git rev-parse HEAD^ >/dev/null 2>&1; then
RANGE="HEAD^..HEAD"
else
RANGE="$(git rev-list --max-parents=0 HEAD)..HEAD"
fi
CHANGED=$(git diff --name-only $RANGE || true)
ADMIN_MATCH=$(echo "$CHANGED" | grep -E '^(apps/admin/|docker/ppanel-admin-web/)' || true)
USER_MATCH=$(echo "$CHANGED" | grep -E '^(apps/user/|docker/ppanel-user-web/)' || true)
PACKAGE_MATCH=$(echo "$CHANGED" | grep -E '^(packages/|turbo.json|package.json|bun.lock)' || true)
if [ -n "$PACKAGE_MATCH" ]; then
BUILD_TARGET="both"
else
if [ -n "$ADMIN_MATCH" ] && [ -z "$USER_MATCH" ]; then BUILD_TARGET="admin"; fi
if [ -n "$USER_MATCH" ] && [ -z "$ADMIN_MATCH" ]; then BUILD_TARGET="user"; fi
if [ -n "$ADMIN_MATCH" ] && [ -n "$USER_MATCH" ]; then BUILD_TARGET="both"; fi
fi
fi
echo "BUILD_TARGET=$BUILD_TARGET" >> $GITHUB_ENV
echo "Decided BUILD_TARGET=$BUILD_TARGET"
- name: Read version from package.json
run: echo "VERSION=$(jq -r .version package.json)" >> $GITHUB_ENV
- name: 根据分支动态设置API地址
run: |
if [ "${{ github.ref_name }}" = "main" ]; then
echo "NEXT_PUBLIC_API_URL=https://api.airoport.co" >> $GITHUB_ENV
echo "为main分支设置生产环境API地址"
elif [ "${{ github.ref_name }}" = "dev" ]; then
echo "NEXT_PUBLIC_API_URL=https://api.kxsw.us" >> $GITHUB_ENV
echo "为dev分支设置开发环境API地址"
else
echo "NEXT_PUBLIC_API_URL=https://api.airoport.co" >> $GITHUB_ENV
echo "为其他分支设置默认API地址"
fi
echo "BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV
- name: Cache Next.js build artifacts (.next/cache)
if: env.CACHE_AVAILABLE == 'true'
uses: actions/cache@v4
continue-on-error: true
with:
path: |
apps/admin/.next/cache
apps/user/.next/cache
key: nextcache-${{ runner.os }}-${{ hashFiles('apps/**/package.json') }}-${{ hashFiles('packages/**/package.json') }}-${{ hashFiles('turbo.json') }}-${{ hashFiles('bun.lock') }}
restore-keys: |
nextcache-${{ runner.os }}-${{ hashFiles('apps/**/package.json') }}-${{ hashFiles('packages/**/package.json') }}-
nextcache-${{ runner.os }}-
- name: Cache build outputs
if: env.CACHE_AVAILABLE == 'true'
uses: actions/cache@v4
continue-on-error: true
with:
path: |
apps/admin/.next
apps/user/.next
apps/admin/dist
apps/user/dist
key: build-${{ runner.os }}-${{ hashFiles('apps/**/*.ts', 'apps/**/*.tsx', 'apps/**/*.js', 'apps/**/*.jsx') }}-${{ hashFiles('packages/**/*.ts', 'packages/**/*.tsx') }}-${{ hashFiles('bun.lock') }}
restore-keys: |
build-${{ runner.os }}-${{ hashFiles('apps/**/*.ts', 'apps/**/*.tsx', 'apps/**/*.js', 'apps/**/*.jsx') }}-
build-${{ runner.os }}-
- name: Cache ESLint
if: env.CACHE_AVAILABLE == 'true'
uses: actions/cache@v4
continue-on-error: true
with:
path: |
.eslintcache
apps/admin/.eslintcache
apps/user/.eslintcache
key: eslint-${{ runner.os }}-${{ hashFiles('.eslintrc*', 'apps/**/.eslintrc*', 'packages/**/.eslintrc*') }}-${{ hashFiles('bun.lock') }}
restore-keys: |
eslint-${{ runner.os }}-
- name: Cache TypeScript
if: env.CACHE_AVAILABLE == 'true'
uses: actions/cache@v4
continue-on-error: true
with:
path: |
.tsbuildinfo
apps/admin/.tsbuildinfo
apps/user/.tsbuildinfo
packages/**/.tsbuildinfo
key: typescript-${{ runner.os }}-${{ hashFiles('tsconfig*.json', 'apps/**/tsconfig*.json', 'packages/**/tsconfig*.json') }}-${{ hashFiles('bun.lock') }}
restore-keys: |
typescript-${{ runner.os }}-
- name: 构建管理面板
if: env.BUILD_TARGET == 'admin' || env.BUILD_TARGET == 'both'
run: bun run build --filter=ppanel-admin-web
- name: 构建用户面板
if: env.BUILD_TARGET == 'user' || env.BUILD_TARGET == 'both'
run: bun run build --filter=ppanel-user-web
- name: 构建并推送管理面板Docker镜像
if: env.BUILD_TARGET == 'admin' || env.BUILD_TARGET == 'both'
run: |
if docker buildx version >/dev/null 2>&1; then
echo "使用docker buildx进行优化构建"
docker buildx build \
--platform linux/amd64 \
--cache-from type=registry,ref=${{ env.DOCKER_REGISTRY }}/ppanel/ppanel-admin-web:cache \
--cache-to type=registry,ref=${{ env.DOCKER_REGISTRY }}/ppanel/ppanel-admin-web:cache,mode=max \
-f ./docker/ppanel-admin-web/Dockerfile \
-t ${{ env.DOCKER_REGISTRY }}/ppanel/ppanel-admin-web:${{ env.VERSION }} \
--push .
else
echo "使用常规docker构建"
docker build -f ./docker/ppanel-admin-web/Dockerfile -t ${{ env.DOCKER_REGISTRY }}/ppanel/ppanel-admin-web:${{ env.VERSION }} .
docker push ${{ env.DOCKER_REGISTRY }}/ppanel/ppanel-admin-web:${{ env.VERSION }}
fi
- name: 构建并推送用户面板Docker镜像
if: env.BUILD_TARGET == 'user' || env.BUILD_TARGET == 'both'
run: |
if docker buildx version >/dev/null 2>&1; then
echo "使用docker buildx进行优化构建"
docker buildx build \
--platform linux/amd64 \
--cache-from type=registry,ref=${{ env.DOCKER_REGISTRY }}/ppanel/ppanel-user-web:cache \
--cache-to type=registry,ref=${{ env.DOCKER_REGISTRY }}/ppanel/ppanel-user-web:cache,mode=max \
-f ./docker/ppanel-user-web/Dockerfile \
-t ${{ env.DOCKER_REGISTRY }}/ppanel/ppanel-user-web:${{ env.VERSION }} \
--push .
else
echo "使用常规docker构建"
docker build -f ./docker/ppanel-user-web/Dockerfile -t ${{ env.DOCKER_REGISTRY }}/ppanel/ppanel-user-web:${{ env.VERSION }} .
docker push ${{ env.DOCKER_REGISTRY }}/ppanel/ppanel-user-web:${{ env.VERSION }}
fi
- name: SSH连接预检查
if: env.BUILD_TARGET == 'admin' || env.BUILD_TARGET == 'user' || env.BUILD_TARGET == 'both'
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
port: ${{ secrets.PORT }}
timeout: 60s
command_timeout: 30s
debug: true
script: |
echo "=== SSH连接测试 ==="
echo "连接时间: $(date)"
echo "服务器主机名: $(hostname)"
echo "当前用户: $(whoami)"
echo "系统信息: $(uname -a)"
echo "Docker版本: $(docker --version 2>/dev/null || echo 'Docker未安装')"
echo "✅ SSH连接成功"
- name: 部署管理面板到服务器
if: env.BUILD_TARGET == 'admin' || env.BUILD_TARGET == 'both'
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
port: ${{ secrets.PORT }}
timeout: 300s
command_timeout: 600s
debug: true
use_insecure_cipher: false
cipher: aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
fingerprint: ""
proxy_host: ""
proxy_port: ""
proxy_username: ""
proxy_password: ""
proxy_timeout: 30s
proxy_use_insecure_cipher: false
script: |
echo "=== SSH变量调试信息 ==="
echo "DOCKER_REGISTRY: ${{ env.DOCKER_REGISTRY }}"
echo "VERSION: ${{ env.VERSION }}"
echo "NEXT_PUBLIC_API_URL: ${{ env.NEXT_PUBLIC_API_URL }}"
echo "BRANCH: ${{ env.BRANCH }}"
echo "=== 部署管理面板 ==="
# 网络连通性检查
echo "检查镜像服务器连通性..."
REGISTRY_HOST=$(echo "${{ env.DOCKER_REGISTRY }}" | sed 's|https\?://||' | cut -d'/' -f1)
echo "镜像仓库地址: $REGISTRY_HOST"
if ping -c 3 "$REGISTRY_HOST" > /dev/null 2>&1; then
echo "✅ 镜像服务器连通性正常"
else
echo "⚠️ 镜像服务器ping失败但继续尝试拉取镜像"
fi
# 检查Docker登录状态
echo "检查Docker登录状态..."
if docker info > /dev/null 2>&1; then
echo "✅ Docker服务正常"
else
echo "❌ Docker服务异常"
exit 1
fi
# 拉取镜像(带重试)
echo "拉取Docker镜像..."
for i in {1..3}; do
echo "尝试拉取镜像 ($i/3): ${{ env.DOCKER_REGISTRY }}/ppanel/ppanel-admin-web:${{ env.VERSION }}"
if docker pull ${{ env.DOCKER_REGISTRY }}/ppanel/ppanel-admin-web:${{ env.VERSION }}; then
echo "✅ 镜像拉取成功"
break
else
echo "❌ 镜像拉取失败,重试 $i/3"
echo "检查网络和镜像仓库状态..."
# 显示详细错误信息
echo "--- 网络诊断信息 ---"
echo "DNS解析测试:"
nslookup "$REGISTRY_HOST" || echo "DNS解析失败"
echo "网络连通性测试:"
ping -c 2 "$REGISTRY_HOST" || echo "ping失败"
echo "Docker镜像仓库连接测试:"
curl -I "https://$REGISTRY_HOST/v2/" 2>/dev/null || echo "仓库API访问失败"
sleep 5
if [ $i -eq 3 ]; then
echo "❌ 镜像拉取失败,部署终止"
echo "请检查:"
echo "1. 网络连接是否正常"
echo "2. 镜像仓库是否可访问"
echo "3. 镜像标签是否存在"
echo "4. Docker登录凭据是否正确"
exit 1
fi
fi
done
# 安全停止和移除容器
echo "检查现有容器状态..."
CONTAINER_NAME="ppanel-admin-web"
# 检查容器是否存在
if docker ps -aq -f name=$CONTAINER_NAME | grep -q .; then
echo "发现现有容器,开始清理..."
# 检查容器是否正在运行
if docker ps -q -f name=$CONTAINER_NAME | grep -q .; then
echo "停止运行中的容器..."
docker stop $CONTAINER_NAME --time=15 || true
sleep 5
fi
# 检查容器是否正在被移除
echo "检查容器移除状态..."
for i in {1..15}; do
# 尝试获取容器状态
CONTAINER_STATUS=$(docker inspect $CONTAINER_NAME --format='{{.State.Status}}' 2>/dev/null || echo "not_found")
if [ "$CONTAINER_STATUS" = "not_found" ]; then
echo "✅ 容器已不存在"
break
elif [ "$CONTAINER_STATUS" = "removing" ]; then
echo "⏳ 容器正在移除中,等待完成... $i/15"
sleep 3
else
echo "尝试移除容器... $i/15"
if docker rm -f $CONTAINER_NAME 2>/dev/null; then
echo "✅ 容器移除成功"
break
else
echo "⚠️ 容器移除失败,重试..."
sleep 2
fi
fi
# 最后一次尝试强制清理
if [ $i -eq 15 ]; then
echo "🔧 执行强制清理..."
docker kill $CONTAINER_NAME 2>/dev/null || true
sleep 2
docker rm -f $CONTAINER_NAME 2>/dev/null || true
sleep 2
fi
done
else
echo "✅ 未发现现有容器"
fi
echo "启动新容器..."
docker run -d \
--name ppanel-admin-web \
--restart unless-stopped \
-p 3001:3000 \
-e NEXT_PUBLIC_API_URL=${{ env.NEXT_PUBLIC_API_URL }} \
${{ env.DOCKER_REGISTRY }}/ppanel/ppanel-admin-web:${{ env.VERSION }}
# 验证容器启动
echo "验证容器启动状态..."
for i in {1..10}; do
if docker ps -q -f name=ppanel-admin-web | grep -q .; then
echo "✅ 管理面板部署成功"
docker ps -f name=ppanel-admin-web --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
exit 0
else
echo "等待容器启动... $i/10"
sleep 3
fi
done
echo "❌ 管理面板部署失败 - 容器未能正常启动"
docker logs ppanel-admin-web || true
exit 1
- name: 部署用户面板到服务器
if: env.BUILD_TARGET == 'user' || env.BUILD_TARGET == 'both'
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
port: ${{ secrets.PORT }}
timeout: 300s
command_timeout: 600s
debug: true
use_insecure_cipher: false
cipher: aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
fingerprint: ""
proxy_host: ""
proxy_port: ""
proxy_username: ""
proxy_password: ""
proxy_timeout: 30s
proxy_use_insecure_cipher: false
script: |
echo "=== SSH变量调试信息 ==="
echo "DOCKER_REGISTRY: ${{ env.DOCKER_REGISTRY }}"
echo "VERSION: ${{ env.VERSION }}"
echo "NEXT_PUBLIC_API_URL: ${{ env.NEXT_PUBLIC_API_URL }}"
echo "BRANCH: ${{ env.BRANCH }}"
echo "=== 部署用户面板 ==="
# 网络连通性检查
echo "检查镜像服务器连通性..."
REGISTRY_HOST=$(echo "${{ env.DOCKER_REGISTRY }}" | sed 's|https\?://||' | cut -d'/' -f1)
echo "镜像仓库地址: $REGISTRY_HOST"
if ping -c 3 "$REGISTRY_HOST" > /dev/null 2>&1; then
echo "✅ 镜像服务器连通性正常"
else
echo "⚠️ 镜像服务器ping失败但继续尝试拉取镜像"
fi
# 检查Docker登录状态
echo "检查Docker登录状态..."
if docker info > /dev/null 2>&1; then
echo "✅ Docker服务正常"
else
echo "❌ Docker服务异常"
exit 1
fi
# 拉取镜像(带重试)
echo "拉取Docker镜像..."
for i in {1..3}; do
echo "尝试拉取镜像 ($i/3): ${{ env.DOCKER_REGISTRY }}/ppanel/ppanel-user-web:${{ env.VERSION }}"
if docker pull ${{ env.DOCKER_REGISTRY }}/ppanel/ppanel-user-web:${{ env.VERSION }}; then
echo "✅ 镜像拉取成功"
break
else
echo "❌ 镜像拉取失败,重试 $i/3"
echo "检查网络和镜像仓库状态..."
# 显示详细错误信息
echo "--- 网络诊断信息 ---"
echo "DNS解析测试:"
nslookup "$REGISTRY_HOST" || echo "DNS解析失败"
echo "网络连通性测试:"
ping -c 2 "$REGISTRY_HOST" || echo "ping失败"
echo "Docker镜像仓库连接测试:"
curl -I "https://$REGISTRY_HOST/v2/" 2>/dev/null || echo "仓库API访问失败"
sleep 5
if [ $i -eq 3 ]; then
echo "❌ 镜像拉取失败,部署终止"
echo "请检查:"
echo "1. 网络连接是否正常"
echo "2. 镜像仓库是否可访问"
echo "3. 镜像标签是否存在"
echo "4. Docker登录凭据是否正确"
exit 1
fi
fi
done
# 安全停止和移除容器
echo "检查现有容器状态..."
CONTAINER_NAME="ppanel-user-web"
# 检查容器是否存在
if docker ps -aq -f name=$CONTAINER_NAME | grep -q .; then
echo "发现现有容器,开始清理..."
# 检查容器是否正在运行
if docker ps -q -f name=$CONTAINER_NAME | grep -q .; then
echo "停止运行中的容器..."
docker stop $CONTAINER_NAME --time=15 || true
sleep 5
fi
# 检查容器是否正在被移除
echo "检查容器移除状态..."
for i in {1..15}; do
# 尝试获取容器状态
CONTAINER_STATUS=$(docker inspect $CONTAINER_NAME --format='{{.State.Status}}' 2>/dev/null || echo "not_found")
if [ "$CONTAINER_STATUS" = "not_found" ]; then
echo "✅ 容器已不存在"
break
elif [ "$CONTAINER_STATUS" = "removing" ]; then
echo "⏳ 容器正在移除中,等待完成... $i/15"
sleep 3
else
echo "尝试移除容器... $i/15"
if docker rm -f $CONTAINER_NAME 2>/dev/null; then
echo "✅ 容器移除成功"
break
else
echo "⚠️ 容器移除失败,重试..."
sleep 2
fi
fi
# 最后一次尝试强制清理
if [ $i -eq 15 ]; then
echo "🔧 执行强制清理..."
docker kill $CONTAINER_NAME 2>/dev/null || true
sleep 2
docker rm -f $CONTAINER_NAME 2>/dev/null || true
sleep 2
fi
done
else
echo "✅ 未发现现有容器"
fi
echo "启动新容器..."
docker run -d \
--name ppanel-user-web \
--restart unless-stopped \
-p 3002:3000 \
-e NEXT_PUBLIC_API_URL=${{ env.NEXT_PUBLIC_API_URL }} \
${{ env.DOCKER_REGISTRY }}/ppanel/ppanel-user-web:${{ env.VERSION }}
# 验证容器启动
echo "验证容器启动状态..."
for i in {1..10}; do
if docker ps -q -f name=ppanel-user-web | grep -q .; then
echo "✅ 用户面板部署成功"
docker ps -f name=ppanel-user-web --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
exit 0
else
echo "等待容器启动... $i/10"
sleep 3
fi
done
echo "❌ 用户面板部署失败 - 容器未能正常启动"
docker logs ppanel-user-web || true
exit 1
- name: 发送成功通知到Telegram
if: success()
run: |
MESSAGE="✅ 部署成功!
📦 项目: ${{ github.repository }}
🌿 分支: ${{ github.ref_name }}
🔖 版本: ${{ env.VERSION }}
🎯 构建目标: ${{ env.BUILD_TARGET }}
🔗 API地址: ${{ env.NEXT_PUBLIC_API_URL }}
🕐 时间: $(date '+%Y-%m-%d %H:%M:%S')"
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
-d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \
-d text="$MESSAGE" \
-d parse_mode="HTML"
- name: 发送失败通知到Telegram
if: failure()
run: |
MESSAGE="❌ 部署失败!
📦 项目: ${{ github.repository }}
🌿 分支: ${{ github.ref_name }}
🔖 版本: ${{ env.VERSION }}
🎯 构建目标: ${{ env.BUILD_TARGET }}
🕐 时间: $(date '+%Y-%m-%d %H:%M:%S')
请检查构建日志获取详细信息。"
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
-d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \
-d text="$MESSAGE" \
-d parse_mode="HTML"