From 8ce1e6531aba865d958e5f02b7ecad9b1958a9e4 Mon Sep 17 00:00:00 2001 From: UnbalancedCat Date: Wed, 25 Mar 2026 00:12:14 +0800 Subject: [PATCH] feat: add foreground mode, pause/resume commands, API docs --- API.md | 191 ++++++++++++++++++++++++++++++++++++ README.md | 110 ++++++++------------- cmd/smart-shutdown/main.go | 172 +++++++++++++++++++++++++++++++- example_config.json | 6 ++ pkg/config/config.go | 7 ++ pkg/config/pause.go | 52 ++++++++++ pkg/logger/logger.go | 18 ++++ pkg/monitor/statemachine.go | 22 +++++ 8 files changed, 502 insertions(+), 76 deletions(-) create mode 100644 API.md create mode 100644 example_config.json create mode 100644 pkg/config/pause.go diff --git a/API.md b/API.md new file mode 100644 index 0000000..60e0b5a --- /dev/null +++ b/API.md @@ -0,0 +1,191 @@ +# API Reference — Smart Network Shutdown Monitor + +完整的 CLI 命令说明与参数参考文档。 + +> **注意:** 服务管理相关指令(`start`, `stop`, `restart`, `install`, `uninstall`)均需在**管理员 (Windows) / root (Linux)** 权限下执行。 + +--- + +## 服务生命周期 + +### `start` +唤起并注册后台监控守护进程,服务将随系统开机自动启动。 + +```bash +smart-shutdown start +``` + +### `stop` +停止当前运行的后台网络探测服务。 + +```bash +smart-shutdown stop +``` + +### `restart` +重启后台服务(配置变更后需执行此命令以应用新配置)。 + +```bash +smart-shutdown restart +``` + +### `status` +输出当前服务存活状态、载入的配置参数、日志存放路径及最近 10 条日志。 + +```bash +smart-shutdown status +``` + +--- + +## 安装与卸载 + +### `install` +将可执行文件复制至系统目录并写入全局环境变量,同时注册为开机自启服务。 + +```bash +smart-shutdown install +``` + +### `uninstall` +停止并注销后台服务,清理系统目录中的程序副本与环境变量。执行时将询问是否同时清除配置文件与日志。 + +```bash +smart-shutdown uninstall +``` + +--- + +## 配置管理 + +### `config set ` +直接修改指定配置项,并执行合法性校验。改写后需执行 `restart` 使其生效。 + +```bash +# 修改目标监控 IP 地址 +smart-shutdown config set TargetIP 192.168.0.1 + +# 修改断网容忍时长(秒) +smart-shutdown config set MonitorWindowSeconds 300 + +# 修改关机倒计时缓冲(秒) +smart-shutdown config set ShutdownCountdown 60 + +# 修改探测发包间隔(秒) +smart-shutdown config set NormalPingInterval 15 +``` + +**可用配置项:** + +| 参数项 | 含义 | 默认值 | +|:---|:---|:---| +| `TargetIP` | 目标 IPv4 地址,程序向其发送 ICMP 包检测联通性。 | `192.168.3.1` | +| `MonitorWindowSeconds` | 网络中断超出此时长(秒)则触发关机流程。 | `180` | +| `ShutdownCountdown` | 正式关机前的倒计时缓冲时间(秒)。 | `60` | +| `NormalPingInterval` | 网络正常时的探测发包间隔(秒)。 | `15` | + +**配置文件路径:** +- Windows: `C:\ProgramData\SmartNetworkMonitor\config.json` +- Linux: `/etc/smart-network-monitor/config.json` + +--- + +## 后台休眠控制 + +### `pause` +向正在运行的后台守护进程发送休眠指令,后台停止发包但**不启动前台监控**。必须指定 `--stop-after` 或 `--stop-at` 之一(或同时指定,以先到达的时间为准)。 + +```bash +# 休眠 2 小时后自动唤醒 +smart-shutdown pause --stop-after 2h + +# 休眠至明早 8 点自动唤醒 +smart-shutdown pause --stop-at "2026-03-25 08:00:00" + +# 同时指定两个时间,取较早者 +smart-shutdown pause --stop-after 3h --stop-at "2026-03-25 08:00:00" +``` + +| Flag | 说明 | 格式示例 | +|:---|:---|:---| +| `--stop-after` | 休眠时长,到期自动唤醒。 | `30m`, `2h`, `1h30m` | +| `--stop-at` | 休眠至指定绝对时间自动唤醒。 | `"2026-03-25 08:00:00"` | + +### `resume` +立即撤销休眠指令,后台将在下一轮探测周期(约 15 秒内)重新激活。 + +```bash +smart-shutdown resume +``` + +--- + +## 前台临时监控模式 + +直接执行 `smart-shutdown`(不带子命令)可进入前台临时监控模式,日志将写入 `network_monitor_front.log` 与原后台日志隔离。 + +- 监控参数(`--target-ip` 等)未指定时,使用配置文件中的值。 +- **运行时长参数**(`--stop-after` / `--stop-at`)均未指定时,前台监控将**一直运行**,直到手动按 `Ctrl+C` 终止。 + +```bash +smart-shutdown [flags] +``` + +| Flag | 说明 | 示例 | +|:---|:---|:---| +| `--target-ip` | 临时覆盖目标监控 IP | `--target-ip 8.8.8.8` | +| `--window-sec` | 临时覆盖断网容忍时长(秒) | `--window-sec 60` | +| `--shutdown-cnt` | 临时覆盖关机倒计时(秒) | `--shutdown-cnt 10` | +| `--ping-interval` | 临时覆盖探测发包间隔(秒) | `--ping-interval 5` | +| `--override-bg` | 挂起后台守护进程,由前台全面接管,退出时自动恢复后台 | `--override-bg` | +| `--stop-after` | 前台运行指定时长后自动退出 | `--stop-after 2h30m` | +| `--stop-at` | 前台运行至指定时间自动退出 | `--stop-at "2026-03-25 08:00:00"` | + +**使用示例:** + +```bash +# 临时将目标 IP 改为 8.8.8.8,60 秒无响应触发 +smart-shutdown --target-ip 8.8.8.8 --window-sec 60 + +# 接管后台,30 分钟后自动退出并恢复后台 +smart-shutdown --override-bg --stop-after 30m + +# 接管后台,运行至明早 8 点自动退出并恢复后台 +smart-shutdown --override-bg --stop-at "2026-03-25 08:00:00" +``` + +--- + +## 其他 + +### `update` +联网拉取最新版本并热部署更新。 + +```bash +smart-shutdown update +``` + +### `--version` / `-V` +查看当前版本号并拉取最新发布状态。 + +```bash +smart-shutdown --version +``` + +### `--verbose` / `-v` +打印底层部署及环境追溯 Debug 信息(对所有子命令生效)。 + +```bash +smart-shutdown status --verbose +``` + +--- + +## 日志文件位置 + +| 场景 | Windows | Linux | +|:---|:---|:---| +| 后台服务日志 | `C:\ProgramData\SmartNetworkMonitor\logs\network_monitor.log` | `/var/log/smart-network-monitor/network_monitor.log` | +| 前台临时监控日志 | `C:\ProgramData\SmartNetworkMonitor\logs\network_monitor_front.log` | `/var/log/smart-network-monitor/network_monitor_front.log` | + +程序按日自动切割日志,默认保留最近 30 天。 diff --git a/README.md b/README.md index f441c0e..f8dafd0 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,6 @@ irm https://raw.githubusercontent.com/UnbalancedCat/smart-shutdown/main/install. > **说明:** 该命令会自动下载最新版本,执行 `install` 将程序复制到 `C:\Program Files\SmartShutdown\` 并写入系统 `PATH` 环境变量,同时注册为 Windows 开机自启服务。此后您可在任意管理员终端直接使用 `smart-shutdown` 命令。 -如需手动安装,也可前往 [Releases](https://github.com/UnbalancedCat/smart-shutdown/releases) 页面下载对应架构版本,然后在管理员终端中执行: -```powershell -.\smart-shutdown_windows_amd64.exe install -smart-shutdown start -``` - ### Linux (Ubuntu/Debian/CentOS 等) 在终端中执行以下一键安装命令: @@ -32,24 +26,9 @@ smart-shutdown start curl -sSL https://raw.githubusercontent.com/UnbalancedCat/smart-shutdown/main/install.sh | sudo sh ``` -如需手动安装,可执行以下命令: - -```bash -sudo curl -sSL https://github.com/UnbalancedCat/smart-shutdown/releases/latest/download/smart-shutdown_linux_amd64 -o /usr/local/bin/smart-shutdown && \ -sudo chmod +x /usr/local/bin/smart-shutdown && \ -sudo smart-shutdown install && \ -sudo smart-shutdown start -``` - ## 快速开始 -安装完成后,在管理员 / root 终端中执行首次启动: - -```bash -smart-shutdown start -``` - -如果程序未检测到配置文件,将自动引导您完成初始设置: +安装完成后,默认会自动运行如下引导,进行初始设置: ``` ========== 首次配置 ========== @@ -76,60 +55,49 @@ Windows `sudo` 默认运行在 **ForceNewWindow(强制新窗口)** 模式下 1. **切换 sudo 为 Inline 模式**:打开 **系统设置 → 开发者选项 → 启用 sudo**,将模式改为 **内联 (Inline)**。此后 `sudo smart-shutdown status` 的输出将正常回显至当前终端。 2. **直接使用管理员终端**:右键点击终端图标,选择 **以管理员身份运行**,随后无需 `sudo` 前缀即可执行所有指令。 -## CLI 终端管理指令 (Commands) +## 命令参考 -完成服务注册后,可通过以下指令直接管理守护进程状态流。 -*(注: 启停服务及修改配置的指令需在 **管理员级别 / root 权限** 终端内执行)* +完整的命令说明、参数列表与使用示例,请查阅 [API.md](./API.md)。 + +常用快捷参考: + +| 命令 | 说明 | +|:---|:---| +| `smart-shutdown start` | 启动后台监控服务 | +| `smart-shutdown stop` | 停止后台监控服务 | +| `smart-shutdown restart` | 重启服务 | +| `smart-shutdown status` | 查看服务状态与最近日志 | +| `smart-shutdown config set ` | 修改配置项 | +| `smart-shutdown pause --stop-after 2h` | 临时挂起后台 2 小时 | +| `smart-shutdown resume` | 立即恢复挂起的后台 | +| `smart-shutdown uninstall` | 卸载服务及环境变量 | + +## 免安装便携使用与前台监控模式 + +如果您只是想临时使用网络检测自动关机功能,而**不想将程序安装为系统常驻服务**,您可以直接下载可执行文件进行免安装的便携式运行(基于前台模式)。 + +前往 [Releases](https://github.com/UnbalancedCat/smart-shutdown/releases) 页面下载对应您系统的可执行文件后,在同一目录下的终端中,直接带参数执行即可。例如: -### 运行状态查询 -输出当前服务存活状态、载入的配置参数、日志存放路径及最近 10 条日志: ```bash -smart-shutdown status +# 临时在前台启动监控(非后台服务),检测目标 IP 8.8.8.8,脱机容忍设为 60 秒 +.\smart-shutdown --target-ip 8.8.8.8 --window-sec 60 ``` -### 修改系统配置 -直接修改配置参数并执行合法性校验。(注: 改写配置后必须执行 `smart-shutdown restart` 重启服务才能应用生效) -```bash -# 修改目标监控 IP 地址 -smart-shutdown config set TargetIP 192.168.0.1 +> **提示:** +> 1. 前台模式下产生的日志将单独写入 `network_monitor_front.log`,不会影响原有数据。关闭当前终端即可停止监控进程。 +> 2. 关于前台模式的 `--stop-after` (设定运行多少时间后退出)、`--override-bg` (接管正在运行的后台服务) 等高阶参数详细说明,请参阅 [API.md](./API.md)。 -# 修改断网响应的容忍监控窗口 -smart-shutdown config set MonitorWindowSeconds 300 -``` +**扩展:手动安装为服务** -### 服务生命周期控制 -```bash -# 停止当前运行的后台网络探测服务 -smart-shutdown stop +如果您下载了二进制文件后改变主意,想手动将其注册为系统服务,也可以直接使用 `install` 命令: -# 启动后台服务并恢复网络探测 -smart-shutdown start - -# 重启后台服务 -smart-shutdown restart -``` - -### 服务与环境卸载 -停止并注销后台服务,清理系统目录中的程序副本与对应的系统环境变量: -```bash -smart-shutdown uninstall -``` - -*(如需查看完整的帮助说明,可执行 `smart-shutdown help`)* - -## 默认配置参数详情 -程序会在对应操作系统的后台数据存放区建立或读取 JSON 配置文件: -- Windows: `C:\ProgramData\SmartNetworkMonitor\config.json` -- Linux: `/etc/smart-network-monitor/config.json` - -| 参数项 | 含义与功能说明 | 默认值 | -|:---|:---|:---| -| `TargetIP` | 需发送 ICMP 包验证联通性的目标 IPv4 地址。 | `192.168.3.1` | -| `MonitorWindowSeconds` | 网络中断被判定为异常并触发系统关机前,所能容忍的最长超时时长 (秒)。 | `180` | -| `ShutdownCountdown` | 容忍超限后,执行正式关机系统指令的警告倒计时缓冲时间 (秒)。 | `60` | -| `NormalPingInterval` | 网络连通性正常时,每次静默发包探测的间隔时间 (秒)。 | `15` | - -## 本地日志存放位置 -程序按日切割保存网络探测及中断记录,默认留存最近 30 天的文件: -- **Windows**: `C:\ProgramData\SmartNetworkMonitor\logs\network_monitor.log` -- **Linux**: `/var/log/smart-network-monitor/network_monitor.log` +- **Windows**: + ```powershell + .\smart-shutdown install + ``` +- **Linux**: + ```bash + sudo mv ./smart-shutdown_linux_amd64 /usr/local/bin/smart-shutdown + sudo chmod +x /usr/local/bin/smart-shutdown + sudo smart-shutdown install + ``` diff --git a/cmd/smart-shutdown/main.go b/cmd/smart-shutdown/main.go index b90872a..6e85c24 100644 --- a/cmd/smart-shutdown/main.go +++ b/cmd/smart-shutdown/main.go @@ -1,24 +1,29 @@ package main import ( + "context" "fmt" "io" + "net" "os" "os/exec" "path/filepath" "runtime" "strings" + "time" "github.com/kardianos/service" "github.com/spf13/cobra" "smart-shutdown/pkg/config" "smart-shutdown/pkg/daemon" "smart-shutdown/pkg/logger" + "smart-shutdown/pkg/monitor" "smart-shutdown/pkg/updater" ) var AppVersion = "dev" var verbose bool +var configPath string func main() { if err := logger.InitLogger(); err != nil { @@ -26,7 +31,22 @@ func main() { os.Exit(1) } - cfg, err := config.LoadConfig() + // Pre-parse -c / --config before cobra so we can load correct config early + for i, arg := range os.Args[1:] { + if (arg == "-c" || arg == "--config") && i+1 < len(os.Args)-1 { + configPath = os.Args[i+2] + break + } + } + + var cfg *config.Config + var err error + if configPath != "" { + logger.Debug("使用自定义配置文件: %s", configPath) + cfg, err = config.LoadConfigFrom(configPath) + } else { + cfg, err = config.LoadConfig() + } if err != nil { logger.Crit("读取配置文件失败: %v", err) os.Exit(1) @@ -38,7 +58,16 @@ func main() { os.Exit(1) } - var showVersion bool + var ( + showVersion bool + tmpTargetIP string + tmpWindowSec int + tmpShutdownCnt int + tmpPingInt int + tmpOverrideBg bool + tmpStopAfterStr string + tmpStopAtStr string + ) var rootCommand = &cobra.Command{ Use: "smart-shutdown", @@ -56,16 +85,94 @@ func main() { return } - err := svc.Run() - if err != nil { - logger.Fail("后台监控流崩溃: %v", err) + if !service.Interactive() { + err := svc.Run() + if err != nil { + logger.Fail("后台监控流崩溃: %v", err) + } + return } + + // Interactive foreground mode + logger.SwitchToFrontLog() + + if tmpTargetIP != "" { + if net.ParseIP(tmpTargetIP) != nil { + cfg.TargetIP = tmpTargetIP + } else { + logger.Fail("参数解析错误: 非法的 IP 地址 %s", tmpTargetIP) + return + } + } + if tmpWindowSec > 0 { cfg.MonitorWindowSeconds = tmpWindowSec } + if tmpShutdownCnt > 0 { cfg.ShutdownCountdown = tmpShutdownCnt } + if tmpPingInt > 0 { cfg.NormalPingInterval = tmpPingInt } + + if tmpOverrideBg { + status, _ := svc.Status() + if status == service.StatusRunning { + logger.Info("检测到后台服务运行中,正在挂起以接管前台监控...") + service.Control(svc, "stop") + defer func() { + logger.Info("前台监控结束,恢复后台服务...") + service.Control(svc, "start") + }() + } + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + importTime := false + var earliest = time.Now().Add(100 * 365 * 24 * time.Hour) // very far + if tmpStopAfterStr != "" { + d, err := time.ParseDuration(tmpStopAfterStr) + if err != nil { + logger.Fail("解析持续时间失败 (如 120m, 2h): %v", err) + return + } + t := time.Now().Add(d) + if t.Before(earliest) { earliest = t; importTime = true } + } + if tmpStopAtStr != "" { + layout := "2006-01-02 15:04:05" + t, err := time.Parse(layout, tmpStopAtStr) + if err != nil { + logger.Fail("解析绝对时间失败 (格式需为 2006-01-02 15:04:05): %v", err) + return + } + if t.Before(earliest) { earliest = t; importTime = true } + } + + if importTime { + if earliest.Before(time.Now()) { + logger.Fail("指定的结束时间不能早于当前时间") + cancel() + return + } + var deadlineCancel context.CancelFunc + ctx, deadlineCancel = context.WithDeadline(ctx, earliest) + defer deadlineCancel() + logger.Info("已设定前台监控截止退出时间: %s", earliest.Format("2006-01-02 15:04:05")) + } + + logger.Info("开始执行前台监控进程...") + monitor.Run(ctx, cfg) }, } rootCommand.CompletionOptions.DisableDefaultCmd = true rootCommand.Flags().BoolVarP(&showVersion, "version", "V", false, "输出当前二进制内核版本并联网拉取发布树状态") rootCommand.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "打印底层部署及环境追溯 Debug 信息") + rootCommand.PersistentFlags().StringVarP(&configPath, "config", "c", "", "指定自定义配置文件路径 (如 ./example_config.json)") + + rootCommand.Flags().StringVar(&tmpTargetIP, "target-ip", "", "临时覆盖: 目标监控 IP") + rootCommand.Flags().IntVar(&tmpWindowSec, "window-sec", 0, "临时覆盖: 容忍超时时长 (秒)") + rootCommand.Flags().IntVar(&tmpShutdownCnt, "shutdown-cnt", 0, "临时覆盖: 关机倒计时 (秒)") + rootCommand.Flags().IntVar(&tmpPingInt, "ping-interval", 0, "临时覆盖: 发包间隔 (秒)") + rootCommand.Flags().BoolVar(&tmpOverrideBg, "override-bg", false, "挂起后台运行的守护进程,由前台全面接管") + rootCommand.Flags().StringVar(&tmpStopAfterStr, "stop-after", "", "前台运行多少时长后自动退出 (如 120m, 2h)") + rootCommand.Flags().StringVar(&tmpStopAtStr, "stop-at", "", "前台运行至指定时间自动退出 (如 '2026-03-24 23:59:00')") cmds := []*cobra.Command{ { @@ -277,8 +384,63 @@ func main() { }, } + var pauseCmd = &cobra.Command{ + Use: "pause", + Short: "临时休眠后台运行的守护进程(不开启前台监控)", + Run: func(cmd *cobra.Command, args []string) { + if tmpStopAfterStr == "" && tmpStopAtStr == "" { + logger.Fail("必须指定 --stop-after 或 --stop-at 参数才能执行休眠指令") + return + } + + var until = time.Now().Add(100 * 365 * 24 * time.Hour) + if tmpStopAfterStr != "" { + d, err := time.ParseDuration(tmpStopAfterStr) + if err != nil { + logger.Fail("解析持续时间失败: %v", err) + return + } + t := time.Now().Add(d) + if t.Before(until) { until = t } + } + if tmpStopAtStr != "" { + layout := "2006-01-02 15:04:05" + t, err := time.Parse(layout, tmpStopAtStr) + if err != nil { + logger.Fail("解析绝对时间失败: %v", err) + return + } + if t.Before(until) { until = t } + } + + if until.Before(time.Now()) { + logger.Fail("指定的唤醒时间不能早于当前时间") + return + } + + if err := config.SetPause(until); err != nil { + logger.Fail("写入休眠指令失败: %v", err) + return + } + logger.Succ("指令下达成功!后台守护进程将挂机休息至 %s", until.Format("2006-01-02 15:04:05")) + }, + } + pauseCmd.Flags().StringVar(&tmpStopAfterStr, "stop-after", "", "休眠多少时长后自动唤醒 (如 120m, 2h)") + pauseCmd.Flags().StringVar(&tmpStopAtStr, "stop-at", "", "休眠至指定时间自动唤醒 (如 '2026-03-24 23:59:00')") + + var resumeCmd = &cobra.Command{ + Use: "resume", + Short: "撤销休眠指令,立即唤醒后台网络探测", + Run: func(cmd *cobra.Command, args []string) { + config.ClearPause() + logger.Succ("休眠指令已撤销,后台将在下一轮探测周期中重新激活!") + }, + } + configCmd.AddCommand(configSetCmd) rootCommand.AddCommand(configCmd) + rootCommand.AddCommand(pauseCmd) + rootCommand.AddCommand(resumeCmd) for _, c := range cmds { rootCommand.AddCommand(c) diff --git a/example_config.json b/example_config.json new file mode 100644 index 0000000..24daa01 --- /dev/null +++ b/example_config.json @@ -0,0 +1,6 @@ +{ + "TargetIP": "192.168.3.1", + "MonitorWindowSeconds": 180, + "ShutdownCountdown": 60, + "NormalPingInterval": 15 +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 49e3bb3..a2ac901 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -43,7 +43,14 @@ func GetLogDir() string { func LoadConfig() (*Config, error) { configDir := GetConfigDir() configPath := filepath.Join(configDir, "config.json") + return loadConfigFile(configPath) +} +func LoadConfigFrom(path string) (*Config, error) { + return loadConfigFile(path) +} + +func loadConfigFile(configPath string) (*Config, error) { data, err := os.ReadFile(configPath) if err != nil { if os.IsNotExist(err) { diff --git a/pkg/config/pause.go b/pkg/config/pause.go new file mode 100644 index 0000000..b884c6d --- /dev/null +++ b/pkg/config/pause.go @@ -0,0 +1,52 @@ +package config + +import ( + "os" + "path/filepath" + "strconv" + "strings" + "time" +) + +// SetPause writes a timestamp into pause_until.txt +func SetPause(until time.Time) error { + dir := GetConfigDir() + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + path := filepath.Join(dir, "pause_until.txt") + ts := strconv.FormatInt(until.Unix(), 10) + return os.WriteFile(path, []byte(ts), 0644) +} + +// ClearPause removes the pause_until.txt file +func ClearPause() error { + path := filepath.Join(GetConfigDir(), "pause_until.txt") + return os.Remove(path) +} + +// IsPaused checks if the service is currently paused via IPC file +func IsPaused() (bool, int64) { + path := filepath.Join(GetConfigDir(), "pause_until.txt") + data, err := os.ReadFile(path) + if err != nil { + return false, 0 + } + + tsStr := strings.TrimSpace(string(data)) + untilUnix, err := strconv.ParseInt(tsStr, 10, 64) + if err != nil { + // Invalid file, clean it up + os.Remove(path) + return false, 0 + } + + now := time.Now().Unix() + if now < untilUnix { + return true, untilUnix - now + } + + // Expired + os.Remove(path) + return false, 0 +} diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index a6b14f5..c94864c 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -34,6 +34,24 @@ func InitLogger() error { return nil } +func SwitchToFrontLog() { + if fileLogger != nil { + fileLogger.Close() + } + logDir := config.GetLogDir() + if err := os.MkdirAll(logDir, 0755); err != nil { + logDir = "logs" + } + logFile := filepath.Join(logDir, "network_monitor_front.log") + fileLogger = &lumberjack.Logger{ + Filename: logFile, + MaxSize: 10, + MaxBackups: 30, + MaxAge: 30, + Compress: false, + } +} + func EnableDebug() { debugEnabled = true } diff --git a/pkg/monitor/statemachine.go b/pkg/monitor/statemachine.go index 66bb199..c3edc1f 100644 --- a/pkg/monitor/statemachine.go +++ b/pkg/monitor/statemachine.go @@ -18,6 +18,7 @@ func Run(ctx context.Context, cfg *config.Config) { var failureStartTime time.Time var inFailureWindow bool = false + var wasPausedBefore bool = false normalStatusCounter := 0 const normalStatusLogInterval = 24 @@ -30,6 +31,27 @@ func Run(ctx context.Context, cfg *config.Config) { default: } + isPaused, remainingSec := config.IsPaused() + if isPaused { + if !wasPausedBefore { + mins := remainingSec / 60 + if mins < 1 { + mins = 1 + } + logger.Info("进入管理员特设休眠挂起状态, 预计将在 %d 分钟后自动唤醒", mins) + wasPausedBefore = true + if inFailureWindow { + inFailureWindow = false + normalStatusCounter = 0 + } + } + time.Sleep(15 * time.Second) + continue + } else if wasPausedBefore { + logger.Info("休眠指令逾期或已被撤消, 唤醒并恢复网络探测") + wasPausedBefore = false + } + isOnline := pinger.Ping(cfg.TargetIP, 3) if isOnline {