chore(部署): 服务器的数据库和 Redis 改由 OJ2 自己起,不用再改 OJDeploy 下的 compose;附一次性搬家脚本
Deploy / deploy (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
compose.debian.yml 去掉外接形态,postgres / redis 固定由本栈起(PostgreSQL 18、Redis 8)。 docker/move-db-into-oj2.sh 把服务器上旧栈的 16 / 7 倒进来:先编镜像、确认机房已断开、 备份、恢复时剥掉 dump 里的 md5 口令(18 只认 SCRAM)、核对行数和网络登录,最后交给 deploy.sh。 生产库口令原来是默认的 onlinejudge(超级用户、5445 对公网开着),脚本要求换成强口令才执行。 本机按线上「之前」的状态完整演练过:停机到恢复 36 秒,行数一致,登录态保住,提交照常判题。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fad43f513f
commit
625bd49801
7 files changed
+350
-239
No files matched your search
+28
-86
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# 在**服务器上**起 OJ2 新栈(并行试跑形态:旧栈的 postgres/redis 继续跑,
|
||||
# 这里只起 api / worker / web / judge,旧站一个容器都不用停)。
|
||||
# 在**服务器上**起整套 OJ2:postgres / redis / 判题沙箱 / api / worker / web,
|
||||
# 全部由 docker/compose.debian.yml 管。
|
||||
#
|
||||
# cd /root/OJDeploy/OJ2
|
||||
# docker/deploy.sh # 自检 → 构建 → 起栈 → 冒烟
|
||||
@@ -61,28 +61,12 @@ say "自检"
|
||||
|
||||
[ -f docker/.env ] || die "docker/.env 不存在。内容见手册第三节,别用 .env.example 直接改名(里面的值全是空的)"
|
||||
|
||||
# 部署形态。判据用 DB_HOST 而不是另加一个开关:它本来就决定了 DATABASE_URL 指向谁,
|
||||
# 两个变量各说各话的话,迟早会出现「起了自带的库、却连到别处」这种自相矛盾的状态。
|
||||
#
|
||||
# DB_HOST 为空 → 自带数据:本栈起 oj-postgres / oj-redis(compose 里挂在 local-data profile 下)
|
||||
# DB_HOST 非空 → 外接数据:连别处的库,试跑期间连的是旧栈那两个容器
|
||||
db_host=$(grep -E '^DB_HOST=' docker/.env | tail -1 | cut -d= -f2- || true)
|
||||
if [ -z "$db_host" ]; then
|
||||
LOCAL_DATA=1
|
||||
COMPOSE+=(--profile local-data)
|
||||
ok "形态:自带数据(postgres / redis 由本栈起)"
|
||||
else
|
||||
LOCAL_DATA=0
|
||||
ok "形态:外接数据(库在 $db_host)"
|
||||
fi
|
||||
|
||||
# compose 版本:depends_on.required 是 v2.20 才有的字段,老版本会解析失败
|
||||
ver=$(docker compose version --short 2>/dev/null || echo 0)
|
||||
# 取 min(2.20, ver):等于 2.20 就说明 ver 不低于它。别用 `sort -V -C`,
|
||||
# 那个判的是「这两行本来就有序」,对 ver 更新的情况会反过来判成太老(写错过一次)。
|
||||
[ "$(printf '2.20\n%s\n' "$ver" | sort -V | head -1)" = "2.20" ] \
|
||||
|| die "docker compose 版本太老($ver),需要 ≥ 2.20(depends_on.required 是那个版本才有的字段)"
|
||||
ok "docker compose $ver"
|
||||
# 以前库和 redis 是外接旧栈的,靠这几个变量指过去;现在 compose 不再读它们。
|
||||
# 留在 .env 里不碍事,但会让人以为还在生效
|
||||
for v in DB_HOST DB_PORT REDIS_HOST REDIS_PORT; do
|
||||
[ -z "$(grep -E "^$v=." docker/.env || true)" ] \
|
||||
|| warn "docker/.env 里的 $v 已经不用了(库和 redis 由本栈起),可以删掉"
|
||||
done
|
||||
|
||||
# --prebuilt 时产物就是构建输入。缺了的话 docker 只会甩一句 COPY 找不到文件,
|
||||
# 这里提前拦下并说清楚该怎么办。
|
||||
@@ -118,38 +102,22 @@ $bad
|
||||
检查 docker/.env 里的 DATA_DIR(注意:DATA_DIR= 空值等于没设,等号两边不能有空格)"
|
||||
ok "数据卷都在 $(grep 'source:' <<<"$cfg" | head -1 | sed 's|.*source: ||; s|/backend.*||')"
|
||||
|
||||
# ② 库指向必须和形态一致,两个方向都要查
|
||||
if [ "$LOCAL_DATA" -eq 0 ]; then
|
||||
# 外接:DB_HOST 不生效会静默回落成 oj-postgres,而这形态下本栈不起那个容器
|
||||
! grep -q 'DATABASE_URL: postgres://[^@]*@oj-postgres' <<<"$cfg" \
|
||||
|| die "DB_HOST 没生效,DATABASE_URL 还指着 oj-postgres —— 外接形态下本栈不起它,必然连不上库"
|
||||
else
|
||||
# 自带:反过来必须指向服务名 + 容器内端口。连 :5432 一起查,是因为清了 DB_HOST 却
|
||||
# 忘了清 DB_PORT 的话会拼出 oj-postgres:5445 —— 那是宿主机映射端口,容器网络里不通。
|
||||
grep -q 'DATABASE_URL: postgres://[^@]*@oj-postgres:5432/' <<<"$cfg" \
|
||||
|| die "自带数据形态(DB_HOST 为空),但 DATABASE_URL 指向 $(grep -m1 'DATABASE_URL:' <<<"$cfg" | sed 's|.*@||; s|/onlinejudge.*||')。
|
||||
docker/.env 里 DB_PORT / REDIS_PORT 是不是也得一并清空?"
|
||||
fi
|
||||
ok "库指向 $(grep -m1 'DATABASE_URL:' <<<"$cfg" | sed 's|.*@||; s|/onlinejudge.*||')"
|
||||
|
||||
# ②b 自带形态下数据目录必须已经是一个 18 版本的库。
|
||||
# 这是整个部署里唯一会**静默**走歪的地方:目录不对的话 postgres 当成全新部署,
|
||||
# 在空目录上初始化一个空库 —— 站点起得来、能注册能登录,就是一道题都没有。
|
||||
if [ "$LOCAL_DATA" -eq 1 ]; then
|
||||
data_dir=$(grep -E '^DATA_DIR=' docker/.env | tail -1 | cut -d= -f2- || true)
|
||||
[ -n "$data_dir" ] || die "自带数据形态必须显式设 DATA_DIR。
|
||||
# ② 数据目录必须已经是一个 18 版本的库。
|
||||
# 这是整个部署里唯一会**静默**走歪的地方:目录不对的话 postgres 当成全新部署,
|
||||
# 在空目录上初始化一个空库 —— 站点起得来、能注册能登录,就是一道题都没有。
|
||||
data_dir=$(grep -E '^DATA_DIR=' docker/.env | tail -1 | cut -d= -f2- || true)
|
||||
[ -n "$data_dir" ] || die "必须显式设 DATA_DIR。
|
||||
默认值 ../data 解析出来是 OJ2/data,那是个空目录,postgres 会在上面初始化一个全新的空库,而且不报任何错。"
|
||||
pgver_file="$data_dir/postgresql/18/docker/PG_VERSION"
|
||||
if [ ! -f "$pgver_file" ] && [ -f "$data_dir/postgres/PG_VERSION" ]; then
|
||||
die "$data_dir 下只有 postgres/(PostgreSQL $(cat "$data_dir/postgres/PG_VERSION") 的老库),没有 postgresql/18/docker。
|
||||
compose 里已经是 postgres:18-alpine,老库要先倒过去,见 docs/deploy.md「PostgreSQL 升级」。"
|
||||
fi
|
||||
[ -f "$pgver_file" ] || die "找不到 $pgver_file —— DATA_DIR 指的不是一个已有的 postgres 数据目录。
|
||||
就这么起下去会得到一个空库。确认 DATA_DIR=$data_dir 对不对。"
|
||||
pgver=$(cat "$pgver_file")
|
||||
[ "$pgver" = 18 ] || die "$data_dir/postgresql 是 PostgreSQL $pgver 的数据目录,但 compose 里是 postgres:18-alpine,起不来。"
|
||||
ok "数据目录 $data_dir/postgresql(PostgreSQL $pgver)"
|
||||
pgver_file="$data_dir/postgresql/18/docker/PG_VERSION"
|
||||
if [ ! -e "$data_dir/postgresql" ] && [ -d "$data_dir/postgres" ]; then
|
||||
die "$data_dir 下只有 postgres/(PostgreSQL 16 的老库,旧栈 compose 起的),
|
||||
还没有 postgresql/18/docker。先把库搬进本栈:docker/move-db-into-oj2.sh(一次性的,见 docs/deploy.md)"
|
||||
fi
|
||||
[ -f "$pgver_file" ] || die "找不到 $pgver_file —— DATA_DIR 指的不是一个已有的 postgres 数据目录。
|
||||
就这么起下去会得到一个空库。确认 DATA_DIR=$data_dir 对不对。"
|
||||
pgver=$(cat "$pgver_file")
|
||||
[ "$pgver" = 18 ] || die "$data_dir/postgresql 是 PostgreSQL $pgver 的数据目录,但 compose 里是 postgres:18-alpine,起不来。"
|
||||
ok "数据目录 $data_dir/postgresql(PostgreSQL $pgver)"
|
||||
|
||||
# ③ 判题机运行目录必须和旧栈分开,否则两个 judger 往同一个目录里写
|
||||
judge_dir=$(grep -E '^JUDGE_STATE_DIR=' docker/.env | tail -1 | cut -d= -f2- || true)
|
||||
@@ -171,29 +139,6 @@ if [ -n "$judge_image" ]; then
|
||||
ok "判题镜像 $judge_image 在本机"
|
||||
fi
|
||||
|
||||
# ⑤ 外接形态才需要预检:库不归本栈管,得确认它已经活着。
|
||||
# 自带形态下这两个容器就是本栈自己起的,起栈那步会拉起来,这里没什么可查。
|
||||
if [ "$LOCAL_DATA" -eq 0 ]; then
|
||||
for c in oj-postgres oj-redis; do
|
||||
docker ps --filter "name=$c" --filter status=running -q | grep -q . \
|
||||
|| die "外接形态依赖的 $c 没在跑 —— 先 docker compose -f /root/OJDeploy/docker-compose.yml up -d $c"
|
||||
done
|
||||
ok "外接的 postgres / redis 都在跑"
|
||||
|
||||
# 外接的 redis 不归本栈管,口令是否真的生效只能在这里查。旧栈那份 compose 的 redis
|
||||
# 原本没有口令、端口发布在公网上 —— 会话存在里面,等于人人可以伪造超管登录态。
|
||||
# 不带口令能 PING 通就说明那边还没加 --requirepass,这种状态不许上线。
|
||||
redis_pw=$(grep -E '^REDIS_PASSWORD=' docker/.env | tail -1 | cut -d= -f2- || true)
|
||||
[ -n "$redis_pw" ] || die "docker/.env 里没有 REDIS_PASSWORD(openssl rand -hex 32 生成)"
|
||||
[ "$(docker exec -e REDISCLI_AUTH= oj-redis redis-cli ping 2>/dev/null)" != PONG ] \
|
||||
|| die "外接的 oj-redis 不带口令也能连上 —— 先给它加 --requirepass,见 docs/deploy.md「Redis 口令」"
|
||||
[ "$(docker exec -e REDISCLI_AUTH="$redis_pw" oj-redis redis-cli ping 2>/dev/null)" = PONG ] \
|
||||
|| die "用 docker/.env 的 REDIS_PASSWORD 连不上外接的 oj-redis —— 两边口令不一致"
|
||||
ok "外接的 redis 要求口令,且和 docker/.env 一致"
|
||||
else
|
||||
ok "postgres / redis 由本栈起,不预检"
|
||||
fi
|
||||
|
||||
port=$(grep -E '^WEB_PORT=' docker/.env | tail -1 | cut -d= -f2- || true)
|
||||
port=${port:-8080}
|
||||
ok "对外端口 $port"
|
||||
@@ -212,12 +157,9 @@ fi
|
||||
# 在起栈**之前**跑:schema 先就位,新代码再启动。
|
||||
#
|
||||
# **不要加 --no-deps。** `run` 只会拉起被点名服务的依赖,而 oj-api 的依赖恰好就是
|
||||
# oj-postgres / oj-redis:
|
||||
# · 自带数据形态 —— 必须靠它把 postgres 起来,否则这一步连不上库(加过 --no-deps,
|
||||
# 踩过这个坑)。depends_on 的 condition: service_healthy 还顺带保证了库真的就绪。
|
||||
# · 外接形态 —— 那两个服务不在启用的 profile 里,depends_on 上的 required: false
|
||||
# 让 compose 直接跳过,不会多起任何东西。
|
||||
# 两种形态下 worker / web 都不会被带起来,它们不是 api 的依赖。
|
||||
# oj-postgres / oj-redis:必须靠它把 postgres 起来,否则这一步连不上库(加过 --no-deps,
|
||||
# 踩过这个坑)。depends_on 的 condition: service_healthy 还顺带保证了库真的就绪。
|
||||
# worker / web 不会被带起来,它们不是 api 的依赖。
|
||||
#
|
||||
# 含 DROP TABLE 这类破坏性语句的迁移会被拦下并退出 4,需要显式放行:
|
||||
# OJ2_ALLOW_DESTRUCTIVE=1 docker/deploy.sh
|
||||
@@ -230,7 +172,7 @@ say "数据库迁移"
|
||||
它对每种情况都打印了具体该做什么,别照搬下面这几条猜。常见的三种:
|
||||
· 破坏性语句被拦 → 确认备份后 OJ2_ALLOW_DESTRUCTIVE=1 docker/deploy.sh
|
||||
· 缺迁移基线记录 → 按它打印的 SQL 插一行
|
||||
· 连不上库 → 自带数据形态下 postgres 是否起来了、DATA_DIR 对不对"
|
||||
· 连不上库 → oj-postgres 是否起来了(docker logs oj-postgres)、DATA_DIR 对不对"
|
||||
|
||||
say "起栈"
|
||||
"${COMPOSE[@]}" up -d
|
||||
@@ -254,13 +196,13 @@ printf ' 未登录进后台 %s (期望 401)\n' "$(code /api/admin/dashboard
|
||||
|
||||
total=$(curl -s "http://localhost:$port/api/problems" | grep -oE '"total":[0-9]+' | head -1 | cut -d: -f2 || true)
|
||||
printf ' 题目总数 %s\n' "${total:-读不出来}"
|
||||
[ "${total:-0}" -gt 0 ] || die "题目数是 0 —— 连的不是生产库。查 DB_HOST / POSTGRES_PASSWORD"
|
||||
[ "${total:-0}" -gt 0 ] || die "题目数是 0 —— 连的不是生产库。查 DATA_DIR"
|
||||
|
||||
cat <<EOF
|
||||
|
||||
$(printf '\033[1;32m✅ 起来了\033[0m')
|
||||
|
||||
https://oj2.xuyue.cc (NPM 反代已配好,指向宿主机:$port)
|
||||
https://oj.xuyue.cc (NPM 反代已配好,指向宿主机:$port)
|
||||
|
||||
NPM 那边是一次性的,只有改了 WEB_PORT 才要回去动 proxy host 的端口。另外两项
|
||||
别关:Websockets Support(关了学生那边「判题中…」永远不动)、
|
||||
|
||||
Reference in new issue
Block a user