Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c9fc03a61 | |||
| 0bdf11d3a7 | |||
| bcdcce8fbd |
@@ -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",
|
||||||
|
|||||||
@@ -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},
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
1
run.sh
@@ -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}"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.upload-statusbar {
|
.upload-statusbar {
|
||||||
grid-template-columns: 1fr 100px;
|
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-stack {
|
.side-stack {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user