diff --git a/vps-xray/deploy.sh b/vps-xray/deploy.sh index 3494c55..8b90144 100644 --- a/vps-xray/deploy.sh +++ b/vps-xray/deploy.sh @@ -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:MM(00: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}" diff --git a/vps-xray/tests/test-restart-schedule.sh b/vps-xray/tests/test-restart-schedule.sh new file mode 100644 index 0000000..1066fc2 --- /dev/null +++ b/vps-xray/tests/test-restart-schedule.sh @@ -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 ]