Files
codit/backend/internal/config/config.go
2026-02-20 16:54:21 +09:00

305 lines
7.6 KiB
Go

package config
import "encoding/json"
import "errors"
import "os"
import "path/filepath"
import "strings"
import "time"
import "strconv"
type Config struct {
HTTPAddrs []string `json:"http_addrs"`
HTTPSAddrs []string `json:"https_addrs"`
PublicBaseURL string `json:"public_base_url"`
DataDir string `json:"data_dir"`
FrontendDir string `json:"frontend_dir"`
DBDriver string `json:"db_driver"`
DBDSN string `json:"db_dsn"`
SessionTTL Duration `json:"session_ttl"`
AuthMode string `json:"auth_mode"`
LDAPURL string `json:"ldap_url"`
LDAPBindDN string `json:"ldap_bind_dn"`
LDAPBindPassword string `json:"ldap_bind_password"`
LDAPUserBaseDN string `json:"ldap_user_base_dn"`
LDAPUserFilter string `json:"ldap_user_filter"`
LDAPTLSInsecureSkipVerify bool `json:"ldap_tls_insecure_skip_verify"`
OIDCClientID string `json:"oidc_client_id"`
OIDCClientSecret string `json:"oidc_client_secret"`
OIDCAuthorizeURL string `json:"oidc_authorize_url"`
OIDCTokenURL string `json:"oidc_token_url"`
OIDCUserInfoURL string `json:"oidc_userinfo_url"`
OIDCRedirectURL string `json:"oidc_redirect_url"`
OIDCScopes string `json:"oidc_scopes"`
OIDCEnabled bool `json:"oidc_enabled"`
OIDCTLSInsecureSkipVerify bool `json:"oidc_tls_insecure_skip_verify"`
TLSServerCertSource string `json:"tls_server_cert_source"`
TLSCertFile string `json:"tls_cert_file"`
TLSKeyFile string `json:"tls_key_file"`
TLSPKIServerCertID string `json:"tls_pki_server_cert_id"`
TLSClientAuth string `json:"tls_client_auth"`
TLSClientCAFile string `json:"tls_client_ca_file"`
TLSPKIClientCAID string `json:"tls_pki_client_ca_id"`
TLSMinVersion string `json:"tls_min_version"`
GitHTTPPrefix string `json:"git_http_prefix"`
RPMHTTPPrefix string `json:"rpm_http_prefix"`
}
func Load(path string) (Config, error) {
var cfg Config
var data []byte
var err error
cfg = Config{
HTTPAddrs: []string{":1080"},
HTTPSAddrs: []string{},
DataDir: "./codit-data",
FrontendDir: filepath.Join("..", "frontend", "dist"),
DBDriver: "sqlite",
DBDSN: "file:./codit-data/codit.db?_pragma=foreign_keys(1)",
SessionTTL: Duration(24 * time.Hour),
AuthMode: "db",
LDAPUserFilter: "(uid={username})",
OIDCScopes: "openid profile email",
TLSServerCertSource: "files",
TLSClientAuth: "none",
TLSMinVersion: "1.2",
GitHTTPPrefix: "/git",
RPMHTTPPrefix: "/rpm",
}
if path != "" {
data, err = os.ReadFile(path)
if err != nil {
return cfg, err
}
err = json.Unmarshal(data, &cfg)
if err != nil {
return cfg, err
}
}
override(&cfg)
cfg.AuthMode = strings.ToLower(strings.TrimSpace(cfg.AuthMode))
cfg.TLSServerCertSource = strings.ToLower(strings.TrimSpace(cfg.TLSServerCertSource))
cfg.TLSClientAuth = strings.ToLower(strings.TrimSpace(cfg.TLSClientAuth))
cfg.HTTPAddrs = normalizeHTTPAddrs(cfg.HTTPAddrs)
cfg.HTTPSAddrs = normalizeHTTPAddrs(cfg.HTTPSAddrs)
if len(cfg.HTTPAddrs) == 0 && len(cfg.HTTPSAddrs) == 0 {
return cfg, errors.New("http_addrs or https_addrs is required")
}
if cfg.DBDSN == "" {
return cfg, errors.New("db dsn is required")
}
return cfg, nil
}
func override(cfg *Config) {
var v string
v = os.Getenv("CODIT_HTTP_ADDRS")
if v != "" {
cfg.HTTPAddrs = splitCSV(v)
}
v = os.Getenv("CODIT_HTTPS_ADDRS")
if v != "" {
cfg.HTTPSAddrs = splitCSV(v)
}
v = os.Getenv("CODIT_PUBLIC_BASE_URL")
if v != "" {
cfg.PublicBaseURL = v
}
v = os.Getenv("CODIT_DATA_DIR")
if v != "" {
cfg.DataDir = v
}
v = os.Getenv("CODIT_FRONTEND_DIR")
if v != "" {
cfg.FrontendDir = v
}
v = os.Getenv("CODIT_DB_DRIVER")
if v != "" {
cfg.DBDriver = v
}
v = os.Getenv("CODIT_DB_DSN")
if v != "" {
cfg.DBDSN = v
}
v = os.Getenv("CODIT_AUTH_MODE")
if v != "" {
cfg.AuthMode = v
}
v = os.Getenv("CODIT_LDAP_URL")
if v != "" {
cfg.LDAPURL = v
}
v = os.Getenv("CODIT_LDAP_BIND_DN")
if v != "" {
cfg.LDAPBindDN = v
}
v = os.Getenv("CODIT_LDAP_BIND_PASSWORD")
if v != "" {
cfg.LDAPBindPassword = v
}
v = os.Getenv("CODIT_LDAP_USER_BASE_DN")
if v != "" {
cfg.LDAPUserBaseDN = v
}
v = os.Getenv("CODIT_LDAP_USER_FILTER")
if v != "" {
cfg.LDAPUserFilter = v
}
v = os.Getenv("CODIT_LDAP_TLS_INSECURE_SKIP_VERIFY")
if v != "" {
cfg.LDAPTLSInsecureSkipVerify = parseEnvBool(v)
}
v = os.Getenv("CODIT_OIDC_CLIENT_ID")
if v != "" {
cfg.OIDCClientID = v
}
v = os.Getenv("CODIT_OIDC_CLIENT_SECRET")
if v != "" {
cfg.OIDCClientSecret = v
}
v = os.Getenv("CODIT_OIDC_AUTHORIZE_URL")
if v != "" {
cfg.OIDCAuthorizeURL = v
}
v = os.Getenv("CODIT_OIDC_TOKEN_URL")
if v != "" {
cfg.OIDCTokenURL = v
}
v = os.Getenv("CODIT_OIDC_USERINFO_URL")
if v != "" {
cfg.OIDCUserInfoURL = v
}
v = os.Getenv("CODIT_OIDC_REDIRECT_URL")
if v != "" {
cfg.OIDCRedirectURL = v
}
v = os.Getenv("CODIT_OIDC_SCOPES")
if v != "" {
cfg.OIDCScopes = v
}
v = os.Getenv("CODIT_OIDC_ENABLED")
if v != "" {
cfg.OIDCEnabled = parseEnvBool(v)
}
v = os.Getenv("CODIT_OIDC_TLS_INSECURE_SKIP_VERIFY")
if v != "" {
cfg.OIDCTLSInsecureSkipVerify = parseEnvBool(v)
}
v = os.Getenv("CODIT_TLS_SERVER_CERT_SOURCE")
if v != "" {
cfg.TLSServerCertSource = v
}
v = os.Getenv("CODIT_TLS_CERT_FILE")
if v != "" {
cfg.TLSCertFile = v
}
v = os.Getenv("CODIT_TLS_KEY_FILE")
if v != "" {
cfg.TLSKeyFile = v
}
v = os.Getenv("CODIT_TLS_PKI_SERVER_CERT_ID")
if v != "" {
cfg.TLSPKIServerCertID = v
}
v = os.Getenv("CODIT_TLS_CLIENT_AUTH")
if v != "" {
cfg.TLSClientAuth = v
}
v = os.Getenv("CODIT_TLS_CLIENT_CA_FILE")
if v != "" {
cfg.TLSClientCAFile = v
}
v = os.Getenv("CODIT_TLS_PKI_CLIENT_CA_ID")
if v != "" {
cfg.TLSPKIClientCAID = v
}
v = os.Getenv("CODIT_TLS_MIN_VERSION")
if v != "" {
cfg.TLSMinVersion = v
}
v = os.Getenv("CODIT_GIT_HTTP_PREFIX")
if v != "" {
cfg.GitHTTPPrefix = v
}
v = os.Getenv("CODIT_RPM_HTTP_PREFIX")
if v != "" {
cfg.RPMHTTPPrefix = v
}
}
type Duration time.Duration
func (d Duration) Duration() time.Duration {
return time.Duration(d)
}
func (d *Duration) UnmarshalJSON(b []byte) error {
var asString string
var asNumber int64
var err error
var parsed time.Duration
err = json.Unmarshal(b, &asString)
if err == nil {
parsed, err = time.ParseDuration(asString)
if err != nil {
return err
}
*d = Duration(parsed)
return nil
}
err = json.Unmarshal(b, &asNumber)
if err == nil {
*d = Duration(time.Duration(asNumber))
return nil
}
return errors.New("invalid duration format")
}
func parseEnvBool(v string) bool {
var lowered string
var parsed bool
var err error
lowered = strings.ToLower(strings.TrimSpace(v))
if lowered == "true" || lowered == "yes" || lowered == "y" || lowered == "on" {
return true
}
if lowered == "false" || lowered == "no" || lowered == "n" || lowered == "off" {
return false
}
parsed, err = strconv.ParseBool(lowered)
if err == nil {
return parsed
}
return false
}
func splitCSV(v string) []string {
var parts []string
var out []string
var i int
var p string
parts = strings.Split(v, ",")
for i = 0; i < len(parts); i++ {
p = strings.TrimSpace(parts[i])
if p == "" {
continue
}
out = append(out, p)
}
return out
}
func normalizeHTTPAddrs(values []string) []string {
var out []string
var i int
var v string
for i = 0; i < len(values); i++ {
v = strings.TrimSpace(values[i])
if v == "" {
continue
}
out = append(out, v)
}
return out
}