9 Commits

Author SHA1 Message Date
8ce1e6531a feat: add foreground mode, pause/resume commands, API docs 2026-03-25 00:12:14 +08:00
b00f95b7fb fix: uninstall now properly cleans files and PATH, stops on permission error, separate config/log cleanup prompts 2026-03-24 23:02:52 +08:00
850cc49daf feat: add interactive config setup on first start, install scripts, and uninstall cleanup 2026-03-24 22:46:45 +08:00
35bf24f0f3 feat(cli): add -v debug flags to trace file writes and env path creations during deployment 2026-03-24 17:16:58 +08:00
b1c7b9f8f7 feat: implement smart self-updater with robust windows file-lock bypasses and semantic version parsing 2026-03-24 17:09:28 +08:00
4e2aba5560 fix(ci): restrict gitignore to root binary files to avoid ignoring source subdirectories
Some checks failed
Release / Build & Release (push) Has been cancelled
2026-03-24 16:19:08 +08:00
3be3e19e49 release: v0.2.0
Some checks failed
Release / Build & Release (push) Has been cancelled
Comprehensive release containing structural, UX, and behavioral upgrades since v0.1.0:

1. Namespace Transition: Renamed core executable and project namespaces from 'smart-monitor' to 'smart-shutdown'.
2. Objective Vocabulary Refactoring: Normalized output strings and logging descriptors system-wide to a strict, professional tone.
3. Advanced Status Query: 'status' subcommand now retrieves the parsed configuration, log locations/sizes, and tails the last 10 lines of the local log file.
4. Runtime Configuration Setter: Introduced 'config set' subcommand to modify the configuration file with strict type validations.
5. Auto-System Deployment: Remapped 'install' to clone the executable into system domains and register global PATH variables.
6. Cleaner Removal: 'uninstall' purges binary clones and clears environmental variables, leaving zero traces.
7. Documentation Rewrite: Generated an objective README file featuring copy-paste ready Markdown blocks.
2026-03-24 16:07:22 +08:00
3525a59976 chore: add GitHub Actions release workflow, MIT license, and update gitignore
Some checks failed
Release / Build & Release (push) Has been cancelled
2026-03-24 14:51:42 +08:00
f288be2a63 feat(migration): upgrade legacy ps1 scripts to single-binary Golang platform 2026-03-24 14:37:44 +08:00
30 changed files with 1871 additions and 3002 deletions

