1
0
Fork 0

Compare commits

..

12 commits

9 changed files with 60 additions and 39 deletions

View file

@ -1,13 +1,19 @@
all: hivemind hivemind-lite all: hivemind hivemind-musl hivemind-lite
hivemind: hivemind:
go build -o build/hivemind ./cmd/hivemind go build -o build/hivemind ./cmd/hivemind
hivemind-musl:
CC=musl-gcc go build \
-ldflags="-linkmode external -extldflags '-static'" \
-o build/hivemind-musl \
./cmd/hivemind
hivemind-lite: hivemind-lite:
CC=musl-gcc go build \ CC=musl-gcc go build \
-ldflags="-linkmode external -extldflags '-static'" \ -ldflags="-linkmode external -extldflags '-static'" \
-o build/hivemind-lite \ -o build/hivemind-lite \
./cmd/hivemind-lite ./cmd/hivemind-lite
.phony: all hivemind hivemind-lite .phony: all hivemind hivemind-musl hivemind-lite

5
TODO.md Normal file
View file

@ -0,0 +1,5 @@
- nginx role
- don't make roles beg to keep them alive, instead wait for a timeout since the last keepalive message and then probe them
- think about choosing the master for the keepalive message (should be somewhat load-balanced)
- hivemind lite should not just print `hivemind-lite` lol
- different transport (maybe something like a custom binary protocol)

View file

