Support plain HTTP mode for mc-proxy L7 deployment
Custom config package with optional TLS fields. When tls_cert/tls_key are empty, serves plain HTTP (behind mc-proxy which terminates TLS). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
138
internal/config/config.go
Normal file
138
internal/config/config.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
|
||||
mcdslauth "git.wntrmute.dev/mc/mcdsl/auth"
|
||||
)
|
||||
|
||||
// Config is the MCQ configuration.
|
||||
type Config struct {
|
||||
Server ServerConfig `toml:"server"`
|
||||
Database DatabaseConfig `toml:"database"`
|
||||
MCIAS mcdslauth.Config `toml:"mcias"`
|
||||
Log LogConfig `toml:"log"`
|
||||
}
|
||||
|
||||
// ServerConfig holds HTTP/gRPC server settings. TLS fields are optional;
|
||||
// when empty, MCQ serves plain HTTP (for use behind mc-proxy L7).
|
||||
type ServerConfig struct {
|
||||
ListenAddr string `toml:"listen_addr"`
|
||||
GRPCAddr string `toml:"grpc_addr"`
|
||||
TLSCert string `toml:"tls_cert"`
|
||||
TLSKey string `toml:"tls_key"`
|
||||
ReadTimeout Duration `toml:"read_timeout"`
|
||||
WriteTimeout Duration `toml:"write_timeout"`
|
||||
IdleTimeout Duration `toml:"idle_timeout"`
|
||||
ShutdownTimeout Duration `toml:"shutdown_timeout"`
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Path string `toml:"path"`
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Level string `toml:"level"`
|
||||
}
|
||||
|
||||
// Duration wraps time.Duration for TOML string parsing.
|
||||
type Duration struct {
|
||||
time.Duration
|
||||
}
|
||||
|
||||
func (d *Duration) UnmarshalText(text []byte) error {
|
||||
var err error
|
||||
d.Duration, err = time.ParseDuration(string(text))
|
||||
return err
|
||||
}
|
||||
|
||||
// Load reads, parses, and validates a config file.
|
||||
func Load(path string) (*Config, error) {
|
||||
data, err := os.ReadFile(path) // #nosec G304 -- config path is operator-controlled
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read config: %w", err)
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Server: ServerConfig{
|
||||
ListenAddr: ":8080",
|
||||
ReadTimeout: Duration{30 * time.Second},
|
||||
WriteTimeout: Duration{30 * time.Second},
|
||||
IdleTimeout: Duration{120 * time.Second},
|
||||
ShutdownTimeout: Duration{60 * time.Second},
|
||||
},
|
||||
Log: LogConfig{Level: "info"},
|
||||
}
|
||||
|
||||
if err := toml.Unmarshal(data, cfg); err != nil {
|
||||
return nil, fmt.Errorf("parse config: %w", err)
|
||||
}
|
||||
|
||||
applyEnvOverrides(cfg)
|
||||
|
||||
if err := cfg.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (c *Config) validate() error {
|
||||
if c.Server.ListenAddr == "" {
|
||||
return fmt.Errorf("config: server.listen_addr is required")
|
||||
}
|
||||
if c.Database.Path == "" {
|
||||
return fmt.Errorf("config: database.path is required")
|
||||
}
|
||||
if c.MCIAS.ServerURL == "" {
|
||||
return fmt.Errorf("config: mcias.server_url is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyEnvOverrides(cfg *Config) {
|
||||
if port := os.Getenv("PORT"); port != "" {
|
||||
cfg.Server.ListenAddr = ":" + port
|
||||
}
|
||||
applyEnvToStruct("MCQ", reflect.ValueOf(cfg).Elem())
|
||||
}
|
||||
|
||||
func applyEnvToStruct(prefix string, v reflect.Value) {
|
||||
t := v.Type()
|
||||
for i := range t.NumField() {
|
||||
field := t.Field(i)
|
||||
fv := v.Field(i)
|
||||
|
||||
tag := field.Tag.Get("toml")
|
||||
if tag == "" || tag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
envKey := prefix + "_" + strings.ToUpper(tag)
|
||||
|
||||
if fv.Kind() == reflect.Struct && field.Type != reflect.TypeOf(Duration{}) {
|
||||
applyEnvToStruct(envKey, fv)
|
||||
continue
|
||||
}
|
||||
|
||||
envVal := os.Getenv(envKey)
|
||||
if envVal == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if fv.Kind() == reflect.String {
|
||||
fv.SetString(envVal)
|
||||
}
|
||||
if field.Type == reflect.TypeOf(Duration{}) {
|
||||
if d, err := time.ParseDuration(envVal); err == nil {
|
||||
fv.Set(reflect.ValueOf(Duration{d}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user