hostSync/cmd/init.go

533 lines
14 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 <block> <domain>' 添加新域名")
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
}