Files
ZFile/backend/config.go

219 lines
6.8 KiB
Go

package main
import (
"log"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/joho/godotenv"
)
type Config struct {
Addr string
DBPath string
StorageRoot string
AppDomain string
AllowedHost string
CORSOrigin string
CookieSecure bool
MaxBodyBytes int64
GoogleAuthEnabled bool
GoogleClientID string
GoogleClientSecret string
GoogleRedirectURL string
GoogleAuthURL string
GoogleTokenURL string
GoogleUserInfoURL string
RateLimitPerMin int
AuthRateLimitPerMin int
JWTSecret string
AccessTTL time.Duration
RefreshTTL time.Duration
ShareDefaultTTL time.Duration
AdminSessionTTL time.Duration
AdminLogin string
AdminPasswordHash string
FTPEnabled bool
FTPHost string
FTPPort int
FTPPublicIP string
FTPPassivePorts string
FTPSEnabled bool
FTPSHost string
FTPSPort int
FTPSPublicIP string
FTPSPassivePorts string
FTPSCertFile string
FTPSKeyFile string
FTPSLEDomain string
FTPSLEDir string
FTPSExplicit bool
FTPSForceTLS bool
SFTPEnabled bool
SFTPHost string
SFTPPort int
SFTPHostKeyPath string
}
func loadConfig() Config {
_ = godotenv.Load(".env", "../.env")
dbPath := getEnv("DB_PATH", "./app.db")
storageRoot := getEnv("STORAGE_ROOT", "./users")
if err := os.MkdirAll(filepath.Dir(dbPath), 0o755); err != nil {
log.Fatalf("failed to create db path parent: %v", err)
}
if err := os.MkdirAll(storageRoot, 0o755); err != nil {
log.Fatalf("failed to create storage root: %v", err)
}
cfg := Config{
Addr: getEnv("ADDR", ":8080"),
DBPath: dbPath,
StorageRoot: storageRoot,
AppDomain: strings.ToLower(strings.TrimSpace(getEnv("APP_DOMAIN", "file.example.com"))),
AllowedHost: strings.ToLower(getEnv("ALLOWED_HOST", "")),
CORSOrigin: getEnv("CORS_ALLOWED_ORIGIN", ""),
CookieSecure: getEnv("COOKIE_SECURE", "false") == "true",
MaxBodyBytes: int64(getEnvInt("MAX_BODY_MB", 8)) * 1024 * 1024,
GoogleAuthEnabled: getEnv("GOOGLE_AUTH_ENABLED", "false") == "true",
GoogleClientID: getEnv("GOOGLE_CLIENT_ID", ""),
GoogleClientSecret: getEnv("GOOGLE_CLIENT_SECRET", ""),
GoogleRedirectURL: getEnv("GOOGLE_REDIRECT_URL", ""),
GoogleAuthURL: getEnv("GOOGLE_AUTH_URL", "https://accounts.google.com/o/oauth2/v2/auth"),
GoogleTokenURL: getEnv("GOOGLE_TOKEN_URL", "https://oauth2.googleapis.com/token"),
GoogleUserInfoURL: getEnv("GOOGLE_USERINFO_URL", "https://openidconnect.googleapis.com/v1/userinfo"),
RateLimitPerMin: getEnvInt("RATE_LIMIT_PER_MIN", 240),
AuthRateLimitPerMin: getEnvInt("AUTH_RATE_LIMIT_PER_MIN", 30),
JWTSecret: getEnv("JWT_SECRET", "dev-change-me-immediately"),
AccessTTL: 15 * time.Minute,
RefreshTTL: 30 * 24 * time.Hour,
ShareDefaultTTL: 24 * time.Hour,
AdminSessionTTL: 12 * time.Hour,
AdminLogin: getEnv("ADMIN_LOGIN", "admin"),
AdminPasswordHash: getEnv("ADMIN_PASSWORD_HASH", ""),
FTPEnabled: getEnv("FTP_ENABLED", "false") == "true",
FTPHost: getEnv("FTP_HOST", "0.0.0.0"),
FTPPort: getEnvInt("FTP_PORT", 2121),
FTPPublicIP: getEnv("FTP_PUBLIC_IP", ""),
FTPPassivePorts: getEnv("FTP_PASSIVE_PORTS", ""),
FTPSEnabled: getEnv("FTPS_ENABLED", "false") == "true",
FTPSHost: getEnv("FTPS_HOST", "0.0.0.0"),
FTPSPort: getEnvInt("FTPS_PORT", 2990),
FTPSPublicIP: getEnv("FTPS_PUBLIC_IP", ""),
FTPSPassivePorts: getEnv("FTPS_PASSIVE_PORTS", ""),
FTPSCertFile: getEnv("FTPS_CERT_FILE", ""),
FTPSKeyFile: getEnv("FTPS_KEY_FILE", ""),
FTPSLEDomain: strings.ToLower(strings.TrimSpace(getEnv("FTPS_LETSENCRYPT_DOMAIN", ""))),
FTPSLEDir: getEnv("FTPS_LETSENCRYPT_DIR", "/etc/letsencrypt/live"),
FTPSExplicit: getEnv("FTPS_EXPLICIT", "true") != "false",
FTPSForceTLS: getEnv("FTPS_FORCE_TLS", "true") != "false",
SFTPEnabled: getEnv("SFTP_ENABLED", "false") == "true",
SFTPHost: getEnv("SFTP_HOST", "0.0.0.0"),
SFTPPort: getEnvInt("SFTP_PORT", 2022),
SFTPHostKeyPath: getEnv("SFTP_HOST_KEY_PATH", "./sftp_host_ed25519"),
}
if cfg.AllowedHost == "" {
cfg.AllowedHost = cfg.AppDomain
}
if cfg.CORSOrigin == "" {
cfg.CORSOrigin = "https://" + cfg.AppDomain
}
if cfg.JWTSecret == "dev-change-me-immediately" {
log.Println("warning: JWT_SECRET is using default development value")
}
if strings.TrimSpace(cfg.AdminPasswordHash) == "" {
log.Fatal("ADMIN_PASSWORD_HASH is required. Generate one with: go run . hash-admin <password>")
}
if cfg.GoogleAuthEnabled {
if strings.TrimSpace(cfg.GoogleClientID) == "" || strings.TrimSpace(cfg.GoogleClientSecret) == "" {
log.Fatal("GOOGLE_AUTH_ENABLED=true requires GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET")
}
}
if cfg.FTPEnabled {
if cfg.FTPPort < 1 || cfg.FTPPort > 65535 {
log.Fatal("FTP_PORT must be in range 1..65535")
}
}
if cfg.FTPSEnabled {
if cfg.FTPSPort < 1 || cfg.FTPSPort > 65535 {
log.Fatal("FTPS_PORT must be in range 1..65535")
}
applyFTPSLetsEncryptDefaults(&cfg)
if strings.TrimSpace(cfg.FTPSCertFile) == "" || strings.TrimSpace(cfg.FTPSKeyFile) == "" {
log.Fatal("FTPS_ENABLED=true requires FTPS_CERT_FILE/FTPS_KEY_FILE or FTPS_LETSENCRYPT_DOMAIN")
}
if _, err := os.Stat(cfg.FTPSCertFile); err != nil {
log.Fatalf("FTPS_CERT_FILE is invalid: %v", err)
}
if _, err := os.Stat(cfg.FTPSKeyFile); err != nil {
log.Fatalf("FTPS_KEY_FILE is invalid: %v", err)
}
}
if cfg.FTPEnabled && cfg.FTPSEnabled {
if cfg.FTPPort == cfg.FTPSPort && strings.EqualFold(cfg.FTPHost, cfg.FTPSHost) {
log.Fatal("FTP and FTPS cannot share the same host:port")
}
}
if cfg.SFTPEnabled {
if cfg.SFTPPort < 1 || cfg.SFTPPort > 65535 {
log.Fatal("SFTP_PORT must be in range 1..65535")
}
}
return cfg
}
func applyFTPSLetsEncryptDefaults(cfg *Config) {
if cfg == nil {
return
}
if strings.TrimSpace(cfg.FTPSLEDomain) == "" {
return
}
base := strings.TrimSpace(cfg.FTPSLEDir)
if base == "" {
base = "/etc/letsencrypt/live"
}
domainDir := filepath.Join(base, cfg.FTPSLEDomain)
if strings.TrimSpace(cfg.FTPSCertFile) == "" {
cfg.FTPSCertFile = filepath.Join(domainDir, "fullchain.pem")
}
if strings.TrimSpace(cfg.FTPSKeyFile) == "" {
cfg.FTPSKeyFile = filepath.Join(domainDir, "privkey.pem")
}
}
func getEnv(key, fallback string) string {
v := strings.TrimSpace(os.Getenv(key))
if v == "" {
return fallback
}
return v
}
func getEnvInt(key string, fallback int) int {
v := strings.TrimSpace(os.Getenv(key))
if v == "" {
return fallback
}
n, err := strconv.Atoi(v)
if err != nil {
return fallback
}
return n
}