122 lines
2.5 KiB
Go
122 lines
2.5 KiB
Go
package dns
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"sync"
|
|
|
|
"git.wzray.com/homelab/hivemind/internal/config"
|
|
"git.wzray.com/homelab/hivemind/internal/state"
|
|
"git.wzray.com/homelab/hivemind/internal/types"
|
|
"git.wzray.com/homelab/hivemind/internal/web/client"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
const hostsDir = "/etc/hosts.d/"
|
|
|
|
type Role struct {
|
|
state *state.RuntimeState
|
|
config config.DnsConfig
|
|
group sync.WaitGroup
|
|
}
|
|
|
|
func New(state *state.RuntimeState, config config.DnsConfig) *Role {
|
|
r := &Role{
|
|
state: state,
|
|
config: config,
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
func (r *Role) updateDnsmasq(filename string, data []byte) error {
|
|
if err := os.WriteFile(filename, data, 0644); err != nil {
|
|
return fmt.Errorf("write endpoint file %q: %w", filename, err)
|
|
}
|
|
|
|
if err := r.reload(); err != nil {
|
|
return fmt.Errorf("reload dnsmasq: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseState(state types.HostState) (string, []byte) {
|
|
var builder strings.Builder
|
|
|
|
for _, d := range state.Domains {
|
|
builder.WriteString(fmt.Sprintf("%s %s\n", state.Address, d))
|
|
}
|
|
|
|
return hostsDir + state.Hostname, []byte(builder.String())
|
|
}
|
|
|
|
func (r *Role) OnStartup(ctx context.Context) error {
|
|
r.group.Go(func() {
|
|
r.syncFromRegistry()
|
|
})
|
|
|
|
c := r.state.Registry.OnChanged()
|
|
r.group.Go(func() {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-c:
|
|
r.syncFromRegistry()
|
|
}
|
|
}
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Role) syncFromRegistry() {
|
|
for _, n := range r.state.Registry.ByRole(types.HostRole) {
|
|
state, err := client.Get[types.HostState](n.Endpoint, types.PathHostDns)
|
|
if err != nil {
|
|
log.Warn().Str("name", n.Hostname).Err(err).Msg("unable to get host config")
|
|
continue
|
|
}
|
|
|
|
filename, data := parseState(*state)
|
|
if err := r.updateDnsmasq(filename, data); err != nil {
|
|
log.Warn().Str("name", n.Hostname).Err(err).Msg("unable to update dnsmasq")
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *Role) OnShutdown() error {
|
|
r.group.Wait()
|
|
return nil
|
|
}
|
|
|
|
func (r *Role) reload() error {
|
|
var err error
|
|
|
|
if r.config.UseSystemd {
|
|
err = exec.Command("systemctl", "reload", "dnsmasq").Run()
|
|
} else {
|
|
err = exec.Command("/etc/init.d/dnsmasq", "reload").Run()
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (r *Role) onCallback(state types.HostState) (bool, error) {
|
|
filename, data := parseState(state)
|
|
|
|
if err := r.updateDnsmasq(filename, data); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (r *Role) RegisterHandlers(rg types.Registrator) {
|
|
rg.Register(types.PostEndpoint(types.PathDnsCallback, r.onCallback))
|
|
}
|