source: Klonkt/README.md@ 96c714f

main
Last change on this file since 96c714f was 96c714f, checked in by Robin <roboburr@…>, 7 weeks ago

Feature: web push slice 4, burst throttle + docs

  • Burst throttle: a wave of likes or a mass-follow becomes one ping, not a wave of pushes. Per (user, type) at most one push per window (like/boost 300s, follow 60s, reply/dm 30s, test never throttled); extras drop silently — the events themselves still land in Berichten, only the ping is deduped. In-memory (one process; a restart costs at most one extra ping). Pure throttled() exported and pinned by test.
  • README: push notifications feature bullet, VAPID_* in the config table, storage/.vapid in the auto-generated-secrets + backup section (restoring without it silently breaks every subscription).
  • .env.example: VAPID block in the SESSION_SECRET/PAID_SECRET style.

Pruning (404/410 → row deleted) and the iOS install hint already landed in
slices 1-2; this closes the plan from docs/webpush-design.md.

Changed files:
src/services/PushService.js

  • throttled() + window table; notifyUser checks it first

test/push.test.js

  • throttle windows, per-type/per-user independence, test bypass

README.md

  • feature bullet, VAPID config row, backup warning

.env.example

  • VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT

-robo
Co-Authored-By: Claude Opus 4.8 <noreply@…>

  • Property mode set to 100644
