feat: add interactive config setup on first start, install scripts, and uninstall cleanup

This commit is contained in:
2026-03-24 22:46:45 +08:00
parent 35bf24f0f3
commit 850cc49daf
6 changed files with 257 additions and 13 deletions

View File

@@ -19,7 +19,7 @@ type Config struct {
func DefaultConfig() *Config {
return &Config{
TargetIP: "192.168.3.3",
TargetIP: "192.168.3.1",
MonitorWindowSeconds: 180,
ShutdownCountdown: 60,
NormalPingInterval: 15,

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

@@ -0,0 +1,124 @@
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
}
// ConfirmConfigCleanup prompts the user to confirm deletion of config and log files.
// Returns true if the user confirms. Non-terminal stdin defaults to no.
func ConfirmConfigCleanup() bool {
if !isTerminal() {
return false
}
configDir := GetConfigDir()
configPath := filepath.Join(configDir, "config.json")
if _, err := os.Stat(configPath); err != nil {
return false
}
fmt.Printf("检测到配置文件: %s\n是否一并清除配置文件与日志[y/N]: ", configDir)
scanner := bufio.NewScanner(os.Stdin)
if scanner.Scan() {
return strings.EqualFold(strings.TrimSpace(scanner.Text()), "y")
}
return false
}
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
}
}