2 Commits

Author SHA1 Message Date
0bdf11d3a7 patch(version): Implemented version reporting for the app
Some checks failed
Build and Publish Docker Image / deploy (push) Has been cancelled
2026-05-04 00:40:02 +03:00
bcdcce8fbd feat(env): Added production and development environment
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m44s
2026-05-04 00:33:18 +03:00
9 changed files with 64 additions and 0 deletions

View File

@@ -28,6 +28,9 @@ func TestDefaults(t *testing.T) {
if cfg.AdminUsername != "admin" { if cfg.AdminUsername != "admin" {
t.Fatalf("unexpected admin username: %s", cfg.AdminUsername) t.Fatalf("unexpected admin username: %s", cfg.AdminUsername)
} }
if cfg.Environment != AppEnvironmentDevelopment {
t.Fatalf("unexpected default environment: %s", cfg.Environment)
}
if cfg.AdminPassword != "" { if cfg.AdminPassword != "" {
t.Fatal("expected default admin password to be empty") t.Fatal("expected default admin password to be empty")
} }
@@ -43,6 +46,7 @@ func TestEnvironmentOverrides(t *testing.T) {
t.Setenv("WARPBOX_ADMIN_USERNAME", "root") t.Setenv("WARPBOX_ADMIN_USERNAME", "root")
t.Setenv("WARPBOX_ONE_TIME_DOWNLOAD_RETRY_ON_FAILURE", "true") t.Setenv("WARPBOX_ONE_TIME_DOWNLOAD_RETRY_ON_FAILURE", "true")
t.Setenv("WARPBOX_SECURITY_ENABLED", "false") t.Setenv("WARPBOX_SECURITY_ENABLED", "false")
t.Setenv("WARPBOX_ENV", "production")
cfg, err := Load() cfg, err := Load()
if err != nil { if err != nil {
@@ -73,6 +77,9 @@ func TestEnvironmentOverrides(t *testing.T) {
if cfg.Source(SettingAPIEnabled) != SourceEnv { if cfg.Source(SettingAPIEnabled) != SourceEnv {
t.Fatalf("expected API setting source to be env, got %s", cfg.Source(SettingAPIEnabled)) t.Fatalf("expected API setting source to be env, got %s", cfg.Source(SettingAPIEnabled))
} }
if cfg.Environment != AppEnvironmentProduction {
t.Fatalf("expected environment override to be production, got %s", cfg.Environment)
}
} }
func TestMegabyteSizeEnvironmentOverrides(t *testing.T) { func TestMegabyteSizeEnvironmentOverrides(t *testing.T) {
@@ -120,6 +127,12 @@ func TestInvalidEnvironmentValues(t *testing.T) {
if _, err := Load(); err == nil { if _, err := Load(); err == nil {
t.Fatal("expected invalid boolean to fail") t.Fatal("expected invalid boolean to fail")
} }
clearConfigEnv(t)
t.Setenv("WARPBOX_ENV", "staging")
if _, err := Load(); err == nil {
t.Fatal("expected invalid environment mode to fail")
}
} }
func TestSettingsOverridePrecedence(t *testing.T) { func TestSettingsOverridePrecedence(t *testing.T) {
@@ -170,6 +183,7 @@ func clearConfigEnv(t *testing.T) {
"WARPBOX_ADMIN_PASSWORD", "WARPBOX_ADMIN_PASSWORD",
"WARPBOX_ADMIN_USERNAME", "WARPBOX_ADMIN_USERNAME",
"WARPBOX_ADMIN_EMAIL", "WARPBOX_ADMIN_EMAIL",
"WARPBOX_ENV",
"WARPBOX_ADMIN_ENABLED", "WARPBOX_ADMIN_ENABLED",
"WARPBOX_ALLOW_ADMIN_SETTINGS_OVERRIDE", "WARPBOX_ALLOW_ADMIN_SETTINGS_OVERRIDE",
"WARPBOX_ADMIN_COOKIE_SECURE", "WARPBOX_ADMIN_COOKIE_SECURE",

View File

@@ -2,6 +2,7 @@ package config
var Definitions = []SettingDefinition{ var Definitions = []SettingDefinition{
{Key: SettingDataDir, EnvName: "WARPBOX_DATA_DIR", Label: "Data directory", Type: SettingTypeText, Editable: false, HardLimit: true}, {Key: SettingDataDir, EnvName: "WARPBOX_DATA_DIR", Label: "Data directory", Type: SettingTypeText, Editable: false, HardLimit: true},
{Key: SettingEnvironment, EnvName: "WARPBOX_ENV", Label: "Environment", Type: SettingTypeText, Editable: false, HardLimit: true},
{Key: SettingGuestUploadsEnabled, EnvName: "WARPBOX_GUEST_UPLOADS_ENABLED", Label: "Guest uploads enabled", Type: SettingTypeBool, Editable: 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: 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: SettingZipDownloadsEnabled, EnvName: "WARPBOX_ZIP_DOWNLOADS_ENABLED", Label: "ZIP downloads enabled", Type: SettingTypeBool, Editable: true},

View File

@@ -11,6 +11,7 @@ import (
func Load() (*Config, error) { func Load() (*Config, error) {
cfg := &Config{ cfg := &Config{
DataDir: "./data", DataDir: "./data",
Environment: AppEnvironmentDevelopment,
AdminUsername: "admin", AdminUsername: "admin",
AdminEnabled: AdminEnabledAuto, AdminEnabled: AdminEnabledAuto,
AllowAdminSettingsOverride: true, AllowAdminSettingsOverride: true,
@@ -49,6 +50,14 @@ func Load() (*Config, error) {
if err := cfg.applyStringEnv(SettingDataDir, "WARPBOX_DATA_DIR", &cfg.DataDir); err != nil { if err := cfg.applyStringEnv(SettingDataDir, "WARPBOX_DATA_DIR", &cfg.DataDir); err != nil {
return nil, err return nil, err
} }
if raw := strings.TrimSpace(os.Getenv("WARPBOX_ENV")); raw != "" {
env := AppEnvironment(strings.ToLower(raw))
if env != AppEnvironmentDevelopment && env != AppEnvironmentProduction {
return nil, fmt.Errorf("WARPBOX_ENV must be development or production")
}
cfg.Environment = env
cfg.setValue(SettingEnvironment, string(env), SourceEnv)
}
if err := cfg.applyStringEnv("", "WARPBOX_ADMIN_PASSWORD", &cfg.AdminPassword); err != nil { if err := cfg.applyStringEnv("", "WARPBOX_ADMIN_PASSWORD", &cfg.AdminPassword); err != nil {
return nil, err return nil, err
} }
@@ -194,6 +203,7 @@ func (cfg *Config) EnsureDirectories() error {
} }
func (cfg *Config) captureDefaults() { func (cfg *Config) captureDefaults() {
cfg.captureDefaultValue(SettingDataDir, cfg.DataDir) cfg.captureDefaultValue(SettingDataDir, cfg.DataDir)
cfg.captureDefaultValue(SettingEnvironment, string(cfg.Environment))
cfg.captureDefaultValue(SettingGuestUploadsEnabled, formatBool(cfg.GuestUploadsEnabled)) cfg.captureDefaultValue(SettingGuestUploadsEnabled, formatBool(cfg.GuestUploadsEnabled))
cfg.captureDefaultValue(SettingAPIEnabled, formatBool(cfg.APIEnabled)) cfg.captureDefaultValue(SettingAPIEnabled, formatBool(cfg.APIEnabled))
cfg.captureDefaultValue(SettingZipDownloadsEnabled, formatBool(cfg.ZipDownloadsEnabled)) cfg.captureDefaultValue(SettingZipDownloadsEnabled, formatBool(cfg.ZipDownloadsEnabled))

View File

@@ -16,6 +16,13 @@ const (
AdminEnabledFalse AdminEnabledMode = "false" AdminEnabledFalse AdminEnabledMode = "false"
) )
type AppEnvironment string
const (
AppEnvironmentDevelopment AppEnvironment = "development"
AppEnvironmentProduction AppEnvironment = "production"
)
const ( const (
SettingGuestUploadsEnabled = "guest_uploads_enabled" SettingGuestUploadsEnabled = "guest_uploads_enabled"
SettingAPIEnabled = "api_enabled" SettingAPIEnabled = "api_enabled"
@@ -36,6 +43,7 @@ const (
SettingThumbnailBatchSize = "thumbnail_batch_size" SettingThumbnailBatchSize = "thumbnail_batch_size"
SettingThumbnailIntervalSeconds = "thumbnail_interval_seconds" SettingThumbnailIntervalSeconds = "thumbnail_interval_seconds"
SettingDataDir = "data_dir" SettingDataDir = "data_dir"
SettingEnvironment = "environment"
SettingActivityRetentionSeconds = "activity_retention_seconds" SettingActivityRetentionSeconds = "activity_retention_seconds"
SettingSecurityEnabled = "security_enabled" SettingSecurityEnabled = "security_enabled"
SettingSecurityIPWhitelist = "security_ip_whitelist" SettingSecurityIPWhitelist = "security_ip_whitelist"
@@ -86,6 +94,7 @@ type Config struct {
AdminPassword string AdminPassword string
AdminUsername string AdminUsername string
AdminEmail string AdminEmail string
Environment AppEnvironment
AdminEnabled AdminEnabledMode AdminEnabled AdminEnabledMode
AdminCookieSecure bool AdminCookieSecure bool
AllowAdminSettingsOverride bool AllowAdminSettingsOverride bool

View File

@@ -236,6 +236,7 @@ func clearAdminSettingsEnv(t *testing.T) {
"WARPBOX_ADMIN_PASSWORD", "WARPBOX_ADMIN_PASSWORD",
"WARPBOX_ADMIN_USERNAME", "WARPBOX_ADMIN_USERNAME",
"WARPBOX_ADMIN_EMAIL", "WARPBOX_ADMIN_EMAIL",
"WARPBOX_ENV",
"WARPBOX_ADMIN_ENABLED", "WARPBOX_ADMIN_ENABLED",
"WARPBOX_ALLOW_ADMIN_SETTINGS_OVERRIDE", "WARPBOX_ALLOW_ADMIN_SETTINGS_OVERRIDE",
"WARPBOX_ADMIN_COOKIE_SECURE", "WARPBOX_ADMIN_COOKIE_SECURE",

View File

@@ -24,6 +24,7 @@ func (app *App) handleIndex(ctx *gin.Context) {
"UploadsEnabled": app.config.GuestUploadsEnabled && app.config.APIEnabled, "UploadsEnabled": app.config.GuestUploadsEnabled && app.config.APIEnabled,
"MaxFileSizeBytes": app.config.GlobalMaxFileSizeBytes, "MaxFileSizeBytes": app.config.GlobalMaxFileSizeBytes,
"MaxBoxSizeBytes": app.config.GlobalMaxBoxSizeBytes, "MaxBoxSizeBytes": app.config.GlobalMaxBoxSizeBytes,
"AppVersion": app.appVersion,
}) })
} }

View File

@@ -3,7 +3,9 @@ package server
import ( import (
"encoding/json" "encoding/json"
"html/template" "html/template"
"os"
"path/filepath" "path/filepath"
"strings"
"time" "time"
"github.com/gin-contrib/gzip" "github.com/gin-contrib/gzip"
@@ -23,6 +25,7 @@ type App struct {
activityStore *activity.Store activityStore *activity.Store
alertStore *alerts.Store alertStore *alerts.Store
securityGuard *security.Guard securityGuard *security.Guard
appVersion string
} }
func Run(addr string) error { func Run(addr string) error {
@@ -30,6 +33,12 @@ func Run(addr string) error {
if err != nil { if err != nil {
return err return err
} }
switch cfg.Environment {
case config.AppEnvironmentProduction:
gin.SetMode(gin.ReleaseMode)
default:
gin.SetMode(gin.DebugMode)
}
if err := cfg.EnsureDirectories(); err != nil { if err := cfg.EnsureDirectories(); err != nil {
return err return err
} }
@@ -50,12 +59,14 @@ func Run(addr string) error {
activityStore: activity.NewStore(filepath.Join(cfg.DBDir, "activity_log.json")), activityStore: activity.NewStore(filepath.Join(cfg.DBDir, "activity_log.json")),
alertStore: alerts.NewStore(filepath.Join(cfg.DBDir, "alerts.json")), alertStore: alerts.NewStore(filepath.Join(cfg.DBDir, "alerts.json")),
securityGuard: security.NewGuard(), securityGuard: security.NewGuard(),
appVersion: currentAppVersion(),
} }
if err := app.reloadSecurityConfig(); err != nil { if err := app.reloadSecurityConfig(); err != nil {
return err return err
} }
router := gin.Default() router := gin.Default()
router.Use(app.versionHeaderMiddleware())
router.Use(app.securityMiddleware()) router.Use(app.securityMiddleware())
router.NoRoute(app.handleNoRoute) router.NoRoute(app.handleNoRoute)
htmlTemplates, err := loadHTMLTemplates() htmlTemplates, err := loadHTMLTemplates()
@@ -143,3 +154,18 @@ func (app *App) handleHealth(c *gin.Context) {
"status": "healthy", "status": "healthy",
}) })
} }
func (app *App) versionHeaderMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
ctx.Header("X-App-Version", app.appVersion)
ctx.Next()
}
}
func currentAppVersion() string {
version := strings.TrimSpace(os.Getenv("APP_VERSION"))
if version == "" {
return "dev"
}
return version
}

1
run.sh
View File

@@ -7,6 +7,7 @@ if [ -f .env ]; then
fi fi
# Core service switches. # Core service switches.
export WARPBOX_ENV="${WARPBOX_ENV:-development}"
export WARPBOX_GUEST_UPLOADS_ENABLED="${WARPBOX_GUEST_UPLOADS_ENABLED:-true}" export WARPBOX_GUEST_UPLOADS_ENABLED="${WARPBOX_GUEST_UPLOADS_ENABLED:-true}"
export WARPBOX_API_ENABLED="${WARPBOX_API_ENABLED:-true}" export WARPBOX_API_ENABLED="${WARPBOX_API_ENABLED:-true}"
export WARPBOX_ZIP_DOWNLOADS_ENABLED="${WARPBOX_ZIP_DOWNLOADS_ENABLED:-true}" export WARPBOX_ZIP_DOWNLOADS_ENABLED="${WARPBOX_ZIP_DOWNLOADS_ENABLED:-true}"

View File

@@ -126,6 +126,7 @@
<div class="win98-statusbar upload-statusbar"> <div class="win98-statusbar upload-statusbar">
<span id="status-text">{{ if .UploadsEnabled }}Ready · drag files anywhere onto the window{{ else }}Guest uploads are disabled{{ end }}</span> <span id="status-text">{{ if .UploadsEnabled }}Ready · drag files anywhere onto the window{{ else }}Guest uploads are disabled{{ end }}</span>
<span>WarpBox</span> <span>WarpBox</span>
<span>v{{ .AppVersion }}</span>
</div> </div>
</section> </section>