Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ab5021667 |
@@ -27,7 +27,8 @@ WARPBOX_SHORT_WINDOW_REQUESTS=60
|
||||
WARPBOX_SHORT_WINDOW_SECONDS=60
|
||||
WARPBOX_ANONYMOUS_STORAGE_BACKEND=local
|
||||
WARPBOX_USER_STORAGE_BACKEND=local
|
||||
WARPBOX_READ_TIMEOUT=15s
|
||||
WARPBOX_WRITE_TIMEOUT=60s
|
||||
WARPBOX_READ_HEADER_TIMEOUT=15s
|
||||
WARPBOX_READ_TIMEOUT=0s
|
||||
WARPBOX_WRITE_TIMEOUT=0s
|
||||
WARPBOX_IDLE_TIMEOUT=120s
|
||||
WARPBOX_TRUSTED_PROXIES=
|
||||
|
||||
@@ -38,6 +38,11 @@ Upload policy defaults are also configured in megabytes and can later be changed
|
||||
Runtime data is configured with `WARPBOX_DATA_DIR` and defaults to `./data` in the dev environment.
|
||||
The dev script resolves that path from the repository root.
|
||||
|
||||
Large uploads are expected to take minutes on normal home/server connections. Keep
|
||||
`WARPBOX_READ_TIMEOUT=0s` and `WARPBOX_WRITE_TIMEOUT=0s` so Go does not close the connection
|
||||
mid-upload; `WARPBOX_READ_HEADER_TIMEOUT=15s` still protects header reads from slowloris-style
|
||||
connections.
|
||||
|
||||
Background jobs are enabled with `WARPBOX_JOBS_ENABLED=true`. Individual jobs can be toggled with
|
||||
`WARPBOX_CLEANUP_ENABLED` and `WARPBOX_THUMBNAIL_ENABLED`, and their schedules are configured with
|
||||
`WARPBOX_CLEANUP_EVERY` and `WARPBOX_THUMBNAIL_EVERY`.
|
||||
@@ -106,6 +111,9 @@ WARPBOX_DATA_DIR=/var/lib/warpbox
|
||||
WARPBOX_STATIC_DIR=/opt/warpbox-dev/backend/static
|
||||
WARPBOX_TEMPLATE_DIR=/opt/warpbox-dev/backend/templates
|
||||
WARPBOX_TRUSTED_PROXIES=127.0.0.1,::1
|
||||
WARPBOX_READ_HEADER_TIMEOUT=15s
|
||||
WARPBOX_READ_TIMEOUT=0s
|
||||
WARPBOX_WRITE_TIMEOUT=0s
|
||||
```
|
||||
|
||||
Example `/etc/systemd/system/warpbox.service`:
|
||||
|
||||
@@ -54,6 +54,24 @@ network edge, or set it to a value that does not include public clients. Direct
|
||||
public exposure is not recommended; use a reverse proxy for TLS and request
|
||||
normalization.
|
||||
|
||||
## Large Uploads
|
||||
|
||||
Multi-GB uploads must not use whole-body read/write deadlines. Keep these
|
||||
Warpbox values for production unless you intentionally want a hard wall-clock
|
||||
upload limit:
|
||||
|
||||
```env
|
||||
WARPBOX_READ_HEADER_TIMEOUT=15s
|
||||
WARPBOX_READ_TIMEOUT=0s
|
||||
WARPBOX_WRITE_TIMEOUT=0s
|
||||
```
|
||||
|
||||
`WARPBOX_READ_HEADER_TIMEOUT` protects request headers. `WARPBOX_READ_TIMEOUT`
|
||||
and `WARPBOX_WRITE_TIMEOUT` cover the whole upload/response lifetime in Go, so
|
||||
small values can cause browser errors such as `NS_ERROR_NET_INTERRUPT` during
|
||||
large transfers. Upload size, daily, storage, and box limits still enforce abuse
|
||||
controls independently of these timeout values.
|
||||
|
||||
## Ban Behavior
|
||||
|
||||
Active bans return:
|
||||
|
||||
@@ -11,26 +11,27 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
AppName string
|
||||
AppVersion string
|
||||
Environment string
|
||||
Addr string
|
||||
BaseURL string
|
||||
DataDir string
|
||||
AdminToken string
|
||||
StaticDir string
|
||||
TemplateDir string
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
IdleTimeout time.Duration
|
||||
TrustedProxies []string
|
||||
JobsEnabled bool
|
||||
CleanupEnabled bool
|
||||
CleanupEvery time.Duration
|
||||
ThumbnailEnabled bool
|
||||
ThumbnailEvery time.Duration
|
||||
MaxUploadSize int64
|
||||
DefaultSettings SettingsDefaults
|
||||
AppName string
|
||||
AppVersion string
|
||||
Environment string
|
||||
Addr string
|
||||
BaseURL string
|
||||
DataDir string
|
||||
AdminToken string
|
||||
StaticDir string
|
||||
TemplateDir string
|
||||
ReadHeaderTimeout time.Duration
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
IdleTimeout time.Duration
|
||||
TrustedProxies []string
|
||||
JobsEnabled bool
|
||||
CleanupEnabled bool
|
||||
CleanupEvery time.Duration
|
||||
ThumbnailEnabled bool
|
||||
ThumbnailEvery time.Duration
|
||||
MaxUploadSize int64
|
||||
DefaultSettings SettingsDefaults
|
||||
}
|
||||
|
||||
type SettingsDefaults struct {
|
||||
@@ -55,25 +56,26 @@ type SettingsDefaults struct {
|
||||
|
||||
func Load() (Config, error) {
|
||||
cfg := Config{
|
||||
AppName: envString("WARPBOX_APP_NAME", "warpbox.dev"),
|
||||
AppVersion: envString("APP_VERSION", "dev"),
|
||||
Environment: envString("WARPBOX_ENV", "development"),
|
||||
Addr: envString("WARPBOX_ADDR", ":8080"),
|
||||
BaseURL: strings.TrimRight(envString("WARPBOX_BASE_URL", "http://localhost:8080"), "/"),
|
||||
DataDir: envString("WARPBOX_DATA_DIR", defaultPath("data")),
|
||||
AdminToken: envString("WARPBOX_ADMIN_TOKEN", ""),
|
||||
StaticDir: envString("WARPBOX_STATIC_DIR", defaultPath("static")),
|
||||
TemplateDir: envString("WARPBOX_TEMPLATE_DIR", defaultPath("templates")),
|
||||
ReadTimeout: envDuration("WARPBOX_READ_TIMEOUT", 15*time.Second),
|
||||
WriteTimeout: envDuration("WARPBOX_WRITE_TIMEOUT", 60*time.Second),
|
||||
IdleTimeout: envDuration("WARPBOX_IDLE_TIMEOUT", 120*time.Second),
|
||||
TrustedProxies: envCSV("WARPBOX_TRUSTED_PROXIES"),
|
||||
JobsEnabled: envBool("WARPBOX_JOBS_ENABLED", true),
|
||||
CleanupEnabled: envBool("WARPBOX_CLEANUP_ENABLED", true),
|
||||
CleanupEvery: envDuration("WARPBOX_CLEANUP_EVERY", time.Hour),
|
||||
ThumbnailEnabled: envBool("WARPBOX_THUMBNAIL_ENABLED", true),
|
||||
ThumbnailEvery: envDuration("WARPBOX_THUMBNAIL_EVERY", time.Minute),
|
||||
MaxUploadSize: envMegabytes("WARPBOX_MAX_UPLOAD_SIZE_MB", 2048), // 2 GiB default.
|
||||
AppName: envString("WARPBOX_APP_NAME", "warpbox.dev"),
|
||||
AppVersion: envString("APP_VERSION", "dev"),
|
||||
Environment: envString("WARPBOX_ENV", "development"),
|
||||
Addr: envString("WARPBOX_ADDR", ":8080"),
|
||||
BaseURL: strings.TrimRight(envString("WARPBOX_BASE_URL", "http://localhost:8080"), "/"),
|
||||
DataDir: envString("WARPBOX_DATA_DIR", defaultPath("data")),
|
||||
AdminToken: envString("WARPBOX_ADMIN_TOKEN", ""),
|
||||
StaticDir: envString("WARPBOX_STATIC_DIR", defaultPath("static")),
|
||||
TemplateDir: envString("WARPBOX_TEMPLATE_DIR", defaultPath("templates")),
|
||||
ReadHeaderTimeout: envDuration("WARPBOX_READ_HEADER_TIMEOUT", 15*time.Second),
|
||||
ReadTimeout: envDuration("WARPBOX_READ_TIMEOUT", 0),
|
||||
WriteTimeout: envDuration("WARPBOX_WRITE_TIMEOUT", 0),
|
||||
IdleTimeout: envDuration("WARPBOX_IDLE_TIMEOUT", 120*time.Second),
|
||||
TrustedProxies: envCSV("WARPBOX_TRUSTED_PROXIES"),
|
||||
JobsEnabled: envBool("WARPBOX_JOBS_ENABLED", true),
|
||||
CleanupEnabled: envBool("WARPBOX_CLEANUP_ENABLED", true),
|
||||
CleanupEvery: envDuration("WARPBOX_CLEANUP_EVERY", time.Hour),
|
||||
ThumbnailEnabled: envBool("WARPBOX_THUMBNAIL_ENABLED", true),
|
||||
ThumbnailEvery: envDuration("WARPBOX_THUMBNAIL_EVERY", time.Minute),
|
||||
MaxUploadSize: envMegabytes("WARPBOX_MAX_UPLOAD_SIZE_MB", 2048), // 2 GiB default.
|
||||
DefaultSettings: SettingsDefaults{
|
||||
AnonymousUploadsEnabled: envBool("WARPBOX_ANONYMOUS_UPLOADS_ENABLED", true),
|
||||
AnonymousMaxUploadMB: envMegabytesLimitFloat("WARPBOX_ANONYMOUS_MAX_UPLOAD_MB", 512),
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package config
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestParseMegabytes(t *testing.T) {
|
||||
tests := map[string]int64{
|
||||
@@ -49,3 +52,20 @@ func TestEnvBool(t *testing.T) {
|
||||
t.Fatalf("envBool() did not fall back to true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDefaultsUseLargeUploadFriendlyTimeouts(t *testing.T) {
|
||||
t.Setenv("WARPBOX_BASE_URL", "http://example.test")
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load returned error: %v", err)
|
||||
}
|
||||
if cfg.ReadHeaderTimeout != 15*time.Second {
|
||||
t.Fatalf("ReadHeaderTimeout = %s, want 15s", cfg.ReadHeaderTimeout)
|
||||
}
|
||||
if cfg.ReadTimeout != 0 {
|
||||
t.Fatalf("ReadTimeout = %s, want 0 for long uploads", cfg.ReadTimeout)
|
||||
}
|
||||
if cfg.WriteTimeout != 0 {
|
||||
t.Fatalf("WriteTimeout = %s, want 0 for long uploads", cfg.WriteTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,11 +54,12 @@ func New(cfg config.Config, logger *slog.Logger) (*http.Server, error) {
|
||||
)
|
||||
|
||||
server := &http.Server{
|
||||
Addr: cfg.Addr,
|
||||
Handler: handler,
|
||||
ReadTimeout: cfg.ReadTimeout,
|
||||
WriteTimeout: cfg.WriteTimeout,
|
||||
IdleTimeout: cfg.IdleTimeout,
|
||||
Addr: cfg.Addr,
|
||||
Handler: handler,
|
||||
ReadHeaderTimeout: cfg.ReadHeaderTimeout,
|
||||
ReadTimeout: cfg.ReadTimeout,
|
||||
WriteTimeout: cfg.WriteTimeout,
|
||||
IdleTimeout: cfg.IdleTimeout,
|
||||
}
|
||||
server.RegisterOnShutdown(func() {
|
||||
stopJobs()
|
||||
|
||||
5
scripts/env/dev.env.example
vendored
5
scripts/env/dev.env.example
vendored
@@ -27,7 +27,8 @@ WARPBOX_SHORT_WINDOW_REQUESTS=60
|
||||
WARPBOX_SHORT_WINDOW_SECONDS=60
|
||||
WARPBOX_ANONYMOUS_STORAGE_BACKEND=local
|
||||
WARPBOX_USER_STORAGE_BACKEND=local
|
||||
WARPBOX_READ_TIMEOUT=15s
|
||||
WARPBOX_WRITE_TIMEOUT=60s
|
||||
WARPBOX_READ_HEADER_TIMEOUT=15s
|
||||
WARPBOX_READ_TIMEOUT=0s
|
||||
WARPBOX_WRITE_TIMEOUT=0s
|
||||
WARPBOX_IDLE_TIMEOUT=120s
|
||||
WARPBOX_TRUSTED_PROXIES=
|
||||
|
||||
Reference in New Issue
Block a user