diff --git a/vps-xray/README.md b/vps-xray/README.md index 05906ea..0bf4097 100644 --- a/vps-xray/README.md +++ b/vps-xray/README.md @@ -226,13 +226,22 @@ bash deploy.sh --restart # 按 .env 现有值重建 timer 所以实现是 timer 每天到点唤醒,由 `/usr/local/bin/xray-restart-guard` 读 `/var/lib/xray/last-restart`,满 N 天才真正重启。间隔因此是「距上次实际重启」的真实天数,`N=3` 与 `N=7` 同样准确。 -查看下次触发时间与执行记录: +查看唤醒计划与执行记录: ```bash systemctl list-timers xray-restart.timer journalctl -u xray-restart.service -n 20 ``` +> ⚠️ `list-timers` 的 `NEXT` 是 **timer 下次「唤醒」的时间**,不是下次重启的时间。timer 每天唤醒一次,NEXT 因此恒为次日的 `XRAY_RESTART_TIME`;是否真的重启由守卫按间隔判断。设了「每 7 天」且 5 天前刚重启过时,NEXT 显示明天凌晨,但实际还要再等 2 天。 +> +> 想直接看「下次实际重启时间」,跑一次不带参数的 `bash deploy.sh --restart`(不改任何配置、不重启服务),输出里会算好打印出来。 + +#### 已知取舍 + +- **间隔锚点是「上次由守卫执行的重启」,不是任何一次 xray 重启。** 完整部署(`bash deploy.sh`)里的 `systemctl restart xray` 不会更新 `/var/lib/xray/last-restart`——设了每 7 天、距上次守卫重启已 6.9 天时跑一次完整部署,次日守卫仍会判定满 7 天而再重启一次。介意的话可在部署后手动执行 `date +%s > /var/lib/xray/last-restart` 把锚点对齐。 +- **重启失败时时间戳仍已更新**,本轮被跳过,下一轮照常——这是为了避免陷入每天硬重启的循环。但若时间戳**写不进去**(磁盘满、只读挂载),守卫会跳过本轮重启并在日志里报错,宁可不重启也不退化成每日重启。 + > 旧字段 `XRAY_DAILY_RESTART` 已废弃。三个新字段都未设置时它仍作为兼容回退(`true` → 每 1 天,其它 → 关闭),重跑部署后 `.env` 会自动换写成新字段。 ### 查看状态 / 日志 diff --git a/vps-xray/deploy.sh b/vps-xray/deploy.sh index ad70a60..913f434 100644 --- a/vps-xray/deploy.sh +++ b/vps-xray/deploy.sh @@ -138,7 +138,11 @@ resolve_restart_config() { error "XRAY_RESTART_EVERY_DAYS 必须是非负整数,当前为 '${days}'" return 1 fi - RESTART_EVERY_DAYS="$days" + # 10# 前缀去掉前导零:这个值会原样写进 guard 的 DAYS=, + # 而 guard 里 $(( DAYS * 86400 - 300 )) 是算术展开—— + # 010 会被当八进制解析成 8 天,08 / 09 直接报 "value too great for base" + # 从而让间隔判断求值为假,退化成每天重启。 + RESTART_EVERY_DAYS=$((10#$days)) local t if ! t=$(normalize_restart_time "${XRAY_RESTART_TIME:-04:00}"); then @@ -163,16 +167,18 @@ update_env_field() { local file="$1" key="$2" value="$3" esc [ -f "$file" ] || return 1 + # 每一步写入都检查返回值:磁盘满或只读时静默失败会让用户以为改动已落盘, + # 下次完整部署又被 .env 里的旧值覆盖回去 if grep -q "^${key}=" "$file"; then # 用 | 作分隔符,并转义 value 中的 | & \,避免值里的 / 破坏 sed esc=$(printf '%s' "$value" | sed 's/[|&\\]/\\&/g') - sed -i "s|^${key}=.*|${key}=${esc}|" "$file" + sed -i "s|^${key}=.*|${key}=${esc}|" "$file" || return 1 else # 文件末尾可能缺换行(用户手工编辑过),直接追加会把新字段黏到上一行 if [ -n "$(tail -c 1 "$file")" ]; then - printf '\n' >> "$file" + printf '\n' >> "$file" || return 1 fi - printf '%s=%s\n' "$key" "$value" >> "$file" + printf '%s=%s\n' "$key" "$value" >> "$file" || return 1 fi return 0 } @@ -199,6 +205,10 @@ if [ -f "\$STATE" ]; then last=\$(cat "\$STATE" 2>/dev/null || echo 0) # 内容被写坏时按 0 处理,即允许立即重启 [[ "\$last" =~ ^[0-9]+\$ ]] || last=0 + # 时间戳落在未来(RTC 错乱或 NTP 大幅向后校正时写下的)同样按 0 处理。 + # 否则时钟校回之后 now - last 恒为负,间隔判断恒成立,此后永远跳过, + # 而状态文件只在真正重启时才被覆盖——没有任何自愈路径。 + [ "\$last" -gt "\$now" ] && last=0 fi # 300 秒容差:systemd timer 默认 AccuracySec=1min,第 N 次触发的实际间隔 @@ -208,11 +218,20 @@ if [ "\$last" -gt 0 ] && [ \$(( now - last )) -lt \$(( DAYS * 86400 - 300 )) ]; exit 0 fi -mkdir -p "\$(dirname "\$STATE")" # 必须先写时间戳:restart 会中断本脚本所在的 systemd 事务, # 顺序反了会丢记录,退化成每天都重启。 # 代价是重启失败时本轮仍被记为已执行——这比陷入每日重启循环可接受。 -echo "\$now" > "\$STATE" +# +# 写不进去(/var 满了、只读挂载)时必须跳过本轮:照常重启会让下一轮 +# 依旧读不到时间戳,从而变成每天硬重启且无限持续——保护完全失效。 +if ! mkdir -p "\$(dirname "\$STATE")" 2>/dev/null; then + echo "无法创建 \$(dirname "\$STATE"),跳过本轮重启以免退化成每日重启" >&2 + exit 1 +fi +if ! echo "\$now" > "\$STATE" 2>/dev/null; then + echo "无法写入 \$STATE,跳过本轮重启以免退化成每日重启" >&2 + exit 1 +fi echo "距上次重启已满 \${DAYS} 天,执行重启" exec systemctl restart xray EOF @@ -234,8 +253,11 @@ EOF Description=Wake xray-restart daily at ${time} ${tz}; the guard enforces the ${days}-day interval [Timer] -OnCalendar=*-*-* ${time}:00 -TimeZone=${tz} +# 时区必须写在日历表达式里(systemd >= 242 支持)。 +# [Timer] 段没有 TimeZone= 这个 key——写了只会被忽略并留下一行 +# "Unknown key name 'TimeZone'",然后按系统本地时区解析。 +# VPS 出厂多为 UTC,那样「04:00 Asia/Shanghai」会在北京时间 12:00 触发。 +OnCalendar=*-*-* ${time}:00 ${tz} Persistent=true [Install] @@ -249,7 +271,11 @@ remove_restart_units() { if [ ! -f "${SYSTEMD_DIR}/xray-restart.timer" ] && [ ! -f "$GUARD_BIN" ]; then return 1 fi - systemctl disable --now xray-restart.timer >/dev/null 2>&1 || true + # 失败不阻断删除(单元可能本就没被 enable 过),但要留一句可见的提示, + # 而不是把结果彻底吞掉 + if ! systemctl disable --now xray-restart.timer >/dev/null 2>&1; then + warn "禁用 xray-restart.timer 未成功(可能本就未启用),继续删除单元文件" + fi rm -f "${SYSTEMD_DIR}/xray-restart.timer" \ "${SYSTEMD_DIR}/xray-restart.service" \ "$GUARD_BIN" @@ -258,21 +284,66 @@ remove_restart_units() { # 依据 RESTART_EVERY_DAYS / RESTART_TIME / RESTART_TZ 建立或移除定时重启, # 并完成 daemon-reload 与 enable/disable。完整部署与 --restart 共用。 +# 本函数被 harden_system 裸调用、harden_system 又被 main 裸调用, +# 全程不在条件位置。deploy.sh 开头是 set -euo pipefail,所以这里任何一条 +# systemctl 返回非 0 都会让整份部署在「8/8 加固」之后无声退出—— +# start_service / save_env / print_result 全都不执行。 +# 因此下面每条 systemctl 都放在条件位置,失败只告警不中断。 configure_restart_timer() { if [ "$RESTART_EVERY_DAYS" -eq 0 ]; then if remove_restart_units; then warn "已移除定时重启(XRAY_RESTART_EVERY_DAYS=0)" fi - systemctl daemon-reload + if ! systemctl daemon-reload; then + warn "systemctl daemon-reload 失败,已删除的单元可能仍残留在 systemd 内存中" + fi log "定时重启: 关闭(崩溃恢复已由 Restart=always 秒级覆盖)" - return + return 0 fi render_restart_units "$RESTART_EVERY_DAYS" "$RESTART_TIME" "$RESTART_TZ" - systemctl daemon-reload - systemctl enable --now xray-restart.timer >/dev/null 2>&1 + if ! systemctl daemon-reload; then + warn "systemctl daemon-reload 失败,新的 timer 可能不会立即生效" + fi + # 不吞 stderr:规格明确「不预校验时区字符串,靠 systemctl 报错」, + # 把报错藏起来就等于既不校验也不提示 + if ! systemctl enable --now xray-restart.timer; then + error "定时重启 timer 启用失败,请检查 XRAY_RESTART_TIMEZONE 是否为合法时区" + error "部署的其余部分不受影响,可用 systemd-analyze verify ${SYSTEMD_DIR}/xray-restart.timer 排查" + return 0 + fi log "定时重启: 每 ${RESTART_EVERY_DAYS} 天 ${RESTART_TIME} (${RESTART_TZ})" warn "重启会切断所有活动连接;崩溃恢复本已由 Restart=always 覆盖,按需启用" + return 0 +} + +# list-timers 里的 NEXT 是 timer 的「下次唤醒」——每天一次,恒为次日的 +# ${RESTART_TIME};真正是否重启由 guard 按间隔判断。设了「每 7 天」且 5 天前 +# 刚重启过的用户,看到 NEXT 是明天凌晨会以为明天就重启,实际还差 2 天。 +# 所以这里直接把「下次实际重启」算出来一并打印。 +print_next_restart() { + local last next now + now=$(date +%s) + + if [ ! -f "$RESTART_STATE_FILE" ]; then + echo " 下次实际重启: 下次唤醒时即会重启(尚无重启记录)" + return 0 + fi + + last=$(cat "$RESTART_STATE_FILE" 2>/dev/null || echo "") + if ! [[ "$last" =~ ^[0-9]+$ ]] || [ "$last" -gt "$now" ]; then + echo " 下次实际重启: 下次唤醒时即会重启(状态文件内容不可用,按无记录处理)" + return 0 + fi + + next=$(( last + RESTART_EVERY_DAYS * 86400 )) + echo " 上次实际重启: $(date -d "@${last}" '+%Y-%m-%d %H:%M:%S %Z' 2>/dev/null || echo "$last")" + if [ "$next" -le "$now" ]; then + echo " 下次实际重启: 下次唤醒时即会重启(距上次已满 ${RESTART_EVERY_DAYS} 天)" + else + echo " 下次实际重启: $(date -d "@${next}" '+%Y-%m-%d %H:%M:%S %Z' 2>/dev/null || echo "$next") 之后的首次唤醒" + fi + return 0 } # ===== --restart:只改重启计划 ===== @@ -302,10 +373,16 @@ apply_restart_only() { # 只把命令行显式给过的字段落盘,没给的不动 .env if [ -n "$RESTART_ARG_DAYS" ]; then - update_env_field "$env_file" XRAY_RESTART_EVERY_DAYS "$RESTART_EVERY_DAYS" + if ! update_env_field "$env_file" XRAY_RESTART_EVERY_DAYS "$RESTART_EVERY_DAYS"; then + error "写入 ${env_file} 失败,未做任何改动" + exit 1 + fi fi if [ -n "$RESTART_ARG_TIME" ]; then - update_env_field "$env_file" XRAY_RESTART_TIME "$RESTART_TIME" + if ! update_env_field "$env_file" XRAY_RESTART_TIME "$RESTART_TIME"; then + error "写入 ${env_file} 失败,未做任何改动" + exit 1 + fi fi configure_restart_timer @@ -316,12 +393,16 @@ apply_restart_only() { echo " 定时重启: 已关闭" else echo " 定时重启: 每 ${RESTART_EVERY_DAYS} 天 ${RESTART_TIME} (${RESTART_TZ})" + print_next_restart fi echo " xray 服务未重启,vless 链接与现有连接均未受影响。" echo "==========================================================" if [ "$RESTART_EVERY_DAYS" -ne 0 ]; then echo "" systemctl list-timers xray-restart.timer --no-pager 2>/dev/null || true + echo "" + echo "注意:上表的 NEXT 是 timer 的下次「唤醒」时间(每天一次)," + echo " 不是下次重启时间。是否真的重启由守卫脚本按上面的间隔判断。" fi } @@ -972,7 +1053,7 @@ SERVER_IP=${SERVER_IP:-} # 定时重启。0 = 关闭(默认);N = 每 N 天在 XRAY_RESTART_TIME 重启一次。 # 崩溃恢复已由 systemd 的 Restart=always 覆盖(秒级),而硬重启会切断 # 全部活动连接。仅在确实观察到长期运行后性能退化时才启用。 -# 改这两项无需完整重新部署: bash deploy.sh --restart 7 04:00 +# 改这几项无需完整重新部署: bash deploy.sh --restart 7 04:00 XRAY_RESTART_EVERY_DAYS=${RESTART_EVERY_DAYS} XRAY_RESTART_TIME=${RESTART_TIME} XRAY_RESTART_TIMEZONE=${RESTART_TZ} diff --git a/vps-xray/tests/test-restart-schedule.sh b/vps-xray/tests/test-restart-schedule.sh index 3aca17b..403beea 100644 --- a/vps-xray/tests/test-restart-schedule.sh +++ b/vps-xray/tests/test-restart-schedule.sh @@ -89,6 +89,20 @@ reset_env; XRAY_DAILY_RESTART=true; XRAY_RESTART_EVERY_DAYS=0 resolve_restart_config is "新字段显式 0 覆盖旧 true" "$RESTART_EVERY_DAYS" "0" +# 前导零必须按十进制归一化:这个值会原样写进 guard 的 DAYS=, +# 而 guard 里是算术展开——010 会变成 8 天,08/09 直接报错导致每天重启 +reset_env; XRAY_RESTART_EVERY_DAYS=08 +resolve_restart_config +is "08 归一化为 8(不当八进制)" "$RESTART_EVERY_DAYS" "8" + +reset_env; XRAY_RESTART_EVERY_DAYS=09 +resolve_restart_config +is "09 归一化为 9(不当八进制)" "$RESTART_EVERY_DAYS" "9" + +reset_env; XRAY_RESTART_EVERY_DAYS=010 +resolve_restart_config +is "010 归一化为 10(不当八进制)" "$RESTART_EVERY_DAYS" "10" + reset_env; XRAY_RESTART_EVERY_DAYS=abc resolve_restart_config 2>/dev/null is "天数非数字报错" "$?" "1" @@ -169,10 +183,12 @@ is "timer 文件存在" "$([ -f "${SYSTEMD_DIR}/xray-restart.timer" ] && echo is "service 文件存在" "$([ -f "${SYSTEMD_DIR}/xray-restart.service" ] && echo yes)" "yes" is "guard 可执行" "$([ -x "$GUARD_BIN" ] && echo yes)" "yes" -is "OnCalendar 每天触发" \ - "$(grep -c '^OnCalendar=\*-\*-\* 04:00:00$' "${SYSTEMD_DIR}/xray-restart.timer")" "1" -is "TimeZone 写入" \ - "$(grep -c '^TimeZone=Asia/Shanghai$' "${SYSTEMD_DIR}/xray-restart.timer")" "1" +# 时区必须写在日历表达式里:[Timer] 段没有 TimeZone= 这个 key, +# 写了会被 systemd 忽略并按系统本地时区(VPS 多为 UTC)解析,静默偏移 8 小时 +is "OnCalendar 每天触发且带时区" \ + "$(grep -c '^OnCalendar=\*-\*-\* 04:00:00 Asia/Shanghai$' "${SYSTEMD_DIR}/xray-restart.timer")" "1" +is "不再使用非法的 TimeZone= 指令" \ + "$(grep -c '^TimeZone=' "${SYSTEMD_DIR}/xray-restart.timer")" "0" is "Persistent 开启" \ "$(grep -c '^Persistent=true$' "${SYSTEMD_DIR}/xray-restart.timer")" "1" is "service 指向 guard" \ @@ -186,8 +202,8 @@ is "guard 用 PATH 查找 systemctl 而非绝对路径" \ render_restart_units 3 23:05 Europe/Berlin is "重复渲染覆盖天数" "$(grep -c '^DAYS=3$' "$GUARD_BIN")" "1" -is "重复渲染覆盖时间" \ - "$(grep -c '^OnCalendar=\*-\*-\* 23:05:00$' "${SYSTEMD_DIR}/xray-restart.timer")" "1" +is "重复渲染覆盖时间与时区" \ + "$(grep -c '^OnCalendar=\*-\*-\* 23:05:00 Europe/Berlin$' "${SYSTEMD_DIR}/xray-restart.timer")" "1" remove_restart_units is "remove 后 timer 消失" "$([ -f "${SYSTEMD_DIR}/xray-restart.timer" ] && echo yes || echo no)" "no" @@ -246,6 +262,37 @@ guard_err=$("$GUARD_BIN" 2>&1 >/dev/null) is "状态文件非法时不产生算术求值报错" \ "$(printf '%s' "$guard_err" | grep -c 'integer expression')" "0" +# 时间戳落在未来(RTC 错乱 / NTP 大幅向后校正)→ 必须按 0 处理立即执行, +# 否则时钟校回后 now-last 恒为负,此后永远跳过且无自愈路径 +echo "$(( $(date +%s) + 30 * 86400 ))" > "$RESTART_STATE_FILE" +: > "$SYSTEMCTL_LOG" +"$GUARD_BIN" > /dev/null 2>&1 +is "未来时间戳不导致永久跳过" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "1" +is "未来时间戳被覆盖为当前时间" \ + "$([ "$(cat "$RESTART_STATE_FILE")" -le "$(date +%s)" ] && echo yes || echo no)" "yes" + +# 状态文件写不进去 → 必须跳过本轮。照常重启会让下一轮依旧读不到时间戳, +# 退化成每天硬重启且无限持续 +guard_saved_state="$RESTART_STATE_FILE" + +# (a) 目录建不出来:拿一个普通文件占住父目录的位置 +: > "${TMP}/blocked" +RESTART_STATE_FILE="${TMP}/blocked/last-restart" +render_restart_units 7 04:00 Asia/Shanghai +: > "$SYSTEMCTL_LOG" +"$GUARD_BIN" > /dev/null 2>&1 +is "状态目录建不出来时跳过重启" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "0" + +# (b) 目录能建但文件写不进去:让状态文件路径本身是个目录 +mkdir -p "${TMP}/asdir/last-restart" +RESTART_STATE_FILE="${TMP}/asdir/last-restart" +render_restart_units 7 04:00 Asia/Shanghai +: > "$SYSTEMCTL_LOG" +"$GUARD_BIN" > /dev/null 2>&1 +is "时间戳写不进去时跳过重启" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "0" + +RESTART_STATE_FILE="$guard_saved_state" + # 每日模式:距上次 1 天即执行 render_restart_units 1 04:00 Asia/Shanghai echo "$(( $(date +%s) - 86400 ))" > "$RESTART_STATE_FILE" @@ -272,6 +319,150 @@ echo "systemctl $*" >> "${SYSTEMCTL_LOG}" STUBEOF chmod 755 "${STUB}/systemctl" +echo "== configure_restart_timer ==" + +# 启用方向 +RESTART_EVERY_DAYS=7 +RESTART_TIME="04:00" +RESTART_TZ="Asia/Shanghai" +rm -f "${SYSTEMD_DIR}/xray-restart.timer" "${SYSTEMD_DIR}/xray-restart.service" "$GUARD_BIN" +: > "$SYSTEMCTL_LOG" +configure_restart_timer > /dev/null 2>&1 +is "启用时渲染出 timer" \ + "$([ -f "${SYSTEMD_DIR}/xray-restart.timer" ] && echo yes || echo no)" "yes" +is "启用时渲染出 guard" "$([ -x "$GUARD_BIN" ] && echo yes || echo no)" "yes" +is "启用时执行 daemon-reload" \ + "$(grep -c '^systemctl daemon-reload$' "$SYSTEMCTL_LOG")" "1" +is "启用时执行 enable --now" \ + "$(grep -c '^systemctl enable --now xray-restart.timer$' "$SYSTEMCTL_LOG")" "1" +is "启用时不重启 xray" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "0" + +# enable 失败时不得中断调用方:configure_restart_timer 在部署主干上被裸调用 +cat > "${STUB}/systemctl" << 'STUBEOF' +#!/bin/sh +echo "systemctl $*" >> "${SYSTEMCTL_LOG}" +case "$1" in + enable) exit 1 ;; +esac +STUBEOF +chmod 755 "${STUB}/systemctl" +: > "$SYSTEMCTL_LOG" +configure_restart_timer > /dev/null 2>&1 +is "enable 失败时仍返回 0(不中断部署主干)" "$?" "0" +cat > "${STUB}/systemctl" << 'STUBEOF' +#!/bin/sh +echo "systemctl $*" >> "${SYSTEMCTL_LOG}" +STUBEOF +chmod 755 "${STUB}/systemctl" + +# 关闭方向 +echo "12345" > "$RESTART_STATE_FILE" +RESTART_EVERY_DAYS=0 +: > "$SYSTEMCTL_LOG" +configure_restart_timer > /dev/null 2>&1 +is "关闭时删除 timer" \ + "$([ -f "${SYSTEMD_DIR}/xray-restart.timer" ] && echo yes || echo no)" "no" +is "关闭时删除 service" \ + "$([ -f "${SYSTEMD_DIR}/xray-restart.service" ] && echo yes || echo no)" "no" +is "关闭时删除 guard" "$([ -f "$GUARD_BIN" ] && echo yes || echo no)" "no" +is "关闭时保留状态文件" "$(cat "$RESTART_STATE_FILE")" "12345" +is "关闭时执行 disable --now" \ + "$(grep -c '^systemctl disable --now xray-restart.timer$' "$SYSTEMCTL_LOG")" "1" +is "关闭时不重启 xray" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "0" + +echo "== apply_restart_only 安全不变式 ==" + +# 这一节守护本特性最核心的承诺:--restart 不重启 xray、不碰 vless 链接。 +# 缺了它,谁在 apply_restart_only 里加一行 systemctl restart xray 都不会被发现。 +APP_DIR="${TMP}/app" +mkdir -p "$APP_DIR" +SCRIPT_DIR="$APP_DIR" + +make_app_env() { + cat > "${APP_DIR}/.env" << 'ENVEOF' +# Xray 部署配置(由 deploy.sh 自动生成) + +XRAY_MODE=reality +XRAY_PORT=443 +XRAY_UUID=11111111-2222-3333-4444-555555555555 +# 这是用户自己加的注释,不许动 +SERVER_IP= +XRAY_RESTART_EVERY_DAYS=0 +XRAY_RESTART_TIME=04:00 +XRAY_RESTART_TIMEZONE=Asia/Shanghai +XRAY_PUBLIC_KEY=pubkeyzzz +XRAY_SHORT_ID=abcdef0123456789 +REALITY_SNI=www.apple.com +ENVEOF +} + +# --- 启用:--restart 7 5:30 --- +reset_env +make_app_env +cp "${APP_DIR}/.env" "${TMP}/env.before" +RESTART_ARG_DAYS=7 +RESTART_ARG_TIME=5:30 +: > "$SYSTEMCTL_LOG" +apply_restart_only > /dev/null 2>&1 + +is "apply_restart_only 绝不重启 xray" \ + "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "0" +is "天数写回 .env" "$(grep '^XRAY_RESTART_EVERY_DAYS=' "${APP_DIR}/.env")" "XRAY_RESTART_EVERY_DAYS=7" +is "时间归一化后写回" "$(grep '^XRAY_RESTART_TIME=' "${APP_DIR}/.env")" "XRAY_RESTART_TIME=05:30" +is ".env 只有这两行发生变化" \ + "$(diff "${TMP}/env.before" "${APP_DIR}/.env" | grep -c '^[<>]')" "4" +is "用户注释原样保留" "$(grep -c '这是用户自己加的注释,不许动' "${APP_DIR}/.env")" "1" +is "UUID 未被改动" "$(grep -c '^XRAY_UUID=11111111-2222-3333-4444-555555555555$' "${APP_DIR}/.env")" "1" +is "SNI 未被改动" "$(grep -c '^REALITY_SNI=www\.apple\.com$' "${APP_DIR}/.env")" "1" +is "PublicKey 未被改动" "$(grep -c '^XRAY_PUBLIC_KEY=pubkeyzzz$' "${APP_DIR}/.env")" "1" +is "启用后 timer 已就位" \ + "$([ -f "${SYSTEMD_DIR}/xray-restart.timer" ] && echo yes || echo no)" "yes" + +# --- 不带位置参数:.env 必须完全不变 --- +reset_env +make_app_env +XRAY_RESTART_EVERY_DAYS=7 +XRAY_RESTART_TIME=04:00 +cp "${APP_DIR}/.env" "${TMP}/env.before2" +RESTART_ARG_DAYS="" +RESTART_ARG_TIME="" +: > "$SYSTEMCTL_LOG" +apply_restart_only > /dev/null 2>&1 +is "不带位置参数时 .env 一字不改" \ + "$(diff "${TMP}/env.before2" "${APP_DIR}/.env" > /dev/null && echo same || echo differ)" "same" +is "不带位置参数时也不重启 xray" \ + "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "0" + +# --- 关闭:--restart 0 --- +reset_env +echo "12345" > "$RESTART_STATE_FILE" +RESTART_ARG_DAYS=0 +RESTART_ARG_TIME="" +: > "$SYSTEMCTL_LOG" +apply_restart_only > /dev/null 2>&1 +is "关闭后 timer 被删" "$([ -f "${SYSTEMD_DIR}/xray-restart.timer" ] && echo yes || echo no)" "no" +is "关闭后 service 被删" "$([ -f "${SYSTEMD_DIR}/xray-restart.service" ] && echo yes || echo no)" "no" +is "关闭后 guard 被删" "$([ -f "$GUARD_BIN" ] && echo yes || echo no)" "no" +is "关闭后状态文件保留" "$(cat "$RESTART_STATE_FILE")" "12345" +is "关闭时天数写回 .env" "$(grep '^XRAY_RESTART_EVERY_DAYS=' "${APP_DIR}/.env")" "XRAY_RESTART_EVERY_DAYS=0" +is "关闭时也不重启 xray" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "0" + +echo "== print_next_restart ==" + +RESTART_EVERY_DAYS=7 +rm -f "$RESTART_STATE_FILE" +is "无状态文件时提示下次唤醒即重启" \ + "$(print_next_restart | grep -c '下次唤醒时即会重启')" "1" + +now_ts=$(date +%s) +echo "$(( now_ts - 6 * 86400 ))" > "$RESTART_STATE_FILE" +is "未满 N 天时给出的是真实重启日期而非明天的唤醒时间" \ + "$(print_next_restart | grep -c "下次实际重启: $(date -d "@$(( now_ts + 86400 ))" '+%Y-%m-%d')")" "1" + +echo "$(( $(date +%s) - 9 * 86400 ))" > "$RESTART_STATE_FILE" +is "已满 N 天时提示下次唤醒即重启" \ + "$(print_next_restart | grep -c '下次唤醒时即会重启')" "1" + echo "" echo "通过 ${PASS},失败 ${FAIL}" [ "$FAIL" -eq 0 ]