- Go 44.1%
- CSS 29.9%
- HTML 23.2%
- JavaScript 2.4%
- Dockerfile 0.4%
| .github/workflows | ||
| static | ||
| templates | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| handlers.go | ||
| handlers_test.go | ||
| main.go | ||
| middleware.go | ||
| models.go | ||
| models_test.go | ||
| README.md | ||
| store.go | ||
| util.go | ||
pastego
A self-hosted pastebin written in Go. Supports text pastes, file uploads, syntax highlighting, password protection, burn-after-read, and TTL-based expiry.
Features
- Text & file pastes — plain text, images, PDFs, and arbitrary binary files (up to 10 MiB)
- Syntax highlighting — powered by highlight.js, with auto-detection from file extension
- Password protection — bcrypt-hashed passwords; protected images/PDFs use a short-lived unlock session
- Burn after read — paste is deleted atomically on first view
- TTL expiry — 1 hour, 1 day, 7 days (default), or 30 days; expired pastes are pruned every 5 minutes
- CLI-friendly API — post with
curl, retrieve raw content by ID - Admin dashboard — list and delete pastes; protected by a shared secret
- Rate limiting — 60 requests per IP per minute
Stack
| Concern | Library |
|---|---|
| Router | chi v5 |
| Database | SQLite via modernc.org/sqlite (no CGO) |
| Passwords | golang.org/x/crypto/bcrypt |
| MIME detection | gabriel-vasile/mimetype |
Running
Locally
go run .
The server starts on :8080. Paste data is stored under ./data/.
Docker
docker run -p 8080:8080 -v $(pwd)/data:/app/data ghcr.io/<owner>/pastego:latest
Docker Compose (example)
services:
pastego:
image: ghcr.io/<owner>/pastego:latest
ports:
- "8080:8080"
volumes:
- ./data:/app/data
environment:
- ADMIN_SECRET=changeme
restart: unless-stopped
Configuration
All configuration is via environment variables.
| Variable | Default | Description |
|---|---|---|
ADDR |
:8080 |
Listen address |
DATA_DIR |
data |
Directory for the SQLite database and paste files |
ADMIN_SECRET |
(unset) | Enables /admin; the dashboard is hidden when unset |
API
Create a paste
# Plain text (raw body)
curl -X POST https://example.com/ --data-binary "hello world"
# With options
curl -X POST "https://example.com/?ttl=1d&burn=1" --data-binary "one-time secret"
# With a password
curl -X POST "https://example.com/?paste-password=secret" --data-binary "protected"
# Upload a file
curl -X POST https://example.com/ -F "file=@script.py"
Query parameters / headers for raw POST:
| Parameter | Header | Values | Description |
|---|---|---|---|
ttl |
X-TTL |
1h, 1d, 7d (default), 30d |
Expiry |
burn |
X-Burn |
1 |
Delete on first read |
language |
X-Language |
highlight.js language name | Syntax hint |
paste-password |
X-Paste-Password |
any string | Password-protect the paste |
Response: 201 Created with the paste URL as the body.
Read a paste
# Raw content
curl https://example.com/<id>
# Password-protected paste
curl -H "X-Password: secret" https://example.com/<id>
# or
curl "https://example.com/<id>?password=secret"
Admin
Set ADMIN_SECRET to enable /admin. The dashboard shows stats and lets you delete individual pastes. Authentication uses a browser session cookie (valid 24 hours) or a Bearer token in the Authorization header.
CI / CD
Pushes to main build a Docker image, push it to GHCR, then deploy to a VPS via SSH. See .github/workflows/deploy.yml.
Required repository secrets: VPS_HOST, VPS_USER, VPS_SSH_KEY, VPS_PORT, VPS_COMPOSE_DIR.
License
MIT