feat(vps-xray): 重启计划配置解析与旧字段迁移

新增 normalize_restart_time / resolve_restart_config,
XRAY_RESTART_EVERY_DAYS 缺失时才回退读 XRAY_DAILY_RESTART。
附单元测试,不需要 root 与 systemd。
This commit is contained in:
2026-08-06 15:48:43 +08:00
parent 938df26cbb
commit 29236e8e1c
2 changed files with 164 additions and 6 deletions
+54 -6
View File
@@ -74,12 +74,60 @@ if [[ "$MODE" != "reality" && "$MODE" != "fast" ]]; then
exit 1
fi
# 布尔开关归一化:容忍 true/TRUE/True/yes/on/1 等写法,
# 避免用户按直觉写了 TRUE 却被静默当成关闭
case "$(printf '%s' "${XRAY_DAILY_RESTART:-false}" | tr '[:upper:]' '[:lower:]')" in
true|yes|on|1) DAILY_RESTART=1 ;;
*) DAILY_RESTART=0 ;;
esac
# ===== 定时重启配置 =====
# 可覆盖路径,测试时指向临时目录
SYSTEMD_DIR="${SYSTEMD_DIR:-/etc/systemd/system}"
GUARD_BIN="${GUARD_BIN:-/usr/local/bin/xray-restart-guard}"
RESTART_STATE_FILE="${RESTART_STATE_FILE:-/var/lib/xray/last-restart}"
RESTART_EVERY_DAYS=0
RESTART_TIME="04:00"
RESTART_TZ="Asia/Shanghai"
# 把 4:00 / 04:00 统一成两位小时的 HH:MM,不合法返回 1。
# 归一化的意义:OnCalendar 字符串格式统一,.env 内容可预测。
normalize_restart_time() {
local t="${1:-}"
[[ "$t" =~ ^([01]?[0-9]|2[0-3]):([0-5][0-9])$ ]] || return 1
# 10# 前缀避免 08/09 被当成八进制
printf '%02d:%s' "$((10#${BASH_REMATCH[1]}))" "${BASH_REMATCH[2]}"
}
resolve_restart_config() {
local days="${XRAY_RESTART_EVERY_DAYS:-}"
# 旧字段迁移:仅当新字段完全未设置时才回退去读,
# 这样 .env 里显式写 0 能覆盖遗留的 XRAY_DAILY_RESTART=true
if [ -z "$days" ]; then
case "$(printf '%s' "${XRAY_DAILY_RESTART:-false}" | tr '[:upper:]' '[:lower:]')" in
true|yes|on|1) days=1 ;;
*) days=0 ;;
esac
fi
if ! [[ "$days" =~ ^[0-9]+$ ]]; then
error "XRAY_RESTART_EVERY_DAYS 必须是非负整数,当前为 '${days}'"
return 1
fi
RESTART_EVERY_DAYS="$days"
local t
if ! t=$(normalize_restart_time "${XRAY_RESTART_TIME:-04:00}"); then
error "XRAY_RESTART_TIME 必须是 HH:MM00:00~23:59),当前为 '${XRAY_RESTART_TIME:-}'"
return 1
fi
RESTART_TIME="$t"
RESTART_TZ="${XRAY_RESTART_TIMEZONE-Asia/Shanghai}"
if [ -z "$RESTART_TZ" ]; then
error "XRAY_RESTART_TIMEZONE 不能为空"
return 1
fi
return 0
}
resolve_restart_config || exit 1
XRAY_PORT="${XRAY_PORT:-443}"
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env bash
# deploy.sh 中重启计划相关逻辑的单元测试
#
# 用法: bash vps-xray/tests/test-restart-schedule.sh
#
# 不需要 root,不接触真实 systemd:所有写入路径都被重定向到临时目录,
# systemctl 用 PATH 打桩拦截。
#
# 加载方式说明:deploy.sh 结尾会直接调用 main,且顶层有 root 检查与参数解析,
# 不能整份 source。这里只抽取「多行函数定义」(形如 `name() {` 开头、`}` 结尾)。
# 单行函数(log/warn/error/step)抽不到,正好由下面的桩覆盖。
set -u
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEPLOY="${TEST_DIR}/../deploy.sh"
if [ ! -f "$DEPLOY" ]; then
echo "找不到 $DEPLOY" >&2
exit 1
fi
eval "$(sed -n '/^[a-z_][a-z_]*() {$/,/^}$/p' "$DEPLOY")"
# 日志桩:error 保留输出便于断言,其余静音
log() { :; }
warn() { :; }
step() { :; }
error() { echo "ERROR: $*" >&2; }
PASS=0
FAIL=0
ok() { PASS=$((PASS + 1)); echo " ok - $1"; }
bad() { FAIL=$((FAIL + 1)); echo " FAIL - $1"; echo " 期望: $2"; echo " 实际: $3"; }
# is <描述> <实际值> <期望值>
is() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1" "$3" "$2"; fi; }
echo "== normalize_restart_time =="
is "04:00 原样通过" "$(normalize_restart_time 04:00)" "04:00"
is "4:00 补零" "$(normalize_restart_time 4:00)" "04:00"
is "08:30 不被当八进制" "$(normalize_restart_time 08:30)" "08:30"
is "23:59 边界" "$(normalize_restart_time 23:59)" "23:59"
is "00:00 边界" "$(normalize_restart_time 00:00)" "00:00"
normalize_restart_time "24:00" >/dev/null 2>&1
is "24:00 被拒" "$?" "1"
normalize_restart_time "04:60" >/dev/null 2>&1
is "04:60 被拒" "$?" "1"
normalize_restart_time "abc" >/dev/null 2>&1
is "abc 被拒" "$?" "1"
normalize_restart_time "" >/dev/null 2>&1
is "空串被拒" "$?" "1"
echo "== resolve_restart_config =="
reset_env() {
unset XRAY_RESTART_EVERY_DAYS XRAY_RESTART_TIME XRAY_RESTART_TIMEZONE XRAY_DAILY_RESTART
RESTART_EVERY_DAYS=""; RESTART_TIME=""; RESTART_TZ=""
}
reset_env
resolve_restart_config
is "全空时天数默认 0" "$RESTART_EVERY_DAYS" "0"
is "全空时时间默认" "$RESTART_TIME" "04:00"
is "全空时时区默认" "$RESTART_TZ" "Asia/Shanghai"
reset_env; XRAY_RESTART_EVERY_DAYS=7; XRAY_RESTART_TIME=5:30
resolve_restart_config
is "读取新字段天数" "$RESTART_EVERY_DAYS" "7"
is "读取新字段时间并补零" "$RESTART_TIME" "05:30"
reset_env; XRAY_DAILY_RESTART=true
resolve_restart_config
is "迁移 true → 1" "$RESTART_EVERY_DAYS" "1"
reset_env; XRAY_DAILY_RESTART=TRUE
resolve_restart_config
is "迁移 TRUE → 1" "$RESTART_EVERY_DAYS" "1"
reset_env; XRAY_DAILY_RESTART=false
resolve_restart_config
is "迁移 false → 0" "$RESTART_EVERY_DAYS" "0"
reset_env; XRAY_DAILY_RESTART=true; XRAY_RESTART_EVERY_DAYS=3
resolve_restart_config
is "新字段优先于旧字段" "$RESTART_EVERY_DAYS" "3"
reset_env; XRAY_DAILY_RESTART=true; XRAY_RESTART_EVERY_DAYS=0
resolve_restart_config
is "新字段显式 0 覆盖旧 true" "$RESTART_EVERY_DAYS" "0"
reset_env; XRAY_RESTART_EVERY_DAYS=abc
resolve_restart_config 2>/dev/null
is "天数非数字报错" "$?" "1"
reset_env; XRAY_RESTART_EVERY_DAYS=-1
resolve_restart_config 2>/dev/null
is "天数负数报错" "$?" "1"
reset_env; XRAY_RESTART_TIME=25:00
resolve_restart_config 2>/dev/null
is "时间非法报错" "$?" "1"
reset_env; XRAY_RESTART_TIMEZONE=""
resolve_restart_config 2>/dev/null
is "时区空串报错" "$?" "1"
echo ""
echo "通过 ${PASS},失败 ${FAIL}"
[ "$FAIL" -eq 0 ]