Files
OJ2/docker/deploy.sh
yuetsh eb849ca5ab fix(镜像): apt 换清华源、去掉用不上的 ca-certificates;补部署脚本
## apt-get update 在服务器上卡死

不是慢,是挂住不返回 —— 本机构建从来没事,所以演练没暴露。换清华源。两个细节:

- trixie 的源是 deb822 格式,在 `/etc/apt/sources.list.d/debian.sources`,
  老的 `sources.list` 在这个基底里是**空文件**,改它没有任何效果。
- **只能换主机名、必须保持 http。** https 源会在这一步失败,因为镜像里还没有根证书。

`ARG APT_MIRROR` 可覆盖。

## ca-certificates 是多余的

原注释写「给 AI 接口的 https 出站用」,不成立:Bun 和 Node 一样内嵌了一份根证书,
走自己那份,不读系统的 /etc/ssl/certs。

实测:把探针二进制丢进裸 debian:trixie-slim(没有 ca-certificates)请求
api.deepseek.com,握手正常、返回 401(没带 key),和装了的镜像行为一致。
去掉之后重新构建,clang-format / ruff 都在,https 出站照旧,487MB → 483MB。

哪天镜像里加了用 OpenSSL 做 TLS 的东西(curl、wget 之类),这条要重新考虑,
注释里写了。

## docker/deploy.sh

没有 git remote 时的部署路径:本机 rsync 推代码 → 服务器上构建 → 起栈 → 冒烟。
默认不推 docker/.env(两边不是一回事,覆盖掉是静默故障),要同步显式 --env。

起栈前两道守卫,就是今天在服务器上真撞到的那两种失败:DATA_DIR 没生效(卷指向
OJ2/data)、DB_HOST 没生效(DATABASE_URL 还指着试跑形态下并不存在的 oj-postgres)。
两道守卫都用当天那份坏 env 正反跑过:齐全时放行,抹掉这两个变量时都触发。
两半的语法也都 `bash -n` 过(远端那半是 heredoc,单独渲染后再查的)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 09:30:50 -06:00

100 lines
4.4 KiB
Bash
Executable File
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.
#!/usr/bin/env bash
#
# 从本机把 OJ2 推到服务器并重启新栈(并行试跑形态)。
#
# docker/deploy.sh # 推代码 → 构建 → 重启 → 自检
# docker/deploy.sh --env # 连 docker/.env 一起推(默认不推,见下)
# SERVER=root@1.2.3.4 docker/deploy.sh # 换台机器
#
# 默认**不推 docker/.env**:本机那份和服务器那份很容易不是一回事(库地址、端口、
# AI key 都不同),覆盖掉是静默故障。确实要同步时显式加 --env。
#
# 这是没有 git remote 时的临时方案。配好 remote 之后应该换成服务器上 git pull
# 那样服务器上有版本记录,也不会把本机的脏改动带上去。
set -euo pipefail
SERVER=${SERVER:-root@xuyue.cc}
REMOTE_DIR=${REMOTE_DIR:-/root/OJDeploy/OJ2}
REPO=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
COMPOSE="docker compose -f docker/compose.debian.yml --env-file docker/.env"
PUSH_ENV=0
[ "${1:-}" = "--env" ] && PUSH_ENV=1
say() { printf '\n\033[1;36m==> %s\033[0m\n' "$*"; }
die() { printf '\n\033[1;31m❌ %s\033[0m\n' "$*" >&2; exit 1; }
# ---------------------------------------------------------------- 1. 推代码
say "1/4 同步代码 → $SERVER:$REMOTE_DIR"
excludes=(
--exclude node_modules
--exclude .git
--exclude data # 运行时数据,服务器上那份才是真的
--exclude 'apps/web/dist'
)
if [ "$PUSH_ENV" -eq 0 ]; then
excludes+=(--exclude 'docker/.env' --exclude 'docker/.env.*' --include 'docker/.env.example')
fi
# --delete 让服务器和本机一致(被 --exclude 排除的文件不会被删rsync 默认保护)
rsync -az --delete --info=stats1 "${excludes[@]}" "$REPO/" "$SERVER:$REMOTE_DIR/"
# ---------------------------------------------------------------- 2-4. 服务器上
ssh "$SERVER" bash -s <<REMOTE
set -euo pipefail
cd "$REMOTE_DIR"
say() { printf '\n\033[1;36m==> %s\033[0m\n' "\$*"; }
die() { printf '\n\033[1;31m❌ %s\033[0m\n' "\$*" >&2; exit 1; }
[ -f docker/.env ] || die "$REMOTE_DIR/docker/.env 不存在,先把 env 放上去(内容见手册第三节)"
say "2/4 起栈前自检"
# 这一条是切换当天唯一会静默走歪的地方DATA_DIR 没生效的话,卷会挂到 OJ2/data
# 这个空目录上 —— 站点起得来,但没有测试点、题面图片 404而且不报任何错。
bad=\$($COMPOSE config | grep 'source:' | grep 'OJ2/data' || true)
[ -z "\$bad" ] || die "DATA_DIR 没生效,这些卷指向了 OJ2/data\$(printf '\n%s' "\$bad")"
# 同理DB_HOST 空着会静默回落成 oj-postgres而试跑形态下那个容器根本没起
$COMPOSE config | grep -q 'DATABASE_URL: postgres://[^@]*@oj-postgres' \
&& die "DB_HOST 没生效DATABASE_URL 还指着 oj-postgres试跑形态下它不存在" || true
# 判题机运行目录必须和旧栈分开,不然两个 judger 往一个目录里写
judge_dir=\$(grep -E '^JUDGE_STATE_DIR=' docker/.env | cut -d= -f2- || true)
[ -n "\$judge_dir" ] || die "JUDGE_STATE_DIR 没设,会和旧判题机共用运行目录"
mkdir -p "\$judge_dir/log" "\$judge_dir/run"
say "3/4 构建镜像(首次约 5 分钟)"
$COMPOSE build
say "4/4 起栈"
$COMPOSE up -d
$COMPOSE ps
# 等 oj-api 变 healthy最多 60 秒
for i in \$(seq 30); do
status=\$(docker inspect -f '{{.State.Health.Status}}' oj-api 2>/dev/null || echo starting)
[ "\$status" = healthy ] && break
sleep 2
done
[ "\$status" = healthy ] || die "oj-api 没起来(状态 \$status看 docker logs oj-api --tail 50"
port=\$(grep -E '^WEB_PORT=' docker/.env | cut -d= -f2- || echo 8080)
port=\${port:-8080}
say "自检(端口 \$port"
printf '首页 %s\n' "\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:\$port/)"
printf '站点配置 %s\n' "\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:\$port/api/site)"
printf '未登录进后台 %s期望 401\n' "\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:\$port/api/admin/dashboard)"
total=\$(curl -s "http://localhost:\$port/api/problems" | grep -oE '"total":[0-9]+' | head -1 | cut -d: -f2)
printf '题目总数 %s\n' "\${total:-读不出来}"
[ "\${total:-0}" -gt 0 ] || die "题目数是 0 —— 连错库了,八成是 DB_HOST 或 POSTGRES_PASSWORD 不对"
printf '\n\033[1;32m✅ 完成。别忘了 NPM 那台 proxy host 指向 %s且 Websockets Support 是开的。\033[0m\n' "\$port"
REMOTE