@ -7,6 +7,7 @@ import (
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings"
"syscall" "syscall"
"git.wzray.com/homelab/hivemind/internal/config" "git.wzray.com/homelab/hivemind/internal/config"
@ -64,22 +65,22 @@ func init() {
} }
func main() { func main() {
config, err := config.FromFile(configFile) configuration, err := config.FromFile(configFile)
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("unable to read config file") log.Fatal().Err(err).Msg("unable to read config file")
} }
if err := config.Validate(); err != nil { if err := configuration.Validate(); err != nil {
log.Fatal().Err(err).Msg("invalid configuration") log.Fatal().Err(err).Msg("invalid configuration")
} }
zerolog.SetGlobalLevel(levelToZerolog(config.Node.LogLevel)) zerolog.SetGlobalLevel(levelToZerolog(configuration.Node.LogLevel))
self := types.NewNode( self := types.NewNode(
config.Node.Hostname, configuration.Node.Hostname,
config.Node.Address, configuration.Node.Address,
config.Node.Port, configuration.Node.Port,
config.Roles, configuration.Roles,
) )
filestore := registry.NewFileStorage(registryFile) filestore := registry.NewFileStorage(registryFile)
@ -88,29 +89,29 @@ func main() {
state := state.New(registry, self) state := state.New(registry, self)
nodeRole := node.New(state, config.Node) nodeRole := node.New(state, configuration.Node)
var builder middleware.MiddlewareBuilder var builder middleware.MiddlewareBuilder
middlewares := builder.Prepare() middlewares := builder.Prepare()
client.Init(middlewares) client.Init(middlewares)
listenAddr := fmt.Sprintf("%v:%v", config.Node.ListenOn, config.Node.Port) listenAddr := fmt.Sprintf("%v:%v", configuration.Node.ListenOn, configuration.Node.Port)
server := server.NewServer(listenAddr, middlewares) server := server.NewServer(listenAddr, middlewares)
roles := make([]roles.Role, 0) roles := make([]roles.Role, 0)
roles = append(roles, nodeRole) roles = append(roles, nodeRole)
for _, role := range config.Roles { for _, role := range configuration.Roles {
switch role { switch role {
case types.MasterRole: case types.MasterRole:
role := master.New(state, config.Configs.Master) role := master.New(state, configuration.Configs.Master)
roles = append(roles, role) roles = append(roles, role)
case types.DnsRole: case types.DnsRole:
role := dns.New(state, config.Configs.Dns) role := dns.New(state, configuration.Configs.Dns)
roles = append(roles, role) roles = append(roles, role)
case types.HostRole: case types.HostRole:
role := host.New(state, config.Configs.Host) role := host.New(state, configuration.Configs.Host)
roles = append(roles, role) roles = append(roles, role)
} }
} }
@ -125,7 +126,12 @@ func main() {
serverError <- server.Listen() serverError <- server.Listen()
}() }()
if err := nodeRole.Join(config.Node.BootstrapMaster); err != nil { bootstrapMaster := configuration.Node.BootstrapMaster
if !strings.Contains(bootstrapMaster, ":") {
bootstrapMaster += fmt.Sprintf(":%d", config.DefaultConfig.Node.Port)
}
if err := nodeRole.Join(bootstrapMaster); err != nil {
log.Warn().Err(err).Msg("unable to join") log.Warn().Err(err).Msg("unable to join")
} else { } else {
log.Info().Msg("joined") log.Info().Msg("joined")

View file

@ -40,7 +40,7 @@ func FromFile(filename string) (Config, error) {
return Config{}, fmt.Errorf("parse config file: %w", err) return Config{}, fmt.Errorf("parse config file: %w", err)
} }
config := defaultConfig config := DefaultConfig
config.Node.Merge(temp.Node) config.Node.Merge(temp.Node)
if c := temp.Configs.Master; c != nil { if c := temp.Configs.Master; c != nil {

View file

@ -1,17 +1,17 @@
package config package config
var defaultConfig = Config{ var DefaultConfig = Config{
Node: NodeConfig{ Node: NodeConfig{
ListenOn: "0.0.0.0", ListenOn: "0.0.0.0",
Port: 56714, Port: 56714,
KeepaliveInterval: 1, KeepaliveInterval: 10,
LogLevel: LogLevelInfo, LogLevel: LogLevelInfo,
}, },
Configs: Configs{ Configs: Configs{
Master: MasterConfig{ Master: MasterConfig{
ObserverInterval: 10, ObserverInterval: 120,
BackoffSeconds: 2, BackoffSeconds: 1,
BackoffCount: 3, BackoffCount: 4,
}, },
}, },
} }

View file

@ -93,7 +93,12 @@ func (r *Role) getInternal() (types.HostState, error) {
} }
func (r *Role) getExternal() (types.HostState, error) { func (r *Role) getExternal() (types.HostState, error) {
return types.HostState{}, nil return types.HostState{
Domains: r.externalDomains,
Address: r.config.IpAddress,
Hostname: r.state.Self.Hostname,
}, nil
} }
func (r *Role) RegisterHandlers(rg types.Registrator) { func (r *Role) RegisterHandlers(rg types.Registrator) {

View file

@ -39,7 +39,7 @@ func (o *observer) pollNodes(ctx context.Context, onLeave func(types.Node) error
delay := time.Duration(o.backoff) delay := time.Duration(o.backoff)
alive := false alive := false
for i := o.backoffCount; i > 0; i-- { for i := o.backoffCount - 1; i >= 0; i-- {
_, err := client.Get[any](n.Endpoint, types.PathNodeHealthcheck) _, err := client.Get[any](n.Endpoint, types.PathNodeHealthcheck)
if err == nil { if err == nil {

View file

@ -3,6 +3,7 @@ package node
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"sync" "sync"
"time" "time"
@ -40,25 +41,28 @@ func (r *Role) Join(bootstrap string) error {
return errors.New("no masters configured") return errors.New("no masters configured")
} }
var errs []error
for m := range masters { for m := range masters {
logger := log.With().Str("host", m).Logger() logger := log.With().Str("host", m).Logger()
logger.Debug().Msg("trying to join via master") logger.Debug().Msg("trying to join via master")
nodes, err := client.Post[[]types.Node](m, "/master/join", r.state.Self) nodes, err := client.Post[[]types.Node](m, "/master/join", r.state.Self)
if err != nil { if err != nil {
errs = append(errs, err)
logger.Debug().Err(err).Msg("unable to join") logger.Debug().Err(err).Msg("unable to join")
continue continue
} }
if err := r.state.Registry.Set(*nodes); err != nil { if err := r.state.Registry.Set(*nodes); err != nil {
logger.Debug().Err(err).Msg("unable to set master's nodes") logger.Debug().Err(err).Msg("unable to set master's nodes")
errs = append(errs, err)
continue continue
} }
return nil return nil
} }
return errors.New("unable to join") return fmt.Errorf("unable to join with any master: %w", errors.Join(errs...))
} }
func (r *Role) Leave() error { func (r *Role) Leave() error {
@ -67,7 +71,7 @@ func (r *Role) Leave() error {
return nil return nil
} }
sent := false var errs []error
for _, m := range masters { for _, m := range masters {
logger := log.With().Str("name", m.Hostname).Logger() logger := log.With().Str("name", m.Hostname).Logger()
logger.Debug().Msg("sending leave message") logger.Debug().Msg("sending leave message")
@ -75,20 +79,16 @@ func (r *Role) Leave() error {
_, err := client.Post[any](m.Endpoint, types.PathMasterLeave, r.state.Self) _, err := client.Post[any](m.Endpoint, types.PathMasterLeave, r.state.Self)
if err != nil { if err != nil {
logger.Debug().Err(err).Msg("unable to send leave message") logger.Debug().Err(err).Msg("unable to send leave message")
errs = append(errs, err)
continue continue
} else { } else {
sent = true
logger.Debug().Msg("leave message sent") logger.Debug().Msg("leave message sent")
break
}
}
if !sent {
return errors.New("unable to send leave message")
}
return nil return nil
} }
}
return fmt.Errorf("unable to send leave message to any master: %w", errors.Join(errs...))
}
func (r *Role) OnStartup(ctx context.Context) error { func (r *Role) OnStartup(ctx context.Context) error {
if r.config.KeepaliveInterval != -1 { if r.config.KeepaliveInterval != -1 {

View file

@ -1 +0,0 @@
{"last_update":1768661288311,"nodes":{"ext.zvray.ru":{"address":"10.160.0.1:56714","name":"ext.zvray.ru","roles":["dns"]},"laptop.spb.wzray.com":{"address":"laptop.spb.wzray.com:56714","name":"laptop.spb.wzray.com","roles":["master","dns","host"]}}}