| 1 | # Klonkt
|
|---|
| 2 |
|
|---|
| 3 | Your own, **self-hosted** corner of the web — for your story, your visuals and
|
|---|
| 4 | your sound. Built on **Node + SQLite + htmx**: lightweight, server-rendered, and
|
|---|
| 5 | yours. No algorithm, no ads, no platform sitting in between.
|
|---|
| 6 |
|
|---|
| 7 | ## What it does
|
|---|
| 8 |
|
|---|
| 9 | - **Solo** — one personal site, entirely yours, on your own domain.
|
|---|
| 10 | - **Blog & photos** — posts with cover, tags, timeline or grid.
|
|---|
| 11 | - **Host your own music** — built-in audio player with tracks, albums and playlists.
|
|---|
| 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.
|
|---|
| 15 | - **Grow**: newsletter, download-for-email, EPK/press kit, link-in-bio,
|
|---|
| 16 | show calendar, and **cookie-free statistics**.
|
|---|
| 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.
|
|---|
| 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.
|
|---|
| 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.
|
|---|
| 28 | - **Themes & languages** — multiple palettes (light + dark), interface in EN/NL/DE.
|
|---|
| 29 | - **Installable (PWA)**, **privacy-first** (self-hosted fonts, no tracking).
|
|---|
| 30 |
|
|---|
| 31 | ### Lite mode (no audio)
|
|---|
| 32 |
|
|---|
| 33 | Set `KLONKT_AUDIO=off` in `.env` to disable the entire audio/music feature.
|
|---|
| 34 | Klonkt then runs as a lightweight **blog/photo/EPK/links site without ffmpeg** —
|
|---|
| 35 | ideal for minimal hosting. Circles and external embeds
|
|---|
| 36 | (YouTube/SoundCloud/Spotify) keep working.
|
|---|
| 37 |
|
|---|
| 38 | It's **fully reversible**: set `KLONKT_AUDIO` back to `on` (or remove the line)
|
|---|
| 39 | and restart — no reinstall. The flag only toggles the audio routes/UI; the
|
|---|
| 40 | database tables are never dropped, so any tracks you had are preserved.
|
|---|
| 41 |
|
|---|
| 42 | ## Self-hosting
|
|---|
| 43 |
|
|---|
| 44 | Klonkt is a **Node app** — run it on a **VPS, in Docker, or on a Node hosting
|
|---|
| 45 | platform (PaaS)**. **Not** on classic shared PHP hosting. The database (SQLite)
|
|---|
| 46 | creates itself on first start.
|
|---|
| 47 |
|
|---|
| 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 |
|
|---|
| 52 | ### Option A — One-command VPS installer (recommended)
|
|---|
| 53 |
|
|---|
| 54 | **Good if:** you have (or just rented) a fresh, empty Debian/Ubuntu VPS and want
|
|---|
| 55 | the easiest path. This single line does everything — installs Node 20, sets up
|
|---|
| 56 | Caddy for automatic HTTPS, runs Klonkt as a background service that auto-restarts
|
|---|
| 57 | on reboot, and adds an update command. It's coexistence-safe (if the server
|
|---|
| 58 | already runs something it picks a free port and skips Caddy), but a clean VPS is
|
|---|
| 59 | simplest. Point your domain's DNS (A + AAAA records) at the server first, then run
|
|---|
| 60 | as root:
|
|---|
| 61 |
|
|---|
| 62 | ```bash
|
|---|
| 63 | curl -fsSL https://klonkt.com/install.sh | sudo bash -s -- --domain yourdomain.com
|
|---|
| 64 | ```
|
|---|
| 65 |
|
|---|
| 66 | Then open `https://yourdomain.com` and finish setup in the browser. Update anytime
|
|---|
| 67 | with `klonkt-update`.
|
|---|
| 68 |
|
|---|
| 69 | Updates track the **`stable`** branch — it only advances to a version that's been verified,
|
|---|
| 70 | so `klonkt-update` never pulls work-in-progress. Want the bleeding edge instead? Install with
|
|---|
| 71 | `--branch main` (or set `KLONKT_BRANCH=main`).
|
|---|
| 72 |
|
|---|
| 73 | ### Option B — Docker
|
|---|
| 74 |
|
|---|
| 75 | **Good if:** you already use Docker, or want everything bundled in one isolated
|
|---|
| 76 | container. Node, ffmpeg and cwebp are inside the image — you only need Docker +
|
|---|
| 77 | Docker Compose.
|
|---|
| 78 |
|
|---|
| 79 | ```bash
|
|---|
| 80 | git clone https://github.com/roboburr/klonkt.git
|
|---|
| 81 | cd klonkt
|
|---|
| 82 | cp .env.example .env # works as-is; optionally set PUBLIC_BASE_URL to your domain
|
|---|
| 83 | docker compose up -d
|
|---|
| 84 | ```
|
|---|
| 85 |
|
|---|
| 86 | `SESSION_SECRET` is auto-generated on first start, so the defaults work as-is.
|
|---|
| 87 | Klonkt runs on port 3000 — put your own reverse proxy in front for HTTPS (see
|
|---|
| 88 | step 5 of Option C). Data (database + media) stays in the `klonkt-data` volume,
|
|---|
| 89 | even across updates. Update: `git pull && docker compose up -d --build`.
|
|---|
| 90 |
|
|---|
| 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
|
|---|
| 94 | manage, or just want to test it locally.
|
|---|
| 95 |
|
|---|
| 96 | **1. Get the code & install dependencies**
|
|---|
| 97 |
|
|---|
| 98 | ```bash
|
|---|
| 99 | git clone https://github.com/roboburr/klonkt.git
|
|---|
| 100 | cd klonkt
|
|---|
| 101 | npm ci
|
|---|
| 102 | ```
|
|---|
| 103 |
|
|---|
| 104 | **2. Create your config.** `SESSION_SECRET` (the key that signs login cookies) is
|
|---|
| 105 | auto-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 &
|
|---|
| 107 | login links are correct:
|
|---|
| 108 |
|
|---|
| 109 | ```bash
|
|---|
| 110 | cp .env.example .env
|
|---|
| 111 | nano .env # optional: PUBLIC_BASE_URL, plus SMTP if you want it
|
|---|
| 112 | ```
|
|---|
| 113 |
|
|---|
| 114 | **3. Start it** — the SQLite database is created automatically on first start:
|
|---|
| 115 |
|
|---|
| 116 | ```bash
|
|---|
| 117 | npm start # runs on http://localhost:3000
|
|---|
| 118 | ```
|
|---|
| 119 |
|
|---|
| 120 | Just 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
|
|---|
| 125 | npm install -g pm2
|
|---|
| 126 | pm2 start src/server.js --name klonkt
|
|---|
| 127 | pm2 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
|
|---|
| 131 | certificates), put this in `/etc/caddy/Caddyfile` and reload Caddy:
|
|---|
| 132 |
|
|---|
| 133 | ```caddy
|
|---|
| 134 | yourdomain.com {
|
|---|
| 135 | reverse_proxy localhost:3000
|
|---|
| 136 | }
|
|---|
| 137 | ```
|
|---|
| 138 |
|
|---|
| 139 | By default the app binds to `127.0.0.1` (via `HOST` in `.env`), so only your
|
|---|
| 140 | reverse proxy can reach it — not the open internet. Local testing on the same
|
|---|
| 141 | machine (`localhost:3000`) still works. Only set `HOST=0.0.0.0` if you need direct
|
|---|
| 142 | external access without a proxy (then open the port in your firewall and add HTTPS
|
|---|
| 143 | yourself).
|
|---|
| 144 |
|
|---|
| 145 | (`cwebp` is optional — `apt install webp` — for WebP image conversion.)
|
|---|
| 146 |
|
|---|
| 147 | ### HTTPS (Docker / bare Node)
|
|---|
| 148 |
|
|---|
| 149 | Put a reverse proxy in front of the app. With **Caddy** (automatic Let's Encrypt):
|
|---|
| 150 |
|
|---|
| 151 | ```caddy
|
|---|
| 152 | yourdomain.com {
|
|---|
| 153 | reverse_proxy localhost:3000
|
|---|
| 154 | encode gzip zstd
|
|---|
| 155 | }
|
|---|
| 156 | ```
|
|---|
| 157 |
|
|---|
| 158 | (The VPS installer sets up Caddy + HTTPS for you already.)
|
|---|
| 159 |
|
|---|
| 160 | ### First run
|
|---|
| 161 |
|
|---|
| 162 | Open your site → you get the **setup wizard**: pick your language, name your site
|
|---|
| 163 | and create your admin. The **first user automatically becomes the administrator**;
|
|---|
| 164 | registration then closes. Lost your password?
|
|---|
| 165 | `npm run reset-admin` (Docker: `docker compose exec klonkt npm run reset-admin`).
|
|---|
| 166 |
|
|---|
| 167 | ## Configuration (`.env`)
|
|---|
| 168 |
|
|---|
| 169 | | Variable | Required | What |
|
|---|
| 170 | |---|---|---|
|
|---|
| 171 | | `SESSION_SECRET` | auto | Signs login sessions. Leave empty to auto-generate (`storage/.session-secret`), or set a random ≥32-char string. |
|
|---|
| 172 | | `PUBLIC_BASE_URL` | ✅ | Canonical URL (e.g. `https://yourdomain.com`) |
|
|---|
| 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. |
|
|---|
| 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`). |
|
|---|
| 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) |
|
|---|
| 178 | | `KLONKT_PREMIUM_ENABLED` | — | `on` (default) enables the Patreon-unlocked extras (incl. paid posts) |
|
|---|
| 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. |
|
|---|
| 180 |
|
|---|
| 181 | ### Auto-generated secrets & backups
|
|---|
| 182 |
|
|---|
| 183 | You never have to hand-edit the env for these: Klonkt generates them on first use
|
|---|
| 184 | and 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)
|
|---|
| 189 | - `VAPID_*` → `storage/.vapid` (identifies this server to browser push services;
|
|---|
| 190 | regenerating it would silently break every existing push subscription)
|
|---|
| 191 |
|
|---|
| 192 | The paid-posts key lives **outside** the database on purpose: encrypting the
|
|---|
| 193 | Patreon secrets would be pointless if the key sat in the same file a database
|
|---|
| 194 | dump 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
|
|---|
| 197 | files). Restoring the database without `storage/.paid-secret` leaves the stored
|
|---|
| 198 | Patreon secrets unreadable — you'd have to reconnect Patreon. Restoring without
|
|---|
| 199 | `storage/.vapid` breaks push subscriptions — every device would have to re-enable
|
|---|
| 200 | notifications.
|
|---|
| 201 |
|
|---|
| 202 | ## Stack
|
|---|
| 203 |
|
|---|
| 204 | - **Runtime:** Node 20+
|
|---|
| 205 | - **Web:** Express + Helmet + express-session
|
|---|
| 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)
|
|---|
| 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.
|
|---|
| 210 | - **Fonts:** self-hosted variable woff2 (Fraunces / Plus Jakarta Sans)
|
|---|
| 211 |
|
|---|
| 212 | ## Project structure
|
|---|
| 213 |
|
|---|
| 214 | ```
|
|---|
| 215 | src/
|
|---|
| 216 | ├── server.js # Express bootstrap + route mounting
|
|---|
| 217 | ├── config/ # database, mailer, feature flags
|
|---|
| 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, …)
|
|---|
| 222 | ├── views/ # shell.ejs + partials/ + pages/ (EJS)
|
|---|
| 223 | └── assets/ # css/ (palette tokens + components), js/ (htmx, player), fonts/
|
|---|
| 224 | ```
|
|---|
| 225 |
|
|---|
| 226 | ## License
|
|---|
| 227 |
|
|---|
| 228 | **AGPL-3.0-or-later** — see [LICENSE](LICENSE). Klonkt is free software: you may
|
|---|
| 229 | use, study, modify and distribute it. The AGPL does require that a modified
|
|---|
| 230 | version you offer as a network service makes its source code available to the
|
|---|
| 231 | users of that service. Made by robo.burr (Robin Genis) ·
|
|---|
| 232 | <https://klonkt.com>
|
|---|
| 233 |
|
|---|
| 234 | ## Acknowledgements
|
|---|
| 235 |
|
|---|
| 236 | Klonkt stands on a lot of open-source work — Node.js, FFmpeg, SQLite, libwebp, resvg, htmx, EJS and
|
|---|
| 237 | more, 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
|
|---|
| 239 | everyone who made them.
|
|---|