From eec98404fe442944b6b4f73553e2b309d44500d8 Mon Sep 17 00:00:00 2001 From: jeessy2 <6205259+jeessy2@users.noreply.github.com> Date: Thu, 11 Jan 2024 15:52:53 +0800 Subject: [PATCH] feat: support english (#967) * feat: init the i18n * fix: porkbun * refactor: http_util GetHTTPResponse * fix: save * fix: add more i18n * feat: i18n at writing.html * feat: README EN * fix: default api * fix: pic --- README.md | 22 +-- README_EN.md | 140 ++++++++++++++++ config/config.go | 55 ++++--- config/domains.go | 20 +-- config/webhook.go | 21 ++- ddns-web.png | Bin 60068 -> 55317 bytes dns/alidns.go | 16 +- dns/baidu.go | 16 +- dns/callback.go | 15 +- dns/cloudflare.go | 17 +- dns/dnspod.go | 16 +- dns/godaddy.go | 11 +- dns/google_domain.go | 17 +- dns/huawei.go | 18 +-- dns/internal/wait_net.go | 8 +- dns/namecheap.go | 21 +-- dns/namesilo.go | 17 +- dns/porkbun.go | 36 ++--- dns/tencent_cloud.go | 16 +- go.mod | 5 +- go.sum | 2 + main.go | 52 +++--- static/common.css | 1 - util/http_util.go | 17 +- util/messages.go | 128 +++++++++++++++ util/update/release.go | 3 +- web/basic_auth.go | 11 +- web/password.go | 86 ---------- web/save.go | 19 ++- web/webhookTest.go | 4 +- web/writing.html | 341 ++++++++++++++++++++++++--------------- 31 files changed, 703 insertions(+), 448 deletions(-) create mode 100644 README_EN.md create mode 100644 util/messages.go delete mode 100644 web/password.go diff --git a/README.md b/README.md index f8fa21f..5fd2618 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ [![GitHub release](https://img.shields.io/github/release/jeessy2/ddns-go.svg?logo=github&style=flat-square) ![GitHub release downloads](https://img.shields.io/github/downloads/jeessy2/ddns-go/total?logo=github)](https://github.com/jeessy2/ddns-go/releases/latest) [![Go version](https://img.shields.io/github/go-mod/go-version/jeessy2/ddns-go)](https://github.com/jeessy2/ddns-go/blob/master/go.mod) [![](https://goreportcard.com/badge/github.com/jeessy2/ddns-go/v5)](https://goreportcard.com/report/github.com/jeessy2/ddns-go/v5) [![](https://img.shields.io/docker/image-size/jeessy/ddns-go)](https://registry.hub.docker.com/r/jeessy/ddns-go) [![](https://img.shields.io/docker/pulls/jeessy/ddns-go)](https://registry.hub.docker.com/r/jeessy/ddns-go) +中文 | [English](https://github.com/jeessy2/ddns-go/blob/master/README_EN.md) + 自动获得你的公网 IPv4 或 IPv6 地址,并解析到对应的域名服务。 - [特性](#特性) @@ -16,7 +18,7 @@ ## 特性 - 支持Mac、Windows、Linux系统,支持ARM、x86架构 -- 支持的域名服务商 `Alidns(阿里云)` `Dnspod(腾讯云)` `Cloudflare` `华为云` `Callback` `百度云` `Porkbun` `GoDaddy` `Google Domain` +- 支持的域名服务商 `阿里云` `腾讯云` `Dnspod` `Cloudflare` `华为云` `Callback` `百度云` `Porkbun` `GoDaddy` `Google Domain` - 支持接口/网卡/[命令](https://github.com/jeessy2/ddns-go/wiki/通过命令获取IP参考)获取IP - 支持以服务的方式运行 - 默认间隔5分钟同步一次 @@ -35,20 +37,18 @@ ## 系统中使用 - 从 [Releases](https://github.com/jeessy2/ddns-go/releases) 下载并解压 ddns-go -- [可选] 使用 [Homebrew](https://brew.sh) 安装 [ddns-go](https://formulae.brew.sh/formula/ddns-go): - - ```bash - brew install ddns-go - ``` - -- 双击运行, 如没有找到配置, 程序将自动打开 http://127.0.0.1:9876 -- [可选] 安装服务 +- 安装服务 - Mac/Linux: `sudo ./ddns-go -s install` - Win(以管理员打开cmd): `.\ddns-go.exe -s install` - [可选] 服务卸载 - Mac/Linux: `sudo ./ddns-go -s uninstall` - Win(以管理员打开cmd): `.\ddns-go.exe -s uninstall` -- [可选] 支持安装或启动时带参数 `-l`监听地址 `-f`同步间隔时间(秒) `-cacheTimes`间隔N次与服务商比对 `-c`自定义配置文件路径 `-noweb`不启动web服务 `-skipVerify`跳过证书验证 `-dns` 自定义 DNS 服务器。如:`./ddns-go -s install -l :9877 -f 600 -c /Users/name/ddns-go.yaml` +- [可选] 支持安装带参数 `-l`监听地址 `-f`同步间隔时间(秒) `-cacheTimes`间隔N次与服务商比对 `-c`自定义配置文件路径 `-noweb`不启动web服务 `-skipVerify`跳过证书验证 `-dns` 自定义 DNS 服务器。如:`./ddns-go -s install -l :9876 -f 600 -c /Users/name/ddns-go.yaml` +- [可选] 使用 [Homebrew](https://brew.sh) 安装 [ddns-go](https://formulae.brew.sh/formula/ddns-go): + + ```bash + brew install ddns-go + ``` > [!NOTE] > 通过合理的配置 `-f` 和 `-cacheTimes` 可以实现 IP 变化即时触发更新且不会被 DDNS 服务商限流, 例如 `-f 10 -cacheTimes 360` 效果为每 10 秒检查一次本地 IP 变化, 每小时去公网对比一下 IP 变化 @@ -61,7 +61,7 @@ docker run -d --name ddns-go --restart=always --net=host -v /opt/ddns-go:/root jeessy/ddns-go ``` -- 在浏览器中打开`http://主机IP:9876`,修改你的配置,成功 +- 在浏览器中打开`http://主机IP:9876`,并修改你的配置 - [可选] 使用 `ghcr.io` 镜像 diff --git a/README_EN.md b/README_EN.md new file mode 100644 index 0000000..718b310 --- /dev/null +++ b/README_EN.md @@ -0,0 +1,140 @@ +# ddns-go + +[![GitHub release](https://img.shields.io/github/release/jeessy2/ddns-go.svg?logo=github&style=flat-square) ![GitHub release downloads](https://img.shields.io/github/downloads/jeessy2/ddns-go/total?logo=github)](https://github.com/jeessy2/ddns-go/releases/latest) [![Go version](https://img.shields.io/github/go-mod/go-version/jeessy2/ddns-go)](https://github.com/jeessy2/ddns-go/blob/master/go.mod) [![](https://goreportcard.com/badge/github.com/jeessy2/ddns-go/v5)](https://goreportcard.com/report/github.com/jeessy2/ddns-go/v5) [![](https://img.shields.io/docker/image-size/jeessy/ddns-go)](https://registry.hub.docker.com/r/jeessy/ddns-go) [![](https://img.shields.io/docker/pulls/jeessy/ddns-go)](https://registry.hub.docker.com/r/jeessy/ddns-go) + +[中文](https://github.com/jeessy2/ddns-go/blob/master/README.md) | English + +Automatically obtain your public IPv4 or IPv6 address and resolve it to the corresponding domain name service. + +- [Features](#Features) +- [Use in system](#Use-in-system) +- [Use in docker](#Use-in-docker) +- [Webhook](#webhook) +- [Callback](#callback) +- [Web interfaces](#Web-interfaces) + +## Features + +- Support Mac, Windows, Linux system, support ARM, x86 architecture +- Support domain service providers `Aliyun` `Tencent` `Dnspod` `Cloudflare` `Huawei` `Callback` `Baidu` `Porkbun` `GoDaddy` `Google Domain` `Namecheap` `NameSilo` +- Support interface / netcard / command to get IP +- Support running as a service +- Default interval is 5 minutes +- Support configuring multiple DNS service providers at the same time +- Support multiple domain name resolution at the same time +- Support multi-level domain name +- Configured on the web page, simple and convenient +- In the web page, you can quickly view the latest 50 logs +- Support Webhook notification +- Support TTL + +> [!NOTE] +> If you enable public network access, it is recommended to use Nginx and other reverse proxy software to enable HTTPS access to ensure security. + +## Use in system + +- Download and unzip ddns-go from [Releases](https://github.com/jeessy2/ddns-go/releases) +- Run in service mode + - Mac/Linux: `sudo ./ddns-go -s install` + - Win(Run as administrator): `.\ddns-go.exe -s install` +- [Optional] Uninstall service + - Mac/Linux: `sudo ./ddns-go -s uninstall` + - Win(Run as administrator): `.\ddns-go.exe -s uninstall` +- [Optional] Support installation with parameters `-l` listen address `-f` Sync frequency(seconds) `-cacheTimes` interval N times compared with service providers `-c` custom configuration file path `-noweb` does not start web service `-skipVerify` skip certificate verification `-dns` custom DNS server. example:`./ddns-go -s install -l :9876 -f 600 -c /Users/name/ddns-go.yaml` +- [Optional] You can use [Homebrew](https://brew.sh) to install [ddns-go](https://formulae.brew.sh/formula/ddns-go) + + ```bash + brew install ddns-go + ``` + +## Use in docker + +- Mount the host directory, use the docker host mode. You can replace `/opt/ddns-go` with any directory on your host, the configuration file is a hidden file + + ```bash + docker run -d --name ddns-go --restart=always --net=host -v /opt/ddns-go:/root jeessy/ddns-go + ``` + +- Open `http://DOCKER_IP:9876` in the browser, modify your configuration + +- [Optional] Use `ghcr.io` mirror + + ```bash + docker run -d --name ddns-go --restart=always --net=host -v /opt/ddns-go:/root ghcr.io/jeessy2/ddns-go + ``` + +- [Optional] Support startup with parameters `-l`listen address `-f`Sync frequency(seconds) + + ```bash + docker run -d --name ddns-go --restart=always --net=host -v /opt/ddns-go:/root jeessy/ddns-go -l :9877 -f 600 + ``` + +- [Optional] Without using docker host mode + + ```bash + docker run -d --name ddns-go --restart=always -p 9876:9876 -v /opt/ddns-go:/root jeessy/ddns-go + ``` + +## Webhook + +- Support webhook, when the domain name is updated successfully or not, the URL filled in will be called back +- Support variables + + | Variable name | Comments | + | ---- | ---- | + | #{ipv4Addr} | The new IPv4 | + | #{ipv4Result} | IPv4 update result: `no changed` `success` `failed`| + | #{ipv4Domains} | IPv4 domains,Split by `,` | + | #{ipv6Addr} | The new IPv6 | + | #{ipv6Result} | IPv6 update result: `no changed` `success` `failed`| + | #{ipv6Domains} | IPv6 domains,Split by `,` | + +- If RequestBody is empty, it is a `GET` request, otherwise it is a `POST` request + +-
Telegram + + [ddns-telegram-bot](https://github.com/WingLim/ddns-telegram-bot) +
+-
Discord + + - Discord client -> Server -> Channel Settings -> Integration -> View Webhook -> New Webhook -> Copy Webhook URL + - Input the `Webhook URL` copied from Discord in the URL + - Input in RequestBody + ```json + { + "content": "The domain name #{ipv4Domains} dynamically resolves to #{ipv4Result}.", + "embeds": [ + { + "description": "Domains: #{ipv4Domains}, Result: #{ipv4Result}, IP: #{ipv4Addr}", + "color": 15258703, + "author": { + "name": "DDNS" + }, + "footer": { + "text": "DDNS #{ipv4Result}" + } + } + ] + } + ``` +
+ +- [More webhook configuration reference](https://github.com/jeessy2/ddns-go/issues/327) + +## Callback + +- Support more third-party DNS service providers through custom callback +- Callback will be called as many times as there are lines in the configured domain name +- Support variables + + | Variable name | Comments | + | ---- | ---- | + | #{ip} | The new IPv4/IPv6 address| + | #{domain} | Current domain | + | #{recordType} | Record type `A` or `AAAA` | + | #{ttl} | TTL | +- If RequestBody is empty, it is a `GET` request, otherwise it is a `POST` request + +## Web interfaces + +![screenshots](https://raw.githubusercontent.com/jeessy2/ddns-go/master/ddns-web.png) diff --git a/config/config.go b/config/config.go index c37db0e..0ef1133 100755 --- a/config/config.go +++ b/config/config.go @@ -60,6 +60,8 @@ type Config struct { Webhook // 禁止公网访问 NotAllowWanAccess bool + // 语言 + Lang string } // ConfigCache ConfigCache @@ -92,14 +94,14 @@ func GetConfigCached() (conf Config, err error) { byt, err := os.ReadFile(configFilePath) if err != nil { - log.Println(configFilePath + " 读取失败") + util.Log("异常信息: %s", err) cache.Err = err return *cache.ConfigSingle, err } err = yaml.Unmarshal(byt, cache.ConfigSingle) if err != nil { - log.Println("反序列化配置文件失败", err) + util.Log("异常信息: %s", err) cache.Err = err return *cache.ConfigSingle, err } @@ -109,6 +111,9 @@ func GetConfigCached() (conf Config, err error) { cache.ConfigSingle.NotAllowWanAccess = true } + // 初始化语言 + util.InitLogLang(cache.ConfigSingle.Lang) + // remove err cache.Err = nil return *cache.ConfigSingle, err @@ -161,7 +166,7 @@ func (conf *Config) SaveConfig() (err error) { return } - log.Printf("配置文件已保存在: %s\n", configFilePath) + util.Log("配置文件已保存在: %s", configFilePath) // 清空配置缓存 cache.ConfigSingle = nil @@ -172,7 +177,7 @@ func (conf *Config) SaveConfig() (err error) { func (conf *DnsConfig) getIpv4AddrFromInterface() string { ipv4, _, err := GetNetInterface() if err != nil { - log.Println("从网卡获得IPv4失败!") + util.Log("从网卡获得IPv4失败") return "" } @@ -182,7 +187,7 @@ func (conf *DnsConfig) getIpv4AddrFromInterface() string { } } - log.Println("从网卡中获得IPv4失败! 网卡名: ", conf.Ipv4.NetInterface) + util.Log("从网卡中获得IPv4失败! 网卡名: %s", conf.Ipv4.NetInterface) return "" } @@ -193,20 +198,20 @@ func (conf *DnsConfig) getIpv4AddrFromUrl() string { url = strings.TrimSpace(url) resp, err := client.Get(url) if err != nil { - log.Printf("连接失败! 点击查看接口能否返回IPv4地址\n", url) - log.Printf("错误信息: %s\n", err) + util.Log("通过接口获取IPv4失败! 接口地址: %s", url) + util.Log("异常信息: %s", err) continue } defer resp.Body.Close() lr := io.LimitReader(resp.Body, 1024000) body, err := io.ReadAll(lr) if err != nil { - log.Println("读取IPv4结果失败! 接口: ", url) + util.Log("异常信息: %s", err) continue } result := Ipv4Reg.FindString(string(body)) if result == "" { - log.Printf("获取IPv4结果失败! 接口: %s ,返回值: %s\n", url, result) + util.Log("获取IPv4结果失败! 接口: %s ,返回值: %s", url, string(body)) } return result } @@ -243,14 +248,14 @@ func (conf *DnsConfig) getAddrFromCmd(addrType string) string { // run cmd out, err := execCmd.CombinedOutput() if err != nil { - log.Printf("获取%s结果失败! 未能成功执行命令:%s,错误:%q,退出状态码:%s\n", addrType, execCmd.String(), out, err) + util.Log("获取%s结果失败! 未能成功执行命令:%s, 错误:%q, 退出状态码:%s", addrType, execCmd.String(), out, err) return "" } str := string(out) // get result result := comp.FindString(str) if result == "" { - log.Printf("获取%s结果失败! 命令:%s,标准输出:%q\n", addrType, execCmd.String(), str) + util.Log("获取%s结果失败! 命令: %s, 标准输出: %q", addrType, execCmd.String(), str) } return result } @@ -269,7 +274,7 @@ func (conf *DnsConfig) GetIpv4Addr() string { // 从命令行获取 IP return conf.getAddrFromCmd("IPv4") default: - log.Println("IPv4 的 获取 IP 方式 未知!") + log.Println("IPv4's get IP method is unknown") return "" // unknown type } } @@ -277,7 +282,7 @@ func (conf *DnsConfig) GetIpv4Addr() string { func (conf *DnsConfig) getIpv6AddrFromInterface() string { _, ipv6, err := GetNetInterface() if err != nil { - log.Println("从网卡获得IPv6失败!") + util.Log("从网卡获得IPv6失败") return "" } @@ -289,34 +294,32 @@ func (conf *DnsConfig) getIpv6AddrFromInterface() string { num, err := strconv.Atoi(conf.Ipv6.IPv6Reg[1:]) if err == nil { if num > 0 { - log.Printf("IPv6将使用第 %d 个IPv6地址\n", num) if num <= len(netInterface.Address) { return netInterface.Address[num-1] } - log.Printf("未找到第 %d 个IPv6地址! 将使用第一个IPv6地址\n", num) + util.Log("未找到第 %d 个IPv6地址! 将使用第一个IPv6地址", num) return netInterface.Address[0] } - log.Printf("IPv6匹配表达式 %s 不正确! 最小从1开始\n", conf.Ipv6.IPv6Reg) + util.Log("IPv6匹配表达式 %s 不正确! 最小从1开始", conf.Ipv6.IPv6Reg) return "" } } // 正则表达式匹配 - log.Printf("IPv6将使用正则表达式 %s 进行匹配\n", conf.Ipv6.IPv6Reg) + util.Log("IPv6将使用正则表达式 %s 进行匹配", conf.Ipv6.IPv6Reg) for i := 0; i < len(netInterface.Address); i++ { matched, err := regexp.MatchString(conf.Ipv6.IPv6Reg, netInterface.Address[i]) if matched && err == nil { - log.Println("匹配成功! 匹配到地址: ", netInterface.Address[i]) + util.Log("匹配成功! 匹配到地址: ", netInterface.Address[i]) return netInterface.Address[i] } - log.Printf("第 %d 个地址 %s 不匹配, 将匹配下一个地址\n", i+1, netInterface.Address[i]) } - log.Println("没有匹配到任何一个IPv6地址, 将使用第一个地址") + util.Log("没有匹配到任何一个IPv6地址, 将使用第一个地址") } return netInterface.Address[0] } } - log.Println("从网卡中获得IPv6失败! 网卡名: ", conf.Ipv6.NetInterface) + util.Log("从网卡中获得IPv6失败! 网卡名: %s", conf.Ipv6.NetInterface) return "" } @@ -327,8 +330,8 @@ func (conf *DnsConfig) getIpv6AddrFromUrl() string { url = strings.TrimSpace(url) resp, err := client.Get(url) if err != nil { - log.Printf("连接失败! 点击查看接口能否返回IPv6地址, 参考说明:点击访问\n", url, "https://github.com/jeessy2/ddns-go#使用ipv6") - log.Printf("错误信息: %s\n", err) + util.Log("通过接口获取IPv6失败! 接口地址: %s", url) + util.Log("异常信息: %s", err) continue } @@ -336,12 +339,12 @@ func (conf *DnsConfig) getIpv6AddrFromUrl() string { lr := io.LimitReader(resp.Body, 1024000) body, err := io.ReadAll(lr) if err != nil { - log.Println("读取IPv6结果失败! 接口: ", url) + util.Log("异常信息: %s", err) continue } result := Ipv6Reg.FindString(string(body)) if result == "" { - log.Printf("获取IPv6结果失败! 接口: %s ,返回值: %s\n", url, result) + util.Log("获取IPv6结果失败! 接口: %s ,返回值: %s", url, result) } return result } @@ -362,7 +365,7 @@ func (conf *DnsConfig) GetIpv6Addr() (result string) { // 从命令行获取 IP return conf.getAddrFromCmd("IPv6") default: - log.Println("IPv6 的 获取 IP 方式 未知!") + log.Println("IPv6's get IP method is unknown") return "" // unknown type } } diff --git a/config/domains.go b/config/domains.go index f48a6bb..47cbd9a 100644 --- a/config/domains.go +++ b/config/domains.go @@ -1,7 +1,6 @@ package config import ( - "log" "net/url" "strings" @@ -81,7 +80,7 @@ func (domains *Domains) GetNewIp(dnsConf *DnsConfig) { if domains.Ipv4Cache.TimesFailedIP == 3 { domains.Ipv4Domains[0].UpdateStatus = UpdatedFailed } - log.Println("未能获取IPv4地址, 将不会更新") + util.Log("未能获取IPv4地址, 将不会更新") } } @@ -97,7 +96,7 @@ func (domains *Domains) GetNewIp(dnsConf *DnsConfig) { if domains.Ipv6Cache.TimesFailedIP == 3 { domains.Ipv6Domains[0].UpdateStatus = UpdatedFailed } - log.Println("未能获取IPv6地址, 将不会更新") + util.Log("未能获取IPv6地址, 将不会更新") } } @@ -124,7 +123,8 @@ func checkParseDomains(domainArr []string) (domains []*Domain) { case 1: // 不使用冒号分割,自动识别域名 domainName, err := publicsuffix.EffectiveTLDPlusOne(domainStr) if err != nil { - log.Println(domainStr, "域名不正确:", err) + util.Log("域名: %s 不正确", domainStr) + util.Log("异常信息: %s", err) continue } domain.DomainName = domainName @@ -136,21 +136,21 @@ func checkParseDomains(domainArr []string) (domains []*Domain) { case 2: // 使用冒号分隔,为 子域名:根域名 格式 sp := strings.Split(dp[1], ".") if len(sp) <= 1 { - log.Println(domainStr, "域名不正确") + util.Log("域名: %s 不正确", domainStr) continue } domain.DomainName = dp[1] domain.SubDomain = dp[0] default: - log.Println(domainStr, "域名不正确") + util.Log("域名: %s 不正确", domainStr) continue } // 参数条件 if len(qp) == 2 { - u, err := url.Parse("http://baidu.com?" + qp[1]) + u, err := url.Parse("https://baidu.com?" + qp[1]) if err != nil { - log.Println(domainStr, "域名解析失败") + util.Log("域名: %s 解析失败", domainStr) continue } domain.CustomParams = u.Query().Encode() @@ -166,7 +166,7 @@ func (domains *Domains) GetNewIpResult(recordType string) (ipAddr string, retDom if domains.Ipv6Cache.Check(domains.Ipv6Addr) { return domains.Ipv6Addr, domains.Ipv6Domains } else { - log.Printf("IPv6未改变,将等待 %d 次后与DNS服务商进行比对\n", domains.Ipv6Cache.Times) + util.Log("IPv6未改变, 将等待 %d 次后与DNS服务商进行比对", domains.Ipv6Cache.Times) return "", domains.Ipv6Domains } } @@ -174,7 +174,7 @@ func (domains *Domains) GetNewIpResult(recordType string) (ipAddr string, retDom if domains.Ipv4Cache.Check(domains.Ipv4Addr) { return domains.Ipv4Addr, domains.Ipv4Domains } else { - log.Printf("IPv4未改变,将等待 %d 次后与DNS服务商进行比对\n", domains.Ipv4Cache.Times) + util.Log("IPv4未改变, 将等待 %d 次后与DNS服务商进行比对", domains.Ipv4Cache.Times) return "", domains.Ipv4Domains } } diff --git a/config/webhook.go b/config/webhook.go index 5283038..1b7f1f6 100644 --- a/config/webhook.go +++ b/config/webhook.go @@ -3,7 +3,6 @@ package config import ( "encoding/json" "fmt" - "log" "net/http" "net/url" "strings" @@ -48,7 +47,7 @@ func ExecWebhook(domains *Domains, conf *Config) (v4Status updateStatusType, v6S if v4Status == UpdatedFailed || v6Status == UpdatedFailed { updatedFailedTimes++ if updatedFailedTimes != 3 { - log.Println("将不会触发Webhook,仅在第 3 次失败时触发一次Webhook,当前失败次数:", updatedFailedTimes) + util.Log("将不会触发Webhook, 仅在第 3 次失败时触发一次Webhook, 当前失败次数:%d", updatedFailedTimes) return } } else { @@ -66,18 +65,18 @@ func ExecWebhook(domains *Domains, conf *Config) (v4Status updateStatusType, v6S contentType = "application/json" } else if hasJSONPrefix(postPara) { // 如果 RequestBody 的 JSON 无效但前缀为 JSON,提示无效 - log.Println("RequestBody 的 JSON 无效!") + util.Log("Webhook中的 RequestBody JSON 无效") } } requestURL := replacePara(domains, conf.WebhookURL, v4Status, v6Status) u, err := url.Parse(requestURL) if err != nil { - log.Println("Webhook配置中的URL不正确") + util.Log("Webhook配置中的URL不正确") return } req, err := http.NewRequest(method, fmt.Sprintf("%s://%s%s?%s", u.Scheme, u.Host, u.Path, u.Query().Encode()), strings.NewReader(postPara)) if err != nil { - log.Println("创建Webhook请求异常, Err:", err) + util.Log("Webhook调用失败! 异常信息:%s", err) return } @@ -89,11 +88,11 @@ func ExecWebhook(domains *Domains, conf *Config) (v4Status updateStatusType, v6S clt := util.CreateHTTPClient() resp, err := clt.Do(req) - body, err := util.GetHTTPResponseOrg(resp, requestURL, err) + body, err := util.GetHTTPResponseOrg(resp, err) if err == nil { - log.Printf("Webhook调用成功, 返回数据: %q\n", string(body)) + util.Log("Webhook调用成功! 返回数据:%s", string(body)) } else { - log.Printf("Webhook调用失败,Err:%s\n", err) + util.Log("Webhook调用失败! 异常信息:%s", err) } } return @@ -122,11 +121,11 @@ func getDomainsStatus(domains []*Domain) updateStatusType { // replacePara 替换参数 func replacePara(domains *Domains, orgPara string, ipv4Result updateStatusType, ipv6Result updateStatusType) (newPara string) { orgPara = strings.ReplaceAll(orgPara, "#{ipv4Addr}", domains.Ipv4Addr) - orgPara = strings.ReplaceAll(orgPara, "#{ipv4Result}", string(ipv4Result)) + orgPara = strings.ReplaceAll(orgPara, "#{ipv4Result}", util.LogStr(string(ipv4Result))) // i18n orgPara = strings.ReplaceAll(orgPara, "#{ipv4Domains}", getDomainsStr(domains.Ipv4Domains)) orgPara = strings.ReplaceAll(orgPara, "#{ipv6Addr}", domains.Ipv6Addr) - orgPara = strings.ReplaceAll(orgPara, "#{ipv6Result}", string(ipv6Result)) + orgPara = strings.ReplaceAll(orgPara, "#{ipv6Result}", util.LogStr(string(ipv6Result))) // i18n orgPara = strings.ReplaceAll(orgPara, "#{ipv6Domains}", getDomainsStr(domains.Ipv6Domains)) return orgPara @@ -153,7 +152,7 @@ func checkParseHeaders(headerStr string) (headers map[string]string) { if headerStr != "" { parts := strings.Split(headerStr, ":") if len(parts) != 2 { - log.Println(headerStr, "Header不正确") + util.Log("Webhook Header不正确: %s", headerStr) continue } headers[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) diff --git a/ddns-web.png b/ddns-web.png index 86ee5af92445d0b1f83155c3dcaaeb6f686e997f..8446b9e320afe185c93e8fd4d62214b89dd1249e 100644 GIT binary patch literal 55317 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�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 diff --git a/dns/alidns.go b/dns/alidns.go index 9e2ebe1..4c5e4e7 100644 --- a/dns/alidns.go +++ b/dns/alidns.go @@ -2,7 +2,6 @@ package dns import ( "bytes" - "log" "net/http" "net/url" @@ -82,6 +81,7 @@ func (ali *Alidns) addUpdateDomainRecords(recordType string) { err := ali.request(params, &records) if err != nil { + util.Log("查询域名信息发生异常! %s", err) domain.UpdateStatus = config.UpdatedFailed return } @@ -120,10 +120,10 @@ func (ali *Alidns) create(domain *config.Domain, recordType string, ipAddr strin err := ali.request(params, &result) if err == nil && result.RecordID != "" { - log.Printf("新增域名解析 %s 成功!IP: %s", domain, ipAddr) + util.Log("新增域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("新增域名解析 %s 失败!", domain) + util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, err) domain.UpdateStatus = config.UpdatedFailed } } @@ -133,7 +133,7 @@ func (ali *Alidns) modify(recordSelected AlidnsRecord, domain *config.Domain, re // 相同不修改 if recordSelected.Value == ipAddr { - log.Printf("你的IP %s 没有变化, 域名 %s", ipAddr, domain) + util.Log("你的IP %s 没有变化, 域名 %s", ipAddr, domain) return } @@ -149,10 +149,10 @@ func (ali *Alidns) modify(recordSelected AlidnsRecord, domain *config.Domain, re err := ali.request(params, &result) if err == nil && result.RecordID != "" { - log.Printf("更新域名解析 %s 成功!IP: %s", domain, ipAddr) + util.Log("更新域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("更新域名解析 %s 失败!", domain) + util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, err) domain.UpdateStatus = config.UpdatedFailed } } @@ -170,13 +170,13 @@ func (ali *Alidns) request(params url.Values, result interface{}) (err error) { req.URL.RawQuery = params.Encode() if err != nil { - log.Println("http.NewRequest失败. Error: ", err) + util.Log("异常信息: %s", err) return } client := util.CreateHTTPClient() resp, err := client.Do(req) - err = util.GetHTTPResponse(resp, alidnsEndpoint, err, result) + err = util.GetHTTPResponse(resp, err, result) return } diff --git a/dns/baidu.go b/dns/baidu.go index 8cc5b59..f47de5a 100644 --- a/dns/baidu.go +++ b/dns/baidu.go @@ -3,7 +3,6 @@ package dns import ( "bytes" "encoding/json" - "log" "net/http" "strconv" @@ -110,6 +109,7 @@ func (baidu *BaiduCloud) addUpdateDomainRecords(recordType string) { err := baidu.request("POST", baiduEndpoint+"/v1/domain/resolve/list", requestBody, &records) if err != nil { + util.Log("查询域名信息发生异常! %s", err) domain.UpdateStatus = config.UpdatedFailed return } @@ -143,10 +143,10 @@ func (baidu *BaiduCloud) create(domain *config.Domain, recordType string, ipAddr err := baidu.request("POST", baiduEndpoint+"/v1/domain/resolve/add", baiduCreateRequest, &result) if err == nil { - log.Printf("新增域名解析 %s 成功!IP: %s", domain, ipAddr) + util.Log("新增域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("新增域名解析 %s 失败!", domain) + util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, err) domain.UpdateStatus = config.UpdatedFailed } } @@ -155,7 +155,7 @@ func (baidu *BaiduCloud) create(domain *config.Domain, recordType string, ipAddr func (baidu *BaiduCloud) modify(record BaiduRecord, domain *config.Domain, rdType string, ipAddr string) { //没有变化直接跳过 if record.Rdata == ipAddr { - log.Printf("你的IP %s 没有变化, 域名 %s", ipAddr, domain) + util.Log("你的IP %s 没有变化, 域名 %s", ipAddr, domain) return } var baiduModifyRequest = BaiduModifyRequest{ @@ -171,10 +171,10 @@ func (baidu *BaiduCloud) modify(record BaiduRecord, domain *config.Domain, rdTyp err := baidu.request("POST", baiduEndpoint+"/v1/domain/resolve/edit", baiduModifyRequest, &result) if err == nil { - log.Printf("更新域名解析 %s 成功!IP: %s", domain, ipAddr) + util.Log("更新域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("更新域名解析 %s 失败!", domain) + util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, err) domain.UpdateStatus = config.UpdatedFailed } } @@ -193,7 +193,7 @@ func (baidu *BaiduCloud) request(method string, url string, data interface{}, re ) if err != nil { - log.Println("http.NewRequest失败. Error: ", err) + util.Log("异常信息: %s", err) return } @@ -201,7 +201,7 @@ func (baidu *BaiduCloud) request(method string, url string, data interface{}, re client := util.CreateHTTPClient() resp, err := client.Do(req) - err = util.GetHTTPResponse(resp, url, err, result) + err = util.GetHTTPResponse(resp, err, result) return } diff --git a/dns/callback.go b/dns/callback.go index ec91d9f..9fb4f7e 100644 --- a/dns/callback.go +++ b/dns/callback.go @@ -2,7 +2,6 @@ package dns import ( "encoding/json" - "log" "net/http" "net/url" "strings" @@ -53,12 +52,12 @@ func (cb *Callback) addUpdateDomainRecords(recordType string) { // 防止多次发送Webhook通知 if recordType == "A" { if cb.lastIpv4 == ipAddr { - log.Println("你的IPv4未变化, 未触发Callback") + util.Log("你的IPv4未变化, 未触发 %s 请求", "Callback") return } } else { if cb.lastIpv6 == ipAddr { - log.Println("你的IPv6未变化, 未触发Callback") + util.Log("你的IPv6未变化, 未触发 %s 请求", "Callback") return } } @@ -77,24 +76,24 @@ func (cb *Callback) addUpdateDomainRecords(recordType string) { requestURL := replacePara(cb.DNS.ID, ipAddr, domain, recordType, cb.TTL) u, err := url.Parse(requestURL) if err != nil { - log.Println("Callback的URL不正确") + util.Log("Callback的URL不正确") return } req, err := http.NewRequest(method, u.String(), strings.NewReader(postPara)) if err != nil { - log.Println("创建Callback请求异常, Err:", err) + util.Log("异常信息: %s", err) return } req.Header.Add("content-type", contentType) clt := util.CreateHTTPClient() resp, err := clt.Do(req) - body, err := util.GetHTTPResponseOrg(resp, requestURL, err) + body, err := util.GetHTTPResponseOrg(resp, err) if err == nil { - log.Printf("Callback调用成功, 域名: %s, IP: %s, 返回数据: %s, \n", domain, ipAddr, string(body)) + util.Log("Callback调用成功, 域名: %s, IP: %s, 返回数据: %s", domain, ipAddr, string(body)) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("Callback调用失败,Err:%s\n", err) + util.Log("Callback调用失败, 异常信息: %s", err) domain.UpdateStatus = config.UpdatedFailed } } diff --git a/dns/cloudflare.go b/dns/cloudflare.go index c7d7977..6324c8f 100644 --- a/dns/cloudflare.go +++ b/dns/cloudflare.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "fmt" - "log" "net/http" "strconv" @@ -93,6 +92,7 @@ func (cf *Cloudflare) addUpdateDomainRecords(recordType string) { // get zone result, err := cf.getZones(domain) if err != nil || len(result.Result) != 1 { + util.Log("查询域名信息发生异常! %s", err) domain.UpdateStatus = config.UpdatedFailed return } @@ -108,6 +108,7 @@ func (cf *Cloudflare) addUpdateDomainRecords(recordType string) { ) if err != nil || !records.Success { + util.Log("查询域名信息发生异常! %s", err) return } @@ -139,10 +140,10 @@ func (cf *Cloudflare) create(zoneID string, domain *config.Domain, recordType st &status, ) if err == nil && status.Success { - log.Printf("新增域名解析 %s 成功!IP: %s", domain, ipAddr) + util.Log("新增域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("新增域名解析 %s 失败!Messages: %s", domain, status.Messages) + util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, err) domain.UpdateStatus = config.UpdatedFailed } } @@ -152,7 +153,7 @@ func (cf *Cloudflare) modify(result CloudflareRecordsResp, zoneID string, domain for _, record := range result.Result { // 相同不修改 if record.Content == ipAddr { - log.Printf("你的IP %s 没有变化, 域名 %s", ipAddr, domain) + util.Log("你的IP %s 没有变化, 域名 %s", ipAddr, domain) continue } var status CloudflareStatus @@ -169,10 +170,10 @@ func (cf *Cloudflare) modify(result CloudflareRecordsResp, zoneID string, domain &status, ) if err == nil && status.Success { - log.Printf("更新域名解析 %s 成功!IP: %s", domain, ipAddr) + util.Log("更新域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("更新域名解析 %s 失败!Messages: %s", domain, status.Messages) + util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, err) domain.UpdateStatus = config.UpdatedFailed } } @@ -202,7 +203,7 @@ func (cf *Cloudflare) request(method string, url string, data interface{}, resul bytes.NewBuffer(jsonStr), ) if err != nil { - log.Println("http.NewRequest失败. Error: ", err) + util.Log("异常信息: %s", err) return } req.Header.Set("Authorization", "Bearer "+cf.DNS.Secret) @@ -210,7 +211,7 @@ func (cf *Cloudflare) request(method string, url string, data interface{}, resul client := util.CreateHTTPClient() resp, err := client.Do(req) - err = util.GetHTTPResponse(resp, url, err, result) + err = util.GetHTTPResponse(resp, err, result) return } diff --git a/dns/dnspod.go b/dns/dnspod.go index b164bec..01f6e6c 100644 --- a/dns/dnspod.go +++ b/dns/dnspod.go @@ -1,7 +1,6 @@ package dns import ( - "log" "net/url" "github.com/jeessy2/ddns-go/v5/config" @@ -76,6 +75,7 @@ func (dnspod *Dnspod) addUpdateDomainRecords(recordType string) { for _, domain := range domains { result, err := dnspod.getRecordList(domain, recordType) if err != nil { + util.Log("查询域名信息发生异常! %s", err) domain.UpdateStatus = config.UpdatedFailed return } @@ -117,10 +117,10 @@ func (dnspod *Dnspod) create(domain *config.Domain, recordType string, ipAddr st status, err := dnspod.commonRequest(recordCreateAPI, params, domain) if err == nil && status.Status.Code == "1" { - log.Printf("新增域名解析 %s 成功!IP: %s", domain, ipAddr) + util.Log("新增域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("新增域名解析 %s 失败!Code: %s, Message: %s", domain, status.Status.Code, status.Status.Message) + util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, status.Status.Message) domain.UpdateStatus = config.UpdatedFailed } } @@ -130,7 +130,7 @@ func (dnspod *Dnspod) modify(record DnspodRecord, domain *config.Domain, recordT // 相同不修改 if record.Value == ipAddr { - log.Printf("你的IP %s 没有变化, 域名 %s", ipAddr, domain) + util.Log("你的IP %s 没有变化, 域名 %s", ipAddr, domain) return } @@ -149,10 +149,10 @@ func (dnspod *Dnspod) modify(record DnspodRecord, domain *config.Domain, recordT } status, err := dnspod.commonRequest(recordModifyURL, params, domain) if err == nil && status.Status.Code == "1" { - log.Printf("更新域名解析 %s 成功!IP: %s", domain, ipAddr) + util.Log("更新域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("更新域名解析 %s 失败!Code: %s, Message: %s", domain, status.Status.Code, status.Status.Message) + util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, status.Status.Message) domain.UpdateStatus = config.UpdatedFailed } } @@ -165,7 +165,7 @@ func (dnspod *Dnspod) commonRequest(apiAddr string, values url.Values, domain *c values, ) - err = util.GetHTTPResponse(resp, apiAddr, err, &status) + err = util.GetHTTPResponse(resp, err, &status) return } @@ -186,7 +186,7 @@ func (dnspod *Dnspod) getRecordList(domain *config.Domain, typ string) (result D params, ) - err = util.GetHTTPResponse(resp, recordListAPI, err, &result) + err = util.GetHTTPResponse(resp, err, &result) return } diff --git a/dns/godaddy.go b/dns/godaddy.go index b7516cd..e4958c3 100644 --- a/dns/godaddy.go +++ b/dns/godaddy.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "fmt" - "log" "net/http" "strconv" @@ -59,12 +58,12 @@ func (g *GoDaddyDNS) updateDomainRecord(recordType string, ipAddr string, domain // 防止多次发送Webhook通知 if recordType == "A" { if g.lastIpv4 == ipAddr { - log.Println("你的IPv4未变化, 未触发Godaddy请求") + util.Log("你的IPv4未变化, 未触发 %s 请求", "godaddy") return } } else { if g.lastIpv6 == ipAddr { - log.Println("你的IPv6未变化, 未触发Godaddy请求") + util.Log("你的IPv6未变化, 未触发 %s 请求", "godaddy") return } } @@ -77,10 +76,10 @@ func (g *GoDaddyDNS) updateDomainRecord(recordType string, ipAddr string, domain Type: recordType, }}) if err == nil { - log.Printf("更新域名解析 %s 成功! IP: %s", domain, ipAddr) + util.Log("更新域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("更新域名解析 %s 失败!", domain) + util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, err) domain.UpdateStatus = config.UpdatedFailed } } @@ -115,6 +114,6 @@ func (g *GoDaddyDNS) sendReq(method string, rType string, domain *config.Domain, } req.Header = g.header resp, err := g.client.Do(req) - _, err = util.GetHTTPResponseOrg(resp, path, err) + _, err = util.GetHTTPResponseOrg(resp, err) return err } diff --git a/dns/google_domain.go b/dns/google_domain.go index 813017f..44fc81a 100644 --- a/dns/google_domain.go +++ b/dns/google_domain.go @@ -2,7 +2,6 @@ package dns import ( "io" - "log" "net/http" "net/url" "strings" @@ -55,12 +54,12 @@ func (gd *GoogleDomain) addUpdateDomainRecords(recordType string) { // 防止多次发送Webhook通知 if recordType == "A" { if gd.lastIpv4 == ipAddr { - log.Println("你的IPv4未变化, 未触发Google请求") + util.Log("你的IPv4未变化, 未触发 %s 请求", "GoogleDomain") return } } else { if gd.lastIpv6 == ipAddr { - log.Println("你的IPv6未变化, 未触发Google请求") + util.Log("你的IPv6未变化, 未触发 %s 请求", "GoogleDomain") return } } @@ -80,19 +79,19 @@ func (gd *GoogleDomain) modify(domain *config.Domain, recordType string, ipAddr err := gd.request(params, &result) if err != nil { - log.Printf("修改域名解析 %s 失败!", domain) + util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, err) domain.UpdateStatus = config.UpdatedFailed return } switch result.Status { case "nochg": - log.Printf("你的IP %s 没有变化, 域名 %s", ipAddr, domain) + util.Log("你的IP %s 没有变化, 域名 %s", ipAddr, domain) case "good": - log.Printf("修改域名解析 %s 成功!IP: %s", domain, ipAddr) + util.Log("更新域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess default: - log.Printf("修改域名解析 %s 失败!Status: %s", domain, result.Status) + util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, result) domain.UpdateStatus = config.UpdatedFailed } } @@ -107,7 +106,7 @@ func (gd *GoogleDomain) request(params url.Values, result *GoogleDomainResp) (er ) if err != nil { - log.Println("http.NewRequest失败. Error: ", err) + util.Log("异常信息: %s", err) return } @@ -117,7 +116,7 @@ func (gd *GoogleDomain) request(params url.Values, result *GoogleDomainResp) (er client := util.CreateHTTPClient() resp, err := client.Do(req) if err != nil { - log.Println("client.Do失败. Error: ", err) + util.Log("异常信息: %s", err) return } diff --git a/dns/huawei.go b/dns/huawei.go index f60d1ea..789c650 100644 --- a/dns/huawei.go +++ b/dns/huawei.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "fmt" - "log" "net/http" "strconv" @@ -94,6 +93,7 @@ func (hw *Huaweicloud) addUpdateDomainRecords(recordType string) { ) if err != nil { + util.Log("查询域名信息发生异常! %s", err) domain.UpdateStatus = config.UpdatedFailed return } @@ -124,7 +124,7 @@ func (hw *Huaweicloud) create(domain *config.Domain, recordType string, ipAddr s return } if len(zone.Zones) == 0 { - log.Println("未能找到公网域名, 请检查域名是否添加") + util.Log("在DNS服务商中未找到域名: %s", domain.String()) return } @@ -150,10 +150,10 @@ func (hw *Huaweicloud) create(domain *config.Domain, recordType string, ipAddr s &result, ) if err == nil && (len(result.Records) > 0 && result.Records[0] == ipAddr) { - log.Printf("新增域名解析 %s 成功!IP: %s", domain, ipAddr) + util.Log("新增域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("新增域名解析 %s 失败!Status: %s", domain, result.Status) + util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, err) domain.UpdateStatus = config.UpdatedFailed } } @@ -163,7 +163,7 @@ func (hw *Huaweicloud) modify(record HuaweicloudRecordsets, domain *config.Domai // 相同不修改 if len(record.Records) > 0 && record.Records[0] == ipAddr { - log.Printf("你的IP %s 没有变化, 域名 %s", ipAddr, domain) + util.Log("你的IP %s 没有变化, 域名 %s", ipAddr, domain) return } @@ -181,10 +181,10 @@ func (hw *Huaweicloud) modify(record HuaweicloudRecordsets, domain *config.Domai ) if err == nil && (len(result.Records) > 0 && result.Records[0] == ipAddr) { - log.Printf("更新域名解析 %s 成功!IP: %s, 状态: %s", domain, ipAddr, result.Status) + util.Log("更新域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("更新域名解析 %s 失败!Status: %s", domain, result.Status) + util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, err) domain.UpdateStatus = config.UpdatedFailed } } @@ -215,7 +215,7 @@ func (hw *Huaweicloud) request(method string, url string, data interface{}, resu ) if err != nil { - log.Println("http.NewRequest失败. Error: ", err) + util.Log("异常信息: %s", err) return } @@ -229,7 +229,7 @@ func (hw *Huaweicloud) request(method string, url string, data interface{}, resu client := util.CreateHTTPClient() resp, err := client.Do(req) - err = util.GetHTTPResponse(resp, url, err, result) + err = util.GetHTTPResponse(resp, err, result) return } diff --git a/dns/internal/wait_net.go b/dns/internal/wait_net.go index 204a7d5..8dcfef4 100644 --- a/dns/internal/wait_net.go +++ b/dns/internal/wait_net.go @@ -1,7 +1,6 @@ package internal import ( - "log" "strings" "time" @@ -28,15 +27,14 @@ func WaitForNetworkConnected(addresses []string) { // 如果 err 包含回环地址([::1]:53)则表示没有 DNS 服务器,设置 DNS 服务器 if strings.Contains(err.Error(), loopbackServer) && !find { server := "1.1.1.1:53" - log.Printf("解析回环地址 %s 失败!将默认使用 %s,可参考文档通过 -dns 自定义 DNS 服务器", - loopbackServer, server) - + util.Log("本机DNS异常! 将默认使用 %s, 可参考文档通过 -dns 自定义 DNS 服务器", loopbackServer, server) util.NewDialerResolver(server) find = true continue } - log.Printf("等待网络连接:%s。%s 后重试...", err, timeout) + util.Log("等待网络连接: %s", err) + util.Log("%s 后重试...", timeout) // 等待 5 秒后重试 time.Sleep(timeout) continue diff --git a/dns/namecheap.go b/dns/namecheap.go index d82a229..e54c5e4 100644 --- a/dns/namecheap.go +++ b/dns/namecheap.go @@ -2,7 +2,6 @@ package dns import ( "io" - "log" "net/http" "strings" @@ -56,17 +55,13 @@ func (nc *NameCheap) addUpdateDomainRecords(recordType string) { // 防止多次发送Webhook通知 if recordType == "A" { if nc.lastIpv4 == ipAddr { - log.Println("你的IPv4未变化, 未触发Namecheap请求") + util.Log("你的IPv4未变化, 未触发 %s 请求", "NameCheap") return } } else { // https://www.namecheap.com/support/knowledgebase/article.aspx/29/11/how-to-dynamically-update-the-hosts-ip-with-an-http-request/ - log.Println("Namecheap DDNS 不支持更新 IPv6!") + util.Log("Namecheap 不支持更新 IPv6") return - // if nc.lastIpv6 == ipAddr { - // log.Println("你的IPv6未变化, 未触发Namecheap请求") - // return - // } } for _, domain := range domains { @@ -80,17 +75,17 @@ func (nc *NameCheap) modify(domain *config.Domain, recordType string, ipAddr str err := nc.request(&result, ipAddr, domain) if err != nil { - log.Printf("修改域名解析 %s 失败!", domain) + util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, result) domain.UpdateStatus = config.UpdatedFailed return } switch result.Status { case "Success": - log.Printf("修改域名解析 %s 成功!IP: %s\n", domain, ipAddr) + util.Log("更新域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess default: - log.Printf("修改域名解析 %s 失败!Status: %s\n", domain, result.Status) + util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, result) domain.UpdateStatus = config.UpdatedFailed } } @@ -110,21 +105,21 @@ func (nc *NameCheap) request(result *NameCheapResp, ipAddr string, domain *confi ) if err != nil { - log.Println("http.NewRequest失败. Error: ", err) + util.Log("异常信息: %s", err) return } client := util.CreateHTTPClient() resp, err := client.Do(req) if err != nil { - log.Println("client.Do失败. Error: ", err) + util.Log("异常信息: %s", err) return } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { - log.Println("请求namecheap失败") + util.Log("异常信息: %s", err) return err } diff --git a/dns/namesilo.go b/dns/namesilo.go index 80a5607..6b762d1 100644 --- a/dns/namesilo.go +++ b/dns/namesilo.go @@ -3,7 +3,6 @@ package dns import ( "encoding/xml" "io" - "log" "net/http" "strings" @@ -97,7 +96,7 @@ func (ns *NameSilo) addUpdateDomainRecords(recordType string) { // 拿到DNS记录列表,从列表中去取对应域名的id,有id进行修改,没ID进行新增 records, err := ns.listRecords(domain) if err != nil { - log.Printf("获取域名列表 %s 失败!", domain) + util.Log("查询域名信息发生异常! %s", err) domain.UpdateStatus = config.UpdatedFailed return } @@ -110,7 +109,7 @@ func (ns *NameSilo) addUpdateDomainRecords(recordType string) { } else { recordID = record.RecordID if record.Value == ipAddr { - log.Printf("你的IP %s 没有变化, 域名 %s", ipAddr, domain) + util.Log("你的IP %s 没有变化, 域名 %s", ipAddr, domain) return } } @@ -127,21 +126,21 @@ func (ns *NameSilo) modify(domain *config.Domain, recordID, recordType, ipAddr s requestType = "新增" result, err = ns.request(ipAddr, domain, "", recordType, nameSiloAddRecordEndpoint) } else { - requestType = "修改" + requestType = "更新" result, err = ns.request(ipAddr, domain, recordID, "", nameSiloUpdateRecordEndpoint) } if err != nil { - log.Printf("修改域名解析 %s 失败!", domain) + util.Log("异常信息: %s", err) domain.UpdateStatus = config.UpdatedFailed return } var resp NameSiloResp xml.Unmarshal([]byte(result), &resp) if resp.Reply.Code == 300 { - log.Printf("%s 域名解析 %s 成功!IP: %s\n", requestType, domain, ipAddr) + util.Log(requestType+"域名解析 %s 成功! IP: %s\n", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("%s 域名解析 %s 失败!Deatil: %s\n", requestType, domain, resp.Reply.Detail) + util.Log(requestType+"域名解析 %s 失败! 异常信息: %s", domain, resp.Reply.Detail) domain.UpdateStatus = config.UpdatedFailed } } @@ -172,14 +171,14 @@ func (ns *NameSilo) request(ipAddr string, domain *config.Domain, recordID, reco ) if err != nil { - log.Println("http.NewRequest失败. Error: ", err) + util.Log("异常信息: %s", err) return } client := util.CreateHTTPClient() resp, err := client.Do(req) if err != nil { - log.Println("client.Do失败. Error: ", err) + util.Log("异常信息: %s", err) return } diff --git a/dns/porkbun.go b/dns/porkbun.go index 057ff37..56cb45e 100644 --- a/dns/porkbun.go +++ b/dns/porkbun.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "fmt" - "log" "net/http" "github.com/jeessy2/ddns-go/v5/config" @@ -87,26 +86,27 @@ func (pb *Porkbun) addUpdateDomainRecords(recordType string) { ) if err != nil { + util.Log("查询域名信息发生异常! %s", err) domain.UpdateStatus = config.UpdatedFailed return } if record.Status == "SUCCESS" { if len(record.Records) > 0 { // 存在,更新 - pb.modify(&record, domain, &recordType, &ipAddr) + pb.modify(&record, domain, recordType, ipAddr) } else { // 不存在,创建 - pb.create(domain, &recordType, &ipAddr) + pb.create(domain, recordType, ipAddr) } } else { - log.Printf("查询现有域名记录失败") + util.Log("在DNS服务商中未找到域名: %s", domain.String()) domain.UpdateStatus = config.UpdatedFailed } } } // 创建 -func (pb *Porkbun) create(domain *config.Domain, recordType *string, ipAddr *string) { +func (pb *Porkbun) create(domain *config.Domain, recordType string, ipAddr string) { var response PorkbunResponse err := pb.request( @@ -118,8 +118,8 @@ func (pb *Porkbun) create(domain *config.Domain, recordType *string, ipAddr *str }, PorkbunDomainRecord: &PorkbunDomainRecord{ Name: &domain.SubDomain, - Type: recordType, - Content: ipAddr, + Type: &recordType, + Content: &ipAddr, Ttl: &pb.TTL, }, }, @@ -127,34 +127,34 @@ func (pb *Porkbun) create(domain *config.Domain, recordType *string, ipAddr *str ) if err == nil && response.Status == "SUCCESS" { - log.Printf("新增域名解析 %s 成功!IP: %s", domain, *ipAddr) + util.Log("新增域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("新增域名解析 %s 失败!", domain) + util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, err) domain.UpdateStatus = config.UpdatedFailed } } // 修改 -func (pb *Porkbun) modify(record *PorkbunDomainQueryResponse, domain *config.Domain, recordType *string, ipAddr *string) { +func (pb *Porkbun) modify(record *PorkbunDomainQueryResponse, domain *config.Domain, recordType string, ipAddr string) { // 相同不修改 - if len(record.Records) > 0 && *record.Records[0].Content == *ipAddr { - log.Printf("你的IP %s 没有变化, 域名 %s", *ipAddr, domain) + if len(record.Records) > 0 && *record.Records[0].Content == ipAddr { + util.Log("你的IP %s 没有变化, 域名 %s", ipAddr, domain) return } var response PorkbunResponse err := pb.request( - porkbunEndpoint+fmt.Sprintf("/editByNameType/%s/%s/%s", domain.DomainName, *recordType, domain.SubDomain), + porkbunEndpoint+fmt.Sprintf("/editByNameType/%s/%s/%s", domain.DomainName, recordType, domain.SubDomain), &PorkbunDomainCreateOrUpdateVO{ PorkbunApiKey: &PorkbunApiKey{ AccessKey: pb.DNSConfig.ID, SecretKey: pb.DNSConfig.Secret, }, PorkbunDomainRecord: &PorkbunDomainRecord{ - Content: ipAddr, + Content: &ipAddr, Ttl: &pb.TTL, }, }, @@ -162,10 +162,10 @@ func (pb *Porkbun) modify(record *PorkbunDomainQueryResponse, domain *config.Dom ) if err == nil && response.Status == "SUCCESS" { - log.Printf("更新域名解析 %s 成功!IP: %s", domain, *ipAddr) + util.Log("更新域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("更新域名解析 %s 失败!", domain) + util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, err) domain.UpdateStatus = config.UpdatedFailed } } @@ -182,14 +182,14 @@ func (pb *Porkbun) request(url string, data interface{}, result interface{}) (er bytes.NewBuffer(jsonStr), ) if err != nil { - log.Println("http.NewRequest失败. Error: ", err) + util.Log("异常信息: %s", err) return } req.Header.Set("Content-Type", "application/json") client := util.CreateHTTPClient() resp, err := client.Do(req) - err = util.GetHTTPResponse(resp, url, err, result) + err = util.GetHTTPResponse(resp, err, result) return } diff --git a/dns/tencent_cloud.go b/dns/tencent_cloud.go index 7c5687c..f778172 100644 --- a/dns/tencent_cloud.go +++ b/dns/tencent_cloud.go @@ -3,7 +3,6 @@ package dns import ( "bytes" "encoding/json" - "log" "net/http" "strconv" @@ -99,6 +98,7 @@ func (tc *TencentCloud) addUpdateDomainRecords(recordType string) { for _, domain := range domains { result, err := tc.getRecordList(domain, recordType) if err != nil { + util.Log("查询域名信息发生异常! %s", err) domain.UpdateStatus = config.UpdatedFailed return } @@ -143,10 +143,10 @@ func (tc *TencentCloud) create(domain *config.Domain, recordType string, ipAddr &status, ) if err == nil && status.Response.Error.Code == "" { - log.Printf("新增域名解析 %s 成功!IP: %s", domain, ipAddr) + util.Log("新增域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("新增域名解析 %s 失败!Code: %s, Message: %s", domain, status.Response.Error.Code, status.Response.Error.Message) + util.Log("新增域名解析 %s 失败! 异常信息: %s", domain, status.Response.Error.Message) domain.UpdateStatus = config.UpdatedFailed } } @@ -156,7 +156,7 @@ func (tc *TencentCloud) create(domain *config.Domain, recordType string, ipAddr func (tc *TencentCloud) modify(record TencentCloudRecord, domain *config.Domain, recordType string, ipAddr string) { // 相同不修改 if record.Value == ipAddr { - log.Printf("你的IP %s 没有变化, 域名 %s", ipAddr, domain) + util.Log("你的IP %s 没有变化, 域名 %s", ipAddr, domain) return } var status TencentCloudStatus @@ -172,10 +172,10 @@ func (tc *TencentCloud) modify(record TencentCloudRecord, domain *config.Domain, &status, ) if err == nil && status.Response.Error.Code == "" { - log.Printf("更新域名解析 %s 成功!IP: %s", domain, ipAddr) + util.Log("更新域名解析 %s 成功! IP: %s", domain, ipAddr) domain.UpdateStatus = config.UpdatedSuccess } else { - log.Printf("更新域名解析 %s 失败!Code: %s, Message: %s", domain, status.Response.Error.Code, status.Response.Error.Message) + util.Log("更新域名解析 %s 失败! 异常信息: %s", domain, status.Response.Error.Message) domain.UpdateStatus = config.UpdatedFailed } } @@ -218,7 +218,7 @@ func (tc *TencentCloud) request(action string, data interface{}, result interfac bytes.NewBuffer(jsonStr), ) if err != nil { - log.Println("http.NewRequest 失败. Error: ", err) + util.Log("异常信息: %s", err) return } @@ -229,7 +229,7 @@ func (tc *TencentCloud) request(action string, data interface{}, result interfac client := util.CreateHTTPClient() resp, err := client.Do(req) - err = util.GetHTTPResponse(resp, tencentCloudEndPoint, err, result) + err = util.GetHTTPResponse(resp, err, result) return } diff --git a/go.mod b/go.mod index 4c2fd79..ddae7fd 100644 --- a/go.mod +++ b/go.mod @@ -9,4 +9,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) -require golang.org/x/sys v0.16.0 // indirect +require ( + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 +) diff --git a/go.sum b/go.sum index 14ae5a7..062bd88 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/main.go b/main.go index a8f02c7..fb54695 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "embed" + "errors" "flag" "fmt" "log" @@ -24,34 +25,34 @@ import ( // ddns-go 版本 // ddns-go version -var versionFlag = flag.Bool("v", false, "ddns-go 版本") +var versionFlag = flag.Bool("v", false, "ddns-go version") // 更新 ddns-go -var updateFlag = flag.Bool("u", false, "更新 ddns-go") +var updateFlag = flag.Bool("u", false, "Upgrade ddns-go to the latest version") // 监听地址 -var listen = flag.String("l", ":9876", "监听地址") +var listen = flag.String("l", ":9876", "Listen address") // 更新频率(秒) -var every = flag.Int("f", 300, "同步间隔时间(秒)") +var every = flag.Int("f", 300, "Sync frequency(seconds)") // 缓存次数 -var ipCacheTimes = flag.Int("cacheTimes", 5, "间隔N次与服务商比对") +var ipCacheTimes = flag.Int("cacheTimes", 5, "Interval N times compared with service providers") // 服务管理 -var serviceType = flag.String("s", "", "服务管理, 支持install, uninstall, restart") +var serviceType = flag.String("s", "", "Service management (install|uninstall|restart)") // 配置文件路径 -var configFilePath = flag.String("c", util.GetConfigFilePathDefault(), "自定义配置文件路径") +var configFilePath = flag.String("c", util.GetConfigFilePathDefault(), "config file path") // Web 服务 -var noWebService = flag.Bool("noweb", false, "不启动 web 服务") +var noWebService = flag.Bool("noweb", false, "No web service") // 跳过验证证书 -var skipVerify = flag.Bool("skipVerify", false, "跳过验证证书, 适合不能升级的老系统") +var skipVerify = flag.Bool("skipVerify", false, "Skip certificate verification") // 自定义 DNS 服务器 -var customDNSServer = flag.String("dns", "", "自定义 DNS 服务器(例如 1.1.1.1)") +var customDNSServer = flag.String("dns", "", "Custom DNS server, example: 8.8.8.8") //go:embed static var staticEmbeddedFiles embed.FS @@ -73,7 +74,7 @@ func main() { return } if _, err := net.ResolveTCPAddr("tcp", *listen); err != nil { - log.Fatalf("解析监听地址异常,%s", err) + log.Fatalf("Parse listen address failed! Exception: %s", err) } os.Setenv(web.VersionEnv, version) if *configFilePath != "" { @@ -111,9 +112,9 @@ func main() { // 非服务方式运行 switch s.Platform() { case "windows-service": - log.Println("可使用 .\\ddns-go.exe -s install 安装服务运行") + util.Log("可使用 .\\ddns-go.exe -s install 安装服务运行") default: - log.Println("可使用 sudo ./ddns-go -s install 安装服务运行") + util.Log("可使用 sudo ./ddns-go -s install 安装服务运行") } run() } @@ -163,11 +164,11 @@ func runWebServer() error { http.HandleFunc("/ipv6NetInterface", web.BasicAuth(web.Ipv6NetInterfaces)) http.HandleFunc("/webhookTest", web.BasicAuth(web.WebhookTest)) - log.Println("监听", *listen, "...") + util.Log("监听 %s", *listen) l, err := net.Listen("tcp", *listen) if err != nil { - return fmt.Errorf("监听端口发生异常, 请检查端口是否被占用: %w", err) + return errors.New(util.LogStr("监听端口发生异常, 请检查端口是否被占用! %s", err)) } // 没有配置, 自动打开浏览器 @@ -211,7 +212,7 @@ func getService() service.Service { svcConfig := &service.Config{ Name: "ddns-go", DisplayName: "ddns-go", - Description: "简单好用的DDNS。自动更新域名解析到公网IP(支持阿里云、腾讯云dnspod、Cloudflare、Callback、华为云、百度云、Porkbun、GoDaddy、Google Domain)", + Description: "Simple and easy to use DDNS. Automatically update domain name resolution to public IP (Support Aliyun, Tencent Cloud, Dnspod, Cloudflare, Callback, Huawei Cloud, Baidu Cloud, Porkbun, GoDaddy...)", Arguments: []string{"-l", *listen, "-f", strconv.Itoa(*every), "-cacheTimes", strconv.Itoa(*ipCacheTimes), "-c", *configFilePath}, Dependencies: depends, Option: options, @@ -247,9 +248,9 @@ func uninstallService() { } } if err := s.Uninstall(); err == nil { - log.Println("ddns-go 服务卸载成功!") + util.Log("ddns-go 服务卸载成功") } else { - log.Printf("ddns-go 服务卸载失败, ERR: %s\n", err) + util.Log("ddns-go 服务卸载失败, 异常信息: %s", err) } } @@ -262,7 +263,7 @@ func installService() { // 服务未知,创建服务 if err = s.Install(); err == nil { s.Start() - log.Println("安装 ddns-go 服务成功! 请打开浏览器并进行配置。") + util.Log("安装 ddns-go 服务成功! 请打开浏览器并进行配置") if service.ChosenSystem().String() == "unix-systemv" { if _, err := exec.Command("/etc/init.d/ddns-go", "enable").Output(); err != nil { log.Println(err) @@ -273,12 +274,11 @@ func installService() { } return } - - log.Printf("安装 ddns-go 服务失败, ERR: %s\n", err) + util.Log("安装 ddns-go 服务失败, 异常信息: %s", err) } if status != service.StatusUnknown { - log.Println("ddns-go 服务已安装, 无需再次安装") + util.Log("ddns-go 服务已安装, 无需再次安装") } } @@ -289,15 +289,15 @@ func restartService() { if err == nil { if status == service.StatusRunning { if err = s.Restart(); err == nil { - log.Println("重启 ddns-go 服务成功!") + util.Log("重启 ddns-go 服务成功") } } else if status == service.StatusStopped { if err = s.Start(); err == nil { - log.Println("启动 ddns-go 服务成功!") + util.Log("启动 ddns-go 服务成功") } } } else { - log.Println("ddns-go 服务未安装, 请先安装服务") + util.Log("ddns-go 服务未安装, 请先安装服务") } } @@ -308,7 +308,7 @@ func autoOpenExplorer() { if err != nil { if util.IsRunInDocker() { // docker中运行, 提示 - fmt.Println("Docker中运行, 请在浏览器中打开 http://docker主机IP:端口 进行配置") + util.Log("Docker中运行, 请在浏览器中打开 http://docker主机IP:9876 进行配置") } else { // 主机运行, 打开浏览器 addr, err := net.ResolveTCPAddr("tcp", *listen) diff --git a/static/common.css b/static/common.css index d07015e..9988113 100644 --- a/static/common.css +++ b/static/common.css @@ -226,6 +226,5 @@ main { margin-right: 8px; line-height: 0; text-align: center; - vertical-align: text-bottom; font-size: 16px; } \ No newline at end of file diff --git a/util/http_util.go b/util/http_util.go index 40d1e43..3622c88 100644 --- a/util/http_util.go +++ b/util/http_util.go @@ -4,21 +4,17 @@ import ( "encoding/json" "fmt" "io" - "log" "net/http" ) // GetHTTPResponse 处理HTTP结果,返回序列化的json -func GetHTTPResponse(resp *http.Response, url string, err error, result interface{}) error { - body, err := GetHTTPResponseOrg(resp, url, err) +func GetHTTPResponse(resp *http.Response, err error, result interface{}) error { + body, err := GetHTTPResponseOrg(resp, err) if err == nil { // log.Println(string(body)) if len(body) != 0 { err = json.Unmarshal(body, &result) - if err != nil { - log.Printf("请求接口%s解析json结果失败! ERROR: %s\n", url, err) - } } } @@ -27,9 +23,8 @@ func GetHTTPResponse(resp *http.Response, url string, err error, result interfac } // GetHTTPResponseOrg 处理HTTP结果,返回byte -func GetHTTPResponseOrg(resp *http.Response, url string, err error) ([]byte, error) { +func GetHTTPResponseOrg(resp *http.Response, err error) ([]byte, error) { if err != nil { - log.Printf("请求接口%s失败! ERROR: %s\n", url, err) return nil, err } @@ -38,14 +33,12 @@ func GetHTTPResponseOrg(resp *http.Response, url string, err error) ([]byte, err body, err := io.ReadAll(lr) if err != nil { - log.Printf("请求接口%s失败! ERROR: %s\n", url, err) + return nil, err } // 300及以上状态码都算异常 if resp.StatusCode >= 300 { - errMsg := fmt.Sprintf("请求接口 %s 失败! 返回内容: %s ,返回状态码: %d\n", url, string(body), resp.StatusCode) - log.Println(errMsg) - err = fmt.Errorf(errMsg) + err = fmt.Errorf(LogStr("返回内容: %s ,返回状态码: %d", string(body), resp.StatusCode)) } return body, err diff --git a/util/messages.go b/util/messages.go new file mode 100644 index 0000000..7b053e8 --- /dev/null +++ b/util/messages.go @@ -0,0 +1,128 @@ +package util + +import ( + "log" + "strings" + + "golang.org/x/text/language" + "golang.org/x/text/message" +) + +var logPrinter = message.NewPrinter(language.English) + +func init() { + + message.SetString(language.English, "可使用 .\\ddns-go.exe -s install 安装服务运行", "You can use 'sudo .\\ddns-go -s install' to install service") + message.SetString(language.English, "可使用 sudo ./ddns-go -s install 安装服务运行", "You can use 'sudo ./ddns-go -s install' to install service") + message.SetString(language.English, "监听 %s", "Listen on %s") + message.SetString(language.English, "配置文件已保存在: %s", "Config file has been saved to: %s") + + message.SetString(language.English, "你的IP %s 没有变化, 域名 %s", "Your's IP %s has not changed! Domain: %s") + message.SetString(language.English, "新增域名解析 %s 成功! IP: %s", "Added domain %s successfully! IP: %s") + message.SetString(language.English, "新增域名解析 %s 失败! 异常信息: %s", "Added domain %s failed! Result: %s") + + message.SetString(language.English, "更新域名解析 %s 成功! IP: %s", "Updated domain %s successfully! IP: %s") + message.SetString(language.English, "更新域名解析 %s 失败! 异常信息: %s", "Updated domain %s failed! Result: %s") + + message.SetString(language.English, "你的IPv4未变化, 未触发 %s 请求", "Your's IPv4 has not changed, %s request has not been triggered") + message.SetString(language.English, "你的IPv6未变化, 未触发 %s 请求", "Your's IPv6 has not changed, %s request has not been triggered") + message.SetString(language.English, "Namecheap 不支持更新 IPv6", "Namecheap don't supports IPv6") + + // http_util + message.SetString(language.English, "异常信息: %s", "Exception: %s") + message.SetString(language.English, "查询域名信息发生异常! %s", "Query domain info failed! %s") + message.SetString(language.English, "返回内容: %s ,返回状态码: %d", "Response body: %s ,Response status code: %d") + message.SetString(language.English, "通过接口获取IPv4失败! 接口地址: %s", "Get IPv4 from %s failed") + message.SetString(language.English, "通过接口获取IPv6失败! 接口地址: %s", "Get IPv6 from %s failed") + message.SetString(language.English, "将不会触发Webhook, 仅在第 3 次失败时触发一次Webhook, 当前失败次数:%d", "Webhook will not be triggered, only trigger once when the third failure, current failure times: %d") + message.SetString(language.English, "在DNS服务商中未找到域名: %s", "Domain %s not found in DNS provider") + + // webhook + message.SetString(language.English, "Webhook配置中的URL不正确", "Webhook url is incorrect") + message.SetString(language.English, "Webhook中的 RequestBody JSON 无效", "Webhook RequestBody JSON is invalid") + message.SetString(language.English, "Webhook调用成功! 返回数据:%s", "Webhook called successfully! Response body: %s") + message.SetString(language.English, "Webhook调用失败! 异常信息:%s", "Webhook called failed! Exception: %s") + message.SetString(language.English, "Webhook Header不正确: %s", "Webhook header is invalid: %s") + message.SetString(language.English, "请输入Webhook的URL", "Please enter the Webhook url") + + // callback + message.SetString(language.English, "Callback的URL不正确", "Callback url is incorrect") + message.SetString(language.English, "Callback调用成功, 域名: %s, IP: %s, 返回数据: %s", "Webhook called successfully! Domain: %s, IP: %s, Response body: %s") + message.SetString(language.English, "Callback调用失败, 异常信息: %s", "Webhook called failed! Exception: %s") + + // save + message.SetString(language.English, "若通过公网访问, 仅允许在ddns-go启动后 5 分钟内完成首次配置", "If accessed via the public network, only allow the first configuration to be completed within 5 minutes after ddns-go starts") + message.SetString(language.English, "若从未设置过帐号密码, 仅允许在ddns-go启动后 5 分钟内设置, 请重启ddns-go", "If you have never set an account password, you can only set it within 5 minutes after ddns-go starts, please restart ddns-go") + message.SetString(language.English, "启用外网访问, 必须输入登录用户名/密码", "Enable external network access, you must enter the login username/password") + message.SetString(language.English, "修改 '通过命令获取' 必须设置帐号密码,请先设置帐号密码", "Modify 'Get by command' must set username/password, please set username/password first") + message.SetString(language.English, "密码不安全!尝试使用更长的密码", "insecure password, try using a longer password") + + // config + message.SetString(language.English, "从网卡获得IPv4失败", "Get IPv4 from network card failed") + message.SetString(language.English, "从网卡中获得IPv4失败! 网卡名: %s", "Get IPv4 from network card failed! Network card name: %s") + message.SetString(language.English, "获取IPv4结果失败! 接口: %s ,返回值: %s", "Get IPv4 result failed! Interface: %s ,Result: %s") + message.SetString(language.English, "获取%s结果失败! 未能成功执行命令:%s, 错误:%q, 退出状态码:%s", "Get %s result failed! Command: %s, Error: %q, Exit status code: %s") + message.SetString(language.English, "获取%s结果失败! 命令: %s, 标准输出: %q", "Get %s result failed! Command: %s, Stdout: %q") + message.SetString(language.English, "从网卡获得IPv6失败", "Get IPv6 from network card failed") + message.SetString(language.English, "从网卡中获得IPv6失败! 网卡名: %s", "Get IPv6 from network card failed! Network card name: %s") + message.SetString(language.English, "获取IPv6结果失败! 接口: %s ,返回值: %s", "Get IPv6 result failed! Interface: %s ,Result: %s") + message.SetString(language.English, "未找到第 %d 个IPv6地址! 将使用第一个IPv6地址", "%dth IPv6 address not found! Will use the first IPv6 address") + message.SetString(language.English, "IPv6匹配表达式 %s 不正确! 最小从1开始", "IPv6 match expression %s is incorrect! Minimum start from 1") + message.SetString(language.English, "IPv6将使用正则表达式 %s 进行匹配", "IPv6 will use regular expression %s for matching") + message.SetString(language.English, "匹配成功! 匹配到地址: %s", "Match successfully! Matched address: %s") + message.SetString(language.English, "没有匹配到任何一个IPv6地址, 将使用第一个地址", "No IPv6 address matched, will use the first address") + message.SetString(language.English, "未能获取IPv4地址, 将不会更新", "Failed to get IPv4 address, will not update") + message.SetString(language.English, "未能获取IPv6地址, 将不会更新", "Failed to get IPv6 address, will not update") + + // domains + message.SetString(language.English, "域名: %s 不正确", "The domain %s is incorrect") + message.SetString(language.English, "域名: %s 解析失败", "The domain %s resolution failed") + message.SetString(language.English, "IPv6未改变, 将等待 %d 次后与DNS服务商进行比对", "IPv6 has not changed, will wait %d times to compare with DNS provider") + message.SetString(language.English, "IPv4未改变, 将等待 %d 次后与DNS服务商进行比对", "IPv4 has not changed, will wait %d times to compare with DNS provider") + + message.SetString(language.English, "本机DNS异常! 将默认使用 %s, 可参考文档通过 -dns 自定义 DNS 服务器", "Local DNS exception! Will use %s by default, you can use -dns to customize DNS server") + message.SetString(language.English, "等待网络连接: %s", "Waiting for network connection: %s") + message.SetString(language.English, "%s 后重试...", "Retry after %s") + + // main + message.SetString(language.English, "监听端口发生异常, 请检查端口是否被占用! %s", "Listen port failed, please check if the port is occupied! %s") + message.SetString(language.English, "Docker中运行, 请在浏览器中打开 http://docker主机IP:9876 进行配置", "Running in Docker, please open http://docker-host-ip:9876 in the browser for configuration") + message.SetString(language.English, "ddns-go 服务卸载成功", "ddns-go service uninstalled successfully") + message.SetString(language.English, "ddns-go 服务卸载失败, 异常信息: %s", "ddns-go service uninstalled failed, Exception: %s") + message.SetString(language.English, "安装 ddns-go 服务成功! 请打开浏览器并进行配置", "Install ddns-go service successfully! Please open the browser and configure it") + message.SetString(language.English, "安装 ddns-go 服务失败, 异常信息: %s", "Install ddns-go service failed, Exception: %s") + message.SetString(language.English, "ddns-go 服务已安装, 无需再次安装", "ddns-go service has been installed, no need to install again") + message.SetString(language.English, "重启 ddns-go 服务成功", "restart ddns-go service successfully") + message.SetString(language.English, "启动 ddns-go 服务成功", "start ddns-go service successfully") + message.SetString(language.English, "ddns-go 服务未安装, 请先安装服务", "ddns-go service is not installed, please install the service first") + + // login + message.SetString(language.English, "%q 配置文件为空, 超过3小时禁止从公网访问", "%q configuration file is empty, public network access is prohibited for more than 3 hours") + message.SetString(language.English, "%q 被禁止从公网访问", "%q is prohibited from accessing the public network") + message.SetString(language.English, "%q 登陆失败超过5次! 并延时5分钟响应", "%q login failed more than 5 times! And delay 5 minutes to respond") + message.SetString(language.English, "%q 帐号密码不正确", "%q username or password is incorrect") + message.SetString(language.English, "%q 请求登陆", "%q request login") + + // webhook通知 + message.SetString(language.English, "未改变", "no changed") + message.SetString(language.English, "失败", "failed") + message.SetString(language.English, "成功", "success") + +} + +func Log(key string, args ...interface{}) { + log.Println(LogStr(key, args...)) +} + +func LogStr(key string, args ...interface{}) string { + return logPrinter.Sprintf(key, args...) +} + +func InitLogLang(lang string) string { + logLang := language.English + if strings.HasPrefix(lang, "zh") { + logLang = language.Chinese + } + logPrinter = message.NewPrinter(logLang) + return logLang.String() +} diff --git a/util/update/release.go b/util/update/release.go index 7085f9e..4e1f0ca 100644 --- a/util/update/release.go +++ b/util/update/release.go @@ -41,8 +41,9 @@ func getLatest(repo string) (*Release, error) { } var result ReleaseResp - err = util.GetHTTPResponse(resp, u, err, &result) + err = util.GetHTTPResponse(resp, err, &result) if err != nil { + util.Log("异常信息: %s", err) return nil, err } diff --git a/web/basic_auth.go b/web/basic_auth.go index cc41896..fb6afa1 100644 --- a/web/basic_auth.go +++ b/web/basic_auth.go @@ -3,7 +3,6 @@ package web import ( "bytes" "encoding/base64" - "log" "net/http" "strings" "time" @@ -30,7 +29,7 @@ func BasicAuth(f ViewFunc) ViewFunc { if err != nil && time.Now().Unix()-startTime > 3*60*60 && (!util.IsPrivateNetwork(r.RemoteAddr) || !util.IsPrivateNetwork(r.Host)) { w.WriteHeader(http.StatusForbidden) - log.Printf("%q 配置文件为空, 超过3小时禁止从公网访问。\n", util.GetRequestIPStr(r)) + util.Log("%q 配置文件为空, 超过3小时禁止从公网访问", util.GetRequestIPStr(r)) return } @@ -38,7 +37,7 @@ func BasicAuth(f ViewFunc) ViewFunc { if conf.NotAllowWanAccess { if !util.IsPrivateNetwork(r.RemoteAddr) || !util.IsPrivateNetwork(r.Host) { w.WriteHeader(http.StatusForbidden) - log.Printf("%q 被禁止从公网访问!\n", util.GetRequestIPStr(r)) + util.Log("%q 被禁止从公网访问", util.GetRequestIPStr(r)) return } } @@ -51,7 +50,7 @@ func BasicAuth(f ViewFunc) ViewFunc { } if ld.FailTimes >= 5 { - log.Printf("%q 登陆失败超过5次! 并延时5分钟响应!\n", util.GetRequestIPStr(r)) + util.Log("%q 登陆失败超过5次! 并延时5分钟响应", util.GetRequestIPStr(r)) time.Sleep(5 * time.Minute) if ld.FailTimes >= 5 { ld.FailTimes = 0 @@ -84,7 +83,7 @@ func BasicAuth(f ViewFunc) ViewFunc { } ld.FailTimes = ld.FailTimes + 1 - log.Printf("%q 帐号密码不正确!\n", util.GetRequestIPStr(r)) + util.Log("%q 帐号密码不正确", util.GetRequestIPStr(r)) } // 认证失败,提示 401 Unauthorized @@ -92,6 +91,6 @@ func BasicAuth(f ViewFunc) ViewFunc { w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) // 401 状态码 w.WriteHeader(http.StatusUnauthorized) - log.Printf("%q 请求登陆!\n", util.GetRequestIPStr(r)) + util.Log("%q 请求登陆", util.GetRequestIPStr(r)) } } diff --git a/web/password.go b/web/password.go deleted file mode 100644 index 3418255..0000000 --- a/web/password.go +++ /dev/null @@ -1,86 +0,0 @@ -package web - -import ( - "errors" - "fmt" - "strings" - - passwordvalidator "github.com/wagslane/go-password-validator" -) - -const ( - replaceChars = `!@$&*` - sepChars = `_-., ` - otherSpecialChars = `"#%'()+/:;<=>?[\]^{|}~` - lowerChars = `abcdefghijklmnopqrstuvwxyz` - upperChars = `ABCDEFGHIJKLMNOPQRSTUVWXYZ` - digitsChars = `0123456789` -) - -// validatePassword 在密码大于或等于 minEntropy 时返回 nil。如果不是则返回错误。 -// 这解释了如何加强密码。向客户端显示此错误是安全的。 -// -// https://github.com/wagslane/go-password-validator/blob/v0.3.0/validate.go#L13 -func validatePassword(password string, minEntropy float64) error { - entropy := passwordvalidator.GetEntropy(password) - if entropy >= minEntropy { - return nil - } - - hasReplace := false - hasSep := false - hasOtherSpecial := false - hasLower := false - hasUpper := false - hasDigits := false - for _, c := range password { - if strings.ContainsRune(replaceChars, c) { - hasReplace = true - continue - } - if strings.ContainsRune(sepChars, c) { - hasSep = true - continue - } - if strings.ContainsRune(otherSpecialChars, c) { - hasOtherSpecial = true - continue - } - if strings.ContainsRune(lowerChars, c) { - hasLower = true - continue - } - if strings.ContainsRune(upperChars, c) { - hasUpper = true - continue - } - if strings.ContainsRune(digitsChars, c) { - hasDigits = true - continue - } - } - - allMessages := []string{} - - if !hasOtherSpecial || !hasSep || !hasReplace { - allMessages = append(allMessages, "包含更多特殊字符") - } - if !hasLower { - allMessages = append(allMessages, "使用小写字母") - } - if !hasUpper { - allMessages = append(allMessages, "使用大写字母") - } - if !hasDigits { - allMessages = append(allMessages, "使用数字") - } - - if len(allMessages) > 0 { - return fmt.Errorf( - "密码不安全!尝试%v或使用更长的密码", - strings.Join(allMessages, ","), - ) - } - - return errors.New("密码不安全!尝试使用更长的密码") -} diff --git a/web/save.go b/web/save.go index d05dc12..5070049 100755 --- a/web/save.go +++ b/web/save.go @@ -9,6 +9,7 @@ import ( "github.com/jeessy2/ddns-go/v5/config" "github.com/jeessy2/ddns-go/v5/dns" "github.com/jeessy2/ddns-go/v5/util" + passwordvalidator "github.com/wagslane/go-password-validator" ) var startTime = time.Now().Unix() @@ -31,6 +32,10 @@ func checkAndSave(request *http.Request) string { usernameNew := strings.TrimSpace(request.FormValue("Username")) passwordNew := request.FormValue("Password") + // 国际化 + accept := request.Header.Get("Accept-Language") + conf.Lang = util.InitLogLang(accept) + // 验证安全性后才允许设置保存配置文件: if time.Now().Unix()-startTime > 5*60 { firstTime := err != nil @@ -38,14 +43,14 @@ func checkAndSave(request *http.Request) string { // 首次设置 && 通过外网访问 必需在服务启动的 5 分钟内 if firstTime && (!util.IsPrivateNetwork(request.RemoteAddr) || !util.IsPrivateNetwork(request.Host)) { - return "若通过公网访问,仅允许在ddns-go启动后 5 分钟内完成首次配置" + return util.LogStr("若通过公网访问, 仅允许在ddns-go启动后 5 分钟内完成首次配置") } // 非首次设置 && 从未设置过帐号密码 && 本次设置了帐号或密码 必须在5分钟内 if !firstTime && (conf.Username == "" && conf.Password == "") && (usernameNew != "" || passwordNew != "") { - return "若从未设置过帐号密码,仅允许在ddns-go启动后 5 分钟内设置,请重启ddns-go" + return util.LogStr("若从未设置过帐号密码, 仅允许在ddns-go启动后 5 分钟内设置, 请重启ddns-go") } } @@ -59,7 +64,7 @@ func checkAndSave(request *http.Request) string { // 如启用公网访问,帐号密码不能为空 if !conf.NotAllowWanAccess && (conf.Username == "" || conf.Password == "") { - return "启用外网访问, 必须输入登录用户名/密码" + return util.LogStr("启用外网访问, 必须输入登录用户名/密码") } // 如果密码不为空则检查是否够强, 内/外网要求强度不同 @@ -68,16 +73,16 @@ func checkAndSave(request *http.Request) string { if conf.NotAllowWanAccess { minEntropyBits = 25 } - err = validatePassword(passwordNew, minEntropyBits) + err = passwordvalidator.Validate(passwordNew, minEntropyBits) if err != nil { - return err.Error() + return util.LogStr("密码不安全!尝试使用更长的密码") } } dnsConfFromJS := []dnsConf4JS{} err = json.Unmarshal([]byte(request.FormValue("DnsConf")), &dnsConfFromJS) if err != nil { - return "解析配置失败,请重试" + return "Please refresh the browser and try again" } dnsConfArray := []config.DnsConfig{} empty := dnsConf4JS{} @@ -125,7 +130,7 @@ func checkAndSave(request *http.Request) string { // 修改cmd需要验证:必须设置帐号密码 if (conf.Username == "" && conf.Password == "") && (c.Ipv4.Cmd != dnsConf.Ipv4.Cmd || c.Ipv6.Cmd != dnsConf.Ipv6.Cmd) { - return "修改 \"通过命令获取\" 必须设置帐号密码,请先设置帐号密码" + return util.LogStr("修改 '通过命令获取' 必须设置帐号密码,请先设置帐号密码") } } diff --git a/web/webhookTest.go b/web/webhookTest.go index d7c9524..b1399fc 100755 --- a/web/webhookTest.go +++ b/web/webhookTest.go @@ -1,7 +1,7 @@ package web import ( - "log" + "github.com/jeessy2/ddns-go/v5/util" "net/http" "strings" @@ -38,6 +38,6 @@ func WebhookTest(writer http.ResponseWriter, request *http.Request) { if url != "" { config.ExecWebhook(fakeDomains, fakeConfig) } else { - log.Println("请输入Webhook的URL") + util.Log("请输入Webhook的URL") } } diff --git a/web/writing.html b/web/writing.html index 843a925..58d8ba4 100755 --- a/web/writing.html +++ b/web/writing.html @@ -1,4 +1,4 @@ - + @@ -32,7 +32,7 @@ id="logsBtn" onclick="showHideLogs()" > - 日志 + Logs