277 lines
10 KiB
Go
277 lines
10 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func Load() (*Config, error) {
|
|
cfg := &Config{
|
|
DataDir: "./data",
|
|
AdminUsername: "admin",
|
|
AdminEnabled: AdminEnabledAuto,
|
|
AllowAdminSettingsOverride: true,
|
|
GuestUploadsEnabled: true,
|
|
APIEnabled: true,
|
|
ZipDownloadsEnabled: true,
|
|
OneTimeDownloadsEnabled: true,
|
|
OneTimeDownloadExpirySeconds: 7 * 24 * 60 * 60,
|
|
OneTimeDownloadRetryOnFailure: false,
|
|
DefaultGuestExpirySeconds: 10,
|
|
MaxGuestExpirySeconds: 48 * 60 * 60,
|
|
SessionTTLSeconds: 24 * 60 * 60,
|
|
BoxPollIntervalMS: 5000,
|
|
ThumbnailBatchSize: 10,
|
|
ThumbnailIntervalSeconds: 30,
|
|
sources: make(map[string]Source),
|
|
values: make(map[string]string),
|
|
defaults: make(map[string]string),
|
|
}
|
|
|
|
// Config precedence: defaults -> env -> overrides.
|
|
// Overrides are applied after Load by the server once the metadata store opens.
|
|
cfg.captureDefaults()
|
|
|
|
if err := cfg.applyStringEnv(SettingDataDir, "WARPBOX_DATA_DIR", &cfg.DataDir); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := cfg.applyStringEnv("", "WARPBOX_ADMIN_PASSWORD", &cfg.AdminPassword); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := cfg.applyStringEnv("", "WARPBOX_ADMIN_USERNAME", &cfg.AdminUsername); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := cfg.applyStringEnv("", "WARPBOX_ADMIN_EMAIL", &cfg.AdminEmail); err != nil {
|
|
return nil, err
|
|
}
|
|
if raw := strings.TrimSpace(os.Getenv("WARPBOX_ADMIN_ENABLED")); raw != "" {
|
|
mode := AdminEnabledMode(strings.ToLower(raw))
|
|
if mode != AdminEnabledAuto && mode != AdminEnabledTrue && mode != AdminEnabledFalse {
|
|
return nil, fmt.Errorf("WARPBOX_ADMIN_ENABLED must be auto, true, or false")
|
|
}
|
|
cfg.AdminEnabled = mode
|
|
}
|
|
if err := cfg.applyBoolEnv("", "WARPBOX_ALLOW_ADMIN_SETTINGS_OVERRIDE", &cfg.AllowAdminSettingsOverride); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := cfg.applyBoolEnv("", "WARPBOX_ADMIN_COOKIE_SECURE", &cfg.AdminCookieSecure); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
envBools := []struct {
|
|
key string
|
|
name string
|
|
target *bool
|
|
}{
|
|
{SettingGuestUploadsEnabled, "WARPBOX_GUEST_UPLOADS_ENABLED", &cfg.GuestUploadsEnabled},
|
|
{SettingAPIEnabled, "WARPBOX_API_ENABLED", &cfg.APIEnabled},
|
|
{SettingZipDownloadsEnabled, "WARPBOX_ZIP_DOWNLOADS_ENABLED", &cfg.ZipDownloadsEnabled},
|
|
{SettingOneTimeDownloadsEnabled, "WARPBOX_ONE_TIME_DOWNLOADS_ENABLED", &cfg.OneTimeDownloadsEnabled},
|
|
{SettingOneTimeDownloadRetryFail, "WARPBOX_ONE_TIME_DOWNLOAD_RETRY_ON_FAILURE", &cfg.OneTimeDownloadRetryOnFailure},
|
|
{SettingRenewOnAccessEnabled, "WARPBOX_RENEW_ON_ACCESS_ENABLED", &cfg.RenewOnAccessEnabled},
|
|
{SettingRenewOnDownloadEnabled, "WARPBOX_RENEW_ON_DOWNLOAD_ENABLED", &cfg.RenewOnDownloadEnabled},
|
|
}
|
|
for _, item := range envBools {
|
|
if err := cfg.applyBoolEnv(item.key, item.name, item.target); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
envInt64s := []struct {
|
|
key string
|
|
name string
|
|
min int64
|
|
target *int64
|
|
}{
|
|
{SettingDefaultGuestExpirySecs, "WARPBOX_DEFAULT_GUEST_EXPIRY_SECONDS", 0, &cfg.DefaultGuestExpirySeconds},
|
|
{SettingMaxGuestExpirySecs, "WARPBOX_MAX_GUEST_EXPIRY_SECONDS", 0, &cfg.MaxGuestExpirySeconds},
|
|
{SettingOneTimeDownloadExpirySecs, "WARPBOX_ONE_TIME_DOWNLOAD_EXPIRY_SECONDS", 0, &cfg.OneTimeDownloadExpirySeconds},
|
|
{SettingSessionTTLSeconds, "WARPBOX_SESSION_TTL_SECONDS", 60, &cfg.SessionTTLSeconds},
|
|
}
|
|
for _, item := range envInt64s {
|
|
if err := cfg.applyInt64Env(item.key, item.name, item.min, item.target); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
sizeEnvVars := []struct {
|
|
key string
|
|
gbName string
|
|
mbName string
|
|
bytesName string
|
|
target *int64
|
|
}{
|
|
{SettingGlobalMaxFileSizeBytes, "WARPBOX_GLOBAL_MAX_FILE_SIZE_GB", "WARPBOX_GLOBAL_MAX_FILE_SIZE_MB", "WARPBOX_GLOBAL_MAX_FILE_SIZE_BYTES", &cfg.GlobalMaxFileSizeBytes},
|
|
{SettingGlobalMaxBoxSizeBytes, "WARPBOX_GLOBAL_MAX_BOX_SIZE_GB", "WARPBOX_GLOBAL_MAX_BOX_SIZE_MB", "WARPBOX_GLOBAL_MAX_BOX_SIZE_BYTES", &cfg.GlobalMaxBoxSizeBytes},
|
|
{SettingDefaultUserMaxFileBytes, "WARPBOX_DEFAULT_USER_MAX_FILE_SIZE_GB", "WARPBOX_DEFAULT_USER_MAX_FILE_SIZE_MB", "WARPBOX_DEFAULT_USER_MAX_FILE_SIZE_BYTES", &cfg.DefaultUserMaxFileSizeBytes},
|
|
{SettingDefaultUserMaxBoxBytes, "WARPBOX_DEFAULT_USER_MAX_BOX_SIZE_GB", "WARPBOX_DEFAULT_USER_MAX_BOX_SIZE_MB", "WARPBOX_DEFAULT_USER_MAX_BOX_SIZE_BYTES", &cfg.DefaultUserMaxBoxSizeBytes},
|
|
}
|
|
for _, item := range sizeEnvVars {
|
|
if err := cfg.applySizeEnv(item.key, item.gbName, item.mbName, item.bytesName, 0, item.target); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
envInts := []struct {
|
|
key string
|
|
name string
|
|
min int
|
|
target *int
|
|
}{
|
|
{SettingBoxPollIntervalMS, "WARPBOX_BOX_POLL_INTERVAL_MS", 1000, &cfg.BoxPollIntervalMS},
|
|
{SettingThumbnailBatchSize, "WARPBOX_THUMBNAIL_BATCH_SIZE", 1, &cfg.ThumbnailBatchSize},
|
|
{SettingThumbnailIntervalSeconds, "WARPBOX_THUMBNAIL_INTERVAL_SECONDS", 1, &cfg.ThumbnailIntervalSeconds},
|
|
}
|
|
for _, item := range envInts {
|
|
if err := cfg.applyIntEnv(item.key, item.name, item.min, item.target); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
cfg.DataDir = filepath.Clean(cfg.DataDir)
|
|
if strings.TrimSpace(cfg.DataDir) == "" || cfg.DataDir == "." && strings.TrimSpace(os.Getenv("WARPBOX_DATA_DIR")) == "" {
|
|
cfg.DataDir = "data"
|
|
}
|
|
if cfg.AdminUsername = strings.TrimSpace(cfg.AdminUsername); cfg.AdminUsername == "" {
|
|
return nil, fmt.Errorf("WARPBOX_ADMIN_USERNAME cannot be empty")
|
|
}
|
|
cfg.AdminEmail = strings.TrimSpace(cfg.AdminEmail)
|
|
cfg.UploadsDir = filepath.Join(cfg.DataDir, "uploads")
|
|
cfg.DBDir = filepath.Join(cfg.DataDir, "db")
|
|
cfg.setValue(SettingDataDir, cfg.DataDir, cfg.sourceFor(SettingDataDir))
|
|
return cfg, nil
|
|
}
|
|
|
|
func (cfg *Config) EnsureDirectories() error {
|
|
for _, path := range []string{cfg.DataDir, cfg.UploadsDir, cfg.DBDir} {
|
|
if err := os.MkdirAll(path, 0755); err != nil {
|
|
return fmt.Errorf("create %s: %w", path, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
func (cfg *Config) captureDefaults() {
|
|
cfg.captureDefaultValue(SettingDataDir, cfg.DataDir)
|
|
cfg.captureDefaultValue(SettingGuestUploadsEnabled, formatBool(cfg.GuestUploadsEnabled))
|
|
cfg.captureDefaultValue(SettingAPIEnabled, formatBool(cfg.APIEnabled))
|
|
cfg.captureDefaultValue(SettingZipDownloadsEnabled, formatBool(cfg.ZipDownloadsEnabled))
|
|
cfg.captureDefaultValue(SettingOneTimeDownloadsEnabled, formatBool(cfg.OneTimeDownloadsEnabled))
|
|
cfg.captureDefaultValue(SettingOneTimeDownloadExpirySecs, strconv.FormatInt(cfg.OneTimeDownloadExpirySeconds, 10))
|
|
cfg.captureDefaultValue(SettingOneTimeDownloadRetryFail, formatBool(cfg.OneTimeDownloadRetryOnFailure))
|
|
cfg.captureDefaultValue(SettingRenewOnAccessEnabled, formatBool(cfg.RenewOnAccessEnabled))
|
|
cfg.captureDefaultValue(SettingRenewOnDownloadEnabled, formatBool(cfg.RenewOnDownloadEnabled))
|
|
cfg.captureDefaultValue(SettingDefaultGuestExpirySecs, strconv.FormatInt(cfg.DefaultGuestExpirySeconds, 10))
|
|
cfg.captureDefaultValue(SettingMaxGuestExpirySecs, strconv.FormatInt(cfg.MaxGuestExpirySeconds, 10))
|
|
cfg.captureDefaultValue(SettingGlobalMaxFileSizeBytes, formatGigabytesFromBytes(cfg.GlobalMaxFileSizeBytes))
|
|
cfg.captureDefaultValue(SettingGlobalMaxBoxSizeBytes, formatGigabytesFromBytes(cfg.GlobalMaxBoxSizeBytes))
|
|
cfg.captureDefaultValue(SettingDefaultUserMaxFileBytes, formatGigabytesFromBytes(cfg.DefaultUserMaxFileSizeBytes))
|
|
cfg.captureDefaultValue(SettingDefaultUserMaxBoxBytes, formatGigabytesFromBytes(cfg.DefaultUserMaxBoxSizeBytes))
|
|
cfg.captureDefaultValue(SettingSessionTTLSeconds, strconv.FormatInt(cfg.SessionTTLSeconds, 10))
|
|
cfg.captureDefaultValue(SettingBoxPollIntervalMS, strconv.Itoa(cfg.BoxPollIntervalMS))
|
|
cfg.captureDefaultValue(SettingThumbnailBatchSize, strconv.Itoa(cfg.ThumbnailBatchSize))
|
|
cfg.captureDefaultValue(SettingThumbnailIntervalSeconds, strconv.Itoa(cfg.ThumbnailIntervalSeconds))
|
|
}
|
|
|
|
func (cfg *Config) captureDefaultValue(key string, value string) {
|
|
cfg.setValue(key, value, SourceDefault)
|
|
if cfg.defaults != nil {
|
|
cfg.defaults[key] = value
|
|
}
|
|
}
|
|
|
|
func (cfg *Config) applyStringEnv(key string, name string, target *string) error {
|
|
raw := os.Getenv(name)
|
|
if raw == "" {
|
|
return nil
|
|
}
|
|
*target = raw
|
|
if key != "" {
|
|
cfg.setValue(key, raw, SourceEnv)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (cfg *Config) applyBoolEnv(key string, name string, target *bool) error {
|
|
raw := strings.TrimSpace(os.Getenv(name))
|
|
if raw == "" {
|
|
return nil
|
|
}
|
|
parsed, err := parseBool(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %w", name, err)
|
|
}
|
|
*target = parsed
|
|
if key != "" {
|
|
cfg.setValue(key, formatBool(parsed), SourceEnv)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (cfg *Config) applyInt64Env(key string, name string, min int64, target *int64) error {
|
|
raw := strings.TrimSpace(os.Getenv(name))
|
|
if raw == "" {
|
|
return nil
|
|
}
|
|
parsed, err := parseInt64(raw, min)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %w", name, err)
|
|
}
|
|
*target = parsed
|
|
if key != "" {
|
|
cfg.setValue(key, strconv.FormatInt(parsed, 10), SourceEnv)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (cfg *Config) applySizeEnv(key string, gbName string, mbName string, bytesName string, min int64, target *int64) error {
|
|
if rawGB := strings.TrimSpace(os.Getenv(gbName)); rawGB != "" {
|
|
parsed, err := parseGigabytes(rawGB, float64(min))
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %w", gbName, err)
|
|
}
|
|
*target = parsed
|
|
cfg.setValue(key, formatGigabytesFromBytes(parsed), SourceEnv)
|
|
return nil
|
|
}
|
|
if rawBytes := strings.TrimSpace(os.Getenv(bytesName)); rawBytes != "" {
|
|
parsed, err := parseInt64(rawBytes, min)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %w", bytesName, err)
|
|
}
|
|
*target = parsed
|
|
cfg.setValue(key, formatGigabytesFromBytes(parsed), SourceEnv)
|
|
return nil
|
|
}
|
|
|
|
rawMB := strings.TrimSpace(os.Getenv(mbName))
|
|
if rawMB == "" {
|
|
return nil
|
|
}
|
|
parsedMB, err := parseInt64(rawMB, min)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %w", mbName, err)
|
|
}
|
|
parsedBytes := parsedMB * 1000 * 1000
|
|
*target = parsedBytes
|
|
cfg.setValue(key, formatGigabytesFromBytes(parsedBytes), SourceEnv)
|
|
return nil
|
|
}
|
|
|
|
func (cfg *Config) applyIntEnv(key string, name string, min int, target *int) error {
|
|
raw := strings.TrimSpace(os.Getenv(name))
|
|
if raw == "" {
|
|
return nil
|
|
}
|
|
parsed, err := parseInt(raw, min)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %w", name, err)
|
|
}
|
|
*target = parsed
|
|
if key != "" {
|
|
cfg.setValue(key, strconv.Itoa(parsed), SourceEnv)
|
|
}
|
|
return nil
|
|
}
|