package cmd import ( "encoding/json" "fmt" "os" "os/user" "path/filepath" "runtime" "strings" "time" "github.com/evil7/hostsync/core" "github.com/evil7/hostsync/service" "github.com/evil7/hostsync/utils" "github.com/spf13/cobra" ) var ( initForce bool ) // initCmd 初始化命令 var initCmd = &cobra.Command{ Use: "init", Short: "初始化系统配置", Long: `初始化HostSync系统配置,包括: - 在用户目录下创建 .hostsync 文件夹 - 安装程序到用户目录(如果不在用户目录运行则复制) - 设置环境变量和别名(方便直接调用) - 解析现有hosts文件(现有内容归入default块) - 创建配置文件和服务器配置 - 注册系统服务(Windows服务/Linux systemd) 示例: hostsync init # 初始化系统 hostsync init --force # 强制重新初始化`, Run: runInit, } func init() { initCmd.Flags().BoolVar(&initForce, "force", false, "强制重新初始化") } func runInit(cmd *cobra.Command, args []string) { utils.CheckAdmin() utils.LogInfo("开始初始化HostSync (force=%t)", initForce) // 1. 获取用户目录和程序安装路径 userDir, installDir, err := getInstallPaths() if err != nil { utils.LogError("获取安装路径失败: %v", err) utils.LogError("获取安装路径失败: %v", err) os.Exit(1) } utils.LogInfo("用户配置目录: %s", userDir) utils.LogInfo("程序安装目录: %s", installDir) // 2. 创建用户配置目录 if err := createUserConfigDirs(userDir); err != nil { utils.LogError("创建用户配置目录失败: %v", err) os.Exit(1) } // 3. 安装程序文件到用户目录 if err := installProgram(installDir); err != nil { utils.LogError("安装程序失败: %v", err) os.Exit(1) } // 4. 设置环境变量和别名 if err := setupEnvironment(installDir); err != nil { utils.LogError("设置环境失败: %v", err) utils.LogInfo("💡 提示: 你可能需要手动将程序目录添加到 PATH 环境变量") } // 5. 创建配置文件 if err := createConfigFiles(userDir); err != nil { utils.LogError("创建配置文件失败: %v", err) os.Exit(1) } // 6. 解析现有hosts文件 if err := parseExistingHosts(); err != nil { utils.LogError("解析hosts文件失败: %v", err) os.Exit(1) } // 7. 注册系统服务 if err := registerSystemService(installDir); err != nil { if initForce { utils.LogError("注册系统服务失败: %v", err) utils.LogInfo("💡 提示: 可以稍后手动使用 'hostsync service install' 安装服务") } else { utils.LogError("注册系统服务失败: %v", err) utils.LogInfo("💡 提示: 使用 'hostsync init --force' 强制重新初始化服务") utils.LogInfo("💡 或者稍后手动使用 'hostsync service install' 安装服务") } } else { utils.LogSuccess("系统服务注册成功") } utils.LogSuccess("HostSync 初始化完成!") utils.LogInfo("接下来你可以:") utils.LogInfo(" - 使用 'hostsync list' 查看现有配置") utils.LogInfo(" - 使用 'hostsync add ' 添加新域名") utils.LogInfo(" - 使用 'hostsync service status' 查看服务状态") if runtime.GOOS == "windows" { utils.LogWarning("重启命令行窗口以生效环境变量设置") } } func createConfigFiles(userDir string) error { utils.LogInfo("创建配置文件...") configDir := filepath.Join(userDir, "config") // 创建主配置文件 - 使用JSON编码避免转义问题 config := map[string]interface{}{ "hostsPath": utils.GetHostsPath(), "backupCount": 5, "dnsTimeout": 5000, "maxConcurrent": 10, "logLevel": "info", "LogPath": filepath.Join(userDir, "logs"), } configBytes, err := json.MarshalIndent(config, "", " ") if err != nil { return fmt.Errorf("生成配置文件内容失败: %v", err) } configPath := filepath.Join(configDir, "config.json") if !utils.FileExists(configPath) || initForce { if err := utils.WriteFile(configPath, configBytes); err != nil { return fmt.Errorf("创建配置文件失败: %v", err) } } // 创建DNS服务器配置 serversPath := filepath.Join(configDir, "servers.json") if !utils.FileExists(serversPath) || initForce { // 创建默认DNS服务器配置 serversContent := `[ { "Name": "Cloudflare", "Dns": "1.1.1.1", "Doh": "https://1.1.1.1/dns-query" }, { "Name": "Google", "Dns": "8.8.8.8", "Doh": "https://8.8.4.4/dns-query" }, { "Name": "OneDNS", "Dns": "117.50.10.10", "Doh": "https://doh.onedns.net/dns-query" }, { "Name": "AdGuard", "Dns": "94.140.14.14", "Doh": "https://94.140.14.14/dns-query" }, { "Name": "Yandex", "Dns": "77.88.8.8", "Doh": "https://77.88.8.8/dns-query" }, { "Name": "DNSPod", "Dns": "119.29.29.29", "Doh": "https://doh.pub/dns-query" }, { "Name": "dns.sb", "Dns": "185.222.222.222", "Doh": "https://185.222.222.222/dns-query" }, { "Name": "Quad101", "Dns": "101.101.101.101", "Doh": "https://101.101.101.101/dns-query" }, { "Name": "Quad9", "Dns": "9.9.9.9", "Doh": "https://9.9.9.9/dns-query" }, { "Name": "OpenDNS", "Dns": "208.67.222.222", "Doh": "https://208.67.222.222/dns-query" }, { "Name": "AliDNS", "Dns": "223.5.5.5", "Doh": "https://223.5.5.5/dns-query" }, { "Name": "Applied", "Dns": "", "Doh": "https://doh.applied-privacy.net/query" }, { "Name": "cira.ca", "Dns": "", "Doh": "https://private.canadianshield.cira.ca/dns-query" }, { "Name": "ControlD", "Dns": "76.76.2.0", "Doh": "https://dns.controld.com/p0" }, { "Name": "switch.ch", "Dns": "", "Doh": "https://dns.switch.ch/dns-query" }, { "Name": "Dnswarden", "Dns": "", "Doh": "https://dns.dnswarden.com/uncensored" }, { "Name": "OSZX", "Dns": "217.160.156.119", "Doh": "https://dns.oszx.co/dns-query" }, { "Name": "Tiarap", "Dns": "174.138.21.128", "Doh": "https://doh.tiar.app/dns-query" } ] ` if err := utils.WriteFile(serversPath, []byte(serversContent)); err != nil { return fmt.Errorf("创建DNS服务器配置失败: %v", err) } } utils.LogSuccess("配置文件创建完成: %s", configDir) return nil } func parseExistingHosts() error { utils.LogInfo("解析现有hosts文件...") hm := core.NewHostsManager() if err := hm.Load(); err != nil { return err } // 如果没有找到任何块,说明这是一个全新的hosts文件 if len(hm.Blocks) == 0 { utils.LogInfo("💡 检测到全新的hosts文件,已准备就绪") return nil } utils.LogSuccess("hosts文件解析完成,发现 %d 个配置块", len(hm.Blocks)) return nil } func registerSystemService(installDir string) error { utils.LogInfo("注册系统服务...") // 获取目标执行文件路径 var execPath string if runtime.GOOS == "windows" { execPath = filepath.Join(installDir, "hostsync.exe") } else { execPath = filepath.Join(installDir, "hostsync") } // 确保文件存在 if !utils.FileExists(execPath) { return fmt.Errorf("执行文件不存在: %s", execPath) } serviceManager := service.NewServiceManager() // 如果是强制初始化,确保完全停止和卸载现有服务 if initForce { utils.LogInfo("强制模式:确保服务完全清理...") // 先检查服务状态(可能在 installProgram 中已经卸载了) status, err := serviceManager.Status() if err == nil && status != service.StatusNotInstalled { // 服务仍然存在,需要停止和卸载 utils.LogInfo("停止现有服务...") if err := serviceManager.Stop(); err != nil { utils.LogWarning("停止服务时遇到问题: %v", err) // 继续尝试卸载,可能服务已经停止 } else { utils.LogSuccess("服务已停止") } // 等待服务完全停止 utils.LogInfo("等待服务完全停止...") time.Sleep(2 * time.Second) // 卸载服务 utils.LogInfo("卸载现有服务...") if err := serviceManager.Uninstall(); err != nil { return fmt.Errorf("卸载服务失败: %v", err) } utils.LogSuccess("服务已卸载") // 再次等待,确保系统完全释放相关资源 utils.LogInfo("等待系统释放资源...") time.Sleep(3 * time.Second) } else { utils.LogInfo("服务已清理或未安装") } } // 安装服务 utils.LogInfo("安装系统服务...") if err := serviceManager.Install(execPath); err != nil { // 如果不是强制模式且服务已存在,这是正常情况 if !initForce && strings.Contains(err.Error(), "已存在") { utils.LogInfo("💡 服务已存在,跳过安装") // 即使服务已存在,也尝试启动它 if err := serviceManager.Start(); err != nil { utils.LogWarning("启动服务失败: %v", err) } else { utils.LogSuccess("服务已启动") } return nil } return fmt.Errorf("安装服务失败: %v", err) } // 安装成功后自动启动服务 utils.LogInfo("启动系统服务...") if err := serviceManager.Start(); err != nil { utils.LogWarning("启动服务失败: %v", err) utils.LogInfo("💡 提示: 可以稍后手动使用 'hostsync service start' 启动服务") } else { utils.LogSuccess("系统服务已启动") } return nil } // getInstallPaths 获取安装路径 func getInstallPaths() (userDir, installDir string, err error) { // 获取当前用户目录 currentUser, err := user.Current() if err != nil { return "", "", fmt.Errorf("获取当前用户信息失败: %v", err) } // 用户配置目录 userDir = filepath.Join(currentUser.HomeDir, ".hostsync") // 程序安装目录(在用户目录下) installDir = filepath.Join(userDir, "bin") return userDir, installDir, nil } // createUserConfigDirs 创建用户配置目录 func createUserConfigDirs(userDir string) error { utils.LogInfo("创建用户配置目录...") dirs := []string{ userDir, filepath.Join(userDir, "bin"), filepath.Join(userDir, "config"), filepath.Join(userDir, "logs"), filepath.Join(userDir, "backup"), } for _, dir := range dirs { if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("创建目录 %s 失败: %v", dir, err) } } utils.LogSuccess("用户配置目录创建完成: %s", userDir) return nil } // installProgram 安装程序到用户目录 func installProgram(installDir string) error { utils.LogInfo("安装程序文件...") // 获取当前执行文件路径 currentExecPath, err := os.Executable() if err != nil { return fmt.Errorf("获取当前执行文件路径失败: %v", err) } currentExecPath, err = filepath.Abs(currentExecPath) if err != nil { return fmt.Errorf("获取绝对路径失败: %v", err) } // 目标执行文件路径 var targetExecPath string if runtime.GOOS == "windows" { targetExecPath = filepath.Join(installDir, "hostsync.exe") } else { targetExecPath = filepath.Join(installDir, "hostsync") } // 检查是否已经在目标位置运行 if currentExecPath == targetExecPath { utils.LogSuccess("程序已在目标位置运行") return nil } serviceManager := service.NewServiceManager() // 在强制模式下,确保服务完全停止和卸载后再进行文件操作 if initForce { utils.LogInfo("强制模式:确保服务完全停止...") // 检查服务状态 status, err := serviceManager.Status() if err == nil && status != service.StatusNotInstalled { // 停止服务 utils.LogInfo("停止现有服务...") if err := serviceManager.Stop(); err != nil { utils.LogWarning("停止服务时遇到问题: %v", err) } else { utils.LogSuccess("服务已停止") } // 等待服务完全停止 utils.LogInfo("等待服务完全停止...") time.Sleep(2 * time.Second) // 卸载服务以释放文件句柄 utils.LogInfo("临时卸载服务以释放文件...") if err := serviceManager.Uninstall(); err != nil { utils.LogWarning("卸载服务时遇到问题: %v", err) } else { utils.LogSuccess("服务已临时卸载") } // 再次等待,确保系统完全释放文件句柄 utils.LogInfo("等待系统释放文件句柄...") time.Sleep(3 * time.Second) } } else { // 非强制模式下,只是尝试停止服务 utils.LogInfo("检查并停止现有服务...") if err := serviceManager.Stop(); err == nil { utils.LogSuccess("已停止现有服务") // 给一点时间让服务完全停止 time.Sleep(1 * time.Second) } } // 复制程序文件到目标位置 if err := copyFile(currentExecPath, targetExecPath); err != nil { return fmt.Errorf("复制程序文件失败: %v", err) } // 设置执行权限 if runtime.GOOS != "windows" { if err := os.Chmod(targetExecPath, 0755); err != nil { return fmt.Errorf("设置执行权限失败: %v", err) } } utils.LogSuccess("程序已安装到: %s", targetExecPath) return nil } // setupEnvironment 设置环境变量 func setupEnvironment(installDir string) error { utils.LogInfo("检查并设置环境变量...") // 首先检查当前环境是否已包含安装路径 if isPathInEnvironment(installDir) { utils.LogSuccess("PATH环境变量已包含程序目录: %s", installDir) return nil } utils.LogInfo("💡 需要将程序目录添加到 PATH 环境变量: %s", installDir) if runtime.GOOS == "windows" { return setupWindowsEnvironment(installDir) } else { return setupUnixEnvironment(installDir) } } // isPathInEnvironment 检查指定路径是否已在PATH环境变量中 func isPathInEnvironment(checkPath string) bool { pathEnv := os.Getenv("PATH") if pathEnv == "" { return false } var separator string if runtime.GOOS == "windows" { separator = ";" } else { separator = ":" } paths := strings.Split(pathEnv, separator) for _, path := range paths { // 规范化路径进行比较 if normalizePath(path) == normalizePath(checkPath) { return true } } return false } // normalizePath 规范化路径用于比较 func normalizePath(path string) string { absPath, err := filepath.Abs(strings.TrimSpace(path)) if err != nil { return strings.TrimSpace(path) } return absPath } // copyFile 复制文件 func copyFile(src, dst string) error { srcFile, err := os.Open(src) if err != nil { return err } defer srcFile.Close() dstFile, err := os.Create(dst) if err != nil { return err } defer dstFile.Close() _, err = dstFile.ReadFrom(srcFile) return err }