package host import ( "context" "fmt" "slices" "sync" "git.wzray.com/homelab/hivemind/internal/app" "git.wzray.com/homelab/hivemind/internal/config" "git.wzray.com/homelab/hivemind/internal/transport" "git.wzray.com/homelab/hivemind/internal/transport/host" "git.wzray.com/homelab/hivemind/internal/types" "github.com/rs/zerolog/log" ) type Role struct { state *app.State config config.HostConfig gateway *TraefikGateway tasksGroup sync.WaitGroup externalDomains []string // TODO: i don't like hardcoding external/internal logic here internalDomains []string lock sync.RWMutex } func New(state *app.State, config config.HostConfig) *Role { r := &Role{ state: state, config: config, } r.gateway = NewTraefikGateway(config, r) return r } func (r *Role) sendUpdate(domains []string, role types.Role) { state := types.HostState{ Domains: domains, Address: r.config.IpAddress, Hostname: r.state.Self.Hostname, } for _, node := range r.state.Registry.ByRole(role) { r.tasksGroup.Go(func() { logger := log.With().Str("name", node.Hostname).Logger() logger.Debug().Msg("sending update") if _, err := r.state.Clients.DNS.Callback(node.Endpoint, state); err != nil { logger.Warn().Err(err).Msg("unable to send dns info") } else { logger.Debug().Msg("update sent") } }) } } func (r *Role) OnTraefikUpdate(resp traefikResponse) { r.lock.Lock() defer r.lock.Unlock() newInternal := resp.Domains(r.config.InternalEntrypoint) newExternal := resp.Domains(r.config.ExternalEntrypoint) if !slices.Equal(newInternal, r.internalDomains) { log.Info().Msg("internal domains updated, propogating") r.internalDomains = newInternal r.sendUpdate(newInternal, types.DnsRole) } if !slices.Equal(newExternal, r.externalDomains) { log.Info().Msg("internal domains updated, propogating") r.externalDomains = newExternal r.sendUpdate(newExternal, types.NameserverRole) } } func (r *Role) Dns() (types.HostState, error) { r.lock.RLock() defer r.lock.RUnlock() return types.HostState{ Domains: r.internalDomains, Address: r.config.IpAddress, Hostname: r.state.Self.Hostname, }, nil } func (r *Role) Nameserver() (types.HostState, error) { r.lock.RLock() defer r.lock.RUnlock() return types.HostState{ Domains: r.externalDomains, Address: r.config.IpAddress, Hostname: r.state.Self.Hostname, }, nil } func (r *Role) RegisterHandlers(rg transport.Registrator) { host.Register(rg, r) } func (r *Role) OnStartup(ctx context.Context) error { r.tasksGroup.Go(func() { if err := r.gateway.Listen(); err != nil { log.Err(err).Msg("traefik gateway stopped") } }) r.tasksGroup.Go(func() { <-ctx.Done() if err := r.gateway.Shutdown(context.Background()); err != nil { log.Err(err).Msg("failed to shutdown traefik gateway") } }) resp, err := r.gateway.GetRawData() if err != nil { return fmt.Errorf("get traefik state: %w", err) } log.Info().Msg("got raw data from traefik") log.Debug().Interface("response", resp).Send() r.OnTraefikUpdate(*resp) return nil } func (r *Role) OnShutdown() error { r.tasksGroup.Wait() return nil }