129 lines
2.9 KiB
Go
129 lines
2.9 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
"slices"
|
||
|
"time"
|
||
|
|
||
|
"github.com/charmbracelet/log"
|
||
|
"github.com/go-chi/chi/v5"
|
||
|
"github.com/go-chi/chi/v5/middleware"
|
||
|
"github.com/go-gandi/go-gandi"
|
||
|
"github.com/go-gandi/go-gandi/config"
|
||
|
)
|
||
|
|
||
|
type Tilde struct {
|
||
|
regs []Registration
|
||
|
debug bool
|
||
|
|
||
|
domain string
|
||
|
conf config.Config
|
||
|
}
|
||
|
|
||
|
var errRegNotFound = errors.New("registration not found")
|
||
|
|
||
|
func (t *Tilde) Add(r Registration) error {
|
||
|
if !r.Valid() {
|
||
|
return errors.New("invalid registration")
|
||
|
}
|
||
|
_, err := r.Update(t.conf)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
t.regs = append(t.regs, r)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (t *Tilde) UpdateReg(secret string, ip string) error {
|
||
|
index := slices.IndexFunc(t.regs, func(ele Registration) bool { return ele.HasSecret(secret) })
|
||
|
if index == -1 {
|
||
|
return errRegNotFound
|
||
|
}
|
||
|
reg, ok := t.regs[index].(*UserRegistration)
|
||
|
if !ok {
|
||
|
return errors.New("error getting user reg")
|
||
|
}
|
||
|
reg.ip = ip
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (t *Tilde) Sync(cnf config.Config) error {
|
||
|
for _, reg := range t.regs {
|
||
|
result, err := reg.Update(cnf)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if result != "" {
|
||
|
log.Info("updating registration", "subdomain", reg.Domain(), "ip", reg.IP())
|
||
|
dnsclient := gandi.NewLiveDNSClient(cnf)
|
||
|
log.Info("getting domain records")
|
||
|
curr, err := dnsclient.GetDomainRecordsByName(t.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 creation", "subdomain", reg.Domain())
|
||
|
}
|
||
|
for _, v := range curr {
|
||
|
if v.RrsetType == "A" {
|
||
|
if slices.Contains(v.RrsetValues, reg.IP()) {
|
||
|
log.Info("not updating, already set", "host", reg.Domain(), "ip", reg.IP())
|
||
|
} else {
|
||
|
_, err := dnsclient.UpdateDomainRecordByNameAndType(t.domain, reg.Domain(), "A", 900, []string{reg.IP()})
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("error updating record %w", err)
|
||
|
}
|
||
|
log.Info("updating domain successfully", "subdomain", reg.Domain())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (watch *Tilde) 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
|
||
|
}
|
||
|
err = watch.UpdateReg(payload.Secret, payload.IPAddr)
|
||
|
if err != nil {
|
||
|
if errors.Is(err, errRegNotFound) {
|
||
|
w.WriteHeader(http.StatusUnauthorized)
|
||
|
}
|
||
|
w.WriteHeader(http.StatusInternalServerError)
|
||
|
return
|
||
|
}
|
||
|
w.WriteHeader(http.StatusOK)
|
||
|
})
|
||
|
return r
|
||
|
}
|
||
|
|
||
|
func (watch *Tilde) run(ctx context.Context) error {
|
||
|
ticker := time.NewTicker(5 * time.Second)
|
||
|
for {
|
||
|
select {
|
||
|
case <-ticker.C:
|
||
|
err := watch.Sync(watch.conf)
|
||
|
if err != nil {
|
||
|
log.Warn(err)
|
||
|
}
|
||
|
case <-ctx.Done():
|
||
|
ticker.Stop()
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
}
|