- Add `applyMegabytesOrBytesEnv` to accept size settings in either bytes or MB - Prefer `*_BYTES` when set, otherwise convert `*_MB` to bytes with overflow guard - Add coverage for MB-based environment overrides - Introduce `static/js/upload-popups.js` to lazy-load and cache popup templatesfeat(config): support *_MB env vars for upload size limits - Add `applyMegabytesOrBytesEnv` to accept size settings in either bytes or MB - Prefer `*_BYTES` when set, otherwise convert `*_MB` to bytes with overflow guard - Add coverage for MB-based environment overrides - Introduce `static/js/upload-popups.js` to lazy-load and cache popup templates
565 lines
19 KiB
Go
565 lines
19 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type Source string
|
|
|
|
const (
|
|
SourceDefault Source = "default"
|
|
SourceEnv Source = "environment"
|
|
SourceDB Source = "db override"
|
|
)
|
|
|
|
type AdminEnabledMode string
|
|
|
|
const (
|
|
AdminEnabledAuto AdminEnabledMode = "auto"
|
|
AdminEnabledTrue AdminEnabledMode = "true"
|
|
AdminEnabledFalse AdminEnabledMode = "false"
|
|
)
|
|
|
|
const (
|
|
SettingGuestUploadsEnabled = "guest_uploads_enabled"
|
|
SettingAPIEnabled = "api_enabled"
|
|
SettingZipDownloadsEnabled = "zip_downloads_enabled"
|
|
SettingOneTimeDownloadsEnabled = "one_time_downloads_enabled"
|
|
SettingRenewOnAccessEnabled = "renew_on_access_enabled"
|
|
SettingRenewOnDownloadEnabled = "renew_on_download_enabled"
|
|
SettingDefaultGuestExpirySecs = "default_guest_expiry_seconds"
|
|
SettingMaxGuestExpirySecs = "max_guest_expiry_seconds"
|
|
SettingGlobalMaxFileSizeBytes = "global_max_file_size_bytes"
|
|
SettingGlobalMaxBoxSizeBytes = "global_max_box_size_bytes"
|
|
SettingDefaultUserMaxFileBytes = "default_user_max_file_size_bytes"
|
|
SettingDefaultUserMaxBoxBytes = "default_user_max_box_size_bytes"
|
|
SettingSessionTTLSeconds = "session_ttl_seconds"
|
|
SettingBoxPollIntervalMS = "box_poll_interval_ms"
|
|
SettingThumbnailBatchSize = "thumbnail_batch_size"
|
|
SettingThumbnailIntervalSeconds = "thumbnail_interval_seconds"
|
|
SettingDataDir = "data_dir"
|
|
)
|
|
|
|
type SettingType string
|
|
|
|
const (
|
|
SettingTypeBool SettingType = "bool"
|
|
SettingTypeInt64 SettingType = "int64"
|
|
SettingTypeInt SettingType = "int"
|
|
SettingTypeText SettingType = "text"
|
|
)
|
|
|
|
type SettingDefinition struct {
|
|
Key string
|
|
EnvName string
|
|
Label string
|
|
Type SettingType
|
|
Editable bool
|
|
HardLimit bool
|
|
Minimum int64
|
|
}
|
|
|
|
type SettingRow struct {
|
|
Definition SettingDefinition
|
|
Value string
|
|
Source Source
|
|
}
|
|
|
|
type Config struct {
|
|
DataDir string
|
|
UploadsDir string
|
|
DBDir string
|
|
|
|
AdminPassword string
|
|
AdminUsername string
|
|
AdminEmail string
|
|
AdminEnabled AdminEnabledMode
|
|
AdminCookieSecure bool
|
|
AllowAdminSettingsOverride bool
|
|
|
|
GuestUploadsEnabled bool
|
|
APIEnabled bool
|
|
ZipDownloadsEnabled bool
|
|
OneTimeDownloadsEnabled bool
|
|
RenewOnAccessEnabled bool
|
|
RenewOnDownloadEnabled bool
|
|
|
|
DefaultGuestExpirySeconds int64
|
|
MaxGuestExpirySeconds int64
|
|
GlobalMaxFileSizeBytes int64
|
|
GlobalMaxBoxSizeBytes int64
|
|
DefaultUserMaxFileSizeBytes int64
|
|
DefaultUserMaxBoxSizeBytes int64
|
|
SessionTTLSeconds int64
|
|
BoxPollIntervalMS int
|
|
ThumbnailBatchSize int
|
|
ThumbnailIntervalSeconds int
|
|
|
|
sources map[string]Source
|
|
values map[string]string
|
|
}
|
|
|
|
var Definitions = []SettingDefinition{
|
|
{Key: SettingDataDir, EnvName: "WARPBOX_DATA_DIR", Label: "Data directory", Type: SettingTypeText, Editable: false, HardLimit: true},
|
|
{Key: SettingGuestUploadsEnabled, EnvName: "WARPBOX_GUEST_UPLOADS_ENABLED", Label: "Guest uploads enabled", Type: SettingTypeBool, Editable: true},
|
|
{Key: SettingAPIEnabled, EnvName: "WARPBOX_API_ENABLED", Label: "API enabled", Type: SettingTypeBool, Editable: true},
|
|
{Key: SettingZipDownloadsEnabled, EnvName: "WARPBOX_ZIP_DOWNLOADS_ENABLED", Label: "ZIP downloads enabled", Type: SettingTypeBool, Editable: true},
|
|
{Key: SettingOneTimeDownloadsEnabled, EnvName: "WARPBOX_ONE_TIME_DOWNLOADS_ENABLED", Label: "One-time downloads enabled", Type: SettingTypeBool, Editable: true},
|
|
{Key: SettingRenewOnAccessEnabled, EnvName: "WARPBOX_RENEW_ON_ACCESS_ENABLED", Label: "Renew on access enabled", Type: SettingTypeBool, Editable: true},
|
|
{Key: SettingRenewOnDownloadEnabled, EnvName: "WARPBOX_RENEW_ON_DOWNLOAD_ENABLED", Label: "Renew on download enabled", Type: SettingTypeBool, Editable: true},
|
|
{Key: SettingDefaultGuestExpirySecs, EnvName: "WARPBOX_DEFAULT_GUEST_EXPIRY_SECONDS", Label: "Default guest expiry seconds", Type: SettingTypeInt64, Editable: true, Minimum: 0},
|
|
{Key: SettingMaxGuestExpirySecs, EnvName: "WARPBOX_MAX_GUEST_EXPIRY_SECONDS", Label: "Max guest expiry seconds", Type: SettingTypeInt64, Editable: true, Minimum: 0},
|
|
{Key: SettingGlobalMaxFileSizeBytes, EnvName: "WARPBOX_GLOBAL_MAX_FILE_SIZE_BYTES", Label: "Global max file size bytes", Type: SettingTypeInt64, Editable: false, HardLimit: true, Minimum: 0},
|
|
{Key: SettingGlobalMaxBoxSizeBytes, EnvName: "WARPBOX_GLOBAL_MAX_BOX_SIZE_BYTES", Label: "Global max box size bytes", Type: SettingTypeInt64, Editable: false, HardLimit: true, Minimum: 0},
|
|
{Key: SettingDefaultUserMaxFileBytes, EnvName: "WARPBOX_DEFAULT_USER_MAX_FILE_SIZE_BYTES", Label: "Default user max file size bytes", Type: SettingTypeInt64, Editable: true, Minimum: 0},
|
|
{Key: SettingDefaultUserMaxBoxBytes, EnvName: "WARPBOX_DEFAULT_USER_MAX_BOX_SIZE_BYTES", Label: "Default user max box size bytes", Type: SettingTypeInt64, Editable: true, Minimum: 0},
|
|
{Key: SettingSessionTTLSeconds, EnvName: "WARPBOX_SESSION_TTL_SECONDS", Label: "Session TTL seconds", Type: SettingTypeInt64, Editable: true, Minimum: 60},
|
|
{Key: SettingBoxPollIntervalMS, EnvName: "WARPBOX_BOX_POLL_INTERVAL_MS", Label: "Box poll interval milliseconds", Type: SettingTypeInt, Editable: true, Minimum: 1000},
|
|
{Key: SettingThumbnailBatchSize, EnvName: "WARPBOX_THUMBNAIL_BATCH_SIZE", Label: "Thumbnail batch size", Type: SettingTypeInt, Editable: true, Minimum: 1},
|
|
{Key: SettingThumbnailIntervalSeconds, EnvName: "WARPBOX_THUMBNAIL_INTERVAL_SECONDS", Label: "Thumbnail interval seconds", Type: SettingTypeInt, Editable: true, Minimum: 1},
|
|
}
|
|
|
|
func Load() (*Config, error) {
|
|
cfg := &Config{
|
|
DataDir: "./data",
|
|
AdminUsername: "admin",
|
|
AdminEnabled: AdminEnabledAuto,
|
|
AllowAdminSettingsOverride: true,
|
|
GuestUploadsEnabled: true,
|
|
APIEnabled: true,
|
|
ZipDownloadsEnabled: true,
|
|
OneTimeDownloadsEnabled: true,
|
|
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),
|
|
}
|
|
|
|
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},
|
|
{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},
|
|
{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
|
|
mbName string
|
|
bytesName string
|
|
target *int64
|
|
}{
|
|
{SettingGlobalMaxFileSizeBytes, "WARPBOX_GLOBAL_MAX_FILE_SIZE_MB", "WARPBOX_GLOBAL_MAX_FILE_SIZE_BYTES", &cfg.GlobalMaxFileSizeBytes},
|
|
{SettingGlobalMaxBoxSizeBytes, "WARPBOX_GLOBAL_MAX_BOX_SIZE_MB", "WARPBOX_GLOBAL_MAX_BOX_SIZE_BYTES", &cfg.GlobalMaxBoxSizeBytes},
|
|
{SettingDefaultUserMaxFileBytes, "WARPBOX_DEFAULT_USER_MAX_FILE_SIZE_MB", "WARPBOX_DEFAULT_USER_MAX_FILE_SIZE_BYTES", &cfg.DefaultUserMaxFileSizeBytes},
|
|
{SettingDefaultUserMaxBoxBytes, "WARPBOX_DEFAULT_USER_MAX_BOX_SIZE_MB", "WARPBOX_DEFAULT_USER_MAX_BOX_SIZE_BYTES", &cfg.DefaultUserMaxBoxSizeBytes},
|
|
}
|
|
for _, item := range sizeEnvVars {
|
|
if err := cfg.applyMegabytesOrBytesEnv(item.key, 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) ApplyOverrides(overrides map[string]string) error {
|
|
if !cfg.AllowAdminSettingsOverride {
|
|
return nil
|
|
}
|
|
for key, value := range overrides {
|
|
if err := cfg.ApplyOverride(key, value); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (cfg *Config) ApplyOverride(key string, value string) error {
|
|
def, ok := Definition(key)
|
|
if !ok {
|
|
return fmt.Errorf("unknown setting %q", key)
|
|
}
|
|
if !def.Editable || def.HardLimit {
|
|
return fmt.Errorf("setting %q cannot be changed from the admin UI", key)
|
|
}
|
|
|
|
switch def.Type {
|
|
case SettingTypeBool:
|
|
parsed, err := parseBool(value)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %w", key, err)
|
|
}
|
|
cfg.assignBool(key, parsed, SourceDB)
|
|
case SettingTypeInt64:
|
|
parsed, err := parseInt64(value, def.Minimum)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %w", key, err)
|
|
}
|
|
cfg.assignInt64(key, parsed, SourceDB)
|
|
case SettingTypeInt:
|
|
parsed64, err := parseInt64(value, def.Minimum)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %w", key, err)
|
|
}
|
|
cfg.assignInt(key, int(parsed64), SourceDB)
|
|
default:
|
|
return fmt.Errorf("setting %q is not runtime editable", key)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (cfg *Config) SettingRows() []SettingRow {
|
|
rows := make([]SettingRow, 0, len(Definitions))
|
|
for _, def := range Definitions {
|
|
rows = append(rows, SettingRow{
|
|
Definition: def,
|
|
Value: cfg.values[def.Key],
|
|
Source: cfg.sourceFor(def.Key),
|
|
})
|
|
}
|
|
return rows
|
|
}
|
|
|
|
func (cfg *Config) Source(key string) Source {
|
|
return cfg.sourceFor(key)
|
|
}
|
|
|
|
func (cfg *Config) AdminLoginEnabled(hasAdminUser bool) bool {
|
|
switch cfg.AdminEnabled {
|
|
case AdminEnabledFalse:
|
|
return false
|
|
case AdminEnabledTrue:
|
|
return hasAdminUser
|
|
default:
|
|
return hasAdminUser
|
|
}
|
|
}
|
|
|
|
func Definition(key string) (SettingDefinition, bool) {
|
|
for _, def := range Definitions {
|
|
if def.Key == key {
|
|
return def, true
|
|
}
|
|
}
|
|
return SettingDefinition{}, false
|
|
}
|
|
|
|
func EditableDefinitions() []SettingDefinition {
|
|
defs := make([]SettingDefinition, 0, len(Definitions))
|
|
for _, def := range Definitions {
|
|
if def.Editable && !def.HardLimit {
|
|
defs = append(defs, def)
|
|
}
|
|
}
|
|
return defs
|
|
}
|
|
|
|
func (cfg *Config) captureDefaults() {
|
|
cfg.setValue(SettingDataDir, cfg.DataDir, SourceDefault)
|
|
cfg.setValue(SettingGuestUploadsEnabled, formatBool(cfg.GuestUploadsEnabled), SourceDefault)
|
|
cfg.setValue(SettingAPIEnabled, formatBool(cfg.APIEnabled), SourceDefault)
|
|
cfg.setValue(SettingZipDownloadsEnabled, formatBool(cfg.ZipDownloadsEnabled), SourceDefault)
|
|
cfg.setValue(SettingOneTimeDownloadsEnabled, formatBool(cfg.OneTimeDownloadsEnabled), SourceDefault)
|
|
cfg.setValue(SettingRenewOnAccessEnabled, formatBool(cfg.RenewOnAccessEnabled), SourceDefault)
|
|
cfg.setValue(SettingRenewOnDownloadEnabled, formatBool(cfg.RenewOnDownloadEnabled), SourceDefault)
|
|
cfg.setValue(SettingDefaultGuestExpirySecs, strconv.FormatInt(cfg.DefaultGuestExpirySeconds, 10), SourceDefault)
|
|
cfg.setValue(SettingMaxGuestExpirySecs, strconv.FormatInt(cfg.MaxGuestExpirySeconds, 10), SourceDefault)
|
|
cfg.setValue(SettingGlobalMaxFileSizeBytes, strconv.FormatInt(cfg.GlobalMaxFileSizeBytes, 10), SourceDefault)
|
|
cfg.setValue(SettingGlobalMaxBoxSizeBytes, strconv.FormatInt(cfg.GlobalMaxBoxSizeBytes, 10), SourceDefault)
|
|
cfg.setValue(SettingDefaultUserMaxFileBytes, strconv.FormatInt(cfg.DefaultUserMaxFileSizeBytes, 10), SourceDefault)
|
|
cfg.setValue(SettingDefaultUserMaxBoxBytes, strconv.FormatInt(cfg.DefaultUserMaxBoxSizeBytes, 10), SourceDefault)
|
|
cfg.setValue(SettingSessionTTLSeconds, strconv.FormatInt(cfg.SessionTTLSeconds, 10), SourceDefault)
|
|
cfg.setValue(SettingBoxPollIntervalMS, strconv.Itoa(cfg.BoxPollIntervalMS), SourceDefault)
|
|
cfg.setValue(SettingThumbnailBatchSize, strconv.Itoa(cfg.ThumbnailBatchSize), SourceDefault)
|
|
cfg.setValue(SettingThumbnailIntervalSeconds, strconv.Itoa(cfg.ThumbnailIntervalSeconds), SourceDefault)
|
|
}
|
|
|
|
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) applyMegabytesOrBytesEnv(key string, mbName string, bytesName string, min int64, target *int64) error {
|
|
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, strconv.FormatInt(parsed, 10), 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)
|
|
}
|
|
if parsedMB > math.MaxInt64/(1024*1024) {
|
|
return fmt.Errorf("%s: is too large", mbName)
|
|
}
|
|
parsedBytes := parsedMB * 1024 * 1024
|
|
*target = parsedBytes
|
|
cfg.setValue(key, strconv.FormatInt(parsedBytes, 10), 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
|
|
}
|
|
|
|
func (cfg *Config) assignBool(key string, value bool, source Source) {
|
|
switch key {
|
|
case SettingGuestUploadsEnabled:
|
|
cfg.GuestUploadsEnabled = value
|
|
case SettingAPIEnabled:
|
|
cfg.APIEnabled = value
|
|
case SettingZipDownloadsEnabled:
|
|
cfg.ZipDownloadsEnabled = value
|
|
case SettingOneTimeDownloadsEnabled:
|
|
cfg.OneTimeDownloadsEnabled = value
|
|
case SettingRenewOnAccessEnabled:
|
|
cfg.RenewOnAccessEnabled = value
|
|
case SettingRenewOnDownloadEnabled:
|
|
cfg.RenewOnDownloadEnabled = value
|
|
}
|
|
cfg.setValue(key, formatBool(value), source)
|
|
}
|
|
|
|
func (cfg *Config) assignInt64(key string, value int64, source Source) {
|
|
switch key {
|
|
case SettingDefaultGuestExpirySecs:
|
|
cfg.DefaultGuestExpirySeconds = value
|
|
case SettingMaxGuestExpirySecs:
|
|
cfg.MaxGuestExpirySeconds = value
|
|
case SettingDefaultUserMaxFileBytes:
|
|
cfg.DefaultUserMaxFileSizeBytes = value
|
|
case SettingDefaultUserMaxBoxBytes:
|
|
cfg.DefaultUserMaxBoxSizeBytes = value
|
|
case SettingSessionTTLSeconds:
|
|
cfg.SessionTTLSeconds = value
|
|
}
|
|
cfg.setValue(key, strconv.FormatInt(value, 10), source)
|
|
}
|
|
|
|
func (cfg *Config) assignInt(key string, value int, source Source) {
|
|
switch key {
|
|
case SettingBoxPollIntervalMS:
|
|
cfg.BoxPollIntervalMS = value
|
|
case SettingThumbnailBatchSize:
|
|
cfg.ThumbnailBatchSize = value
|
|
case SettingThumbnailIntervalSeconds:
|
|
cfg.ThumbnailIntervalSeconds = value
|
|
}
|
|
cfg.setValue(key, strconv.Itoa(value), source)
|
|
}
|
|
|
|
func (cfg *Config) setValue(key string, value string, source Source) {
|
|
if key == "" {
|
|
return
|
|
}
|
|
cfg.values[key] = value
|
|
cfg.sources[key] = source
|
|
}
|
|
|
|
func (cfg *Config) sourceFor(key string) Source {
|
|
source, ok := cfg.sources[key]
|
|
if !ok {
|
|
return SourceDefault
|
|
}
|
|
return source
|
|
}
|
|
|
|
func parseBool(value string) (bool, error) {
|
|
switch strings.ToLower(strings.TrimSpace(value)) {
|
|
case "1", "t", "true", "y", "yes", "on":
|
|
return true, nil
|
|
case "0", "f", "false", "n", "no", "off":
|
|
return false, nil
|
|
default:
|
|
return false, fmt.Errorf("must be a boolean")
|
|
}
|
|
}
|
|
|
|
func parseInt64(value string, min int64) (int64, error) {
|
|
parsed, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("must be an integer")
|
|
}
|
|
if parsed < min {
|
|
return 0, fmt.Errorf("must be at least %d", min)
|
|
}
|
|
return parsed, nil
|
|
}
|
|
|
|
func parseInt(value string, min int) (int, error) {
|
|
parsed64, err := parseInt64(value, int64(min))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if parsed64 > int64(^uint(0)>>1) {
|
|
return 0, fmt.Errorf("is too large")
|
|
}
|
|
return int(parsed64), nil
|
|
}
|
|
|
|
func formatBool(value bool) string {
|
|
if value {
|
|
return "true"
|
|
}
|
|
return "false"
|
|
}
|