feat: introduce YAML configuration

This commit is contained in:
2025-05-06 13:19:20 +02:00
parent 6017bf86f9
commit 3bbb689cac
9 changed files with 170 additions and 25 deletions

View File

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

View File

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

View File

@@ -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 <address>\n", os.Args[0])
os.Exit(0)
fmt.Println("Usage: rcond <flags>")
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
}

3
config.yaml Normal file
View File

@@ -0,0 +1,3 @@
rcond:
addr: 0.0.0.0:8080
api_token: 1234567890

1
go.mod
View File

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

4
go.sum
View File

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

13
install/rcond.service Normal file
View File

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

38
pkg/config/config.go Normal file
View File

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

View File

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