A self-hosted file downloader portal. Paste a link from any supported sharing service and download the file straight through your server — no direct internet access needed for users, no files stored on disk.
- Multi-source by design: providers self-declare which URLs they handle; the rest of the system never cares where the file came from
- Zero-disk streaming: bytes flow source CDN → your server → user, nothing touches disk
- Web UI: paste URL → click download; per-user Recent list with one-click retry
- Accounts & RBAC: local users, LDAP/AD SSO, or org-email (IMAP); groups grant permissions (
download,audit,users,groups); admin role = full access - Admin console: download stats, live audit log, user/group management, LDAP + IMAP settings editable in the UI (no restart)
- Resilient: retries, resume via Range headers
- Dockerized: single multi-stage image (~20MB), non-root user
- Observable: JSON logging, health checks, in-app audit trail with SQLite persistence
- Secure: domain allowlist, SSRF protection, rate limiting, security headers
# Create config directory and copy example
mkdir -p config
cp configs/config.yaml.example config/config.yaml
# Start with docker compose (single volume mount for all persistent data)
docker compose -f deploy/docker/compose.yaml up -dDefault login: admin / admin123 — change it immediately.
go build -o webridge ./cmd/server
./webridge -config configs/config.yaml.example/app/
├── webridge # binary
└── config/
├── config.yaml # main config (edit this)
├── ldap-settings.json # LDAP settings (managed by UI)
├── imap-settings.json # IMAP settings (managed by UI)
└── data/
├── audit.db # SQLite audit database
└── logs/ # daily access logs (JSONL)
Mount a single volume: -v ./config:/app/config
Copy configs/config.yaml.example to config/config.yaml and adjust:
server:
host: "0.0.0.0"
port: 8080
write_timeout: 0 # 0 = no timeout for streaming
auth:
session_ttl: 24h
users:
- username: "admin"
password: "admin123" # CHANGE THIS
wetransfer:
request_timeout: 30s
max_redirects: 10
limits:
max_concurrent_downloads: 50
max_file_size_gb: 2
rate_limit_per_minute: 30
logging:
level: "info"
format: "json"
ui:
title: "WeBridge"
audit:
db_path: "/app/config/data/audit.db"
retention_days: 90
access_log_dir: "/app/config/data/logs"All PROXY_* vars override the YAML. Common ones:
| Variable | Default | Description |
|---|---|---|
PROXY_SERVER_PORT |
8080 | Listen port |
PROXY_LOGGING_LEVEL |
info |
debug, info, warn, error |
PROXY_AUDIT_DB_PATH |
/app/config/data/audit.db |
SQLite path |
PROXY_AUDIT_ACCESS_LOG_DIR |
/app/config/data/logs |
Access log dir |
LDAP_SETTINGS_FILE |
/app/config/ldap-settings.json |
LDAP settings file |
IMAP_SETTINGS_FILE |
/app/config/imap-settings.json |
IMAP settings file |
CONFIG_PATH |
/app/config/config.yaml |
Main config path |
Optional — configure in the file (ldap: section) or live in the admin UI (Settings → LDAP). Users authenticate against the directory on first login, then are auto-added as local users (source: ldap) so you can assign groups/roles.
Optional — users can log in with their org email address. Credentials verified against the IMAP server; no password stored locally. Configure in Settings → Email Sign-in. Domain allowlist enforced.
| Endpoint | Method | Description |
|---|---|---|
/ |
GET | Web UI (embedded SPA) |
/api/v1/info |
GET | File metadata (?url=...) |
/api/v1/download |
GET | Streamed download (?url=...) |
/api/v1/downloads/recent |
GET | Current user's last downloads |
/api/v1/admin/* |
GET/POST/PUT/DELETE | Users, groups, audit log, metrics, LDAP, IMAP |
/healthz |
GET | Liveness probe |
| Service | Hosts | Status |
|---|---|---|
| WeTransfer | wetransfer.com, we.tl |
✅ built in |
| SendGB | sendgb.com, www.sendgb.com |
✅ built in |
| TransferNow | transfernow.net, www.transfernow.net |
✅ built in |
| Wesendit | wesendit.com, www.wesendit.com |
✅ built in |
| SendSpace | sendspace.com, www.sendspace.com |
✅ built in |
Password-protected transfers are supported (the UI asks when needed).
Terminate TLS at a reverse proxy (nginx, Traefik, Caddy):
server {
listen 443 ssl http2;
server_name download.yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
proxy_read_timeout 3600s;
proxy_request_buffering off;
location / {
proxy_pass http://localhost:8080;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}
}Kubernetes: run the image as a Deployment with liveness/readiness probes on /healthz; mount your config/ directory.
services:
webridge:
build:
context: ..
dockerfile: deploy/docker/Dockerfile
container_name: webridge
restart: unless-stopped
ports:
- "8080:8080"
environment:
- CONFIG_PATH=/app/config/config.yaml
- LDAP_SETTINGS_FILE=/app/config/ldap-settings.json
- IMAP_SETTINGS_FILE=/app/config/imap-settings.json
- PROXY_LOGGING_LEVEL=info
volumes:
- ./config:/app/configUser (Browser) → [HTTPS] → Your Server → [HTTPS] → Source API/CDN
↓
Resolve direct URL
↓
Stream → User
No files stored on disk — pure streaming with io.CopyBuffer.
- Domain allowlist enforced per request (SSRF guard)
- Private/loopback/link-local IP blocking
- Rate limiting per IP (token bucket) + global concurrency cap
- Security headers (CSP, HSTS, ...)
- Session cookies, SHA-256+salt hashed passwords at runtime, non-root container
Any type with three methods is a provider:
func (c *MyClient) Matches(rawURL string) bool // "this URL is mine"
func (c *MyClient) Resolve(ctx, url, auth) (*TransferInfo, err) // metadata + direct link
func (c *MyClient) Stream(ctx, info, w) error // pipe bytes outSee internal/providers/wetransfer.go for a complete implementation. Wire it into cmd/server/main.go alongside the existing clients.
MIT