Files
vps-control/control-plane/backend/config.go
DizelBadCoder c4dc19cd41
Some checks failed
CI / containers (push) Has been cancelled
CI / agent (push) Has been cancelled
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
git init
2026-09-19 19:31:36 +03:00

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
}