Deploy / deploy (push) Canceled after 0s
按外部分析报告(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>
143 lines
7.0 KiB
YAML
143 lines
7.0 KiB
YAML
# 在 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"
|