Files
OJ2/.github/workflows/deploy.yml
T
xuyueandClaude Opus 5.5 b046af4ba0
Deploy / deploy (push) Canceled after 0s
chore(部署): push 后先过检查再部署;判题机重启那几秒交的题不再变成系统错误;worker 挂了看得出来
按外部分析报告(reports/analysis)挑出来做的几条:
- CI 在编完前端之后跑 `bunx vp run verify`,有一项不过就不部署(必须排在编前端之后:
  前端类型检查要的 auto-imports.d.ts / components.d.ts 是 vite build 时才生成的)
- 连不上判题机(连接被拒、解析不到、被重置)时,正式判题等 2/5/10 秒重试,不再当场记成
  系统错误;超时、判题机回错、页面上的运行和自测不重试
- worker 每 10 秒刷一次「还活着」,compose 加 `oj2-api healthcheck --worker`,deploy.sh 起栈后
  也等 worker 健康(docker 不会因 unhealthy 自动重启,只是看得见、部署时拦得住)
- 三套 compose 的容器日志限 10MB × 3
- `/public/%` 这类畸形编码回 404 而不是 500;找不到的接口回统一的 JSON 错误
- 删掉没人用的「停用判题机」接口,契约不再给 task_number / is_disabled(列留在库里)
- 删掉腐化的 apps/web/tests/submitButtonState.test.ts;契约包 zod 范围对齐到 ^4.6.6
- 修几处和实际对不上的注释与文档(表数、编译并发、AST 语言数、deploy.sh 流程、校验处数)

⚠️ 改了 compose 的日志配置,这次部署会把 postgres / redis / 判题机也重建一遍,各停几秒

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:59:19 +08:00

