# 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 1. Generate defaults directly: - `go run ./cmd/scratchbox generate-config > config.yaml` 2. Adjust settings for your environment. 3. Start the server: - `go run ./cmd/scratchbox server --config config.yaml` 4. 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-data` - `cp .env.example .env` - `docker 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 uses `SCRATCHBOX_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_PATH` to empty (stdout-only access logs). Use the included Compose example: - `cp docker-compose.example.yml docker-compose.yml` - `cp .env.example .env` - `mkdir -p ./docker-data` - `docker 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_PATH` to empty. Storage encryption key behavior: - The key file is always stored at `storage.data_dir/metadata.key` (next to `metadata`). - Scratchbox only auto-generates this key when `storage.data_dir` has 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_dir` persistent; losing or replacing `metadata.key` makes 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). The canonical list of supported vars is envSuffixes in internal/config/config.go — see the comment there for how to keep this documentation in sync when adding new configuration. - `SCRATCHBOX_SERVER_LISTEN_ADDR` - `SCRATCHBOX_SERVER_READ_HEADER_TIMEOUT` - `SCRATCHBOX_SERVER_READ_TIMEOUT` - `SCRATCHBOX_SERVER_WRITE_TIMEOUT` - `SCRATCHBOX_SERVER_IDLE_TIMEOUT` - `SCRATCHBOX_SERVER_SHUTDOWN_TIMEOUT` - `SCRATCHBOX_SERVER_MAX_HEADER_BYTES` - `SCRATCHBOX_SERVER_ACCESS_LOG_ENABLED` - `SCRATCHBOX_SERVER_ACCESS_LOG_FILE_PATH` (forced empty by Docker image) - `SCRATCHBOX_SERVER_ACCESS_LOG_MAX_SIZE` - `SCRATCHBOX_SERVER_ACCESS_LOG_MAX_BACKUPS` - `SCRATCHBOX_LIMITS_MAX_UPLOAD_SIZE` - `SCRATCHBOX_LIMITS_DEFAULT_TTL` - `SCRATCHBOX_LIMITS_RAW_CACHE_MAX_SIZE` - `SCRATCHBOX_LIMITS_RAW_CACHE_MAX_ENTRIES` - `SCRATCHBOX_STORAGE_DATA_DIR` - `SCRATCHBOX_STORAGE_CLEANUP_INTERVAL` - `SCRATCHBOX_SECURITY_ALLOWED_IPS` (comma-separated) - `SCRATCHBOX_SECURITY_TRUST_PROXY_HEADERS` - `SCRATCHBOX_SECURITY_TRUSTED_PROXY_IPS` (comma-separated) - `SCRATCHBOX_SECURITY_RATE_LIMIT_UI_ENABLED` - `SCRATCHBOX_SECURITY_RATE_LIMIT_UI_REQUESTS_PER_MINUTE` - `SCRATCHBOX_SECURITY_RATE_LIMIT_UI_BURST` - `SCRATCHBOX_SECURITY_RATE_LIMIT_API_READ_ENABLED` - `SCRATCHBOX_SECURITY_RATE_LIMIT_API_READ_REQUESTS_PER_MINUTE` - `SCRATCHBOX_SECURITY_RATE_LIMIT_API_READ_BURST` - `SCRATCHBOX_SECURITY_RATE_LIMIT_API_WRITE_ENABLED` - `SCRATCHBOX_SECURITY_RATE_LIMIT_API_WRITE_REQUESTS_PER_MINUTE` - `SCRATCHBOX_SECURITY_RATE_LIMIT_API_WRITE_BURST` - `SCRATCHBOX_SECURITY_HSTS_ENABLED` Example `.env` for Docker (must be kept in sync with envSuffixes in internal/config/config.go): ```dotenv # 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 SCRATCHBOX_SERVER_ACCESS_LOG_FILE_PATH= SCRATCHBOX_SERVER_ACCESS_LOG_MAX_SIZE=100MiB SCRATCHBOX_SERVER_ACCESS_LOG_MAX_BACKUPS=5 # 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--linux-amd64-default.tar.gz` To unpack into a specific directory: - `mkdir -p ./scratchbox-release` - `tar -xzf scratchbox--linux-amd64-default.tar.gz -C ./scratchbox-release` To run from an unpacked artifact: - `./scratchbox--- 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` - `read_header_timeout`: maximum time for reading request headers. - Must be `> 0` - Default: `5s` - `read_timeout`: maximum time for reading the full request (including body). - Must be `>= 0`. `0` disables the deadline (same as Go `http.Server`). - Raise this (or set `0`) when `limits.max_upload_size` is large or clients are slow: a 30s body timeout cannot complete multi-GiB uploads. - Default: `30s` - `write_timeout`: maximum time allowed for writing the response (also bounds slow request handling after headers are read). - Must be `>= 0`. `0` disables the deadline. - Raise this (or set `0`) for large uploads/downloads; after request headers are read the write deadline covers the rest of the handler. - Default: `30s` - `idle_timeout`: maximum keep-alive idle time between requests. - Must be `> 0` - Default: `120s` - `shutdown_timeout`: graceful shutdown timeout for in-flight requests on SIGTERM/INT. - Must be `> 0` - Default: `10s` - `max_header_bytes`: max HTTP header size in bytes. - Must be `> 0` - Default: `1048576` (1 MiB) - `access_log`: structured request logs. - `enabled`: enable HTTP access logs - Default: `true` - `file_path`: optional log file path - When set, logs are tee'd to both stdout and the file - `max_size`: rotation threshold for `file_path` - Supported units: `B`, `KB`, `MB`, `GB`, `KiB`, `MiB`, `GiB` - Default: `100MiB` - `max_backups`: number of rotated files to retain - Default: `5` ### `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` - `default_ttl`: expiration applied to new scratches. - Must be `> 0` and `<= 24h` - Default: `15m` - `raw_cache_max_size`: max total decompressed bytes cached in memory for `/api/raw` range requests. - Supported units: `B`, `KB`, `MB`, `GB`, `KiB`, `MiB`, `GiB` (case-insensitive) - Must be `> 0` - Default: `200MiB` - `raw_cache_max_entries`: max number of decompressed `/api/raw` entries kept in memory. - Must be `> 0` - Used together with `raw_cache_max_size` as a secondary cap - Default: `32` ### `storage` - `data_dir`: directory for scratch files and metadata index. - Default: `./data` - `cleanup_interval`: background interval for deleting expired content. - Must be at least `10s` - Default: `1m` - Encryption key file is managed automatically at `storage.data_dir/metadata.key`. - Auto-generated only when `storage.data_dir` has 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 ### `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 - `trust_proxy_headers`: if `true`, client IP extraction honors `X-Forwarded-For` then `X-Real-IP`. - Only used when source IP matches `trusted_proxy_ips`. - Keep `false` unless behind a trusted reverse proxy that sets these headers. - `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` - `rate_limit_ui`: per-IP token-bucket for UI routes (`GET /`, `GET /u`, `GET /s/{id}`). - `enabled` default: `false` - `requests_per_minute` must be `> 0` (default `360`) - `burst` must be `> 0` (default `120`) - `rate_limit_api_read`: per-IP token-bucket for API read routes (`GET /api/config`, `GET /api/scratch/{id}`, `GET /api/raw/{id}`). - `enabled` default: `false` - `requests_per_minute` must be `> 0` (default `300`) - `burst` must be `> 0` (default `100`) - `rate_limit_api_write`: per-IP token-bucket for API write route (`POST /api/scratch`). - `enabled` default: `true` - `requests_per_minute` must be `> 0` (default `30`) - `burst` must be `> 0` (default `10`) - `hsts_enabled`: enable HSTS header (default: `true`; for public proxy TLS setups; set `false` for 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) or `192.168.x.x:8080`. - Use `allowed_ips` to restrict write access to trusted subnets. - Keep `max_upload_size` and `default_ttl` small. - Keep `trust_proxy_headers` disabled 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.