phase 0 complete

This commit is contained in:
2026-08-14 11:29:11 -06:00
parent 4da16c7573
commit 85df9a208e
102 changed files with 25019 additions and 30 deletions
+139
View File
@@ -0,0 +1,139 @@
// Package config loads and validates SoloPM's environment-variable configuration.
package config
import (
"fmt"
"os"
"strconv"
"strings"
)
// Config holds every environment-driven setting for the SoloPM server.
type Config struct {
DatabaseURL string
HTTPPort string
BaseURL string
SessionSecret string
UploadDir string
MaxUploadMB int
MeiliURL string
MeiliKey string
SMTPHost string
SMTPPort string
SMTPUser string
SMTPPass string
SMTPFrom string
GitHubClientID string
GitHubClientSecret string
GiteaURL string
GiteaClientID string
GiteaClientSecret string
CullAfterDays int
LogLevel string
}
// SearchEnabled reports whether Meilisearch is configured.
func (c *Config) SearchEnabled() bool {
return c.MeiliURL != "" && c.MeiliKey != ""
}
// EmailEnabled reports whether SMTP is configured.
func (c *Config) EmailEnabled() bool {
return c.SMTPHost != ""
}
// GitHubOAuthEnabled reports whether GitHub OAuth is configured.
func (c *Config) GitHubOAuthEnabled() bool {
return c.GitHubClientID != "" && c.GitHubClientSecret != ""
}
// GiteaOAuthEnabled reports whether Gitea OAuth is configured.
func (c *Config) GiteaOAuthEnabled() bool {
return c.GiteaURL != "" && c.GiteaClientID != "" && c.GiteaClientSecret != ""
}
// Load reads configuration from the environment, applying defaults and
// failing fast (returning an error) if a required variable is missing or
// malformed.
func Load() (*Config, error) {
var missing []string
required := func(key string) string {
v := os.Getenv(key)
if v == "" {
missing = append(missing, key)
}
return v
}
c := &Config{
DatabaseURL: required("DATABASE_URL"),
HTTPPort: getDefault("HTTP_PORT", "8080"),
BaseURL: required("BASE_URL"),
SessionSecret: required("SESSION_SECRET"),
UploadDir: getDefault("UPLOAD_DIR", "./uploads"),
MeiliURL: os.Getenv("MEILI_URL"),
MeiliKey: os.Getenv("MEILI_KEY"),
SMTPHost: os.Getenv("SMTP_HOST"),
SMTPPort: os.Getenv("SMTP_PORT"),
SMTPUser: os.Getenv("SMTP_USER"),
SMTPPass: os.Getenv("SMTP_PASS"),
SMTPFrom: os.Getenv("SMTP_FROM"),
GitHubClientID: os.Getenv("GITHUB_CLIENT_ID"),
GitHubClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"),
GiteaURL: os.Getenv("GITEA_URL"),
GiteaClientID: os.Getenv("GITEA_CLIENT_ID"),
GiteaClientSecret: os.Getenv("GITEA_CLIENT_SECRET"),
LogLevel: getDefault("LOG_LEVEL", "info"),
}
if len(missing) > 0 {
return nil, fmt.Errorf("config: missing required environment variable(s): %s", strings.Join(missing, ", "))
}
if len(c.SessionSecret) < 32 {
return nil, fmt.Errorf("config: SESSION_SECRET must be at least 32 bytes, got %d", len(c.SessionSecret))
}
maxUploadMB, err := getIntDefault("MAX_UPLOAD_MB", 25)
if err != nil {
return nil, err
}
c.MaxUploadMB = maxUploadMB
cullAfterDays, err := getIntDefault("CULL_AFTER_DAYS", 30)
if err != nil {
return nil, err
}
c.CullAfterDays = cullAfterDays
return c, nil
}
func getDefault(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}
func getIntDefault(key string, def int) (int, error) {
v := os.Getenv(key)
if v == "" {
return def, nil
}
n, err := strconv.Atoi(v)
if err != nil {
return 0, fmt.Errorf("config: %s must be an integer, got %q", key, v)
}
return n, nil
}