53
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: Release
on:
push:
tags:
- 'v*' # 褰撴帹閫佹爣绛惧舰濡?v1.0.0 鏃惰Е鍙戣嚜鍔ㄦ瀯寤?
permissions:
contents: write
jobs:
build:
name: Build & Release
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
cache: true
- name: Download Dependencies
run: go mod download
- name: Compile Cross-Platform Binaries
run: |
mkdir -p build_output
echo "Building Windows amd64..."
GOOS=windows GOARCH=amd64 go build -ldflags="-X 'main.AppVersion=${{ github.ref_name }}' -s -w" -o build_output/smart-shutdown_windows_amd64.exe ./cmd/smart-shutdown
echo "Building Windows arm64..."
GOOS=windows GOARCH=arm64 go build -ldflags="-X 'main.AppVersion=${{ github.ref_name }}' -s -w" -o build_output/smart-shutdown_windows_arm64.exe ./cmd/smart-shutdown
echo "Building Linux amd64..."
GOOS=linux GOARCH=amd64 go build -ldflags="-X 'main.AppVersion=${{ github.ref_name }}' -s -w" -o build_output/smart-shutdown_linux_amd64 ./cmd/smart-shutdown
echo "Building Linux arm64..."
GOOS=linux GOARCH=arm64 go build -ldflags="-X 'main.AppVersion=${{ github.ref_name }}' -s -w" -o build_output/smart-shutdown_linux_arm64 ./cmd/smart-shutdown
- name: Publish GitHub Release
uses: softprops/action-gh-release@v1
with:
files: build_output/*
draft: false
prerelease: false
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

27
.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output directory for cross-platform builds
/bin/
/build/
/smart-shutdown
/smart-shutdown.exe
# Output of the go coverage tool
*.out
# IDE configs
.idea/
.vscode/
# Local logs or configuration testing directories
logs/
*.log
config.json

191
API.md Normal file
View File

@@ -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 <Key> <Value>`
直接修改指定配置项,并执行合法性校验。改写后需执行 `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.860 秒无响应触发
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 天。

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 UnbalancedCat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

199
README.md
View File

@@ -1,122 +1,103 @@
# 智能网络监控脚本 - 系统部署版 # Smart Network Shutdown Monitor
## 📁 文件结构 持续监测目标网络连通性并在网络断开超时后自动关闭系统的跨平台常驻服务程序。
``` > **注意:** 本程序涉及系统服务注册与网络监控,所有安装、卸载及服务管理操作均**必须在管理员 (Windows) 或 root (Linux) 权限**下执行。
smart_shutdown/
├── smart_shutdown.ps1 # 原始监控脚本(用户模式)
├── deploy_system.ps1 # 系统级部署脚本
├── uninstall_system.ps1 # 系统级卸载脚本
├── TEST_REPORT.md # 测试报告
├── README.md # 本文件
└── logs/ # 本地测试日志目录
└── network_monitor_20250606.log
```
## 🚀 快速部署 ## 安装指南 (Installation)
### 系统级部署(推荐) 本程序支持安装为操作系统的后台服务,并自动注册全局环境变量以便统一管理。
```powershell
# 以管理员身份运行
.\deploy_system.ps1
```
**部署效果:** ### Windows 系统
- 程序部署到:`C:\Program Files\SmartNetworkMonitor\`
- 配置和日志:`C:\ProgramData\SmartNetworkMonitor\`
- 开机自启动,无需用户登录
- 以 SYSTEM 权限运行
### 用户级运行 **以管理员身份打开 PowerShell**,执行以下一键安装命令:
```powershell
# 以管理员身份运行(需要用户登录)
.\smart_shutdown.ps1
```
## 🎯 系统部署后的文件位置
### 程序文件位置
```
C:\Program Files\SmartNetworkMonitor\
├── smart_shutdown_system.ps1 # 系统优化版监控脚本
└── manage.ps1 # 管理工具脚本
```
### 数据文件位置
```
C:\ProgramData\SmartNetworkMonitor\
├── config.json # 配置文件
└── logs\ # 系统日志目录
└── network_monitor_YYYYMMDD.log
```
## 🔧 管理命令
### 查看任务状态
```powershell
Get-ScheduledTask -TaskName "Smart Network Shutdown Monitor"
```
### 启动/停止监控
```powershell
Start-ScheduledTask -TaskName "Smart Network Shutdown Monitor"
Stop-ScheduledTask -TaskName "Smart Network Shutdown Monitor"
```
### 查看日志
```powershell
# 查看今天的日志
Get-Content "C:\ProgramData\SmartNetworkMonitor\logs\network_monitor_$(Get-Date -Format 'yyyyMMdd').log"
# 实时监控日志
Get-Content "C:\ProgramData\SmartNetworkMonitor\logs\network_monitor_$(Get-Date -Format 'yyyyMMdd').log" -Wait
```
### 查看系统事件日志
```powershell
Get-EventLog -LogName Application -Source SmartNetworkMonitor -Newest 20
```
## ⚙️ 配置说明
系统部署后,配置文件位于:`C:\ProgramData\SmartNetworkMonitor\config.json`
默认配置:
```json
{
"TargetIP": "192.168.3.3",
"MonitorWindowSeconds": 180,
"ShutdownCountdown": 60,
"NormalPingInterval": 15
}
```
修改配置文件后,重启监控任务使配置生效:
```powershell
Stop-ScheduledTask -TaskName "Smart Network Shutdown Monitor"
Start-ScheduledTask -TaskName "Smart Network Shutdown Monitor"
```
## 🗑️ 卸载
```powershell ```powershell
# 以管理员身份运行 irm https://raw.githubusercontent.com/UnbalancedCat/smart-shutdown/main/install.ps1 | iex
.\uninstall_system.ps1
``` ```
## 📊 特性 > **说明:** 该命令会自动下载最新版本,执行 `install` 将程序复制到 `C:\Program Files\SmartShutdown\` 并写入系统 `PATH` 环境变量,同时注册为 Windows 开机自启服务。此后您可在任意管理员终端直接使用 `smart-shutdown` 命令。
- ✅ 开机自启动,无需用户登录 ### Linux (Ubuntu/Debian/CentOS 等)
- ✅ 系统级权限运行
- ✅ 智能日志管理
- ✅ 配置文件支持
- ✅ 事件日志备份
- ✅ 自动日志清理30天
- ✅ 网络恢复自动取消关机
- ✅ 完整的错误处理
## 🔐 安全说明 在终端中执行以下一键安装命令:
- 脚本以 SYSTEM 权限运行,具有执行关机的完整权限 ```bash
- 所有操作都会记录在日志和系统事件日志中 curl -sSL https://raw.githubusercontent.com/UnbalancedCat/smart-shutdown/main/install.sh | sudo sh
- 支持通过任务计划程序进行管理和监控 ```
## 快速开始
安装完成后,默认会自动运行如下引导,进行初始设置:
```
========== 首次配置 ==========
未检测到配置文件,将引导您完成初始设置。直接按回车使用 [默认值]。
目标监控 IP 地址 [192.168.3.1]: <输入您的目标 IP 或直接回车>
断网容忍超时时长 (秒) [180]:
关机倒计时缓冲 (秒) [60]:
探测发包间隔 (秒) [15]:
配置已保存至: C:\ProgramData\SmartNetworkMonitor\config.json
```
> **提示:** 服务注册后将**随系统开机自动启动**,无需手动干预。如需暂停监控,请执行 `smart-shutdown stop`。
## Windows 注意事项
如您在 Windows 上使用 `sudo` 命令Windows 11 24H2+ 内置)来执行本程序,请注意:
Windows `sudo` 默认运行在 **ForceNewWindow强制新窗口** 模式下,提权进程的输出将打印到一个瞬间关闭的新窗口中,**导致 `status` 等展示类命令的输出不可见。**
**解决方法(任选其一):**
1. **切换 sudo 为 Inline 模式**:打开 **系统设置 → 开发者选项 → 启用 sudo**,将模式改为 **内联 (Inline)**。此后 `sudo smart-shutdown status` 的输出将正常回显至当前终端。
2. **直接使用管理员终端**:右键点击终端图标,选择 **以管理员身份运行**,随后无需 `sudo` 前缀即可执行所有指令。
## 命令参考
完整的命令说明、参数列表与使用示例,请查阅 [API.md](./API.md)。
常用快捷参考:
| 命令 | 说明 |
|:---|:---|
| `smart-shutdown start` | 启动后台监控服务 |
| `smart-shutdown stop` | 停止后台监控服务 |
| `smart-shutdown restart` | 重启服务 |
| `smart-shutdown status` | 查看服务状态与最近日志 |
| `smart-shutdown config set <Key> <Value>` | 修改配置项 |
| `smart-shutdown pause --stop-after 2h` | 临时挂起后台 2 小时 |
| `smart-shutdown resume` | 立即恢复挂起的后台 |
| `smart-shutdown uninstall` | 卸载服务及环境变量 |
## 免安装便携使用与前台监控模式
如果您只是想临时使用网络检测自动关机功能,而**不想将程序安装为系统常驻服务**,您可以直接下载可执行文件进行免安装的便携式运行(基于前台模式)。
前往 [Releases](https://github.com/UnbalancedCat/smart-shutdown/releases) 页面下载对应您系统的可执行文件后,在同一目录下的终端中,直接带参数执行即可。例如:
```bash
# 临时在前台启动监控(非后台服务),检测目标 IP 8.8.8.8,脱机容忍设为 60 秒
.\smart-shutdown --target-ip 8.8.8.8 --window-sec 60
```
> **提示:**
> 1. 前台模式下产生的日志将单独写入 `network_monitor_front.log`,不会影响原有数据。关闭当前终端即可停止监控进程。
> 2. 关于前台模式的 `--stop-after` (设定运行多少时间后退出)、`--override-bg` (接管正在运行的后台服务) 等高阶参数详细说明,请参阅 [API.md](./API.md)。
**扩展:手动安装为服务**
如果您下载了二进制文件后改变主意,想手动将其注册为系统服务,也可以直接使用 `install` 命令:
- **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
```

View File

@@ -1,290 +0,0 @@
# 智能网络监控脚本 - Ubuntu版本
## 📁 项目结构
```
smart-shutdown/
├── smart_shutdown.sh # 主监控脚本(用户模式)
├── deploy_system.sh # 系统级部署脚本
├── uninstall_system.sh # 系统级卸载脚本
├── manage.sh # 管理工具脚本
├── README.md # 本文件
└── logs/ # 本地运行时的日志目录
└── network_monitor_YYYYMMDD.log
```
## 🚀 快速开始
### 系统级部署(推荐)
```bash
# 1. 克隆或下载项目到本地
cd /path/to/smart-shutdown/
# 2. 以root权限运行部署脚本
sudo ./deploy_system.sh
```
**部署效果:**
- 程序文件:`/opt/smart-network-monitor/`
- 配置文件:`/etc/smart-network-monitor/config.json`
- 日志文件:`/var/log/smart-network-monitor/`
- systemd服务`smart-network-monitor.service`
- 开机自启动,无需用户登录
### 用户级运行
```bash
# 以root权限直接运行需要用户登录
sudo ./smart_shutdown.sh
```
## 🎯 系统部署后的文件位置
### 程序文件位置
```
/opt/smart-network-monitor/
├── smart_shutdown_system.sh # 系统优化版监控脚本
└── manage.sh # 管理工具脚本
```
### 配置文件位置
```
/etc/smart-network-monitor/
└── config.json # 配置文件
```
### 日志文件位置
```
/var/log/smart-network-monitor/
└── network_monitor_YYYYMMDD.log # 日志文件
```
## 🔧 管理命令
### 使用管理工具(推荐)
```bash
# 使用快捷命令(系统安装后可用)
smart-monitor
# 或者直接运行管理脚本
sudo /opt/smart-network-monitor/manage.sh
```
### 直接使用systemctl命令
#### 查看服务状态
```bash
systemctl status smart-network-monitor
```
#### 启动/停止/重启服务
```bash
sudo systemctl start smart-network-monitor
sudo systemctl stop smart-network-monitor
sudo systemctl restart smart-network-monitor
```
#### 启用/禁用开机自启动
```bash
sudo systemctl enable smart-network-monitor
sudo systemctl disable smart-network-monitor
```
### 查看日志
#### 查看今天的应用日志
```bash
cat /var/log/smart-network-monitor/network_monitor_$(date '+%Y%m%d').log
```
#### 实时监控应用日志
```bash
tail -f /var/log/smart-network-monitor/network_monitor_$(date '+%Y%m%d').log
```
#### 查看系统日志
```bash
# 查看最近的系统日志
journalctl -u smart-network-monitor -n 50
# 实时监控系统日志
journalctl -u smart-network-monitor -f
```
## ⚙️ 配置说明
系统部署后,配置文件位于:`/etc/smart-network-monitor/config.json`
### 默认配置
```json
{
"TargetIP": "192.168.3.3",
"MonitorWindowSeconds": 180,
"ShutdownCountdown": 60,
"NormalPingInterval": 15
}
```
### 配置参数说明
- `TargetIP`: 监控的目标IP地址
- `MonitorWindowSeconds`: 监控窗口时长(秒),网络持续中断超过此时间将触发关机
- `ShutdownCountdown`: 关机倒计时时长(秒)
- `NormalPingInterval`: 正常监控时的ping间隔
### 修改配置
```bash
# 使用管理工具编辑(推荐)
smart-monitor
# 或直接编辑配置文件
sudo nano /etc/smart-network-monitor/config.json
# 修改配置后重启服务使配置生效
sudo systemctl restart smart-network-monitor
```
## 🗑️ 卸载
```bash
# 运行卸载脚本
sudo ./uninstall_system.sh
```
卸载脚本会询问是否删除配置文件和日志文件,您可以选择保留或删除。
## 📊 功能特性
- ✅ 开机自启动,无需用户登录
- ✅ 系统级权限运行
- ✅ 智能日志管理(应用日志 + 系统日志)
- ✅ JSON配置文件支持
- ✅ 自动日志清理30天
- ✅ 网络恢复自动取消关机
- ✅ 完整的错误处理和信号处理
- ✅ 彩色终端输出
- ✅ 交互式管理工具
## 🔧 系统要求
### 必需软件包
- `ping` (通常已预装)
- `systemctl` (systemd)
- `journalctl` (systemd)
### 推荐软件包
```bash
# 安装jq以获得更好的JSON配置支持
sudo apt-get update
sudo apt-get install jq
```
### 支持的系统
- Ubuntu 16.04+ (带systemd)
- Debian 8+ (带systemd)
- 其他使用systemd的Linux发行版
## 🔐 安全说明
- 脚本需要root权限以执行关机操作
- systemd服务以root权限运行
- 所有操作都会记录在应用日志和系统日志中
- 支持通过systemd进行完整的服务管理和监控
## 📝 使用示例
### 1. 基本使用流程
```bash
# 1. 部署系统级服务
sudo ./deploy_system.sh
# 2. 检查服务状态
systemctl status smart-network-monitor
# 3. 查看实时日志
journalctl -u smart-network-monitor -f
# 4. 使用管理工具
smart-monitor
```
### 2. 自定义配置示例
```bash
# 编辑配置文件
sudo nano /etc/smart-network-monitor/config.json
# 修改为监控路由器
{
"TargetIP": "192.168.1.1",
"MonitorWindowSeconds": 300,
"ShutdownCountdown": 120,
"NormalPingInterval": 30
}
# 重启服务应用配置
sudo systemctl restart smart-network-monitor
```
### 3. 故障排除
```bash
# 查看服务详细状态
systemctl status smart-network-monitor -l
# 查看最近的错误日志
journalctl -u smart-network-monitor --since "1 hour ago"
# 测试网络连接
ping -c 4 192.168.3.3
# 手动运行脚本进行调试
sudo /opt/smart-network-monitor/smart_shutdown_system.sh
```
## 🚨 注意事项
1. **关机权限**脚本需要root权限才能执行关机操作
2. **网络依赖**确保目标IP地址可达且稳定响应ping
3. **测试模式**:默认脚本在关机时会输出模拟信息,取消注释 `shutdown -h now` 行以启用实际关机
4. **备份重要数据**:在启用实际关机功能前,请确保重要数据已备份
5. **防火墙设置**确保ICMP ping包不被防火墙阻挡
## 🔗 相关命令速查
```bash
# 服务管理
sudo systemctl start smart-network-monitor # 启动
sudo systemctl stop smart-network-monitor # 停止
sudo systemctl restart smart-network-monitor # 重启
sudo systemctl status smart-network-monitor # 状态
sudo systemctl enable smart-network-monitor # 开机启动
sudo systemctl disable smart-network-monitor # 禁用启动
# 日志查看
journalctl -u smart-network-monitor # 所有日志
journalctl -u smart-network-monitor -f # 实时日志
journalctl -u smart-network-monitor -n 50 # 最近50行
tail -f /var/log/smart-network-monitor/network_monitor_$(date '+%Y%m%d').log # 应用日志
# 配置管理
sudo nano /etc/smart-network-monitor/config.json # 编辑配置
cat /etc/smart-network-monitor/config.json # 查看配置
# 管理工具
smart-monitor # 交互式管理
```
## 📞 技术支持
如果您在使用过程中遇到问题,请:
1. 检查系统日志:`journalctl -u smart-network-monitor -n 100`
2. 验证网络连接:`ping 目标IP`
3. 检查配置文件格式:`jq . /etc/smart-network-monitor/config.json`
4. 查看服务状态:`systemctl status smart-network-monitor -l`
---
**享受智能网络监控带来的便利!** 🎉

View File

@@ -1,113 +0,0 @@
# 智能网络监控脚本测试报告
## 测试日期
2025年6月6日
## 脚本概述
这是一个PowerShell智能网络监控脚本用于监控指定IP的网络连通性当网络持续中断超过设定时间后自动关闭计算机。
## 主要功能
1. **网络连通性监控** - 定期ping目标IP地址
2. **智能故障检测** - 区分临时网络波动和持续故障
3. **可配置监控窗口** - 防止因短暂网络中断导致误关机
4. **关机倒计时机制** - 提供最后的取消机会
5. **完整日志记录** - 记录所有操作和状态变化
6. **手动取消功能** - 用户可以随时按键取消关机
## 配置参数(原版)
- **目标IP**: 192.168.3.3
- **正常监控间隔**: 15秒
- **监控窗口**: 180秒3分钟
- **关机倒计时**: 60秒
- **倒计时ping间隔**: 3秒
- **Ping超时**: 3秒
## 测试配置(测试版)
- **目标IP**: 192.168.3.99不存在的IP模拟网络故障
- **正常监控间隔**: 3秒
- **监控窗口**: 15秒
- **关机倒计时**: 10秒
- **倒计时ping间隔**: 2秒
## 修复的问题
1. **兼容性问题**: 修复了`Test-Connection`命令在旧版PowerShell中不支持`-TimeoutSeconds`参数的问题
2. **编码问题**: 将特殊Unicode字符✓ ✗ 替换为ASCII字符避免控制台显示乱码
## 测试结果
### ✅ 功能测试全部通过
#### 1. 脚本启动功能
- ✅ 正常启动和初始化
- ✅ 日志系统正常工作
- ✅ 配置参数正确加载
- ✅ 本地IP获取功能正常
#### 2. 网络监控功能
- ✅ 正常IP连通性检测如8.8.8.8
- ✅ 故障IP检测如192.168.3.99
- ✅ 网络异常处理机制
- ✅ Ping命令兼容性修复
#### 3. 监控窗口机制
- ✅ 首次网络中断检测
- ✅ 监控窗口计时功能
- ✅ 剩余时间计算和显示
- ✅ 监控窗口超时触发
#### 4. 关机倒计时功能
- ✅ 倒计时启动机制
- ✅ 倒计时期间网络快速检测
- ✅ 按键手动取消功能
- ✅ 模拟关机操作
#### 5. 日志记录功能
- ✅ 日志目录自动创建
- ✅ 按日期分类的日志文件
- ✅ 完整的操作记录
- ✅ 不同级别的日志分类INFO、WARN、CRITICAL、FAIL、SUCCESS
#### 6. 用户界面和显示
- ✅ 彩色控制台输出
- ✅ 清晰的状态提示
- ✅ 编码问题修复
- ✅ 用户友好的信息显示
## 实际测试执行记录
### 测试113:02:43开始
- 监控窗口15秒
- 实际触发时间18秒后进入关机倒计时
- 关机倒计时10秒
- 结果:正常完成模拟关机,重置状态继续监控
### 测试213:08:30开始
- 同样配置重复测试
- 确认功能稳定性和一致性
- 编码问题已完全修复
## 实际使用建议
### 安全配置
1. **取消注释关机命令**:在`smart_shutdown.ps1`第217行`# Stop-Computer -Force`的注释去掉
2. **以管理员身份运行**:脚本需要管理员权限执行关机命令
3. **测试配置**:建议先用较短时间参数测试,确认无误后再使用正式配置
### 最佳实践
1. **监控窗口设置**建议设置为3-5分钟避免网络短暂波动造成误关机
2. **目标IP选择**建议使用网关IP或重要服务器IP
3. **日志监控**:定期检查日志文件,了解网络状况
4. **备用方案**考虑配置多个目标IP进行冗余检测
## 结论
**脚本功能完整,测试全部通过,可以安全投入使用。**
经过全面测试,智能网络监控脚本各项功能均正常工作:
- 网络监控准确可靠
- 故障检测机制有效
- 用户交互友好
- 日志记录完整
- 编码问题已解决
- 兼容性问题已修复
脚本已准备好在生产环境中使用,能够有效监控网络连接并在必要时自动关闭计算机。

535
cmd/smart-shutdown/main.go Normal file
View File

@@ -0,0 +1,535 @@
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 {
fmt.Printf("无法初始化日志系统: %v\n", err)
os.Exit(1)
}
// 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)
}
svc, err := daemon.GetService(cfg)
if err != nil {
logger.Crit("构建后台服务实例失败: %v", err)
os.Exit(1)
}
var (
showVersion bool
tmpTargetIP string
tmpWindowSec int
tmpShutdownCnt int
tmpPingInt int
tmpOverrideBg bool
tmpStopAfterStr string
tmpStopAtStr string
)
var rootCommand = &cobra.Command{
Use: "smart-shutdown",
Short: "智能网络状态检测与自动关机后台服务",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if verbose {
logger.EnableDebug()
}
},
Run: func(cmd *cobra.Command, args []string) {
if showVersion {
fmt.Printf("========== Smart Network Shutdown Monitor ==========\n")
fmt.Printf("执行架构体基准: %s/%s\n", runtime.GOOS, runtime.GOARCH)
updater.CheckAndPrintUpdate(AppVersion)
return
}
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{
{
Use: "install",
Short: "将执行文件拷贝至系统目录并全局注入环境变量体系",
Run: func(cmd *cobra.Command, args []string) {
targetDir, targetExe := getTargetSystemPath()
currentExe, _ := os.Executable()
if !strings.EqualFold(filepath.Clean(currentExe), filepath.Clean(targetExe)) {
logger.Debug("检测到跨域部署: 待本体克隆存放底层根目录: %s", targetExe)
if err := os.MkdirAll(targetDir, 0755); err != nil {
logger.Fail("创建系统目录核心区路径溃败 (请核查最高系统权限身份执行需求): %v", err)
return
}
if err := copyFile(currentExe, targetExe); err != nil {
logger.Fail("注入独立执行程序副本被拒: %v", err)
return
}
if runtime.GOOS != "windows" {
os.Chmod(targetExe, 0755)
} else {
envSetupCmd := fmt.Sprintf(`$p=[Environment]::GetEnvironmentVariable("Path","Machine");if(-not($p -split ';' -contains "%s")){[Environment]::SetEnvironmentVariable("Path",$p+";%s","Machine")}`, targetDir, targetDir)
err := exec.Command("powershell", "-Command", envSetupCmd).Run()
if err != nil {
logger.Warn("改写操作系统的环境变量集合遭受阻拦: %v", err)
} else {
logger.Debug("成功挂载底层环境变量: Windows Machine Path 追加了 %s 指引。", targetDir)
}
}
}
targetSvc, err := daemon.GetService(cfg, targetExe)
if err != nil {
logger.Crit("获取安装节点子域结构体构建回执失败: %v", err)
return
}
handleServiceControl(targetSvc, "install")
},
},
{
Use: "uninstall",
Short: "废除并移除在册的后台服务、扫除环境关联残留",
Run: func(cmd *cobra.Command, args []string) {
service.Control(svc, "stop")
if !handleServiceControl(svc, "uninstall") {
return
}
targetDir, targetExe := getTargetSystemPath()
currentExe, _ := os.Executable()
isSelfExe := strings.EqualFold(filepath.Clean(currentExe), filepath.Clean(targetExe))
// Clean up PATH (Windows)
if runtime.GOOS == "windows" {
envClearCmd := fmt.Sprintf(`$p=[Environment]::GetEnvironmentVariable("Path","Machine");$np=($p -split ';' | Where-Object {$_ -ne "%s" -and $_ -ne ""}) -join ';';[Environment]::SetEnvironmentVariable("Path",$np,"Machine")`, targetDir)
exec.Command("powershell", "-Command", envClearCmd).Run()
logger.Debug("环境变量系统清理Windows Global Path 中的指向挂载项业已剥除卸载完成。")
}
// Delete executable
if _, err := os.Stat(targetExe); err == nil {
if isSelfExe && runtime.GOOS == "windows" {
// Windows: cannot delete a running exe, use delayed self-deletion
delCmd := fmt.Sprintf(`ping 127.0.0.1 -n 2 > nul & del "%s" & rmdir "%s"`, targetExe, targetDir)
exec.Command("cmd", "/C", "start", "/min", "cmd", "/C", delCmd).Start()
logger.Debug("已调度延迟自删除任务: %s", targetExe)
} else {
os.Remove(targetExe)
if runtime.GOOS == "windows" {
os.Remove(targetDir)
}
logger.Debug("已移除可执行文件: %s", targetExe)
}
}
// Prompt for config and log cleanup
config.ConfirmAndCleanup()
},
},
{
Use: "update",
Short: "获取并免干预热部署在线的最新系统构建程序",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("当前内核架构版本: %s\n正在向全向节点请求效验最新发版记录...\n", AppVersion)
hasNew, latestVer, dlURL := updater.CheckForUpdate(AppVersion)
if !hasNew {
fmt.Println("\n[核验完毕] 您当前的程序正处于主线分支顶点,无需任何更新动作。")
return
}
fmt.Printf("\n[匹配成功] 查收最新稳定版释出包裹: %s\n", latestVer)
fmt.Println("预备接管环境树并构建热更新映射管道...")
status, _ := svc.Status()
isRunningService := (status == service.StatusRunning)
if isRunningService {
fmt.Println("[生命周期管控] 已探明应用正交由系统守护树作为后台运行,正在为其下放休眠截停指派以退换抢驻的内核独占锁...")
service.Control(svc, "stop")
}
fmt.Println("[数据流传输] 正在拉取远端预编译二进制核心封包...")
if err := updater.DownloadAndReplace(dlURL); err != nil {
fmt.Printf("[中断] 内核代码层执行热重载遭挫回滚: %v\n", err)
if isRunningService {
service.Control(svc, "start")
}
return
}
fmt.Println("[实体部署] 核心节点原子级替换覆写完成,系统块检验通过!")
if isRunningService {
fmt.Println("[后端苏醒] 重新挂载启动系统最高级权限守护网络接驳中心...")
service.Control(svc, "start")
fmt.Println("============ 热更新无感流闭环执行彻底成功! ============")
} else {
fmt.Println("============ 热更核心接替执行完毕!============\n(注: 您的当前终端并不作为真正的宿存执行体。\n如目前您另行开启了 CMD 窗格在死循环执行此包前台,请人工叉掉那个窗口使其重新载入新版本!)")
}
},
},
{
Use: "start",
Short: "唤起执行守护后台",
Run: func(cmd *cobra.Command, args []string) {
if !config.ConfigFileExists() {
newCfg, err := config.InteractiveSetup()
if err != nil {
logger.Fail("配置初始化失败: %v", err)
return
}
cfg = newCfg
}
handleServiceControl(svc, "start")
},
},
{
Use: "stop",
Short: "停机系统守护状态流",
Run: func(cmd *cobra.Command, args []string) {
handleServiceControl(svc, "stop")
},
},
{
Use: "restart",
Short: "阻断并复用重新起跳服务控制主程",
Run: func(cmd *cobra.Command, args []string) {
handleServiceControl(svc, "restart")
},
},
{
Use: "status",
Short: "展示全向服务参数结构、后端存活性报告与探针汇集",
Run: func(cmd *cobra.Command, args []string) {
status, err := svc.Status()
fmt.Println("\n========== 运行状态 ==========")
if err != nil {
fmt.Printf("特权访问受抵 (查验核算需要系统特权根源准允): %v\n", err)
} else {
switch status {
case service.StatusRunning:
fmt.Println("全向系统基石服务: [运转值守中]")
case service.StatusStopped:
fmt.Println("全向系统基石服务: [已被静默挂起]")
default:
fmt.Println("全向系统基石服务: [尚未注册落位 / 孤儿状态]")
}
}
fmt.Println("\n========== 核心配置 ==========")
fmt.Printf("探测标的机器 IP : %s\n", cfg.TargetIP)
fmt.Printf("脱网迟滞容忍 (秒) : %d\n", cfg.MonitorWindowSeconds)
fmt.Printf("临危核准倒计 (秒) : %d\n", cfg.ShutdownCountdown)
fmt.Printf("静默发包跳频 (秒) : %d\n", cfg.NormalPingInterval)
logFilePath := filepath.Join(config.GetLogDir(), "network_monitor.log")
if fi, fileErr := os.Stat(logFilePath); fileErr == nil {
fmt.Println("\n========== 日志系统 ==========")
fmt.Printf("硬盘归档落点: %s\n", logFilePath)
fmt.Printf("现时容量尺寸: %.2f KB\n", float64(fi.Size())/1024)
}
printLastLogLines(10)
},
},
}
var configCmd = &cobra.Command{
Use: "config",
Short: "提供运行参数结构的修载校验准入系统",
}
var configSetCmd = &cobra.Command{
Use: "set [键] [值]",
Short: "安全的对 JSON 属性执行改写封装验证 (如 config set TargetIP 192.168.3.1)",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
key := args[0]
val := args[1]
err := config.UpdateConfig(key, val)
if err != nil {
fmt.Printf("基于硬编码的防呆数据验证发回抵拦截口指令: %v\n", err)
} else {
fmt.Printf("系统确认承接了新的设参: [%s] 指标结构被赋予了 [%s] 新制规格 \n(附留: 更易此配置文件必须人为发起 'smart-shutdown restart' 才能覆盖常驻内存的解析图谱。)\n", key, val)
}
},
}
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)
}
rootCommand.InitDefaultHelpCmd()
for _, cmd := range rootCommand.Commands() {
if cmd.Name() == "help" {
cmd.Short = "按需展示其它操作指令及其详情"
}
}
if err := rootCommand.Execute(); err != nil {
os.Exit(1)
}
}
func handleServiceControl(s service.Service, action string) bool {
err := service.Control(s, action)
if err != nil {
errStr := err.Error()
if strings.Contains(errStr, "Access is denied") || strings.Contains(errStr, "permission denied") || strings.Contains(errStr, "拒绝访问") {
logger.Fail("管控流程 [%s] 触发越级防卫!无核准身份特设记录。请开具含有全 Root 及高配权限窗格承接口径。", action)
return false
}
logger.Crit("后台执行流程组块阻断报错 [%s] : %v", action, err)
return false
}
logger.Succ("指令动作 [%s] 解析下发执行完毕,无阻断警告。", action)
return true
}
func printLastLogLines(n int) {
logFilePath := filepath.Join(config.GetLogDir(), "network_monitor.log")
file, err := os.Open(logFilePath)
if err != nil {
fmt.Printf("\n(排障辅录: 获取探针断联记录池为空,全空栈态)\n")
return
}
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
return
}
lines := strings.Split(string(data), "\n")
validLines := make([]string, 0, len(lines))
for _, l := range lines {
if strings.TrimSpace(l) != "" {
validLines = append(validLines, l)
}
}
start := len(validLines) - n
if start < 0 {
start = 0
}
fmt.Printf("\n========== 最新沿线抓取探测轨迹尾部排 %d 行 ==========\n", n)
for i := start; i < len(validLines); i++ {
fmt.Println(validLines[i])
}
}
func getTargetSystemPath() (dir string, exe string) {
if runtime.GOOS == "windows" {
dir = filepath.Join(os.Getenv("ProgramFiles"), "SmartShutdown")
exe = filepath.Join(dir, "smart-shutdown.exe")
} else {
dir = "/usr/local/bin"
exe = filepath.Join(dir, "smart-shutdown")
}
return dir, exe
}
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
return err
}

View File

@@ -1,519 +0,0 @@
<#
.SYNOPSIS
智能网络监控脚本 - 系统级部署脚本
.DESCRIPTION
将脚本部署到系统级目录,配置为开机自启动,无需用户登录
部署位置:
- 程序文件: C:\Program Files\SmartNetworkMonitor\
- 配置文件: C:\ProgramData\SmartNetworkMonitor\
- 日志文件: C:\ProgramData\SmartNetworkMonitor\logs\
#>
# 定义系统级路径
$ProgramPath = "C:\Program Files\SmartNetworkMonitor"
$DataPath = "C:\ProgramData\SmartNetworkMonitor"
$LogPath = "$DataPath\logs"
Write-Host "==========================================" -ForegroundColor Cyan
Write-Host "智能网络监控脚本 - 系统级部署" -ForegroundColor Cyan
Write-Host "==========================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "部署计划:" -ForegroundColor Yellow
Write-Host "- 程序目录: $ProgramPath" -ForegroundColor White
Write-Host "- 数据目录: $DataPath" -ForegroundColor White
Write-Host "- 日志目录: $LogPath" -ForegroundColor White
Write-Host ""
# 检查源文件
$SourcePath = $PSScriptRoot
$RequiredFiles = @(
"smart_shutdown.ps1"
)
foreach ($file in $RequiredFiles) {
if (-not (Test-Path -Path (Join-Path $SourcePath $file))) {
Write-Error "找不到必需文件: $file"
exit 1
}
}
Write-Host "[OK] 源文件检查完成" -ForegroundColor Green
# 创建目录结构
Write-Host "正在创建目录结构..." -ForegroundColor Yellow
try {
# 创建程序目录
if (-not (Test-Path -Path $ProgramPath)) {
New-Item -Path $ProgramPath -ItemType Directory -Force | Out-Null
Write-Host "[OK] 创建程序目录: $ProgramPath" -ForegroundColor Green
}
# 创建数据目录
if (-not (Test-Path -Path $DataPath)) {
New-Item -Path $DataPath -ItemType Directory -Force | Out-Null
Write-Host "[OK] 创建数据目录: $DataPath" -ForegroundColor Green
}
# 创建日志目录
if (-not (Test-Path -Path $LogPath)) {
New-Item -Path $LogPath -ItemType Directory -Force | Out-Null
Write-Host "[OK] 创建日志目录: $LogPath" -ForegroundColor Green
}
}
catch {
Write-Error "创建目录失败: $($_.Exception.Message)"
exit 1
}
# 创建系统优化版的主脚本
Write-Host "正在创建系统版本的脚本..." -ForegroundColor Yellow
$SystemScriptContent = @"
#Requires -Version 5.1
<#
.SYNOPSIS
-
.DESCRIPTION
-
-
-
#>
# ==================== ====================
# IP
[string]`$TargetIP = "192.168.3.3"
# ping
[int]`$NormalPingInterval = 15
# -
[int]`$MonitorWindowSeconds = 180
#
[int]`$ShutdownCountdown = 60
# ping
[int]`$CountdownPingInterval = 3
#
[string]`$LogDirectory = "$LogPath"
[string]`$LogFile = Join-Path -Path `$LogDirectory -ChildPath "network_monitor_`$(Get-Date -Format 'yyyyMMdd').log"
[string]`$ConfigFile = "$DataPath\config.json"
[int]`$MaxLogDays = 30
#
[int]`$normalLogInterval = 24 # 24 (6)
[int]`$normalLogCounter = 0
# =================================================
# --- ---
#
function Load-Configuration {
if (Test-Path -Path `$ConfigFile) {
try {
`$config = Get-Content -Path `$ConfigFile | ConvertFrom-Json
if (`$config.TargetIP) { `$script:TargetIP = `$config.TargetIP }
if (`$config.MonitorWindowSeconds) { `$script:MonitorWindowSeconds = `$config.MonitorWindowSeconds }
if (`$config.ShutdownCountdown) { `$script:ShutdownCountdown = `$config.ShutdownCountdown }
if (`$config.NormalPingInterval) { `$script:NormalPingInterval = `$config.NormalPingInterval }
Write-Log -Level "INFO" -Message ""
}
catch {
Write-Log -Level "WARN" -Message "使"
}
} else {
#
`$defaultConfig = @{
TargetIP = `$TargetIP
MonitorWindowSeconds = `$MonitorWindowSeconds
ShutdownCountdown = `$ShutdownCountdown
NormalPingInterval = `$NormalPingInterval
}
try {
`$defaultConfig | ConvertTo-Json | Set-Content -Path `$ConfigFile
Write-Log -Level "INFO" -Message ": `$ConfigFile"
}
catch {
Write-Log -Level "WARN" -Message "使"
}
}
}
#
function Write-Log {
param(
[Parameter(Mandatory=`$true)]
[ValidateSet("INFO", "SUCCESS", "FAIL", "WARN", "CRITICAL", "DEBUG")]
[string]`$Level,
[Parameter(Mandatory=`$true)]
[string]`$Message
)
`$logTimestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
`$logEntry = "[`$logTimestamp] [`$Level] `$Message"
#
try {
if (-not (Test-Path -Path `$LogDirectory)) {
New-Item -Path `$LogDirectory -ItemType Directory -ErrorAction Stop | Out-Null
}
Add-Content -Path `$LogFile -Value `$logEntry -ErrorAction Stop
}
catch {
#
try {
if (-not [System.Diagnostics.EventLog]::SourceExists("SmartNetworkMonitor")) {
New-EventLog -LogName "Application" -Source "SmartNetworkMonitor"
}
Write-EventLog -LogName "Application" -Source "SmartNetworkMonitor" -EventId 1001 -EntryType Information -Message `$logEntry
}
catch {
#
Write-Host "[`$logTimestamp] [CRITICAL] `$Message"
}
}
#
try {
if (`$Level -eq "CRITICAL" -or `$Level -eq "FAIL") {
Write-EventLog -LogName "Application" -Source "SmartNetworkMonitor" -EventId 1002 -EntryType Warning -Message `$logEntry -ErrorAction SilentlyContinue
}
}
catch {
#
}
}
#
function Remove-OldLogs {
try {
`$cutoffDate = (Get-Date).AddDays(-`$MaxLogDays)
`$oldLogs = Get-ChildItem -Path `$LogDirectory -Filter "network_monitor_*.log" | Where-Object { `$_.LastWriteTime -lt `$cutoffDate }
foreach (`$oldLog in `$oldLogs) {
Remove-Item -Path `$oldLog.FullName -Force
Write-Log -Level "INFO" -Message ": `$(`$oldLog.Name)"
}
}
catch {
Write-Log -Level "WARN" -Message ": `$(`$_.Exception.Message)"
}
}
# =================================================
# --- ---
#
Load-Configuration
Remove-OldLogs
#
try {
`$runningAs = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
`$localIP = (Get-NetIPAddress -AddressFamily IPv4 | Where-Object { `$_.InterfaceAlias -notmatch "Loopback" } | Select-Object -First 1).IPAddress
}
catch {
`$runningAs = "Unknown"
`$localIP = "Unknown"
}
Write-Log -Level "INFO" -Message "========== =========="
Write-Log -Level "INFO" -Message ": `$runningAs"
Write-Log -Level "INFO" -Message "IP: `$localIP"
Write-Log -Level "INFO" -Message ": `$TargetIP"
Write-Log -Level "INFO" -Message ": `${MonitorWindowSeconds}"
Write-Log -Level "INFO" -Message ": `${ShutdownCountdown}"
Write-Log -Level "INFO" -Message ": `$LogDirectory"
Write-Log -Level "INFO" -Message ": `$ConfigFile"
#
`$failureStartTime = `$null
while (`$true) {
#
try {
`$pingResult = Test-Connection -ComputerName `$TargetIP -Count 1 -Quiet -ErrorAction SilentlyContinue
`$isOnline = `$pingResult
}
catch {
Write-Log -Level "FAIL" -Message ": `$(`$_.Exception.Message)"
`$isOnline = `$false
}
if (`$isOnline) {
if (`$failureStartTime) {
Write-Log -Level "SUCCESS" -Message ""
`$failureStartTime = `$null
} else {
#
`$normalLogCounter++
if (`$normalLogCounter -ge `$normalLogInterval) {
Write-Log -Level "INFO" -Message " ()"
`$normalLogCounter = 0
}
}
Start-Sleep -Seconds `$NormalPingInterval
} else {
#
Write-Log -Level "FAIL" -Message "Ping - : `$TargetIP"
if (-not `$failureStartTime) {
`$failureStartTime = Get-Date
Write-Log -Level "WARN" -Message ""
}
`$failureDuration = (New-TimeSpan -Start `$failureStartTime -End (Get-Date)).TotalSeconds
Write-Log -Level "INFO" -Message " `$([math]::Round(`$failureDuration)) / `${MonitorWindowSeconds} "
if (`$failureDuration -ge `$MonitorWindowSeconds) {
Write-Log -Level "CRITICAL" -Message " `${MonitorWindowSeconds} "
`$shutdownCancelled = `$false
`$countdownEndTime = (Get-Date).AddSeconds(`$ShutdownCountdown)
while ((Get-Date) -lt `$countdownEndTime -and -not `$shutdownCancelled) {
`$remainingSeconds = [math]::Ceiling((`$countdownEndTime - (Get-Date)).TotalSeconds)
if (`$remainingSeconds -le 0) { break }
Write-Log -Level "WARN" -Message " `$remainingSeconds ... "
#
try {
if (Test-Connection -ComputerName `$TargetIP -Count 1 -Quiet -ErrorAction SilentlyContinue) {
Write-Log -Level "SUCCESS" -Message ""
`$failureStartTime = `$null
`$shutdownCancelled = `$true
break
}
}
catch {
Write-Log -Level "WARN" -Message ": `$(`$_.Exception.Message)"
}
Start-Sleep -Seconds `$CountdownPingInterval
}
if (-not `$shutdownCancelled) {
Write-Log -Level "CRITICAL" -Message ""
try {
#
Stop-Computer -Force
Write-Log -Level "CRITICAL" -Message ""
}
catch {
Write-Log -Level "CRITICAL" -Message ": `$(`$_.Exception.Message)"
}
exit
}
} else {
Start-Sleep -Seconds `$NormalPingInterval
}
}
}
"@
# 写入系统版本脚本
$SystemScriptPath = Join-Path $ProgramPath "smart_shutdown_system.ps1"
try {
$SystemScriptContent | Set-Content -Path $SystemScriptPath -Encoding UTF8
Write-Host "[OK] 创建系统脚本: $SystemScriptPath" -ForegroundColor Green
}
catch {
Write-Error "创建系统脚本失败: $($_.Exception.Message)"
exit 1
}
# 创建默认配置文件
Write-Host "正在创建配置文件..." -ForegroundColor Yellow
$DefaultConfig = @{
TargetIP = "192.168.3.3"
MonitorWindowSeconds = 180
ShutdownCountdown = 60
NormalPingInterval = 15
}
$ConfigPath = Join-Path $DataPath "config.json"
try {
$DefaultConfig | ConvertTo-Json -Depth 10 | Set-Content -Path $ConfigPath -Encoding UTF8
Write-Host "[OK] 创建配置文件: $ConfigPath" -ForegroundColor Green
}
catch {
Write-Error "创建配置文件失败: $($_.Exception.Message)"
exit 1
}
# 配置任务计划程序
Write-Host "正在配置任务计划程序..." -ForegroundColor Yellow
try {
# 删除已存在的任务(如果有)
try {
Unregister-ScheduledTask -TaskName "Smart Network Shutdown Monitor" -Confirm:$false -ErrorAction SilentlyContinue
}
catch {
# 忽略错误,任务可能不存在
}
# 创建触发器(系统启动时)
$Trigger = New-ScheduledTaskTrigger -AtStartup
# 创建操作
$Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-WindowStyle Hidden -ExecutionPolicy Bypass -File `"$SystemScriptPath`""
# 创建设置
$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -DontStopOnIdleEnd -ExecutionTimeLimit (New-TimeSpan -Days 365)
# 创建主体以SYSTEM身份运行
$Principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
# 注册任务
Register-ScheduledTask -TaskName "Smart Network Shutdown Monitor" -Trigger $Trigger -Action $Action -Settings $Settings -Principal $Principal -Description "智能网络监控脚本 - 系统级运行"
Write-Host "[OK] 任务计划程序配置完成" -ForegroundColor Green
}
catch {
Write-Error "配置任务计划程序失败: $($_.Exception.Message)"
exit 1
}
# 创建管理脚本
Write-Host "正在创建管理工具..." -ForegroundColor Yellow
$ManagementScript = @"
# -
Write-Host " - " -ForegroundColor Cyan
Write-Host "==============================" -ForegroundColor Cyan
Write-Host ""
Write-Host ":" -ForegroundColor Yellow
Write-Host "1. "
Write-Host "2. "
Write-Host "3. "
Write-Host "4. "
Write-Host "5. "
Write-Host "6. "
Write-Host "7. "
Write-Host ""
`$choice = Read-Host " (1-7)"
switch (`$choice) {
"1" {
Get-ScheduledTask -TaskName "Smart Network Shutdown Monitor" | Format-Table
}
"2" {
Start-ScheduledTask -TaskName "Smart Network Shutdown Monitor"
Write-Host "" -ForegroundColor Green
}
"3" {
Stop-ScheduledTask -TaskName "Smart Network Shutdown Monitor"
Write-Host "" -ForegroundColor Green
}
"4" {
`$logFile = "$LogPath\network_monitor_`$(Get-Date -Format 'yyyyMMdd').log"
if (Test-Path `$logFile) {
Get-Content `$logFile
} else {
Write-Host "" -ForegroundColor Red
}
}
"5" {
`$logFile = "$LogPath\network_monitor_`$(Get-Date -Format 'yyyyMMdd').log"
if (Test-Path `$logFile) {
Get-Content `$logFile -Wait
} else {
Write-Host "" -ForegroundColor Red
}
}
"6" {
Get-Content "$DataPath\config.json"
}
"7" {
notepad "$DataPath\config.json"
}
default {
Write-Host "" -ForegroundColor Red
}
}
Write-Host ""
Write-Host ":" -ForegroundColor Yellow
Write-Host "1. :"
Write-Host " Get-ScheduledTask -TaskName 'Smart Network Shutdown Monitor'"
Write-Host ""
Write-Host "2. :"
Write-Host " Start-ScheduledTask -TaskName 'Smart Network Shutdown Monitor'"
Write-Host ""
Write-Host "3. :"
Write-Host " Stop-ScheduledTask -TaskName 'Smart Network Shutdown Monitor'"
Write-Host ""
Write-Host "4. :"
Write-Host " Get-Content '$LogPath\network_monitor_`$(Get-Date -Format 'yyyyMMdd').log'"
Write-Host ""
Write-Host "5. :"
Write-Host " Get-Content '$LogPath\network_monitor_`$(Get-Date -Format 'yyyyMMdd').log' -Wait"
Write-Host ""
Write-Host "6. :"
Write-Host " Get-Content '$DataPath\config.json'"
Write-Host ""
Write-Host "7. :"
Write-Host " notepad '$DataPath\config.json'"
Write-Host ""
"@
$ManagementScriptPath = Join-Path $ProgramPath "manage.ps1"
try {
$ManagementScript | Set-Content -Path $ManagementScriptPath -Encoding UTF8
Write-Host "[OK] 创建管理脚本: $ManagementScriptPath" -ForegroundColor Green
}
catch {
Write-Error "创建管理脚本失败: $($_.Exception.Message)"
exit 1
}
Write-Host ""
Write-Host "[INFO] 系统级部署完成!" -ForegroundColor Green
Write-Host ""
Write-Host "部署摘要:" -ForegroundColor Cyan
Write-Host "[OK] 程序文件已部署到: $ProgramPath" -ForegroundColor White
Write-Host "[OK] 数据目录已创建: $DataPath" -ForegroundColor White
Write-Host "[OK] 日志目录已创建: $LogPath" -ForegroundColor White
Write-Host "[OK] 任务计划程序已配置(系统启动时运行)" -ForegroundColor White
Write-Host "[OK] SYSTEM 权限配置完成" -ForegroundColor White
Write-Host ""
Write-Host "管理工具:" -ForegroundColor Yellow
Write-Host "- 管理脚本: $ManagementScriptPath" -ForegroundColor White
Write-Host "- 配置文件: $DataPath\config.json" -ForegroundColor White
Write-Host "- 今天日志: $LogPath\network_monitor_$(Get-Date -Format 'yyyyMMdd').log" -ForegroundColor White
Write-Host ""
Write-Host "下次重启后,脚本将自动运行!" -ForegroundColor Green
Write-Host ""
# 询问是否立即启动
$response = Read-Host "是否立即启动监控任务?(Y/N)"
if ($response -eq 'Y' -or $response -eq 'y' -or $response -eq '') {
try {
Start-ScheduledTask -TaskName "Smart Network Shutdown Monitor"
Write-Host "[OK] 监控任务已启动" -ForegroundColor Green
}
catch {
Write-Host "[X] 启动任务失败: $($_.Exception.Message)" -ForegroundColor Red
}
}

View File

@@ -1,528 +0,0 @@
#!/bin/bash
#
# 智能网络监控脚本 - Ubuntu系统级部署脚本
#
# 功能描述:
# 将脚本部署到系统级目录配置为systemd服务开机自启动
#
# 部署位置:
# - 程序文件: /opt/smart-network-monitor/
# - 配置文件: /etc/smart-network-monitor/
# - 日志文件: /var/log/smart-network-monitor/
# - 服务文件: /etc/systemd/system/smart-network-monitor.service
#
# 使用方法:
# sudo ./deploy_system.sh
#
# 设置颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# 定义系统级路径
PROGRAM_PATH="/opt/smart-network-monitor"
CONFIG_PATH="/etc/smart-network-monitor"
LOG_PATH="/var/log/smart-network-monitor"
SERVICE_FILE="/etc/systemd/system/smart-network-monitor.service"
SERVICE_NAME="smart-network-monitor"
# 当前脚本目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo -e "${CYAN}==========================================${NC}"
echo -e "${CYAN}智能网络监控脚本 - Ubuntu系统级部署${NC}"
echo -e "${CYAN}==========================================${NC}"
echo ""
echo -e "${YELLOW}部署计划:${NC}"
echo -e "- 程序目录: ${PROGRAM_PATH}"
echo -e "- 配置目录: ${CONFIG_PATH}"
echo -e "- 日志目录: ${LOG_PATH}"
echo -e "- 服务文件: ${SERVICE_FILE}"
echo ""
# 检查root权限
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}错误此脚本需要root权限执行${NC}"
echo -e "${YELLOW}请使用: sudo $0${NC}"
exit 1
fi
# 检查源文件
REQUIRED_FILES=(
"smart_shutdown.sh"
)
for file in "${REQUIRED_FILES[@]}"; do
if [[ ! -f "$SCRIPT_DIR/$file" ]]; then
echo -e "${RED}错误:找不到必需文件: $file${NC}"
exit 1
fi
done
echo -e "${GREEN}[OK] 源文件检查完成${NC}"
# 停止已存在的服务
if systemctl is-active --quiet "$SERVICE_NAME"; then
echo -e "${YELLOW}正在停止现有服务...${NC}"
systemctl stop "$SERVICE_NAME"
echo -e "${GREEN}[OK] 服务已停止${NC}"
fi
# 创建目录结构
echo -e "${YELLOW}正在创建目录结构...${NC}"
# 创建程序目录
if [[ ! -d "$PROGRAM_PATH" ]]; then
mkdir -p "$PROGRAM_PATH"
echo -e "${GREEN}[OK] 创建程序目录: $PROGRAM_PATH${NC}"
fi
# 创建配置目录
if [[ ! -d "$CONFIG_PATH" ]]; then
mkdir -p "$CONFIG_PATH"
echo -e "${GREEN}[OK] 创建配置目录: $CONFIG_PATH${NC}"
fi
# 创建日志目录
if [[ ! -d "$LOG_PATH" ]]; then
mkdir -p "$LOG_PATH"
# 设置适当的权限,允许服务用户写入
chmod 755 "$LOG_PATH"
echo -e "${GREEN}[OK] 创建日志目录: $LOG_PATH${NC}"
fi
# 创建系统优化版的主脚本
echo -e "${YELLOW}正在创建系统版本的脚本...${NC}"
cat > "$PROGRAM_PATH/smart_shutdown_system.sh" << 'EOF'
#!/bin/bash
#
# 智能网络监控脚本 - 系统服务版本
#
# 功能描述:
# 系统级网络监控脚本,在网络持续中断时自动关机
#
# 特点:
# - 开机自启动作为systemd服务运行
# - 日志存储在系统日志目录
# - 优化的错误处理和权限管理
#
# ==================== 系统级配置参数 ====================
# 默认配置参数
TARGET_IP="192.168.3.3"
NORMAL_PING_INTERVAL=15
MONITOR_WINDOW_SECONDS=180
SHUTDOWN_COUNTDOWN=60
COUNTDOWN_PING_INTERVAL=3
PING_TIMEOUT=3
# 系统级路径配置
CONFIG_FILE="/etc/smart-network-monitor/config.json"
LOG_DIRECTORY="/var/log/smart-network-monitor"
MAX_LOG_DAYS=30
# 正常连接时的日志记录间隔计数器
NORMAL_LOG_INTERVAL=24 # 每24次循环记录一次状态 (约6分钟)
NORMAL_LOG_COUNTER=0
# ==================== 函数定义 ====================
# 日志写入函数
write_log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local log_file="$LOG_DIRECTORY/network_monitor_$(date '+%Y%m%d').log"
local log_entry="[$timestamp] [$level] $message"
# 确保日志目录存在
mkdir -p "$LOG_DIRECTORY"
# 写入日志文件
echo "$log_entry" >> "$log_file" 2>/dev/null
# 同时写入系统日志
logger -t "smart-network-monitor" "$log_entry"
# 对于关键信息输出到标准输出systemd会捕获
if [[ "$level" == "CRITICAL" || "$level" == "SUCCESS" || "$level" == "WARN" ]]; then
echo "$log_entry"
fi
}
# 加载配置文件
load_configuration() {
if [[ -f "$CONFIG_FILE" ]]; then
write_log "INFO" "加载配置文件: $CONFIG_FILE"
# 使用jq解析JSON配置文件如果可用
if command -v jq >/dev/null 2>&1; then
local target_ip=$(jq -r '.TargetIP // empty' "$CONFIG_FILE" 2>/dev/null)
local monitor_window=$(jq -r '.MonitorWindowSeconds // empty' "$CONFIG_FILE" 2>/dev/null)
local shutdown_countdown=$(jq -r '.ShutdownCountdown // empty' "$CONFIG_FILE" 2>/dev/null)
local ping_interval=$(jq -r '.NormalPingInterval // empty' "$CONFIG_FILE" 2>/dev/null)
[[ -n "$target_ip" ]] && TARGET_IP="$target_ip"
[[ -n "$monitor_window" ]] && MONITOR_WINDOW_SECONDS="$monitor_window"
[[ -n "$shutdown_countdown" ]] && SHUTDOWN_COUNTDOWN="$shutdown_countdown"
[[ -n "$ping_interval" ]] && NORMAL_PING_INTERVAL="$ping_interval"
write_log "SUCCESS" "配置文件加载成功"
else
write_log "WARN" "jq未安装无法解析JSON配置文件使用默认配置"
fi
else
write_log "WARN" "配置文件不存在,使用默认配置"
fi
}
# 测试网络连接
test_network_connection() {
local target_ip="$1"
ping -c 1 -W "$PING_TIMEOUT" "$target_ip" >/dev/null 2>&1
return $?
}
# 清理旧日志文件
cleanup_old_logs() {
if [[ -d "$LOG_DIRECTORY" ]]; then
find "$LOG_DIRECTORY" -name "network_monitor_*.log" -type f -mtime +$MAX_LOG_DAYS -exec rm -f {} \; 2>/dev/null
fi
}
# 信号处理函数
cleanup_and_exit() {
write_log "INFO" "接收到退出信号,正在清理..."
write_log "INFO" "========== 服务退出 =========="
exit 0
}
# ==================== 主程序开始 ====================
# 设置信号处理
trap cleanup_and_exit SIGINT SIGTERM
# 清理旧日志
cleanup_old_logs
# 加载配置
load_configuration
# 获取系统信息
running_user=$(whoami)
local_ip=$(ip route get 8.8.8.8 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -1)
[[ -z "$local_ip" ]] && local_ip="未知"
write_log "INFO" "========== 系统级网络监控服务启动 =========="
write_log "INFO" "运行用户: $running_user"
write_log "INFO" "本机IP: $local_ip"
write_log "INFO" "监控目标: $TARGET_IP"
write_log "INFO" "监控窗口: ${MONITOR_WINDOW_SECONDS}秒"
write_log "INFO" "关机倒计时: ${SHUTDOWN_COUNTDOWN}秒"
write_log "INFO" "日志目录: $LOG_DIRECTORY"
write_log "INFO" "配置文件: $CONFIG_FILE"
# 主循环
failure_start_time=""
while true; do
# 测试网络连接
if test_network_connection "$TARGET_IP"; then
# 网络连接正常
if [[ -n "$failure_start_time" ]]; then
write_log "SUCCESS" "网络连接已恢复"
failure_start_time=""
NORMAL_LOG_COUNTER=0
else
# 减少正常连接时的日志频率
((NORMAL_LOG_COUNTER++))
if [[ $NORMAL_LOG_COUNTER -ge $NORMAL_LOG_INTERVAL ]]; then
write_log "INFO" "网络连接正常(定期状态报告)"
NORMAL_LOG_COUNTER=0
fi
fi
sleep "$NORMAL_PING_INTERVAL"
else
# 网络连接失败
write_log "FAIL" "Ping失败 - 目标: $TARGET_IP"
if [[ -z "$failure_start_time" ]]; then
failure_start_time=$(date +%s)
write_log "WARN" "网络首次中断,开始进入监控窗口计时"
fi
current_time=$(date +%s)
failure_duration=$((current_time - failure_start_time))
write_log "INFO" "网络已持续中断 $failure_duration / ${MONITOR_WINDOW_SECONDS} 秒"
if [[ $failure_duration -ge $MONITOR_WINDOW_SECONDS ]]; then
write_log "CRITICAL" "网络持续中断已超过 ${MONITOR_WINDOW_SECONDS} 秒,开始关机流程"
shutdown_cancelled=false
countdown_end_time=$((current_time + SHUTDOWN_COUNTDOWN))
while [[ $(date +%s) -lt $countdown_end_time && "$shutdown_cancelled" == "false" ]]; do
current_time=$(date +%s)
remaining_seconds=$((countdown_end_time - current_time))
if [[ $remaining_seconds -le 0 ]]; then
break
fi
write_log "WARN" "距离关机还有 $remaining_seconds 秒... 正在快速检测网络"
# 在倒计时中再次检测网络
if test_network_connection "$TARGET_IP"; then
write_log "SUCCESS" "网络在倒计时期间恢复!取消关机"
failure_start_time=""
shutdown_cancelled=true
break
fi
sleep "$COUNTDOWN_PING_INTERVAL"
done
if [[ "$shutdown_cancelled" == "false" ]]; then
write_log "CRITICAL" "关机倒计时完成,执行系统关机命令"
# 执行实际关机命令
shutdown -h now
write_log "CRITICAL" "关机命令已执行"
exit 0
fi
else
sleep "$NORMAL_PING_INTERVAL"
fi
fi
done
EOF
# 设置脚本权限
chmod +x "$PROGRAM_PATH/smart_shutdown_system.sh"
echo -e "${GREEN}[OK] 创建系统脚本: $PROGRAM_PATH/smart_shutdown_system.sh${NC}"
# 创建默认配置文件
echo -e "${YELLOW}正在创建配置文件...${NC}"
cat > "$CONFIG_PATH/config.json" << EOF
{
"TargetIP": "192.168.3.3",
"MonitorWindowSeconds": 180,
"ShutdownCountdown": 60,
"NormalPingInterval": 15
}
EOF
echo -e "${GREEN}[OK] 创建配置文件: $CONFIG_PATH/config.json${NC}"
# 创建systemd服务文件
echo -e "${YELLOW}正在配置systemd服务...${NC}"
cat > "$SERVICE_FILE" << EOF
[Unit]
Description=Smart Network Monitor Service
Documentation=https://github.com/example/smart-network-monitor
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
Group=root
ExecStart=$PROGRAM_PATH/smart_shutdown_system.sh
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
# 安全设置
NoNewPrivileges=false
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=$LOG_PATH
# 环境变量
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
[Install]
WantedBy=multi-user.target
EOF
echo -e "${GREEN}[OK] 创建systemd服务文件: $SERVICE_FILE${NC}"
# 重新加载systemd配置
echo -e "${YELLOW}正在重新加载systemd配置...${NC}"
systemctl daemon-reload
echo -e "${GREEN}[OK] systemd配置已重新加载${NC}"
# 启用服务
echo -e "${YELLOW}正在启用服务开机自启动...${NC}"
systemctl enable "$SERVICE_NAME"
echo -e "${GREEN}[OK] 服务已设置为开机自启动${NC}"
# 创建管理脚本
echo -e "${YELLOW}正在创建管理工具...${NC}"
cat > "$PROGRAM_PATH/manage.sh" << 'EOF'
#!/bin/bash
#
# 智能网络监控脚本 - 管理工具
#
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
SERVICE_NAME="smart-network-monitor"
CONFIG_FILE="/etc/smart-network-monitor/config.json"
LOG_PATH="/var/log/smart-network-monitor"
echo -e "${CYAN}智能网络监控脚本 - 管理工具${NC}"
echo -e "${CYAN}==============================${NC}"
echo ""
echo -e "${YELLOW}可用操作:${NC}"
echo "1. 查看服务状态"
echo "2. 启动服务"
echo "3. 停止服务"
echo "4. 重启服务"
echo "5. 查看今天的日志"
echo "6. 查看实时日志"
echo "7. 查看配置文件"
echo "8. 编辑配置文件"
echo "9. 查看系统日志"
echo ""
read -p "请选择操作 (1-9): " choice
case "$choice" in
"1")
systemctl status "$SERVICE_NAME"
;;
"2")
sudo systemctl start "$SERVICE_NAME"
echo -e "${GREEN}服务已启动${NC}"
;;
"3")
sudo systemctl stop "$SERVICE_NAME"
echo -e "${GREEN}服务已停止${NC}"
;;
"4")
sudo systemctl restart "$SERVICE_NAME"
echo -e "${GREEN}服务已重启${NC}"
;;
"5")
log_file="$LOG_PATH/network_monitor_$(date '+%Y%m%d').log"
if [[ -f "$log_file" ]]; then
cat "$log_file"
else
echo -e "${RED}今天的日志文件不存在${NC}"
fi
;;
"6")
log_file="$LOG_PATH/network_monitor_$(date '+%Y%m%d').log"
if [[ -f "$log_file" ]]; then
tail -f "$log_file"
else
echo -e "${RED}今天的日志文件不存在显示systemd日志${NC}"
journalctl -u "$SERVICE_NAME" -f
fi
;;
"7")
if [[ -f "$CONFIG_FILE" ]]; then
cat "$CONFIG_FILE"
else
echo -e "${RED}配置文件不存在${NC}"
fi
;;
"8")
if [[ -f "$CONFIG_FILE" ]]; then
sudo nano "$CONFIG_FILE"
echo -e "${YELLOW}配置已修改,重启服务使配置生效:${NC}"
echo "sudo systemctl restart $SERVICE_NAME"
else
echo -e "${RED}配置文件不存在${NC}"
fi
;;
"9")
journalctl -u "$SERVICE_NAME" -n 50
;;
*)
echo -e "${RED}无效选择${NC}"
;;
esac
echo ""
echo -e "${YELLOW}常用命令:${NC}"
echo -e "${BLUE}查看服务状态:${NC} systemctl status $SERVICE_NAME"
echo -e "${BLUE}启动服务:${NC} sudo systemctl start $SERVICE_NAME"
echo -e "${BLUE}停止服务:${NC} sudo systemctl stop $SERVICE_NAME"
echo -e "${BLUE}重启服务:${NC} sudo systemctl restart $SERVICE_NAME"
echo -e "${BLUE}查看今天日志:${NC} cat $LOG_PATH/network_monitor_\$(date '+%Y%m%d').log"
echo -e "${BLUE}实时查看日志:${NC} tail -f $LOG_PATH/network_monitor_\$(date '+%Y%m%d').log"
echo -e "${BLUE}查看系统日志:${NC} journalctl -u $SERVICE_NAME -f"
echo -e "${BLUE}编辑配置:${NC} sudo nano $CONFIG_FILE"
echo ""
EOF
chmod +x "$PROGRAM_PATH/manage.sh"
echo -e "${GREEN}[OK] 创建管理脚本: $PROGRAM_PATH/manage.sh${NC}"
# 创建符号链接到系统PATH
if [[ ! -L "/usr/local/bin/smart-monitor" ]]; then
ln -s "$PROGRAM_PATH/manage.sh" "/usr/local/bin/smart-monitor"
echo -e "${GREEN}[OK] 创建管理工具快捷命令: smart-monitor${NC}"
fi
echo ""
echo -e "${GREEN}[INFO] 系统级部署完成!${NC}"
echo ""
echo -e "${CYAN}部署摘要:${NC}"
echo -e "${GREEN}[OK]${NC} 程序文件已部署到: $PROGRAM_PATH"
echo -e "${GREEN}[OK]${NC} 配置目录已创建: $CONFIG_PATH"
echo -e "${GREEN}[OK]${NC} 日志目录已创建: $LOG_PATH"
echo -e "${GREEN}[OK]${NC} systemd服务已配置: $SERVICE_NAME"
echo -e "${GREEN}[OK]${NC} 开机自启动已启用"
echo ""
echo -e "${YELLOW}管理工具:${NC}"
echo -e "- 管理脚本: $PROGRAM_PATH/manage.sh"
echo -e "- 快捷命令: ${CYAN}smart-monitor${NC}"
echo -e "- 配置文件: $CONFIG_PATH/config.json"
echo -e "- 今天日志: $LOG_PATH/network_monitor_$(date '+%Y%m%d').log"
echo ""
echo -e "${GREEN}下次重启后,服务将自动运行!${NC}"
echo ""
# 询问是否立即启动
read -p "是否立即启动监控服务?(Y/n): " response
if [[ "$response" == "Y" || "$response" == "y" || "$response" == "" ]]; then
if systemctl start "$SERVICE_NAME"; then
echo -e "${GREEN}[OK] 监控服务已启动${NC}"
echo -e "${CYAN}使用以下命令查看服务状态:${NC}"
echo -e "${BLUE}systemctl status $SERVICE_NAME${NC}"
echo -e "${BLUE}journalctl -u $SERVICE_NAME -f${NC}"
else
echo -e "${RED}[X] 启动服务失败${NC}"
fi
fi
echo ""
echo -e "${CYAN}使用 'smart-monitor' 命令来管理服务${NC}"

6
example_config.json Normal file
View File

@@ -0,0 +1,6 @@
{
"TargetIP": "192.168.3.1",
"MonitorWindowSeconds": 180,
"ShutdownCountdown": 60,
"NormalPingInterval": 15
}

23
go.mod Normal file
View File

@@ -0,0 +1,23 @@
module smart-shutdown
go 1.25.0
require (
github.com/fatih/color v1.19.0
github.com/kardianos/service v1.2.4
github.com/prometheus-community/pro-bing v0.8.0
github.com/spf13/cobra v1.10.2
golang.org/x/mod v0.34.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
)
require (
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/spf13/pflag v1.0.9 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.42.0 // indirect
)

33
go.sum Normal file
View File

@@ -0,0 +1,33 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kardianos/service v1.2.4 h1:XNlGtZOYNx2u91urOdg/Kfmc+gfmuIo1Dd3rEi2OgBk=
github.com/kardianos/service v1.2.4/go.mod h1:E4V9ufUuY82F7Ztlu1eN9VXWIQxg8NoLQlmFe0MtrXc=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/prometheus-community/pro-bing v0.8.0 h1:CEY/g1/AgERRDjxw5P32ikcOgmrSuXs7xon7ovx6mNc=
github.com/prometheus-community/pro-bing v0.8.0/go.mod h1:Idyxz8raDO6TgkUN6ByiEGvWJNyQd40kN9ZUeho3lN0=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=

27
install.ps1 Normal file
View File

@@ -0,0 +1,27 @@
# Smart Network Shutdown Monitor - Windows Installer
# Usage: irm https://raw.githubusercontent.com/UnbalancedCat/smart-shutdown/main/install.ps1 | iex
$ErrorActionPreference = "Stop"
# Check admin privileges
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Host "[FAIL] 此安装脚本必须以管理员身份运行。请右键 PowerShell 选择「以管理员身份运行」后重试。" -ForegroundColor Red
exit 1
}
$arch = if ([Environment]::Is64BitOperatingSystem) { "amd64" } else { "386" }
$fileName = "smart-shutdown_windows_${arch}.exe"
$downloadUrl = "https://github.com/UnbalancedCat/smart-shutdown/releases/latest/download/$fileName"
$tempPath = Join-Path $env:TEMP "smart-shutdown.exe"
Write-Host "正在下载最新版本: $fileName ..." -ForegroundColor Cyan
Invoke-WebRequest -Uri $downloadUrl -OutFile $tempPath -UseBasicParsing
Write-Host "正在执行安装与服务注册..." -ForegroundColor Cyan
& $tempPath install
Write-Host "正在启动后台服务..." -ForegroundColor Cyan
smart-shutdown start
Remove-Item $tempPath -Force -ErrorAction SilentlyContinue
Write-Host "安装完成。此后可在管理员终端中直接使用 smart-shutdown 命令。" -ForegroundColor Green

35
install.sh Normal file
View File

@@ -0,0 +1,35 @@
#!/bin/sh
# Smart Network Shutdown Monitor - Linux Installer
# Usage: curl -sSL https://raw.githubusercontent.com/UnbalancedCat/smart-shutdown/main/install.sh | sudo sh
set -e
# Check root privileges
if [ "$(id -u)" -ne 0 ]; then
echo "[FAIL] 此安装脚本必须以 root 权限运行。请使用 sudo 执行。"
exit 1
fi
ARCH=$(uname -m)
case "$ARCH" in
x86_64) ARCH="amd64" ;;
aarch64) ARCH="arm64" ;;
armv7l) ARCH="armv7" ;;
*) echo "[FAIL] 不支持的架构: $ARCH"; exit 1 ;;
esac
FILE_NAME="smart-shutdown_linux_${ARCH}"
DOWNLOAD_URL="https://github.com/UnbalancedCat/smart-shutdown/releases/latest/download/$FILE_NAME"
TARGET_PATH="/usr/local/bin/smart-shutdown"
echo "正在下载最新版本: $FILE_NAME ..."
curl -sSL "$DOWNLOAD_URL" -o "$TARGET_PATH"
chmod +x "$TARGET_PATH"
echo "正在执行安装与服务注册..."
smart-shutdown install
echo "正在启动后台服务..."
smart-shutdown start
echo "安装完成。此后可直接使用 smart-shutdown 命令。"

574
manage.sh
View File

@@ -1,574 +0,0 @@
#!/bin/bash
#
# 智能网络监控脚本 - 管理工具
#
# 功能描述:
# 提供智能网络监控脚本的管理界面
# 包括服务控制、日志查看、配置管理等功能
#
# 使用方法:
# ./manage.sh
# 或者如果已安装到系统smart-monitor
#
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
PURPLE='\033[0;35m'
NC='\033[0m' # No Color
# 系统路径配置
SERVICE_NAME="smart-network-monitor"
SYSTEM_CONFIG_FILE="/etc/smart-network-monitor/config.json"
SYSTEM_LOG_PATH="/var/log/smart-network-monitor"
LOCAL_CONFIG_FILE="./config.json"
LOCAL_LOG_PATH="./logs"
# 检测是否为系统级安装
if [[ -f "$SYSTEM_CONFIG_FILE" ]]; then
CONFIG_FILE="$SYSTEM_CONFIG_FILE"
LOG_PATH="$SYSTEM_LOG_PATH"
IS_SYSTEM_INSTALL=true
else
CONFIG_FILE="$LOCAL_CONFIG_FILE"
LOG_PATH="$LOCAL_LOG_PATH"
IS_SYSTEM_INSTALL=false
fi
# 显示标题
show_header() {
clear
echo -e "${CYAN}==========================================${NC}"
echo -e "${CYAN}智能网络监控脚本 - 管理工具${NC}"
echo -e "${CYAN}==========================================${NC}"
echo ""
if [[ "$IS_SYSTEM_INSTALL" == "true" ]]; then
echo -e "${GREEN}检测到系统级安装${NC}"
echo -e "服务名称: ${BLUE}$SERVICE_NAME${NC}"
else
echo -e "${YELLOW}使用本地模式${NC}"
echo -e "配置文件: ${BLUE}$CONFIG_FILE${NC}"
fi
echo -e "日志路径: ${BLUE}$LOG_PATH${NC}"
echo ""
}
# 显示菜单
show_menu() {
echo -e "${YELLOW}可用操作:${NC}"
if [[ "$IS_SYSTEM_INSTALL" == "true" ]]; then
echo "1. 查看服务状态"
echo "2. 启动服务"
echo "3. 停止服务"
echo "4. 重启服务"
echo "5. 启用开机自启动"
echo "6. 禁用开机自启动"
echo "7. 查看今天的日志"
echo "8. 查看实时日志"
echo "9. 查看系统日志"
echo "10. 查看配置文件"
echo "11. 编辑配置文件"
echo "12. 测试网络连接"
echo "13. 显示服务信息"
echo "0. 退出"
else
echo "1. 查看配置文件"
echo "2. 编辑配置文件"
echo "3. 查看今天的日志"
echo "4. 查看实时日志"
echo "5. 测试网络连接"
echo "6. 启动本地脚本(前台运行)"
echo "7. 清理旧日志"
echo "0. 退出"
fi
echo ""
}
# 检查服务状态
check_service_status() {
if systemctl is-active --quiet "$SERVICE_NAME"; then
echo -e "${GREEN}✓ 服务正在运行${NC}"
return 0
else
echo -e "${RED}✗ 服务未运行${NC}"
return 1
fi
}
# 显示服务状态
show_service_status() {
echo -e "${CYAN}=== 服务状态 ===${NC}"
systemctl status "$SERVICE_NAME" --no-pager -l
echo ""
echo -e "${CYAN}=== 服务是否启用 ===${NC}"
if systemctl is-enabled --quiet "$SERVICE_NAME" 2>/dev/null; then
echo -e "${GREEN}✓ 开机自启动已启用${NC}"
else
echo -e "${YELLOW}✗ 开机自启动未启用${NC}"
fi
echo ""
}
# 启动服务
start_service() {
echo -e "${YELLOW}正在启动服务...${NC}"
if sudo systemctl start "$SERVICE_NAME"; then
echo -e "${GREEN}[OK] 服务已启动${NC}"
else
echo -e "${RED}[ERROR] 启动服务失败${NC}"
fi
}
# 停止服务
stop_service() {
echo -e "${YELLOW}正在停止服务...${NC}"
if sudo systemctl stop "$SERVICE_NAME"; then
echo -e "${GREEN}[OK] 服务已停止${NC}"
else
echo -e "${RED}[ERROR] 停止服务失败${NC}"
fi
}
# 重启服务
restart_service() {
echo -e "${YELLOW}正在重启服务...${NC}"
if sudo systemctl restart "$SERVICE_NAME"; then
echo -e "${GREEN}[OK] 服务已重启${NC}"
else
echo -e "${RED}[ERROR] 重启服务失败${NC}"
fi
}
# 启用开机自启动
enable_service() {
echo -e "${YELLOW}正在启用开机自启动...${NC}"
if sudo systemctl enable "$SERVICE_NAME"; then
echo -e "${GREEN}[OK] 开机自启动已启用${NC}"
else
echo -e "${RED}[ERROR] 启用开机自启动失败${NC}"
fi
}
# 禁用开机自启动
disable_service() {
echo -e "${YELLOW}正在禁用开机自启动...${NC}"
if sudo systemctl disable "$SERVICE_NAME"; then
echo -e "${GREEN}[OK] 开机自启动已禁用${NC}"
else
echo -e "${RED}[ERROR] 禁用开机自启动失败${NC}"
fi
}
# 查看今天的日志
show_today_log() {
local log_file="$LOG_PATH/network_monitor_$(date '+%Y%m%d').log"
echo -e "${CYAN}=== 今天的日志 ($log_file) ===${NC}"
if [[ -f "$log_file" ]]; then
echo -e "${BLUE}文件大小: $(du -h "$log_file" | cut -f1)${NC}"
echo -e "${BLUE}最后修改: $(stat -c %y "$log_file")${NC}"
echo ""
echo -e "${YELLOW}最近20行:${NC}"
tail -n 20 "$log_file"
echo ""
echo -e "${CYAN}按任意键查看所有内容,或按 Ctrl+C 返回菜单${NC}"
read -n 1 -s
less "$log_file"
else
echo -e "${RED}今天的日志文件不存在${NC}"
echo -e "${BLUE}日志文件路径: $log_file${NC}"
fi
}
# 查看实时日志
show_live_log() {
local log_file="$LOG_PATH/network_monitor_$(date '+%Y%m%d').log"
echo -e "${CYAN}=== 实时日志监控 ===${NC}"
echo -e "${YELLOW}按 Ctrl+C 退出实时监控${NC}"
echo ""
if [[ -f "$log_file" ]]; then
tail -f "$log_file"
else
echo -e "${RED}今天的日志文件不存在,监控系统日志...${NC}"
if [[ "$IS_SYSTEM_INSTALL" == "true" ]]; then
journalctl -u "$SERVICE_NAME" -f
else
echo -e "${RED}无可用日志${NC}"
fi
fi
}
# 查看系统日志
show_system_log() {
echo -e "${CYAN}=== 系统日志 ===${NC}"
echo -e "${YELLOW}最近100条系统日志:${NC}"
journalctl -u "$SERVICE_NAME" -n 100 --no-pager
echo ""
echo -e "${CYAN}按任意键查看实时系统日志,或按 Ctrl+C 返回菜单${NC}"
read -n 1 -s
journalctl -u "$SERVICE_NAME" -f
}
# 查看配置文件
show_config() {
echo -e "${CYAN}=== 配置文件 ($CONFIG_FILE) ===${NC}"
if [[ -f "$CONFIG_FILE" ]]; then
echo -e "${BLUE}文件路径: $CONFIG_FILE${NC}"
echo -e "${BLUE}最后修改: $(stat -c %y "$CONFIG_FILE")${NC}"
echo ""
echo -e "${YELLOW}当前配置:${NC}"
cat "$CONFIG_FILE"
echo ""
# 如果有jq则格式化显示
if command -v jq >/dev/null 2>&1; then
echo -e "${YELLOW}格式化显示:${NC}"
jq . "$CONFIG_FILE" 2>/dev/null || echo -e "${RED}JSON格式错误${NC}"
fi
else
echo -e "${RED}配置文件不存在: $CONFIG_FILE${NC}"
echo -e "${YELLOW}是否创建默认配置文件? (y/N): ${NC}"
read -n 1 response
echo ""
if [[ "$response" == "y" || "$response" == "Y" ]]; then
create_default_config
fi
fi
}
# 创建默认配置文件
create_default_config() {
local config_dir=$(dirname "$CONFIG_FILE")
# 确保配置目录存在
if [[ ! -d "$config_dir" ]]; then
if [[ "$IS_SYSTEM_INSTALL" == "true" ]]; then
sudo mkdir -p "$config_dir"
else
mkdir -p "$config_dir"
fi
fi
local default_config='{
"TargetIP": "192.168.3.3",
"MonitorWindowSeconds": 180,
"ShutdownCountdown": 60,
"NormalPingInterval": 15
}'
if [[ "$IS_SYSTEM_INSTALL" == "true" ]]; then
echo "$default_config" | sudo tee "$CONFIG_FILE" > /dev/null
else
echo "$default_config" > "$CONFIG_FILE"
fi
echo -e "${GREEN}[OK] 默认配置文件已创建: $CONFIG_FILE${NC}"
}
# 编辑配置文件
edit_config() {
if [[ ! -f "$CONFIG_FILE" ]]; then
echo -e "${YELLOW}配置文件不存在,是否创建默认配置? (y/N): ${NC}"
read -n 1 response
echo ""
if [[ "$response" == "y" || "$response" == "Y" ]]; then
create_default_config
else
return
fi
fi
echo -e "${CYAN}=== 编辑配置文件 ===${NC}"
echo -e "${YELLOW}使用编辑器编辑配置文件...${NC}"
# 选择编辑器
local editor=""
if command -v nano >/dev/null 2>&1; then
editor="nano"
elif command -v vi >/dev/null 2>&1; then
editor="vi"
else
echo -e "${RED}未找到可用的编辑器${NC}"
return
fi
echo -e "${BLUE}使用编辑器: $editor${NC}"
echo -e "${YELLOW}编辑完成后,如果是系统安装,需要重启服务使配置生效${NC}"
echo ""
if [[ "$IS_SYSTEM_INSTALL" == "true" ]]; then
sudo "$editor" "$CONFIG_FILE"
echo ""
echo -e "${YELLOW}配置已修改,是否重启服务使配置生效? (y/N): ${NC}"
read -n 1 response
echo ""
if [[ "$response" == "y" || "$response" == "Y" ]]; then
restart_service
fi
else
"$editor" "$CONFIG_FILE"
fi
}
# 测试网络连接
test_network() {
local target_ip="192.168.3.3"
# 尝试从配置文件读取目标IP
if [[ -f "$CONFIG_FILE" ]] && command -v jq >/dev/null 2>&1; then
local config_ip=$(jq -r '.TargetIP // empty' "$CONFIG_FILE" 2>/dev/null)
[[ -n "$config_ip" ]] && target_ip="$config_ip"
fi
echo -e "${CYAN}=== 网络连接测试 ===${NC}"
echo -e "${BLUE}目标IP: $target_ip${NC}"
echo ""
echo -e "${YELLOW}正在测试网络连接...${NC}"
for i in {1..5}; do
echo -n "测试 $i/5: "
if ping -c 1 -W 3 "$target_ip" >/dev/null 2>&1; then
echo -e "${GREEN}✓ 连接成功${NC}"
else
echo -e "${RED}✗ 连接失败${NC}"
fi
[[ $i -lt 5 ]] && sleep 1
done
echo ""
echo -e "${CYAN}详细ping测试:${NC}"
ping -c 4 "$target_ip"
}
# 启动本地脚本
start_local_script() {
local script_path="./smart_shutdown.sh"
echo -e "${CYAN}=== 启动本地脚本 ===${NC}"
if [[ ! -f "$script_path" ]]; then
echo -e "${RED}本地脚本不存在: $script_path${NC}"
return
fi
if [[ ! -x "$script_path" ]]; then
echo -e "${YELLOW}脚本没有执行权限,正在添加...${NC}"
chmod +x "$script_path"
fi
echo -e "${YELLOW}即将启动本地脚本(前台运行)${NC}"
echo -e "${YELLOW}按 Ctrl+C 可以停止脚本${NC}"
echo -e "${BLUE}3秒后开始...${NC}"
for i in {3..1}; do
echo -n "$i "
sleep 1
done
echo ""
echo ""
sudo "$script_path"
}
# 清理旧日志
cleanup_old_logs() {
echo -e "${CYAN}=== 清理旧日志 ===${NC}"
if [[ ! -d "$LOG_PATH" ]]; then
echo -e "${RED}日志目录不存在: $LOG_PATH${NC}"
return
fi
echo -e "${BLUE}日志目录: $LOG_PATH${NC}"
echo -e "${YELLOW}查找30天前的日志文件...${NC}"
local old_logs=$(find "$LOG_PATH" -name "network_monitor_*.log" -type f -mtime +30 2>/dev/null)
if [[ -z "$old_logs" ]]; then
echo -e "${GREEN}没有找到需要清理的旧日志文件${NC}"
return
fi
echo -e "${YELLOW}找到以下旧日志文件:${NC}"
echo "$old_logs"
echo ""
echo -e "${YELLOW}确定要删除这些文件吗? (y/N): ${NC}"
read -n 1 response
echo ""
if [[ "$response" == "y" || "$response" == "Y" ]]; then
echo "$old_logs" | xargs rm -f
echo -e "${GREEN}[OK] 旧日志文件已清理${NC}"
else
echo -e "${YELLOW}清理已取消${NC}"
fi
}
# 显示服务信息
show_service_info() {
echo -e "${CYAN}=== 服务详细信息 ===${NC}"
echo -e "${YELLOW}基本信息:${NC}"
echo -e "服务名称: ${BLUE}$SERVICE_NAME${NC}"
echo -e "配置文件: ${BLUE}$CONFIG_FILE${NC}"
echo -e "日志目录: ${BLUE}$LOG_PATH${NC}"
echo ""
echo -e "${YELLOW}服务状态:${NC}"
systemctl show "$SERVICE_NAME" --no-pager
echo ""
echo -e "${YELLOW}最近的服务日志:${NC}"
journalctl -u "$SERVICE_NAME" -n 10 --no-pager
}
# 等待用户按键
wait_for_key() {
echo ""
echo -e "${CYAN}按任意键继续...${NC}"
read -n 1 -s
}
# 主程序
main() {
while true; do
show_header
show_menu
read -p "请选择操作: " choice
echo ""
case "$choice" in
"1")
if [[ "$IS_SYSTEM_INSTALL" == "true" ]]; then
show_service_status
else
show_config
fi
wait_for_key
;;
"2")
if [[ "$IS_SYSTEM_INSTALL" == "true" ]]; then
start_service
else
edit_config
fi
wait_for_key
;;
"3")
if [[ "$IS_SYSTEM_INSTALL" == "true" ]]; then
stop_service
else
show_today_log
fi
wait_for_key
;;
"4")
if [[ "$IS_SYSTEM_INSTALL" == "true" ]]; then
restart_service
else
show_live_log
fi
;;
"5")
if [[ "$IS_SYSTEM_INSTALL" == "true" ]]; then
enable_service
else
test_network
fi
wait_for_key
;;
"6")
if [[ "$IS_SYSTEM_INSTALL" == "true" ]]; then
disable_service
else
start_local_script
fi
wait_for_key
;;
"7")
if [[ "$IS_SYSTEM_INSTALL" == "true" ]]; then
show_today_log
else
cleanup_old_logs
fi
;;
"8")
[[ "$IS_SYSTEM_INSTALL" == "true" ]] && show_live_log
;;
"9")
[[ "$IS_SYSTEM_INSTALL" == "true" ]] && show_system_log
;;
"10")
[[ "$IS_SYSTEM_INSTALL" == "true" ]] && show_config
wait_for_key
;;
"11")
[[ "$IS_SYSTEM_INSTALL" == "true" ]] && edit_config
wait_for_key
;;
"12")
[[ "$IS_SYSTEM_INSTALL" == "true" ]] && test_network
wait_for_key
;;
"13")
[[ "$IS_SYSTEM_INSTALL" == "true" ]] && show_service_info
wait_for_key
;;
"0")
echo -e "${GREEN}再见!${NC}"
exit 0
;;
*)
echo -e "${RED}无效选择,请重新输入${NC}"
wait_for_key
;;
esac
done
}
# 检查依赖
check_dependencies() {
local missing_deps=()
# 检查必要命令
for cmd in ping systemctl journalctl; do
if ! command -v "$cmd" >/dev/null 2>&1; then
missing_deps+=("$cmd")
fi
done
if [[ ${#missing_deps[@]} -gt 0 ]]; then
echo -e "${RED}缺少必要的命令:${NC}"
for dep in "${missing_deps[@]}"; do
echo -e "${RED} - $dep${NC}"
done
echo -e "${YELLOW}请安装缺少的软件包${NC}"
exit 1
fi
# 检查可选命令
if ! command -v jq >/dev/null 2>&1; then
echo -e "${YELLOW}建议安装 jq 以获得更好的JSON配置文件支持${NC}"
echo -e "${BLUE}安装命令: sudo apt-get install jq${NC}"
echo ""
fi
}
# 启动主程序
check_dependencies
main

122
pkg/config/config.go Normal file
View File

@@ -0,0 +1,122 @@
package config
import (
"encoding/json"
"fmt"
"net"
"os"
"path/filepath"
"runtime"
"strconv"
)
type Config struct {
TargetIP string `json:"TargetIP"`
MonitorWindowSeconds int `json:"MonitorWindowSeconds"`
ShutdownCountdown int `json:"ShutdownCountdown"`
NormalPingInterval int `json:"NormalPingInterval"`
}
func DefaultConfig() *Config {
return &Config{
TargetIP: "192.168.3.1",
MonitorWindowSeconds: 180,
ShutdownCountdown: 60,
NormalPingInterval: 15,
}
}
func GetConfigDir() string {
if runtime.GOOS == "windows" {
return `C:\ProgramData\SmartNetworkMonitor`
}
return `/etc/smart-network-monitor`
}
func GetLogDir() string {
if runtime.GOOS == "windows" {
return `C:\ProgramData\SmartNetworkMonitor\logs`
}
return `/var/log/smart-network-monitor`
}
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) {
return DefaultConfig(), nil
}
return nil, err
}
if len(data) >= 3 && data[0] == 0xef && data[1] == 0xbb && data[2] == 0xbf {
data = data[3:]
}
cfg := &Config{}
if err := json.Unmarshal(data, cfg); err != nil {
return nil, err
}
return cfg, nil
}
func SaveConfig(cfg *Config) error {
configDir := GetConfigDir()
if err := os.MkdirAll(configDir, 0755); err != nil {
return err
}
configPath := filepath.Join(configDir, "config.json")
data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return err
}
return os.WriteFile(configPath, data, 0644)
}
func UpdateConfig(key string, value string) error {
cfg, err := LoadConfig()
if err != nil {
return fmt.Errorf("加载配置文件失败: %v", err)
}
switch key {
case "TargetIP":
if net.ParseIP(value) == nil {
return fmt.Errorf("非法 IP 地址: %s", value)
}
cfg.TargetIP = value
case "MonitorWindowSeconds":
val, err := strconv.Atoi(value)
if err != nil || val <= 0 {
return fmt.Errorf("非法输入: MonitorWindowSeconds 必须为正整数")
}
cfg.MonitorWindowSeconds = val
case "ShutdownCountdown":
val, err := strconv.Atoi(value)
if err != nil || val <= 0 {
return fmt.Errorf("非法输入: ShutdownCountdown 必须为正整数")
}
cfg.ShutdownCountdown = val
case "NormalPingInterval":
val, err := strconv.Atoi(value)
if err != nil || val <= 0 {
return fmt.Errorf("非法输入: NormalPingInterval 必须为正整数")
}
cfg.NormalPingInterval = val
default:
return fmt.Errorf("未知配置项: %s (开放编辑项: TargetIP, MonitorWindowSeconds, ShutdownCountdown, NormalPingInterval)", key)
}
return SaveConfig(cfg)
}

138
pkg/config/interactive.go Normal file
View File

@@ -0,0 +1,138 @@
package config
import (
"bufio"
"fmt"
"net"
"os"
"path/filepath"
"strconv"
"strings"
)
// ConfigFileExists checks if the config.json file exists at the expected location.
func ConfigFileExists() bool {
configPath := filepath.Join(GetConfigDir(), "config.json")
_, err := os.Stat(configPath)
return err == nil
}
// isTerminal checks if stdin is connected to a terminal (not a pipe).
func isTerminal() bool {
fi, err := os.Stdin.Stat()
if err != nil {
return false
}
return fi.Mode()&os.ModeCharDevice != 0
}
// InteractiveSetup prompts the user to configure each parameter interactively.
// If stdin is not a terminal (pipe mode), it silently saves and returns defaults.
func InteractiveSetup() (*Config, error) {
cfg := DefaultConfig()
if !isTerminal() {
if err := SaveConfig(cfg); err != nil {
return nil, fmt.Errorf("写入默认配置失败: %v", err)
}
return cfg, nil
}
fmt.Println("\n========== 首次配置 ==========")
fmt.Println("未检测到配置文件,将引导您完成初始设置。直接按回车使用 [默认值]。")
scanner := bufio.NewScanner(os.Stdin)
cfg.TargetIP = promptString(scanner, "目标监控 IP 地址", cfg.TargetIP, func(s string) error {
if net.ParseIP(s) == nil {
return fmt.Errorf("非法 IP 地址: %s", s)
}
return nil
})
cfg.MonitorWindowSeconds = promptInt(scanner, "断网容忍超时时长 (秒)", cfg.MonitorWindowSeconds)
cfg.ShutdownCountdown = promptInt(scanner, "关机倒计时缓冲 (秒)", cfg.ShutdownCountdown)
cfg.NormalPingInterval = promptInt(scanner, "探测发包间隔 (秒)", cfg.NormalPingInterval)
if err := SaveConfig(cfg); err != nil {
return nil, fmt.Errorf("写入配置文件失败: %v", err)
}
configPath := filepath.Join(GetConfigDir(), "config.json")
fmt.Printf("\n配置已保存至: %s\n", configPath)
return cfg, nil
}
// ConfirmAndCleanup prompts the user to confirm deletion of config and log files separately.
// Non-terminal stdin skips all prompts.
func ConfirmAndCleanup() {
if !isTerminal() {
return
}
scanner := bufio.NewScanner(os.Stdin)
configDir := GetConfigDir()
configPath := filepath.Join(configDir, "config.json")
logDir := GetLogDir()
if _, err := os.Stat(configPath); err == nil {
fmt.Printf("检测到配置文件: %s\n是否清除配置文件[y/N]: ", configPath)
if scanner.Scan() && strings.EqualFold(strings.TrimSpace(scanner.Text()), "y") {
os.Remove(configPath)
fmt.Println("配置文件已清除。")
}
}
if _, err := os.Stat(logDir); err == nil {
fmt.Printf("检测到日志目录: %s\n是否清除日志文件[y/N]: ", logDir)
if scanner.Scan() && strings.EqualFold(strings.TrimSpace(scanner.Text()), "y") {
os.RemoveAll(logDir)
fmt.Println("日志文件已清除。")
}
}
// If configDir is now empty, remove it too
entries, err := os.ReadDir(configDir)
if err == nil && len(entries) == 0 {
os.Remove(configDir)
}
}
func promptString(scanner *bufio.Scanner, label, defaultVal string, validate func(string) error) string {
for {
fmt.Printf("\n%s [%s]: ", label, defaultVal)
if !scanner.Scan() {
return defaultVal
}
input := strings.TrimSpace(scanner.Text())
if input == "" {
return defaultVal
}
if validate != nil {
if err := validate(input); err != nil {
fmt.Printf(" 输入无效: %v请重新输入。\n", err)
continue
}
}
return input
}
}
func promptInt(scanner *bufio.Scanner, label string, defaultVal int) int {
for {
fmt.Printf("%s [%d]: ", label, defaultVal)
if !scanner.Scan() {
return defaultVal
}
input := strings.TrimSpace(scanner.Text())
if input == "" {
return defaultVal
}
val, err := strconv.Atoi(input)
if err != nil || val <= 0 {
fmt.Println(" 输入无效: 必须为正整数,请重新输入。")
continue
}
return val
}
}

52
pkg/config/pause.go Normal file
View File

@@ -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
}

63
pkg/daemon/service.go Normal file
View File

@@ -0,0 +1,63 @@
package daemon
import (
"context"
"github.com/kardianos/service"
"smart-shutdown/pkg/config"
"smart-shutdown/pkg/logger"
"smart-shutdown/pkg/monitor"
)
type program struct {
exit chan struct{}
cancel context.CancelFunc
cfg *config.Config
}
func (p *program) Start(s service.Service) error {
logger.Info("启动系统监控后台服务")
ctx, cancel := context.WithCancel(context.Background())
p.cancel = cancel
p.exit = make(chan struct{})
go p.run(ctx)
return nil
}
func (p *program) run(ctx context.Context) {
monitor.Run(ctx, p.cfg)
close(p.exit)
}
func (p *program) Stop(s service.Service) error {
logger.Info("停止系统监控后台服务")
if p.cancel != nil {
p.cancel()
}
<-p.exit
return nil
}
func GetService(cfg *config.Config, execPath ...string) (service.Service, error) {
svcConfig := &service.Config{
Name: "SmartNetworkMonitor",
DisplayName: "Smart Network Shutdown Monitor",
Description: "A reliable daemon that periodically monitors network states and triggers node suspension logically.",
}
if len(execPath) > 0 && execPath[0] != "" {
svcConfig.Executable = execPath[0]
}
prg := &program{
cfg: cfg,
}
s, err := service.New(prg, svcConfig)
if err != nil {
return nil, err
}
return s, nil
}

120
pkg/logger/logger.go Normal file
View File

@@ -0,0 +1,120 @@
package logger
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/fatih/color"
"gopkg.in/natefinch/lumberjack.v2"
"smart-shutdown/pkg/config"
)
var fileLogger *lumberjack.Logger
var debugEnabled bool
func InitLogger() error {
logDir := config.GetLogDir()
if err := os.MkdirAll(logDir, 0755); err != nil {
logDir = "logs"
os.MkdirAll(logDir, 0755)
}
logFile := filepath.Join(logDir, "network_monitor.log")
fileLogger = &lumberjack.Logger{
Filename: logFile,
MaxSize: 10,
MaxBackups: 30,
MaxAge: 30,
Compress: false,
}
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
}
func writeLog(level, plainPrefix, format string, v ...interface{}) {
msg := fmt.Sprintf(format, v...)
timestamp := time.Now().Format("2006/01/02 15:04:05")
if fileLogger != nil {
plainLine := fmt.Sprintf("%s %s %s\n", plainPrefix, timestamp, msg)
fileLogger.Write([]byte(plainLine))
}
prefixColorFunc := getPrefixColor(level)
coloredPrefix := prefixColorFunc(plainPrefix)
coloredLine := fmt.Sprintf("%s %s %s\n", coloredPrefix, timestamp, msg)
if level == "FAIL" || level == "CRITICAL" {
fmt.Fprint(os.Stderr, coloredLine)
} else {
fmt.Fprint(os.Stdout, coloredLine)
}
}
func getPrefixColor(level string) func(a ...interface{}) string {
switch level {
case "SUCCESS":
return color.New(color.FgGreen).SprintFunc()
case "WARN":
return color.New(color.FgYellow).SprintFunc()
case "FAIL":
return color.New(color.FgRed).SprintFunc()
case "CRITICAL":
return color.New(color.FgHiRed).SprintFunc()
case "DEBUG":
return color.New(color.FgCyan).SprintFunc()
default:
return color.New(color.Reset).SprintFunc()
}
}
func Debug(format string, v ...interface{}) {
if debugEnabled {
writeLog("DEBUG", "[DEBUG]", format, v...)
}
}
func Info(format string, v ...interface{}) {
writeLog("INFO", "[INFO]", format, v...)
}
func Succ(format string, v ...interface{}) {
writeLog("SUCCESS", "[SUCCESS]", format, v...)
}
func Warn(format string, v ...interface{}) {
writeLog("WARN", "[WARN]", format, v...)
}
func Fail(format string, v ...interface{}) {
writeLog("FAIL", "[FAIL]", format, v...)
}
func Crit(format string, v ...interface{}) {
writeLog("CRITICAL", "[CRITICAL]", format, v...)
}

129
pkg/monitor/statemachine.go Normal file
View File

@@ -0,0 +1,129 @@
package monitor
import (
"context"
"time"
"smart-shutdown/pkg/config"
"smart-shutdown/pkg/logger"
"smart-shutdown/pkg/pinger"
"smart-shutdown/pkg/system"
)
func Run(ctx context.Context, cfg *config.Config) {
logger.Info("启动后台网络监控")
logger.Info("监控目标 IP: %s", cfg.TargetIP)
logger.Info("故障判定窗口: %d秒", cfg.MonitorWindowSeconds)
logger.Info("关机倒计时阈值: %d秒", cfg.ShutdownCountdown)
var failureStartTime time.Time
var inFailureWindow bool = false
var wasPausedBefore bool = false
normalStatusCounter := 0
const normalStatusLogInterval = 24
for {
select {
case <-ctx.Done():
logger.Info("接收到系统停止指令, 退出核心调度监控")
return
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 {
if inFailureWindow {
logger.Succ("网络连通性恢复, 监控窗口重置")
inFailureWindow = false
normalStatusCounter = 0
} else {
normalStatusCounter++
if normalStatusCounter >= normalStatusLogInterval {
logger.Info("网络探测状态正常 (定期状态更新)")
normalStatusCounter = 0
}
}
time.Sleep(time.Duration(cfg.NormalPingInterval) * time.Second)
} else {
logger.Warn("目标节点未响应探针包: %s", cfg.TargetIP)
if !inFailureWindow {
failureStartTime = time.Now()
inFailureWindow = true
logger.Warn("触发网络状态失效, 开始累计中断时间监控")
}
failureDuration := time.Since(failureStartTime).Seconds()
if int(failureDuration) >= cfg.MonitorWindowSeconds {
logger.Crit("持续中断时长超越设定的阈值上限 (%d秒), 进入关机响应流程", cfg.MonitorWindowSeconds)
shutdownCancelled := startCountdown(ctx, cfg.TargetIP, cfg.ShutdownCountdown)
if !shutdownCancelled {
logger.Crit("倒计时时限完毕, 开始下发系统级级终态关机指令")
system.ExecuteShutdown()
return
} else {
inFailureWindow = false
}
} else {
logger.Info("网络持续断失: %.0f/%d 秒", failureDuration, cfg.MonitorWindowSeconds)
time.Sleep(time.Duration(cfg.NormalPingInterval) * time.Second)
}
}
}
}
func startCountdown(ctx context.Context, targetIP string, countdownSec int) bool {
logger.Warn("执行系统关机倒计时流程 (预设: %d秒)...", countdownSec)
endTime := time.Now().Add(time.Duration(countdownSec) * time.Second)
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
for {
remaining := int(time.Until(endTime).Seconds())
if remaining <= 0 {
return false
}
logger.Warn("距离关机指令处分剩余: %d 秒", remaining)
select {
case <-ctx.Done():
logger.Info("挂起关机倒计流程:收到系统强制干预信号")
return true
case <-ticker.C:
if pinger.Ping(targetIP, 3) {
logger.Succ("倒计时周期内网络指标恢复, 全局关机流程已被重置并取消")
return true
}
}
}
}

34
pkg/pinger/pinger.go Normal file
View File

@@ -0,0 +1,34 @@
package pinger
import (
"time"
probing "github.com/prometheus-community/pro-bing"
"smart-shutdown/pkg/logger"
)
func Ping(targetIP string, timeoutSec int) bool {
pinger, err := probing.NewPinger(targetIP)
if err != nil {
logger.Warn("目标 IP 解析异常 [%s]: %v", targetIP, err)
return false
}
pinger.SetPrivileged(true)
pinger.Count = 1
pinger.Timeout = time.Duration(timeoutSec) * time.Second
err = pinger.Run()
if err != nil {
logger.Fail("探针执行失败: %v", err)
return false
}
stats := pinger.Statistics()
if stats.PacketsRecv > 0 {
return true
}
return false
}

26
pkg/system/shutdown.go Normal file
View File

@@ -0,0 +1,26 @@
package system
import (
"os/exec"
"runtime"
"smart-shutdown/pkg/logger"
)
func ExecuteShutdown() error {
logger.Crit("系统调用:执行关机指令")
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = exec.Command("shutdown", "-s", "-f", "-t", "0")
} else {
cmd = exec.Command("shutdown", "-h", "now")
}
err := cmd.Run()
if err != nil {
logger.Crit("系统关机指令执行失败: %v", err)
return err
}
return nil
}

146
pkg/updater/updater.go Normal file
View File

@@ -0,0 +1,146 @@
package updater
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"golang.org/x/mod/semver"
)
type githubRelease struct {
TagName string `json:"tag_name"`
Assets []struct {
Name string `json:"name"`
BrowserDownloadURL string `json:"browser_download_url"`
} `json:"assets"`
}
func CheckForUpdate(currentVersion string) (hasNew bool, latestVersion string, downloadURL string) {
if currentVersion == "dev" || currentVersion == "" {
return false, "", ""
}
client := http.Client{Timeout: 8 * time.Second}
resp, err := client.Get("https://api.github.com/repos/UnbalancedCat/smart-shutdown/releases/latest")
if err != nil || resp.StatusCode != 200 {
return false, "", ""
}
defer resp.Body.Close()
var release githubRelease
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
return false, "", ""
}
latestVersion = release.TagName
cv := currentVersion
if !semver.IsValid(cv) && !strings.HasPrefix(cv, "v") {
cv = "v" + cv
}
lv := latestVersion
if !semver.IsValid(lv) && !strings.HasPrefix(lv, "v") {
lv = "v" + lv
}
if semver.Compare(cv, lv) < 0 {
expectedAsset := fmt.Sprintf("smart-shutdown_%s_%s", runtime.GOOS, runtime.GOARCH)
if runtime.GOOS == "windows" {
expectedAsset += ".exe"
}
for _, asset := range release.Assets {
if asset.Name == expectedAsset {
return true, latestVersion, asset.BrowserDownloadURL
}
}
}
return false, "", ""
}
func CheckAndPrintUpdate(currentVersion string) {
fmt.Printf("当前内核版本: %s\n", currentVersion)
fmt.Println("正在检测云端发布节点是否有可用新版本...")
hasNew, latest, _ := CheckForUpdate(currentVersion)
if hasNew {
fmt.Printf("\n[发现更新] 获取到最新稳定版本: %s\n", latest)
fmt.Println("请执行 'smart-shutdown update' 以全自动获取并覆盖部署该更新。")
} else {
fmt.Println("当前已是最新运行版本,暂无可用更新。")
}
}
func DownloadAndReplace(downloadURL string) error {
currentExe, err := os.Executable()
if err != nil {
return fmt.Errorf("无法溯源自身执行路径: %v", err)
}
tempExe := filepath.Join(os.TempDir(), "smart-shutdown-update.tmp")
out, err := os.Create(tempExe)
if err != nil {
return fmt.Errorf("系统缓存区句柄开辟失败: %v", err)
}
resp, err := http.Get(downloadURL)
if err != nil || resp.StatusCode != 200 {
out.Close()
os.Remove(tempExe)
return fmt.Errorf("网络传输流建立失败,节点远端可能受限")
}
_, err = io.Copy(out, resp.Body)
resp.Body.Close()
out.Close()
if err != nil {
os.Remove(tempExe)
return fmt.Errorf("物理覆盖字节流中断: %v", err)
}
if runtime.GOOS == "windows" {
oldExe := currentExe + ".old"
os.Remove(oldExe)
if err := os.Rename(currentExe, oldExe); err != nil {
return fmt.Errorf("操作系统拒绝进程脱壳 (文件锁互斥): %v", err)
}
}
if err := copyFile(tempExe, currentExe); err != nil {
if runtime.GOOS == "windows" {
os.Rename(currentExe+".old", currentExe)
}
return fmt.Errorf("原子级覆盖执行核心挫败: %v", err)
}
os.Remove(tempExe)
if runtime.GOOS != "windows" {
os.Chmod(currentExe, 0755)
}
return nil
}
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
return err
}

View File

View File

@@ -1,239 +0,0 @@
#Requires -Version 5.1
#Requires -RunAsAdministrator
<#
.SYNOPSIS
一个智能网络监控脚本当检测到与目标IP的网络持续中断后会自动关闭计算机。
.DESCRIPTION
该脚本会定期测试与指定目标IP的网络连通性。
如果在设定的“监控窗口”时间内连接持续失败,脚本将启动一个可取消的关机倒计时。
如果在倒计时期间网络恢复,关机将被中止。
所有操作都会被记录在日志文件中。
#>
# ==================== 配置参数 ====================
# 监控的目标IP地址
[string]$TargetIP = "192.168.3.15"
# 正常监控时的ping间隔
[int]$NormalPingInterval = 15
# 监控窗口时长(秒) - 在此期间持续失败则触发关机
[int]$MonitorWindowSeconds = 60
# 关机倒计时时长(秒)
[int]$ShutdownCountdown = 60
# 关机倒计时阶段的ping间隔
[int]$CountdownPingInterval = 3
# ping超时时间
[int]$PingTimeout = 3
# 日志配置
[string]$LogDirectory = Join-Path -Path $PSScriptRoot -ChildPath "logs"
#[string]$LogFile = Join-Path -Path $LogDirectory -ChildPath "network_monitor_$(Get-Date -Format 'yyyyMMdd').log"
[int]$MaxLogDays = 30
# =================================================
# --- 函数定义 ---
# 日志写入函数
function Write-Log {
param(
[Parameter(Mandatory=$true)]
[ValidateSet("INFO", "SUCCESS", "FAIL", "WARN", "CRITICAL", "DEBUG")]
[string]$Level,
[Parameter(Mandatory=$true)]
[string]$Message
)
# ===================== V0.2update =====================
# 每次写日志时,都重新根据当前日期确定日志文件名
$LogFile = Join-Path -Path $LogDirectory -ChildPath "network_monitor_$(Get-Date -Format 'yyyyMMdd').log"
# ====================================================
$logTimestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$logTimestamp] [$Level] $Message"
# 尝试写入日志文件
try {
if (-not (Test-Path -Path $LogDirectory)) {
New-Item -Path $LogDirectory -ItemType Directory -ErrorAction Stop | Out-Null
}
Add-Content -Path $LogFile -Value $logEntry -ErrorAction Stop
}
catch {
Write-Host "[$logTimestamp] [CRITICAL] 日志文件写入失败!请检查权限或磁盘空间。" -ForegroundColor Red
}
# 根据日志级别在控制台输出不同颜色的信息
switch ($Level) {
"SUCCESS" { Write-Host $logEntry -ForegroundColor Green }
"FAIL" { Write-Host $logEntry -ForegroundColor Red }
"WARN" { Write-Host $logEntry -ForegroundColor Yellow }
"CRITICAL" { Write-Host $logEntry -ForegroundColor DarkRed }
default { Write-Host $logEntry }
}
}
# 获取本机的最佳IP地址
function Get-LocalIP {
param($TargetIP)
try {
# 尝试寻找与目标IP在同一个子网的本机IP地址
$targetSubnet = ($TargetIP.Split('.')[0..2]) -join '.'
$ip = Get-NetIPAddress -AddressFamily IPv4 -AddressState Preferred | Where-Object { $_.IPAddress -like "$targetSubnet.*" } | Select-Object -First 1 -ExpandProperty IPAddress
if ($ip) { return $ip }
# 如果找不到则返回任意一个非环回的私有IP地址
$ip = Get-NetIPAddress -AddressFamily IPv4 -AddressState Preferred | Where-Object { $_.IPAddress -notlike "127.0.0.1" -and $_.InterfaceAlias -notlike "Loopback*" } | Select-Object -First 1 -ExpandProperty IPAddress
return $ip
}
catch {
return "未知"
}
}
# --- 脚本主逻辑 ---
# 清理旧日志
if (Test-Path -Path $LogDirectory) {
Get-ChildItem -Path $LogDirectory -Filter "*.log" | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$MaxLogDays) } | Remove-Item
}
# 初始化
Clear-Host
$localIP = Get-LocalIP -TargetIP $TargetIP
Write-Log -Level "INFO" -Message "========== 脚本启动 =========="
Write-Log -Level "INFO" -Message "本机IP: $localIP"
Write-Log -Level "INFO" -Message "监控目标: $TargetIP"
Write-Log -Level "INFO" -Message "监控窗口: ${MonitorWindowSeconds}"
Write-Log -Level "INFO" -Message "关机倒计时: ${ShutdownCountdown}"
Write-Log -Level "INFO" -Message "日志文件: $LogFile"
Write-Log -Level "INFO" -Message "脚本已启动,按 Ctrl+C 退出..."
Write-Host "========================================"
# 主循环
$failureStartTime = $null
$normalStatusCounter = 0 # 正常状态计数器,用于定期记录正常运行日志
$normalStatusLogInterval = 24 # 每24次正常检测记录一次日志约6分钟
while ($true) {# 测试网络连接
try {
# 使用兼容性更好的ping方法
$pingResult = Test-Connection -ComputerName $TargetIP -Count 1 -Quiet -ErrorAction SilentlyContinue
$isOnline = $pingResult
} catch {
Write-Log -Level "FAIL" -Message "网络测试出现异常: $($_.Exception.Message)"
$isOnline = $false
} if ($isOnline) {
if ($failureStartTime) {
Write-Log -Level "SUCCESS" -Message "网络连接已恢复。"
Write-Host "[OK] 网络连接已恢复,重置监控窗口" -ForegroundColor Green
$failureStartTime = $null # 重置失败计时器
$normalStatusCounter = 0 # 重置正常状态计数器
} else {
Write-Host "[OK] 网络连接正常" -ForegroundColor Green
# 正常连接时减少日志频率,只定期记录
$normalStatusCounter++
if ($normalStatusCounter -ge $normalStatusLogInterval) {
Write-Log -Level "INFO" -Message "网络连接持续正常(已检测 $normalStatusCounter 次)"
$normalStatusCounter = 0 # 重置计数器
}
}
# 只在控制台显示等待信息,不写入日志文件
Write-Host "等待 ${NormalPingInterval}秒后继续监控..." -ForegroundColor Cyan
Start-Sleep -Seconds $NormalPingInterval
} else {
# 如果网络不通
Write-Log -Level "FAIL" -Message "Ping失败 - 目标: $TargetIP"
if (-not $failureStartTime) {
# 记录首次失败的时间
$failureStartTime = Get-Date
Write-Log -Level "WARN" -Message "网络首次中断,开始进入监控窗口计时。"
} # 计算网络已中断的时间
$failureDuration = (New-TimeSpan -Start $failureStartTime -End (Get-Date)).TotalSeconds
Write-Log -Level "INFO" -Message "网络已持续中断 $([math]::Round($failureDuration)) / ${MonitorWindowSeconds} 秒。"
# 显示详细状态
$remainingTime = $MonitorWindowSeconds - [math]::Round($failureDuration)
Write-Host "监控状态 - 已中断: $([math]::Round($failureDuration))/${MonitorWindowSeconds}秒,剩余: ${remainingTime}" -ForegroundColor Yellow
if ($failureDuration -ge $MonitorWindowSeconds) {
# 持续失败时间超过监控窗口,开始关机倒计时
Write-Log -Level "CRITICAL" -Message "网络持续中断已超过 ${MonitorWindowSeconds} 秒,开始关机流程。"
Write-Host "========================================" -ForegroundColor Yellow
$shutdownCancelled = $false
$countdownEndTime = (Get-Date).AddSeconds($ShutdownCountdown)
while ((Get-Date) -lt $countdownEndTime -and -not $shutdownCancelled) {
$remainingSeconds = [math]::Ceiling(($countdownEndTime - (Get-Date)).TotalSeconds)
if ($remainingSeconds -le 0) { break }
Write-Log -Level "WARN" -Message "距离关机还有 $remainingSeconds 秒... 正在快速检测网络。"
Write-Host "----------------------------------------" -ForegroundColor Yellow
Write-Host "距离关机还有 $remainingSeconds 秒..." -ForegroundColor Yellow
Write-Host "正在快速检测网络连接... 按任意键可手动取消关机" -ForegroundColor Yellow # 在倒计时中再次检测网络
try {
if (Test-Connection -ComputerName $TargetIP -Count 1 -Quiet -ErrorAction SilentlyContinue) {
Write-Log -Level "SUCCESS" -Message "网络在倒计时期间恢复!取消关机。"
Write-Host "========================================" -ForegroundColor Green
Write-Host "[OK] 网络连接已恢复!取消关机操作" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
$failureStartTime = $null
$shutdownCancelled = $true
break
} else {
Write-Host "[X] 网络仍然中断" -ForegroundColor Red
}
}
catch {
Write-Log -Level "WARN" -Message "倒计时期间网络测试出现异常: $($_.Exception.Message)"
Write-Host "[X] 网络测试异常" -ForegroundColor Red
}
# 检查是否有按键输入(手动取消)
$timeout = $CountdownPingInterval
$startTime = Get-Date
while (((Get-Date) - $startTime).TotalSeconds -lt $timeout -and -not $shutdownCancelled) {
if ([Console]::KeyAvailable) {
$null = [Console]::ReadKey($true) # 读取按键但不使用
Write-Log -Level "WARN" -Message "用户手动取消了关机操作" Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "[INFO] 用户已手动取消关机,返回监控模式" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
$failureStartTime = $null
$shutdownCancelled = $true
break }
Start-Sleep -Milliseconds 100
}
}
if (-not $shutdownCancelled) {
Write-Log -Level "CRITICAL" -Message "关机倒计时完成,执行系统关机命令。"
try {
# 取消注释下一行以启用实际关机
# Stop-Computer -Force
Write-Host "模拟关机Stop-Computer -Force (为防止意外,此行已注释)" -ForegroundColor Red
Write-Log -Level "CRITICAL" -Message "关机命令已执行(模拟模式)。"
}
catch {
Write-Log -Level "CRITICAL" -Message "关机命令执行失败: $($_.Exception.Message)"
}
# 脚本执行完关机命令后可以退出
exit
}
} else {
# 在监控窗口期内,继续等待
Write-Log -Level "INFO" -Message "等待 ${NormalPingInterval}秒后继续..."
Start-Sleep -Seconds $NormalPingInterval
}
}
}

View File

@@ -1,316 +0,0 @@
#!/bin/bash
#
# 智能网络监控脚本 - Ubuntu版本
#
# 功能描述:
# 该脚本会定期测试与指定目标IP的网络连通性。
# 如果在设定的"监控窗口"时间内连接持续失败,脚本将启动一个可取消的关机倒计时。
# 如果在倒计时期间网络恢复,关机将被中止。
# 所有操作都会被记录在日志文件中。
#
# 使用方法:
# sudo ./smart_shutdown.sh
#
# 要求需要root权限来执行关机命令
#
# ==================== 配置参数 ====================
# 监控的目标IP地址
TARGET_IP="192.168.3.15"
# 正常监控时的ping间隔
NORMAL_PING_INTERVAL=15
# 监控窗口时长(秒) - 在此期间持续失败则触发关机
MONITOR_WINDOW_SECONDS=180
# 关机倒计时时长(秒)
SHUTDOWN_COUNTDOWN=60
# 关机倒计时阶段的ping间隔
COUNTDOWN_PING_INTERVAL=3
# ping超时时间
PING_TIMEOUT=3
# 日志配置
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIRECTORY="$SCRIPT_DIR/logs"
MAX_LOG_DAYS=30
# 配置文件路径
CONFIG_FILE="$SCRIPT_DIR/config.json"
# ==================== 函数定义 ====================
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
PURPLE='\033[0;35m'
NC='\033[0m' # No Color
# 日志写入函数
write_log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local log_file="$LOG_DIRECTORY/network_monitor_$(date '+%Y%m%d').log"
local log_entry="[$timestamp] [$level] $message"
# 确保日志目录存在
mkdir -p "$LOG_DIRECTORY"
# 写入日志文件
echo "$log_entry" >> "$log_file"
# 根据日志级别在控制台输出不同颜色的信息
case "$level" in
"SUCCESS")
echo -e "${GREEN}$log_entry${NC}"
;;
"FAIL")
echo -e "${RED}$log_entry${NC}"
;;
"WARN")
echo -e "${YELLOW}$log_entry${NC}"
;;
"CRITICAL")
echo -e "${PURPLE}$log_entry${NC}"
;;
"INFO")
echo -e "$log_entry"
;;
*)
echo -e "$log_entry"
;;
esac
}
# 加载配置文件
load_configuration() {
if [[ -f "$CONFIG_FILE" ]]; then
write_log "INFO" "加载配置文件: $CONFIG_FILE"
# 使用jq解析JSON配置文件如果可用
if command -v jq >/dev/null 2>&1; then
local target_ip=$(jq -r '.TargetIP // empty' "$CONFIG_FILE" 2>/dev/null)
local monitor_window=$(jq -r '.MonitorWindowSeconds // empty' "$CONFIG_FILE" 2>/dev/null)
local shutdown_countdown=$(jq -r '.ShutdownCountdown // empty' "$CONFIG_FILE" 2>/dev/null)
local ping_interval=$(jq -r '.NormalPingInterval // empty' "$CONFIG_FILE" 2>/dev/null)
[[ -n "$target_ip" ]] && TARGET_IP="$target_ip"
[[ -n "$monitor_window" ]] && MONITOR_WINDOW_SECONDS="$monitor_window"
[[ -n "$shutdown_countdown" ]] && SHUTDOWN_COUNTDOWN="$shutdown_countdown"
[[ -n "$ping_interval" ]] && NORMAL_PING_INTERVAL="$ping_interval"
write_log "SUCCESS" "配置文件加载成功"
else
write_log "WARN" "jq未安装无法解析JSON配置文件使用默认配置"
fi
else
# 创建默认配置文件
create_default_config
fi
}
# 创建默认配置文件
create_default_config() {
cat > "$CONFIG_FILE" << EOF
{
"TargetIP": "$TARGET_IP",
"MonitorWindowSeconds": $MONITOR_WINDOW_SECONDS,
"ShutdownCountdown": $SHUTDOWN_COUNTDOWN,
"NormalPingInterval": $NORMAL_PING_INTERVAL
}
EOF
write_log "INFO" "创建默认配置文件: $CONFIG_FILE"
}
# 获取本机IP地址
get_local_ip() {
local local_ip
# 尝试获取默认路由接口的IP地址
local_ip=$(ip route get 8.8.8.8 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -1)
if [[ -z "$local_ip" ]]; then
# 备用方法获取第一个非环回接口的IP
local_ip=$(ip addr show | grep -E 'inet [0-9]' | grep -v '127.0.0.1' | head -1 | awk '{print $2}' | cut -d'/' -f1)
fi
[[ -n "$local_ip" ]] && echo "$local_ip" || echo "未知"
}
# 测试网络连接
test_network_connection() {
local target_ip="$1"
# 使用ping命令测试连接发送1个包超时时间为PING_TIMEOUT秒
ping -c 1 -W "$PING_TIMEOUT" "$target_ip" >/dev/null 2>&1
return $?
}
# 清理旧日志文件
cleanup_old_logs() {
if [[ -d "$LOG_DIRECTORY" ]]; then
# 删除超过MAX_LOG_DAYS天的日志文件
find "$LOG_DIRECTORY" -name "network_monitor_*.log" -type f -mtime +$MAX_LOG_DAYS -exec rm -f {} \; 2>/dev/null
fi
}
# 信号处理函数
cleanup_and_exit() {
write_log "INFO" "接收到退出信号,正在清理..."
write_log "INFO" "========== 脚本退出 =========="
exit 0
}
# 检查root权限
check_root_permission() {
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}错误此脚本需要root权限才能执行关机操作${NC}"
echo -e "${YELLOW}请使用以下命令运行:${NC}"
echo -e "${CYAN}sudo $0${NC}"
exit 1
fi
}
# ==================== 主程序开始 ====================
# 设置信号处理
trap cleanup_and_exit SIGINT SIGTERM
# 检查权限
check_root_permission
# 清理旧日志
cleanup_old_logs
# 加载配置
load_configuration
# 初始化
clear
local_ip=$(get_local_ip)
write_log "INFO" "========== 脚本启动 =========="
write_log "INFO" "运行用户: $(whoami)"
write_log "INFO" "本机IP: $local_ip"
write_log "INFO" "监控目标: $TARGET_IP"
write_log "INFO" "监控窗口: ${MONITOR_WINDOW_SECONDS}"
write_log "INFO" "关机倒计时: ${SHUTDOWN_COUNTDOWN}"
write_log "INFO" "日志目录: $LOG_DIRECTORY"
write_log "INFO" "配置文件: $CONFIG_FILE"
write_log "INFO" "脚本已启动,按 Ctrl+C 退出..."
echo "========================================"
# 主循环变量
failure_start_time=""
normal_status_counter=0
normal_status_log_interval=24 # 每24次正常检测记录一次日志约6分钟
# 主循环
while true; do
# 测试网络连接
if test_network_connection "$TARGET_IP"; then
# 网络连接正常
if [[ -n "$failure_start_time" ]]; then
write_log "SUCCESS" "网络连接已恢复"
echo -e "${GREEN}[OK] 网络连接已恢复,重置监控窗口${NC}"
failure_start_time="" # 重置失败计时器
normal_status_counter=0 # 重置正常状态计数器
else
echo -e "${GREEN}[OK] 网络连接正常${NC}"
# 正常连接时减少日志频率,只定期记录
((normal_status_counter++))
if [[ $normal_status_counter -ge $normal_status_log_interval ]]; then
write_log "INFO" "网络连接持续正常(已检测 $normal_status_counter 次)"
normal_status_counter=0 # 重置计数器
fi
fi
# 只在控制台显示等待信息,不写入日志文件
echo -e "${CYAN}等待 ${NORMAL_PING_INTERVAL}秒后继续监控...${NC}"
sleep "$NORMAL_PING_INTERVAL"
else
# 网络连接失败
write_log "FAIL" "Ping失败 - 目标: $TARGET_IP"
if [[ -z "$failure_start_time" ]]; then
# 记录首次失败的时间
failure_start_time=$(date +%s)
write_log "WARN" "网络首次中断,开始进入监控窗口计时"
fi
# 计算网络已中断的时间
current_time=$(date +%s)
failure_duration=$((current_time - failure_start_time))
write_log "INFO" "网络已持续中断 $failure_duration / ${MONITOR_WINDOW_SECONDS}"
# 显示详细状态
remaining_time=$((MONITOR_WINDOW_SECONDS - failure_duration))
echo -e "${YELLOW}监控状态 - 已中断: ${failure_duration}/${MONITOR_WINDOW_SECONDS}秒,剩余: ${remaining_time}${NC}"
if [[ $failure_duration -ge $MONITOR_WINDOW_SECONDS ]]; then
# 持续失败时间超过监控窗口,开始关机倒计时
write_log "CRITICAL" "网络持续中断已超过 ${MONITOR_WINDOW_SECONDS} 秒,开始关机流程"
echo "========================================"
shutdown_cancelled=false
countdown_end_time=$((current_time + SHUTDOWN_COUNTDOWN))
while [[ $(date +%s) -lt $countdown_end_time && "$shutdown_cancelled" == "false" ]]; do
current_time=$(date +%s)
remaining_seconds=$((countdown_end_time - current_time))
if [[ $remaining_seconds -le 0 ]]; then
break
fi
write_log "WARN" "距离关机还有 $remaining_seconds 秒... 正在快速检测网络"
echo "----------------------------------------"
echo -e "${YELLOW}距离关机还有 $remaining_seconds 秒...${NC}"
echo -e "${YELLOW}正在快速检测网络连接... 按 Ctrl+C 可取消关机${NC}"
# 在倒计时中再次检测网络
if test_network_connection "$TARGET_IP"; then
write_log "SUCCESS" "网络在倒计时期间恢复!取消关机"
echo "========================================"
echo -e "${GREEN}[OK] 网络连接已恢复!取消关机操作${NC}"
echo "========================================"
failure_start_time=""
shutdown_cancelled=true
break
else
echo -e "${RED}[X] 网络仍然中断${NC}"
fi
# 等待下一次检测
sleep "$COUNTDOWN_PING_INTERVAL"
done
if [[ "$shutdown_cancelled" == "false" ]]; then
write_log "CRITICAL" "关机倒计时完成,执行系统关机命令"
# 执行关机命令
# 注意:为了安全起见,默认注释掉实际的关机命令
# 如需启用,请取消下面一行的注释
# shutdown -h now
echo -e "${RED}模拟关机shutdown -h now (为防止意外,此行已注释)${NC}"
write_log "CRITICAL" "关机命令已执行(模拟模式)"
# 脚本执行完关机命令后可以退出
exit 0
fi
else
# 在监控窗口期内,继续等待
write_log "INFO" "等待 ${NORMAL_PING_INTERVAL}秒后继续..."
sleep "$NORMAL_PING_INTERVAL"
fi
fi
done

View File

@@ -1,91 +0,0 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
智能网络监控脚本 - 系统卸载脚本
.DESCRIPTION
完全卸载系统级部署的智能网络监控脚本
#>
$ProgramPath = "C:\Program Files\SmartNetworkMonitor"
$DataPath = "C:\ProgramData\SmartNetworkMonitor"
Write-Host "==========================================" -ForegroundColor Red
Write-Host "智能网络监控脚本 - 系统卸载" -ForegroundColor Red
Write-Host "==========================================" -ForegroundColor Red
Write-Host ""
Write-Host "将要删除:" -ForegroundColor Yellow
Write-Host "- 程序目录: $ProgramPath" -ForegroundColor White
Write-Host "- 数据目录: $DataPath (包含日志文件)" -ForegroundColor White
Write-Host "- 任务计划程序条目" -ForegroundColor White
Write-Host "- 事件日志源" -ForegroundColor White
Write-Host ""
$confirmation = Read-Host "确定要继续吗?这将删除所有相关文件和配置 (y/N)"
if ($confirmation -ne 'y' -and $confirmation -ne 'Y') {
Write-Host "卸载已取消" -ForegroundColor Green
exit 0
}
Write-Host ""
Write-Host "正在卸载..." -ForegroundColor Yellow
# 停止并删除任务计划程序
try {
$task = Get-ScheduledTask -TaskName "Smart Network Shutdown Monitor" -ErrorAction SilentlyContinue
if ($task) {
Stop-ScheduledTask -TaskName "Smart Network Shutdown Monitor" -ErrorAction SilentlyContinue
Unregister-ScheduledTask -TaskName "Smart Network Shutdown Monitor" -Confirm:$false
Write-Host "[OK] 任务计划程序已删除" -ForegroundColor Green
}
}
catch {
Write-Host "[X] 删除任务计划程序失败: $($_.Exception.Message)" -ForegroundColor Red
}
# 删除事件日志源
try {
if ([System.Diagnostics.EventLog]::SourceExists("SmartNetworkMonitor")) {
[System.Diagnostics.EventLog]::DeleteEventSource("SmartNetworkMonitor")
Write-Host "[OK] 事件日志源已删除" -ForegroundColor Green
}
}
catch {
Write-Host "[X] 删除事件日志源失败: $($_.Exception.Message)" -ForegroundColor Red
}
# 删除程序目录
try {
if (Test-Path $ProgramPath) {
Remove-Item -Path $ProgramPath -Recurse -Force
Write-Host "[OK] 程序目录已删除: $ProgramPath" -ForegroundColor Green
}
}
catch {
Write-Host "[X] 删除程序目录失败: $($_.Exception.Message)" -ForegroundColor Red
}
# 询问是否删除数据目录(包含日志)
Write-Host ""
$deleteData = Read-Host "是否同时删除数据目录和所有日志文件?(y/N)"
if ($deleteData -eq 'y' -or $deleteData -eq 'Y') {
try {
if (Test-Path $DataPath) {
Remove-Item -Path $DataPath -Recurse -Force
Write-Host "[OK] 数据目录已删除: $DataPath" -ForegroundColor Green
}
}
catch {
Write-Host "[X] 删除数据目录失败: $($_.Exception.Message)" -ForegroundColor Red
}
} else {
Write-Host "[INFO] 数据目录保留: $DataPath" -ForegroundColor Cyan
}
Write-Host ""
Write-Host "[OK] 卸载完成!" -ForegroundColor Green
Read-Host "按任意键退出"

View File

@@ -1,223 +0,0 @@
#!/bin/bash
#
# 智能网络监控脚本 - Ubuntu系统级卸载脚本
#
# 功能描述:
# 完全卸载系统级部署的智能网络监控脚本
# 包括停止服务、删除文件、清理配置等
#
# 使用方法:
# sudo ./uninstall_system.sh
#
# 设置颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# 定义系统级路径
PROGRAM_PATH="/opt/smart-network-monitor"
CONFIG_PATH="/etc/smart-network-monitor"
LOG_PATH="/var/log/smart-network-monitor"
SERVICE_FILE="/etc/systemd/system/smart-network-monitor.service"
SERVICE_NAME="smart-network-monitor"
SYMLINK_PATH="/usr/local/bin/smart-monitor"
echo -e "${CYAN}==========================================${NC}"
echo -e "${CYAN}智能网络监控脚本 - Ubuntu系统级卸载${NC}"
echo -e "${CYAN}==========================================${NC}"
echo ""
# 检查root权限
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}错误此脚本需要root权限执行${NC}"
echo -e "${YELLOW}请使用: sudo $0${NC}"
exit 1
fi
echo -e "${YELLOW}将要删除的组件:${NC}"
echo -e "- 程序目录: ${PROGRAM_PATH}"
echo -e "- 配置目录: ${CONFIG_PATH}"
echo -e "- 日志目录: ${LOG_PATH}"
echo -e "- 服务文件: ${SERVICE_FILE}"
echo -e "- 符号链接: ${SYMLINK_PATH}"
echo ""
# 询问用户确认
read -p "确定要完全卸载智能网络监控脚本吗?这将删除所有相关文件和日志 (y/N): " confirm
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
echo -e "${YELLOW}卸载已取消${NC}"
exit 0
fi
echo ""
echo -e "${YELLOW}开始卸载过程...${NC}"
# 停止并禁用服务
if systemctl is-active --quiet "$SERVICE_NAME"; then
echo -e "${YELLOW}正在停止服务...${NC}"
if systemctl stop "$SERVICE_NAME"; then
echo -e "${GREEN}[OK] 服务已停止${NC}"
else
echo -e "${RED}[WARNING] 停止服务失败${NC}"
fi
fi
if systemctl is-enabled --quiet "$SERVICE_NAME" 2>/dev/null; then
echo -e "${YELLOW}正在禁用服务自启动...${NC}"
if systemctl disable "$SERVICE_NAME"; then
echo -e "${GREEN}[OK] 服务自启动已禁用${NC}"
else
echo -e "${RED}[WARNING] 禁用服务自启动失败${NC}"
fi
fi
# 删除服务文件
if [[ -f "$SERVICE_FILE" ]]; then
echo -e "${YELLOW}正在删除服务文件...${NC}"
if rm -f "$SERVICE_FILE"; then
echo -e "${GREEN}[OK] 服务文件已删除: $SERVICE_FILE${NC}"
else
echo -e "${RED}[ERROR] 删除服务文件失败${NC}"
fi
fi
# 重新加载systemd配置
echo -e "${YELLOW}正在重新加载systemd配置...${NC}"
if systemctl daemon-reload; then
echo -e "${GREEN}[OK] systemd配置已重新加载${NC}"
else
echo -e "${RED}[WARNING] 重新加载systemd配置失败${NC}"
fi
# 删除符号链接
if [[ -L "$SYMLINK_PATH" ]]; then
echo -e "${YELLOW}正在删除符号链接...${NC}"
if rm -f "$SYMLINK_PATH"; then
echo -e "${GREEN}[OK] 符号链接已删除: $SYMLINK_PATH${NC}"
else
echo -e "${RED}[ERROR] 删除符号链接失败${NC}"
fi
fi
# 删除程序目录
if [[ -d "$PROGRAM_PATH" ]]; then
echo -e "${YELLOW}正在删除程序目录...${NC}"
if rm -rf "$PROGRAM_PATH"; then
echo -e "${GREEN}[OK] 程序目录已删除: $PROGRAM_PATH${NC}"
else
echo -e "${RED}[ERROR] 删除程序目录失败${NC}"
fi
fi
# 询问是否删除配置文件
echo ""
read -p "是否删除配置文件?这将删除您的自定义配置 (y/N): " delete_config
if [[ "$delete_config" == "y" || "$delete_config" == "Y" ]]; then
if [[ -d "$CONFIG_PATH" ]]; then
echo -e "${YELLOW}正在删除配置目录...${NC}"
if rm -rf "$CONFIG_PATH"; then
echo -e "${GREEN}[OK] 配置目录已删除: $CONFIG_PATH${NC}"
else
echo -e "${RED}[ERROR] 删除配置目录失败${NC}"
fi
fi
else
echo -e "${BLUE}[INFO] 保留配置文件: $CONFIG_PATH${NC}"
fi
# 询问是否删除日志文件
echo ""
read -p "是否删除日志文件?这将删除所有监控历史记录 (y/N): " delete_logs
if [[ "$delete_logs" == "y" || "$delete_logs" == "Y" ]]; then
if [[ -d "$LOG_PATH" ]]; then
echo -e "${YELLOW}正在删除日志目录...${NC}"
if rm -rf "$LOG_PATH"; then
echo -e "${GREEN}[OK] 日志目录已删除: $LOG_PATH${NC}"
else
echo -e "${RED}[ERROR] 删除日志目录失败${NC}"
fi
fi
else
echo -e "${BLUE}[INFO] 保留日志文件: $LOG_PATH${NC}"
fi
# 清理系统日志中的相关条目(可选)
echo ""
read -p "是否清理系统日志中的相关条目?(y/N): " clean_syslogs
if [[ "$clean_syslogs" == "y" || "$clean_syslogs" == "Y" ]]; then
echo -e "${YELLOW}正在清理系统日志...${NC}"
if command -v journalctl >/dev/null 2>&1; then
# 注意journalctl --vacuum-time 需要适当的权限
journalctl --vacuum-time=1d 2>/dev/null || echo -e "${YELLOW}[WARNING] 清理系统日志需要额外权限${NC}"
echo -e "${GREEN}[OK] 系统日志清理完成${NC}"
else
echo -e "${YELLOW}[WARNING] journalctl命令不可用${NC}"
fi
fi
echo ""
echo -e "${GREEN}==========================================${NC}"
echo -e "${GREEN}卸载完成!${NC}"
echo -e "${GREEN}==========================================${NC}"
echo ""
# 显示卸载摘要
echo -e "${CYAN}卸载摘要:${NC}"
echo -e "${GREEN}[OK]${NC} 服务已停止并禁用"
echo -e "${GREEN}[OK]${NC} 服务文件已删除"
echo -e "${GREEN}[OK]${NC} 程序文件已删除"
echo -e "${GREEN}[OK]${NC} 符号链接已删除"
if [[ "$delete_config" == "y" || "$delete_config" == "Y" ]]; then
echo -e "${GREEN}[OK]${NC} 配置文件已删除"
else
echo -e "${BLUE}[INFO]${NC} 配置文件已保留"
fi
if [[ "$delete_logs" == "y" || "$delete_logs" == "Y" ]]; then
echo -e "${GREEN}[OK]${NC} 日志文件已删除"
else
echo -e "${BLUE}[INFO]${NC} 日志文件已保留"
fi
echo ""
# 检查是否有残留文件
echo -e "${YELLOW}检查残留文件...${NC}"
remaining_files=()
[[ -f "$SERVICE_FILE" ]] && remaining_files+=("$SERVICE_FILE")
[[ -d "$PROGRAM_PATH" ]] && remaining_files+=("$PROGRAM_PATH")
[[ -L "$SYMLINK_PATH" ]] && remaining_files+=("$SYMLINK_PATH")
if [[ ${#remaining_files[@]} -eq 0 ]]; then
echo -e "${GREEN}[OK] 没有发现残留文件${NC}"
else
echo -e "${YELLOW}[WARNING] 发现以下残留文件:${NC}"
for file in "${remaining_files[@]}"; do
echo -e "${RED} - $file${NC}"
done
echo -e "${YELLOW}您可能需要手动删除这些文件${NC}"
fi
echo ""
echo -e "${CYAN}智能网络监控脚本已完全卸载!${NC}"
# 如果保留了配置或日志文件,提供再次卸载的说明
if [[ "$delete_config" != "y" && "$delete_config" != "Y" ]] || [[ "$delete_logs" != "y" && "$delete_logs" != "Y" ]]; then
echo ""
echo -e "${BLUE}如果您之后想删除保留的文件,可以手动删除:${NC}"
[[ "$delete_config" != "y" && "$delete_config" != "Y" ]] && echo -e "${BLUE} 配置文件: sudo rm -rf $CONFIG_PATH${NC}"
[[ "$delete_logs" != "y" && "$delete_logs" != "Y" ]] && echo -e "${BLUE} 日志文件: sudo rm -rf $LOG_PATH${NC}"
fi
echo ""