mirror of
https://github.com/0x1d/rcond.git
synced 2025-12-14 18:25:21 +01:00
feat: introduce YAML configuration
This commit is contained in:
8
Makefile
8
Makefile
@@ -1,5 +1,5 @@
|
|||||||
SHELL := bash
|
SHELL := bash
|
||||||
ARCH ?= arm64
|
ARCH ?= amd64
|
||||||
ADDR ?= 0.0.0.0:8080
|
ADDR ?= 0.0.0.0:8080
|
||||||
|
|
||||||
generate:
|
generate:
|
||||||
@@ -11,10 +11,12 @@ build:
|
|||||||
env GOOS=linux GOARCH=${ARCH} go build -o bin/rcond-${ARCH} ./cmd/rcond/main.go
|
env GOOS=linux GOARCH=${ARCH} go build -o bin/rcond-${ARCH} ./cmd/rcond/main.go
|
||||||
|
|
||||||
run:
|
run:
|
||||||
source .env && bin/rcond-${ARCH} ${ADDR}
|
bin/rcond-${ARCH} -config config.yaml
|
||||||
|
|
||||||
dev:
|
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:
|
upload:
|
||||||
scp bin/rcond-${ARCH} pi@rpi-test:/home/pi/rcond
|
scp bin/rcond-${ARCH} pi@rpi-test:/home/pi/rcond
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -6,6 +6,7 @@ A simple daemon and REST API to manage:
|
|||||||
- authorized SSH keys through the user's authorized_keys file
|
- authorized SSH keys through the user's authorized_keys file
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Make
|
- Make
|
||||||
- Go 1.19 or later
|
- Go 1.19 or later
|
||||||
- NetworkManager
|
- NetworkManager
|
||||||
@@ -50,6 +51,20 @@ All endpoints except `/health` require authentication via an API token passed in
|
|||||||
### Request/Response Format
|
### Request/Response Format
|
||||||
All endpoints use JSON for request and response payloads.
|
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 Variables
|
||||||
|
|
||||||
| Environment Variable | Description | Default |
|
| Environment Variable | Description | Default |
|
||||||
|
|||||||
@@ -3,41 +3,105 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/0x1d/rcond/pkg/config"
|
||||||
http "github.com/0x1d/rcond/pkg/http"
|
http "github.com/0x1d/rcond/pkg/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
NETWORK_CONNECTION_UUID = "7d706027-727c-4d4c-a816-f0e1b99db8ab"
|
|
||||||
)
|
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
fmt.Printf("Usage: %s <address>\n", os.Args[0])
|
fmt.Println("Usage: rcond <flags>")
|
||||||
os.Exit(0)
|
flag.PrintDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
appConfig, err := loadConfig()
|
||||||
addr := os.Getenv("RCOND_ADDR")
|
if err != nil {
|
||||||
if addr == "" {
|
usage()
|
||||||
addr = "0.0.0.0:8080"
|
fmt.Printf("\nFailed to load config: %v\n", err)
|
||||||
}
|
os.Exit(1)
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := http.NewServer(addr, apiToken)
|
srv := http.NewServer(appConfig)
|
||||||
srv.RegisterRoutes()
|
srv.RegisterRoutes()
|
||||||
|
|
||||||
log.Printf("Starting server on %s", addr)
|
log.Printf("Starting server on %s", appConfig.Rcond.Addr)
|
||||||
if err := srv.Start(); err != nil {
|
if err := srv.Start(); err != nil {
|
||||||
log.Fatal(err)
|
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
3
config.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
rcond:
|
||||||
|
addr: 0.0.0.0:8080
|
||||||
|
api_token: 1234567890
|
||||||
1
go.mod
1
go.mod
@@ -11,6 +11,7 @@ require (
|
|||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
golang.org/x/crypto v0.37.0
|
golang.org/x/crypto v0.37.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/sys v0.32.0 // indirect
|
require golang.org/x/sys v0.32.0 // indirect
|
||||||
|
|||||||
4
go.sum
4
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/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
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
13
install/rcond.service
Normal 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
38
pkg/config/config.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/0x1d/rcond/pkg/config"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,11 +16,15 @@ type Server struct {
|
|||||||
apiToken string
|
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()
|
router := mux.NewRouter()
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: addr,
|
Addr: cfg.Rcond.Addr,
|
||||||
Handler: router,
|
Handler: router,
|
||||||
ReadTimeout: 15 * time.Second,
|
ReadTimeout: 15 * time.Second,
|
||||||
WriteTimeout: 15 * time.Second,
|
WriteTimeout: 15 * time.Second,
|
||||||
@@ -28,7 +33,7 @@ func NewServer(addr string, apiToken string) *Server {
|
|||||||
return &Server{
|
return &Server{
|
||||||
router: router,
|
router: router,
|
||||||
srv: srv,
|
srv: srv,
|
||||||
apiToken: apiToken,
|
apiToken: cfg.Rcond.ApiToken,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user