#!/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" # 前导零必须按十进制归一化:这个值会原样写进 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" 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 "== update_env_field ==" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT cat > "${TMP}/.env" << 'ENVEOF' # 这是用户自己加的注释 XRAY_PORT=443 XRAY_RESTART_EVERY_DAYS=0 # 尾部注释 ENVEOF update_env_field "${TMP}/.env" XRAY_RESTART_EVERY_DAYS 7 is "替换已有字段" "$(grep '^XRAY_RESTART_EVERY_DAYS=' "${TMP}/.env")" "XRAY_RESTART_EVERY_DAYS=7" is "保留用户注释" "$(grep -c '这是用户自己加的注释' "${TMP}/.env")" "1" is "保留尾部注释" "$(grep -c '尾部注释' "${TMP}/.env")" "1" is "不影响其它字段" "$(grep '^XRAY_PORT=' "${TMP}/.env")" "XRAY_PORT=443" update_env_field "${TMP}/.env" XRAY_RESTART_TIME 05:30 is "追加缺失字段" "$(grep '^XRAY_RESTART_TIME=' "${TMP}/.env")" "XRAY_RESTART_TIME=05:30" update_env_field "${TMP}/.env" XRAY_RESTART_TIMEZONE Asia/Shanghai is "含斜杠的值不被 sed 分隔符干扰" \ "$(grep '^XRAY_RESTART_TIMEZONE=' "${TMP}/.env")" "XRAY_RESTART_TIMEZONE=Asia/Shanghai" update_env_field "${TMP}/.env" XRAY_RESTART_TIMEZONE Europe/Berlin is "含斜杠的值可被再次替换" \ "$(grep '^XRAY_RESTART_TIMEZONE=' "${TMP}/.env")" "XRAY_RESTART_TIMEZONE=Europe/Berlin" is "字段总行数正确" "$(grep -c '^XRAY_RESTART_TIMEZONE=' "${TMP}/.env")" "1" update_env_field "${TMP}/nonexistent" FOO bar 2>/dev/null is "文件不存在返回 1" "$?" "1" printf 'KEY=old' > "${TMP}/.env2" update_env_field "${TMP}/.env2" NEWKEY val is "缺尾换行时不黏连" "$(grep -c '^NEWKEY=val$' "${TMP}/.env2")" "1" is "缺尾换行时原字段完好" "$(grep -c '^KEY=old$' "${TMP}/.env2")" "1" echo "== render_restart_units ==" SYSTEMD_DIR="${TMP}/systemd" GUARD_BIN="${TMP}/bin/xray-restart-guard" RESTART_STATE_FILE="${TMP}/state/last-restart" mkdir -p "${TMP}/systemd" "${TMP}/bin" "${TMP}/state" # systemctl 打桩:记录被调用的参数,避免碰真实 systemd STUB="${TMP}/stub" mkdir -p "$STUB" cat > "${STUB}/systemctl" << 'STUBEOF' #!/bin/sh echo "systemctl $*" >> "${SYSTEMCTL_LOG}" STUBEOF chmod 755 "${STUB}/systemctl" export SYSTEMCTL_LOG="${TMP}/systemctl.log" : > "$SYSTEMCTL_LOG" export PATH="${STUB}:${PATH}" render_restart_units 7 04:00 Asia/Shanghai is "timer 文件存在" "$([ -f "${SYSTEMD_DIR}/xray-restart.timer" ] && echo yes)" "yes" is "service 文件存在" "$([ -f "${SYSTEMD_DIR}/xray-restart.service" ] && echo yes)" "yes" is "guard 可执行" "$([ -x "$GUARD_BIN" ] && echo yes)" "yes" # 时区必须写在日历表达式里:[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" \ "$(grep -c "^ExecStart=${GUARD_BIN}\$" "${SYSTEMD_DIR}/xray-restart.service")" "1" is "guard 里天数正确" \ "$(grep -c '^DAYS=7$' "$GUARD_BIN")" "1" is "guard 里状态文件路径正确" \ "$(grep -c "^STATE=\"${RESTART_STATE_FILE}\"\$" "$GUARD_BIN")" "1" is "guard 用 PATH 查找 systemctl 而非绝对路径" \ "$(grep -c '^exec systemctl restart xray$' "$GUARD_BIN")" "1" render_restart_units 3 23:05 Europe/Berlin is "重复渲染覆盖天数" "$(grep -c '^DAYS=3$' "$GUARD_BIN")" "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" is "remove 后 service 消失" "$([ -f "${SYSTEMD_DIR}/xray-restart.service" ] && echo yes || echo no)" "no" is "remove 后 guard 消失" "$([ -f "$GUARD_BIN" ] && echo yes || echo no)" "no" remove_restart_units is "重复 remove 返回 1" "$?" "1" echo "== guard 脚本行为 ==" render_restart_units 7 04:00 Asia/Shanghai rm -f "$RESTART_STATE_FILE" : > "$SYSTEMCTL_LOG" # 无状态文件 → 首次必须执行 out=$("$GUARD_BIN" 2>&1) is "首次执行重启" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "1" is "首次写入时间戳" "$([ -f "$RESTART_STATE_FILE" ] && echo yes)" "yes" # 刚重启过 → 必须跳过 : > "$SYSTEMCTL_LOG" out=$("$GUARD_BIN" 2>&1) is "刚重启过则跳过" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "0" is "跳过时有提示" "$(echo "$out" | grep -c '跳过')" "1" # 距上次 6 天 → 仍未满 7 天,跳过 echo "$(( $(date +%s) - 6 * 86400 ))" > "$RESTART_STATE_FILE" : > "$SYSTEMCTL_LOG" "$GUARD_BIN" > /dev/null 2>&1 is "6 天不足 7 天则跳过" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "0" # 距上次 7 天差 100 秒 → 300 秒容差内,必须执行 echo "$(( $(date +%s) - 7 * 86400 + 100 ))" > "$RESTART_STATE_FILE" : > "$SYSTEMCTL_LOG" "$GUARD_BIN" > /dev/null 2>&1 is "容差内视为已满 7 天" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "1" # 距上次 7 天差 600 秒 → 超出容差,跳过 echo "$(( $(date +%s) - 7 * 86400 + 600 ))" > "$RESTART_STATE_FILE" : > "$SYSTEMCTL_LOG" "$GUARD_BIN" > /dev/null 2>&1 is "超出容差仍判不足" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "0" # 状态文件内容被写坏 → 按 0 处理,立即执行 echo "garbage" > "$RESTART_STATE_FILE" : > "$SYSTEMCTL_LOG" "$GUARD_BIN" > /dev/null 2>&1 is "状态文件非法时立即执行" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "1" is "非法内容被覆盖为数字" \ "$(grep -cE '^[0-9]+$' "$RESTART_STATE_FILE")" "1" echo "garbage" > "$RESTART_STATE_FILE" : > "$SYSTEMCTL_LOG" 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" : > "$SYSTEMCTL_LOG" "$GUARD_BIN" > /dev/null 2>&1 is "N=1 时满一天即执行" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "1" # 时间戳先于重启写入:用一个必定失败的 systemctl 桩验证 cat > "${STUB}/systemctl" << 'STUBEOF' #!/bin/sh echo "systemctl $*" >> "${SYSTEMCTL_LOG}" exit 1 STUBEOF chmod 755 "${STUB}/systemctl" rm -f "$RESTART_STATE_FILE" : > "$SYSTEMCTL_LOG" "$GUARD_BIN" > /dev/null 2>&1 is "重启失败时时间戳仍已写入" "$([ -s "$RESTART_STATE_FILE" ] && echo yes)" "yes" # 恢复正常桩,避免影响后续 cat > "${STUB}/systemctl" << 'STUBEOF' #!/bin/sh 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 ]