2026-04-28 21:11:37 +03:00
package config
import (
"fmt"
2026-04-29 01:42:41 +03:00
"math"
2026-04-28 21:11:37 +03:00
"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 (
2026-04-30 03:54:50 +03:00
SettingGuestUploadsEnabled = "guest_uploads_enabled"
SettingAPIEnabled = "api_enabled"
SettingZipDownloadsEnabled = "zip_downloads_enabled"
SettingOneTimeDownloadsEnabled = "one_time_downloads_enabled"
SettingOneTimeDownloadExpirySecs = "one_time_download_expiry_seconds"
2026-04-30 04:24:49 +03:00
SettingOneTimeDownloadRetryFail = "one_time_download_retry_on_failure"
2026-04-30 03:54:50 +03:00
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"
2026-04-28 21:11:37 +03:00
)
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
2026-04-30 04:24:49 +03:00
GuestUploadsEnabled bool
APIEnabled bool
ZipDownloadsEnabled bool
OneTimeDownloadsEnabled bool
OneTimeDownloadExpirySeconds int64
OneTimeDownloadRetryOnFailure bool
RenewOnAccessEnabled bool
RenewOnDownloadEnabled bool
2026-04-28 21:11:37 +03:00
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 } ,
2026-04-30 03:54:50 +03:00
{ Key : SettingOneTimeDownloadExpirySecs , EnvName : "WARPBOX_ONE_TIME_DOWNLOAD_EXPIRY_SECONDS" , Label : "One-time download expiry seconds" , Type : SettingTypeInt64 , Editable : true , Minimum : 0 } ,
2026-04-30 04:24:49 +03:00
{ Key : SettingOneTimeDownloadRetryFail , EnvName : "WARPBOX_ONE_TIME_DOWNLOAD_RETRY_ON_FAILURE" , Label : "One-time download retry on failure" , Type : SettingTypeBool , Editable : false } ,
2026-04-28 21:11:37 +03:00
{ 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 {
2026-04-30 04:24:49 +03:00
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 ) ,
2026-04-28 21:11:37 +03:00
}
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 } ,
2026-04-30 04:24:49 +03:00
{ SettingOneTimeDownloadRetryFail , "WARPBOX_ONE_TIME_DOWNLOAD_RETRY_ON_FAILURE" , & cfg . OneTimeDownloadRetryOnFailure } ,
2026-04-28 21:11:37 +03:00
{ 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 } ,
2026-04-30 03:54:50 +03:00
{ SettingOneTimeDownloadExpirySecs , "WARPBOX_ONE_TIME_DOWNLOAD_EXPIRY_SECONDS" , 0 , & cfg . OneTimeDownloadExpirySeconds } ,
2026-04-28 21:11:37 +03:00
{ 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
}
}
2026-04-29 01:42:41 +03:00
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
}
}
2026-04-28 21:11:37 +03:00
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 )
2026-04-30 03:54:50 +03:00
cfg . setValue ( SettingOneTimeDownloadExpirySecs , strconv . FormatInt ( cfg . OneTimeDownloadExpirySeconds , 10 ) , SourceDefault )
2026-04-30 04:24:49 +03:00
cfg . setValue ( SettingOneTimeDownloadRetryFail , formatBool ( cfg . OneTimeDownloadRetryOnFailure ) , SourceDefault )
2026-04-28 21:11:37 +03:00
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
}
2026-04-29 01:42:41 +03:00
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
}
2026-04-28 21:11:37 +03:00
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
2026-04-30 03:54:50 +03:00
case SettingOneTimeDownloadExpirySecs :
cfg . OneTimeDownloadExpirySeconds = value
2026-04-28 21:11:37 +03:00
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"
}