Initial commit

This commit is contained in:
Fabian 2024-02-04 12:58:01 +01:00
commit 1ffcd90796
4 changed files with 247 additions and 0 deletions

93
.gitignore vendored Normal file
View File

@ -0,0 +1,93 @@
# Created by https://www.toptal.com/developers/gitignore/api/goland+all
# Edit at https://www.toptal.com/developers/gitignore?templates=goland+all
### GoLand+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### GoLand+all Patch ###
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
.idea/*
!.idea/codeStyles
!.idea/runConfigurations
# End of https://www.toptal.com/developers/gitignore/api/goland+all

15
go.mod Normal file
View File

@ -0,0 +1,15 @@
module dyndns-inwx-go
go 1.21
require (
github.com/libdns/inwx v0.2.0
github.com/libdns/libdns v0.2.1
github.com/sethvargo/go-envconfig v1.0.0
)
require (
github.com/boombuler/barcode v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pquerna/otp v1.4.0 // indirect
)

22
go.sum Normal file
View File

@ -0,0 +1,22 @@
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/libdns/inwx v0.2.0 h1:yCP5EiVqmmp71dFhCD34PkUxLQixJTEZNOlO6itdNeA=
github.com/libdns/inwx v0.2.0/go.mod h1:QNKQqPp5wVvbuMMmGFZz0UL9tZ6FaWlKTMPeUqPsJ+g=
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/sethvargo/go-envconfig v1.0.0 h1:1C66wzy4QrROf5ew4KdVw942CQDa55qmlYmw9FZxZdU=
github.com/sethvargo/go-envconfig v1.0.0/go.mod h1:Lzc75ghUn5ucmcRGIdGQ33DKJrcjk4kihFYgSTBmjIc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=

117
main.go Normal file
View File

@ -0,0 +1,117 @@
package main
import (
"context"
"fmt"
"github.com/libdns/libdns"
"log"
"net"
"os"
"strings"
"time"
"github.com/libdns/inwx"
"github.com/sethvargo/go-envconfig"
)
type Config struct {
Username string `env:"DYNDNS_USERNAME, required"`
Password string `env:"DYNDNS_PASSWORD, required"`
Zone string `env:"DYNDNS_ZONE, required"`
Record string `env:"DYNDNS_RECORD, required"`
Nameservers string `env:"DYNDNS_NAMESERVERS, required"`
}
func main() {
ctx := context.Background()
var cfg Config
if err := envconfig.Process(ctx, &cfg); err != nil {
log.Fatal("Error reading config: ", err)
}
ifaces, err := net.Interfaces()
if err != nil {
log.Fatal("Error getting interfaces: ", err)
}
var ownIp string
for _, iface := range ifaces {
addrs, err := iface.Addrs()
if err != nil {
log.Fatalf("Error getting addresses for %v: %s", iface, err)
}
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok &&
ipnet.IP.To4() == nil &&
!ipnet.IP.IsPrivate() &&
!ipnet.IP.IsLinkLocalUnicast() &&
!ipnet.IP.IsLoopback() &&
ipnet.IP.IsGlobalUnicast() {
ownIp = ipnet.IP.String()
}
}
}
log.Print("Detected own IP: ", ownIp)
resolv := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{
Timeout: time.Duration(10) * time.Second,
}
var conn net.Conn
var err error
for _, server := range strings.Split(cfg.Nameservers, ",") {
conn, err = d.DialContext(ctx, network, fmt.Sprintf("%s:53", server))
if err == nil {
return conn, err
} else {
log.Printf("Error resolving with NS \"%s\"", server)
}
}
return conn, err
},
}
target := fmt.Sprintf("%s.%s", cfg.Record, cfg.Zone)
resolvIps, err := resolv.LookupHost(ctx, target)
if err != nil {
log.Fatalf("Error resolving %s: %v", target, err)
}
if len(resolvIps) != 1 {
log.Fatalf("Detected %d IPs for \"%s\". Don't know what to do", len(resolvIps), target)
}
resolvIp := resolvIps[0]
log.Printf("Resolved IP: %s", resolvIp)
forceUpdate := len(os.Args) == 2 && os.Args[1] == "--force"
if forceUpdate {
log.Println("Detected force update")
}
if ownIp == resolvIp && !forceUpdate {
log.Println("Resolved IP is equal to own IP. Nothing to do.")
os.Exit(0)
}
provider := &inwx.Provider{
Username: cfg.Username,
Password: cfg.Password,
}
records, err := provider.SetRecords(context.Background(), cfg.Zone, []libdns.Record{
{
Type: "AAAA",
Name: cfg.Record,
Value: ownIp,
TTL: time.Duration(15) * time.Minute,
},
})
if err != nil {
log.Fatalf("Error updating DNS record \"%s\": %s", target, err)
}
for _, record := range records {
log.Printf("Set record: %v", record)
}
}