143 lines
7.0 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.
# 在 runner 上编好产物 → rsync 到服务器 → 服务器上跑 docker/deploy.sh 起栈。
#
# 为什么产物在这里编:服务器性能差,镜像 builder 阶段(1238 个包的 bun install +
# bun compile + vite build)首次约 5 分钟,光前端就 160s。runner 编完传过去,
# 服务器那边的镜像构建只剩几条 COPY。
#
# 服务器自己编的能力**没有砍掉**:docker/deploy.sh 不带 --prebuilt 就是原来的
# 行为(ARTIFACTS=build),只要机器上有 docker 就能手动部署,不依赖这个工作流。
#
# 触发的是 push 到 **github** 这个 remote:
#
# git push github main
#
# origin(git.xuyue.cc)是 Gitea,仓库里没有 .gitea/workflows 时它的 Actions 会退回来
# 读这份 .github/workflows,每次 push origin 都会把部署再跑一遍 —— 那边不需要。
# 所以下面 job 上卡了 server_url:只在 GitHub 上跑,Gitea 上直接跳过。
#
# 仓库 Settings → Secrets 需要两个:
# HOST 服务器地址
# KEY 能以 root 登录该服务器的私钥(完整 PEM,含首尾 BEGIN/END 行)
name: Deploy
on:
push:
branches:
- main
# 手动触发时可以放行破坏性迁移。push 触发拿不到这个 input,值恒为空 ——
# 也就是说**自动部署永远不会执行 DROP TABLE 之类的迁移**,只会停在闸门上把
# 工作流判红。这是有意的:那种改动得有人先确认备份。
workflow_dispatch:
inputs:
allow_destructive:
description: "放行破坏性迁移(DROP TABLE / DROP COLUMN / ALTER TYPE …)。勾之前先确认已备份数据库。"
type: boolean
default: false
permissions:
contents: read
# 两次 push 挨得近时,别让两个 deploy.sh 在服务器上同时起栈。
concurrency:
group: deploy
cancel-in-progress: false
env:
REMOTE_PORT: 22
REMOTE_DIR: /root/OJDeploy/OJ2
jobs:
deploy:
if: github.server_url == 'https://github.com'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
# 版本跟着 package.json 的 packageManager 走。单二进制会把运行时一起打进去,
# 这里装的是哪个版本,线上跑的就是哪个版本 —— 不能是 latest。
- uses: oven-sh/setup-bun@v2
with:
bun-version-file: package.json
# 依赖装到 ~/.bun/install/cache,命中的话 install 只剩几秒。
# v6 起跑在 node24 上(v4 还是 node20,GitHub 正在退役 node20 runtime),
# 和上面两个 action 一致。v5+ 要求 runner >= 2.327.1,ubuntu-latest 是
# GitHub 托管的、始终满足;换自建 runner 的话要留意这条。
- uses: actions/cache@v6
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: bun-${{ runner.os }}-
- name: 装依赖
run: bun install --frozen-lockfile
# 单二进制,--target=bun-linux-x64(runner 就是 linux-x64,和服务器一致)。
# 输出到 dist/oj2-api,正是 Dockerfile 里 artifacts-prebuilt 阶段要的位置。
- name: 编后端
run: bun run --filter '@oj2/api' build
# 默认 production 模式,读 apps/web/.env.production(在版本库里)。
- name: 编前端
run: cd apps/web && bun run build
# 格式、lint、前后端类型检查、路由遮蔽、AST 节点,有一项不过就不部署(同步到服务器之前)。
# 不加它的话,每次 push 都是直接上线、中间没有任何检查 —— vite build 不做类型检查。
#
# 必须排在「编前端」之后:前端类型检查要 unplugin 生成的 auto-imports.d.ts /
# components.d.ts,它们不进版本库,vite build 时才生成;排在前面会报几百条
# Cannot find name 'computed'(实测过)。
- name: 检查
run: bunx vp run verify
- name: 配置 SSH
run: |
mkdir -p ~/.ssh && chmod 700 ~/.ssh
printf '%s\n' "${{ secrets.KEY }}" > ~/.ssh/id_deploy
chmod 600 ~/.ssh/id_deploy
ssh-keyscan -p "$REMOTE_PORT" -H "${{ secrets.HOST }}" >> ~/.ssh/known_hosts 2>/dev/null
# 源码和产物一起传。dist/oj2-api 有 77MB,但 rsync 对已存在的文件做增量,
# 重编后大部分(bun 运行时那段)字节不变,实际传输量远小于这个数。
#
# --delete 的排除项少一个就会出事,逐条说明:
# docker/.env 服务器上唯一一份,不在版本库里(.gitignore 排掉了 .env*)。
# 删了下一步 deploy.sh 自检就直接 die。
# data/ 测试点、题面图片、学生上传的文件,还有「自带数据」形态下的
# 整个 postgres 数据目录。删了就是删库。
# node_modules 服务器上根本不需要(prebuilt 形态下连编都不编),传过去纯浪费。
# .git checkout 出来的 .git 没有任何用处。
#
# --no-owner --no-group 别删:`-a` 展开含 `-o -g`(保留属主/属组),而接收端是
# root,rsync 会把**源端的 uid 真写上去**——GitHub 托管 runner 的 runner 用户是
# uid 1001,于是服务器上的工作区目录变成 1001 所有。而 `.git` 被 --exclude 掉,
# 还是当初手动 clone 时的 root。两者对不上,之后在服务器上跑任何 git 命令都会被
# 「检测到可疑的仓库所有权」挡下(git 2.35.2 起的 safe.directory 检查)。
- name: 同步到服务器
run: |
rsync -az --no-owner --no-group --delete \
-e "ssh -i ~/.ssh/id_deploy -p $REMOTE_PORT" \
--exclude node_modules \
--exclude .git \
--exclude data \
--exclude 'docker/.env*' \
./ "root@${{ secrets.HOST }}:$REMOTE_DIR/"
# deploy.sh 自检不过 / 迁移被拦 / 容器没起来 / 冒烟发现题目数是 0,都会非零退出,
# 这一步跟着红,日志里能看到具体是哪一条。
#
# 迁移在 deploy.sh 里排在起栈之前:非破坏性的(加索引、加列)自动执行;
# 破坏性的会被拦下并退出 4,除非手动触发时勾了 allow_destructive。
# 走 env: 传而不是把 ${{ }} 直接拼进命令串 —— 值虽然是 GitHub 约束过的布尔,
# 但拼字符串进 shell 的写法不该在这个仓库里留下先例。
#
# ⚠️ 必须用 `inputs.`,**不能换成 `github.event.inputs.`**。后者把布尔
# 字符串化,没勾选时拿到的是字符串 "false" —— 那是个非空字符串,在
# `&&` 里为真,等于每次手动部署都无条件放行破坏性迁移。
- name: 起栈
env:
ALLOW_DESTRUCTIVE: ${{ inputs.allow_destructive && '1' || '' }}
run: |
ssh -i ~/.ssh/id_deploy -p "$REMOTE_PORT" "root@${{ secrets.HOST }}" \
"cd $REMOTE_DIR && OJ2_ALLOW_DESTRUCTIVE='$ALLOW_DESTRUCTIVE' docker/deploy.sh --prebuilt"