164 lines
4.6 KiB
Go
164 lines
4.6 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
_ "time/tzdata"
|
|
)
|
|
|
|
type Config struct {
|
|
ListenAddr string
|
|
PublicURL *url.URL
|
|
DatabaseDSN string
|
|
GiteaURL *url.URL
|
|
GiteaClientID string
|
|
GiteaClientSecret string
|
|
GiteaPackageOwner string
|
|
GiteaPackageUser string
|
|
GiteaPackageToken string
|
|
GiteaWriteUser string
|
|
GiteaWriteToken string
|
|
AllowedGiteaUsers map[string]struct{}
|
|
CookieSecret []byte
|
|
DefaultDuration time.Duration
|
|
DefaultHostLimit int
|
|
MaxHostLimit int
|
|
MaxUploadBytes int64
|
|
TrustProxyHeaders bool
|
|
DisplayTimeZone *time.Location
|
|
}
|
|
|
|
func LoadConfig() (Config, error) {
|
|
var cfg Config
|
|
var err error
|
|
|
|
cfg.ListenAddr = envDefault("TAPM_LISTEN_ADDR", ":8080")
|
|
cfg.DatabaseDSN = envDefault(
|
|
"TAPM_DATABASE_DSN",
|
|
"file:/data/tapm.db?_pragma=busy_timeout(5000)&_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)",
|
|
)
|
|
cfg.GiteaClientID = os.Getenv("TAPM_GITEA_CLIENT_ID")
|
|
cfg.GiteaClientSecret = os.Getenv("TAPM_GITEA_CLIENT_SECRET")
|
|
cfg.GiteaPackageOwner = envDefault("TAPM_GITEA_PACKAGE_OWNER", "TAI")
|
|
cfg.GiteaPackageUser = os.Getenv("TAPM_GITEA_PACKAGE_USERNAME")
|
|
cfg.GiteaPackageToken = os.Getenv("TAPM_GITEA_PACKAGE_TOKEN")
|
|
cfg.GiteaWriteUser = os.Getenv("TAPM_GITEA_PACKAGE_WRITE_USERNAME")
|
|
cfg.GiteaWriteToken = os.Getenv("TAPM_GITEA_PACKAGE_WRITE_TOKEN")
|
|
cfg.CookieSecret = []byte(os.Getenv("TAPM_COOKIE_SECRET"))
|
|
|
|
cfg.PublicURL, err = parseAbsoluteURL("TAPM_PUBLIC_URL")
|
|
if err != nil {
|
|
return cfg, err
|
|
}
|
|
cfg.GiteaURL, err = parseAbsoluteURL("TAPM_GITEA_URL")
|
|
if err != nil {
|
|
return cfg, err
|
|
}
|
|
|
|
cfg.DefaultDuration, err = time.ParseDuration(
|
|
envDefault("TAPM_DEFAULT_DURATION", "3h"),
|
|
)
|
|
if err != nil || cfg.DefaultDuration <= 0 {
|
|
return cfg, fmt.Errorf("TAPM_DEFAULT_DURATION must be positive")
|
|
}
|
|
cfg.DefaultHostLimit, err = envInt("TAPM_DEFAULT_HOST_LIMIT", 3)
|
|
if err != nil {
|
|
return cfg, err
|
|
}
|
|
cfg.MaxHostLimit, err = envInt("TAPM_MAX_HOST_LIMIT", 25)
|
|
if err != nil {
|
|
return cfg, err
|
|
}
|
|
if cfg.DefaultHostLimit < 1 || cfg.MaxHostLimit < cfg.DefaultHostLimit {
|
|
return cfg, fmt.Errorf("invalid default or maximum host limit")
|
|
}
|
|
cfg.MaxUploadBytes, err = envInt64("TAPM_MAX_UPLOAD_BYTES", 1<<30)
|
|
if err != nil || cfg.MaxUploadBytes < 1<<20 {
|
|
return cfg, fmt.Errorf("TAPM_MAX_UPLOAD_BYTES must be at least 1048576")
|
|
}
|
|
|
|
cfg.TrustProxyHeaders, err = strconv.ParseBool(
|
|
envDefault("TAPM_TRUST_PROXY_HEADERS", "false"),
|
|
)
|
|
if err != nil {
|
|
return cfg, fmt.Errorf("TAPM_TRUST_PROXY_HEADERS: %w", err)
|
|
}
|
|
cfg.DisplayTimeZone, err = time.LoadLocation(
|
|
envDefault("TAPM_DISPLAY_TIME_ZONE", "America/Chicago"),
|
|
)
|
|
if err != nil {
|
|
return cfg, fmt.Errorf("TAPM_DISPLAY_TIME_ZONE: %w", err)
|
|
}
|
|
|
|
cfg.AllowedGiteaUsers = make(map[string]struct{})
|
|
for _, user := range strings.Split(
|
|
envDefault("TAPM_ALLOWED_GITEA_USERS", "taiadmin"),
|
|
",",
|
|
) {
|
|
user = strings.ToLower(strings.TrimSpace(user))
|
|
if user != "" {
|
|
cfg.AllowedGiteaUsers[user] = struct{}{}
|
|
}
|
|
}
|
|
|
|
required := map[string]string{
|
|
"TAPM_GITEA_CLIENT_ID": cfg.GiteaClientID,
|
|
"TAPM_GITEA_CLIENT_SECRET": cfg.GiteaClientSecret,
|
|
"TAPM_GITEA_PACKAGE_USERNAME": cfg.GiteaPackageUser,
|
|
"TAPM_GITEA_PACKAGE_TOKEN": cfg.GiteaPackageToken,
|
|
"TAPM_GITEA_PACKAGE_WRITE_USERNAME": cfg.GiteaWriteUser,
|
|
"TAPM_GITEA_PACKAGE_WRITE_TOKEN": cfg.GiteaWriteToken,
|
|
}
|
|
for name, value := range required {
|
|
if strings.TrimSpace(value) == "" {
|
|
return cfg, fmt.Errorf("%s is required", name)
|
|
}
|
|
}
|
|
if len(cfg.CookieSecret) < 32 {
|
|
return cfg, fmt.Errorf("TAPM_COOKIE_SECRET must contain at least 32 bytes")
|
|
}
|
|
if len(cfg.AllowedGiteaUsers) == 0 {
|
|
return cfg, fmt.Errorf("at least one Gitea user must be allowed")
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func parseAbsoluteURL(name string) (*url.URL, error) {
|
|
raw := os.Getenv(name)
|
|
parsed, err := url.Parse(raw)
|
|
if err != nil || parsed.Scheme != "https" || parsed.Host == "" {
|
|
return nil, fmt.Errorf("%s must be an absolute HTTPS URL", name)
|
|
}
|
|
return parsed, nil
|
|
}
|
|
|
|
func envDefault(name, fallback string) string {
|
|
if value := os.Getenv(name); value != "" {
|
|
return value
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func envInt(name string, fallback int) (int, error) {
|
|
raw := envDefault(name, strconv.Itoa(fallback))
|
|
value, err := strconv.Atoi(raw)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("%s must be an integer", name)
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func envInt64(name string, fallback int64) (int64, error) {
|
|
raw := envDefault(name, strconv.FormatInt(fallback, 10))
|
|
value, err := strconv.ParseInt(raw, 10, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("%s must be an integer", name)
|
|
}
|
|
return value, nil
|
|
}
|