diff --git a/scripts/core/clashctl.sh b/scripts/core/clashctl.sh index ba161b6..bc19603 100644 --- a/scripts/core/clashctl.sh +++ b/scripts/core/clashctl.sh @@ -3129,6 +3129,51 @@ logs_service() { esac } +systemd_unit_file_path() { + printf '/etc/systemd/system/%s\n' "$(service_unit_name)" +} + +systemd_user_unit_file_path() { + printf '%s/.config/systemd/user/%s\n' "$(user_home_dir)" "$(service_unit_name)" +} + +systemd_unit_has_stale_runtime_template() { + local unit_file="$1" + + [ -f "$unit_file" ] || return 1 + + grep -Eq '^[[:space:]]*Type=forking([[:space:]]|$)|^[[:space:]]*PIDFile=|clashctl[[:space:]]+start-direct' "$unit_file" +} + +systemd_unit_stale_runtime_template_warning() { + local backend="$1" + local unit_file label refresh_cmd restart_cmd + + case "$backend" in + systemd) + unit_file="$(systemd_unit_file_path)" + label="systemd" + refresh_cmd="sudo bash install.sh system" + restart_cmd="sudo systemctl restart $(service_unit_name)" + ;; + systemd-user) + unit_file="$(systemd_user_unit_file_path)" + label="用户级 systemd" + refresh_cmd="bash install.sh user" + restart_cmd="systemctl --user restart $(service_unit_name)" + ;; + *) + return 1 + ;; + esac + + systemd_unit_has_stale_runtime_template "$unit_file" || return 1 + + echo "${label} 服务文件仍是旧模板:$unit_file" + echo "建议刷新服务文件:$refresh_cmd" + echo "刷新后重启服务:$restart_cmd" +} + cmd_logs() { prepare @@ -3595,7 +3640,7 @@ doctor_build() { } doctor_service() { - local backend + local backend stale_warning doctor_print_title "服务检查" @@ -3608,6 +3653,12 @@ doctor_service() { case "$backend" in systemd) + stale_warning="$(systemd_unit_stale_runtime_template_warning systemd 2>/dev/null || true)" + if [ -n "${stale_warning:-}" ]; then + doctor_warn "$stale_warning" + else + doctor_ok "systemd 服务模板:当前版本" + fi if systemctl is-active --quiet "$(service_unit_name)"; then doctor_ok "systemd 服务运行中" systemctl show "$(service_unit_name)" --property MainPID --value 2>/dev/null | awk '{print " 进程号:" $1}' @@ -3616,6 +3667,12 @@ doctor_service() { fi ;; systemd-user) + stale_warning="$(systemd_unit_stale_runtime_template_warning systemd-user 2>/dev/null || true)" + if [ -n "${stale_warning:-}" ]; then + doctor_warn "$stale_warning" + else + doctor_ok "用户级 systemd 服务模板:当前版本" + fi if systemctl --user is-active --quiet "$(service_unit_name)"; then doctor_ok "用户级 systemd 服务运行中" systemctl --user show "$(service_unit_name)" --property MainPID --value 2>/dev/null | awk '{print " 进程号:" $1}' diff --git a/scripts/dev/check-doctor-stale-systemd-unit.sh b/scripts/dev/check-doctor-stale-systemd-unit.sh new file mode 100644 index 0000000..1f34aa7 --- /dev/null +++ b/scripts/dev/check-doctor-stale-systemd-unit.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" + +source_clashctl_for_tests() { + set -- "" + source "$PROJECT_DIR/scripts/core/clashctl.sh" >/dev/null +} + +source_clashctl_for_tests + +tmp_dir="$(mktemp -d)" +trap 'rm -rf "$tmp_dir"' EXIT + +test_unit_file="$tmp_dir/clash-for-linux.service" + +service_unit_name() { printf 'clash-for-linux.service\n'; } + +run_case() { + local name="$1" + local expected="$2" + local content="$3" + + printf '%s\n' "$content" > "$test_unit_file" + + case "$expected" in + stale) + if ! systemd_unit_has_stale_runtime_template "$test_unit_file"; then + echo "not ok - $name: stale unit was not detected" >&2 + return 1 + fi + ;; + fresh) + if systemd_unit_has_stale_runtime_template "$test_unit_file"; then + echo "not ok - $name: fresh unit was marked stale" >&2 + return 1 + fi + ;; + *) + echo "not ok - bad expected value: $expected" >&2 + return 1 + ;; + esac + + echo "ok - $name" +} + +run_case "detects forking type" stale '[Service] +Type=forking +ExecStart=/usr/local/bin/clashctl run-direct' + +run_case "detects pid file" stale '[Service] +Type=simple +PIDFile=/tmp/mihomo.pid +ExecStart=/usr/local/bin/clashctl run-direct' + +run_case "detects old start direct command" stale '[Service] +Type=simple +ExecStart=/usr/local/bin/clashctl start-direct' + +run_case "accepts current run direct template" fresh '[Service] +Type=simple +ExecStart=/usr/local/bin/clashctl run-direct +ExecStopPost=/bin/rm -f /tmp/mihomo.pid' + +systemd_unit_file_path() { printf '%s\n' "$test_unit_file"; } +systemd_user_unit_file_path() { printf '%s\n' "$test_unit_file"; } + +printf '%s\n' '[Service] +Type=forking +PIDFile=/tmp/mihomo.pid +ExecStart=/usr/local/bin/clashctl start-direct' > "$test_unit_file" + +output="$(systemd_unit_stale_runtime_template_warning systemd)" +if ! printf '%s\n' "$output" | grep -Fq "systemd 服务文件仍是旧模板"; then + echo "not ok - systemd warning missing" >&2 + printf '%s\n' "$output" >&2 + exit 1 +fi + +output="$(systemd_unit_stale_runtime_template_warning systemd-user)" +if ! printf '%s\n' "$output" | grep -Fq "用户级 systemd 服务文件仍是旧模板"; then + echo "not ok - systemd-user warning missing" >&2 + printf '%s\n' "$output" >&2 + exit 1 +fi + +echo "ok - stale unit warning text"