feat(vps-xray): 生成 timer/service/guard 三个单元文件

timer 每天唤醒,N 天间隔判断交给 guard 脚本,
绕开 OnCalendar 无法表达任意 N 天间隔的限制。
This commit is contained in:
2026-08-06 16:15:03 +08:00
parent dfcaf7e4d0
commit d19d337bf2
2 changed files with 132 additions and 0 deletions
+79
View File
@@ -148,6 +148,85 @@ update_env_field() {
return 0
}
# 生成 timer / service / guard 三个文件。
# 完整部署与 --restart 共用本函数,避免两条路径各写一份实现后漂移。
render_restart_units() {
local days="$1" time="$2" tz="$3"
mkdir -p "$SYSTEMD_DIR" "$(dirname "$GUARD_BIN")"
# 守卫脚本:systemd 的 OnCalendar 表达不了「每 N 天」——
# *-*-1/7 是「每月 1/8/15/22/29 号」,跨月会重置成 2~3 天。
# 所以 timer 只负责每天到点唤醒,间隔判断放到这里。
cat > "$GUARD_BIN" << EOF
#!/bin/bash
# 由 deploy.sh 生成,请勿手工编辑
STATE="${RESTART_STATE_FILE}"
DAYS=${days}
now=\$(date +%s)
last=0
if [ -f "\$STATE" ]; then
last=\$(cat "\$STATE" 2>/dev/null || echo 0)
# 内容被写坏时按 0 处理,即允许立即重启
[[ "\$last" =~ ^[0-9]+\$ ]] || last=0
fi
# 300 秒容差:systemd timer 默认 AccuracySec=1min,第 N 次触发的实际间隔
# 可能是 N*86400-30s。严格比较会判「不满」从而顺延整整一天,且误差会累积。
if [ "\$last" -gt 0 ] && [ \$(( now - last )) -lt \$(( DAYS * 86400 - 300 )) ]; then
echo "距上次重启不足 \${DAYS} 天(已过 \$(( (now - last) / 86400 )) 天),跳过"
exit 0
fi
mkdir -p "\$(dirname "\$STATE")"
# 必须先写时间戳:restart 会中断本脚本所在的 systemd 事务,
# 顺序反了会丢记录,退化成每天都重启。
# 代价是重启失败时本轮仍被记为已执行——这比陷入每日重启循环可接受。
echo "\$now" > "\$STATE"
echo "距上次重启已满 \${DAYS} 天,执行重启"
exec systemctl restart xray
EOF
chmod 755 "$GUARD_BIN"
cat > "${SYSTEMD_DIR}/xray-restart.service" << EOF
[Unit]
Description=Scheduled restart of Xray (every ${days} day(s))
After=network.target
[Service]
Type=oneshot
Environment=PATH=/usr/sbin:/usr/bin:/sbin:/bin
ExecStart=${GUARD_BIN}
EOF
cat > "${SYSTEMD_DIR}/xray-restart.timer" << EOF
[Unit]
Description=Wake xray-restart daily at ${time} ${tz}; the guard enforces the ${days}-day interval
[Timer]
OnCalendar=*-*-* ${time}:00
TimeZone=${tz}
Persistent=true
[Install]
WantedBy=timers.target
EOF
}
# 删除 timer / service / guard。状态文件刻意保留:
# 以后重新启用时,距上次已超 N 天则首次触发即重启,符合直觉。
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
rm -f "${SYSTEMD_DIR}/xray-restart.timer" \
"${SYSTEMD_DIR}/xray-restart.service" \
"$GUARD_BIN"
return 0
}
resolve_restart_config || exit 1
# 过渡兼容:harden_system / start_service / save_env 仍引用 DAILY_RESTART
+53
View File
@@ -144,6 +144,59 @@ 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"
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"
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$' "${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 ""
echo "通过 ${PASS},失败 ${FAIL}"
[ "$FAIL" -eq 0 ]