Adds configuration options and environment variables to manage box owner policies, including settings for refresh counts and expiry.
281 lines
11 KiB
Go
281 lines
11 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"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,
|
|
BoxOwnerEditEnabled: true,
|
|
BoxOwnerRefreshEnabled: true,
|
|
BoxOwnerMaxRefreshCount: 3,
|
|
BoxOwnerMaxRefreshAmountSeconds: 24 * 60 * 60,
|
|
BoxOwnerMaxTotalExpirySeconds: 7 * 24 * 60 * 60,
|
|
BoxOwnerPasswordEditEnabled: true,
|
|
sources: make(map[string]Source),
|
|
values: 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},
|
|
{SettingBoxOwnerEditEnabled, "WARPBOX_BOX_OWNER_EDIT_ENABLED", &cfg.BoxOwnerEditEnabled},
|
|
{SettingBoxOwnerRefreshEnabled, "WARPBOX_BOX_OWNER_REFRESH_ENABLED", &cfg.BoxOwnerRefreshEnabled},
|
|
{SettingBoxOwnerPasswordEdit, "WARPBOX_BOX_OWNER_PASSWORD_EDIT_ENABLED", &cfg.BoxOwnerPasswordEditEnabled},
|
|
}
|
|
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},
|
|
{SettingBoxOwnerMaxRefreshAmount, "WARPBOX_BOX_OWNER_MAX_REFRESH_AMOUNT_SECONDS", 0, &cfg.BoxOwnerMaxRefreshAmountSeconds},
|
|
{SettingBoxOwnerMaxTotalExpiry, "WARPBOX_BOX_OWNER_MAX_TOTAL_EXPIRY_SECONDS", 0, &cfg.BoxOwnerMaxTotalExpirySeconds},
|
|
}
|
|
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},
|
|
{SettingBoxOwnerMaxRefreshCount, "WARPBOX_BOX_OWNER_MAX_REFRESH_COUNT", 0, &cfg.BoxOwnerMaxRefreshCount},
|
|
}
|
|
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.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(SettingOneTimeDownloadExpirySecs, strconv.FormatInt(cfg.OneTimeDownloadExpirySeconds, 10), SourceDefault)
|
|
cfg.setValue(SettingOneTimeDownloadRetryFail, formatBool(cfg.OneTimeDownloadRetryOnFailure), 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)
|
|
cfg.setValue(SettingBoxOwnerEditEnabled, formatBool(cfg.BoxOwnerEditEnabled), SourceDefault)
|
|
cfg.setValue(SettingBoxOwnerRefreshEnabled, formatBool(cfg.BoxOwnerRefreshEnabled), SourceDefault)
|
|
cfg.setValue(SettingBoxOwnerMaxRefreshCount, strconv.Itoa(cfg.BoxOwnerMaxRefreshCount), SourceDefault)
|
|
cfg.setValue(SettingBoxOwnerMaxRefreshAmount, strconv.FormatInt(cfg.BoxOwnerMaxRefreshAmountSeconds, 10), SourceDefault)
|
|
cfg.setValue(SettingBoxOwnerMaxTotalExpiry, strconv.FormatInt(cfg.BoxOwnerMaxTotalExpirySeconds, 10), SourceDefault)
|
|
cfg.setValue(SettingBoxOwnerPasswordEdit, formatBool(cfg.BoxOwnerPasswordEditEnabled), 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
|
|
}
|