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 }