poslop 76d7ac12e7
CI / test (push) Successful in 21s
CI / docker (push) Skipped
Small fixes: gutter alignment, paste column, sort arrows, copy feedback, stats bar, title slug (#50-#55)
- #50 gutter/code line misalignment: shared --code-lh/--code-fs tokens;
  .code .gutter drops its own vertical padding (was 14px vs 0, 14px offset)
- #51 history Paste column shows only the paste name, or slug pill when untitled
- #52 sort indicator arrow sits LEFT of the column label for all sortable columns
- #53 copy buttons give in-place success feedback ('Success!' in --ok for 2s):
  paste view copy button and new-page result copy button
- #54 collapsed stats summary bar: roomier padding (14px 18px) and item gap (16px);
  wraps gracefully on mobile
- #55 paste view title bar: slug pill only shown for custom slugs, id not repeated

Closes #50, closes #51, closes #52, closes #53, closes #54, closes #55
2026-09-08 22:35:43 -05:00

Palette

Fast, self-hosted pastebin with paste cans, password lock, expiry, custom URLs, and an API-first design.

Quick start

go build -o palette .
./palette
# UI at http://localhost:8080

Docker

docker build -t palette .
docker run -p 8080:8080 -v palette-data:/data palette

Configuration

Env var Default Description
PALETTE_ADDR :8080 Listen address
PALETTE_DB palette.db SQLite database path
PALETTE_MAX_TEXT 5242880 Max paste size in bytes (5 MB)
PALETTE_MAX_ITEM 26214400 Max can item size in bytes (25 MB)

API

Create paste

curl -X POST http://localhost:8080/api/pastes \
  -H "Content-Type: application/json" \
  -d '{
    "content": "print(hello)",
    "title": "my snippet",
    "language": "python",
    "expires_in": "168h",
    "password": "optional",
    "custom_slug": "optional",
    "burn_after_read": false,
    "visibility": "public"
  }'

Response includes id, url, raw_url, api_url, and a one-time deletion_token.

Get paste

curl http://localhost:8080/api/pastes/{id}
# password-protected pastes:
curl "http://localhost:8080/api/pastes/{id}?password=secret"
# or via header: X-Paste-Password: secret

Raw content

curl http://localhost:8080/raw/{id}

Soft delete

curl -X DELETE http://localhost:8080/api/pastes/{id}

Hard delete (requires deletion token)

curl -X DELETE "http://localhost:8080/api/pastes/{id}/redeem?token=TOKEN"

Public history

curl "http://localhost:8080/api/public?limit=25&offset=0"

Create can (bundle of items)

curl -X POST http://localhost:8080/api/pastes/can \
  -F "title=My bundle" \
  -F "expires_in=48h" \
  -F 'json_items=[{"title":"notes.txt","content":"some notes"}]' \
  -F "files=@screenshot.png" \
  -F "files=@log.txt"

Get can + items

curl http://localhost:8080/api/cans/{id}
curl http://localhost:8080/api/cans/{id}/items/{item_id}

Expiry and deletion

  • Expired pastes are soft-deleted by a background sweeper (runs every minute).
  • Soft-deleted pastes are hard-deleted after a 7-day grace period.
  • Deletion tokens allow immediate hard delete.
  • Burn-after-read pastes are soft-deleted on first read.

Web pages

  • /new — create a paste
  • /history — public paste history
  • /{id} — view a paste
  • /unlock/{id} — password gate for protected pastes
  • /raw/{id} — raw content with original content type

CI

Gitea Actions workflow at .gitea/workflows/ci.yml:

  • On push to main: go vet + go test
  • On tags: build and push Docker image to git.archfox.org/poslop/palette
S
Description
Fast self-hosted pastebin with small file support
Readme AGPL-3.0
106 MiB
v0.5.1
Latest
2026-09-18 18:50:51 +00:00
Languages
Go 61.1%
HTML 14.4%
JavaScript 13.3%
CSS 11%
Dockerfile 0.2%