feat: improve ENV variable overrides, introduce Node struct, refactoring

This commit is contained in:
2025-05-20 11:04:23 +02:00
parent bfc82870c3
commit d0a478d172
12 changed files with 210 additions and 149 deletions

View File

@@ -121,10 +121,19 @@ cluster:
### Environment Variables ### Environment Variables
| Environment Variable | Description | Default | | Environment Variable | Description | Default |
|----------------------|-----------------------------------------|---------------| |------------------------------|------------------------------------------|----------------|
| RCOND_ADDR | Address to bind the HTTP server to. | 0.0.0.0:8080 | | HOSTNAME | Hostname to be set at startup. | N/A |
| RCOND_API_TOKEN | API token to use for authentication. | 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 ## API

View File

@@ -5,15 +5,10 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"log"
"os" "os"
"github.com/0x1d/rcond/pkg/cluster"
"github.com/0x1d/rcond/pkg/config" "github.com/0x1d/rcond/pkg/config"
http "github.com/0x1d/rcond/pkg/http" "github.com/0x1d/rcond/pkg/rcond"
"github.com/0x1d/rcond/pkg/network"
"github.com/0x1d/rcond/pkg/system"
"github.com/godbus/dbus/v5"
) )
func usage() { func usage() {
@@ -29,9 +24,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
configureSystem(appConfig) rcond.NewNode(appConfig).Up()
clusterAgent := startClusterAgent(appConfig)
startApiServer(appConfig, clusterAgent)
select {} select {}
} }
@@ -42,8 +35,6 @@ func loadConfig() (*config.Config, error) {
help := false help := false
flag.StringVar(&configPath, "config", configPath, "Path to the configuration file") 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.BoolVar(&help, "help", false, "Show help")
flag.Parse() flag.Parse()
@@ -61,17 +52,6 @@ func loadConfig() (*config.Config, error) {
appConfig = configFile 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 // Validate required fields
if err := validateRequiredFields(map[string]*string{ if err := validateRequiredFields(map[string]*string{
"addr": &appConfig.Rcond.Addr, "addr": &appConfig.Rcond.Addr,
@@ -83,88 +63,6 @@ func loadConfig() (*config.Config, error) {
return appConfig, nil 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 { func validateRequiredFields(fields map[string]*string) error {
for name, value := range fields { for name, value := range fields {
if *value == "" { if *value == "" {

9
go.mod
View File

@@ -12,6 +12,7 @@ require (
github.com/gorilla/mux v1.8.1 github.com/gorilla/mux v1.8.1
github.com/hashicorp/logutils v1.0.0 github.com/hashicorp/logutils v1.0.0
github.com/hashicorp/serf v0.10.2 github.com/hashicorp/serf v0.10.2
github.com/kelseyhightower/envconfig v1.4.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
golang.org/x/crypto v0.37.0 golang.org/x/crypto v0.37.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
@@ -29,11 +30,13 @@ require (
github.com/hashicorp/go-sockaddr v1.0.5 // indirect github.com/hashicorp/go-sockaddr v1.0.5 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/memberlist v0.5.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/miekg/dns v1.1.56 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
golang.org/x/mod v0.13.0 // indirect golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.21.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/sys v0.32.0 // indirect
golang.org/x/tools v0.14.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
) )

28
go.sum
View File

@@ -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/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/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/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/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.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 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.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/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/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.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/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.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/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 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= 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/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 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 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.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/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= 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.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.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 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 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 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= 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.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 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 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.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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-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-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-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-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-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.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 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/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-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-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-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.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.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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-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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= 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= 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/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=

View File

@@ -54,6 +54,23 @@ func NewAgent(clusterConfig *config.ClusterConfig, clusterEvents map[string]func
return &Agent{Serf: serf}, nil 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. // 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. // 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 { func (a *Agent) Event(event ClusterEvent) error {

View File

@@ -3,19 +3,20 @@ package config
import ( import (
"os" "os"
"github.com/kelseyhightower/envconfig"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type Config struct { type Config struct {
Hostname string `yaml:"hostname"` Hostname string `yaml:"hostname" envconfig:"HOSTNAME"`
Rcond RcondConfig `yaml:"rcond"` Rcond RcondConfig `yaml:"rcond"`
Network NetworkConfig `yaml:"network"` Network NetworkConfig `yaml:"network"`
Cluster ClusterConfig `yaml:"cluster"` Cluster ClusterConfig `yaml:"cluster"`
} }
type RcondConfig struct { type RcondConfig struct {
Addr string `yaml:"addr"` Addr string `yaml:"addr" envconfig:"RCOND_ADDR"`
ApiToken string `yaml:"api_token"` ApiToken string `yaml:"api_token" envconfig:"RCOND_API_TOKEN"`
} }
type NetworkConfig struct { type NetworkConfig struct {
@@ -38,28 +39,35 @@ type ConnectionConfig struct {
} }
type ClusterConfig struct { type ClusterConfig struct {
Enabled bool `yaml:"enabled"` Enabled bool `yaml:"enabled" envconfig:"CLUSTER_ENABLED"`
NodeName string `yaml:"node_name"` NodeName string `yaml:"node_name" envconfig:"CLUSTER_NODE_NAME"`
SecretKey string `yaml:"secret_key"` SecretKey string `yaml:"secret_key" envconfig:"CLUSTER_SECRET_KEY"`
Join []string `yaml:"join"` Join []string `yaml:"join" envconfig:"CLUSTER_JOIN"`
AdvertiseAddr string `yaml:"advertise_addr"` AdvertiseAddr string `yaml:"advertise_addr" envconfig:"CLUSTER_ADVERTISE_ADDR"`
AdvertisePort int `yaml:"advertise_port"` AdvertisePort int `yaml:"advertise_port" envconfig:"CLUSTER_ADVERTISE_PORT"`
BindAddr string `yaml:"bind_addr"` BindAddr string `yaml:"bind_addr" envconfig:"CLUSTER_BIND_ADDR"`
BindPort int `yaml:"bind_port"` BindPort int `yaml:"bind_port" envconfig:"CLUSTER_BIND_PORT"`
LogLevel string `yaml:"log_level"` LogLevel string `yaml:"log_level" envconfig:"CLUSTER_LOG_LEVEL"`
} }
func LoadConfig(path string) (*Config, error) { // LoadConfig reads the configuration from a YAML file and environment variables.
yamlFile, err := os.ReadFile(path) func LoadConfig(filename string) (*Config, error) {
var config Config
data, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var config Config err = yaml.Unmarshal(data, &config)
err = yaml.Unmarshal(yamlFile, &config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = envconfig.Process("", &config)
if err != nil {
return nil, err
}
return &config, nil return &config, nil
} }

View File

@@ -3,6 +3,7 @@ package http
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"log"
"net/http" "net/http"
"time" "time"
@@ -44,6 +45,18 @@ func (s *Server) WithClusterAgent(agent *cluster.Agent) *Server {
return s 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 { func (s *Server) Start() error {
return s.srv.ListenAndServe() return s.srv.ListenAndServe()
} }

View File

@@ -6,7 +6,7 @@ import (
"os" "os"
"time" "time"
"github.com/0x1d/rcond/pkg/system" "github.com/0x1d/rcond/pkg/util"
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
"github.com/google/uuid" "github.com/google/uuid"
) )
@@ -334,7 +334,7 @@ func GetHostname() (string, error) {
// SetHostname changes the static hostname via the system bus. // SetHostname changes the static hostname via the system bus.
// newHost is your desired hostname, interactive=false skips any prompt. // newHost is your desired hostname, interactive=false skips any prompt.
func SetHostname(newHost string) error { func SetHostname(newHost string) error {
return system.WithDbus(func(conn *dbus.Conn) error { return util.WithConnection(func(conn *dbus.Conn) error {
obj := conn.Object( obj := conn.Object(
"org.freedesktop.hostname1", "org.freedesktop.hostname1",
dbus.ObjectPath("/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) { func ConfigureSTA(iface string, ssid string, password string, autoconnect bool) (string, error) {
uuid := uuid.New() 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) _, err := AddStationConnection(conn, uuid, ssid, password, autoconnect)
if err != nil { if err != nil {
return fmt.Errorf("failed to create station connection: %v", err) 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) { func ConfigureAP(iface string, ssid string, password string, autoconnect bool) (string, error) {
uuid := uuid.New() 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) _, err := AddAccessPointConnection(conn, uuid, ssid, password, autoconnect)
if err != nil { if err != nil {
return fmt.Errorf("failed to create access point connection: %v", err) 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. // The connection will be activated on the specified interface.
// Returns an error if any operation fails. // Returns an error if any operation fails.
func Up(iface string, uuid string) error { 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) connPath, err := GetConnectionPath(conn, uuid)
if err != nil { if err != nil {
return err return err
@@ -428,7 +428,7 @@ func Up(iface string, uuid string) error {
// It takes the interface name as an argument. // It takes the interface name as an argument.
// Returns an error if the device cannot be found or disconnected. // Returns an error if the device cannot be found or disconnected.
func Down(iface string) error { 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) devPath, err := GetDeviceByIpIface(conn, iface)
if err != nil { if err != nil {
return err return err
@@ -446,7 +446,7 @@ func Down(iface string) error {
// If no connection with the UUID exists, it returns nil. // If no connection with the UUID exists, it returns nil.
// Returns an error if the connection exists but cannot be deleted. // Returns an error if the connection exists but cannot be deleted.
func Remove(uuid string) error { 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) connPath, err := GetConnectionPath(conn, uuid)
if err != nil { if err != nil {
return err return err

57
pkg/rcond/node.go Normal file
View File

@@ -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
}

47
pkg/system/init.go Normal file
View File

@@ -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
}

View File

@@ -3,12 +3,13 @@ package system
import ( import (
"log" "log"
"github.com/0x1d/rcond/pkg/util"
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
) )
// Restart restarts the system. // Restart restarts the system.
func Restart() error { 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") obj := conn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1")
log.Println("Rebooting system...") log.Println("Rebooting system...")
call := obj.Call("org.freedesktop.systemd1.Manager.Reboot", 0) call := obj.Call("org.freedesktop.systemd1.Manager.Reboot", 0)
@@ -21,7 +22,7 @@ func Restart() error {
// Shutdown shuts down the system. // Shutdown shuts down the system.
func Shutdown() error { 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") obj := conn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1")
log.Println("Shutting down system...") log.Println("Shutting down system...")
call := obj.Call("org.freedesktop.systemd1.Manager.PowerOff", 0) call := obj.Call("org.freedesktop.systemd1.Manager.PowerOff", 0)

View File

@@ -1,4 +1,4 @@
package system package util
import ( import (
"log" "log"
@@ -6,9 +6,9 @@ import (
"github.com/godbus/dbus/v5" "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 // 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() conn, err := dbus.SystemBus()
if err != nil { if err != nil {
log.Printf("[ERROR] Failed to connect to system bus: %v", err) log.Printf("[ERROR] Failed to connect to system bus: %v", err)