219 lines
6.8 KiB
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
|
|
}
|