From ae397fdbdf7348ea55bb141d783f7463ab56f760 Mon Sep 17 00:00:00 2001 From: Licoy Date: Fri, 17 Jun 2022 13:20:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0go=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E7=9A=84cli=EF=BC=8C=E6=94=AF=E6=8C=81server=E5=8F=8Aclient?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + .htaccess | 0 args.go | 38 +++++++ domains.json | 40 +++++++ fetch_hosts.go | 133 ++++++++++++++++++++++ fetch_hosts.php | 44 +------ fetch_hosts_test.go | 7 ++ go.mod | 9 ++ go.sum | 8 ++ index-template.php => index-template.html | 0 main.go | 64 +++++++++++ util.go | 67 +++++++++++ 12 files changed, 372 insertions(+), 40 deletions(-) delete mode 100644 .htaccess create mode 100644 args.go create mode 100644 domains.json create mode 100644 fetch_hosts.go create mode 100644 fetch_hosts_test.go create mode 100644 go.mod create mode 100644 go.sum rename index-template.php => index-template.html (100%) create mode 100644 main.go create mode 100644 util.go diff --git a/.gitignore b/.gitignore index 32bbaab..71fd0f4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ hosts.txt hosts.json index.php +index.html +output \ No newline at end of file diff --git a/.htaccess b/.htaccess deleted file mode 100644 index e69de29..0000000 diff --git a/args.go b/args.go new file mode 100644 index 0000000..1dd1404 --- /dev/null +++ b/args.go @@ -0,0 +1,38 @@ +package main + +import ( + "errors" + "fmt" + "github.com/jessevdk/go-flags" + "os" +) + +type CmdArgs struct { + Mode string `default:"client" short:"m" long:"mode" description:"启动模式(client或server)"` + FetchInterval int `default:"60" short:"i" long:"interval" description:"获取hosts的间隔时间,单位为分钟"` + Version bool `short:"v" long:"version" description:"查看当前版本"` +} + +func ParseBootArgs() *CmdArgs { + args := &CmdArgs{} + _, err := flags.ParseArgs(args, os.Args) + if err != nil { + et, y := err.(*flags.Error) + if y { + if errors.Is(flags.ErrHelp, et.Type) { + os.Exit(0) + } + } + panic(fmt.Sprintf("解析参数错误: %v", err)) + } + if args.Version { + fmt.Printf("版本号: V%.1f\n", VERSION) + } + if args.Mode != "client" && args.Mode != "server" { + panic(fmt.Sprintf("无效的启动模式: %s", args.Mode)) + } + if args.FetchInterval < 1 { + panic(fmt.Sprintf("获取hosts的间隔时间不可以小于1分钟,当前为%d分钟", args.FetchInterval)) + } + return args +} diff --git a/domains.json b/domains.json new file mode 100644 index 0000000..2b52b83 --- /dev/null +++ b/domains.json @@ -0,0 +1,40 @@ +[ + "alive.github.com", + "live.github.com", + "github.githubassets.com", + "central.github.com", + "desktop.githubusercontent.com", + "assets-cdn.github.com", + "camo.githubusercontent.com", + "github.map.fastly.net", + "github.global.ssl.fastly.net", + "gist.github.com", + "github.io", + "github.com", + "github.blog", + "api.github.com", + "raw.githubusercontent.com", + "user-images.githubusercontent.com", + "favicons.githubusercontent.com", + "avatars5.githubusercontent.com", + "avatars4.githubusercontent.com", + "avatars3.githubusercontent.com", + "avatars2.githubusercontent.com", + "avatars1.githubusercontent.com", + "avatars0.githubusercontent.com", + "avatars.githubusercontent.com", + "codeload.github.com", + "github-cloud.s3.amazonaws.com", + "github-com.s3.amazonaws.com", + "github-production-release-asset-2e65be.s3.amazonaws.com", + "github-production-user-asset-6210df.s3.amazonaws.com", + "github-production-repository-file-5c1aeb.s3.amazonaws.com", + "githubstatus.com", + "github.community", + "github.dev", + "collector.github.com", + "pipelines.actions.githubusercontent.com", + "media.githubusercontent.com", + "cloud.githubusercontent.com", + "objects.githubusercontent.com" +] \ No newline at end of file diff --git a/fetch_hosts.go b/fetch_hosts.go new file mode 100644 index 0000000..4190e6c --- /dev/null +++ b/fetch_hosts.go @@ -0,0 +1,133 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net" + "net/http" + "os" + "regexp" + "strings" + "time" +) + +const ( + Windows = "windows" + Linux = "linux" + Darwin = "darwin" + HostUrl = "https://hosts.gitcdn.top/hosts.txt" +) + +// ClientFetchHosts 获取最新的host并写入hosts文件 +func ClientFetchHosts() (err error) { + hostsPath := GetSystemHostsPath() + hostsBytes, err := ioutil.ReadFile(hostsPath) + if err != nil { + err = ComposeError("读取文件hosts错误", err) + return + } + + resp, err := http.Get(HostUrl) + if err != nil || resp.StatusCode != http.StatusOK { + err = ComposeError("获取最新的hosts失败", err) + return + } + + fetchHosts, err := ioutil.ReadAll(resp.Body) + if err != nil { + err = ComposeError("读取最新的hosts失败", err) + return + } + + hosts := string(hostsBytes) + + mth, err := regexp.Compile(`# fetch-github-hosts begin(([\s\S])*.?)# fetch-github-hosts end`) + if err != nil { + err = ComposeError("创建内容正则匹配失败", err) + return + } + findStr := mth.FindStringSubmatch(hosts) + if len(findStr) > 0 { + hosts = strings.Replace(hosts, findStr[0], string(fetchHosts), 1) + } else { + hosts += "\n\n" + string(fetchHosts) + } + + if err = ioutil.WriteFile(hostsPath, []byte(hosts), os.ModeType); err != nil { + err = ComposeError("写入hosts文件失败,请用超级管理员身份启动本程序!", err) + return + } + + return +} + +// ServerFetchHosts 服务端获取github最新的hosts并写入到对应文件及更新首页 +func ServerFetchHosts() (err error) { + execDir := AppExecDir() + fileData, err := ioutil.ReadFile(execDir + "/domains.json") + if err != nil { + err = ComposeError("读取文件domains.json错误", err) + return + } + + var domains []string + if err = json.Unmarshal(fileData, &domains); err != nil { + err = ComposeError("domain.json解析失败", err) + return + } + + hostJson, hostFile, now, err := FetchHosts(domains) + if err != nil { + err = ComposeError("获取Github的Host失败", err) + return + } + + if err = ioutil.WriteFile(execDir+"/hosts.json", hostJson, 0775); err != nil { + err = ComposeError("写入数据到hosts.json文件失败", err) + return + } + + if err = ioutil.WriteFile(execDir+"/hosts.txt", hostFile, 0775); err != nil { + err = ComposeError("写入数据到hosts.txt文件失败", err) + return + } + + var templateFile []byte + templateFile, err = ioutil.ReadFile(execDir + "/index-template.html") + if err != nil { + err = ComposeError("读取首页模板文件失败", err) + return + } + + templateData := strings.Replace(string(templateFile), "", now, 1) + if err = ioutil.WriteFile(execDir+"/index.html", []byte(templateData), 0775); err != nil { + err = ComposeError("写入更新信息到首页文件失败", err) + return + } + + return +} + +func FetchHosts(domains []string) (hostsJson, hostsFile []byte, now string, err error) { + now = time.Now().Format("2006-01-02 15:04:05") + hosts := make([][]string, 0, len(domains)) + hostsFileData := bytes.NewBufferString("# fetch-github-hosts begin\n") + for _, domain := range domains { + host, err := net.LookupHost(domain) + if err != nil { + fmt.Println("获取主机记录失败: ", err.Error()) + continue + } + item := []string{host[0], domain} + hosts = append(hosts, item) + hostsFileData.WriteString(fmt.Sprintf("%-28s%s\n", item[0], item[1])) + } + hostsFileData.WriteString("# last fetch time: ") + hostsFileData.WriteString(now) + hostsFileData.WriteString("\n# update url: https://hosts.gitcdn.top/hosts.txt\n# fetch-github-hosts end\n\n") + hostsFile = hostsFileData.Bytes() + hostsJson, err = json.Marshal(hosts) + return +} diff --git a/fetch_hosts.php b/fetch_hosts.php index 93948d9..ebaf214 100644 --- a/fetch_hosts.php +++ b/fetch_hosts.php @@ -1,44 +1,8 @@ ', $utc_date, $template)); +file_put_contents('index.html', str_replace('', $utc_date, $template)); file_put_contents('hosts.txt', $hosts_content); file_put_contents('hosts.json', json_encode($github_hosts)); diff --git a/fetch_hosts_test.go b/fetch_hosts_test.go new file mode 100644 index 0000000..012d9d0 --- /dev/null +++ b/fetch_hosts_test.go @@ -0,0 +1,7 @@ +package main + +import "testing" + +func TestClientFetchHosts(t *testing.T) { + ClientFetchHosts() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..159c4bc --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module github.com/Licoy/fetch-github-hosts + +go 1.18 + +require github.com/jessevdk/go-flags v1.5.0 + +require ( + golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fa42e0d --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw= +github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef h1:UD99BBEz19F21KhOFHLNAI6KodDWUvXaPr4Oqu8yMV8= +github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 h1:EZ2mChiOa8udjfp6rRmswTbtZN/QzUQp4ptM4rnjHvc= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/index-template.php b/index-template.html similarity index 100% rename from index-template.php rename to index-template.html diff --git a/main.go b/main.go new file mode 100644 index 0000000..c7f05b5 --- /dev/null +++ b/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "fmt" + "runtime" + "time" +) + +func main() { + permission, err := PreCheckHasHostsRWPermission() + if err != nil { + fmt.Println("检查hosts读写权限失败", err.Error()) + return + } + if !permission { + if runtime.GOOS == Windows { + fmt.Println("请鼠标右键选择【以管理员的身份运行】来执行本程序!") + } else { + fmt.Println("请以root账户或sudo来执行本程序!", err.Error()) + } + return + } + args := ParseBootArgs() + ticker := time.NewTicker(time.Minute * time.Duration(args.FetchInterval)) + logPrint(fmt.Sprintf("开始程序监听,当前以%d分钟更新一次Github-Hosts!", args.FetchInterval)) + logPrint("请不要关闭此窗口以保持再前台运行") + logPrint("可以将此程序注册为服务,具体请参考项目说明:https://github.com/Licoy/fetch-github-hosts") + if args.Mode == "server" { + startServer(ticker) + } else { + startClient(ticker) + } +} + +func startServer(ticker *time.Ticker) { + for { + select { + case <-ticker.C: + if err := ServerFetchHosts(); err != nil { + logPrint("执行更新Github-Hosts失败:" + err.Error()) + } else { + logPrint("执行更新Github-Hosts成功!") + } + } + } +} + +func startClient(ticker *time.Ticker) { + for { + select { + case <-ticker.C: + if err := ClientFetchHosts(); err != nil { + logPrint("更新Github-Hosts失败:" + err.Error()) + } else { + logPrint("更新Github-Hosts成功!") + } + } + } +} + +func logPrint(msg string) { + now := time.Now().Format("2006-01-02 15:04:05") + fmt.Printf("[%s] %s\n", now, msg) +} diff --git a/util.go b/util.go new file mode 100644 index 0000000..7fc4e83 --- /dev/null +++ b/util.go @@ -0,0 +1,67 @@ +package main + +import ( + "errors" + "os" + "runtime" + "syscall" +) + +const ( + VERSION = 1.0 +) + +var ( + _debug bool + _execDir string +) + +func init() { + _debug = os.Getenv("FETCH_GITHUB_HOST_DEBUG") != "" + initAppExecDir() +} + +func initAppExecDir() { + if _debug { + _execDir, _ = os.Getwd() + } else { + _execDir, _ = os.Executable() + } +} + +func IsDebug() bool { + return _debug +} + +func AppExecDir() string { + return _execDir +} + +func GetSystemHostsPath() string { + switch runtime.GOOS { + case Windows: + return "C:/Windows/System32/drivers/etc/hosts" + case Linux, Darwin: + return "/etc/hosts" + } + return "/etc/hosts" +} + +func PreCheckHasHostsRWPermission() (yes bool, err error) { + _, err = syscall.Open(GetSystemHostsPath(), syscall.O_RDWR, 0655) + if err != nil { + if errors.Is(err, syscall.ERROR_ACCESS_DENIED) { + err = nil + } + return + } + yes = true + return +} + +func ComposeError(msg string, err error) error { + if err == nil { + return errors.New(msg) + } + return errors.New(msg + ": " + err.Error()) +}