feat(vps-xray): 生成 timer/service/guard 三个单元文件
timer 每天唤醒,N 天间隔判断交给 guard 脚本, 绕开 OnCalendar 无法表达任意 N 天间隔的限制。
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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 ]
|
||||
|
||||
Reference in New Issue
Block a user