Skip to content

Commit

Permalink
init commit
Browse files Browse the repository at this point in the history
  • Loading branch information
scofieldpeng committed Apr 11, 2018
0 parents commit 22a02fc
Show file tree
Hide file tree
Showing 6 changed files with 430 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dnspod-ddns
.idea
12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM golang:1.10 as builder
WORKDIR /go/src/github.com/scofieldpeng/dnspod-ddns/
COPY main.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM scofieldpeng/alpine:glibc-2.7
RUN mkdir /app
COPY --from=builder /go/src/github.com/scofieldpeng/dnspod-ddns/app /app/app
RUN chmod +x /app/app
WORKDIR /app/

ENTRYPOINT /app/app
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# dnspod-ddns

通过dnspod来进行ddns的小脚本,用来做什么,你懂得,本来不想写的,只是github.com另外一个哥们儿用py写的跑不起来,看了下代码有问题,懒得修改直接用golang撸了一个

如果对你有用,可以star一下,2333

## 使用

**配置项:**

配置采用环境变量驱动,具体见下:

```ini
# 填写你在dnspod申请的id
DNSPOD_ID=123456
# 在dnspod申请的token
DNSPOD_TOKEN=123456
# 在dnspod要更新的顶级域名
DNSPOD_DOMAIN=example.com
# 在dnspod要更新的子域名前缀,如果是根域名,填写@即可
DNSPOD_SUBDOMAIN=example
# 你的邮箱
DNSPOD_EMAIL[email protected]
```

### docker运行

```bash
docer run --name=ddns --restart=always -d \
-e DNSPOD_ID=${DNSPOD_ID} \
-e DNSPOD_TOKEN=${DNSPOD_TOKEN} \
-e DNSPOD_DOMAIN=${DNSPOD_DOMAIN} \
-e DNSPOD_SUBDOMAIN=${DNSPOD_SUBDOMAIN} \
-e [email protected] \
scofieldpeng/dnspod-ddns:1.0.0
```

### 源码编译

1. 安装go
2. 下载源码
```
go get github.com/scofieldpeng/dnspod-ddns
```
3. 编译
```
go build -o app .
```

$GOPATH/src/github.com/scofieldpeng/dnspod-ddns/app文件即为二进制包,直接运行即可

## 申请dnspod的ID和token

教程详见dnspod官网[https://support.dnspod.cn/Kb/showarticle/tsid/227/](https://support.dnspod.cn/Kb/showarticle/tsid/227/)
258 changes: 258 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
package main

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)

type appConfig struct {
dnspodId string
dnspodToken string
recordId string
domain string
subDomain string
internal int
email string
}

// 验证appConfig
func (c appConfig) Validate() (err error) {
if c.dnspodId == "" {
return errors.New("environment DNSPOD_ID required")
}
if c.dnspodToken == "" {
return errors.New("environment DNSPOD_TOKEN required")
}

if c.recordId == "" && c.subDomain == "" {
return errors.New("environment DNSPOD_RECORDID or DNSPOD_SUBDOMAIN required")
}
if c.domain == "" {
return errors.New("environment DNSPOD_DOMAIN required")
}
if c.internal < 5 {
return errors.New("environment DNSPOD_INTERNAL should range from 0 to 5")
}

return
}

var (
config = appConfig{
dnspodId: os.Getenv("DNSPOD_ID"),
dnspodToken: os.Getenv("DNSPOD_TOKEN"),
domain: os.Getenv("DNSPOD_DOMAIN"),
subDomain: os.Getenv("DNSPOD_SUBDOMAIN"),
internal: 60,
email: os.Getenv("DNSPOD_EMAIL"),
}
)

const (
ClientUserAgent = "DNSPOD-DDNS-CLIENT"
Version = "1.0.0"
StatusOk = "1"
)

func init() {
internal := os.Getenv("DNSPOD_INTERNAL")
config.internal, _ = strconv.Atoi(internal)
if config.internal < 5 {
config.internal = 60
}
if config.email == "" {
config.email = "[email protected]"
}
}

func main() {
var (
err error
lastPublicIP string
publicIP string
)

if err := config.Validate(); err != nil {
fmt.Println("[error]", err)
os.Exit(1)
}

fmt.Println("start")
for {
publicIP, err = GetPublicIP()
if err != nil {
fmt.Println(err.Error())
time.Sleep(time.Duration(config.internal) * time.Second)
continue
}
if config.recordId == "" || lastPublicIP == "" {
config.recordId, lastPublicIP, err = GetRecord()
if err != nil {
fmt.Println(err.Error())
time.Sleep(time.Duration(config.internal) * time.Second)
continue
}
}
if publicIP != lastPublicIP {
fmt.Println("发现公网IP变化,开始更新")
if err = UpdateRecord(config.recordId, publicIP); err != nil {
fmt.Println(err.Error())
time.Sleep(time.Duration(config.internal) * time.Second)
continue
}
fmt.Println("公网IP更新成功,新的公网IP:", publicIP)
lastPublicIP = publicIP
}
fmt.Println("下次更新时间:", time.Now().Add(time.Duration(config.internal)*time.Second).Format("2006-01-02 15:04:05"))
time.Sleep(time.Duration(config.internal) * time.Second)
}
}