File size: 10.7 KB
RevLine 
[184393c]1# Klonkt
[7bc636b]2
[bbbfa3c]3Your own, **self-hosted** corner of the web — for your story, your visuals and
4your sound. Built on **Node + SQLite + htmx**: lightweight, server-rendered, and
5yours. No algorithm, no ads, no platform sitting in between.
[7bc636b]6
[bbbfa3c]7## What it does
[7bc636b]8
[7008b28]9- **Solo** — one personal site, entirely yours, on your own domain.
[bbbfa3c]10- **Blog & photos** — posts with cover, tags, timeline or grid.
11- **Host your own music** — built-in audio player with tracks, albums and playlists.
[7008b28]12- **Fediverse-native** — your posts federate over **ActivityPub**: people follow,
13 like, boost and reply from Mastodon (or another Klonkt). You can follow accounts,
14 see a home timeline, and reply/like back — Klonkt is a full fediverse client.
[bbbfa3c]15- **Grow**: newsletter, download-for-email, EPK/press kit, link-in-bio,
16 show calendar, and **cookie-free statistics**.
[f574435]17- **Paid posts** — put a post behind your **own Patreon**. Supporters unlock it
18 with a **passkey**: no account on your site, no cookie, and a patron's identity
19 (name/email) is never stored — only a pseudonymous, expiring entitlement. You
20 connect your Patreon once in the admin and pick a minimum amount per post.
[ce16d8a]21- **Circles** — a curated feed of the makers you choose: feature accounts (other Klonkt
22 sites, Mastodon, PeerTube — any fediverse server) and their public posts appear in your
23 Circle. Decentralized, no central platform — built on ActivityPub.
[96c714f]24- **Push notifications** — a browser/PWA notification for new followers, replies,
25 mentions, likes, boosts and private messages, even with the site closed.
26 Self-hosted Web Push (VAPID): payloads are encrypted end-to-end to your
27 browser, and private messages never carry their text in the push.
[bbbfa3c]28- **Themes & languages** — multiple palettes (light + dark), interface in EN/NL/DE.
29- **Installable (PWA)**, **privacy-first** (self-hosted fonts, no tracking).
[7bc636b]30
[bbbfa3c]31### Lite mode (no audio)
[7bc636b]32
[bbbfa3c]33Set `KLONKT_AUDIO=off` in `.env` to disable the entire audio/music feature.
34Klonkt then runs as a lightweight **blog/photo/EPK/links site without ffmpeg** —
[7008b28]35ideal for minimal hosting. Circles and external embeds
[bbbfa3c]36(YouTube/SoundCloud/Spotify) keep working.
[7bc636b]37
[196e1be]38It's **fully reversible**: set `KLONKT_AUDIO` back to `on` (or remove the line)
39and restart — no reinstall. The flag only toggles the audio routes/UI; the
40database tables are never dropped, so any tracks you had are preserved.
41
[bbbfa3c]42## Self-hosting
[5a2cb69]43
[bbbfa3c]44Klonkt is a **Node app** — run it on a **VPS, in Docker, or on a Node hosting
45platform (PaaS)**. **Not** on classic shared PHP hosting. The database (SQLite)
46creates itself on first start.
[5a2cb69]47
[c648b04]48> **Requires [Node.js](https://nodejs.org/) 20 or newer.** The one-command VPS
49> installer and the Docker image include it; only a manual install needs you to
50> provide it.
51
[6d3e2c1]52### Option A — One-command VPS installer (recommended)
[5a2cb69]53
[45271b7]54**Good if:** you have (or just rented) a fresh, empty Debian/Ubuntu VPS and want
55the easiest path. This single line does everything — installs Node 20, sets up
56Caddy for automatic HTTPS, runs Klonkt as a background service that auto-restarts
57on reboot, and adds an update command. It's coexistence-safe (if the server
58already runs something it picks a free port and skips Caddy), but a clean VPS is
59simplest. Point your domain's DNS (A + AAAA records) at the server first, then run
60as root:
[7bc636b]61
62```bash
[6d3e2c1]63curl -fsSL https://klonkt.com/install.sh | sudo bash -s -- --domain yourdomain.com
[7bc636b]64```
65
[45271b7]66Then open `https://yourdomain.com` and finish setup in the browser. Update anytime
67with `klonkt-update`.
[184393c]68
[0343a7a]69Updates track the **`stable`** branch — it only advances to a version that's been verified,
70so `klonkt-update` never pulls work-in-progress. Want the bleeding edge instead? Install with
71`--branch main` (or set `KLONKT_BRANCH=main`).
72
[6d3e2c1]73### Option B — Docker
[184393c]74
[45271b7]75**Good if:** you already use Docker, or want everything bundled in one isolated
76container. Node, ffmpeg and cwebp are inside the image — you only need Docker +
77Docker Compose.
[5a2cb69]78
79```bash
[45271b7]80git clone https://github.com/roboburr/klonkt.git
81cd klonkt
[09ee2bd]82cp .env.example .env # works as-is; optionally set PUBLIC_BASE_URL to your domain
[6d3e2c1]83docker compose up -d
[5a2cb69]84```
85
[09ee2bd]86`SESSION_SECRET` is auto-generated on first start, so the defaults work as-is.
[45271b7]87Klonkt runs on port 3000 — put your own reverse proxy in front for HTTPS (see
88step 5 of Option C). Data (database + media) stays in the `klonkt-data` volume,
89even across updates. Update: `git pull && docker compose up -d --build`.
[184393c]90
[45271b7]91### Option C — manual (Node 20+), step by step
92
93**Good if:** you want full control, are adding Klonkt to a server you already
94manage, or just want to test it locally.
95
96**1. Get the code & install dependencies**
[5a2cb69]97
98```bash
[45271b7]99git clone https://github.com/roboburr/klonkt.git
100cd klonkt
[5a2cb69]101npm ci
102```
103
[09ee2bd]104**2. Create your config.** `SESSION_SECRET` (the key that signs login cookies) is
105auto-generated on first start, so this works as-is. For production, set
106`PUBLIC_BASE_URL` to your site address (e.g. `https://yourdomain.com`) so email &
107login links are correct:
[45271b7]108
109```bash
110cp .env.example .env
[7008b28]111nano .env # optional: PUBLIC_BASE_URL, plus SMTP if you want it
[45271b7]112```
113
114**3. Start it** — the SQLite database is created automatically on first start:
115
116```bash
117npm start # runs on http://localhost:3000
118```
119
120Just testing locally? Stop here and open `http://localhost:3000`.
121
122**4. Keep it running** across crashes and reboots (easiest is pm2):
123
124```bash
125npm install -g pm2
126pm2 start src/server.js --name klonkt
127pm2 save && pm2 startup # run the one command it prints, once
128```
129
130**5. Add HTTPS** with a reverse proxy in front. With Caddy (automatic
131certificates), put this in `/etc/caddy/Caddyfile` and reload Caddy:
132
133```caddy
134yourdomain.com {
135 reverse_proxy localhost:3000
136}
137```
138
[f99bbe8]139By default the app binds to `127.0.0.1` (via `HOST` in `.env`), so only your
140reverse proxy can reach it — not the open internet. Local testing on the same
141machine (`localhost:3000`) still works. Only set `HOST=0.0.0.0` if you need direct
142external access without a proxy (then open the port in your firewall and add HTTPS
143yourself).
144
[45271b7]145(`cwebp` is optional — `apt install webp` — for WebP image conversion.)
[5a2cb69]146
[bbbfa3c]147### HTTPS (Docker / bare Node)
[5a2cb69]148
[bbbfa3c]149Put a reverse proxy in front of the app. With **Caddy** (automatic Let's Encrypt):
[5a2cb69]150
151```caddy
[bbbfa3c]152yourdomain.com {
[5a2cb69]153 reverse_proxy localhost:3000
154 encode gzip zstd
155}
156```
157
[bbbfa3c]158(The VPS installer sets up Caddy + HTTPS for you already.)
[184393c]159
[bbbfa3c]160### First run
[5a2cb69]161
[bbbfa3c]162Open your site → you get the **setup wizard**: pick your language, name your site
163and create your admin. The **first user automatically becomes the administrator**;
164registration then closes. Lost your password?
[184393c]165`npm run reset-admin` (Docker: `docker compose exec klonkt npm run reset-admin`).
166
[bbbfa3c]167## Configuration (`.env`)
[184393c]168
[bbbfa3c]169| Variable | Required | What |
[184393c]170|---|---|---|
[f574435]171| `SESSION_SECRET` | auto | Signs login sessions. Leave empty to auto-generate (`storage/.session-secret`), or set a random ≥32-char string. |
[bbbfa3c]172| `PUBLIC_BASE_URL` | ✅ | Canonical URL (e.g. `https://yourdomain.com`) |
[f574435]173| `PAID_SECRET` | auto | Encrypts the stored Patreon secrets for **paid posts**. Leave empty to auto-generate on first use (`storage/.paid-secret`), or set your own ≥16-char string. |
[96c714f]174| `VAPID_PUBLIC_KEY` / `VAPID_PRIVATE_KEY` / `VAPID_SUBJECT` | auto | Identify this server to browser push services (**push notifications**). Leave empty to auto-generate on first use (`storage/.vapid`). |
[bbbfa3c]175| `SMTP_HOST` / `_PORT` / `_USER` / `_PASS` / `_FROM` | — | Email for password reset + newsletter |
176| `KLONKT_DEFAULT_LANG` | — | Default language for visitors (`en`/`nl`/`de`) |
177| `KLONKT_AUDIO` | — | `off` = lite mode (no audio/ffmpeg) |
[f574435]178| `KLONKT_PREMIUM_ENABLED` | — | `on` (default) enables the Patreon-unlocked extras (incl. paid posts) |
[897c33b]179| `HSTS_STRICT` | — | `1` = stricter HTTPS header (`includeSubDomains` + `preload`). Only set this if Klonkt owns the **whole** domain and all its subdomains are HTTPS — it forces every subdomain to HTTPS and can bake your domain into browsers near-permanently. Leave unset otherwise; the default is already safe. |
[7bc636b]180
[f574435]181### Auto-generated secrets & backups
182
183You never have to hand-edit the env for these: Klonkt generates them on first use
184and keeps them stable across restarts and updates.
185
186- `SESSION_SECRET` → `storage/.session-secret` (signs login sessions)
187- `PAID_SECRET` → `storage/.paid-secret` (encrypts the stored Patreon secrets for
188 paid posts)
[96c714f]189- `VAPID_*` → `storage/.vapid` (identifies this server to browser push services;
190 regenerating it would silently break every existing push subscription)
[f574435]191
192The paid-posts key lives **outside** the database on purpose: encrypting the
193Patreon secrets would be pointless if the key sat in the same file a database
194dump would leak. Set either variable in `.env` to override the generated one.
195
196**Back up the whole `storage/` directory** (database, media *and* these key
197files). Restoring the database without `storage/.paid-secret` leaves the stored
[96c714f]198Patreon secrets unreadable — you'd have to reconnect Patreon. Restoring without
199`storage/.vapid` breaks push subscriptions — every device would have to re-enable
200notifications.
[f574435]201
[7bc636b]202## Stack
203
204- **Runtime:** Node 20+
205- **Web:** Express + Helmet + express-session
[bbbfa3c]206- **DB:** better-sqlite3 (WAL), self-migrating on boot
207- **Templates:** EJS (server-rendered) + **htmx 1.9** (vendored, no build step)
208- **Audio:** ffmpeg-static (bundled)
[f574435]209- **Fediverse / Circles:** ActivityPub (HTTP Signatures) — federates with Mastodon, PeerTube and other Klonkt sites. See [FEDERATION.md](FEDERATION.md) for the supported activities and FEPs.
[184393c]210- **Fonts:** self-hosted variable woff2 (Fraunces / Plus Jakarta Sans)
[7bc636b]211
[bbbfa3c]212## Project structure
[7bc636b]213
214```
215src/
[bbbfa3c]216├── server.js # Express bootstrap + route mounting
[7008b28]217├── config/ # database, mailer, feature flags
[bbbfa3c]218├── db/migrations/ # SQLite schema (001-init.sql)
219├── middleware/ # site resolving, auth, render (htmx-aware)
220├── routes/ # per-resource Express routers (posts, auth, admin-*, circle, …)
221├── services/ # domain logic (federation, stats, mailer, permissions, …)
[184393c]222├── views/ # shell.ejs + partials/ + pages/ (EJS)
[bbbfa3c]223└── assets/ # css/ (palette tokens + components), js/ (htmx, player), fonts/
[7bc636b]224```
225
[bbbfa3c]226## License
[7bc636b]227
[bbbfa3c]228**AGPL-3.0-or-later** — see [LICENSE](LICENSE). Klonkt is free software: you may
229use, study, modify and distribute it. The AGPL does require that a modified
230version you offer as a network service makes its source code available to the
231users of that service. Made by robo.burr (Robin Genis) ·
[184393c]232<https://klonkt.com>
[f3663e5]233
234## Acknowledgements
235
236Klonkt stands on a lot of open-source work — Node.js, FFmpeg, SQLite, libwebp, resvg, htmx, EJS and
237more, plus the Fraunces, Plus Jakarta Sans and Literata fonts. See
238[THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md) for the full list and their licences. Thank you to
239everyone who made them.
Note: See TracBrowser for help on using the repository browser.