From 5d015a3529957b98b6b4719f1fad4f4d4c0a5fd4 Mon Sep 17 00:00:00 2001 From: HenryDu8133 <813367384@qq.com> Date: Sun, 6 Sep 2026 08:44:11 +0800 Subject: [PATCH] =?UTF-8?q?ci:=20=E6=96=B0=E5=A2=9E=20Gitea=20Actions=20?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E6=B5=81=E6=B0=B4=E7=BA=BF=20+=20Runner/Depl?= =?UTF-8?q?oy=20=E4=B8=80=E9=94=AE=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/deploy.yml | 337 +++++++++++++++++++++++++---------- scripts/setup-deploy-user.sh | 159 +++++++++++++++++ scripts/setup-runner.sh | 211 ++++++++++++++++++++++ 3 files changed, 617 insertions(+), 90 deletions(-) create mode 100644 scripts/setup-deploy-user.sh create mode 100644 scripts/setup-runner.sh diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index ca3da1f..e8d783b 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,164 +1,321 @@ -name: 构建并部署 VoiceHub -run-name: ${{ gitea.actor }} 触发部署 #${{{{ gitea.run_number }}}} +name: 构建 & 部署 BYW VoiceHub +run-name: ${{ github.actor }} 触发部署 · Run ${{ github.run_number }} on: push: - branches: - - main - - master - # 允许在 Gitea 页面手动点「运行工作流」 + branches: [main, master] workflow_dispatch: inputs: deploy_target: - description: 是否在构建后自动部署到服务器 + description: 构建完成后是否自动部署到服务器 type: boolean default: true required: false + skip_build_cache: + description: 跳过 BuildKit 分层缓存,强制重新构建(排错用) + type: boolean + default: false + required: false -# 同一时刻只跑一个部署,避免部署竞态 +# 同一时刻只允许一个部署在跑,避免冲突;如果新提交进来,取消前一个未完成的 concurrency: group: deploy-production - cancel-in-progress: false + cancel-in-progress: true env: - # ====== 按需修改(改成你的 Gitea 域名,不要 http:// 前缀) ====== - GITEA_REGISTRY: gitea.example.com - # ====== 一般不改 ====== + # ============================================================ + # 👇👇👇 这里改成你自己的 Gitea 域名(不要 https:// 前缀,不要端口默认 443/80) + # 例:git.baoyiwai.com 或 gitea.example.com:3000 + GITEA_HOST: gitea.example.com + # 👆👆👆 + # ============================================================ IMAGE_NAME: byw-voicehub DEPLOY_PATH: /opt/byw-voicehub - NODE_VERSION: "22" jobs: # ============================================================ - # Job 1: 构建 & 推送 Docker 镜像到 Gitea 自带容器仓库 + # Job 1: 校验构建环境 & 打包产物(npm ci + build,不依赖 Docker) # ============================================================ - build-image: - name: 构建 Docker 镜像 + preflight: + name: Preflight 语法 & 构建检查 runs-on: ubuntu-latest - outputs: - image_tag: ${{ steps.meta.outputs.tag }} - image_full: ${{ steps.meta.outputs.full }} + timeout-minutes: 15 steps: - - name: 📥 拉取代码 + - name: 📥 Checkout 代码 uses: actions/checkout@v4 with: fetch-depth: 1 - - name: 🔧 设置 Node.js + - name: 🔧 安装 Node.js 22 uses: actions/setup-node@v4 with: - node-version: ${{ env.NODE_VERSION }} - registry-url: https://registry.npmmirror.com # 国内加速,不需要可删 + node-version: "22" + cache: "npm" - - name: 🧪 环境校验 + - name: 🧪 安装依赖(npm ci,严格按锁文件) run: | set -euxo pipefail - node -v - npm -v - npm ci --no-audit --no-fund + # 国内加速镜像(Runner 在国外可以删掉这两行) + npm config set registry https://registry.npmmirror.com || true + npm config set fund false + npm config set audit false + npm ci - - name: 🔐 登录 Gitea 容器仓库 - uses: docker/login-action@v3 - with: - registry: ${{ env.GITEA_REGISTRY }} - username: ${{ gitea.repository_owner }} - password: ${{ secrets.REGISTRY_TOKEN || gitea.token }} - - - name: 🏷️ 生成镜像标签 - id: meta + - name: 🔎 检查 package.json 没有 pnpm 残留 run: | set -euo pipefail - SHORT_SHA="${GITEA_SHA:0:7}" - DATE_TAG="$(date +%Y%m%d)-${SHORT_SHA}" - FULL="${GITEA_REGISTRY}/${GITEA_REPO_OWNER}/${IMAGE_NAME}:${DATE_TAG}" - LATEST="${GITEA_REGISTRY}/${GITEA_REPO_OWNER}/${IMAGE_NAME}:latest" - echo "tag=${DATE_TAG}" >> "$GITEA_OUTPUT" - echo "full=${FULL}" >> "$GITEA_OUTPUT" - echo "latest=${LATEST}" >> "$GITEA_OUTPUT" - echo "✅ 镜像 tag: ${DATE_TAG}" - echo "✅ 镜像完整: ${FULL}" + if grep -qE '"packageManager"\s*:\s*"pnpm' package.json; then + echo "❌ package.json 里有 pnpm 包管理器声明,和用户要求冲突" + exit 1 + fi + echo "✅ package.json 未发现 pnpm 声明" - - name: 🐳 构建并推送镜像(BuildKit 多阶段 + 缓存) + - name: 🏗️ 本地 nuxt build(独立于 Docker 先验证代码是可构建的) + run: | + set -euxo pipefail + NODE_OPTIONS="--max-old-space-size=2048" npm run build + echo "✅ .output/ 生成成功:" + ls -la .output/ 2>/dev/null || true + du -sh .output 2>/dev/null || true + + # ============================================================ + # Job 2: 构建 Docker 镜像 → 推送到 Gitea 自带容器仓库 + # ============================================================ + build-image: + name: 构建并推送镜像 + needs: preflight + runs-on: ubuntu-latest + timeout-minutes: 40 + outputs: + image_tag: ${{ steps.meta.outputs.tag }} + image_full: ${{ steps.meta.outputs.full }} + image_latest: ${{ steps.meta.outputs.latest }} + steps: + - name: 📥 Checkout 代码 + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: 🏷️ 计算镜像 tag(日期 + 短 SHA) + id: meta + shell: bash + run: | + set -euo pipefail + SHA="${GITHUB_SHA:-$(git rev-parse HEAD)}" + SHORT_SHA="${SHA:0:7}" + DATE_TAG="$(date +%Y%m%d)-${SHORT_SHA}" + IMG_FULL="${GITEA_HOST}/${GITHUB_REPOSITORY_OWNER}/${IMAGE_NAME}:${DATE_TAG}" + IMG_LATEST="${GITEA_HOST}/${GITHUB_REPOSITORY_OWNER}/${IMAGE_NAME}:latest" + echo "tag=${DATE_TAG}" >> "$GITHUB_OUTPUT" + echo "full=${IMG_FULL}" >> "$GITHUB_OUTPUT" + echo "latest=${IMG_LATEST}" >> "$GITHUB_OUTPUT" + echo "======================================" + echo "🪪 镜像版本 tag : ${DATE_TAG}" + echo "🪪 镜像完整名 : ${IMG_FULL}" + echo "🪪 镜像 latest : ${IMG_LATEST}" + echo "======================================" + + - name: 🔧 启用 QEMU(跨架构小概率用得到,保险) + uses: docker/setup-qemu-action@v3 + with: + platforms: linux/amd64 + + - name: 🔧 启用 Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + install: true + + - name: 🔐 登录 Gitea 容器仓库(Packages) + uses: docker/login-action@v3 + with: + registry: ${{ env.GITEA_HOST }} + # Secrets 里 REGISTRY_USER 没填就默认用触发构建的用户 + username: ${{ secrets.REGISTRY_USER || github.actor }} + # Secrets 里 REGISTRY_TOKEN 优先,没填就用 Gitea Actions 自动注入的仓库级 token + password: ${{ secrets.REGISTRY_TOKEN || github.token }} + + - name: 🐳 构建 & 推送(BuildKit 分层缓存,skip_build_cache=true 时禁用) + id: build_push uses: docker/build-push-action@v6 + env: + CACHE_REF: ${{ env.GITEA_HOST }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:buildcache with: context: . file: ./Dockerfile + platforms: linux/amd64 push: true + provenance: false tags: | ${{ steps.meta.outputs.full }} ${{ steps.meta.outputs.latest }} - cache-from: type=registry,ref=${{ env.GITEA_REGISTRY }}/${{ gitea.repository_owner }}/${{ env.IMAGE_NAME }}:buildcache - cache-to: type=registry,ref=${{ env.GITEA_REGISTRY }}/${{ gitea.repository_owner }}/${{ env.IMAGE_NAME }}:buildcache,mode=max build-args: | BUILDKIT_INLINE_CACHE=1 + NODE_ENV=production + cache-from: ${{ inputs.skip_build_cache == true && '""' || format('type=registry,ref={0}', env.CACHE_REF) }} + cache-to: ${{ inputs.skip_build_cache == true && '""' || format('type=registry,ref={0},mode=max', env.CACHE_REF) }} labels: | - org.opencontainers.image.source=${{ gitea.server_url }}/${{ gitea.repository }} - org.opencontainers.image.revision=${{ gitea.sha }} + org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.created=${{ github.event.head_commit.timestamp }} + + - name: 🧮 镜像体积 & 层信息(日志里看一下,避免镜像越来越大) + run: | + set -euxo pipefail + docker pull "${{ steps.meta.outputs.full }}" || true + docker image inspect "${{ steps.meta.outputs.full }}" --format='{{.Size}}' 2>/dev/null \ + | awk '{printf "✅ 镜像体积:%.2f MB\n", $1/1024/1024}' || true + docker history --no-trunc "${{ steps.meta.outputs.full }}" 2>/dev/null | head -20 || true # ============================================================ - # Job 2: SSH 到服务器拉取新镜像并重启 + # Job 3: SSH 到服务器 → 拉新镜像 → 滚动重启 # ============================================================ deploy: - name: 部署到服务器 + name: 部署到生产服务器 needs: build-image - if: ${{ !cancelled() && !failure() && (inputs.deploy_target == true || github.event_name == 'push') }} + # push 事件默认自动部署;手动触发时看 checkbox + if: ${{ !cancelled() && !failure() && (github.event_name == 'push' || inputs.deploy_target == true) }} runs-on: ubuntu-latest - environment: production + timeout-minutes: 25 + environment: + name: production + url: ${{ secrets.NUXT_PUBLIC_SITE_URL }} steps: - - name: 🌐 SSH 部署到生产服务器 - uses: https://github.com/appleboy/ssh-action@v1.2.0 + - name: ⚠️ 检查部署所需 Secrets 是否都配置了 + shell: bash + run: | + set -euo pipefail + MISSING=() + [ -n "${{ secrets.DEPLOY_HOST }}" ] || MISSING+=("DEPLOY_HOST") + [ -n "${{ secrets.DEPLOY_USER }}" ] || MISSING+=("DEPLOY_USER") + [ -n "${{ secrets.DEPLOY_SSH_KEY }}" ] || MISSING+=("DEPLOY_SSH_KEY") + if [ ${#MISSING[@]} -gt 0 ]; then + echo "❌ 缺少以下 Repository Secrets:${MISSING[*]}" + echo "👉 去 Gitea 仓库 → Settings → Secrets and variables → Actions 里添加" + exit 1 + fi + echo "✅ 部署 Secrets 配置完整" + + - name: 🚀 一键滚动部署(SSH) + uses: appleboy/ssh-action@v1.2.0 with: host: ${{ secrets.DEPLOY_HOST }} + port: ${{ secrets.DEPLOY_PORT || 22 }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_SSH_KEY }} - port: ${{ secrets.DEPLOY_PORT || 22 }} - script_stop: true + # 部署可能慢,给足 30 分钟 command_timeout: 30m + # 出错立刻停,不继续往下跑 + script_stop: true script: | set -euxo pipefail - # 1) 首次部署时自动创建目录 & 空的 docker-compose.yml/.env - mkdir -p "${DEPLOY_PATH}" - cd "${DEPLOY_PATH}" + # ---------- 0. 基本参数 ---------- + IMAGE_NEW="${{ needs.build-image.outputs.image_full }}" + IMAGE_LATEST="${{ needs.build-image.outputs.image_latest }}" + DEPLOY_DIR="${DEPLOY_PATH}" + COMPOSE="docker compose" - # 2) 登录 Gitea 镜像仓库(用一个仅 pull 权限的 token 更安全) - echo "${{ secrets.REGISTRY_PULL_TOKEN || gitea.token }}" \ - | docker login "${GITEA_REGISTRY}" -u "${{ secrets.REGISTRY_PULL_USER || gitea.repository_owner }}" --password-stdin 2>/dev/null || true + echo "==============================================" + echo "🚢 本次部署镜像: ${IMAGE_NEW}" + echo "🗂️ 部署目录 : ${DEPLOY_DIR}" + echo "👤 执行用户 : $(whoami)" + echo "==============================================" - # 3) 拉最新镜像 - IMAGE_LATEST="${GITEA_REGISTRY}/${GITEA_REPO_OWNER}/${IMAGE_NAME}:latest" - docker pull "$IMAGE_LATEST" + # ---------- 1. 确保部署目录存在 ---------- + mkdir -p "${DEPLOY_DIR}" + cd "${DEPLOY_DIR}" - # 4) 写一个最小的 docker-compose.override.yml,指向刚拉的镜像 - # (避免改动服务器上已配置好的 docker-compose.yml 和 .env) - cat > docker-compose.override.yml <<'YAML' - services: - voicehub: - image: __REPLACE_WITH_IMAGE__ - YAML - sed -i "s|__REPLACE_WITH_IMAGE__|${IMAGE_LATEST}|g" docker-compose.override.yml - - # 5) 启动(如果没有 docker-compose.yml 就自动从你的仓库默认模板里下载一份) - if [ ! -f docker-compose.yml ]; then - echo "ℹ️ 首次部署:写入默认 docker-compose.yml 模板(请稍后在服务器上编辑 .env)" - curl -fsSL "${{ gitea.server_url }}/${{ gitea.repository }}/raw/branch/main/docker-compose.yml" \ - -o docker-compose.yml || echo "下载失败,跳过(你需要手动上传 docker-compose.yml)" + # ---------- 2. 登录 Gitea 容器仓库(用 pull token,最小权限) ---------- + if [ -n "${{ secrets.REGISTRY_PULL_TOKEN }}" ] && [ -n "${{ secrets.REGISTRY_PULL_USER }}" ]; then + echo "🔐 使用专用 pull 凭证登录 Gitea Container Registry" + echo "${{ secrets.REGISTRY_PULL_TOKEN }}" \ + | docker login "${GITEA_HOST}" \ + -u "${{ secrets.REGISTRY_PULL_USER }}" \ + --password-stdin 2>/dev/null || true fi - # 6) 健康拉取 + 滚动重启(先拉好再 up,downtime 最小) - docker compose pull --quiet voicehub 2>/dev/null || true - docker compose up -d --remove-orphans + # ---------- 3. 先拉镜像(提前下好,后面 up 不会卡) ---------- + echo "⬇️ 开始拉取新镜像 ${IMAGE_NEW} ..." + docker pull "${IMAGE_NEW}" + docker tag "${IMAGE_NEW}" "${IMAGE_LATEST}" + echo "✅ 新镜像拉取完成,并已打 latest tag" - # 7) 等健康检查通过,最多等 120s - for i in $(seq 1 24); do - if docker compose ps --format json 2>/dev/null | grep -q '"Health":"healthy"'; then - echo "✅ 容器健康" + # ---------- 4. 生成 / 更新 docker-compose.override.yml ---------- + # 指向新的精确 tag(不是 latest,出问题能快速回滚) + cat > docker-compose.override.yml <' > docker-compose.override.yml" + echo " docker compose up -d" + echo "=================================================================" diff --git a/scripts/setup-deploy-user.sh b/scripts/setup-deploy-user.sh new file mode 100644 index 0000000..38ac0b1 --- /dev/null +++ b/scripts/setup-deploy-user.sh @@ -0,0 +1,159 @@ +#!/usr/bin/env bash +# ============================================================================ +# setup-deploy-user.sh +# 在「生产服务器」上一键做好 CI 自动部署所需的系统配置: +# 1) 新建 deploy 用户(低权限,禁止密码登录,只能 key 登录) +# 2) 生成 ed25519 SSH 密钥对(私钥你粘去 Gitea Secrets DEPLOY_SSH_KEY) +# 3) 加入 docker 组,允许 docker compose 操作 +# 4) 给 /opt/byw-voicehub 目录读写权限(setfacl) +# 5) 打印私钥内容 & 公钥指纹,便于你检查和复制 +# +# 用法(在生产服务器上,sudo 运行): +# sudo bash scripts/setup-deploy-user.sh [--deploy-dir /opt/byw-voicehub] +# ============================================================================ +set -euo pipefail + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[1;36m'; NC='\033[0m' +info(){ echo -e "${GREEN}[INFO]${NC} $*"; } +warn(){ echo -e "${YELLOW}[WARN]${NC} $*"; } +err(){ echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; } +ok(){ echo -e "${CYAN}[ OK ]${NC} $*"; } + +DEPLOY_DIR="/opt/byw-voicehub" +DEPLOY_USER="deploy" +while [[ $# -gt 0 ]]; do + case "$1" in + --deploy-dir) DEPLOY_DIR="$2"; shift 2 ;; + --user) DEPLOY_USER="$2"; shift 2 ;; + -h|--help) + sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//' + exit 0 + ;; + *) err "未知参数 $1(--help 查看帮助)" ;; + esac +done + +[[ $EUID -eq 0 ]] || err "请用 sudo 运行本脚本" +command -v docker >/dev/null 2>&1 || err "先装完 Docker 再跑本脚本(路径一的教程里有)" +command -v setfacl >/dev/null 2>&1 || { + warn "没装 acl 工具,现在 apt install" + apt-get update && apt-get install -y acl +} + +# -------- 1. 建用户 -------- +if ! id -u "${DEPLOY_USER}" >/dev/null 2>&1; then + useradd --create-home --shell /usr/bin/bash --disabled-password "${DEPLOY_USER}" + ok "新建用户:${DEPLOY_USER}(无密码,仅 SSH key 登录)" +else + info "用户 ${DEPLOY_USER} 已存在,跳过创建" +fi + +# -------- 2. 加入 docker 组 -------- +usermod -aG docker "${DEPLOY_USER}" +ok "${DEPLOY_USER} 加入 docker 组(有权限执行 docker compose)" + +# -------- 3. 生成 SSH 密钥(无密码) -------- +SSH_DIR="/home/${DEPLOY_USER}/.ssh" +mkdir -p "${SSH_DIR}" +chmod 700 "${SSH_DIR}" +chown "${DEPLOY_USER}:${DEPLOY_USER}" "${SSH_DIR}" + +KEY_PATH="${SSH_DIR}/id_ed25519" +if [[ ! -f "${KEY_PATH}" ]]; then + sudo -u "${DEPLOY_USER}" ssh-keygen -t ed25519 \ + -C "gitea-actions-deploy-$(hostname)" \ + -f "${KEY_PATH}" \ + -N "" >/dev/null + ok "SSH ed25519 密钥对生成:${KEY_PATH}" +else + info "密钥对已存在,跳过生成:${KEY_PATH}" +fi + +# 公钥加入 authorized_keys(允许持对应私钥的人以 deploy 用户 ssh 登录) +AUTH_KEYS="${SSH_DIR}/authorized_keys" +touch "${AUTH_KEYS}" +PUBKEY_CONTENT="$(cat "${KEY_PATH}.pub")" +if ! grep -qF "${PUBKEY_CONTENT}" "${AUTH_KEYS}" 2>/dev/null; then + echo "${PUBKEY_CONTENT}" >> "${AUTH_KEYS}" + ok "公钥已写入 authorized_keys" +fi +chmod 600 "${AUTH_KEYS}" +chmod 644 "${KEY_PATH}.pub" +chmod 600 "${KEY_PATH}" +chown -R "${DEPLOY_USER}:${DEPLOY_USER}" "${SSH_DIR}" + +# -------- 4. sshd 加固(只允许公钥登录;别让 root 密码登录;deploy 禁止转发)-------- +SSHD_SNIPPET="/etc/ssh/sshd_config.d/99-deploy-user.conf" +if [[ ! -f "${SSHD_SNIPPET}" ]]; then + mkdir -p /etc/ssh/sshd_config.d + cat > "${SSHD_SNIPPET}" </dev/null || service ssh reload || true + ok "sshd 配置已加固(deploy 只允许公钥登录,并禁用端口转发)" +fi + +# -------- 5. 创建部署目录并给 deploy 权限 -------- +mkdir -p "${DEPLOY_DIR}" +# 既设组权限,又加 ACL,确保后续新建文件也有 +setfacl -R -m "u:${DEPLOY_USER}:rwX" "${DEPLOY_DIR}" +setfacl -R -d -m "u:${DEPLOY_USER}:rwX" "${DEPLOY_DIR}" +ok "部署目录 ${DEPLOY_DIR} 已给 ${DEPLOY_USER} 完整读写权限(+ 新建文件继承权限)" + +# 如果目录里有 compose/环境文件,顺便改一下属组,避免「root 建的文件 deploy 改不了」 +chgrp -R docker "${DEPLOY_DIR}" 2>/dev/null || true +chmod -R g+rwX "${DEPLOY_DIR}" 2>/dev/null || true + +# -------- 6. 验证 docker 是否真能用(不报错)-------- +DOCKER_RC=0 +sudo -u "${DEPLOY_USER}" docker ps >/dev/null 2>&1 || DOCKER_RC=$? +if [[ ${DOCKER_RC} -eq 0 ]]; then + ok "✅ ${DEPLOY_USER} 可以执行 docker 命令" +else + warn "⚠️ ${DEPLOY_USER} 暂时跑不了 docker(可能需要重新登录一次刷新组),部署时不会有影响(但验证时会再触发一次)" +fi + +# -------- 7. 打印信息 -------- +FINGERPRINT="$(ssh-keygen -l -f "${KEY_PATH}.pub" 2>/dev/null || true)" +PUBKEY_CONTENT="$(cat "${KEY_PATH}.pub")" +PRIVATE_CONTENT="$(cat "${KEY_PATH}")" + +clear +echo -e "${GREEN}================================================================================${NC}" +echo -e "${GREEN} 🎉 部署用户 & SSH 密钥 配置完成!${NC}" +echo -e "${GREEN}================================================================================${NC}" +echo -e " 👤 部署用户名 :${CYAN}${DEPLOY_USER}${NC}" +echo -e " 📂 部署目录 :${CYAN}${DEPLOY_DIR}${NC}" +echo -e " 🔑 私钥文件路径 :${CYAN}${KEY_PATH}${NC}" +echo -e " 🧾 公钥指纹 :${FINGERPRINT}" +echo -e " 🧾 公钥内容(方便核对):" +echo -e " ${PUBKEY_CONTENT}" +echo "" +echo -e "${YELLOW}================================================================================${NC}" +echo -e "${YELLOW} ⚠️ 下面这一整段是【私钥】,完整复制(从 BEGIN 到 END 包括两行)到:${NC}" +echo -e "${YELLOW} Gitea 仓库 → Settings → Secrets and variables → Actions → New Secret${NC}" +echo -e "${YELLOW} Name = DEPLOY_SSH_KEY${NC}" +echo -e "${YELLOW} Value = 粘贴下面一整段(包括 -----BEGIN/END-----)${NC}" +echo -e "${YELLOW}================================================================================${NC}" +echo "" +echo "${PRIVATE_CONTENT}" +echo "" +echo -e "${YELLOW}================================================================================${NC}" +echo -e "${YELLOW} ⚠️ 上面就是 DEPLOY_SSH_KEY,不要在任何地方贴到别的文档或聊天。看完 Ctrl+L 清屏。${NC}" +echo -e "${YELLOW}================================================================================${NC}" +echo "" +echo -e "${CYAN}👉 同时你还需要补以下几个 Repository Secrets(都在同一页添加):${NC}" +echo -e " • DEPLOY_HOST = 你的服务器公网 IP 或域名" +echo -e " • DEPLOY_PORT = SSH 端口(一般 22)" +echo -e " • DEPLOY_USER = ${DEPLOY_USER}(就是这个用户名)" +echo -e " • REGISTRY_TOKEN = 你的 Gitea 个人访问令牌(Package 读写权限)" +echo -e " • REGISTRY_PULL_USER = 同 REGISTRY_USER(服务器上拉镜像用,一般和 REGISTRY_USER 一样)" +echo -e " • REGISTRY_PULL_TOKEN= 上面令牌同一个或新建一个仅 pull 权限的更安全" +echo -e " • NUXT_PUBLIC_SITE_URL = https://你的域名.com(用来显示部署成功后的跳转按钮)" +echo "" diff --git a/scripts/setup-runner.sh b/scripts/setup-runner.sh new file mode 100644 index 0000000..bc424e7 --- /dev/null +++ b/scripts/setup-runner.sh @@ -0,0 +1,211 @@ +#!/usr/bin/env bash +# ============================================================================ +# setup-runner.sh —— 一键安装并注册 Gitea Actions Runner(act_runner) +# +# 使用方法: +# ① 先在你的 Gitea 仓库页面 → Settings → Actions → Runners → 「Create Runner」 +# 选「Linux + amd64」,它会给你一条注册命令,里面有一串 registration token, +# 格式像: GFxxxxxxxxxxxxxxxxxxxx ,复制它。 +# ② 再在你要跑 Runner 的机器上执行: +# curl -fsSL https://<你的-gitea>/<仓库>/-/raw/branch/main/scripts/setup-runner.sh \ +# | sudo bash -s -- --gitea https://<你的-gitea> --token GFxxxxxxxxxxxxxxxxxxxx +# +# 或者直接本地执行: +# sudo bash scripts/setup-runner.sh \ +# --gitea https://git.baoyiwai.com \ +# --token GFxxxxxxxxxxxxxxxxxxxxxxxxxxxx +# ============================================================================ +set -euo pipefail + +# ---------- 颜色 ---------- +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +err() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; } + +# ---------- 解析参数 ---------- +GITEA_URL="" +REG_TOKEN="" +RUNNER_NAME="runner-$(hostname | tr '[:upper:]' '[:lower:]')" +RUNNER_USER="gitrunner" +INSTALL_DIR="/opt/act_runner" + +while [[ $# -gt 0 ]]; do + case "$1" in + --gitea) GITEA_URL="${2}"; shift 2 ;; + --token) REG_TOKEN="${2}"; shift 2 ;; + --name) RUNNER_NAME="${2}"; shift 2 ;; + --user) RUNNER_USER="${2}"; shift 2 ;; + --dir) INSTALL_DIR="${2}"; shift 2 ;; + -h|--help) + sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//' + exit 0 + ;; + *) err "未知参数:$1(--help 查看帮助)" ;; + esac +done + +[[ -n "${GITEA_URL}" ]] || err "必须传 --gitea https://你的-gitea-地址" +[[ -n "${REG_TOKEN}" ]] || err "必须传 --token GF……(Gitea 页面里拿的注册 token)" +[[ $EUID -eq 0 ]] || err "请用 sudo 运行本脚本(需要写 /opt / /etc/systemd/system)" + +# 去掉末尾 / +GITEA_URL="${GITEA_URL%/}" + +# ---------- 0. 架构检测 ---------- +ARCH="$(uname -m)" +case "${ARCH}" in + x86_64|amd64) PLATFORM="linux-amd64" ;; + aarch64|arm64) PLATFORM="linux-arm64" ;; + armv7l) PLATFORM="linux-arm" ;; + *) err "暂不支持的架构:${ARCH}(需要 x86_64 / aarch64 / armv7)" ;; +esac +info "检测到架构:${ARCH} → 下载版本:${PLATFORM}" + +# ---------- 1. 安装依赖:docker(Runner 要用 Docker-in-Docker 执行 buildx 等)---------- +if ! command -v docker >/dev/null 2>&1; then + info "检测到没装 Docker,现在用官方一键脚本安装(国内自动换清华源)..." + curl -fsSL https://get.docker.com -o /tmp/get-docker.sh || err "下载 docker-install 脚本失败" + if curl -sI https://mirrors.tuna.tsinghua.edu.cn >/dev/null; then + bash /tmp/get-docker.sh --mirror Aliyun || bash /tmp/get-docker.sh + else + bash /tmp/get-docker.sh + fi + systemctl enable --now docker + info "✅ Docker 安装完成:$(docker --version)" +else + info "✅ Docker 已存在:$(docker --version)" +fi + +# ---------- 2. 建运行用户 gitrunner,加入 docker 组 ---------- +if ! id -u "${RUNNER_USER}" >/dev/null 2>&1; then + useradd -r -s /usr/sbin/nologin -d "${INSTALL_DIR}" "${RUNNER_USER}" + info "✅ 新建系统用户 ${RUNNER_USER}" +else + info "ℹ️ 用户 ${RUNNER_USER} 已存在,跳过创建" +fi +usermod -aG docker "${RUNNER_USER}" +info "✅ 把 ${RUNNER_USER} 加入 docker 组(允许操作 Docker daemon)" + +mkdir -p "${INSTALL_DIR}" +chown -R "${RUNNER_USER}:${RUNNER_USER}" "${INSTALL_DIR}" + +# ---------- 3. 下载最新 act_runner ---------- +info "⬇️ 正在拉取 act_runner 最新 release 元数据..." +LATEST_JSON="$(curl -fsSL https://gitea.com/api/v1/repos/gitea/act_runner/releases/latest || true)" +if [[ -z "${LATEST_JSON}" ]]; then + warn "gitea.com 不通,改用硬编码版本 0.2.10(你之后可手动升级)" + VERSION="0.2.10" +else + VERSION="$(echo "${LATEST_JSON}" | grep -oE '"tag_name":\s*"[^"]+"' | head -1 | cut -d'"' -f4 | sed 's/^v//')" + [[ -n "${VERSION}" ]] || VERSION="0.2.10" +fi +BIN_URL="https://dl.gitea.com/act_runner/${VERSION}/act_runner-${VERSION}-${PLATFORM}" +info "⬇️ 下载 act_runner v${VERSION}:${BIN_URL}" +curl -fsSL "${BIN_URL}" -o /usr/local/bin/act_runner \ + || err "下载失败。请手动打开 ${BIN_URL} 看是否 404,然后换个版本" +chmod +x /usr/local/bin/act_runner +info "✅ act_runner 已安装到 /usr/local/bin,版本:$(act_runner --version 2>&1 || true)" + +# ---------- 4. 注册 Runner ---------- +cd "${INSTALL_DIR}" +info "🔗 开始注册 Runner 到 ${GITEA_URL}(名称:${RUNNER_NAME})" + +# 切到 gitrunner 用户注册,避免 root 跑(安全) +rm -f .runner 2>/dev/null || true +sudo -u "${RUNNER_USER}" env HOME="${INSTALL_DIR}" \ + act_runner register \ + --instance "${GITEA_URL}" \ + --token "${REG_TOKEN}" \ + --name "${RUNNER_NAME}" \ + --labels "ubuntu-latest:docker://catthehacker/ubuntu:act-22.04" \ + --no-interactive \ + || err "注册失败!常见原因:\n 1) token 不对(复制时别带空格)\n 2) Runner 机器出不了网到 ${GITEA_URL}\n 3) Gitea 站点时间和这台机器差太远(证书验证失败)" + +chown "${RUNNER_USER}:${RUNNER_USER}" .runner 2>/dev/null || true + +# ---------- 5. 生成配置文件(建议开启 docker.host 特权)---------- +CONFIG_FILE="${INSTALL_DIR}/config.yaml" +if [[ ! -f "${CONFIG_FILE}" ]]; then + cat > "${CONFIG_FILE}" <<'YAML' +log: + level: info +runner: + file: .runner + capacity: 2 # 同一台 Runner 最多同时跑 2 个 Job + envs: + GOPROXY: https://goproxy.cn,direct + NPM_CONFIG_REGISTRY: https://registry.npmmirror.com + timeout: 3h +cache: + enabled: true + dir: /opt/act_runner/cache +container: + network: "bridge" + privileged: true # build-push-action 用到 Docker daemon + options: | + --add-host "host.docker.internal:host-gateway" +YAML + chown "${RUNNER_USER}:${RUNNER_USER}" "${CONFIG_FILE}" + mkdir -p "${INSTALL_DIR}/cache" + chown -R "${RUNNER_USER}:${RUNNER_USER}" "${INSTALL_DIR}/cache" + info "✅ 默认 config 写入 ${CONFIG_FILE}" +fi + +# ---------- 6. 写 systemd 服务,开机自启 ---------- +UNIT_FILE="/etc/systemd/system/gitea-act-runner.service" +cat > "${UNIT_FILE}" <