7 changed files with 391 additions and 182 deletions
@ -1,42 +1,43 @@ |
|||||||
#!/bin/bash -e |
#!/bin/bash -e |
||||||
|
|
||||||
VERSION="0.0.1" |
VERSION="0.1.0" |
||||||
|
|
||||||
rm -rf dockerbuild || true |
rm -rf dockerbuild || true |
||||||
mkdir dockerbuild |
mkdir dockerbuild |
||||||
|
|
||||||
#cp Dockerfile dockerbuild/Dockerfile-amd64 |
cp Dockerfile dockerbuild/Dockerfile-amd64 |
||||||
cp Dockerfile dockerbuild/Dockerfile-arm |
cp Dockerfile dockerbuild/Dockerfile-arm |
||||||
#cp Dockerfile dockerbuild/Dockerfile-arm64 |
#cp Dockerfile dockerbuild/Dockerfile-arm64 |
||||||
|
|
||||||
#sed -E 's|FROM alpine|FROM amd64/alpine|' -i dockerbuild/Dockerfile-amd64 |
sed -E 's|FROM alpine|FROM amd64/alpine|' -i dockerbuild/Dockerfile-amd64 |
||||||
sed -E 's|FROM alpine|FROM arm32v7/alpine|' -i dockerbuild/Dockerfile-arm |
sed -E 's|FROM alpine|FROM arm32v7/alpine|' -i dockerbuild/Dockerfile-arm |
||||||
#sed -E 's|FROM alpine|FROM arm64v8/alpine|' -i dockerbuild/Dockerfile-arm64 |
#sed -E 's|FROM alpine|FROM arm64v8/alpine|' -i dockerbuild/Dockerfile-arm64 |
||||||
|
|
||||||
#sed -E 's/GOARCH=/GOARCH=amd64/' -i dockerbuild/Dockerfile-amd64 |
sed -E 's/GOARCH=/GOARCH=amd64/' -i dockerbuild/Dockerfile-amd64 |
||||||
sed -E 's/GOARCH=/GOARCH=arm/' -i dockerbuild/Dockerfile-arm |
sed -E 's/GOARCH=/GOARCH=arm/' -i dockerbuild/Dockerfile-arm |
||||||
#sed -E 's/GOARCH=/GOARCH=arm64/' -i dockerbuild/Dockerfile-arm64 |
#sed -E 's/GOARCH=/GOARCH=arm64/' -i dockerbuild/Dockerfile-arm64 |
||||||
|
|
||||||
#docker build -f dockerbuild/Dockerfile-amd64 -t sequentialread/gandi-dns-updater:$VERSION-amd64 . |
docker build -f dockerbuild/Dockerfile-amd64 -t sequentialread/dns-updater:$VERSION-amd64 . |
||||||
docker build -f dockerbuild/Dockerfile-arm -t sequentialread/gandi-dns-updater:$VERSION-arm . |
docker build -f dockerbuild/Dockerfile-arm -t sequentialread/dns-updater:$VERSION-arm . |
||||||
#docker build -f dockerbuild/Dockerfile-arm64 -t sequentialread/gandi-dns-updater:$VERSION-arm64 . |
#docker build -f dockerbuild/Dockerfile-arm64 -t sequentialread/dns-updater:$VERSION-arm64 . |
||||||
|
|
||||||
#docker push sequentialread/gandi-dns-updater:$VERSION-amd64 |
docker push sequentialread/dns-updater:$VERSION-amd64 |
||||||
docker push sequentialread/gandi-dns-updater:$VERSION-arm |
docker push sequentialread/dns-updater:$VERSION-arm |
||||||
#docker push sequentialread/gandi-dns-updater:$VERSION-arm64 |
#docker push sequentialread/dns-updater:$VERSION-arm64 |
||||||
|
|
||||||
export DOCKER_CLI_EXPERIMENTAL=enabled |
export DOCKER_CLI_EXPERIMENTAL=enabled |
||||||
|
|
||||||
# docker manifest create sequentialread/gandi-dns-updater:$VERSION \ |
# docker manifest create sequentialread/dns-updater:$VERSION \ |
||||||
# sequentialread/gandi-dns-updater:$VERSION-amd64 \ |
# sequentialread/dns-updater:$VERSION-amd64 \ |
||||||
# sequentialread/gandi-dns-updater:$VERSION-arm \ |
# sequentialread/dns-updater:$VERSION-arm \ |
||||||
# sequentialread/gandi-dns-updater:$VERSION-arm64 |
# sequentialread/dns-updater:$VERSION-arm64 |
||||||
|
|
||||||
docker manifest create sequentialread/gandi-dns-updater:$VERSION \ |
docker manifest create sequentialread/dns-updater:$VERSION \ |
||||||
sequentialread/gandi-dns-updater:$VERSION-arm \ |
sequentialread/dns-updater:$VERSION-arm \ |
||||||
|
sequentialread/dns-updater:$VERSION-amd64 |
||||||
|
|
||||||
#docker manifest annotate --arch amd64 sequentialread/gandi-dns-updater:$VERSION sequentialread/gandi-dns-updater:$VERSION-amd64 |
docker manifest annotate --arch amd64 sequentialread/dns-updater:$VERSION sequentialread/dns-updater:$VERSION-amd64 |
||||||
docker manifest annotate --arch arm sequentialread/gandi-dns-updater:$VERSION sequentialread/gandi-dns-updater:$VERSION-arm |
docker manifest annotate --arch arm sequentialread/dns-updater:$VERSION sequentialread/dns-updater:$VERSION-arm |
||||||
#docker manifest annotate --arch arm64 sequentialread/gandi-dns-updater:$VERSION sequentialread/gandi-dns-updater:$VERSION-arm64 |
#docker manifest annotate --arch arm64 sequentialread/dns-updater:$VERSION sequentialread/dns-updater:$VERSION-arm64 |
||||||
|
|
||||||
docker manifest push sequentialread/gandi-dns-updater:$VERSION |
docker manifest push sequentialread/dns-updater:$VERSION |
@ -1,5 +1,4 @@ |
|||||||
{ |
{ |
||||||
"GandiRecordTTLSeconds": 300, |
|
||||||
"HealthPollingSeconds": 10, |
"HealthPollingSeconds": 10, |
||||||
"ResetAfterConsecutiveFailures": 10 |
"ResetAfterConsecutiveFailures": 10 |
||||||
} |
} |
@ -0,0 +1,185 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"encoding/json" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"io/ioutil" |
||||||
|
"log" |
||||||
|
"net/http" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
errors "git.sequentialread.com/forest/pkg-errors" |
||||||
|
) |
||||||
|
|
||||||
|
// https://porkbun.com/api/json/v3/documentation
|
||||||
|
|
||||||
|
type PorkbunService struct { |
||||||
|
Client *http.Client |
||||||
|
APIUrl string |
||||||
|
RecordTTLSeconds int |
||||||
|
Auth PorkbunAuth |
||||||
|
} |
||||||
|
|
||||||
|
type PorkbunAuth struct { |
||||||
|
KeyId string `json:"apikey"` |
||||||
|
SecretKey string `json:"secretapikey"` |
||||||
|
} |
||||||
|
|
||||||
|
type PorkbunRecordsResponse struct { |
||||||
|
Records []PorkbunRecord `json:"records"` |
||||||
|
} |
||||||
|
|
||||||
|
type PorkbunStatusResponse struct { |
||||||
|
Status string `json:"status"` |
||||||
|
} |
||||||
|
|
||||||
|
type PorkbunRecord struct { |
||||||
|
Id string `json:"id"` |
||||||
|
Name string `json:"name"` |
||||||
|
Type string `json:"type"` |
||||||
|
Content string `json:"content"` |
||||||
|
TTL string `json:"ttl"` |
||||||
|
Priority string `json:"prio"` |
||||||
|
Notes string `json:"notes"` |
||||||
|
} |
||||||
|
|
||||||
|
type PorkbunUpdate struct { |
||||||
|
KeyId string `json:"apikey"` |
||||||
|
SecretKey string `json:"secretapikey"` |
||||||
|
Name string `json:"name"` |
||||||
|
Type string `json:"type"` |
||||||
|
Content string `json:"content"` |
||||||
|
TTL string `json:"ttl"` |
||||||
|
Priority string `json:"prio"` |
||||||
|
} |
||||||
|
|
||||||
|
func NewPorkbunService(keyId, secretKey string, recordTTLSeconds int) *PorkbunService { |
||||||
|
return &PorkbunService{ |
||||||
|
Client: &http.Client{Timeout: 30 * time.Second}, |
||||||
|
APIUrl: "https://porkbun.com/api/json/v3", |
||||||
|
Auth: PorkbunAuth{ |
||||||
|
KeyId: keyId, |
||||||
|
SecretKey: secretKey, |
||||||
|
}, |
||||||
|
RecordTTLSeconds: recordTTLSeconds, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (service *PorkbunService) TryToUpdateRecordsForDomain(domain DomainConfig, currentPublicIPv4 string) bool { |
||||||
|
path := fmt.Sprintf("/dns/retrieve/%s", domain.Domain) |
||||||
|
authJsonBytes, _ := json.Marshal(service.Auth) |
||||||
|
statusCode, responseBytes, err := service.PorkbunHTTP("POST", path, bytes.NewBuffer(authJsonBytes)) |
||||||
|
if err != nil { |
||||||
|
// if porkbun isn't responding at all, we can just give up and try again in 10 seconds.
|
||||||
|
log.Println("waiting for porkbun to respond...") |
||||||
|
return false |
||||||
|
} |
||||||
|
if statusCode >= 300 { |
||||||
|
// if we got a non-200 status code from gandi, thats a fatal error. Log it.
|
||||||
|
log.Printf("porkbun (GET %s) returned http %d: \n%s\n\n", path, statusCode, responseBytes) |
||||||
|
return false |
||||||
|
} |
||||||
|
var responseObj PorkbunRecordsResponse |
||||||
|
err = json.Unmarshal(responseBytes, &responseObj) |
||||||
|
if err != nil { |
||||||
|
log.Printf("porkbun (GET %s) returned http %d, but it didn't return valid json: %s: \n%s\n\n", path, statusCode, err, responseBytes) |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
configIndexesToAdd := map[int]bool{} |
||||||
|
for i, _ := range domain.Records { |
||||||
|
configIndexesToAdd[i] = true |
||||||
|
} |
||||||
|
for _, porkbunRecord := range responseObj.Records { |
||||||
|
for i, configRecord := range domain.Records { |
||||||
|
if configRecord.FQDN(domain.Domain) == porkbunRecord.Name && recordTypesMatch(configRecord.Type, porkbunRecord.Type) { |
||||||
|
configIndexesToAdd[i] = false |
||||||
|
if porkbunRecord.Content != currentPublicIPv4 { |
||||||
|
updateJsonBytes, _ := json.Marshal(PorkbunUpdate{ |
||||||
|
KeyId: service.Auth.KeyId, |
||||||
|
SecretKey: service.Auth.SecretKey, |
||||||
|
Name: strings.ReplaceAll(configRecord.Name, "@", ""), |
||||||
|
Type: configRecord.Type, |
||||||
|
TTL: strconv.Itoa(service.RecordTTLSeconds), |
||||||
|
Content: currentPublicIPv4, |
||||||
|
}) |
||||||
|
path := fmt.Sprintf("/dns/edit/%s/%s", domain.Domain, porkbunRecord.Id) |
||||||
|
log.Printf("porkbun POST %s\n", path) |
||||||
|
statusCode, responseBytes, err := service.PorkbunHTTP("POST", path, bytes.NewBuffer(updateJsonBytes)) |
||||||
|
if err != nil { |
||||||
|
log.Println("waiting for porkbun to respond...") |
||||||
|
return false |
||||||
|
} |
||||||
|
var responseObj PorkbunStatusResponse |
||||||
|
err = json.Unmarshal(responseBytes, &responseObj) |
||||||
|
if err != nil { |
||||||
|
log.Printf("porkbun (POST %s) returned http %d, but it didn't return valid json: %s: \n%s\n\n", path, statusCode, err, responseBytes) |
||||||
|
return false |
||||||
|
} |
||||||
|
if responseObj.Status != "SUCCESS" { |
||||||
|
log.Printf("porkbun (POST %s) returned http %d: \n%s\n\n", path, statusCode, responseBytes) |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for i, configRecord := range domain.Records { |
||||||
|
if configIndexesToAdd[i] { |
||||||
|
createJsonBytes, _ := json.Marshal(PorkbunUpdate{ |
||||||
|
KeyId: service.Auth.KeyId, |
||||||
|
SecretKey: service.Auth.SecretKey, |
||||||
|
Name: strings.ReplaceAll(configRecord.Name, "@", ""), |
||||||
|
Type: configRecord.Type, |
||||||
|
TTL: strconv.Itoa(service.RecordTTLSeconds), |
||||||
|
Content: currentPublicIPv4, |
||||||
|
}) |
||||||
|
path := fmt.Sprintf("/dns/create/%s", domain.Domain) |
||||||
|
log.Printf("porkbun POST %s\n", path) |
||||||
|
statusCode, responseBytes, err := service.PorkbunHTTP("POST", path, bytes.NewBuffer(createJsonBytes)) |
||||||
|
if err != nil { |
||||||
|
log.Println("waiting for porkbun to respond...") |
||||||
|
return false |
||||||
|
} |
||||||
|
var responseObj PorkbunStatusResponse |
||||||
|
err = json.Unmarshal(responseBytes, &responseObj) |
||||||
|
if err != nil { |
||||||
|
log.Printf("porkbun (POST %s) returned http %d, but it didn't return valid json: %s: \n%s\n\n", path, statusCode, err, responseBytes) |
||||||
|
return false |
||||||
|
} |
||||||
|
if responseObj.Status != "SUCCESS" { |
||||||
|
log.Printf("porkbun (POST %s) returned http %d: \n%s\n\n", path, statusCode, responseBytes) |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
func (service *PorkbunService) PorkbunHTTP( |
||||||
|
method string, |
||||||
|
url string, |
||||||
|
body io.Reader, |
||||||
|
) (int, []byte, error) { |
||||||
|
|
||||||
|
request, err := http.NewRequest(method, url, body) |
||||||
|
if err != nil { |
||||||
|
return 0, nil, errors.Wrapf(err, "failed to create HTTP request calling %s %s", method, url) |
||||||
|
} |
||||||
|
response, err := service.Client.Do(request) |
||||||
|
if err != nil { |
||||||
|
return 0, nil, errors.Wrapf(err, "HTTP request error when calling %s %s", method, url) |
||||||
|
} |
||||||
|
bytes, err := ioutil.ReadAll(response.Body) |
||||||
|
if err != nil { |
||||||
|
return response.StatusCode, nil, errors.Wrapf(err, "HTTP read error when calling %s %s ", method, url) |
||||||
|
} |
||||||
|
|
||||||
|
return response.StatusCode, bytes, err |
||||||
|
} |
Loading…
Reference in new issue