From 447387c0a558a6ca9d53aa732d25856946d71d22 Mon Sep 17 00:00:00 2001 From: shanshanzhong Date: Thu, 22 Jan 2026 00:55:41 -0800 Subject: [PATCH] =?UTF-8?q?add:=20=E5=A2=9E=E5=8A=A0CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/docker.yml | 196 ++++++++++++++++++++++++++++++++++++ scripts/ci-build.sh | 133 ++++++++++++++++++++++++ 2 files changed, 329 insertions(+) create mode 100644 .gitea/workflows/docker.yml create mode 100644 scripts/ci-build.sh diff --git a/.gitea/workflows/docker.yml b/.gitea/workflows/docker.yml new file mode 100644 index 0000000..f3decac --- /dev/null +++ b/.gitea/workflows/docker.yml @@ -0,0 +1,196 @@ +name: site-dist-deploy + +on: + push: + branches: + - main + - develop + pull_request: + branches: + - main + - develop + + +env: + VITE_APP_BASE_URL: / + SSH_HOST: ${{ vars.PRO_SSH_HOST }} + SSH_PORT: ${{ vars.PRO_SSH_PORT }} + SSH_USER: ${{ vars.PRO_SSH_USER }} + SSH_PASSWORD: ${{ vars.PRO_SSH_PASSWORD }} + DEPLOY_PATH: /var/www/hi-download + # TG通知 + TG_BOT_TOKEN: 8114337882:AAHkEx03HSu7RxN4IHBJJEnsK9aPPzNLIk0 + TG_CHAT_ID: "-4940243803" + +jobs: + build-and-deploy: + runs-on: hi-download + steps: + - name: Manual checkout (no Node required) + run: | + set -e + if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + git fetch --all --tags + git checkout "${{ github.ref_name }}" + git reset --hard "origin/${{ github.ref_name }}" + else + REPO_URL="${{ github.server_url }}/${{ github.repository }}" + echo "Cloning $REPO_URL" + git clone --depth=1 --branch "${{ github.ref_name }}" "$REPO_URL" . + git fetch --tags + fi + + - name: Build dist with Unified Script + env: + VITE_APP_BASE_URL: "/" + run: | + chmod +x scripts/ci-build.sh + ./scripts/ci-build.sh + + - name: Check Artifacts + run: | + echo "Current directory: $(pwd)" + echo "Listing all files in workspace:" + find . -maxdepth 2 -not -path '*/.*' + if [ -f "site_dist.tgz" ]; then + echo "✅ File exists: site_dist.tgz" + ls -lh site_dist.tgz + echo "File path: $(readlink -f site_dist.tgz)" + else + echo "❌ File NOT found: site_dist.tgz" + exit 1 + fi + + - name: Deploy to Host (Native SSH/SCP) + run: | + echo "Installing SSH tools..." + if command -v apk &> /dev/null; then + echo "Detected Alpine Linux. Installing sshpass openssh-client via apk..." + apk add --no-cache sshpass openssh-client + elif command -v apt-get &> /dev/null; then + echo "Detected Debian/Ubuntu. Installing sshpass openssh-client via apt..." + apt-get update -y && apt-get install -y sshpass openssh-client + elif command -v yum &> /dev/null; then + echo "Detected RHEL/CentOS. Installing sshpass openssh-clients via yum..." + yum install -y sshpass openssh-clients + elif command -v dnf &> /dev/null; then + echo "Detected Fedora/RHEL8+. Installing sshpass openssh-clients via dnf..." + dnf install -y sshpass openssh-clients + elif command -v zypper &> /dev/null; then + echo "Detected OpenSUSE. Installing sshpass openssh via zypper..." + zypper install -y sshpass openssh + else + echo "Error: No known package manager found. Cannot install sshpass." + exit 1 + fi + + echo "Uploading artifact..." + # 使用 sshpass 传递密码 (更安全的方式是使用 key,但此处沿用 password) + export SSHPASS="${{ env.SSH_PASSWORD }}" + + # 1. 检查连接并创建目录 (包含 /tmp/ci-upload 和 目标目录的准备) + sshpass -e ssh -o StrictHostKeyChecking=no -p ${{ env.SSH_PORT }} ${{ env.SSH_USER }}@${{ env.SSH_HOST }} "mkdir -p /tmp/ci-upload" + + # 2. SCP 上传 (直接使用当前目录下的 site_dist.tgz,规避跨容器挂载问题) + if [ ! -f "site_dist.tgz" ]; then + echo "❌ Error: site_dist.tgz not found in current directory!" + exit 1 + fi + + sshpass -e scp -o StrictHostKeyChecking=no -P ${{ env.SSH_PORT }} site_dist.tgz ${{ env.SSH_USER }}@${{ env.SSH_HOST }}:/tmp/ci-upload/site_dist.tgz + + # 3. 解压并重启 Nginx + echo "Deploying on remote host..." + sshpass -e ssh -o StrictHostKeyChecking=no -p ${{ env.SSH_PORT }} ${{ env.SSH_USER }}@${{ env.SSH_HOST }} " + echo 'Preparing target directory ${{ env.DEPLOY_PATH }}...' + mkdir -p ${{ env.DEPLOY_PATH }} + + # 切换到目录,确保操作安全 + cd ${{ env.DEPLOY_PATH }} || exit 1 + + echo 'Cleaning up old files (preserving download/downsload)...' + + # 使用更安全的策略: + # 1. 创建临时备份目录 + mkdir -p /tmp/site_backup_safe + rm -rf /tmp/site_backup_safe/* + + # 2. 将需要保留的文件夹移到备份目录 (如果存在) + if [ -d 'download' ]; then + echo 'Backing up download folder...' + mv download /tmp/site_backup_safe/ + fi + if [ -d 'downsload' ]; then + echo 'Backing up downsload folder...' + mv downsload /tmp/site_backup_safe/ + fi + + # 3. 清空当前目录 (此时 download/downsload 已经移走,安全删除所有) + # 注意:不删除当前目录本身,只删除内容 + find . -mindepth 1 -delete + + # 4. 移回备份的文件夹 + if [ -d '/tmp/site_backup_safe/download' ]; then + echo 'Restoring download folder...' + mv /tmp/site_backup_safe/download . + fi + if [ -d '/tmp/site_backup_safe/downsload' ]; then + echo 'Restoring downsload folder...' + mv /tmp/site_backup_safe/downsload . + fi + + # 5. 清理备份目录 + rm -rf /tmp/site_backup_safe + + echo 'Extracting to ${{ env.DEPLOY_PATH }}...' + # 解压覆盖 + tar -xzf /tmp/ci-upload/site_dist.tgz -C ${{ env.DEPLOY_PATH }} + + echo 'Reloading Nginx...' + # 尝试多种 reload 方式 + nginx -s reload || systemctl reload nginx || echo 'Warning: Nginx reload returned non-zero' + + echo 'Cleanup...' + rm -f /tmp/ci-upload/site_dist.tgz + " + echo "✅ Deployment complete!" + + + # 步骤6: TG通知 (成功) + - name: 📱 发送成功通知到Telegram + if: success() + 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 }} + + 🚀 服务已成功部署到生产环境 + parse_mode: Markdown + + # 步骤5: TG通知 (失败) + - name: 📱 发送失败通知到Telegram + if: failure() + 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 }} + + ⚠️ 请检查构建日志获取详细信息 + parse_mode: Markdown + \ No newline at end of file diff --git a/scripts/ci-build.sh b/scripts/ci-build.sh new file mode 100644 index 0000000..2b285ee --- /dev/null +++ b/scripts/ci-build.sh @@ -0,0 +1,133 @@ +#!/bin/bash +set -e + +# ========================================== +# 统一构建脚本 (CI Build Script) +# 功能:自动检测环境、安装 Node.js (不依赖系统预装)、构建项目、打包产物 +# 解决:Runner 环境缺失 Node/Docker/Python 等工具导致的问题 +# ========================================== + +# 配置节点版本 +NODE_VERSION="20.10.0" +DIST_FILE="site_dist.tgz" + +echo ">>> [Init] Starting CI Build Script..." + +# ---------------------------------------------------------------- +# 1. 基础工具检测与安装 (curl, tar, xz) +# ---------------------------------------------------------------- +ensure_tools() { + echo ">>> [Tools] Checking basic tools..." + local missing_tools=() + + for tool in curl tar xz; do + if ! command -v "$tool" &> /dev/null; then + missing_tools+=("$tool") + fi + done + + if [ ${#missing_tools[@]} -eq 0 ]; then + echo " All tools present." + return 0 + fi + + echo " Missing tools: ${missing_tools[*]}. Attempting installation..." + + if command -v apk &> /dev/null; then + apk add --no-cache curl tar xz + elif command -v apt-get &> /dev/null; then + apt-get update && apt-get install -y curl tar xz-utils + elif command -v yum &> /dev/null; then + yum install -y curl tar xz + elif command -v dnf &> /dev/null; then + dnf install -y curl tar xz + elif command -v zypper &> /dev/null; then + zypper install -y curl tar xz + else + echo "!!! Error: Cannot install missing tools. No known package manager found." + exit 1 + fi +} + +# ---------------------------------------------------------------- +# 2. 环境安装 (System Node.js - Most Reliable on Alpine) +# ---------------------------------------------------------------- +install_env() { + echo ">>> [Env] Setting up System Node.js environment..." + + # 尝试使用系统包管理器安装 Node.js 和 npm + if command -v apk &> /dev/null; then + echo " Detected Alpine Linux. Installing nodejs and npm via apk..." + apk add --no-cache nodejs npm + elif command -v apt-get &> /dev/null; then + echo " Detected Debian/Ubuntu. Installing nodejs and npm via apt..." + apt-get update && apt-get install -y nodejs npm + elif command -v yum &> /dev/null; then + yum install -y nodejs npm + elif command -v dnf &> /dev/null; then + dnf install -y nodejs npm + elif command -v zypper &> /dev/null; then + zypper install -y nodejs npm + else + echo "!!! Warning: No package manager found. Checking if Node is pre-installed..." + fi + + # 验证安装 + if ! command -v node &> /dev/null; then + echo "!!! Error: Node.js not found and could not be installed." + exit 1 + fi + + if ! command -v npm &> /dev/null; then + echo "!!! Error: npm not found and could not be installed." + exit 1 + fi + + echo " Node version: $(node -v)" + echo " npm version: $(npm -v)" +} + +# ---------------------------------------------------------------- +# 3. 项目构建与打包 +# ---------------------------------------------------------------- +build_project() { + echo ">>> [Build] Starting project build..." + + # 注入环境变量 + if [ -n "$VITE_APP_BASE_URL" ]; then + echo " Setting VITE_APP_BASE_URL=${VITE_APP_BASE_URL}" + # 兼容 package.json 中的 mode: pord (Typo in original project) + echo "VITE_APP_BASE_URL=${VITE_APP_BASE_URL}" > .env.pord + # 同时写入 .env 以防万一 + echo "VITE_APP_BASE_URL=${VITE_APP_BASE_URL}" >> .env + fi + + echo " Installing dependencies..." + # 使用 npm install 而不是 npm ci,以避免因 lockfile 版本不匹配或 engines 检查导致的失败 + npm install --no-audit --progress=false + + echo " Building..." + # 使用 package.json 中定义的 build:prod 命令 + npm run build:prod + + echo ">>> [Package] Compressing artifacts..." + if [ ! -d "dist" ]; then + echo "!!! Error: 'dist' directory not found after build." + # 列出当前目录以便调试 + ls -la + exit 1 + fi + + # 直接打包 dist 目录下的内容 + tar -C dist -czf "$DIST_FILE" . + + echo " Success! Artifact created: $DIST_FILE" + ls -lh "$DIST_FILE" +} + +# ========================================== +# Main Execution Flow +# ========================================== +ensure_tools +install_env +build_project