korrosync¶
Self-hosted KOReader Progress Sync server, running as a Docker stack on the arrstack VM. Syncs reading position between KOReader on the Kobo Clara BW and Crosspoint Reader on the XTEINK X4 — same wire protocol as the official koreader/kosync server, written in Rust on top of an embedded redb K/V store, no Redis sidecar.
LAN URL: http://192.168.1.252:3030. Public URL: https://kosync.rampancy.cloud via Caddy on the edge LXC, TLS terminated at the edge with the Let's Encrypt wildcard cert.
Service details¶
| Property | Value |
|---|---|
| Host | arrstack VM (192.168.1.252) — see arrstack service page |
| Image | szaffarano/korrosync:latest (upstream) |
| Storage | embedded redb K/V file under named volume korrosync_data (/data/db.redb in-container) |
| Compose | stacks/korrosync/docker-compose.yml |
| Management | Dockhand (Git-backed Docker Compose) — controller is local on arrstack |
| Host port | 3030 → container 3000 (host-side remapped to dodge Dockhand's own UI on :3000) |
| Reverse proxy | Caddy on edge LXC — kosync.rampancy.cloud → 192.168.1.252:3030 |
| Backup | Captured by pbs-daily (named volume sits under /var/lib/docker/volumes/korrosync_data/_data, part of the VM's vzdump snapshot) |
Devices¶
Both devices register self-service via KOReader's Progress Sync plugin — no server-side account creation.
| Device | Reader | Plugin | Server URL |
|---|---|---|---|
| Kobo Clara BW | KOReader (sideloaded via MobileRead OCP) | Progress Sync (built-in) | http://192.168.1.252:3030 |
| XTEINK X4 | Crosspoint Reader firmware | KOReader Sync (built-in to Crosspoint) | http://192.168.1.252:3030 |
Open Progress Sync → Custom sync server → enter the URL above, then Register / Login on the first device and Login with the same credentials on the second.
Note
The plugin matches books by binary hash by default — both devices need the byte-identical file. If a book gets re-derived in Calibre on the way to one device, switch the plugin to filename matching.
Warning
Only reading position syncs through this plugin. Bookmarks and highlights live in .sdr sidecar files alongside each book — separate problem, separate tool (Syncthing is the usual answer if it ever matters).
Deployment flow¶
- Compose lives in
homelab-ansible/stacks/korrosync/— not edited on the host. - Dockhand (on arrstack) reads the compose from git on its sync schedule.
- Dockhand dispatches
docker compose upagainst the local Docker daemon (no Hawser hop — same host). - korrosync container starts, binds
0.0.0.0:3000inside the container, host exposes:3030, persists state to thekorrosync_datanamed volume.
The named volume sits under /var/lib/docker/volumes/korrosync_data/_data on arrstack, part of the VM's vzdump rootfs snapshot. PBS backup is automatic.
Reverse-proxy & TLS¶
Reverse proxy lives on the edge LXC (Caddy, Phase 5D). Caddy terminates TLS with the LE wildcard *.rampancy.cloud issued via Cloudflare DNS-01 and proxies to the upstream over plain HTTP.
KORROSYNC's compose ships KORROSYNC_USE_TLS=false — no server-side TLS, edge handles it. KOReader's plugin honours the custom hostname; on each device, set Server URL to https://kosync.rampancy.cloud and re-login.
Lessons from the 2026-04-29 deploy¶
Port-collision miss on :3000¶
First push of the compose (190629d) used the upstream-default 3000:3000. Dockhand's own UI runs on arrstack:3000 (and the Hawser agents on nginx + n8n VMs phone home there via ws://192.168.1.252:3000/api/hawser/connect — see hawser role page). Container failed to bind on first reconcile.
Fix (236b53b) remapped host-side to 3030; container still listens on 3000. Lesson: before publishing a stack onto an existing host, grep homelab-ansible/ for the upstream's default ports — most app READMEs default to 3000/8080/8000 and arrstack already runs a controller on each.
serve subcommand required — README example is stale¶
After the port fix, the container went into a restart loop. docker logs showed the binary printing its CLI help (Commands: serve / user / db / help) and exiting, then restarting under unless-stopped. Cause: the image's ENTRYPOINT is just /app/korrosync with no default subcommand, so without explicit serve the binary treats the invocation as --help and exits 0.
Upstream README's docker run examples don't pass a subcommand and imply the image runs as a server out of the box. Either the README is stale or the CLI was restructured into subcommands recently. Fix (dbee9b8) adds command: serve to the compose service.
Lesson: FROM scratch images leave you with only the binary's stdout to debug from — read the actual ENTRYPOINT in the image's Dockerfile (or docker inspect its Config.Cmd/Config.Entrypoint) rather than trusting the README's example, especially if the binary has subcommands. This is a close cousin of the existing memory rule about verifying a tool's own issue tracker before iterating on theory; here it's "verify the actual image, not the marketing README."
Related¶
- arrstack service page — host VM and its other services
- Role: hawser — Dockhand remote-host agent (also bound to
:3000, the port that bit me) - KOReader Progress Sync wiki — upstream protocol docs
- korrosync upstream