diff --git a/Makefile b/Makefile
index 3d79a7e..b3f17f6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
SHELL := bash
-ARCH ?= arm64
+ARCH ?= amd64
ADDR ?= 0.0.0.0:8080
generate:
@@ -11,10 +11,12 @@ build:
env GOOS=linux GOARCH=${ARCH} go build -o bin/rcond-${ARCH} ./cmd/rcond/main.go
run:
- source .env && bin/rcond-${ARCH} ${ADDR}
+ bin/rcond-${ARCH} -config config.yaml
dev:
- RCOND_API_TOKEN=1234567890 go run cmd/rcond/main.go
+ RCOND_ADDR=127.0.0.1:8080 \
+ RCOND_API_TOKEN=1234567890 \
+ go run cmd/rcond/main.go
upload:
scp bin/rcond-${ARCH} pi@rpi-test:/home/pi/rcond
diff --git a/README.md b/README.md
index d714e36..abd8408 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@ A simple daemon and REST API to manage:
- authorized SSH keys through the user's authorized_keys file
## Requirements
+
- Make
- Go 1.19 or later
- NetworkManager
@@ -50,6 +51,20 @@ All endpoints except `/health` require authentication via an API token passed in
### Request/Response Format
All endpoints use JSON for request and response payloads.
+## Configuration
+
+### File
+
+The default config file location is `/etc/rcond/config.yaml`.
+It can be overwritten by environment variables and flags.
+
+Example configuration:
+```yaml
+rcond:
+ addr: 0.0.0.0:8080
+ api_token: 1234567890
+```
+
### Environment Variables
| Environment Variable | Description | Default |
diff --git a/cmd/rcond/main.go b/cmd/rcond/main.go
index 7b6beaf..5b073f7 100644
--- a/cmd/rcond/main.go
+++ b/cmd/rcond/main.go
@@ -3,41 +3,105 @@
package main
import (
+ "flag"
"fmt"
"log"
"os"
+ "github.com/0x1d/rcond/pkg/config"
http "github.com/0x1d/rcond/pkg/http"
)
-const (
- NETWORK_CONNECTION_UUID = "7d706027-727c-4d4c-a816-f0e1b99db8ab"
-)
-
func usage() {
- fmt.Printf("Usage: %s
\n", os.Args[0])
- os.Exit(0)
+ fmt.Println("Usage: rcond ")
+ flag.PrintDefaults()
}
func main() {
-
- addr := os.Getenv("RCOND_ADDR")
- if addr == "" {
- addr = "0.0.0.0:8080"
- }
- if len(os.Args) > 1 {
- addr = os.Args[1]
- }
- apiToken := os.Getenv("RCOND_API_TOKEN")
- if apiToken == "" {
- log.Fatal("RCOND_API_TOKEN environment variable not set")
+ appConfig, err := loadConfig()
+ if err != nil {
+ usage()
+ fmt.Printf("\nFailed to load config: %v\n", err)
+ os.Exit(1)
}
- srv := http.NewServer(addr, apiToken)
+ srv := http.NewServer(appConfig)
srv.RegisterRoutes()
- log.Printf("Starting server on %s", addr)
+ log.Printf("Starting server on %s", appConfig.Rcond.Addr)
if err := srv.Start(); err != nil {
log.Fatal(err)
}
}
+
+func loadConfig() (*config.Config, error) {
+ configPath := "/etc/rcond/config.yaml"
+ appConfig := &config.Config{}
+ 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()
+
+ if help {
+ usage()
+ os.Exit(0)
+ }
+
+ // Load config from file
+ if _, err := os.Stat(configPath); !os.IsNotExist(err) {
+ configFile, err := config.LoadConfig(configPath)
+ if err != nil {
+ return nil, err
+ }
+ 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,
+ "token": &appConfig.Rcond.ApiToken,
+ }); err != nil {
+ return nil, err
+ }
+
+ return appConfig, 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 == "" {
+ return fmt.Errorf("%s is required", name)
+ }
+ }
+ return nil
+}
diff --git a/config.yaml b/config.yaml
new file mode 100644
index 0000000..5627dea
--- /dev/null
+++ b/config.yaml
@@ -0,0 +1,3 @@
+rcond:
+ addr: 0.0.0.0:8080
+ api_token: 1234567890
diff --git a/go.mod b/go.mod
index acbb638..2fe6c29 100644
--- a/go.mod
+++ b/go.mod
@@ -11,6 +11,7 @@ require (
github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1
golang.org/x/crypto v0.37.0
+ gopkg.in/yaml.v3 v3.0.1
)
require golang.org/x/sys v0.32.0 // indirect
diff --git a/go.sum b/go.sum
index bee5136..83db8c0 100644
--- a/go.sum
+++ b/go.sum
@@ -10,3 +10,7 @@ golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/install/rcond.service b/install/rcond.service
new file mode 100644
index 0000000..f284444
--- /dev/null
+++ b/install/rcond.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=rcond service
+After=network.target
+
+[Service]
+Type=simple
+User=root
+WorkingDirectory=/path/to/your/rcond
+ExecStart=/path/to/your/rcond/bin/rcond-${ARCH} ${ADDR}
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target
diff --git a/pkg/config/config.go b/pkg/config/config.go
new file mode 100644
index 0000000..f699b89
--- /dev/null
+++ b/pkg/config/config.go
@@ -0,0 +1,38 @@
+package config
+
+import (
+ "os"
+
+ "gopkg.in/yaml.v3"
+)
+
+type Config struct {
+ Rcond RcondConfig `yaml:"rcond"`
+}
+
+type RcondConfig struct {
+ Addr string `yaml:"addr"`
+ ApiToken string `yaml:"api_token"`
+}
+
+func LoadConfig(path string) (*Config, error) {
+ yamlFile, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+
+ var config Config
+ err = yaml.Unmarshal(yamlFile, &config)
+ if err != nil {
+ return nil, err
+ }
+ return &config, nil
+}
+
+func SaveConfig(path string, config *Config) error {
+ yamlFile, err := yaml.Marshal(config)
+ if err != nil {
+ return err
+ }
+ return os.WriteFile(path, yamlFile, 0644)
+}
diff --git a/pkg/http/server.go b/pkg/http/server.go
index 200cd61..ad14f9d 100644
--- a/pkg/http/server.go
+++ b/pkg/http/server.go
@@ -6,6 +6,7 @@ import (
"net/http"
"time"
+ "github.com/0x1d/rcond/pkg/config"
"github.com/gorilla/mux"
)
@@ -15,11 +16,15 @@ type Server struct {
apiToken string
}
-func NewServer(addr string, apiToken string) *Server {
+func NewServer(cfg *config.Config) *Server {
+ if cfg.Rcond.Addr == "" || cfg.Rcond.ApiToken == "" {
+ panic("addr or api_token is not set")
+ }
+
router := mux.NewRouter()
srv := &http.Server{
- Addr: addr,
+ Addr: cfg.Rcond.Addr,
Handler: router,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
@@ -28,7 +33,7 @@ func NewServer(addr string, apiToken string) *Server {
return &Server{
router: router,
srv: srv,
- apiToken: apiToken,
+ apiToken: cfg.Rcond.ApiToken,
}
}