improve: 使用标准项目结构
This commit is contained in:
60
internal/controllers/daemon.go
Normal file
60
internal/controllers/daemon.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Mmx233/BitSrunLoginGo/internal/global"
|
||||
"github.com/Mmx233/tool"
|
||||
"github.com/howeyc/fsnotify"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type daemon struct {
|
||||
Mark string
|
||||
Path string
|
||||
}
|
||||
|
||||
// Daemon 后台模式控制包
|
||||
var Daemon = daemon{
|
||||
Mark: fmt.Sprint(time.Now().UnixNano()),
|
||||
Path: global.Config.Settings.Daemon.Path,
|
||||
}
|
||||
|
||||
// MarkDaemon 写入后台标记文件
|
||||
func (a *daemon) MarkDaemon() error {
|
||||
return tool.File.WriteAll(a.Path, []byte(a.Mark))
|
||||
}
|
||||
|
||||
// CheckDaemon 检查后台标记文件
|
||||
func (a *daemon) CheckDaemon() bool {
|
||||
if data, err := tool.File.ReadAll(a.Path); err != nil {
|
||||
return false
|
||||
} else {
|
||||
return string(data) == a.Mark
|
||||
}
|
||||
}
|
||||
|
||||
// DaemonChan 后台标记文件监听
|
||||
func (a *daemon) DaemonChan() bool {
|
||||
f, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = f.Watch(Daemon.Path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-f.Event:
|
||||
if event.IsModify() && a.CheckDaemon() {
|
||||
continue
|
||||
}
|
||||
os.Exit(0)
|
||||
case e := <-f.Error:
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
67
internal/controllers/guardian.go
Normal file
67
internal/controllers/guardian.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/Mmx233/BitSrunLoginGo/internal/global"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/Mmx233/BitSrunLoginGo/tools"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Guardian 守护模式逻辑
|
||||
func Guardian() {
|
||||
GuardianDuration := time.Duration(global.Config.Settings.Guardian.Duration) * time.Second
|
||||
|
||||
if global.Config.Settings.Daemon.Enable {
|
||||
go Daemon.DaemonChan()
|
||||
|
||||
if e := Daemon.MarkDaemon(); e != nil {
|
||||
log.Warnln("写入daemon标记文件失败: ", e)
|
||||
}
|
||||
}
|
||||
|
||||
var c = make(chan bool)
|
||||
for {
|
||||
go func() {
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
if global.Config.Settings.Basic.Interfaces == "" { //单网卡
|
||||
e := Login(nil, true)
|
||||
if e != nil {
|
||||
log.Errorln("登录出错: ", e)
|
||||
}
|
||||
} else { //多网卡
|
||||
interfaces, e := tools.GetInterfaceAddr()
|
||||
if e == nil {
|
||||
for _, eth := range interfaces {
|
||||
log.Debugf("使用 %s 网口登录 ", eth.Name)
|
||||
e = Login(eth.Addr, true)
|
||||
if e != nil {
|
||||
log.Errorln("网口 ", eth.Name+" 登录出错: ", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c <- false
|
||||
}()
|
||||
<-c
|
||||
time.Sleep(GuardianDuration)
|
||||
}
|
||||
}
|
||||
|
||||
// EnterGuardian 守护模式入口,控制是否进入daemon
|
||||
func EnterGuardian() {
|
||||
log.Infoln("[以守护模式启动]")
|
||||
if global.Config.Settings.Daemon.Enable || global.Flags.Daemon {
|
||||
if err := exec.Command(os.Args[0], append(os.Args[1:], "--running-daemon")...).Start(); err != nil {
|
||||
log.Fatalln("启动守护失败: ", err)
|
||||
}
|
||||
log.Infoln("[进入后台进程模式]")
|
||||
return
|
||||
}
|
||||
Guardian()
|
||||
}
|
||||
89
internal/controllers/login.go
Normal file
89
internal/controllers/login.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
global2 "github.com/Mmx233/BitSrunLoginGo/internal/global"
|
||||
dns2 "github.com/Mmx233/BitSrunLoginGo/internal/pkg/dns"
|
||||
"github.com/Mmx233/BitSrunLoginGo/pkg/srun"
|
||||
"github.com/Mmx233/BitSrunLoginGo/tools"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Login 登录逻辑
|
||||
func Login(localAddr net.Addr, debugOutput bool) error {
|
||||
// 登录状态检查
|
||||
|
||||
httpClient := tools.HttpPackSelect(localAddr).Client
|
||||
conf := &srun.Conf{
|
||||
Https: global2.Config.Settings.Basic.Https,
|
||||
LoginInfo: srun.LoginInfo{
|
||||
Form: &global2.Config.Form,
|
||||
Meta: &global2.Config.Meta,
|
||||
},
|
||||
Client: httpClient,
|
||||
}
|
||||
|
||||
var output func(args ...interface{})
|
||||
if debugOutput {
|
||||
output = log.Debugln
|
||||
} else {
|
||||
output = log.Infoln
|
||||
}
|
||||
|
||||
output("正在获取登录状态")
|
||||
|
||||
online, ip, e := srun.LoginStatus(conf)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
if localAddr != nil && global2.Config.Settings.Basic.UseDhcpIP {
|
||||
ip = localAddr.(*net.TCPAddr).IP.String()
|
||||
} else if global2.Flags.ClientIP != "" {
|
||||
ip = global2.Flags.ClientIP
|
||||
}
|
||||
|
||||
log.Debugln("认证客户端 ip: ", ip)
|
||||
|
||||
// 登录执行
|
||||
|
||||
if online {
|
||||
output("已登录~")
|
||||
|
||||
if global2.Config.Settings.DDNS.Enable && global2.Config.Settings.Guardian.Enable && ipLast != ip {
|
||||
if ddns(ip, httpClient) == nil {
|
||||
ipLast = ip
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
} else {
|
||||
log.Infoln("检测到用户未登录,开始尝试登录...")
|
||||
|
||||
if e = srun.DoLogin(ip, conf); e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
log.Infoln("登录成功~")
|
||||
|
||||
if global2.Config.Settings.DDNS.Enable {
|
||||
_ = ddns(ip, httpClient)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var ipLast string
|
||||
|
||||
func ddns(ip string, httpClient *http.Client) error {
|
||||
return dns2.Run(&dns2.Config{
|
||||
Provider: global2.Config.Settings.DDNS.Provider,
|
||||
IP: ip,
|
||||
Domain: global2.Config.Settings.DDNS.Domain,
|
||||
TTL: global2.Config.Settings.DDNS.TTL,
|
||||
Conf: global2.Config.Settings.DDNS.Config,
|
||||
Http: httpClient,
|
||||
})
|
||||
}
|
||||
67
internal/global/config.go
Normal file
67
internal/global/config.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"github.com/Mmx233/BitSrunLoginGo/internal/global/models"
|
||||
"github.com/Mmx233/BitSrunLoginGo/pkg/srun"
|
||||
"github.com/Mmx233/tool"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Config models.Config
|
||||
|
||||
var Timeout time.Duration
|
||||
|
||||
func readConfig() {
|
||||
//配置文件默认值
|
||||
viper.SetDefault("form", srun.LoginForm{
|
||||
Domain: "www.msftconnecttest.com",
|
||||
UserType: "cmcc",
|
||||
})
|
||||
viper.SetDefault("meta", srun.LoginMeta{
|
||||
N: "200",
|
||||
Type: "1",
|
||||
Acid: "5",
|
||||
Enc: "srun_bx1",
|
||||
})
|
||||
viper.SetDefault("settings", models.Settings{
|
||||
Basic: models.Basic{
|
||||
Timeout: 5,
|
||||
},
|
||||
Daemon: models.Daemon{
|
||||
Path: ".BitSrun",
|
||||
},
|
||||
Guardian: models.Guardian{
|
||||
Duration: 300,
|
||||
},
|
||||
Log: models.Log{
|
||||
FilePath: "./",
|
||||
},
|
||||
DDNS: models.DDNS{
|
||||
Enable: false,
|
||||
TTL: 600,
|
||||
Domain: "www.example.com",
|
||||
},
|
||||
})
|
||||
|
||||
//生成配置文件
|
||||
if !tool.File.Exists(Flags.Path) {
|
||||
e := viper.WriteConfigAs(Flags.Path)
|
||||
if e != nil {
|
||||
log.Fatalln("[init] 生成配置文件失败:", e)
|
||||
}
|
||||
log.Infoln("[init] 已生成配置文件,请编辑 '" + Flags.Path + "' 然后重试")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
//读取配置文件
|
||||
viper.SetConfigFile(Flags.Path)
|
||||
if e := viper.ReadInConfig(); e != nil {
|
||||
log.Fatalln("[init] 读取配置文件失败:", e)
|
||||
}
|
||||
if e := viper.Unmarshal(&Config); e != nil {
|
||||
log.Fatalln("[init] 解析配置文件失败:", e)
|
||||
}
|
||||
}
|
||||
24
internal/global/flags.go
Normal file
24
internal/global/flags.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"flag"
|
||||
)
|
||||
|
||||
var Flags struct {
|
||||
//配置文件路径
|
||||
Path string
|
||||
//daemon模式内置标记
|
||||
RunningDaemon bool
|
||||
//强制daemon
|
||||
Daemon bool
|
||||
//指定 client ip
|
||||
ClientIP string
|
||||
}
|
||||
|
||||
func initFlags() {
|
||||
flag.StringVar(&Flags.Path, "config", "Config.yaml", "config path")
|
||||
flag.StringVar(&Flags.ClientIP, "ip", "", "client ip for login")
|
||||
flag.BoolVar(&Flags.RunningDaemon, "running-daemon", false, "")
|
||||
flag.BoolVar(&Flags.Daemon, "daemon", false, "")
|
||||
flag.Parse()
|
||||
}
|
||||
18
internal/global/init.go
Normal file
18
internal/global/init.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initFlags()
|
||||
|
||||
//配置文件初始化
|
||||
readConfig()
|
||||
|
||||
//初始化常变量
|
||||
Timeout = time.Duration(Config.Settings.Basic.Timeout) * time.Second
|
||||
|
||||
//初始化日志配置
|
||||
initLog()
|
||||
}
|
||||
48
internal/global/log.go
Normal file
48
internal/global/log.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
nested "github.com/antonfisher/nested-logrus-formatter"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func initLog() {
|
||||
if Config.Settings.Log.DebugLevel {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
if Config.Settings.Log.WriteFile {
|
||||
//日志路径初始化与处理
|
||||
if !strings.HasSuffix(Config.Settings.Log.FilePath, "/") {
|
||||
Config.Settings.Log.FilePath += "/"
|
||||
}
|
||||
e := os.MkdirAll(Config.Settings.Log.FilePath, os.ModePerm)
|
||||
if e != nil {
|
||||
log.Fatalln(e)
|
||||
}
|
||||
|
||||
if Config.Settings.Log.FileName == "" {
|
||||
Config.Settings.Log.FileName = time.Now().Format("2006.01.02-15.04.05") + ".log"
|
||||
}
|
||||
|
||||
f, e := os.OpenFile(Config.Settings.Log.FilePath+Config.Settings.Log.FileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
|
||||
if e != nil {
|
||||
log.Fatalln(e)
|
||||
}
|
||||
|
||||
//设置双重输出
|
||||
mw := io.MultiWriter(os.Stdout, f)
|
||||
log.SetOutput(mw)
|
||||
|
||||
//设置输出格式
|
||||
log.SetFormatter(&nested.Formatter{
|
||||
HideKeys: true,
|
||||
NoColors: Config.Settings.Log.WriteFile,
|
||||
TimestampFormat: "2006-01-02 15:04:05",
|
||||
})
|
||||
}
|
||||
}
|
||||
52
internal/global/models/config.go
Normal file
52
internal/global/models/config.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/Mmx233/BitSrunLoginGo/pkg/srun"
|
||||
)
|
||||
|
||||
type Daemon struct {
|
||||
Enable bool `json:"enable" yaml:"enable" mapstructure:"enable"`
|
||||
Path string `json:"path" yaml:"path" mapstructure:"path"`
|
||||
}
|
||||
|
||||
type Guardian struct {
|
||||
Enable bool `json:"enable" yaml:"enable" mapstructure:"enable"`
|
||||
Duration uint `json:"duration" yaml:"duration" mapstructure:"duration"`
|
||||
}
|
||||
|
||||
type Basic struct {
|
||||
Https bool `json:"https" yaml:"https" mapstructure:"https"`
|
||||
SkipCertVerify bool `json:"skip_cert_verify" yaml:"skip_cert_verify" mapstructure:"skip_cert_verify"`
|
||||
Timeout uint `json:"timeout" yaml:"timeout" mapstructure:"timeout"`
|
||||
Interfaces string `json:"interfaces" yaml:"interfaces" mapstructure:"interfaces"`
|
||||
UseDhcpIP bool `json:"use_dhcp_ip" yaml:"use_dhcp_ip" mapstructure:"use_dhcp_ip"`
|
||||
}
|
||||
|
||||
type Log struct {
|
||||
DebugLevel bool `json:"debug_level" yaml:"debug_level" mapstructure:"debug_level"`
|
||||
WriteFile bool `json:"write_file" yaml:"write_file" mapstructure:"write_file"`
|
||||
FilePath string `json:"log_path" yaml:"log_path" mapstructure:"log_path"`
|
||||
FileName string `json:"log_name" yaml:"log_name" mapstructure:"log_name"`
|
||||
}
|
||||
|
||||
type DDNS struct {
|
||||
Enable bool `json:"enable" yaml:"enable" mapstructure:"enable"`
|
||||
TTL uint `json:"ttl" yaml:"ttl" mapstructure:"ttl"`
|
||||
Domain string `json:"domain" yaml:"domain" mapstructure:"domain"`
|
||||
Provider string `json:"provider" yaml:"provider" mapstructure:"provider"`
|
||||
Config map[string]interface{} `mapstructure:",remain"`
|
||||
}
|
||||
|
||||
type Settings struct {
|
||||
Basic Basic `json:"basic" yaml:"basic" mapstructure:"basic"`
|
||||
Guardian Guardian `json:"guardian" yaml:"guardian" mapstructure:"guardian"`
|
||||
Daemon Daemon `json:"daemon" yaml:"daemon" mapstructure:"daemon"`
|
||||
Log Log `json:"log" yaml:"log" mapstructure:"log"`
|
||||
DDNS DDNS `json:"ddns" yaml:"ddns" mapstructure:"ddns"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Form srun.LoginForm `json:"form" yaml:"form" mapstructure:"form"`
|
||||
Meta srun.LoginMeta `json:"meta" yaml:"meta" mapstructure:"meta"`
|
||||
Settings Settings `json:"settings" yaml:"settings" mapstructure:"settings"`
|
||||
}
|
||||
182
internal/pkg/dns/aliyun/aliyun.go
Normal file
182
internal/pkg/dns/aliyun/aliyun.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package aliyun
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
dnsUtil2 "github.com/Mmx233/BitSrunLoginGo/internal/pkg/dns/util"
|
||||
"github.com/Mmx233/tool"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DnsProvider struct {
|
||||
TTL uint `mapstructure:"-"`
|
||||
Http *tool.Http `mapstructure:"-"`
|
||||
AccessKeyId string `mapstructure:"access_key_id"`
|
||||
AccessKeySecret string `mapstructure:"access_key_secret"`
|
||||
}
|
||||
|
||||
func New(ttl uint, conf map[string]interface{}, Http *http.Client) (*DnsProvider, error) {
|
||||
var p = DnsProvider{
|
||||
TTL: ttl,
|
||||
Http: tool.NewHttpTool(Http),
|
||||
}
|
||||
e := dnsUtil2.DecodeConfig(conf, &p)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
if p.AccessKeyId == "" || p.AccessKeySecret == "" {
|
||||
return nil, errors.New("aliyun AccessKey 不能为空")
|
||||
}
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func (a DnsProvider) SendRequest(Type, Action string, data map[string]interface{}) (*http.Response, error) {
|
||||
var reqOpt = tool.DoHttpReq{
|
||||
Url: "https://alidns.aliyuncs.com",
|
||||
}
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
data["Format"] = "json"
|
||||
data["Version"] = "2015-01-09"
|
||||
data["SignatureMethod"] = "HMAC-SHA1"
|
||||
data["SignatureVersion"] = "1.0"
|
||||
data["SignatureNonce"] = fmt.Sprint(tool.Rand.Num(10000000, 90000000))
|
||||
data["Timestamp"] = time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
||||
data["Action"] = Action
|
||||
data["AccessKeyId"] = a.AccessKeyId
|
||||
|
||||
signStr := Type + "&" + url.QueryEscape("/") + "&"
|
||||
var keys = make([]string, len(data))
|
||||
var i int
|
||||
for k := range data {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for i, k := range keys {
|
||||
str := k + "=" + url.QueryEscape(fmt.Sprint(data[k]))
|
||||
if i == 0 {
|
||||
str = url.QueryEscape(str)
|
||||
} else {
|
||||
str = url.QueryEscape("&" + str)
|
||||
}
|
||||
signStr += str
|
||||
}
|
||||
|
||||
mac := hmac.New(sha1.New, []byte(a.AccessKeySecret+"&"))
|
||||
_, e := mac.Write([]byte(signStr))
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
data["Signature"] = base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||
|
||||
if Type == "GET" || Type == "DELETE" {
|
||||
reqOpt.Query = data
|
||||
} else {
|
||||
reqOpt.Body = data
|
||||
}
|
||||
|
||||
resp, e := a.Http.Request(Type, &reqOpt)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
if resp.StatusCode > 299 {
|
||||
defer resp.Body.Close()
|
||||
var res Response
|
||||
if e = json.NewDecoder(resp.Body).Decode(&res); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return nil, errors.New(res.Message)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (a DnsProvider) DomainRecordStatus(subDomain, rootDomain string) (*DomainStatus, bool, error) {
|
||||
resp, e := a.SendRequest("GET", "DescribeDomainRecords", map[string]interface{}{
|
||||
"DomainName": rootDomain,
|
||||
"SearchMode": "EXACT",
|
||||
"KeyWord": subDomain,
|
||||
"PageSize": 1,
|
||||
"Type": "A",
|
||||
})
|
||||
if e != nil {
|
||||
return nil, false, e
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var res DomainStatusRes
|
||||
if e = json.NewDecoder(resp.Body).Decode(&res); e != nil {
|
||||
return nil, false, e
|
||||
}
|
||||
|
||||
if res.TotalCount == 0 || len(res.DomainRecords.Record) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
return &res.DomainRecords.Record[0], true, nil
|
||||
}
|
||||
|
||||
func (a DnsProvider) UpdateRecord(RecordId, subDomain, ip string) error {
|
||||
resp, e := a.SendRequest("POST", "UpdateDomainRecord", map[string]interface{}{
|
||||
"RecordId": RecordId,
|
||||
"RR": subDomain,
|
||||
"Type": "A",
|
||||
"Value": ip,
|
||||
"TTL": a.TTL,
|
||||
})
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a DnsProvider) NewRecord(subDomain, rootDomain, ip string) error {
|
||||
resp, e := a.SendRequest("POST", "AddDomainRecord", map[string]interface{}{
|
||||
"DomainName": rootDomain,
|
||||
"RR": subDomain,
|
||||
"Type": "A",
|
||||
"Value": ip,
|
||||
"TTL": a.TTL,
|
||||
})
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a DnsProvider) SetDomainRecord(domain, ip string) error {
|
||||
subDomain, rootDomain, e := dnsUtil2.DecodeDomain(domain)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
record, exist, e := a.DomainRecordStatus(subDomain, rootDomain)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
if exist {
|
||||
if record.Value == ip {
|
||||
return nil
|
||||
}
|
||||
return a.UpdateRecord(record.RecordId, subDomain, ip)
|
||||
} else {
|
||||
return a.NewRecord(subDomain, rootDomain, ip)
|
||||
}
|
||||
}
|
||||
19
internal/pkg/dns/aliyun/models.go
Normal file
19
internal/pkg/dns/aliyun/models.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package aliyun
|
||||
|
||||
type Response struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type DomainStatus struct {
|
||||
DomainName string `json:"DomainName"`
|
||||
RecordId string `json:"RecordId"`
|
||||
Value string `json:"Value"`
|
||||
}
|
||||
|
||||
type DomainStatusRes struct {
|
||||
TotalCount uint `json:"TotalCount"`
|
||||
DomainRecords struct {
|
||||
Record []DomainStatus `json:"Record"`
|
||||
} `json:"DomainRecords"`
|
||||
}
|
||||
68
internal/pkg/dns/cloudflare/cloudflare.go
Normal file
68
internal/pkg/dns/cloudflare/cloudflare.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/Mmx233/BitSrunLoginGo/internal/pkg/dns/util"
|
||||
"github.com/cloudflare/cloudflare-go"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type DnsProvider struct {
|
||||
Api *cloudflare.API `mapstructure:"-"`
|
||||
TTL int `mapstructure:"-"`
|
||||
Zone string `mapstructure:"zone"`
|
||||
ZoneResource *cloudflare.ResourceContainer `mapstructure:"-"`
|
||||
Token string `mapstructure:"token"`
|
||||
}
|
||||
|
||||
func New(ttl int, conf map[string]interface{}, Http *http.Client) (*DnsProvider, error) {
|
||||
var p = DnsProvider{
|
||||
TTL: ttl,
|
||||
}
|
||||
e := dnsUtil.DecodeConfig(conf, &p)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
if p.Zone == "" {
|
||||
return nil, errors.New("cloudflare zone 不能为空")
|
||||
}
|
||||
p.ZoneResource = cloudflare.ZoneIdentifier(p.Zone)
|
||||
|
||||
if p.Token == "" {
|
||||
return nil, errors.New("cloudflare token 不能为空")
|
||||
}
|
||||
|
||||
p.Api, e = cloudflare.NewWithAPIToken(p.Token, cloudflare.HTTPClient(Http))
|
||||
return &p, e
|
||||
}
|
||||
|
||||
func (a DnsProvider) SetDomainRecord(domain, ip string) error {
|
||||
records, _, e := a.Api.ListDNSRecords(context.Background(), a.ZoneResource, cloudflare.ListDNSRecordsParams{
|
||||
Type: "A",
|
||||
Name: domain,
|
||||
})
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
if len(records) == 0 {
|
||||
_, e = a.Api.CreateDNSRecord(context.Background(), a.ZoneResource, cloudflare.CreateDNSRecordParams{
|
||||
Type: "A",
|
||||
Name: domain,
|
||||
Content: ip,
|
||||
TTL: a.TTL,
|
||||
})
|
||||
return e
|
||||
} else {
|
||||
record := records[0]
|
||||
if record.Content == ip {
|
||||
return nil
|
||||
}
|
||||
return a.Api.UpdateDNSRecord(context.Background(), a.ZoneResource, cloudflare.UpdateDNSRecordParams{
|
||||
ID: record.ID,
|
||||
Content: ip,
|
||||
})
|
||||
}
|
||||
}
|
||||
71
internal/pkg/dns/dnspod/dnspod.go
Normal file
71
internal/pkg/dns/dnspod/dnspod.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package dnspod
|
||||
|
||||
import (
|
||||
dnsUtil2 "github.com/Mmx233/BitSrunLoginGo/internal/pkg/dns/util"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/regions"
|
||||
dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DnsProvider struct {
|
||||
Client *dnspod.Client `mapstructure:"-"`
|
||||
TTL uint64 `mapstructure:"-"`
|
||||
SecretId string `mapstructure:"secret_id"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
}
|
||||
|
||||
func New(ttl uint64, conf map[string]interface{}, Http http.RoundTripper) (*DnsProvider, error) {
|
||||
var p = DnsProvider{TTL: ttl}
|
||||
e := dnsUtil2.DecodeConfig(conf, &p)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
p.Client, e = dnspod.NewClient(common.NewCredential(p.SecretId, p.SecretKey), regions.Guangzhou, profile.NewClientProfile())
|
||||
p.Client.WithHttpTransport(Http)
|
||||
return &p, e
|
||||
}
|
||||
|
||||
func (a DnsProvider) SetDomainRecord(domain, ip string) error {
|
||||
subDomain, rootDomain, e := dnsUtil2.DecodeDomain(domain)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
var (
|
||||
recordType = "A"
|
||||
recordLine = "默认"
|
||||
limit uint64 = 1
|
||||
)
|
||||
|
||||
reqRecordList := dnspod.NewDescribeRecordListRequest()
|
||||
reqRecordList.Domain = &rootDomain
|
||||
reqRecordList.Subdomain = &subDomain
|
||||
reqRecordList.Limit = &limit
|
||||
res, e := a.Client.DescribeRecordList(reqRecordList)
|
||||
if (e != nil && strings.Contains(e.Error(), dnspod.RESOURCENOTFOUND_NODATAOFRECORD)) || (e == nil && len(res.Response.RecordList) == 0) {
|
||||
reqNewRecord := dnspod.NewCreateRecordRequest()
|
||||
reqNewRecord.TTL = &a.TTL
|
||||
reqNewRecord.Domain = &rootDomain
|
||||
reqNewRecord.RecordType = &recordType
|
||||
reqNewRecord.RecordLine = &recordLine
|
||||
reqNewRecord.Value = &ip
|
||||
reqNewRecord.SubDomain = &subDomain
|
||||
_, e = a.Client.CreateRecord(reqNewRecord)
|
||||
return e
|
||||
} else if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
reqModifyRecord := dnspod.NewModifyRecordRequest()
|
||||
reqModifyRecord.Domain = &rootDomain
|
||||
reqModifyRecord.SubDomain = &subDomain
|
||||
reqModifyRecord.Value = &ip
|
||||
reqModifyRecord.RecordId = res.Response.RecordList[0].RecordId
|
||||
reqModifyRecord.RecordLine = &recordLine
|
||||
reqModifyRecord.RecordType = &recordType
|
||||
_, e = a.Client.ModifyRecord(reqModifyRecord)
|
||||
return e
|
||||
}
|
||||
55
internal/pkg/dns/initer.go
Normal file
55
internal/pkg/dns/initer.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Mmx233/BitSrunLoginGo/internal/pkg/dns/aliyun"
|
||||
"github.com/Mmx233/BitSrunLoginGo/internal/pkg/dns/cloudflare"
|
||||
"github.com/Mmx233/BitSrunLoginGo/internal/pkg/dns/dnspod"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func Run(c *Config) error {
|
||||
log.Debugln("开始 DDNS 流程")
|
||||
|
||||
if c.TTL == 0 {
|
||||
c.TTL = 600
|
||||
}
|
||||
|
||||
// 配置解析
|
||||
|
||||
var dns Provider
|
||||
var e error
|
||||
switch c.Provider {
|
||||
case "aliyun":
|
||||
dns, e = aliyun.New(c.TTL, c.Conf, c.Http)
|
||||
case "cloudflare":
|
||||
dns, e = cloudflare.New(int(c.TTL), c.Conf, c.Http)
|
||||
case "dnspod":
|
||||
dns, e = dnspod.New(uint64(c.TTL), c.Conf, c.Http.Transport)
|
||||
default:
|
||||
var msg string
|
||||
if c.Provider == "" {
|
||||
msg = "DDNS 模块 dns 运营商不能为空"
|
||||
} else {
|
||||
msg = fmt.Sprintf("DDNS 模块 dns 运营商 %s 不支持", c.Provider)
|
||||
}
|
||||
log.Warnln(msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
if e != nil {
|
||||
log.Warnf("解析 DDNS 配置失败:%v", e)
|
||||
return e
|
||||
}
|
||||
|
||||
// 修改 dns 记录
|
||||
|
||||
if e = dns.SetDomainRecord(c.Domain, c.IP); e != nil {
|
||||
log.Warnf("设置 dns 解析记录失败:%v", e)
|
||||
return e
|
||||
}
|
||||
|
||||
log.Debugln("DDNS 配置应用成功")
|
||||
|
||||
return nil
|
||||
}
|
||||
16
internal/pkg/dns/models.go
Normal file
16
internal/pkg/dns/models.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package dns
|
||||
|
||||
import "net/http"
|
||||
|
||||
type Provider interface {
|
||||
SetDomainRecord(domain, ip string) error
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Provider string
|
||||
IP string
|
||||
Domain string
|
||||
TTL uint
|
||||
Conf map[string]interface{}
|
||||
Http *http.Client
|
||||
}
|
||||
9
internal/pkg/dns/util/config.go
Normal file
9
internal/pkg/dns/util/config.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package dnsUtil
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
func DecodeConfig(conf map[string]interface{}, output interface{}) error {
|
||||
return mapstructure.Decode(conf, output)
|
||||
}
|
||||
18
internal/pkg/dns/util/domain.go
Normal file
18
internal/pkg/dns/util/domain.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package dnsUtil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func DecodeDomain(domain string) (subStr string, rootDomain string, e error) {
|
||||
t := strings.Split(domain, ".")
|
||||
if len(t) == 1 {
|
||||
return "", "", errors.New("域名不合法")
|
||||
} else if len(t) == 2 {
|
||||
return "@", domain, nil
|
||||
}
|
||||
|
||||
l := len(t)
|
||||
return strings.Join(t[:l-2], "."), strings.Join(t[l-2:l], "."), nil
|
||||
}
|
||||
Reference in New Issue
Block a user