diff --git a/README.md b/README.md index 67075d8..da394a4 100644 --- a/README.md +++ b/README.md @@ -121,10 +121,19 @@ cluster: ### Environment Variables -| Environment Variable | Description | Default | -|----------------------|-----------------------------------------|---------------| -| RCOND_ADDR | Address to bind the HTTP server to. | 0.0.0.0:8080 | -| RCOND_API_TOKEN | API token to use for authentication. | N/A | +| Environment Variable | Description | Default | +|------------------------------|------------------------------------------|----------------| +| HOSTNAME | Hostname to be set at startup. | N/A | +| RCOND_ADDR | Address to bind the HTTP server to. | 0.0.0.0:8080 | +| RCOND_API_TOKEN | API token to use for authentication. | N/A | +| RCOND_CLUSTER_ENABLED | Enable the cluster agent. | false | +| RCOND_CLUSTER_NODE_NAME | Name of the node in the cluster. | rcond | +| RCOND_CLUSTER_SECRET_KEY | Secret key for the cluster agent. | N/A | +| RCOND_CLUSTER_ADVERTISE_ADDR | Advertise address for the cluster agent. | 0.0.0.0 | +| RCOND_CLUSTER_ADVERTISE_PORT | Advertise port for the cluster agent. | 7946 | +| RCOND_CLUSTER_BIND_ADDR | Bind address for the cluster agent. | 0.0.0.0 | +| RCOND_CLUSTER_BIND_PORT | Bind port for the cluster agent. | 7946 | +| RCOND_CLUSTER_JOIN | Join addresses for the cluster agent. | 127.0.0.1:7947 | ## API diff --git a/cmd/rcond/main.go b/cmd/rcond/main.go index 80d9de5..b9a557c 100644 --- a/cmd/rcond/main.go +++ b/cmd/rcond/main.go @@ -5,15 +5,10 @@ package main import ( "flag" "fmt" - "log" "os" - "github.com/0x1d/rcond/pkg/cluster" "github.com/0x1d/rcond/pkg/config" - http "github.com/0x1d/rcond/pkg/http" - "github.com/0x1d/rcond/pkg/network" - "github.com/0x1d/rcond/pkg/system" - "github.com/godbus/dbus/v5" + "github.com/0x1d/rcond/pkg/rcond" ) func usage() { @@ -29,9 +24,7 @@ func main() { os.Exit(1) } - configureSystem(appConfig) - clusterAgent := startClusterAgent(appConfig) - startApiServer(appConfig, clusterAgent) + rcond.NewNode(appConfig).Up() select {} } @@ -42,8 +35,6 @@ func loadConfig() (*config.Config, error) { help := false flag.StringVar(&configPath, "config", configPath, "Path to the configuration file") - flag.StringVar(&appConfig.Rcond.Addr, "addr", "", "Address to bind the HTTP server to") - flag.StringVar(&appConfig.Rcond.ApiToken, "token", "", "API token to use for authentication") flag.BoolVar(&help, "help", false, "Show help") flag.Parse() @@ -61,17 +52,6 @@ func loadConfig() (*config.Config, error) { appConfig = configFile } - // Override config values from environment variables and flags - overrideConfigValuesFromEnv(map[string]*string{ - "RCOND_ADDR": &appConfig.Rcond.Addr, - "RCOND_API_TOKEN": &appConfig.Rcond.ApiToken, - }) - - overrideConfigValuesFromFlag(map[string]*string{ - "addr": &appConfig.Rcond.Addr, - "token": &appConfig.Rcond.ApiToken, - }) - // Validate required fields if err := validateRequiredFields(map[string]*string{ "addr": &appConfig.Rcond.Addr, @@ -83,88 +63,6 @@ func loadConfig() (*config.Config, error) { return appConfig, nil } -func startApiServer(appConfig *config.Config, clusterAgent *cluster.Agent) *http.Server { - srv := http.NewServer(appConfig) - srv.WithClusterAgent(clusterAgent) - srv.RegisterRoutes() - - log.Printf("[INFO] Starting API server on %s", appConfig.Rcond.Addr) - if err := srv.Start(); err != nil { - log.Fatal(err) - } - return srv -} - -func startClusterAgent(appConfig *config.Config) *cluster.Agent { - clusterConfig := &appConfig.Cluster - if clusterConfig.Enabled { - log.Printf("[INFO] Starting cluster agent on %s:%d", clusterConfig.BindAddr, clusterConfig.BindPort) - clusterAgent, err := cluster.NewAgent(clusterConfig, cluster.ClusterEventsMap()) - if err != nil { - log.Fatal(err) - } - // join nodes in the cluster if the join addresses are provided - if len(clusterConfig.Join) > 0 { - clusterAgent.Join(clusterConfig.Join, true) - } - return clusterAgent - } - return nil -} - -func configureSystem(appConfig *config.Config) error { - log.Print("[INFO] Configure system") - // configure hostname - if err := network.SetHostname(appConfig.Hostname); err != nil { - log.Printf("[ERROR] %s", err) - } - // configure network connections - for _, connection := range appConfig.Network.Connections { - err := system.WithDbus(func(conn *dbus.Conn) error { - _, err := network.AddConnectionWithConfig(conn, &network.ConnectionConfig{ - Type: connection.Type, - UUID: connection.UUID, - ID: connection.ID, - AutoConnect: connection.AutoConnect, - SSID: connection.SSID, - Mode: connection.Mode, - Band: connection.Band, - Channel: connection.Channel, - KeyMgmt: connection.KeyMgmt, - PSK: connection.PSK, - IPv4Method: connection.IPv4Method, - IPv6Method: connection.IPv6Method, - }) - if err != nil { - return err - } - return nil - }) - if err != nil { - log.Printf("[ERROR] %s", err) - } - - } - log.Print("[INFO] System configured") - return nil -} - -func overrideConfigValuesFromEnv(envMap map[string]*string) { - for varName, configValue := range envMap { - if envValue, ok := os.LookupEnv(varName); ok { - *configValue = envValue - } - } -} - -func overrideConfigValuesFromFlag(flagMap map[string]*string) { - for flagName, configValue := range flagMap { - if flagValue := flag.Lookup(flagName).Value.String(); flagValue != "" { - *configValue = flagValue - } - } -} - func validateRequiredFields(fields map[string]*string) error { for name, value := range fields { if *value == "" { diff --git a/go.mod b/go.mod index 98528f2..3e4ece3 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/hashicorp/logutils v1.0.0 github.com/hashicorp/serf v0.10.2 + github.com/kelseyhightower/envconfig v1.4.0 github.com/stretchr/testify v1.10.0 golang.org/x/crypto v0.37.0 gopkg.in/yaml.v3 v3.0.1 @@ -29,11 +30,13 @@ require ( github.com/hashicorp/go-sockaddr v1.0.5 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/memberlist v0.5.2 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/miekg/dns v1.1.56 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect - golang.org/x/mod v0.13.0 // indirect - golang.org/x/net v0.21.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sync v0.13.0 // indirect golang.org/x/sys v0.32.0 // indirect - golang.org/x/tools v0.14.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect ) diff --git a/go.sum b/go.sum index da7c50e..81aa3dc 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,7 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -83,14 +84,18 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= @@ -102,6 +107,7 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -124,6 +130,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -142,23 +150,23 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -178,8 +186,8 @@ golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/pkg/cluster/agent.go b/pkg/cluster/agent.go index d2caef9..59a50f9 100644 --- a/pkg/cluster/agent.go +++ b/pkg/cluster/agent.go @@ -54,6 +54,23 @@ func NewAgent(clusterConfig *config.ClusterConfig, clusterEvents map[string]func return &Agent{Serf: serf}, nil } +func Up(clusterConfig *config.ClusterConfig) (*Agent, error) { + if clusterConfig.Enabled { + log.Printf("[INFO] Starting cluster agent on %s:%d", clusterConfig.BindAddr, clusterConfig.BindPort) + clusterAgent, err := NewAgent(clusterConfig, ClusterEventsMap()) + if err != nil { + log.Print(err) + return nil, err + } + // join nodes in the cluster if the join addresses are provided + if len(clusterConfig.Join) > 0 { + clusterAgent.Join(clusterConfig.Join, true) + } + return clusterAgent, nil + } + return nil, nil +} + // Event sends a custom event to the Serf cluster. // It marshals the provided ClusterEvent into JSON and then uses Serf's UserEvent method to send the event. func (a *Agent) Event(event ClusterEvent) error { diff --git a/pkg/config/config.go b/pkg/config/config.go index c15787e..8aec0a8 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -3,19 +3,20 @@ package config import ( "os" + "github.com/kelseyhightower/envconfig" "gopkg.in/yaml.v3" ) type Config struct { - Hostname string `yaml:"hostname"` + Hostname string `yaml:"hostname" envconfig:"HOSTNAME"` Rcond RcondConfig `yaml:"rcond"` Network NetworkConfig `yaml:"network"` Cluster ClusterConfig `yaml:"cluster"` } type RcondConfig struct { - Addr string `yaml:"addr"` - ApiToken string `yaml:"api_token"` + Addr string `yaml:"addr" envconfig:"RCOND_ADDR"` + ApiToken string `yaml:"api_token" envconfig:"RCOND_API_TOKEN"` } type NetworkConfig struct { @@ -38,28 +39,35 @@ type ConnectionConfig struct { } type ClusterConfig struct { - Enabled bool `yaml:"enabled"` - NodeName string `yaml:"node_name"` - SecretKey string `yaml:"secret_key"` - Join []string `yaml:"join"` - AdvertiseAddr string `yaml:"advertise_addr"` - AdvertisePort int `yaml:"advertise_port"` - BindAddr string `yaml:"bind_addr"` - BindPort int `yaml:"bind_port"` - LogLevel string `yaml:"log_level"` + Enabled bool `yaml:"enabled" envconfig:"CLUSTER_ENABLED"` + NodeName string `yaml:"node_name" envconfig:"CLUSTER_NODE_NAME"` + SecretKey string `yaml:"secret_key" envconfig:"CLUSTER_SECRET_KEY"` + Join []string `yaml:"join" envconfig:"CLUSTER_JOIN"` + AdvertiseAddr string `yaml:"advertise_addr" envconfig:"CLUSTER_ADVERTISE_ADDR"` + AdvertisePort int `yaml:"advertise_port" envconfig:"CLUSTER_ADVERTISE_PORT"` + BindAddr string `yaml:"bind_addr" envconfig:"CLUSTER_BIND_ADDR"` + BindPort int `yaml:"bind_port" envconfig:"CLUSTER_BIND_PORT"` + LogLevel string `yaml:"log_level" envconfig:"CLUSTER_LOG_LEVEL"` } -func LoadConfig(path string) (*Config, error) { - yamlFile, err := os.ReadFile(path) +// LoadConfig reads the configuration from a YAML file and environment variables. +func LoadConfig(filename string) (*Config, error) { + var config Config + data, err := os.ReadFile(filename) if err != nil { return nil, err } - var config Config - err = yaml.Unmarshal(yamlFile, &config) + err = yaml.Unmarshal(data, &config) if err != nil { return nil, err } + + err = envconfig.Process("", &config) + if err != nil { + return nil, err + } + return &config, nil } diff --git a/pkg/http/server.go b/pkg/http/server.go index 5aa23c0..bcd7bde 100644 --- a/pkg/http/server.go +++ b/pkg/http/server.go @@ -3,6 +3,7 @@ package http import ( "context" "encoding/json" + "log" "net/http" "time" @@ -44,6 +45,18 @@ func (s *Server) WithClusterAgent(agent *cluster.Agent) *Server { return s } +func Up(appConfig *config.Config, clusterAgent *cluster.Agent) *Server { + srv := NewServer(appConfig) + srv.WithClusterAgent(clusterAgent) + srv.RegisterRoutes() + + log.Printf("[INFO] Starting API server on %s", appConfig.Rcond.Addr) + if err := srv.Start(); err != nil { + log.Fatal(err) + } + return srv +} + func (s *Server) Start() error { return s.srv.ListenAndServe() } diff --git a/pkg/network/network.go b/pkg/network/network.go index d0c4c06..80f6199 100644 --- a/pkg/network/network.go +++ b/pkg/network/network.go @@ -6,7 +6,7 @@ import ( "os" "time" - "github.com/0x1d/rcond/pkg/system" + "github.com/0x1d/rcond/pkg/util" "github.com/godbus/dbus/v5" "github.com/google/uuid" ) @@ -334,7 +334,7 @@ func GetHostname() (string, error) { // SetHostname changes the static hostname via the system bus. // newHost is your desired hostname, interactive=false skips any prompt. func SetHostname(newHost string) error { - return system.WithDbus(func(conn *dbus.Conn) error { + return util.WithConnection(func(conn *dbus.Conn) error { obj := conn.Object( "org.freedesktop.hostname1", dbus.ObjectPath("/org/freedesktop/hostname1"), @@ -355,7 +355,7 @@ func SetHostname(newHost string) error { func ConfigureSTA(iface string, ssid string, password string, autoconnect bool) (string, error) { uuid := uuid.New() - err := system.WithDbus(func(conn *dbus.Conn) error { + err := util.WithConnection(func(conn *dbus.Conn) error { _, err := AddStationConnection(conn, uuid, ssid, password, autoconnect) if err != nil { return fmt.Errorf("failed to create station connection: %v", err) @@ -377,7 +377,7 @@ func ConfigureSTA(iface string, ssid string, password string, autoconnect bool) func ConfigureAP(iface string, ssid string, password string, autoconnect bool) (string, error) { uuid := uuid.New() - err := system.WithDbus(func(conn *dbus.Conn) error { + err := util.WithConnection(func(conn *dbus.Conn) error { _, err := AddAccessPointConnection(conn, uuid, ssid, password, autoconnect) if err != nil { return fmt.Errorf("failed to create access point connection: %v", err) @@ -398,7 +398,7 @@ func ConfigureAP(iface string, ssid string, password string, autoconnect bool) ( // The connection will be activated on the specified interface. // Returns an error if any operation fails. func Up(iface string, uuid string) error { - return system.WithDbus(func(conn *dbus.Conn) error { + return util.WithConnection(func(conn *dbus.Conn) error { connPath, err := GetConnectionPath(conn, uuid) if err != nil { return err @@ -428,7 +428,7 @@ func Up(iface string, uuid string) error { // It takes the interface name as an argument. // Returns an error if the device cannot be found or disconnected. func Down(iface string) error { - return system.WithDbus(func(conn *dbus.Conn) error { + return util.WithConnection(func(conn *dbus.Conn) error { devPath, err := GetDeviceByIpIface(conn, iface) if err != nil { return err @@ -446,7 +446,7 @@ func Down(iface string) error { // If no connection with the UUID exists, it returns nil. // Returns an error if the connection exists but cannot be deleted. func Remove(uuid string) error { - return system.WithDbus(func(conn *dbus.Conn) error { + return util.WithConnection(func(conn *dbus.Conn) error { connPath, err := GetConnectionPath(conn, uuid) if err != nil { return err diff --git a/pkg/rcond/node.go b/pkg/rcond/node.go new file mode 100644 index 0000000..dad81e2 --- /dev/null +++ b/pkg/rcond/node.go @@ -0,0 +1,57 @@ +package rcond + +import ( + "log" + + "github.com/0x1d/rcond/pkg/cluster" + "github.com/0x1d/rcond/pkg/config" + "github.com/0x1d/rcond/pkg/http" + "github.com/0x1d/rcond/pkg/system" +) + +type Node struct { + Config *config.Config + ClusterAgent *cluster.Agent + HttpApi *http.Server +} + +func NewNode(appConfig *config.Config) *Node { + return &Node{ + Config: appConfig, + HttpApi: Api(appConfig), + ClusterAgent: Cluster(&appConfig.Cluster), + } +} + +func (n *Node) Up() { + system.Configure(n.Config) + n.HttpApi.WithClusterAgent(n.ClusterAgent) + n.HttpApi.RegisterRoutes() + + log.Printf("[INFO] Starting API server on %s", n.Config.Rcond.Addr) + if err := n.HttpApi.Start(); err != nil { + log.Fatal(err) + } +} + +func Api(appConfig *config.Config) *http.Server { + srv := http.NewServer(appConfig) + return srv +} + +func Cluster(clusterConfig *config.ClusterConfig) *cluster.Agent { + if clusterConfig.Enabled { + log.Printf("[INFO] Starting cluster agent on %s:%d", clusterConfig.BindAddr, clusterConfig.BindPort) + clusterAgent, err := cluster.NewAgent(clusterConfig, cluster.ClusterEventsMap()) + if err != nil { + log.Print(err) + return nil + } + // join nodes in the cluster if the join addresses are provided + if len(clusterConfig.Join) > 0 { + clusterAgent.Join(clusterConfig.Join, true) + } + return clusterAgent + } + return nil +} diff --git a/pkg/system/init.go b/pkg/system/init.go new file mode 100644 index 0000000..f42f905 --- /dev/null +++ b/pkg/system/init.go @@ -0,0 +1,47 @@ +package system + +import ( + "log" + + "github.com/0x1d/rcond/pkg/config" + "github.com/0x1d/rcond/pkg/network" + "github.com/0x1d/rcond/pkg/util" + "github.com/godbus/dbus/v5" +) + +func Configure(appConfig *config.Config) error { + log.Print("[INFO] Configure system") + // configure hostname + if err := network.SetHostname(appConfig.Hostname); err != nil { + log.Printf("[ERROR] setting hostname failed: %s", err) + } + // configure network connections + for _, connection := range appConfig.Network.Connections { + err := util.WithConnection(func(conn *dbus.Conn) error { + _, err := network.AddConnectionWithConfig(conn, &network.ConnectionConfig{ + Type: connection.Type, + UUID: connection.UUID, + ID: connection.ID, + AutoConnect: connection.AutoConnect, + SSID: connection.SSID, + Mode: connection.Mode, + Band: connection.Band, + Channel: connection.Channel, + KeyMgmt: connection.KeyMgmt, + PSK: connection.PSK, + IPv4Method: connection.IPv4Method, + IPv6Method: connection.IPv6Method, + }) + if err != nil { + return err + } + return nil + }) + if err != nil { + log.Printf("[ERROR] configuring connections failed: %s", err) + } + + } + log.Print("[INFO] System configured") + return nil +} diff --git a/pkg/system/state.go b/pkg/system/state.go index 8de4ff1..70b2044 100644 --- a/pkg/system/state.go +++ b/pkg/system/state.go @@ -3,12 +3,13 @@ package system import ( "log" + "github.com/0x1d/rcond/pkg/util" "github.com/godbus/dbus/v5" ) // Restart restarts the system. func Restart() error { - return WithDbus(func(conn *dbus.Conn) error { + return util.WithConnection(func(conn *dbus.Conn) error { obj := conn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1") log.Println("Rebooting system...") call := obj.Call("org.freedesktop.systemd1.Manager.Reboot", 0) @@ -21,7 +22,7 @@ func Restart() error { // Shutdown shuts down the system. func Shutdown() error { - return WithDbus(func(conn *dbus.Conn) error { + return util.WithConnection(func(conn *dbus.Conn) error { obj := conn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1") log.Println("Shutting down system...") call := obj.Call("org.freedesktop.systemd1.Manager.PowerOff", 0) diff --git a/pkg/system/dbus.go b/pkg/util/dbus.go similarity index 71% rename from pkg/system/dbus.go rename to pkg/util/dbus.go index 60f59e0..50750f1 100644 --- a/pkg/system/dbus.go +++ b/pkg/util/dbus.go @@ -1,4 +1,4 @@ -package system +package util import ( "log" @@ -6,9 +6,9 @@ import ( "github.com/godbus/dbus/v5" ) -// WithDbus executes the given function with a D-Bus system connection +// WithConnection executes the given function with a D-Bus system connection // and handles any connection errors -func WithDbus(fn func(*dbus.Conn) error) error { +func WithConnection(fn func(*dbus.Conn) error) error { conn, err := dbus.SystemBus() if err != nil { log.Printf("[ERROR] Failed to connect to system bus: %v", err)