// 公共返回参数
type CommonResponse struct {
Status struct {
Code string `json:"code"`
Message string `json:"message"`
CreateTime string `json:"created_at"`
} `json:"status"`
}

// 记录列表返回值
type RecordListResponse struct {
CommonResponse
Records []struct {
SubDomain string `json:"name"`
Id string `json:"id"`
PublicIP string `json:"value"`
} `json:"records"`
}

// 更新record记录
func UpdateRecord(recordId string, publicIP string) (err error) {
var (
request *http.Request
response *http.Response
c *http.Client
body = url.Values{}
responseData CommonResponse
)
body.Add("login_token", fmt.Sprintf("%s,%s", config.dnspodId, config.dnspodToken))
body.Add("format", "json")
body.Add("lang", "cn")
body.Add("error_on_empty", "no")
body.Add("domain", config.domain)
body.Add("sub_domain", config.subDomain)
body.Add("record_id", recordId)
body.Add("record_type", "A")
body.Add("record_line", "默认")
body.Add("value", publicIP)

request, err = http.NewRequest("POST", "https://dnsapi.cn/Record.Modify", strings.NewReader(body.Encode()))
if err != nil {
err = errors.New("request对象创建失败,err:" + err.Error())
return
}
request.Header.Add("User-Agent", fmt.Sprintf("%s/%s(%s)", ClientUserAgent, Version, config.email))
request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
c = &http.Client{Timeout: time.Second * 30}
response, err = c.Do(request)
if err != nil {
err = errors.New("请求出错,err:" + err.Error())
return
}

if err = json.NewDecoder(response.Body).Decode(&responseData); err != nil {
err = errors.New("解析数据失败,err:" + err.Error())
return
}
defer response.Body.Close()

if responseData.Status.Code != StatusOk {
err = errors.New(fmt.Sprintf("更新失败,code:%s,message:%s", responseData.Status.Code, responseData.Status.Message))
return
}

return
}

// 获取recordid
func GetRecord() (recordId, IP string, err error) {
var (
request *http.Request
response *http.Response
c *http.Client
body = url.Values{}
responseData RecordListResponse
)
body.Add("login_token", fmt.Sprintf("%s,%s", config.dnspodId, config.dnspodToken))
body.Add("format", "json")
body.Add("lang", "cn")
body.Add("error_on_empty", "no")

body.Add("domain", config.domain)
body.Add("sub_domain", config.subDomain)
request, err = http.NewRequest("POST", "https://dnsapi.cn/Record.List", strings.NewReader(body.Encode()))
if err != nil {
err = errors.New("request对象创建失败,err:" + err.Error())
return
}
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
request.Header.Set("User-Agent", fmt.Sprintf("%s/%s(%s)", ClientUserAgent, Version, config.email))
c = &http.Client{Timeout: time.Second * 30}
response, err = c.Do(request)
if err != nil {
err = errors.New("请求出错,err:" + err.Error())
return
}

if err = json.NewDecoder(response.Body).Decode(&responseData); err != nil {
err = errors.New("解析数据失败,err:" + err.Error())
return
}
defer response.Body.Close()
if responseData.Status.Code != StatusOk {
err = errors.New(fmt.Sprintf("获取record失败,code:%s,message:%s", responseData.Status.Code, responseData.Status.Message))
return
}

for _, v := range responseData.Records {
if v.SubDomain == config.subDomain {
recordId = v.Id
IP = v.PublicIP
return
}
}

err = errors.New("没有找到相关记录,请先前往dnspod进行添加")
return
}

type getPublicIPResponse struct {
IP string `json:"origin"`
}

// 获取公网IP,如果出错,返回第二个参数
func GetPublicIP() (publicIP string, err error) {
var (
response *http.Response
responseData getPublicIPResponse
)
if response, err = http.Get("http://www.httpbin.org/ip"); err != nil {
err = errors.New("获取公网IP出错,err:" + err.Error())
return
}
if err = json.NewDecoder(response.Body).Decode(&responseData); err != nil {
err = errors.New("获取公网IP出错,err:" + err.Error())
return
}
defer response.Body.Close()

return responseData.IP, nil
}
Loading

0 comments on commit 22a02fc

Please sign in to comment.