Compare commits
2 Commits
4e2aba5560
...
35bf24f0f3
| Author | SHA1 | Date | |
|---|---|---|---|
| 35bf24f0f3 | |||
| b1c7b9f8f7 |
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@@ -1,10 +1,9 @@
|
|||||||
name: Release
|
name: Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*' # 当推送标签形如 v1.0.0 时触发自动构建
|
- 'v*' # 褰撴帹閫佹爣绛惧舰濡?v1.0.0 鏃惰Е鍙戣嚜鍔ㄦ瀯寤?
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
@@ -31,16 +30,16 @@ jobs:
|
|||||||
mkdir -p build_output
|
mkdir -p build_output
|
||||||
|
|
||||||
echo "Building Windows amd64..."
|
echo "Building Windows amd64..."
|
||||||
GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o build_output/smart-shutdown_windows_amd64.exe ./cmd/smart-shutdown
|
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..."
|
echo "Building Windows arm64..."
|
||||||
GOOS=windows GOARCH=arm64 go build -ldflags="-s -w" -o build_output/smart-shutdown_windows_arm64.exe ./cmd/smart-shutdown
|
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..."
|
echo "Building Linux amd64..."
|
||||||
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o build_output/smart-shutdown_linux_amd64 ./cmd/smart-shutdown
|
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..."
|
echo "Building Linux arm64..."
|
||||||
GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o build_output/smart-shutdown_linux_arm64 ./cmd/smart-shutdown
|
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
|
- name: Publish GitHub Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
@@ -51,3 +50,4 @@ jobs:
|
|||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,22 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"smart-shutdown/pkg/config"
|
|
||||||
"smart-shutdown/pkg/daemon"
|
|
||||||
"smart-shutdown/pkg/logger"
|
|
||||||
|
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"smart-shutdown/pkg/config"
|
||||||
|
"smart-shutdown/pkg/daemon"
|
||||||
|
"smart-shutdown/pkg/logger"
|
||||||
|
"smart-shutdown/pkg/updater"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var AppVersion = "dev"
|
||||||
|
var verbose bool
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := logger.InitLogger(); err != nil {
|
if err := logger.InitLogger(); err != nil {
|
||||||
fmt.Printf("无法初始化日志系统: %v\n", err)
|
fmt.Printf("无法初始化日志系统: %v\n", err)
|
||||||
@@ -23,98 +28,204 @@ func main() {
|
|||||||
|
|
||||||
cfg, err := config.LoadConfig()
|
cfg, err := config.LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Crit("读取配置失败: %v", err)
|
logger.Crit("读取配置文件失败: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
svc, err := daemon.GetService(cfg)
|
svc, err := daemon.GetService(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Crit("构建服务对象失败: %v", err)
|
logger.Crit("构建后台服务实例失败: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var showVersion bool
|
||||||
|
|
||||||
var rootCommand = &cobra.Command{
|
var rootCommand = &cobra.Command{
|
||||||
Use: "smart-shutdown",
|
Use: "smart-shutdown",
|
||||||
Short: "智能网络状态检测与自动关机后台服务",
|
Short: "智能网络状态检测与自动关机后台服务",
|
||||||
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
if verbose {
|
||||||
|
logger.EnableDebug()
|
||||||
|
}
|
||||||
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
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
|
||||||
|
}
|
||||||
|
|
||||||
err := svc.Run()
|
err := svc.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fail("运行异常: %v", err)
|
logger.Fail("后台监控流崩溃: %v", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 禁用生成补全帮助
|
|
||||||
rootCommand.CompletionOptions.DisableDefaultCmd = true
|
rootCommand.CompletionOptions.DisableDefaultCmd = true
|
||||||
|
rootCommand.Flags().BoolVarP(&showVersion, "version", "V", false, "输出当前二进制内核版本并联网拉取发布树状态")
|
||||||
|
rootCommand.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "打印底层部署及环境追溯 Debug 信息")
|
||||||
|
|
||||||
cmds := []*cobra.Command{
|
cmds := []*cobra.Command{
|
||||||
{
|
{
|
||||||
Use: "install",
|
Use: "install",
|
||||||
Short: "安装系统常驻服务",
|
Short: "将执行文件拷贝至系统目录并全局注入环境变量体系",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
handleServiceControl(svc, "install")
|
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",
|
Use: "uninstall",
|
||||||
Short: "卸载系统常驻服务",
|
Short: "废除并移除在册的后台服务、扫除环境关联残留",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
service.Control(svc, "stop")
|
service.Control(svc, "stop")
|
||||||
handleServiceControl(svc, "uninstall")
|
handleServiceControl(svc, "uninstall")
|
||||||
|
|
||||||
|
targetDir, targetExe := getTargetSystemPath()
|
||||||
|
currentExe, _ := os.Executable()
|
||||||
|
|
||||||
|
if !strings.EqualFold(filepath.Clean(currentExe), filepath.Clean(targetExe)) {
|
||||||
|
if _, err := os.Stat(targetExe); err == nil {
|
||||||
|
logger.Debug("查明环境曾记录过全局部署逻辑,现已启动强力移除可执行源地址流: %s", targetExe)
|
||||||
|
os.Remove(targetExe)
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
os.Remove(targetDir)
|
||||||
|
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 中的指向挂载项业已剥除卸载完成。")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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",
|
Use: "start",
|
||||||
Short: "启动后台运行服务",
|
Short: "唤起执行守护后台",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
handleServiceControl(svc, "start")
|
handleServiceControl(svc, "start")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Use: "stop",
|
Use: "stop",
|
||||||
Short: "停止后台运行服务",
|
Short: "停机系统守护状态流",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
handleServiceControl(svc, "stop")
|
handleServiceControl(svc, "stop")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Use: "restart",
|
Use: "restart",
|
||||||
Short: "重启服务",
|
Short: "阻断并复用重新起跳服务控制主程",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
handleServiceControl(svc, "restart")
|
handleServiceControl(svc, "restart")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Use: "status",
|
Use: "status",
|
||||||
Short: "查询核心配置、服务运行状态及近期日志",
|
Short: "展示全向服务参数结构、后端存活性报告与探针汇集",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
status, err := svc.Status()
|
status, err := svc.Status()
|
||||||
|
|
||||||
fmt.Println("\n========== 运行状态 ==========")
|
fmt.Println("\n========== 运行状态 ==========")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("服务状态查询失败 (需系统管理员权限): %v\n", err)
|
fmt.Printf("特权访问受抵 (查验核算需要系统特权根源准允): %v\n", err)
|
||||||
} else {
|
} else {
|
||||||
switch status {
|
switch status {
|
||||||
case service.StatusRunning:
|
case service.StatusRunning:
|
||||||
fmt.Println("系统服务: [运行中]")
|
fmt.Println("全向系统基石服务: [运转值守中]")
|
||||||
case service.StatusStopped:
|
case service.StatusStopped:
|
||||||
fmt.Println("系统服务: [已停止]")
|
fmt.Println("全向系统基石服务: [已被静默挂起]")
|
||||||
default:
|
default:
|
||||||
fmt.Println("系统服务: [未注册或状态未知]")
|
fmt.Println("全向系统基石服务: [尚未注册落位 / 孤儿状态]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("\n========== 核心配置 ==========")
|
fmt.Println("\n========== 核心配置 ==========")
|
||||||
fmt.Printf("探测目标 IP : %s\n", cfg.TargetIP)
|
fmt.Printf("探测标的机器 IP : %s\n", cfg.TargetIP)
|
||||||
fmt.Printf("容忍断连窗口 (秒) : %d\n", cfg.MonitorWindowSeconds)
|
fmt.Printf("脱网迟滞容忍 (秒) : %d\n", cfg.MonitorWindowSeconds)
|
||||||
fmt.Printf("预警关机倒计 (秒) : %d\n", cfg.ShutdownCountdown)
|
fmt.Printf("临危核准倒计 (秒) : %d\n", cfg.ShutdownCountdown)
|
||||||
fmt.Printf("平稳探测频次 (秒) : %d\n", cfg.NormalPingInterval)
|
fmt.Printf("静默发包跳频 (秒) : %d\n", cfg.NormalPingInterval)
|
||||||
|
|
||||||
logFilePath := filepath.Join(config.GetLogDir(), "network_monitor.log")
|
logFilePath := filepath.Join(config.GetLogDir(), "network_monitor.log")
|
||||||
if fi, fileErr := os.Stat(logFilePath); fileErr == nil {
|
if fi, fileErr := os.Stat(logFilePath); fileErr == nil {
|
||||||
fmt.Println("\n========== 日志系统 ==========")
|
fmt.Println("\n========== 日志系统 ==========")
|
||||||
fmt.Printf("日志位置: %s\n", logFilePath)
|
fmt.Printf("硬盘归档落点: %s\n", logFilePath)
|
||||||
fmt.Printf("当前占用: %.2f KB\n", float64(fi.Size())/1024)
|
fmt.Printf("现时容量尺寸: %.2f KB\n", float64(fi.Size())/1024)
|
||||||
}
|
}
|
||||||
|
|
||||||
printLastLogLines(10)
|
printLastLogLines(10)
|
||||||
@@ -124,21 +235,21 @@ func main() {
|
|||||||
|
|
||||||
var configCmd = &cobra.Command{
|
var configCmd = &cobra.Command{
|
||||||
Use: "config",
|
Use: "config",
|
||||||
Short: "管理运行配置文件",
|
Short: "提供运行参数结构的修载校验准入系统",
|
||||||
}
|
}
|
||||||
|
|
||||||
var configSetCmd = &cobra.Command{
|
var configSetCmd = &cobra.Command{
|
||||||
Use: "set [键] [值]",
|
Use: "set [键] [值]",
|
||||||
Short: "修改指定的配置参数 (例如 config set TargetIP 192.168.1.1)",
|
Short: "安全的对 JSON 属性执行改写封装验证 (如 config set TargetIP 192.168.3.1)",
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
key := args[0]
|
key := args[0]
|
||||||
val := args[1]
|
val := args[1]
|
||||||
err := config.UpdateConfig(key, val)
|
err := config.UpdateConfig(key, val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("修改配置失败: %v\n", err)
|
fmt.Printf("基于硬编码的防呆数据验证发回抵拦截口指令: %v\n", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("成功将配置 [%s] 更新为 [%s]。\n(提示: 请主动执行 smart-shutdown restart 使更改生效)\n", key, val)
|
fmt.Printf("系统确认承接了新的设参: [%s] 指标结构被赋予了 [%s] 新制规格 \n(附留: 更易此配置文件必须人为发起 'smart-shutdown restart' 才能覆盖常驻内存的解析图谱。)\n", key, val)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -150,11 +261,10 @@ func main() {
|
|||||||
rootCommand.AddCommand(c)
|
rootCommand.AddCommand(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化并汉化帮助菜单
|
|
||||||
rootCommand.InitDefaultHelpCmd()
|
rootCommand.InitDefaultHelpCmd()
|
||||||
for _, cmd := range rootCommand.Commands() {
|
for _, cmd := range rootCommand.Commands() {
|
||||||
if cmd.Name() == "help" {
|
if cmd.Name() == "help" {
|
||||||
cmd.Short = "获取任意指令的帮助文档"
|
cmd.Short = "按需展示其它操作指令及其详情"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,20 +278,20 @@ func handleServiceControl(s service.Service, action string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
errStr := err.Error()
|
errStr := err.Error()
|
||||||
if strings.Contains(errStr, "Access is denied") || strings.Contains(errStr, "permission denied") || strings.Contains(errStr, "拒绝访问") {
|
if strings.Contains(errStr, "Access is denied") || strings.Contains(errStr, "permission denied") || strings.Contains(errStr, "拒绝访问") {
|
||||||
logger.Fail("执行动作 [%s] 失败: 权限遭拒。请以 Administrator 或 Root 权限重新执行。", action)
|
logger.Fail("管控流程 [%s] 触发越级防卫!无核准身份特设记录。请开具含有全 Root 及高配权限窗格承接口径。", action)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Crit("执行动作 [%s] 失败: %v", action, err)
|
logger.Crit("后台执行流程组块阻断报错 [%s] : %v", action, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Succ("执行动作 [%s] 成功", action)
|
logger.Succ("指令动作 [%s] 解析下发执行完毕,无阻断警告。", action)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printLastLogLines(n int) {
|
func printLastLogLines(n int) {
|
||||||
logFilePath := filepath.Join(config.GetLogDir(), "network_monitor.log")
|
logFilePath := filepath.Join(config.GetLogDir(), "network_monitor.log")
|
||||||
file, err := os.Open(logFilePath)
|
file, err := os.Open(logFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("\n(暂无历史探测日志)\n")
|
fmt.Printf("\n(排障辅录: 获取探针断联记录池为空,全空栈态)\n")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
@@ -204,8 +314,36 @@ func printLastLogLines(n int) {
|
|||||||
start = 0
|
start = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n========== 最近 %d 条抓取日志 ==========\n", n)
|
fmt.Printf("\n========== 最新沿线抓取探测轨迹尾部排 %d 行 ==========\n", n)
|
||||||
for i := start; i < len(validLines); i++ {
|
for i := start; i < len(validLines); i++ {
|
||||||
fmt.Println(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
|
||||||
|
}
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -7,6 +7,7 @@ require (
|
|||||||
github.com/kardianos/service v1.2.4
|
github.com/kardianos/service v1.2.4
|
||||||
github.com/prometheus-community/pro-bing v0.8.0
|
github.com/prometheus-community/pro-bing v0.8.0
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
|
golang.org/x/mod v0.34.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -19,6 +19,8 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT
|
|||||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
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 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
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 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ package daemon
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/kardianos/service"
|
||||||
"smart-shutdown/pkg/config"
|
"smart-shutdown/pkg/config"
|
||||||
"smart-shutdown/pkg/logger"
|
"smart-shutdown/pkg/logger"
|
||||||
"smart-shutdown/pkg/monitor"
|
"smart-shutdown/pkg/monitor"
|
||||||
|
|
||||||
"github.com/kardianos/service"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type program struct {
|
type program struct {
|
||||||
@@ -41,13 +40,17 @@ func (p *program) Stop(s service.Service) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetService(cfg *config.Config) (service.Service, error) {
|
func GetService(cfg *config.Config, execPath ...string) (service.Service, error) {
|
||||||
svcConfig := &service.Config{
|
svcConfig := &service.Config{
|
||||||
Name: "SmartNetworkMonitor",
|
Name: "SmartNetworkMonitor",
|
||||||
DisplayName: "Smart Network Shutdown Monitor",
|
DisplayName: "Smart Network Shutdown Monitor",
|
||||||
Description: "A reliable daemon that periodically monitors network states and triggers node suspension logically.",
|
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{
|
prg := &program{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var fileLogger *lumberjack.Logger
|
var fileLogger *lumberjack.Logger
|
||||||
|
var debugEnabled bool
|
||||||
|
|
||||||
func InitLogger() error {
|
func InitLogger() error {
|
||||||
logDir := config.GetLogDir()
|
logDir := config.GetLogDir()
|
||||||
@@ -33,6 +34,10 @@ func InitLogger() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func EnableDebug() {
|
||||||
|
debugEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
func writeLog(level, plainPrefix, format string, v ...interface{}) {
|
func writeLog(level, plainPrefix, format string, v ...interface{}) {
|
||||||
msg := fmt.Sprintf(format, v...)
|
msg := fmt.Sprintf(format, v...)
|
||||||
timestamp := time.Now().Format("2006/01/02 15:04:05")
|
timestamp := time.Now().Format("2006/01/02 15:04:05")
|
||||||
@@ -63,11 +68,19 @@ func getPrefixColor(level string) func(a ...interface{}) string {
|
|||||||
return color.New(color.FgRed).SprintFunc()
|
return color.New(color.FgRed).SprintFunc()
|
||||||
case "CRITICAL":
|
case "CRITICAL":
|
||||||
return color.New(color.FgHiRed).SprintFunc()
|
return color.New(color.FgHiRed).SprintFunc()
|
||||||
|
case "DEBUG":
|
||||||
|
return color.New(color.FgCyan).SprintFunc()
|
||||||
default:
|
default:
|
||||||
return color.New(color.Reset).SprintFunc()
|
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{}) {
|
func Info(format string, v ...interface{}) {
|
||||||
writeLog("INFO", "[INFO]", format, v...)
|
writeLog("INFO", "[INFO]", format, v...)
|
||||||
}
|
}
|
||||||
|
|||||||
146
pkg/updater/updater.go
Normal file
146
pkg/updater/updater.go
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user