initial
This commit is contained in:
commit
65882afcff
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
config.yaml
|
||||||
|
env
|
||||||
|
tildewatch
|
12
Containerfile
Normal file
12
Containerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM golang:1.20 as builder
|
||||||
|
WORKDIR /go/src/app
|
||||||
|
COPY . .
|
||||||
|
RUN apt update && apt upgrade -y
|
||||||
|
RUN CGO_ENABLED=0 go build
|
||||||
|
|
||||||
|
FROM alpine:latest as final
|
||||||
|
WORKDIR /app
|
||||||
|
RUN mkdir -p /lib64
|
||||||
|
COPY --from=builder /go/src/app/tildewatch /app/
|
||||||
|
|
||||||
|
CMD ["/app/tildewatch"]
|
32
go.mod
Normal file
32
go.mod
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
module git.saintnet.tech/stryan/tildewatch
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/charmbracelet/log v0.2.2
|
||||||
|
github.com/go-chi/chi v1.5.4
|
||||||
|
github.com/go-chi/chi/v5 v5.0.10
|
||||||
|
github.com/go-gandi/go-gandi v0.6.0
|
||||||
|
golang.org/x/exp v0.0.0-20230724220655-d98519c11495
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
|
github.com/charmbracelet/lipgloss v0.7.1 // indirect
|
||||||
|
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||||
|
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
|
github.com/muesli/reflow v0.3.0 // indirect
|
||||||
|
github.com/muesli/termenv v0.15.2 // indirect
|
||||||
|
github.com/peterhellberg/link v1.2.0 // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.4 // indirect
|
||||||
|
github.com/smarty/assertions v1.15.1 // indirect
|
||||||
|
golang.org/x/sys v0.10.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
moul.io/http2curl v1.0.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/go-gandi/go-gandi => github.com/stryan/go-gandi v0.0.0-20230725010359-2d3115fcccde
|
54
go.sum
Normal file
54
go.sum
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
|
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
|
||||||
|
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
|
||||||
|
github.com/charmbracelet/log v0.2.2 h1:CaXgos+ikGn5tcws5Cw3paQuk9e/8bIwuYGhnkqQFjo=
|
||||||
|
github.com/charmbracelet/log v0.2.2/go.mod h1:Zs11hKpb8l+UyX4y1srwZIGW+MPCXJHIty3MB9l/sno=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs=
|
||||||
|
github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||||
|
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
|
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||||
|
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||||
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||||
|
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||||
|
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||||
|
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||||
|
github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c=
|
||||||
|
github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||||
|
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/smarty/assertions v1.15.1 h1:812oFiXI+G55vxsFf+8bIZ1ux30qtkdqzKbEFwyX3Tk=
|
||||||
|
github.com/smarty/assertions v1.15.1/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
|
||||||
|
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stryan/go-gandi v0.0.0-20230725010359-2d3115fcccde h1:HNiHYiVE8tjdx8NRgRyXWj7HltWZT4cPcHI3zZmWJGM=
|
||||||
|
github.com/stryan/go-gandi v0.0.0-20230725010359-2d3115fcccde/go.mod h1:jRMLmlWJUFaFHHP7mSm5pYFrjoU2PU0mYKEHWdczksU=
|
||||||
|
golang.org/x/exp v0.0.0-20230724220655-d98519c11495 h1:zKGKw2WlGb8oPoRGqQ2PT8g2YoCN1w/YbbQjHXCdUWE=
|
||||||
|
golang.org/x/exp v0.0.0-20230724220655-d98519c11495/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||||
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
|
||||||
|
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=
|
203
main.go
Normal file
203
main.go
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/log"
|
||||||
|
"github.com/go-chi/chi/middleware"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-gandi/go-gandi"
|
||||||
|
"github.com/go-gandi/go-gandi/config"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func load_config(filename string) (*Config, error) {
|
||||||
|
u := &Config{}
|
||||||
|
yamlFile, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = yaml.Unmarshal(yamlFile, u)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx, endFn := context.WithCancel(context.Background())
|
||||||
|
domain, ok := os.LookupEnv("WATCH_DOMAIN")
|
||||||
|
if !ok {
|
||||||
|
log.Fatal("no domain specified")
|
||||||
|
}
|
||||||
|
apikey, ok := os.LookupEnv("WATCH_API")
|
||||||
|
if !ok {
|
||||||
|
log.Fatal("no Gandi api key")
|
||||||
|
}
|
||||||
|
port, ok := os.LookupEnv("WATCH_PORT")
|
||||||
|
if !ok {
|
||||||
|
port = "6283"
|
||||||
|
}
|
||||||
|
file, ok := os.LookupEnv("WATCH_FILE")
|
||||||
|
if !ok {
|
||||||
|
file = "./config.yaml"
|
||||||
|
}
|
||||||
|
|
||||||
|
debug := os.Getenv("WATCH_DEBUG")
|
||||||
|
watchusers, err := load_config(file)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, v := range watchusers.Users {
|
||||||
|
if v.Hostname == "" {
|
||||||
|
log.Fatal("bad config", "user", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := config.Config{
|
||||||
|
APIKey: apikey,
|
||||||
|
}
|
||||||
|
|
||||||
|
a := &Watch{
|
||||||
|
Tildes: make(map[string]Registration),
|
||||||
|
debug: debug != "",
|
||||||
|
domain: domain,
|
||||||
|
conf: conf,
|
||||||
|
}
|
||||||
|
for _, v := range watchusers.Users {
|
||||||
|
a.Tildes[v.Secret] = Registration{
|
||||||
|
Domain: v.Hostname,
|
||||||
|
Update: false,
|
||||||
|
IPAddr: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info(a.Tildes)
|
||||||
|
server := &http.Server{Addr: fmt.Sprintf("0.0.0.0:%v", port), Handler: a.server()}
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, os.Interrupt)
|
||||||
|
go func() {
|
||||||
|
<-c
|
||||||
|
log.Info("trying to shutdown cleanly")
|
||||||
|
endFn()
|
||||||
|
server.Shutdown(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
server.ListenAndServe()
|
||||||
|
wg.Done()
|
||||||
|
log.Info("watch web server shutdown")
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
a.run(ctx)
|
||||||
|
wg.Done()
|
||||||
|
log.Info("watch updater shutdown")
|
||||||
|
}()
|
||||||
|
log.Info("watch running")
|
||||||
|
wg.Wait()
|
||||||
|
log.Info("shut down")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (watch *Watch) server() http.Handler {
|
||||||
|
r := chi.NewRouter()
|
||||||
|
r.Use(middleware.Logger)
|
||||||
|
r.Post("/submit", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var payload Request
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&payload)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("error parsing ip submission", "error", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for i, v := range watch.Tildes {
|
||||||
|
if payload.Secret == i && v.IPAddr != payload.IPAddr {
|
||||||
|
cur := watch.Tildes[i]
|
||||||
|
watch.Tildes[i] = Registration{
|
||||||
|
IPAddr: payload.IPAddr,
|
||||||
|
Update: true,
|
||||||
|
Domain: cur.Domain,
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (watch *Watch) run(ctx context.Context) error {
|
||||||
|
for {
|
||||||
|
ticker := time.NewTicker(5 * time.Second)
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
for i, v := range watch.Tildes {
|
||||||
|
if v.Update {
|
||||||
|
err := watch.run_update(i, v)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("error running update", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
ticker.Stop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (watch *Watch) run_update(token string, reg Registration) error {
|
||||||
|
if reg.Domain == "" || reg.IPAddr == "" {
|
||||||
|
return fmt.Errorf("invalid registration for update %v: %v", reg.Domain, reg.IPAddr)
|
||||||
|
}
|
||||||
|
if watch.debug {
|
||||||
|
log.Info("would run updates", "subdomain", reg.Domain)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Info("updating registration", "subdomain", reg.Domain, "ip", reg.IPAddr)
|
||||||
|
dnsclient := gandi.NewLiveDNSClient(watch.conf)
|
||||||
|
log.Info("getting domain records", "domain", watch.domain, "subdomain", reg.Domain)
|
||||||
|
curr, err := dnsclient.GetDomainRecordsByName(watch.domain, reg.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting domain records: %w", err)
|
||||||
|
}
|
||||||
|
if len(curr) == 0 {
|
||||||
|
log.Info("no records found for subdomain, not forcing creationg", "subdomain", reg.Domain)
|
||||||
|
}
|
||||||
|
for _, v := range curr {
|
||||||
|
if v.RrsetType == "A" {
|
||||||
|
if slices.Contains(v.RrsetValues, reg.IPAddr) {
|
||||||
|
log.Info("not updating, already set", "host", reg.Domain, "ip", reg.IPAddr)
|
||||||
|
} else {
|
||||||
|
_, err := dnsclient.UpdateDomainRecordByNameAndType(watch.domain, reg.Domain, "A", 900, []string{reg.IPAddr})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error updating record %w", err)
|
||||||
|
}
|
||||||
|
log.Info("updating domain succesfully", "subdomain", reg.Domain)
|
||||||
|
}
|
||||||
|
toUp := watch.Tildes[token]
|
||||||
|
toUp.Update = false
|
||||||
|
watch.Tildes[token] = toUp
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
31
types.go
Normal file
31
types.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/go-gandi/go-gandi/config"
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Hostname string `yaml:"hostname"`
|
||||||
|
Secret string `yaml:"secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Users []User `yaml:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
IPAddr string `json:"ipaddr"`
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Registration struct {
|
||||||
|
IPAddr string
|
||||||
|
Domain string
|
||||||
|
Update bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Watch struct {
|
||||||
|
Tildes map[string]Registration
|
||||||
|
debug bool
|
||||||
|
|
||||||
|
domain string
|
||||||
|
conf config.Config
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user