会话存在 Redis 里(session:<token> → 用户 id),能连上它就能伪造任意用户包括超管的
登录态。旧栈起的 oj-redis 发布在 0.0.0.0:5446、不带口令,2026-09-27 从公网实测
PING 得通。
- compose.debian.yml:REDIS_URL 用 ${REDIS_PASSWORD:?} 带上口令,缺了直接报错;
自带数据形态的 oj-redis 加 --requirepass,端口只发布在 127.0.0.1
- deploy.sh:外接形态下实测「不带口令连不上、带 docker/.env 的口令连得上」,
任一条不满足就中止部署
- clear-sessions.sh:从 docker/.env 读口令走 REDISCLI_AUTH;口令为空时不带 -e
(空的 REDISCLI_AUTH 会发一条 AUTH "",实测会报一行 AUTH failed)
- compose.dev.yml:三个端口只绑 127.0.0.1
- docs/deploy.md 加「Redis 口令」一节:给旧栈 redis 加口令的一次性步骤
旧栈那份 compose 不在本仓库,线上 redis 的口令要照手册在服务器上手工加;
加之前 deploy.sh 会一直拦着。机房的 redis 不发布端口,不需要口令。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
4.3 KiB
Bash
Executable File
96 lines
4.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
#
|
||
# 一次性运维动作:清掉所有会话,强制全员重新登录。
|
||
#
|
||
# ## 为什么要跑
|
||
#
|
||
# 会话的反向索引 `user-sessions:<uid>` 是 498fc1c 才加的。在那之前签发的会话不在索引
|
||
# 里,revokeUserSessions 靠 SMEMBERS 找不到它们 —— 也就是说**改密码、重置密码、禁用
|
||
# 账号对这批会话统统无效**,只能等最长一个 SESSION_TTL_SECONDS(默认 7 天)自然过期。
|
||
#
|
||
# 学生密码是明文存着给老师查的,改密码正是密码泄露之后唯一的补救手段,这个空窗不能留。
|
||
# 代价只是所有人重新登录一次。
|
||
#
|
||
# 跑过一次就不用再跑了:此后签发的会话都带索引。
|
||
#
|
||
# ## 两个站点都要跑
|
||
#
|
||
# 机房和服务器**共用一个数据库,但各有各的 Redis**(见 compose.school.yml 头部)。
|
||
# 会话存在各自的 Redis 里,只清一边等于只解决一半。
|
||
#
|
||
# ## 为什么不是 FLUSHALL
|
||
#
|
||
# 同一个 Redis 里还装着 BullMQ 的判题队列(`bull:*`)。FLUSHALL 会把还在队列里的提交
|
||
# 一起丢掉,那些 submission 会永远停在 PENDING,只能超管逐条重判 —— 而重判还会把
|
||
# user_profile 的反范式计数带偏(见 apps/api/src/scripts/recount.ts)。
|
||
# 这里只删 `session:*` 和 `user-sessions:*`。
|
||
#
|
||
# ## 用法
|
||
#
|
||
# docker/clear-sessions.sh # 容器名默认 oj-redis
|
||
# CONTAINER=oj2-redis docker/clear-sessions.sh # 本机 dev
|
||
# YES=1 docker/clear-sessions.sh # 跳过确认
|
||
#
|
||
# `sh docker/clear-sessions.sh` 会用 dash 跑(Debian 的 /bin/sh 就是 dash),而下面
|
||
# 那行的 pipefail 是 bash 专有的,一上来就报 `Illegal option -o pipefail`。
|
||
# 这行必须在 set 之前,且只能用 dash 也认的语法。deploy.sh 里是同一道垫片。
|
||
[ -n "${BASH_VERSION:-}" ] || exec bash "$0" "$@"
|
||
|
||
set -euo pipefail
|
||
|
||
CONTAINER="${CONTAINER:-oj-redis}"
|
||
|
||
# redis 带口令时(服务器那台),redis-cli 靠 REDISCLI_AUTH 认证。没显式给就从
|
||
# docker/.env 里读;机房和本机 dev 的 redis 不带口令,读不到就留空,不影响。
|
||
if [ -z "${REDIS_PASSWORD:-}" ] && [ -f "$(dirname "$0")/.env" ]; then
|
||
REDIS_PASSWORD=$(grep -E '^REDIS_PASSWORD=' "$(dirname "$0")/.env" | tail -1 | cut -d= -f2- || true)
|
||
fi
|
||
# 口令为空时连 -e 都不能带:空的 REDISCLI_AUTH 会让 redis-cli 发一条 AUTH "",
|
||
# 在不带口令的 redis 上报一行 AUTH failed(实测,虽然随后命令照常执行)。
|
||
EXEC=(docker exec)
|
||
[ -z "${REDIS_PASSWORD:-}" ] || EXEC+=(-e REDISCLI_AUTH="$REDIS_PASSWORD")
|
||
EXEC+=("$CONTAINER" redis-cli)
|
||
cli() { "${EXEC[@]}" "$@"; }
|
||
|
||
say() { printf '\n\033[1;36m==> %s\033[0m\n' "$*"; }
|
||
ok() { printf ' \033[32m✓\033[0m %s\n' "$*"; }
|
||
die() { printf '\n\033[1;31m❌ %s\033[0m\n\n' "$*" >&2; exit 1; }
|
||
|
||
[ "$(cli ping 2>/dev/null)" = PONG ] \
|
||
|| die "连不上容器 $CONTAINER 里的 redis(用 CONTAINER=... 指定容器名;带口令的话检查 REDIS_PASSWORD)"
|
||
|
||
# 用 SCAN 而不是 KEYS:KEYS 会阻塞住整个 Redis,而这上面还挂着判题队列和所有人的
|
||
# 会话读写。xargs 分批是因为单条 DEL 传太多 key 会顶到命令行长度上限;每批 DEL 返回
|
||
# 删掉的个数,累加起来就是总数。
|
||
purge() {
|
||
cli --scan --pattern "$1" 2>/dev/null \
|
||
| xargs -r -n 400 "${EXEC[@]}" del \
|
||
| awk '{ sum += $1 } END { print sum + 0 }'
|
||
}
|
||
|
||
count() { cli --scan --pattern "$1" 2>/dev/null | wc -l; }
|
||
|
||
say "容器 $CONTAINER"
|
||
before_sessions=$(count 'session:*')
|
||
before_index=$(count 'user-sessions:*')
|
||
before_bull=$(count 'bull:*')
|
||
printf ' session:* %s\n' "$before_sessions"
|
||
printf ' user-sessions:* %s\n' "$before_index"
|
||
printf ' bull:* %s(不动)\n' "$before_bull"
|
||
|
||
if [ "${YES:-}" != "1" ]; then
|
||
read -rp $'\n 删掉上面的会话、让所有人重新登录?[y/N] ' answer
|
||
[ "$answer" = "y" ] || [ "$answer" = "Y" ] || die "已取消,什么都没做"
|
||
fi
|
||
|
||
say "清理"
|
||
ok "session:* 删掉 $(purge 'session:*') 个"
|
||
ok "user-sessions:* 删掉 $(purge 'user-sessions:*') 个"
|
||
|
||
after_bull=$(count 'bull:*')
|
||
[ "$after_bull" = "$before_bull" ] \
|
||
|| die "判题队列的键数变了($before_bull → $after_bull),这不该发生,请人工检查"
|
||
ok "bull:* 仍是 $after_bull 个,判题队列没被动过"
|
||
|
||
say "完成 —— 另一个站点的 Redis 也要跑一遍"
|