# Pastebox curl-based file sharing service English | [Korean](./README_ko.md) For CLI packages and usage, see [pastebox-cli](https://github.com/kmw0410/pastebox-cli). For upload, retrieval, clone, and management endpoints, see the [English HTTP API reference](./API.md). ![](./main.png)
Previews ### Paste ![](./paste.png) ### Password-protected ![](./password-protected.png) ### Admin Page ![](./admin.png) ### 404 ![](./404.png)
### Tech stack | Layer | Stack | |--------|------| | OS | Alpine Linux 3.24.2
Mirror: [mirror5.krfoss.org/alpine](https://mirror5.krfoss.org/alpine) | | Language | Go 1.27.3 | | Frontend | Go HTML Template | | Backend | Go Standard Library HTTP Server | | Storage | Local / MySQL & MariaDB | *If there is a specific mirror you want to use, you can modify it in the Dockerfile.* ### Directory structure ```text pastebox/ ├── .github/workflows/ # validate.yml, docker-publish.yml, release.yml ├── cmd/server/ # HTTP handlers, lifecycle, admin features and tests ├── internal/ # Local/MySQL storage, admin persistence and tests ├── locales/ # en.json, ko.json ├── templates/ # HTML, css/, js/ ├── tests/browser/ # Chromium regression tests ├── Dockerfile ├── docker-compose.yml ├── docker-entrypoint.sh ├── go.mod ├── go.sum └── README*, API*, DATA_POLICY*, AGENTS.md ``` ### How to use? > [!IMPORTANT] > The default time zone is set to Asia/Seoul. Please set it to match the country where you currently reside. 1. Clone the repository or download it as a .zip file. 2. Run `docker compose up -d` to use the GHCR image configured in `docker-compose.yml`. For a local image build, run `docker build -t pastebox:local .` and set the Compose image to `pastebox:local`. 3. Open `http://localhost:8080` in your browser, or access the service through a reverse proxy configured with Nginx, Traefik, or Caddy. Once the service is running properly, you can use it with `curl`. HTTP connections have a 10-second header timeout and a 60-second idle timeout. Request bodies and responses have no overall timeout so large streaming transfers can complete. On SIGINT/SIGTERM, the server waits up to 30 seconds for active requests; Compose allows 40 seconds before forced termination. ### Healthcheck Pastebox exposes `GET /healthz` and `HEAD /healthz` for container and load balancer health checks. - A healthy response returns `200 OK` with `ok`. - An unhealthy response returns `503 Service Unavailable`. - The check always verifies the admin SQLite database. - When `STORAGE_BACKEND=mysql`, it also verifies the configured MySQL or MariaDB connection. - Uploads being disabled does not make the service unhealthy. The provided Compose files already include a Docker health check that probes `http://127.0.0.1:8080/healthz`. Example: ```bash curl -i http://localhost:8080/healthz ``` When the database becomes unavailable after startup, Pastebox also writes state-transition logs to the container output. It records the first failure and the recovery event, rather than logging every probe failure repeatedly. Typical log messages: ```text store health check failed: backend=mysql error=dial tcp 10.0.0.10:3306: connect: connection refused store health recovered: backend=mysql ``` ### Storage backend Pastebox supports `local` and `mysql` storage backends. Use `local` for local file storage, or use `mysql` for an external MySQL & MariaDB database. | Variable | Default | Description | |----------|---------|-------------| | `STORAGE_BACKEND` | `local` | Paste storage backend. Use `local` for `/paste-data` files or `mysql` for an external MySQL & MariaDB database. | | `MYSQL_DSN` | empty | Required when `STORAGE_BACKEND=mysql`. Keep `parseTime=true` and `utf8mb4` options for compatibility. | | `DB_ZSTD_LEVEL` | `3` | Optional zstd compression level for DB-mode paste content. | | `MIGRATE_LOCAL_PASTES` | `false` | When `STORAGE_BACKEND=mysql`, migrate existing local paste files into MySQL during startup. This is a one-time migration: once it succeeds, Pastebox records completion in SQLite and skips it on later restarts. | | `MIGRATE_SQLITE_ADMIN_ACCOUNTS` | `false` | When `STORAGE_BACKEND=mysql`, migrate the existing SQLite admin account, admin sessions, and admin settings into MySQL during startup. This is a one-time migration: once it succeeds, Pastebox records completion in SQLite and skips it on later restarts. | Example MySQL & MariaDB configuration: ```yaml environment: STORAGE_BACKEND: "mysql" MYSQL_DSN: "pastebox:pastebox@tcp(mysql.example.com:3306)/pastebox?parseTime=true&charset=utf8mb4&collation=utf8mb4_unicode_ci" DB_ZSTD_LEVEL: "3" MIGRATE_LOCAL_PASTES: "true" MIGRATE_SQLITE_ADMIN_ACCOUNTS: "true" ``` In `local` mode, paste content is stored under `DATA_DIR` with JSON sidecar metadata. In `mysql` mode, paste content, the admin account, admin sessions, and admin settings are stored in the configured MySQL or MariaDB database. SQLite at `/paste-data/pastebox.db` remains in use only for migration completion markers. When using a bind mount such as `./data:/paste-data`, set `UID` and `GID` to the host user's numeric IDs if you want the host account to keep direct access to the stored files. For example, `UID=1000` and `GID=1000` make the container write data as that host user instead of leaving the bind-mounted directory owned only by container root. The two migration flags are startup-only helpers for moving an existing local installation into MySQL: - `MIGRATE_LOCAL_PASTES=true` scans `DATA_DIR` for local paste files and their JSON sidecars, copies each paste into MySQL, then removes the local file pair after each successful copy. - `MIGRATE_SQLITE_ADMIN_ACCOUNTS=true` copies the existing SQLite admin account, admin sessions, and admin settings into MySQL, then removes the migrated SQLite rows after success. Migration completion markers remain in SQLite. Both migrations are protected by a completion marker stored in SQLite under `pastebox_settings`. After a migration finishes successfully, later restarts skip it. If a migration fails before the marker is written, Pastebox will try again on the next startup. ### Features > [!NOTE] > **DON'T FORGET TO REPLACE `localhost` WITH THE DOMAIN OR IP ADDRESS YOU'RE CURRENTLY USING.** 1. **Automatic File Deletion**: Uploaded files are automatically deleted 30 days after upload. 2. **Text Upload**: You can upload text directly by combining Pastebox with Linux commands such as **echo** and **cat (cat << EOF)**. ```bash echo "hello" | curl -X POST --data-binary @- http://localhost:8080/ ``` 3. **File Upload**: Supports file uploads using the `multipart/form-data` format. ```bash curl -F "file=@test.txt" http://localhost:8080/ ``` For a streaming direct upload, send the filename explicitly so its extension selects syntax highlighting. ```bash curl -X POST --data-binary @server.go -H "filename: server.go" http://localhost:8080/ ``` 4. **Permanent Storage**: Use the `data-policy: permanent` header to exclude an uploaded file from automatic deletion and store it permanently. ```bash curl -H "data-policy: permanent" -F "file=@test.txt" http://localhost:8080/ ``` ```json // Storage path: ./data/code.json { "id": "code", "created_at": "2026-05-25T06:46:51.108540924Z", "expires_at": "0001-01-01T00:00:00Z", "data_policy": "permanent", "size": 5, "content_type": "application/octet-stream" } ``` 5. **One-time storage**: When the `data-policy: once` header is used, the data is stored only once and is automatically deleted once the user has confirmed it. ```bash curl -H "data-policy: once" -F "file=@test.txt" http://localhost:8080/ ``` ```json { "id": "code", "delete_token_hash": "yourDeleteToken", "created_at": "2026-05-26T11:11:09.799454368Z", "expires_at": "2026-06-25T11:11:09.799454368Z", "data_policy": "once", "size": 6, "content_type": "text/plain; charset=utf-8" } ``` 6. **Custom Expiration**: Use `data-policy` values such as `30m`, `12h`, or `7d` to set a custom expiration up to 30 days. The suffix must be `m` for minutes, `h` for hours, or `d` for days. Invalid values such as `0m`, `31d`, `721h`, `1w`, or `1.5h` are rejected with `400 Bad Request`. ```bash curl -H "data-policy: 12h" -F "file=@test.txt" http://localhost:8080/ ``` 7. **Expiration Information**: Temporary and custom-duration uploads include an `expires` field in the response so you can check when the file will expire. The response timestamp is formatted in the server's local timezone. If `data-policy: permanent` is used, the expiration date is not shown. ``` url: http://localhost:8080/RANDOM_CODE expires: 2026-06-24T14:10:26+09:00 manage: http://localhost:8080/RANDOM_CODE?manage=MANAGE_TOKEN ``` 8. **Manual Deletion**: Open the private manage URL returned by the upload and use its delete button. The manage page sends an authenticated `DELETE` request to the management API. CLI clients can make the same request with the manage token. Deletion requests are also recorded in the container logs. ```bash curl -X DELETE -H "paste-manage-token: MANAGE_TOKEN" http://localhost:8080/api/v1/pastes/RANDOM_CODE ``` 9. **Password-Protected Links**: Private upload link creation using the `usepassword: true` header is supported. When this header is used, an 8-character password is issued, generated from a combination of uppercase English letters, lowercase English letters, numbers, and special characters. Files can be viewed directly using the `?password=...` query parameter or the `paste-password: ...` header, or by entering the password manually when accessing the link in a browser. ```bash # Create password-protected link curl -H "usepassword: true" -F "file=@secret.txt" http://localhost:8080/ # View file: header method curl -H "paste-password: RANDOM_PASSWORD" http://localhost:8080/RANDOM_CODE # View file: query parameter method curl "http://localhost:8080/RANDOM_CODE?password=RANDOM_PASSWORD" ``` Interactive clients may instead supply an 8-128 character password in the `password` header. Do not combine it with `usepassword: true`. The supplied password is stored only as a hash and is not echoed in the upload response. JSON upload and clone responses include `password_protected` so clients can verify that protection was applied. 10. **Custom Code**: You can use the `code: ...` header to create a link with a code of your choice instead of a randomly generated code. **Uppercase and lowercase English letters, numbers, and the special characters `_` and `-` are supported.** Codes longer than 10 characters or duplicate codes cannot be created. ```bash curl -H "code: custom123" -F "file=@secret.txt" http://localhost:8080/ ``` Use the optional `label: ...` header to identify a paste independently from its code and original filename. Labels may contain up to 100 characters, are preserved when cloning, and can be changed from the private manage page. ```bash curl -H "label: production deploy log" -F "file=@server.log" http://localhost:8080/ ``` 11. **Upload Response Format**: When an upload succeeds, Pastebox returns the URL, expiration time, and private manage link. If the upload is password-protected with a generated password, the `password` field is also included. ``` url: http://localhost:8080/RANDOM_CODE expires: 2026-06-24T14:10:26+09:00 password: RANDOM_PASSWORD manage: http://localhost:8080/RANDOM_CODE?manage=MANAGE_TOKEN ``` If you need a JSON response for parsing by scripts or other tools, append `?format=json` to the upload request: ```bash curl -F "file=@test.txt" "http://localhost:8080/?format=json" ``` ```json { "url": "http://localhost:8080/RANDOM_CODE", "expires": "2026-06-24T14:10:26+09:00", "password": "RANDOM_PASSWORD", "manage": "http://localhost:8080/RANDOM_CODE?manage=MANAGE_TOKEN", "password_protected": true } ``` 12. **Paste Management Link**: Each successful upload now also generates a private management URL. The link is delivered as a query parameter in the upload response using `?manage=...`. Anyone with that token can access the management page directly without entering a password. Later requests must continue to include the same `manage` token in the URL. The management link can be used to: - switch between public and password-protected access - change the retention policy - delete the paste When converting a password-protected paste back to public, Pastebox requires the current generated password for verification first. CLI and automation clients can use the versioned JSON API at `/api/v1/pastes/`. Send the private manage token in the `paste-manage-token` header; tokens are never returned by this API. `GET` returns management metadata, `PATCH` accepts the actions `set_label`, `set_policy`, `enable_password`, and `disable_password`, and `DELETE` removes the paste. ```bash curl -H "paste-manage-token: MANAGE_TOKEN" http://localhost:8080/api/v1/pastes/RANDOM_CODE curl -X PATCH -H "Content-Type: application/json" -H "paste-manage-token: MANAGE_TOKEN" --data '{"action":"set_policy","data_policy":"12h"}' http://localhost:8080/api/v1/pastes/RANDOM_CODE curl -X DELETE -H "paste-manage-token: MANAGE_TOKEN" http://localhost:8080/api/v1/pastes/RANDOM_CODE ``` 13. **Copy Content in Browser**: When opening a text-based upload link in the browser, you can copy the content to your clipboard using the `Copy` button next to the `Raw` button. 14. **Text File Rendering in Browser**: Text-based files such as `.txt` and `.log` are displayed directly in the browser instead of being downloaded. If you need the original raw response, use `?format=raw`. 15. **Creation and Deletion Logs**: File creation and deletion events are recorded in the container logs. ``` created: id=AbC12 remote=127.0.0.1:51234 size=123 content_type="text/plain; charset=utf-8" policy=temporary expires=2026-06-24T05:10:26Z protected=false deleted: id=AbC12 remote=127.0.0.1:51234 ``` 16. **Fine-Grained Lock Manager**: Pastebox applies locks per upload ID to reduce conflicts when viewing, deleting, or cleaning up the same file concurrently. Different files can still be processed in parallel. 17. **Admin Page**: You can access the admin page by adding `/admin` after the IP address or domain. If no account exists, the first created account becomes the administrator account, and additional account creation is disabled afterward. When `STORAGE_BACKEND=mysql`, the admin account, admin sessions, and admin settings are stored in MySQL. SQLite at `/paste-data/pastebox.db` inside the container, or `./data/pastebox.db` on the host, is then used only for migration completion markers. Passwords are stored in hashed form. The admin dashboard shows paste counts, storage usage, policy breakdown, expiring and expired items, and the current paste storage backend. It also lets administrators enable or disable uploads, delete a single paste, or bulk-delete selected pastes. 18. **Admin Password Reset**: If you lose the admin password, set `ADMIN_RESET_TOKEN` in `docker-compose.yml`, restart the container, and open `/admin/reset`. Enter the reset token and a new password. After reset, existing admin sessions are invalidated and you must log in again with the new password. 19. **Admin Manage Page**: Every successful upload also gets a private manage URL using `?manage=...`. The manage page lets you copy the public URL and manage URL, switch between public and password-protected access, change the retention policy, and delete the paste. If a password-protected paste is converted back to public, Pastebox first asks for the current generated password. 20. **Syntax Highlighting Support**: Syntax highlighting is supported for common text formats including `.txt`, `.md`, `.log`, `.csv`, `.conf`, `.yaml`, `.toml`, `.go`, `.rs`, `.js`, `.py`, `.ts`, `.php`, `.html`, `.css`, `.sql`, `.lua`, and shell scripts such as `.sh`. `Dockerfile`, `*.Dockerfile`, `Makefile`, `.env.example`, `.gitignore`, `compose.yaml`, `compose.yml`, `docker-compose.yaml`, `docker-compose.yml`, `nginx.conf`, and `*.nginx.conf` are also detected by filename. 21. **Long-line Wrap Mode**: When a paste contains a very long single line, the view page shows a `Long line detected` hint and provides a `Wrap` button so you can switch from horizontal scrolling to wrapped reading mode in the browser. 22. **Paste Clone**: You can clone the current paste into a new link by clicking the `Clone` button on the view page. 23. **Open Graph Metadata**: Paste pages expose a `Pastebox - CODE` preview title. Public pastes use the original filename as the preview description when available, while password-protected pastes expose only `Password-protected paste` and never reveal the filename, label, content, password, or management tokens. ### Data Policy For details about the data policy header, see [DATA_POLICY.md](./DATA_POLICY.md)