Files
scratchbox/README.md
T
s1d3sw1ped 90eb56465b
Format / gofmt (push) Successful in 1m17s
CI / Build (push) Successful in 1m31s
CI / Go Tests (push) Successful in 1m36s
Enhance environment variable documentation and configuration
- Updated `.env.example` to include new environment variables for server access logging and security settings, ensuring comprehensive configuration options.
- Revised `docker-entrypoint.sh` to clarify the source of supported environment variables.
- Improved `README.md` to reflect the latest environment variable changes and maintain synchronization with the canonical list in `internal/config/config.go`.
- Added tests to ensure documentation consistency across `.env.example` and `README.md`, preventing future discrepancies.
2026-07-15 16:24:55 -05:00

319 lines
14 KiB
Markdown

# 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-<tag>-linux-amd64-default.tar.gz`
To unpack into a specific directory:
- `mkdir -p ./scratchbox-release`
- `tar -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`
- `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.