Files
2026-07-13 01:26:49 +08:00

54 lines
2.3 KiB
Go

package app
import (
"errors"
"net/url"
"os"
"time"
)
type Config struct {
Environment, Address, PublicBaseURL, StudentPIN, InstructorPIN, SessionSecret, LogLevel, LogFormat string
SessionDuration, ReadHeaderTimeout, ReadTimeout, WriteTimeout, IdleTimeout, ShutdownTimeout time.Duration
}
func LoadConfigFromEnv() (Config, error) {
c := Config{Environment: get("APP_ENV", "development"), Address: get("HTTP_ADDRESS", ":8080"), PublicBaseURL: os.Getenv("PUBLIC_BASE_URL"), StudentPIN: os.Getenv("STUDENT_PIN"), InstructorPIN: os.Getenv("INSTRUCTOR_PIN"), SessionSecret: os.Getenv("SESSION_SIGNING_SECRET"), LogLevel: get("LOG_LEVEL", "info"), LogFormat: get("LOG_FORMAT", "text")}
var err error
c.SessionDuration, err = duration("SESSION_DURATION", "4h")
if err != nil {
return c, err
}
values := []struct {
target *time.Duration
key, fallback string
}{{&c.ReadHeaderTimeout, "HTTP_READ_HEADER_TIMEOUT", "5s"}, {&c.ReadTimeout, "HTTP_READ_TIMEOUT", "30s"}, {&c.WriteTimeout, "HTTP_WRITE_TIMEOUT", "10m"}, {&c.IdleTimeout, "HTTP_IDLE_TIMEOUT", "60s"}, {&c.ShutdownTimeout, "HTTP_SHUTDOWN_TIMEOUT", "10s"}}
for _, value := range values {
*value.target, err = duration(value.key, value.fallback)
if err != nil {
return c, err
}
}
if c.StudentPIN == "" || c.InstructorPIN == "" || len(c.SessionSecret) < 32 || c.PublicBaseURL == "" {
return c, errors.New("STUDENT_PIN, INSTRUCTOR_PIN, PUBLIC_BASE_URL, and a 32+ character SESSION_SIGNING_SECRET are required")
}
if c.LogFormat != "text" && c.LogFormat != "json" {
return c, errors.New("LOG_FORMAT must be text or json")
}
publicURL, err := url.Parse(c.PublicBaseURL)
if err != nil || publicURL.Host == "" || (publicURL.Scheme != "http" && publicURL.Scheme != "https") || (publicURL.Path != "" && publicURL.Path != "/") || publicURL.RawQuery != "" || publicURL.Fragment != "" || publicURL.User != nil {
return c, errors.New("PUBLIC_BASE_URL must be an HTTP(S) origin without a path, query, credentials, or fragment")
}
if c.Environment == "production" && publicURL.Scheme != "https" {
return c, errors.New("PUBLIC_BASE_URL must use HTTPS in production")
}
return c, nil
}
func get(k, d string) string {
if v := os.Getenv(k); v != "" {
return v
}
return d
}
func duration(k, d string) (time.Duration, error) { return time.ParseDuration(get(k, d)) }