| static | ||
| .dockerignore | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| main.go | ||
| README.md | ||
🎵 Music Gift
A personal page to receive song gifts, integrated with ListenBrainz and MusicBrainz. Think “buy me a coffee”, but for music.
Each song costs R$ 10 / € 2 / ≈ € 2 in Bitcoin (configurable).
✨ What this is
A SPA (HTML + CSS + JS) served by a minimal Go HTTP server.
- No login
- No database
- No external Go dependencies
- JSONL append-only storage
- Works great on Raspberry Pi
🧠 How it works
- The visitor picks a song:
- Top and Recent come from your ListenBrainz user.
- Search uses MusicBrainz via the backend (cache + rate limit).
- They add a name/handle (or stay anonymous) and an optional message.
- They choose a payment method (Pix / SEPA / Bitcoin).
- They confirm the order → the songs are reserved for 1 hour.
- They click “I’ve paid” → you get a notification, but it does not auto-confirm payment.
- The songs appear in the public list with status:
- 🟡 reserved (awaiting payment)
- ⏳ pending confirmation (notified)
- ✅ confirmed (manual admin action)
⚠️ There is no automatic payment validation. This is a trust model + manual confirmation. Messages are private and only visible to the gift recipient.
💾 Storage (no database)
Data is stored in a JSONL file (one event per line):
gifts.jsonl
Append-only event log:
order→ order creationorder_patch→ payment method + notified_at (order)order_confirm→ manual order confirmationorder_reject→ manual order rejectiongift→ gift creation (items)patch→ payment method + notified_at (gift)confirm→ manual gift confirmationreject→ manual gift rejection
Why this format
- Safer on crashes (doesn’t corrupt the whole file)
- Updates without full rewrite
- Easy to inspect/debug
- Simple backups (copy one file)
⚙️ Configuration
Before running, copy .env.dist to .env and adjust values as needed:
cp .env.dist .env
1) Payment data (env)
Set the payment info via .env:
PAY_PIX_PRICE="R$ 10"
PAY_PIX_DETAILS="Pix: your.pix.key@example.com"
PAY_PIX_PAYLOAD="PIX_COPIA_E_COLA_AQUI"
PAY_SEPA_PRICE="€ 2"
PAY_SEPA_DETAILS="IBAN: DE00 0000 0000 0000 0000 00 · BIC: DEUTDEFF"
PAY_SEPA_PAYLOAD="BCD\\n001\\n1\\nSCT\\nDEUTDEFF\\nYOUR_NAME\\nDE00 0000 0000 0000 0000 00\\nEUR2\\n\\nMUSIC GIFT\\n"
PAY_BTC_PRICE="₿ 0.00003"
PAY_BTC_DETAILS="BTC: YOUR_BITCOIN_ADDRESS"
PAY_BTC_PAYLOAD="bitcoin:YOUR_BITCOIN_ADDRESS?amount=0.00003"
PAY_*_PAYLOAD is what gets encoded as a QR code and copied to clipboard.
If you want privacy with Pix, you can leave PAY_PIX_PAYLOAD empty and use only the key in PAY_PIX_DETAILS.
2) Environment variables
| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
HTTP server port |
GIFTS_FILE |
./data/gifts.jsonl |
JSONL file path (overridden to /data/gifts.jsonl in Docker) |
LB_USER |
eher |
ListenBrainz user |
LB_USER_AGENT |
(empty) | ListenBrainz User-Agent (include contact) |
MB_USER_AGENT |
(empty) | MusicBrainz User-Agent (include contact) |
RESERVE_TTL_HOURS |
1 |
Reservation TTL in hours |
ADMIN_TOKEN |
(empty) | Token for admin endpoints |
PAY_* |
(empty) | Payment data (see above) |
Recommended example:
LB_USER=eher
MB_USER_AGENT="MusicGift/1.0 (contact: you@example.com)"
ADMIN_TOKEN="a_long_random_secret"
🐳 Run with Docker Compose (recommended)
Expected structure
music-gift/
├── docker-compose.yml
├── Dockerfile
├── .env
├── .env.dist
├── main.go
├── go.mod
├── static/
│ └── index.html
└── data/
└── gifts.jsonl
Start (Raspberry Pi)
docker compose up -d --build
View logs
docker compose logs -f
Update (pull + rebuild)
git pull
docker compose up -d --build
🗄️ Backup
Local file backup:
cp data/gifts.jsonl data/gifts.jsonl.bak
If you are using a Docker volume:
docker compose exec music-gift sh -c "cp /data/gifts.jsonl /data/gifts.jsonl.bak"
Change port or ListenBrainz user
Edit .env:
PORT=8080
LB_USER=eher
Then recreate the container:
docker compose up -d --build
🔐 API endpoints
Public
GET /api/gifts→ list gifts (newest first)POST /api/orders→ create a new order with multiple songsPATCH /api/orders/:id→ set payment method and notifyGET /api/search?q=→ MusicBrainz search (via backend)GET /api/pay→ payment config (public)
Admin (requires ADMIN_TOKEN)
Confirm order payment (id or ref):
curl -X PATCH "http://localhost:8080/api/admin/orders/12/confirm" \
-H "X-Admin-Token: $ADMIN_TOKEN" \
Reject order payment (id or ref):
curl -X PATCH "http://localhost:8080/api/admin/orders/12/reject" \
-H "X-Admin-Token: $ADMIN_TOKEN" \
List orders (with items):
curl "http://localhost:8080/api/admin/orders?status=pending" \
-H "X-Admin-Token: $ADMIN_TOKEN"
Get a single order (id or ref):
curl "http://localhost:8080/api/admin/orders/12" \
-H "X-Admin-Token: $ADMIN_TOKEN"
Compact the JSONL file (optional):
curl -X POST "http://localhost:8080/api/admin/compact" \
-H "X-Admin-Token: $ADMIN_TOKEN"
🧪 Run without Docker (dev)
go run main.go
# http://localhost:8080
No external dependencies. Works on amd64 and arm64.
🚀 Deployment notes
- Ideal for Raspberry Pi
- Use Docker Compose
- Optionally put Caddy / Nginx in front for HTTPS
- The container runs as a non-root user
- Persistent data is ensured via a volume