13 KiB
Scratchbox
Scratchbox is a minimal self-hosted scratch service written in Go. It is intentionally unauthenticated and optimized for temporary sharing of text or small files with an expiration time.
For multi-file sharing, bundle files into an archive (for example .zip or .tar.gz) before upload.
Scratch content is gzip-compressed and AES-encrypted on disk transparently; API/UI reads return original uncompressed content.
Quick Start
- Generate defaults directly:
go run ./cmd/scratchbox generate-config > config.yaml
- Adjust settings for your environment.
- Start the server:
go run ./cmd/scratchbox server --config config.yaml
- Open:
http://127.0.0.1:8080/
Without --config, built-in defaults are used.
If you modify the web UI source (web/ui), rebuild frontend assets with:
make build
Docker
Build the container image:
make docker- Default image tag:
scratchbox:dev
Optionally set a custom image name/tag:
make docker DOCKER_IMAGE=scratchbox:latest
Run with persistent data and env-based config:
mkdir -p ./docker-datacp .env.example .envdocker run --rm -p 8080:8080 --env-file .env -v "$(pwd)/docker-data:/data" scratchbox:dev- Without
.env, the image still starts with default config and usesSCRATCHBOX_STORAGE_DATA_DIR=/data. - The image always forces
SCRATCHBOX_STORAGE_DATA_DIR=/data. - The image always forces
SCRATCHBOX_SERVER_LISTEN_ADDR=:8080. - The image always forces
SCRATCHBOX_SERVER_ACCESS_LOG_FILE_PATHto empty (stdout-only access logs).
Use the included Compose example:
cp docker-compose.example.yml docker-compose.ymlcp .env.example .envmkdir -p ./docker-datadocker compose up -d
Inside the container, the default command is:
scratchbox server --env- Custom container command/args are not supported; configure behavior via
SCRATCHBOX_*env vars. - The image forces
SCRATCHBOX_STORAGE_DATA_DIR=/data. - The image forces
SCRATCHBOX_SERVER_LISTEN_ADDR=:8080. - The image forces
SCRATCHBOX_SERVER_ACCESS_LOG_FILE_PATHto empty.
Storage encryption key behavior:
- The key file is always stored at
storage.data_dir/metadata.key(next tometadata). - Scratchbox only auto-generates this key when
storage.data_dirhas no existing files. - If the key is missing and files already exist in
storage.data_dir, startup fails to prevent corruption. - On subsequent startups, Scratchbox reuses the same key file from that data directory.
- Keep
storage.data_dirpersistent; losing or replacingmetadata.keymakes existing scratches unreadable.
Environment variables supported by scratchbox server --env (note: when --env is used, the variables and their values are printed to stdout on startup for diagnostics, with values of IP allow/trusted lists redacted):
SCRATCHBOX_SERVER_LISTEN_ADDRSCRATCHBOX_SERVER_READ_HEADER_TIMEOUTSCRATCHBOX_SERVER_READ_TIMEOUTSCRATCHBOX_SERVER_WRITE_TIMEOUTSCRATCHBOX_SERVER_IDLE_TIMEOUTSCRATCHBOX_SERVER_SHUTDOWN_TIMEOUTSCRATCHBOX_SERVER_MAX_HEADER_BYTESSCRATCHBOX_SERVER_ACCESS_LOG_ENABLEDSCRATCHBOX_SERVER_ACCESS_LOG_FILE_PATH(forced empty by Docker image)SCRATCHBOX_SERVER_ACCESS_LOG_MAX_SIZESCRATCHBOX_SERVER_ACCESS_LOG_MAX_BACKUPSSCRATCHBOX_LIMITS_MAX_UPLOAD_SIZESCRATCHBOX_LIMITS_DEFAULT_TTLSCRATCHBOX_LIMITS_RAW_CACHE_MAX_SIZESCRATCHBOX_LIMITS_RAW_CACHE_MAX_ENTRIESSCRATCHBOX_STORAGE_DATA_DIRSCRATCHBOX_STORAGE_CLEANUP_INTERVALSCRATCHBOX_SECURITY_ALLOWED_IPS(comma-separated)SCRATCHBOX_SECURITY_TRUST_PROXY_HEADERSSCRATCHBOX_SECURITY_TRUSTED_PROXY_IPS(comma-separated)SCRATCHBOX_SECURITY_RATE_LIMIT_UI_ENABLEDSCRATCHBOX_SECURITY_RATE_LIMIT_UI_REQUESTS_PER_MINUTESCRATCHBOX_SECURITY_RATE_LIMIT_UI_BURSTSCRATCHBOX_SECURITY_RATE_LIMIT_API_READ_ENABLEDSCRATCHBOX_SECURITY_RATE_LIMIT_API_READ_REQUESTS_PER_MINUTESCRATCHBOX_SECURITY_RATE_LIMIT_API_READ_BURSTSCRATCHBOX_SECURITY_RATE_LIMIT_API_WRITE_ENABLEDSCRATCHBOX_SECURITY_RATE_LIMIT_API_WRITE_REQUESTS_PER_MINUTESCRATCHBOX_SECURITY_RATE_LIMIT_API_WRITE_BURSTSCRATCHBOX_SECURITY_HSTS_ENABLED
Example .env for Docker:
# server
SCRATCHBOX_SERVER_LISTEN_ADDR=:8080
SCRATCHBOX_SERVER_READ_HEADER_TIMEOUT=5s
SCRATCHBOX_SERVER_READ_TIMEOUT=30s
SCRATCHBOX_SERVER_WRITE_TIMEOUT=30s
SCRATCHBOX_SERVER_IDLE_TIMEOUT=120s
SCRATCHBOX_SERVER_SHUTDOWN_TIMEOUT=10s
SCRATCHBOX_SERVER_MAX_HEADER_BYTES=1048576
SCRATCHBOX_SERVER_ACCESS_LOG_ENABLED=true
# limits
SCRATCHBOX_LIMITS_MAX_UPLOAD_SIZE=100MiB
SCRATCHBOX_LIMITS_DEFAULT_TTL=15m
SCRATCHBOX_LIMITS_RAW_CACHE_MAX_SIZE=200MiB
SCRATCHBOX_LIMITS_RAW_CACHE_MAX_ENTRIES=32
# storage
SCRATCHBOX_STORAGE_DATA_DIR=/data
SCRATCHBOX_STORAGE_CLEANUP_INTERVAL=1m
# security
SCRATCHBOX_SECURITY_ALLOWED_IPS=127.0.0.1
SCRATCHBOX_SECURITY_TRUST_PROXY_HEADERS=false
SCRATCHBOX_SECURITY_TRUSTED_PROXY_IPS=
SCRATCHBOX_SECURITY_RATE_LIMIT_UI_ENABLED=false
SCRATCHBOX_SECURITY_RATE_LIMIT_UI_REQUESTS_PER_MINUTE=360
SCRATCHBOX_SECURITY_RATE_LIMIT_UI_BURST=120
SCRATCHBOX_SECURITY_RATE_LIMIT_API_READ_ENABLED=false
SCRATCHBOX_SECURITY_RATE_LIMIT_API_READ_REQUESTS_PER_MINUTE=300
SCRATCHBOX_SECURITY_RATE_LIMIT_API_READ_BURST=100
SCRATCHBOX_SECURITY_RATE_LIMIT_API_WRITE_ENABLED=true
SCRATCHBOX_SECURITY_RATE_LIMIT_API_WRITE_REQUESTS_PER_MINUTE=30
SCRATCHBOX_SECURITY_RATE_LIMIT_API_WRITE_BURST=10
SCRATCHBOX_SECURITY_HSTS_ENABLED=true
Release Artifacts
Tagged releases provide .tar.gz archives with prebuilt Linux binaries.
Accepted release tag formats:
1.2.3(stable release)1.2.3-alpha1,1.2.3-beta1,1.2.3-rc1(prerelease)
Variant guide:
| Variant | Recommended for | Notes |
|---|---|---|
default |
Most Linux distributions (Ubuntu, Debian, Fedora, Arch, etc.) | Smaller, dynamically linked Linux build |
static |
Any Linux distro (including Alpine/musl) | Statically linked for maximum Linux compatibility |
To unpack an artifact:
tar -xzf scratchbox-<tag>-linux-amd64-default.tar.gz
To unpack into a specific directory:
mkdir -p ./scratchbox-releasetar -xzf scratchbox-<tag>-linux-amd64-default.tar.gz -C ./scratchbox-release
To run from an unpacked artifact:
./scratchbox-<os>-<arch>-<variant> server --config config.yaml
If you do not have a config file yet, generate one first:
go run ./cmd/scratchbox generate-config > config.yaml
Configuration
All configuration is YAML.
server
listen_addr: address for the HTTP server (required).- Default:
:8080
- Default:
read_header_timeout: maximum time for reading request headers.- Must be
> 0 - Default:
5s
- Must be
read_timeout: maximum time for reading the full request.- Must be
> 0 - Default:
30s
- Must be
write_timeout: maximum time allowed for writing the response.- Must be
> 0 - Default:
30s
- Must be
idle_timeout: maximum keep-alive idle time between requests.- Must be
> 0 - Default:
120s
- Must be
shutdown_timeout: graceful shutdown timeout for in-flight requests on SIGTERM/INT.- Must be
> 0 - Default:
10s
- Must be
max_header_bytes: max HTTP header size in bytes.- Must be
> 0 - Default:
1048576(1 MiB)
- Must be
access_log: structured request logs.enabled: enable HTTP access logs- Default:
true
- Default:
file_path: optional log file path- When set, logs are tee'd to both stdout and the file
max_size: rotation threshold forfile_path- Supported units:
B,KB,MB,GB,KiB,MiB,GiB - Default:
100MiB
- Supported units:
max_backups: number of rotated files to retain- Default:
5
- Default:
limits
max_upload_size: max request body size for uploads.- Supported units:
B,KB,MB,GB,KiB,MiB,GiB(case-insensitive) - If no unit is provided, value is interpreted as bytes
- Examples:
5MB,100MiB,1GiB,1048576 - Default:
100MiB
- Supported units:
default_ttl: expiration applied to new scratches.- Must be
> 0and<= 24h - Default:
15m
- Must be
raw_cache_max_size: max total decompressed bytes cached in memory for/api/rawrange requests.- Supported units:
B,KB,MB,GB,KiB,MiB,GiB(case-insensitive) - Must be
> 0 - Default:
200MiB
- Supported units:
raw_cache_max_entries: max number of decompressed/api/rawentries kept in memory.- Must be
> 0 - Used together with
raw_cache_max_sizeas a secondary cap - Default:
32
- Must be
storage
data_dir: directory for scratch files and metadata index.- Default:
./data
- Default:
cleanup_interval: background interval for deleting expired content.- Must be at least
10s - Default:
1m
- Must be at least
- Encryption key file is managed automatically at
storage.data_dir/metadata.key.- Auto-generated only when
storage.data_dirhas no existing files - If missing while files exist in
storage.data_dir, startup fails - Reused on subsequent startups for the same data directory
- Changing/removing this file makes existing scratch files unreadable
- Auto-generated only when
security
allowed_ips: optional list of IPs and CIDRs allowed to create scratches.- Applies to write route only:
POST /api/scratch - Default includes
127.0.0.1(localhost-only write access) - Add trusted IPs/CIDRs for LAN/proxy clients (for example
192.168.1.0/24) - Empty list means no allowlist restriction
- Applies to write route only:
trust_proxy_headers: iftrue, client IP extraction honorsX-Forwarded-ForthenX-Real-IP.- Only used when source IP matches
trusted_proxy_ips. - Keep
falseunless behind a trusted reverse proxy that sets these headers.
- Only used when source IP matches
trusted_proxy_ips: list of reverse-proxy IPs/CIDRs permitted to supply forwarded headers.- Required when
trust_proxy_headers: true - Must be the immediate reverse proxies only (e.g. the nginx/caddy in front of this service); never use 0.0.0.0/0 or broad ranges unless you fully control the entire path.
- Examples:
127.0.0.1,10.0.0.0/8
- Required when
rate_limit_ui: per-IP token-bucket for UI routes (GET /,GET /u,GET /s/{id}).enableddefault:falserequests_per_minutemust be> 0(default360)burstmust be> 0(default120)
rate_limit_api_read: per-IP token-bucket for API read routes (GET /api/config,GET /api/scratch/{id},GET /api/raw/{id}).enableddefault:falserequests_per_minutemust be> 0(default300)burstmust be> 0(default100)
rate_limit_api_write: per-IP token-bucket for API write route (POST /api/scratch).enableddefault:truerequests_per_minutemust be> 0(default30)burstmust be> 0(default10)
hsts_enabled: enable HSTS header (default:true; for public proxy TLS setups; setfalsefor local HTTP-only per LAN notes).
Security Posture
This service is intentionally unauthenticated. Anyone with network access can read scratches, and (unless restricted) create scratches.
- No authentication, user accounts, or moderation controls.
- No malware scanning.
- Abuse controls are limited to upload size limits, TTL expiration, optional IP allowlisting, and write-route rate limiting.
- Treat this as a convenience utility, not a hardened internet-facing platform.
LAN Deployment Notes
For home/lab/LAN-only use:
- Bind to a private address, for example
127.0.0.1:8080(single host) or192.168.x.x:8080. - Use
allowed_ipsto restrict write access to trusted subnets. - Keep
max_upload_sizeanddefault_ttlsmall. - Keep
trust_proxy_headersdisabled unless using a trusted reverse proxy.
Public Deployment Notes
If exposed beyond a trusted LAN, place a reverse proxy in front (Nginx, Caddy, Traefik, etc.) and add external controls:
- TLS termination and strict transport settings.
- Additional request filtering/WAF controls.
- Stronger rate limits at the edge.
- Optional network-level restrictions to write route.
- Monitoring and log retention suitable for abuse triage.
Recommended baseline: run behind a reverse proxy, keep low TTL and size limits, and use allowed_ips for write access whenever possible.
Storage Model
Scratchbox storage is designed for a single process instance per storage.data_dir.
- Do not run multiple scratchbox processes against the same data directory.
- Metadata/index writes are process-local and are not coordinated with cross-process locking.
- Scratch payload files and metadata index are encrypted at rest.
- Public scratch IDs are short random aliases; stored blobs still use SHA-256-derived internal blob IDs on disk.
Operational internals (not tunable; see source consts):
- Public IDs: 12 alphanum chars (crypto/rand), up to 8 attempts to reserve unique.
- Encrypted chunks: 64 KiB plaintext (SBX1 magic + per-chunk nonce prefix+counter for AES-256-GCM).
- Index: full random nonce per persist (SMD1).
- Background expiry + startup reconcile walk the in-memory map under exclusive lock (plus optional persist); practical cardinality limited by default 15m TTL + 100MiB uploads (low hundreds of live entries expected).
- Raw /api/raw cache: small LRU (default 32 entries, total bytes cap) with coalescing; serves decompressed content.