127 lines
3.6 KiB
Go
127 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Config struct {
|
|
ListenAddress string
|
|
DatabaseURL string
|
|
CookieSecure bool
|
|
TrustProxy bool
|
|
SessionTTL time.Duration
|
|
CredentialsDir string
|
|
AdminUsername string
|
|
AdminPasswordFile string
|
|
BootstrapServerName string
|
|
BootstrapEndpoint string
|
|
BootstrapCredential string
|
|
MetricsRetention time.Duration
|
|
LogsRetention time.Duration
|
|
IntegrationTokenFile string
|
|
}
|
|
|
|
func LoadConfig() (Config, error) {
|
|
config := Config{
|
|
ListenAddress: envOr("LISTEN_ADDRESS", ":8080"),
|
|
DatabaseURL: os.Getenv("DATABASE_URL"),
|
|
CookieSecure: envBool("COOKIE_SECURE", true),
|
|
TrustProxy: envBool("TRUST_PROXY", false),
|
|
SessionTTL: envDuration("SESSION_TTL", 12*time.Hour),
|
|
CredentialsDir: envOr("AGENT_CREDENTIALS_DIR", "/run/secrets"),
|
|
AdminUsername: envOr("INITIAL_ADMIN_USERNAME", "admin"),
|
|
AdminPasswordFile: os.Getenv("INITIAL_ADMIN_PASSWORD_FILE"),
|
|
BootstrapServerName: os.Getenv("BOOTSTRAP_SERVER_NAME"),
|
|
BootstrapEndpoint: os.Getenv("BOOTSTRAP_SERVER_ENDPOINT"),
|
|
BootstrapCredential: os.Getenv("BOOTSTRAP_AGENT_CREDENTIAL_REF"),
|
|
MetricsRetention: envDuration("METRICS_RETENTION", 30*24*time.Hour),
|
|
LogsRetention: envDuration("LOGS_RETENTION", 7*24*time.Hour),
|
|
IntegrationTokenFile: os.Getenv("INTEGRATION_TOKEN_FILE"),
|
|
}
|
|
if config.DatabaseURL == "" {
|
|
config.DatabaseURL = buildDatabaseURL()
|
|
}
|
|
if config.DatabaseURL == "" {
|
|
return Config{}, errors.New("DATABASE_URL or DB_HOST/DB_PASSWORD_FILE is required")
|
|
}
|
|
if config.SessionTTL < 5*time.Minute || config.SessionTTL > 7*24*time.Hour {
|
|
return Config{}, errors.New("SESSION_TTL must be between 5m and 168h")
|
|
}
|
|
if config.MetricsRetention < 24*time.Hour {
|
|
return Config{}, errors.New("METRICS_RETENTION must be at least 24h")
|
|
}
|
|
if config.LogsRetention < time.Hour || config.LogsRetention > 90*24*time.Hour {
|
|
return Config{}, errors.New("LOGS_RETENTION must be between 1h and 2160h")
|
|
}
|
|
if config.BootstrapEndpoint != "" {
|
|
if err := validateAgentEndpoint(config.BootstrapEndpoint); err != nil {
|
|
return Config{}, fmt.Errorf("bootstrap endpoint: %w", err)
|
|
}
|
|
if !validCredentialRef(config.BootstrapCredential) {
|
|
return Config{}, errors.New("invalid BOOTSTRAP_AGENT_CREDENTIAL_REF")
|
|
}
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
func buildDatabaseURL() string {
|
|
host := os.Getenv("DB_HOST")
|
|
passwordFile := os.Getenv("DB_PASSWORD_FILE")
|
|
if host == "" || passwordFile == "" {
|
|
return ""
|
|
}
|
|
password, err := os.ReadFile(filepath.Clean(passwordFile))
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
user := envOr("DB_USER", "vps_control")
|
|
database := envOr("DB_NAME", "vps_control")
|
|
values := url.Values{"sslmode": {envOr("DB_SSLMODE", "disable")}}
|
|
return (&url.URL{
|
|
Scheme: "postgres",
|
|
User: url.UserPassword(user, strings.TrimSpace(string(password))),
|
|
Host: net.JoinHostPort(host, envOr("DB_PORT", "5432")),
|
|
Path: database,
|
|
RawQuery: values.Encode(),
|
|
}).String()
|
|
}
|
|
|
|
func envOr(name, fallback string) string {
|
|
if value := os.Getenv(name); value != "" {
|
|
return value
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func envBool(name string, fallback bool) bool {
|
|
value := os.Getenv(name)
|
|
if value == "" {
|
|
return fallback
|
|
}
|
|
parsed, err := strconv.ParseBool(value)
|
|
if err != nil {
|
|
return fallback
|
|
}
|
|
return parsed
|
|
}
|
|
|
|
func envDuration(name string, fallback time.Duration) time.Duration {
|
|
value := os.Getenv(name)
|
|
if value == "" {
|
|
return fallback
|
|
}
|
|
parsed, err := time.ParseDuration(value)
|
|
if err != nil {
|
|
return fallback
|
|
}
|
|
return parsed
|
|
}
|