{"id":50661851,"url":"https://github.com/d0nizam/kosyncthing_plus.koplugin","last_synced_at":"2026-06-08T03:01:29.259Z","repository":{"id":362875351,"uuid":"1261125046","full_name":"d0nizam/kosyncthing_plus.koplugin","owner":"d0nizam","description":"Sync books, annotations, and reading progress across your e-readers — privately, without any cloud. KOSyncthing+ embeds a fully managed Syncthing daemon inside KOReader, with a polished menu, automatic Wi‑Fi handling, conflict resolution, and a plugin API. Works on Kindle, Kobo, and Android and others.","archived":false,"fork":false,"pushed_at":"2026-06-06T10:41:17.000Z","size":454,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-06T11:14:30.362Z","etag":null,"topics":["android","book-sync","e-ink","ebook-reader","kindle","kobo","koplugin","koreader","koreader-plugin","reading","syncthing"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/d0nizam.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-06T09:12:40.000Z","updated_at":"2026-06-06T10:41:21.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/d0nizam/kosyncthing_plus.koplugin","commit_stats":null,"previous_names":["d0nizam/kosyncthing_plus.koplugin"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/d0nizam/kosyncthing_plus.koplugin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d0nizam%2Fkosyncthing_plus.koplugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d0nizam%2Fkosyncthing_plus.koplugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d0nizam%2Fkosyncthing_plus.koplugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d0nizam%2Fkosyncthing_plus.koplugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/d0nizam","download_url":"https://codeload.github.com/d0nizam/kosyncthing_plus.koplugin/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d0nizam%2Fkosyncthing_plus.koplugin/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34046003,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","book-sync","e-ink","ebook-reader","kindle","kobo","koplugin","koreader","koreader-plugin","reading","syncthing"],"created_at":"2026-06-08T03:00:51.489Z","updated_at":"2026-06-08T03:01:29.250Z","avatar_url":"https://github.com/d0nizam.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# KOSyncthing+\n\n[![Release](https://img.shields.io/badge/release-v1.1.4-blue)](https://github.com/d0nizam/kosyncthing_plus.koplugin/releases)\n[![License](https://img.shields.io/badge/license-AGPL--3.0-blue)](LICENSE)\n![Platform](https://img.shields.io/badge/platform-Kindle%20%7C%20Kobo%20%7C%20Android-lightgrey)\n![Tests](https://img.shields.io/badge/tests-460%20passing-brightgreen)\n[![Star on GitHub](https://img.shields.io/badge/Star_on_GitHub-181717?logo=github\u0026logoColor=white)](https://github.com/d0nizam/kosyncthing_plus.koplugin/stargazers)\n\n**Peer-to-peer file synchronisation integrated into KOReader.**\n\n\u003c/div\u003e\n\nKOSyncthing+ is a KOReader plugin that embeds a fully managed [Syncthing](https://syncthing.net/) daemon right inside your e-reader. Books, annotations, and sidecar files stay in sync across all your devices, over your local network or the internet, without ever touching a third-party server.\n\n---\n\n## Contents\n\n- [Why KOSyncthing+?](#why-kosyncthing)\n- [Features](#features)\n- [Supported devices](#supported-devices)\n- [Android (remote mode)](#android-remote-mode)\n- [Installation](#installation)\n- [Migrating from koreader-syncthing or syncthing.koplugin](#migrating-from-koreader-syncthing-or-syncthingkoplugin)\n- [First-time setup](#first-time-setup)\n- [Menu reference](#menu-reference)\n- [Automation](#automation)\n- [Conflict resolution](#conflict-resolution)\n- [Companion plugin API](#companion-plugin-api)\n- [Translations](#translations)\n- [Settings reference](#settings-reference)\n- [Architecture overview](#architecture-overview)\n- [Troubleshooting](#troubleshooting)\n- [Acknowledgements](#acknowledgements)\n- [License](#license)\n\n---\n\n## Why KOSyncthing+?\n\nTwo excellent projects laid the groundwork for running Syncthing on KOReader:\n\n- **[jasonchoimtt/koreader-syncthing](https://github.com/jasonchoimtt/koreader-syncthing)** — the original, 320-star plugin that proved Syncthing could run comfortably on Kindle and Kobo hardware.\n\n- **[bps/syncthing.koplugin](https://github.com/bps/syncthing.koplugin)** — a clean, focused reimplementation with automatic architecture detection (ARM / ARM64) and binary auto-download from GitHub Releases.\n\nKOSyncthing+ stands on both of their shoulders. It takes those foundations and pushes them much further — a deep, polished KOReader menu, automation that truly understands e‑ink, smart Wi‑Fi management that never nags you, and a rich public API that lets other plugins integrate directly.\n\n---\n\n## Features\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eQuick Sync, folder \u0026 device management, pairing wizard, smart header, performance tuning, legacy support, binary management, maintenance, notifications …\u003c/b\u003e – click to expand\u003c/summary\u003e\n\n### Quick Sync\n\nQuick Sync is the one-tap sync flow designed for e-readers that are not left running continuously:\n\n1. If Wi‑Fi is off, Quick Sync turns it on **silently** (no prompt).\n   When the sync completes, Wi‑Fi is returned to its previous state —\n   if it was off before, it is turned back off automatically.\n   If Quick Sync is already in progress, tapping the button again shows\n   a brief \"already in progress\" message instead of starting a second flow.\n   If Wi‑Fi does not come up immediately, Quick Sync retries with\n   **exponential backoff** (7 s → 14 s → 28 s → 56 s → 60 s, up to **2 minutes**\n   total). If Wi‑Fi still cannot connect, it aborts with a clear\n   message and releases all resources — the device is not kept awake.\n   Periodic Sync will try again at the next scheduled interval;\n   manual Quick Sync waits for the next tap.\n\n2. Starts Syncthing, waiting up to 12 seconds for the daemon to initialise.\n\n3. Checks disk space on every synced folder's filesystem — aborts if less than 100 MB free.\n\n4. Triggers a forced rescan (`db/scan`) on every non-paused folder.\n\n5. Polls folder status every 2 seconds, backing off to 10 seconds when no progress is detected.\n\n6. Shows progress notifications: \"Syncing… N items (X MB) remaining\".\n\n7. When all folders reach idle with zero `needItems`, reads per-device transfer stats and shows a summary:\n   \"Sync done — ↑ X sent, ↓ Y received\" or \"Sync done — everything up to date\".\n   During the sync, the smart status header updates to show the percentage\n   complete (e.g. \"Syncing… 45% (12 MB remaining)\"). This works both for\n   manual Quick Sync and for background sync when the daemon is running.\n\n8. Stops Syncthing and releases the wakelock.\n\n9. Times out after 30 minutes with a warning if folders are still not idle.\n\nA **wakelock** (`preventSuspend` / `allowSuspend`) is held for the entire Quick Sync so the device does not sleep mid-transfer.\n\n\u003e When Syncthing is **already running**, the Quick Sync button changes label to **Rescan all folders** and only triggers a fresh rescan without stopping the daemon afterwards.\n\n### Folder and device management\n\n- **Per-folder status** — shows each folder's live state (Up to date, Syncing… N MB remaining, Scanning, Error, Paused) inside the Status menu.\n- **Honest \"Fix error\"** — when a folder reports an error, the Status menu shows the real Syncthing message (not just a count). The folder's action button reads **\"Fix error\"** only when a rescan would actually clear it (a transient *„… changed during …\"* error); for errors a rescan cannot fix (permission denied, no space, folder marker missing, I/O) it stays a neutral **\"Rescan folder\"**, so the UI never promises a fix it cannot deliver. The full error text is also included in **Copy diagnostic info**, tagged *rescan-fixable* or *needs attention*.\n- **Pause / resume all folders** — the button label reflects the live paused count. When some folders are paused, it reads \"Resume N paused folder(s)\"; when all are active, \"Pause all N folders\".\n- **Accept pending devices** — view and accept incoming pairing requests from the KOReader menu. After accepting, offers to share all existing configured folders with the new device in one tap.\n- **Accept pending folders** — accept a folder shared by another device.\n  The plugin suggests `\u003chome_dir\u003e/\u003cfolder_label\u003e`; you can change the\n  destination path before confirming. The folder is shared with the\n  offering device, **and automatically enables `modTimeWindowS=2`\n  together with other FAT‑friendly defaults** so the folder is ready for e‑reader\n  storage without extra configuration.\n- **Remove a folder** — remove a folder from Syncthing's config from the Status menu. Synced files on disk are not touched.\n- **Pause / resume a single device** — pause an individual remote device from the Devices submenu.\n\n### Guided pairing wizard\n\n- Displays this device's ID as both plain text and a QR code with step-by-step instructions.\n- Polls the Syncthing API for incoming pair requests using exponential backoff: 4 s → 8 s → 16 s → 30 s (capped), for fast response in the first two minutes and low API load during a longer wait.\n- Abandons polling after 5 minutes with a clear message.\n- On receiving a request, shows a name/ID confirmation dialog.\n- Shows a periodic \"Still watching… X min remaining\" reminder so you\n  always know the wizard is alive and how much longer it will wait.\n\n### Smart status header\n\nThe top-level KOSyncthing+ menu entry shows a live one-line summary.\nAll possible states, in priority order:\n\n| Priority | Header text | Tappable? |\n|----------|-------------|-----------|\n| 1 | `Not installed — use \"Install Syncthing binary\" below` | no |\n| 2 | `Stopped — tap to start` | no |\n| 3 | `⚠ %1 file conflict(s) need attention` | **yes** |\n| 4 | `Starting up…` | no |\n| 5 | `⚠ Errors in %1 folder(s)` | **yes** |\n| 6 | `Syncing… X% (Y remaining)` | no |\n| 7 | `All folders paused` | no |\n| 8 | `Up to date · %1 device(s) online` | no |\n| 9 | `Up to date · no devices online` | no |\n\n- When Quick Sync or background sync is actively transferring files, the\n  header shows the percentage complete (e.g. \"Syncing… 45% (12 MB remaining)\").\n\n- The first match is shown (e.g. conflicts hide everything else).\n- When the header **starts with `⚠`**, it becomes **tappable** and the tap is\n  routed by what is actually wrong, with a matching hint appended:\n  - **Conflicts** → *\" — tap to resolve\"*; tap opens *Status \u0026 conflicts*.\n  - **Errors a rescan can fix** (transient *\"… changed during hashing/scan\"*) →\n    *\" — tap to fix\"*; tap rescans straight away — the same action as *Rescan all\n    folders*. The *Rescan all folders* / *Quick Sync* button is also relabelled\n    **Fix errors** in this state.\n  - **Errors a rescan will not fix** (permission denied, no space, folder marker\n    missing, I/O) → *\" — tap to view\"*; tap opens *Status \u0026 conflicts*, where the\n    real error text is shown. *Status \u0026 conflicts* is also always reachable from\n    its own row, independent of the header.\n- When everything is fine, the header is greyed out and read‑only.\n- On **Android (remote mode)** there is no local daemon to start, so the\n  `Stopped — tap to start` state instead reads\n  `Syncthing app not reachable — open it to sync`.\n- `headerNeedsAction()` (in `st_health.lua`) drives both the tappable state\n  and the hint text, so they can never get out of sync.\n\n### Performance and network tuning\n\n**Fine resource tuning** — applies additional per‑folder and per‑device limits that match the **currently active Resource profile** (Low or Normal). These limits are **not applied automatically** when you switch profiles — you must open **Setup → Fine resource tuning → Apply** to push the correct numbers to a running Syncthing instance. It does so via the Syncthing REST API (PATCH config/folders/{id} and PATCH config/devices/{id}):\n\n| Setting | Low | Normal | v1.2.2 legacy |\n|---------|-----|--------|---------------|\n| `copiers` | 1 | 2 | ✅ applied |\n| `hashers` | 1 | 2 | ✅ applied |\n| `pullerMaxPendingKiB` | 16384 | 32768 | ✅ applied |\n| `scanProgressIntervalS` | -1 | 10 | ✅ applied |\n| `numConnections` (per device) | 1 | 2 | ⚠ skipped (added v1.20.0) |\n\nA **Reset to defaults** option sets all values to Syncthing's built-in defaults.\n\n**Resource profile (Low / Normal)** — applied at startup by the `start-syncthing` shell script via Go runtime environment variables, plus additional API-level options three seconds after start:\n\n| | Low | Normal | v1.2.2 legacy |\n|-|-----|--------|---------------|\n| `GOMEMLIMIT` | 64 MiB | 128 MiB | ✅ applied (env var) |\n| `GOGC` | 50 | 100 | ✅ applied (env var) |\n| `GOMAXPROCS` | 1 | 1 | ✅ applied (env var) |\n| `maxConcurrentIncomingRequestKiB` | 32768 | 262144 | ⚠ skipped (added v1.4.0) |\n| `maxFolderConcurrency` | 1 | 0 | ⚠ skipped (added v1.4.0) |\n\n\u003e **v1.2.2 note:** Go runtime environment variables (`GOMEMLIMIT`, `GOGC`) are\n\u003e set by `start-syncthing` regardless of binary version and provide the primary\n\u003e memory protection on constrained devices.  The API-level limits are\n\u003e supplementary and are safely skipped on v1.2.2.\n\u003e All configuration mutations (add/remove folders and devices,\n\u003e pause, resume, patch settings) are handled via transparent shims, so\n\u003e there is no loss of functionality. Only the fine‑tuning options listed\n\u003e above are skipped because the corresponding config fields did not exist\n\u003e in that version.\n\n**Automatic FAT/FUSE tuning**  \nEvery time Syncthing starts, the plugin checks all configured folders.  \nIf a folder lives on a FAT or FUSE filesystem (the default on Kindle and Kobo),  \nit automatically applies the following safe defaults:\n\n  • `modTimeWindowS = 2` – prevents spurious conflicts caused by the\n    2‑second timestamp resolution of these filesystems.\n    *(Skipped for legacy v1.2.2 — that version had built-in FAT detection\n    before this field was introduced in v1.11.0.)*\n  • `ignorePerms = true` – avoids conflicts from permission mismatches\n    between Linux and FAT/FUSE.\n  • `syncOwnership = false` / `sendOwnership = false` – prevents\n    ownership tracking, which is not supported on FAT/FUSE.\n    *(Skipped for legacy v1.2.2 — ownership sync did not exist in that\n    version, so disabling it is unnecessary.)*\n\nThese changes are **applied automatically at startup** for all folders,\nso no manual action is needed.  \nThe **Fine resource tuning** menu item still handles **profile‑specific**\nsettings (copiers, hashers, pullerMaxPendingKiB, etc.) and can reset\neverything to defaults if needed.\n\n**Network access (LAN only / Global)** — applied via `PATCH config/options` three seconds after startup:\n\n| Option | LAN only | Global | v1.2.2 legacy |\n|--------|----------|--------|---------------|\n| `globalAnnounceEnabled` | false | true | ✅ applied |\n| `relaysEnabled` | false | true | ✅ applied |\n| `natEnabled` | false | true | ✅ applied |\n| `crashReportingEnabled` | false | true | ✅ applied |\n| `autoUpgradeIntervalH` | 0 | 12 | ✅ applied |\n| `urAccepted` | -1 | 0 (if not already set) | ✅ applied |\n\nLAN only also passes `--no-upgrade` to the daemon and sets `STNOUPGRADE=1`.\n\n### Legacy Syncthing support\n\n\u003e ⚠️ **Experimental — this feature may not work as intended.** Legacy mode\n\u003e has **not been tested on real old-kernel hardware** — the author has no\n\u003e such device. Its decision logic (kernel detection and version selection),\n\u003e the GitHub download URLs, and the v1.2.2 API-compatibility shim are covered\n\u003e by an offline test suite (`spec/st_legacy_spec.lua`, 69 tests), and the\n\u003e download URLs were confirmed against Syncthing's published release assets.\n\u003e But the full on-device path — downloading a years-old Syncthing build,\n\u003e launching it on a 2.6.x/3.0.x kernel, and actually syncing — has never been\n\u003e exercised on a device. Treat it as best-effort: it may fail in ways the\n\u003e offline tests cannot catch, **especially on the v1.2.2 path** for the\n\u003e oldest kernels. If you try it, please report the result (whether it works\n\u003e or not, with any error text) on the issue tracker so it can be improved.\n\nSome e-readers run a Linux kernel older than 3.2 (for example, the Kindle\nPaperwhite 1st Generation with kernel 2.6.31).  The Go runtime used by\ncurrent Syncthing releases requires kernel ≥ 3.2; starting the daemon on an\nolder kernel produces an immediate fatal crash:\n\n```\nruntime: epollwait on fd 4 failed with 38\nfatal error: runtime: netpoll failed\n```\n\n\nLegacy mode solves this by running a separate, older Syncthing binary that\nwas compiled with a Go version compatible with the device's kernel:\n\n- **v1.27.12** — for kernels 2.6.32–3.1 (recommended).  Full modern REST\n  API; all plugin features work identically to the standard binary.\n\n- **v1.2.2** — very old kernels below 2.6.32 (e.g. Kindle PW1). Pre-dates the\n  modern `/rest/config` API. The plugin transparently translates all\n  configuration changes (adding/removing devices and folders, pausing,\n  patching settings) through a read‑modify‑write shim, so the full\n  folder/device management works exactly as with the standard binary.\n  Only the features listed in the table under *Performance and network\n  tuning* are skipped or adjusted.\n\n**Automatic detection** — on first load the plugin silently classifies the\nkernel via `uname -r` into one of three states: *old* (\u003c 3.2, needs legacy),\n*modern* (≥ 3.2, runs the standard binary), or *unknown* (could not be read).\nIf the kernel is old and legacy is not yet configured, a non-intrusive hint\nappears suggesting **Setup → Legacy Syncthing**.\n\n**Guided setup** — enabling legacy mode is one decision, not a quiz.\n**Setup → Legacy Syncthing → Set up Legacy mode…** detects the right version\nfor the device's kernel, downloads it, and offers to start Syncthing straight\naway.  A *Choose version manually* option remains for the rare case where the\ndetected kernel is misleading.\n\n**Isolated state** — the legacy daemon uses its own config directory\n(`settings/syncthing-legacy/`) separate from the standard one\n(`settings/syncthing/`).  Each binary has its own API key, TLS certificate,\nand device ID.  This means re-pairing is required when switching modes, but\nit prevents config-schema corruption that would occur if two binaries with\ndifferent config-field sets shared the same `config.xml`.\n\n**Binary management** — the legacy binary (`syncthing-legacy`) is downloaded\nas part of the guided setup, or re-downloaded later via **Setup → Legacy\nSyncthing → Re-download legacy binary**.  The selected version is recorded on\ndisk, so the plugin refuses to start a binary that does not match the chosen\nversion.  Factory reset and plugin removal clean up both directories.\n\n### Binary management\n\n- **First-run download prompt** — if no Syncthing binary is present, starting the plugin presents a friendly dialog explaining what Syncthing is and offering to download it. No technical knowledge required.\n- **Auto-download from official releases** — fetches the correct binary for your device architecture (ARM 32‑bit or ARM64) from the official [syncthing/syncthing](https://github.com/syncthing/syncthing/releases) GitHub Releases page.\n- **Architecture verification before download** — detects `uname -m` and selects the matching `linux-\u003carch\u003e` tarball. Shows a warning dialog if the architecture is unrecognised and lets you decide whether to proceed.\n- **Architecture check before start** — verifies the installed binary matches the device before attempting to launch. Refuses to start with an informative error if there is a mismatch.\n- **Size sanity check** — after downloading, verifies the file is within 75%–125% of the expected size. Rejects corrupted or truncated downloads before attempting extraction.\n- **In-place update** — if Syncthing is running when a new binary is installed, the plugin stops it, installs the binary, and restarts silently.\n- **Free space check** — requires at least 20 MB free before starting a download.\n- **Download transport** — uses `curl` (with `cacert.pem` for certificate verification) when available, falls back to LuaSec (HTTPS), then LuaSocket (HTTP).\n- **Atomic install** — binary is installed via a temporary `.new` file that replaces the current one only after ELF and size checks pass.\n\n### Starting and stopping\n\n- **Architecture-aware start guard** — refuses to start a binary that does not match the device CPU.\n- **Loopback interface management (Kobo)** — on Kobo devices, brings up the `lo` interface with `ifconfig lo up` / `ip link set lo up` if it is down, since Syncthing communicates with its own REST API over `127.0.0.1`.\n- **Kindle firewall** — opens the Web GUI port, sync port (22000/TCP) and local discovery port (21027/UDP) in `iptables` before starting, removes all three on stop. Fixes the pairing \"connection refused\" problem on Kindle.\n- **Re-entrancy guard** — a `_starting` / `_stopping` flag prevents two concurrent start or stop operations from racing on rapid taps.\n- **Clean shutdown** — sends `SIGTERM`, polls for up to 4 seconds, then sends `SIGKILL`. On device suspend, uses a synchronous 1-second sleep so the kernel does not pause Syncthing's I/O mid-flush.\n- **Silent start** — auto-start and periodic sync bypass the \"Syncthing started\" toast; only user-initiated starts show a notification.\n- **Auto-merge after sync** — opt-in setting in the Automation menu; after each Quick Sync, reading-progress conflicts are merged automatically (higher progress wins). Disabled by default.\n\n### Maintenance\n\n- **View logs** — displays `settings/syncthing/syncthing.log` in a KOReader text viewer. The log is capped at 5 MB with 1 rotation file (--log-max-size / --log-max-old-files). Total disk usage for logs never exceeds about 10 MB.\n- **Clear log file** — deletes the log; a new one is created on the next start.\n- **View errors only** — filters the log to show only `[WARNING]` and `[ERROR]` lines, making it easier to spot problems without scrolling through the full output.\n- **View API errors (N)** — shows up to 8 recent REST API errors (the number is shown in the menu label).\n- **Clear all API errors** — removes all stored API errors.\n- **Copy diagnostic info** — collects plugin version, Syncthing version, running state, port, binary file status (ELF check, architecture, size), process details (RSS, threads, CPU time), filesystem type \u0026 free space, network state (loopback, IP, Kindle firewall ports), folder/device counts, database location, last 5 API errors, the last 5 Syncthing log entries, and the last 5 plugin WARN/ERROR lines. Displays the result as a QR code (scan with your phone) and copies the same text to the clipboard. Paste into a bug report or support request.\n- **Copy API key** — copies the Syncthing REST API key to the clipboard.\n- **Reset sync database** — deletes the index database directory, forcing a full re-index on the next start. Stops Syncthing first if running.\n- **Reset everything to factory defaults** — double-confirmed destructive reset: stops Syncthing, deletes `settings/syncthing/` entirely (config, TLS keys, device ID, database), wipes all `syncthing_*` keys from KOReader settings, and resets all in-memory plugin state. Synced files on disk are **not** deleted.  After stopping Syncthing, the reset verifies that the daemon has actually exited before wiping any files. If Syncthing cannot be stopped (e.g. due to a kernel I/O hang), the reset is refused with a clear message.\n- **Restart Syncthing** — stop and start in one tap.\n- **Download / update Syncthing** — fetch the latest binary from GitHub Releases.\n\n### Notifications\n\n- Configurable on/off globally via **Show notifications** in the Automation menu.\n- Queued: on e-ink devices, simultaneous toasts render on top of each other and become unreadable. All notifications pass through a queue that waits for each toast's timeout before showing the next one.\n\n\u003c/details\u003e\n\n---\n\n## Supported devices\n\nThis plugin is tested and confirmed working on:\n\n| Platform | Device | Notes |\n|----------|--------|-------|\n| Kindle | **Paperwhite 12th Generation (2024)** | Standard mode |\n| Kindle | **Basic 10th Generation (2019)** | Standard mode |\n| Kobo | **Libra Colour (2024)** | Standard mode |\n\n(Please send pull requests to add your tested device here!)\n\nBecause Syncthing itself is cross‑platform, the plugin should work on any\nLinux‑based e‑reader that runs KOReader (Kobo, other Kindle models, etc.),\nbut these have **not been explicitly verified**.\n\n### Older devices (kernel \u003c 3.2)\n\nDevices with kernels older than 3.2 require **Legacy mode**.  The plugin\ndetects the kernel version on first load and guides you to the right binary:\n\n| Kernel range | Binary | Example devices |\n|-------------|--------|-----------------|\n| ≥ 3.2 | Standard (latest) | Kobo Libra 2, Kindle PW4/5/12 |\n| 2.6.32–3.1 | Legacy **v1.27.12** | Kobo Touch 2, Kindle PW2/PW3 |\n| \u003c 2.6.32 | Legacy **v1.2.2** | Kindle PW1, Kindle Touch |\n\n\u003e ✅ **Android is supported in remote mode.**  \n\u003e On Android the plugin does **not** run its own daemon; instead it connects as a\n\u003e REST client to a Syncthing **app** that you install separately, such as\n\u003e [Syncthing-Fork](https://github.com/Catfriend1/syncthing-android) or\n\u003e [BasicSync](https://github.com/chenxiaolong/BasicSync).  \n\u003e See **[Android (remote mode)](#android-remote-mode)** below for setup and details.\n\nThe Syncthing binary is **not** included in the plugin ZIP — it is\ndownloaded on first use and the downloader detects your architecture\nautomatically.\n\n---\n\n## Android (remote mode)\n\nOn Android the plugin runs in **remote mode**: it does not download or manage a\nSyncthing binary itself.  Instead it talks to a Syncthing **app** that already\nruns the daemon on `127.0.0.1` — for example\n[Syncthing-Fork](https://github.com/Catfriend1/syncthing-android) or\n[BasicSync](https://github.com/chenxiaolong/BasicSync).  The plugin becomes a\nthin client of that app's REST API, so the conflict-resolution and reading-tools\nparts of the plugin work exactly as on Kindle/Kobo while daemon management stays\nwith the app.\n\n**Setup**\n\n1. Install and start a Syncthing app on the device.\n2. In the app, open its **Web GUI** (usually `http://127.0.0.1:8384`) or settings\n   and copy the **API Key**.\n3. In KOReader, open **Tools → KOSyncthing+ → Connect to the Syncthing app** and\n   paste the key.  The plugin probes both `https` and `http`, remembers whichever\n   the app uses, and reuses it on every later launch.\n\nThe plugin reaches the API with an `X-API-Key` header and accepts the app's\nself-signed certificate (`verify = none`), so both plain-HTTP and HTTPS apps —\nincluding BasicSync — work without extra configuration.\n\n**What works on Android**\n\n- **Status \u0026 conflicts** — the core feature: conflict files are found by scanning\n  the synced folders, and the same auto-merge / keep-local / keep-remote\n  resolution runs entirely on the local filesystem.\n- **Rescan all folders**, **Pause / resume all folders**\n- **Pair with another device**, **Web GUI access** (address + QR)\n- **Copy diagnostic info**\n- Gestures: **Quick Sync** (rescan) and **Pause / resume all**\n\n**What is not shown (handled by the Syncthing app instead)**\n\nInstalling/starting/stopping the daemon, automation (auto-start, periodic sync),\nlogs, database reset, restart, the GUI password, the listen port, the resource\nprofile, and the network mode (LAN-only vs relays + global discovery) all belong\nto the Syncthing app's own settings, so the plugin does not duplicate them.\nNetwork-mode changes in particular only take effect after the Syncthing app is\nrestarted — something the plugin can't do — so they are intentionally left to\nthe app.  On Android the menu is a dedicated, shorter **KOSyncthing+** menu\nthat offers only the items above.\n\n\u003e **Note:** This is a remote client, not a substitute for the Syncthing app — the\n\u003e app must be installed and running for syncing to happen.\n\n---\n\n## Installation\n\n### Install\n\n1. Download the latest **kosyncthing_plus.koplugin.zip** from [Releases](../../releases).\n2. Extract the `kosyncthing_plus.koplugin/` folder into your KOReader plugins directory:\n\n   | Device | Plugins directory |\n   |--------|-------------------|\n   | Kobo | `/mnt/onboard/.adds/koreader/plugins/` |\n   | Kindle | `/mnt/us/koreader/plugins/` |\n   | Android | `/koreader/plugins/` |\n\n3. Restart KOReader. The plugin appears as **KOSyncthing+** in **☰ → Tools**.\n\n### Migrating from koreader-syncthing or syncthing.koplugin\n\nYour existing Syncthing configuration migrates automatically — no need to re-pair devices or re-add folders.\n\nBoth predecessor plugins store their configuration in the same location that KOSyncthing+ uses:\n\n```\nsettings/syncthing/config.xml   ← device ID, folders, paired devices, API key\nsettings/syncthing/cert.pem     ← TLS identity (device ID is derived from this)\nsettings/syncthing/key.pem\n```\n\nWhen KOSyncthing+ starts for the first time it finds this directory and uses it as-is. Your device ID, all paired devices, all configured folders, and their sync states are preserved.\n\nThe only manual step is downloading the Syncthing binary via **Maintenance → Download / update Syncthing**, since KOSyncthing+ manages its own copy of the binary separately from whichever binary the old plugin used.\n\n---\n\n## First-time setup\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eStep-by-step: download binary, set password, pair, accept folders, sync\u003c/b\u003e\u003c/summary\u003e\n\n### 1 — Download the Syncthing binary\n\nOpen **☰ → Tools → KOSyncthing+ → Start Syncthing** (or **Install Syncthing binary** if no binary is present).\n\nBecause no binary is installed yet, a welcome dialog appears. Tap **Download**. The plugin detects your architecture and fetches the correct binary from the official Syncthing GitHub Releases (~10–15 MB). Wi-Fi is required.\n\nIf auto-download fails (no network, rate-limited, etc.), install manually:\n\n1. Download the Linux ARM or ARM64 build from [syncthing.net/downloads](https://syncthing.net/downloads/).\n2. Extract the `syncthing` binary and copy it to `kosyncthing_plus.koplugin/syncthing` inside your plugins folder.\n3. Make it executable: `chmod +x syncthing`.\n\n### 2 — Set a GUI password\n\n\u003e **Important:** The Syncthing binary must already be downloaded and started at\n\u003e least once before a password can be set – the daemon creates `config.xml` on\n\u003e first launch, and the plugin writes the password into that file.\n\n**If you followed step 1** (binary was downloaded and launched):\nAbout 4 s after KOReader starts, if a binary is present but no password is\nconfigured, the plugin prompts you to set one. You can also do this at any\ntime via the **KOSyncthing+** menu under **Setup → Web GUI password**.\n\n\u003e **Do this before connecting to a network that others can reach.**\n\u003e The Syncthing Web UI listens on `0.0.0.0:8384`. Anyone on your local\n\u003e network can open it until a password is set.\n\n### 3 — Pair with another device\n\nOpen **Setup → Pair with another device**. The wizard shows your device ID and a QR code. On your other device, open Syncthing and add this e-reader by scanning the QR code or entering the ID manually. As soon as the other device sends a pairing request, the wizard detects it and shows a confirmation dialog.\n\n### 4 — Accept shared folders\n\nAfter pairing, your sync partner can share folders with you. Open the **KOSyncthing+** menu, go to **Status \u0026 conflicts** and check the **Pending** section. When you accept a folder, the plugin suggests placing it at `\u003chome_dir\u003e/\u003cfolder_label\u003e`, but you can edit the path before confirming. You can choose any path you like — the plugin only refuses system directories (like /proc, /sys, /dev) and paths with .. for security reasons.\n\n### 5 — Sync\n\nTap **Quick Sync**. The plugin starts Syncthing, waits for all folders to finish, shows a transfer summary, and stops the daemon.\n\n\u003c/details\u003e\n\n---\n\n## Menu reference\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eFull menu tree\u003c/b\u003e\u003c/summary\u003e\n\n\n\n```\nKOSyncthing+                                   ← top‑level entry\n│\n├── [status line]                            ← smart one‑line header\n│   • Normally read‑only and greyed out.\n│   • When conflicts or folder errors exist,\n│     the line starts with ⚠ and becomes\n│     tappable — tapping opens Status \u0026\n│     conflicts right away.\n│   • The appended hint (\" — tap to resolve\"\n│     or \" — tap to view\") tells you exactly\n│     what to do.\n│   • If nothing needs attention the line\n│     simply says \"Up to date\" or \"Stopped\".\n│   • Long‑press performs the most likely\n│     desired action given the current state:\n│     start Syncthing if stopped, open\n│     conflicts if any exist, or run a\n│     Quick Sync if everything is up to date.\n│\n├── Start Syncthing / Stop Syncthing /\n│   Install Syncthing binary                 ← changes label by state:\n│     • no binary → \"Install Syncthing binary\"\n│     • installed \u0026 stopped → \"Start Syncthing\"\n│     • running → \"Stop Syncthing\"\n│\n├── Quick Sync / Rescan all folders          ← label adapts to daemon state:\n│     • stopped → \"Quick Sync\"\n│       (start → scan → wait → stop, shows\n│       transfer summary at the end)\n│     • running → \"Rescan all folders\"\n│       (only triggers a fresh scan,\n│       daemon stays up afterwards)\n│\n├── Pause all N folders /\n│   Resume N paused folder(s)                ← label shows live paused count:\n│     • no paused folders → \"Pause all N folders\"\n│     • some paused → \"Resume N paused folder(s)\"\n│     • all paused → \"Resume all N folders\"\n│\n├── Status \u0026 conflicts (N)                   ← badge = number of conflicts\n│   │                                         also contains pending devices\n│   │                                         \u0026 folders when there are any\n│   │\n│   ├── [dashboard bullets]                  ← read‑only health summary:\n│   │   folder states, online devices, etc.\n│   │\n│   ├── ── Pending ──                        ← only shown when at least one\n│   │   │                                     pending request exists\n│   │   ├── \u003cdevice name\u003e  (device)          ← tap: Accept or Ignore\n│   │   └── \u003cfolder label\u003e (folder)          ← tap: Accept or Ignore\n│   │\n│   ├── \u003cfolder name\u003e: Up to date / Syncing… / Paused\n│   │   │                                     live state from cached health\n│   │   └── Tap → ConfirmBox with three actions:\n│   │       [Pause / Resume]                 ← toggle; label changes instantly\n│   │       [Full details]                   ← opens scrollable info page\n│   │       [Remove folder]                  ← confirm; files on disk stay\n│   │\n│   ├── \u003cdevice name\u003e: Connected / Last seen: …\n│   │   │                                     live connection status\n│   │   └── Tap → ConfirmBox with two actions:\n│   │       [Pause / Resume]                 ← toggle single device\n│   │       [Full details]                   ← ID, address, last-seen time\n│   │\n│   └── ── Conflicts (N) ──                  ← only shown when conflicts exist\n│       ├── Resolve all N conflicts…          ← bulk strategy dialog with\n│       │   three buttons:                     Auto‑merge progress (keeps\n│       │   Keep ALL mine / Use ALL theirs     higher reading progress for\n│       │                                      metadata sidecar files)\n│       ├── \u003cconflict file 1\u003e                ← tap for per‑file dialog:\n│       │   metadata with progress → Mine vs Theirs (percent)\n│       │   other files → timestamps + \"which is newer\"\n│       │   original missing → Keep as new file / Discard\n│       └── … and N more (use bulk resolve)   ← shown when \u003e 50\n│\n├── Setup\n│   ├── Web GUI access                       ← shows URL + optional QR code\n│   ├── Pair with another device             ← guided wizard (QR + auto‑detect)\n│   ├── Web GUI password                     ← set or remove; prompts to stop\n│   │                                         daemon if running\n│   ├── Web GUI port                         ← default 8384;\n│   │   prompts to stop daemon if running\n│   ├── Resource profile                     ← Low (64 MiB RAM) / Normal (128 MiB);\n│   │   on change: folder \u0026 device API tweaks\n│   │   applied instantly; memory \u0026 CPU limits\n│   │   prompt a Syncthing restart\n│   ├── Fine resource tuning                 ← Apply via API or Reset to defaults;\n│   │   uses three‑button dialog (Apply /\n│   │   Reset to defaults / Cancel)\n│   ├── Network access                       ← LAN only / Global;\n│   │   applied immediately when running\n│   └── Legacy Syncthing [⚠ when needed]   ← shown only when the kernel is\n│       │                                    too old (\u003c 3.2), OR legacy is\n│       │                                    already enabled, OR the kernel\n│       │                                    is unknown and a start failed.\n│       │                                    A modern-kernel device never\n│       │                                    sees this entry.\n│       ├── Set up Legacy mode… /            ← when OFF: opens guided setup\n│       │   Legacy mode: ON (vX.Y.Z)            (auto-detects version, offers\n│       │                                       to download and start).\n│       │                                    when ON: tap to disable.\n│       ├── Legacy version: v1.27.12 / v1.2.2 ← only when enabled; manual\n│       │   version override picker\n│       └── Re-download legacy binary (vX.Y.Z) ← only when enabled; offers\n│           to start Syncthing after install\n│\n├── Automation\n│   ├── Show notifications                   ← on/off; when off no completion\n│   │   or conflict toasts appear\n│   ├── Autostart Syncthing         ← actively turns Wi‑Fi on when needed; stops when Wi‑Fi disconnects\n│   ├── Periodic Quick Sync                  ← on/off; runs at chosen interval\n│   ├── Sync interval: X min  ·  next in Y min ← hidden when disabled;\n│   │   shows live countdown to next sync\n│   ├── Auto-merge conflicts after sync      ← opt-in; merges reading-progress\n│   │   conflicts automatically after every Quick Sync; off by default\n│   └── Apply automation only when charging  ← gates all automation; greyed\n│       out when no automation is active\n│\n└── Maintenance\n    ├── View logs                             ← opens scrollable log viewer\n    ├── Clear log file                        ← deletes log; new one created on\n    │   next start\n    ├── View errors only                      ← filters log to show only [WARNING]\n    │   and [ERROR] lines\n    ├── View API errors (N)                   ← shows up to 8 recent REST API errors;\n    │   only active when errors are stored\n    ├── Clear all API errors                  ← removes all stored API errors\n    ├── Copy diagnostic info                  ← version, running state, last 5 API\n    │                                           errors and last 5 WARN/ERROR log\n    │                                           lines; shown as QR code + clipboard\n    ├── Copy API key                          ← copies REST API key to clipboard;\n    │                                           only active when a key exists\n    ├── Reset sync database                   ← prompts to stop daemon if running;\n    │   deletes index files, forces full\n    │   re‑index on next start\n    ├── Reset everything to factory defaults  ← double‑confirm; wipes config,\n    │   database, device ID, all plugin\n    │   settings; synced files on disk are\n    │   NOT deleted\n    ├── Restart Syncthing                     ← only active when running;\n    │   stops and starts silently (no toast)\n    └── Check for updates  (vX.Y.Z installed) /\n        Install Syncthing binary              ← downloads latest binary from\n        GitHub Releases; Wi‑Fi required\n```\n\n\u003c/details\u003e\n\n---\n\n## Automation\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eNotifications, autostart, periodic sync, auto-merge after sync, charging gate, dispatcher actions\u003c/b\u003e\u003c/summary\u003e\n\n### Show notifications\n\nNotifications are **enabled by default**.\nControls whether completion toasts appear outside the KOSyncthing+ menu.\nWhen **on** (checked) you get brief, self‑dismissing messages for\nsuccessful Quick Sync, detected conflicts, and automation errors.\nWhen **off** all background activity stays completely silent.\n\nNotifications are queued so two messages never overlap on screen.\n\n### Autostart Syncthing\n\nAutomatically start Syncthing and keep it running whenever possible.\n• Wi-Fi will be turned on automatically when needed.\n• If Wi-Fi cannot be turned on, Syncthing will not start.\n• A health-check timer runs every 60 seconds: if Syncthing\nshould be running but isn't, it tries to start it again.\n• When Wi-Fi disconnects, Syncthing stops automatically.\n• Works also on LAN-only networks without internet access.\n• Manually stopping Syncthing pauses auto-start for the rest of this session — it starts again next time you open KOReader. Turn this off to stop it permanently.\n\n### Periodic Quick Sync\n\nSchedules a full Quick Sync every N minutes (1–1440, default 30).\nThe Automation menu shows **\"Next sync in: X min\"** live.\n\n**Wi‑Fi behaviour**\n- If Wi‑Fi is **already on** (you turned it on manually), Periodic Sync\n  uses the existing connection, performs the sync, and **leaves Wi‑Fi on**\n  afterwards.\n\n- If Wi‑Fi is **off**, the plugin turns it on before the sync and restores it\n  according to your KOReader Wi‑Fi settings afterwards.\n\n- If Wi‑Fi cannot be turned on immediately, Periodic Sync retries with\n  **exponential backoff** (30 s → 60 s → 120 s → 240 s, up to **8 minutes**\n  total). If Wi‑Fi still cannot connect, it skips this cycle silently\n  and waits for the next scheduled run — no manual intervention is\n  needed.\n\n- The plugin uses KOReader's enableWifi to bring up Wi‑Fi automatically when needed.\n  On platforms where this is not possible, the sync may be skipped.\n\n**Required KOReader Wi‑Fi settings**\n\nBecause the plugin uses KOReader's internal `enableWifi` API with\n`interactive=false`, it can bring up Wi‑Fi by itself without any prompts,\nregardless of the **Action when Wi‑Fi is off** setting. The **Action when\ndone with Wi‑Fi** setting is still respected: if it is set to `turn off`,\nthe plugin will switch Wi‑Fi off after a periodic sync.\n\n\u003e You can still configure those settings in **☰ → ⚙ → Network** – they\n\u003e affect manual Quick Sync and other parts of KOReader, but they are no\n\u003e longer required for KOSyncthing+'s automation.\n\n### Sync interval\n\nOpens a number picker to set the interval in minutes (1–1440).\nWhen Periodic Quick Sync is off, this row is greyed out.\n\n### Auto-merge conflicts after sync\n\nWhen enabled, every Quick Sync that completes triggers an automatic scan for\nreading-progress conflicts. For each KOReader metadata (`.sdr`) conflict, the\ncopy with the higher `percent_finished` wins. Non-metadata files are skipped.\n\n- **Off by default.** Enable only once you are comfortable with the manual\n  *Auto-merge progress* action in Status \u0026 conflicts.\n- A brief notification appears only when merges actually occur or when one\n  fails — silent syncs that have no conflicts produce no notification.\n- The *Show notifications* master switch in this menu still applies; with\n  notifications off, no toast is shown even when merges happen.\n- Uses the same engine as the manual auto-merge action, so the result is\n  identical to running it by hand.\n\n### Charging gate\n\nWhen enabled, all automation — auto‑start and periodic sync — fires\n**only** when the device is plugged in and charging.\n\n### Resume after suspend\n\nIf Syncthing was running before sleep, it starts again automatically when\nthe device wakes up (needs Wi‑Fi; if you turned on \"charging only\", the\ndevice must also be plugged in). With Periodic Quick Sync enabled, an\nimmediate sync runs on resume so nothing is missed.\n\n### Dispatcher actions\n\nThe plugin registers three actions that you can bind to gestures or\nhardware buttons in KOReader's Gesture Manager:\n\n- **Toggle Syncthing** — start or stop the daemon.\n- **Quick Sync** — run a one‑shot sync.\n- **Pause / resume all folders** — toggle the pause state of all folders.\n\nBind these to a gesture in KOReader's Gesture Manager, or open the menu directly via \u003ckbd\u003e☰\u003c/kbd\u003e → **Tools → KOSyncthing+**.\n\n\u003c/details\u003e\n\n---\n\n## Conflict resolution\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003ePer-file and bulk resolution strategies\u003c/b\u003e\u003c/summary\u003e\n\nSyncthing creates `filename.sync-conflict-YYYYMMDD-HHMMSS-DEVID.ext` files when two devices modify the same file concurrently. The plugin scans all configured folder paths for these files and surfaces them in **Status \u0026 conflicts → Conflicts (N)**.\n\n### Per-file resolution\n\nThe dialog shown depends on the file type:\n\n**KOReader metadata sidecar** (`*.sdr/metadata.*.lua`) **with reading progress:**\n- Shows \"Your device: X%\" vs \"Other device: Y%\".\n- Tap **Mine (X%)** or **Theirs (Y%)** to keep that version.\n- Both `percent_finished` (current KOReader format) and `last_percent` (pre-2022 format) are recognised; a dialog is shown if either side has either field.\n- When the conflict copy carries **this device’s** short ID (Syncthing set your own version aside when a remote write arrived first), the labels switch to **Keep incoming (X%)** / **Restore mine (Y%)** — incoming progress vs your own — so you don’t accidentally keep the wrong reading position.\n\n**Any other file (or metadata without progress):**\n- Shows both modification timestamps with a hint (\"→ Your version is newer.\" etc.).\n- Tap **Mine (timestamp)** or **Theirs (timestamp)**.\n- When the conflict copy carries **this device’s** short ID (Syncthing moved your own version aside when a remote write arrived first), the labels switch to **Keep incoming** / **Restore mine** so the intent is unambiguous.\n- When the device that created the conflict copy is known and reachable, its name is shown alongside the timestamp (e.g. “2026-01-01 12:00 (Phone)”).\n\n**Original file missing:**\n- **Keep as new file** renames the conflict copy to the original path.\n- **Discard it** deletes the conflict copy.\n\n### Bulk resolution\n\nThe **Resolve all N conflicts…** option offers three strategies:\n\n- **Auto-merge progress** — for each KOReader metadata conflict where at least one side has `percent_finished` (or the legacy `last_percent`), keeps whichever has the *higher* reading progress. Non-metadata files and conflicts where neither side has a progress value are skipped. Shows a summary: \"Merged N — kept local for X, kept remote for Y, skipped Z.\"\n- **Keep ALL mine** — discards every conflict copy.\n- **Use ALL theirs** — replaces every local file with its conflict copy.\n\nEach row is labelled by the book or file the conflict belongs to — for reading-progress conflicts the book name rather than the internal `metadata.*.lua` — with the time Syncthing recorded the conflict shown on the right. The conflict list is capped at 50 visible entries; a note prompts you to use bulk resolve if more exist.\n\n\u003c/details\u003e\n\n---\n\n## Companion plugin API\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003ePublic API for other KOReader plugins\u003c/b\u003e\u003c/summary\u003e\n\nThe plugin exposes a rich public API for other KOReader plugins.  \nAll methods are documented in detail in **[API.md](API.md)**.\n\nA quick overview of what's available:\n\n- **Status** – `isRunning`, `getConflicts`, `getFolderHealth`, `getStatusHeader`, `getDeviceId`\n- **Control** – `start`, `stop`, `quickSync`, `toggle`, `pauseAllFolders`, `resumeAllFolders`\n- **Conflict resolution** – `resolveAllConflicts` (keep_local / use_remote / auto_merge), `resolveConflictByPath`\n- **Information** – `getFolders`, `getDevices`, `getPendingDevices`, `getPendingFolders`, `getConflictsDetailed`, `getFolderIgnore`, `setFolderIgnore`\n- **Proxied REST call** – `apiCall(endpoint, method, body)` — talk to Syncthing without ever seeing the API key\n- **Events** – `onStatusChange` / `offStatusChange` (custom listeners) and KOReader global events (`SyncthingSyncCompleted`, `SyncthingConflictDetected`)\n- **Utilities** – `formatBytes`, `formatTime`, `isValidDeviceID`\n\nThe API is **platform-agnostic**.  On Android (remote mode) every call is transparently routed to the remote Syncthing app — `apiCall` and the `status` / `control` / `info` helpers all work unchanged, and the reported `version` is the same.  Companion plugins need **no** Android-specific code: they consume `_G.KOSyncthingPlusAPI` exactly as they do on Kindle/Kobo.\n\nAccess it via the global `_G.KOSyncthingPlusAPI` or, preferably, by requiring the module:\n\n\n```lua\nlocal Syncthing = require(\"st_api_public\").api\n```\n\n\nThe IgnoreRegistry (also documented in the API) lets companion plugins exclude\ntheir own sidecar files from the conflict scanner.\n\nAll listener callbacks are wrapped in `pcall` — a broken listener will never crash KOSyncthing+.\n\n\u003c/details\u003e\n\n---\n\n## Translations\n\n| Language | File |\n|----------|------|\n| Bulgarian | `locale/bg.po` |\n\nThe master template for all translatable strings is `locale/syncthing.pot`. To contribute a new language or improve an existing one, edit the relevant `.po` file and open a pull request.\n\n---\n\n## Settings reference\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eAll syncthing_* settings in KOReader\u003c/b\u003e\u003c/summary\u003e\n\nAll settings are stored in KOReader's `G_reader_settings` under the `syncthing_*` prefix.\n\n| Key | Type | Default | Description |\n|-----|------|---------|-------------|\n| `syncthing_port` | string | `\"8384\"` | Web GUI port |\n| `syncthing_gui_user` | string | `\"syncthing\"` | Web GUI username |\n| `syncthing_gui_password` | string | `nil` | Web GUI password (stored locally only) |\n| `syncthing_auto_start_always` | bool | `false` | Autostart Syncthing (actively turns Wi‑Fi on when needed, stops when Wi‑Fi disconnects) |\n| `syncthing_auto_start_charging` | bool | `false` | Gate all automation on charging state |\n| `syncthing_notifications_enabled` | bool | `true` | Enable toast notifications |\n| `syncthing_resource_profile` | string | `\"low\"` | `\"low\"` or `\"normal\"` |\n| `syncthing_network_access` | string | `\"lan\"` | `\"lan\"` or `\"global\"` |\n| `syncthing_android_apikey` | string | `nil` | Android remote mode: API key for the Syncthing app |\n| `syncthing_android_port` | string | `\"8384\"` | Android remote mode: REST API port of the Syncthing app |\n| `syncthing_android_scheme` | string | `nil` | Android remote mode: scheme remembered after the first connect (`http` / `https`) |\n| `syncthing_periodic_sync_enabled` | bool | `false` | Enable periodic Quick Sync |\n| `syncthing_periodic_sync_interval_min` | number | `30` | Periodic sync interval (minutes, 1–1440) |\n| `syncthing_auto_merge_conflicts` | bool | `false` | Auto-merge reading-progress conflicts after every Quick Sync (v1.1.1+) |\n| `syncthing_settings_version` | number | — | Internal migration counter; do not edit |\n| `syncthing_password_dialog_seen` | bool | — | Suppresses the first-launch password prompt |\n| `syncthing_was_running` | bool | — | Internal flag: remembers daemon state across suspend/resume; do not edit |\n| `syncthing_use_legacy` | bool | `false` | Legacy mode enabled; set via **Setup → Legacy Syncthing** |\n| `syncthing_legacy_version` | string | `\"v1.27.12\"` | Selected legacy binary version tag; retained across disable so re-enable is one tap |\n| `syncthing_legacy_installed_version` | string | — | Version of the `syncthing-legacy` binary actually on disk; written after a successful download, checked before start so a version/file mismatch is refused |\n| `syncthing_start_failed` | bool | — | Internal flag: set when a start attempt times out on a non-modern kernel; surfaces the Legacy menu as an escape hatch; cleared on the next successful start |\n\nSyncthing's own config, TLS keys, device ID cache, and index database live in\n`settings/syncthing/` (standard) or `settings/syncthing-legacy/` (legacy mode)\ninside KOReader's data directory — not inside the plugin folder. Plugin updates\nnever touch your pairing configuration or folder setup.\n\n\u003c/details\u003e\n\n---\n\n## Architecture overview\n\n```mermaid\nflowchart TD\n    KO[KOReader host] --\u003e MENU[\"st_menu.lua\u003cbr/\u003emenu tree + tappable header\"]\n    KO -. lifecycle events .-\u003e MAIN[\"main.lua\u003cbr/\u003eplugin object · cache · timers · Dispatcher\"]\n    MAIN --\u003e ORCH[\"st_orchestrator.lua\u003cbr/\u003epolicy: start / stop / Quick Sync / suspend\"]\n    ORCH --\u003e PROC[\"st_process.lua\u003cbr/\u003edaemon lifecycle\"]\n    ORCH --\u003e SYNC[\"st_sync.lua\u003cbr/\u003eQuick Sync · folder health\"]\n    SYNC --\u003e CONF[\"st_conflict.lua\u003cbr/\u003efind · resolve · auto-merge\"]\n    PROC --\u003e SH[\"start-syncthing\u003cbr/\u003eshell launcher\"]\n    SH --\u003e BIN((\"Syncthing daemon\"))\n    SYNC --\u003e API[\"st_api.lua\u003cbr/\u003eREST client\"]\n    API --\u003e BIN\n    CONF --\u003e FS[\"st_filesystem.lua\u003cbr/\u003esafe file ops\"]\n    MENU --\u003e HEALTH[\"st_health.lua\u003cbr/\u003estatus-header logic\"]\n    HEALTH -. reads .-\u003e CACHE[(\"CacheSQLite /\u003cbr/\u003ein-memory cache\")]\n    MAIN --\u003e CACHE\n    ORCH -. notifiers .-\u003e PUB[\"st_api_public.lua\u003cbr/\u003e_G.KOSyncthingPlusAPI\"]\n    PUB --\u003e COMP{{Companion plugins}}\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eFile structure and module descriptions\u003c/b\u003e\u003c/summary\u003e\n\n```\nkosyncthing_plus.koplugin/\n│\n├── _meta.lua            Plugin manifest: name, version, description\n│\n│\n├── main.lua             Plugin class; lifecycle; CacheSQLite / in-memory cache layer;\n│                        settings migration (versioned MIGRATIONS table); periodic sync\n│                        timer; event handlers: onNetworkConnected, onSuspend, onResume,\n│                        onCharging; notification queue; Dispatcher registration\n│\n├── st_orchestrator.lua  Lifecycle orchestrator: manual/auto start, stop,\n│                        Quick Sync, periodic sync, suspend/resume,\n│                        network/charging/close, Wi-Fi cleanup, silent\n│                        flags and callback timing. Policy layer above\n│                        st_process and st_sync.\n│\n├── st_insert_menu.lua   Positions the plugin in the Tools tab (reader + file\n│                        manager): just above \"syncery\" when present, otherwise\n│                        after \"cloudstorage\"/\"move_to_archive\". Uniquely named\n│                        so the shared require() module cache can't collide with\n│                        another plugin's own insert_menu.lua\n│\n├── syncthing_i18n.lua   Gettext loader\n│\n├── cacert.pem           Mozilla CA bundle (required on Kobo, no system CA store)\n│\n│\n├── syncthing            Syncthing binary — not shipped, downloaded on first use;\n│                        lives at plugin root\n│\n├── syncthing-legacy     Legacy Syncthing binary — only present when legacy mode\n│                        is enabled and downloaded; same folder as above\n│\n├── legacy.lua           Legacy mode module: kernel detection (kernelState →\n│                        old/modern/unknown, and recommendedVersion → the\n│                        v1.2.2-vs-v1.27.12 choice, both cached); lifecycle\n│                        (enable/disable with cache invalidation for api_key,\n│                        device-id, and binary-exists); binary download from\n│                        GitHub with chmod verification and installed-version\n│                        recording; API compat patch for v1.2.2 (read-modify-\n│                        write via PUT /rest/system/config replaces PATCH\n│                        endpoints that didn't exist before v1.12.0); guided\n│                        setup flow and manual version picker\n│\n├── st_android.lua       Android remote-mode module (loaded only on Android):\n│                        patches the plugin into a REST client of a separately\n│                        installed Syncthing app — TLS-capable apiCall override,\n│                        no-op start/stop, bounded isRunning probe with TTL,\n│                        getAPIKey from settings, and an lfs-based conflict\n│                        scanner that honours IgnoreRegistry exclusions\n│\n├── start-syncthing      Shell launcher script:\n│                          • argument validation (home_dir, port, resource,\n│                            network, binary_name [default: syncthing],\n│                            config_dirname [default: syncthing],\n│                            legacy_version [default: empty])\n│                          • double-start guard via PID file\n│                          • CLI dialect branch keyed on legacy_version:\n│                            - v1.2.2 → historical single-dash CLI\n│                              (-generate, -home, -gui-address, -logfile,\n│                              -no-browser, -no-restart) — predates the\n│                              serve/generate subcommands\n│                            - standard / v1.27.12 → modern subcommand CLI\n│                              with progressive first-run flag fallback\n│                              (--no-port-probing --no-default-folder →\n│                              --no-port-probing → bare generate)\n│                          • device ID caching to settings/\u003cconfig_dirname\u003e/\n│                            device-id (skipped on v1.2.2, which has no\n│                            device-id subcommand)\n│                          • Go runtime env vars (GOMEMLIMIT, GOGC, GOMAXPROCS=1)\n│                          • STNOUPGRADE / --no-upgrade for LAN mode\n│                          • setsid detach when available; nice -n 10 launch\n│\n├── st_api.lua           Low-level REST API client (GET / POST / PUT / PATCH);\n│                        circular buffer for last 8 API errors\n│\n├── st_api_public.lua    _G.KOSyncthingPlusAPI global; IgnoreRegistry; event notifiers\n│                        (notifyProcessStarted, notifyProcessStopped,\n│                         notifyConflictsChanged)\n│\n├── st_conflict.lua      findConflicts (find command + IgnoreRegistry exclusions);\n│                        resolveConflict (per-file dialog: missing-original, reading-progress\n│                        percentage, generic timestamp, conflict_is_mine label swap);\n│                        autoMergeReadingProgress (keep higher percent_finished / last_percent);\n│                        getConflictsDetailed (structured per-conflict metadata for API callers);\n│                        parseConflictShortId / deviceNameForShortId (conflict-filename device ID\n│                        resolution with daemon-down and self-conflict fallbacks)\n├── st_disabled.lua      Hold-callback helpers that explain why an item is greyed out\n│\n│\n├── st_filesystem.lua    Filesystem safety module: safe, checked wrappers around all\n│                        file and directory operations. Returns (true) on success or\n│                        (false, errmsg) on failure. Eliminates bugs where code assumes\n│                        os.remove/os.rename/io.open always succeed on FAT/FUSE.\n│\n│\n├── st_health.lua        getStatusHeader() with sync progress percentage;\n│                        headerNeedsAction(); connected device count\n│                        with 10-second cache; \"X min ago\" relative time formatting;\n│                        getStatusBullets() with conditional \"Waiting for local API…\"\n│\n│\n├── st_menu.lua          Complete KOReader menu tree: addToMainMenu, getStatusMenu,\n│                        getSetupMenu, getAutomationMenu, getMaintenanceMenu,\n│                        getPendingMenu; re-exports showPasswordDialog and\n│                        _suggestPassword from st_settings so main.lua mix-in\n│                        requires no changes\n│\n├── st_pair.lua          Guided pairing wizard; exponential-backoff polling\n│                        (4→8→16→30 s, 5-minute timeout);\n│                        acceptDevice (add device + optional share-all-folders\n│                        offer); acceptFolder (path confirmation dialog +\n│                        automatic FAT/FUSE safe defaults)\n│\n├── st_process.lua       Binary lifecycle: start (with arch check, home dir check,\n│                        loopback bring-up, Kindle iptables, credential injection,\n│                        0.5-second PID polling), stop (SIGTERM→poll→SIGKILL),\n│                        isRunning (3-layer PID verification: /proc/comm, cmdline, ps),\n│                        applyPerformanceSettings, resetPerformanceSettings,\n│                        applyNetworkSettings, showFirstRunDialog, stopPlugin,\n│                        deletePluginSettings;\n│\n│                        kindlePortGuard(port) — RAII closure that opens the\n│                        iptables rule and returns a one-shot release function;\n│                        releaseKindlePort(self) — calls the closure idempotently\n│                        and clears it, ensuring every exit path closes the port\n│                        exactly once with no per-path boilerplate\n│\n├── st_reset.lua         resetEverything: double-confirm flow; _wipe clears data dir\n│                        + all settings keys + all in-memory state + all module caches\n│\n├── st_settings.lua      GUI password management: showPasswordDialog (password-first\n│                        dialog with optional username step; prompts to stop daemon\n│                        if running); _suggestPassword (first-run prompt shown ~4 s\n│                        after start when no password is configured)\n│\n├── st_sync.lua          quickSync; _startQuickSync (wakelock, disk space check,\n│                        db/scan, byte-transfer snapshot for accurate delta\n│                        reporting, _waitForIdle with adaptive polling and\n│                        30-min timeout); getFolderHealth (per-folder state\n│                        snapshot + aggregates); findConflicts (with configurable\n│                        TTL and IgnoreRegistry); syncNow (rescan trigger);\n│                        setPauseAll; getMountPoint for accurate disk-space check\n│\n├── st_update.lua        checkForUpdates; GitHub Releases API fetch; detectArch();\n│                        performUpdate (download + size check + extraction + atomic mv);\n│                        getCurrentVersion; transport: curl → LuaSec → LuaSocket\n│\n└── st_utils.lua         Shared constants: plugin_path, cacert_path, DANGEROUS_PATHS,\n                         FOLDER_CACHE_TTL, ALL_SETTINGS_KEYS; shellEscape; getDeviceIP\n                         (IPv4-first, IPv6 fallback); kindleOpenPort/kindleClosePort;\n                         loopback cache + invalidation; getFreeSpace; isValidDeviceID;\n                         isOk(r) — nil-safe check for SafeClient result tables\n                         (isOk(nil) → false, never errors); errOf(r) — extracts the\n                         error string from a result or returns \"no response\" when the\n                         result is nil or carries no error field;\n                         isLegacy() / getBinaryPath() / getConfigDir() — mode-aware\n                         helpers evaluated at call time (not module load time) so that\n                         enabling or disabling legacy mode takes effect without a module\n                         reload; all three read G_reader_settings on every invocation\n\nlocale/\n├── syncthing.pot        Master translatable string template\n└── *.po                 Per-language translations (bg)\n```\n\n\nThe main plugin class (`Syncthing`) is assembled by mixing all module return tables into it at startup. Each module exports a flat table of functions; `main.lua` assigns them with `for name, func in pairs(mod) do Syncthing[name] = func end`. This keeps each concern isolated while presenting a single unified object to KOReader.\n\nThe cache layer uses KOReader's `CacheSQLite` (persistent, zstd-compressed) when available and falls back to an in-memory table otherwise. Invalidation is surgical: process events, folder changes, and conflict changes each invalidate only the relevant keys.\n\n\u003c/details\u003e\n\n---\n\n## Troubleshooting\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eCommon problems and solutions\u003c/b\u003e\u003c/summary\u003e\n\n### General advice\n\nWhen Syncthing doesn't behave as expected, the first step is always\nto check the logs: **Maintenance → View logs**.\nThe last 200 lines will often contain the exact error message.\n\n### Quick Sync says \"No active folders to scan\"\n\nThis means all configured folders are either paused or none exist yet.\n- If you just added a folder via the Web GUI, make sure it's not paused.\n- If you have no folders yet, ask another device to share one with you,\n  or add a folder manually via the Web GUI.\n\n### Quick Sync says \"Could not start Syncthing\"\n\nThis usually means Syncthing crashed during startup.\n- Check **Maintenance → View logs** for details.\n- Try restarting KOReader and trying again.\n- If the problem persists, try **Reset sync database** from the Maintenance menu.\n\n### Logs show \"disk I/O error: no such file or directory\"\n\nOn Kindle, the user storage (`/mnt/us`) is a FUSE mount that often deletes a file\nthe moment it is unlinked, even while a program still has it open. Syncthing 2.x\nuses an SQLite database, and SQLite relies on exactly that \"open, unlink, keep\nwriting\" pattern for its journal and temporary files — so on this storage every\nindex update fails with a disk I/O error and syncing never makes progress, while\nthe daemon itself keeps running.\n\nThe plugin handles this automatically: at startup it briefly tests whether the\nstorage behaves this way (it does not rely on the device model or on mount\nsettings, which don't reveal the problem), and if so it places the database on\nthe device's internal storage instead. Your books and synced files are not moved\nor affected. The first scan after relocation may take longer because the index is\nrebuilt once from disk; you'll see a brief one-time notice when this happens.\n\nYou can see where the database lives, and how many disk I/O errors (if any) were\nlogged, under **Maintenance → Diagnostic snapshot** (\"Database\" section). When the\ndatabase has been relocated, the **Status** menu also shows a short read-only row\nsaying so. Devices whose storage is not affected (e.g. most Kobo and PocketBook\nmodels) keep the database in the normal location with no change in behaviour.\n\n**Why you'll see Syncthing files in two places after relocation.** This is\nexpected, not a bug. Syncthing keeps two separate things: its *configuration*\n(the `config.xml`, the `cert.pem`/`key.pem` files that define your device ID, the\n`device-id` cache, and `syncthing.log`) and its *database* (the search index).\nOnly the database hits the storage problem above, so only the database is moved.\nAfter relocation:\n\n- `…/koreader/settings/syncthing/` (on `/mnt/us`) holds the configuration and\n  log — these write fine on this storage and stay where they always were.\n- `/var/local/kosyncthing_plus/` holds the database (an `index-v2` folder and a\n  `syncthing.lock` file) — this is the part that was moved.\n\nA healthy relocated setup has the config files in the first location with **no**\n`index-v2` there, and the `index-v2` folder in the second location with **no**\n`config.xml` there. Seeing both locations populated this way is the relocation\nworking correctly. (The plugin's own small cache, `syncthing_cache.db`, lives in\n`…/koreader/settings/` and is unrelated to either — it is not affected and is not\nmoved.)\n\n### Quick Sync says \"Low disk space\"\n\nSyncthing detected less than 100 MB free on one of your synced folders' filesystems.\n- Free up space by deleting unused files.\n- Or pause the folder that's on the full filesystem.\n\n### Quick Sync says \"Wi‑Fi disconnected\"\n\nWi‑Fi dropped during the sync. The plugin will not automatically retry.\n- Try Quick Sync again when Wi‑Fi is stable.\n\n### Quick Sync says \"Quick Sync skipped — Wi‑Fi unavailable\"\n\nThis appears when Wi‑Fi could not be turned on within 2 minutes\n(despite multiple retries). The plugin releases all resources\nand does **not** retry automatically. The daemon doesn't work in the background,\nno Wi‑Fi no meaningless background resource usage.\n\n- Tap **Quick Sync** again when you have a stable Wi‑Fi connection.\n- If you use Periodic Sync, it will automatically try again later.\n\n### Syncthing crashes immediately with \"netpoll failed\"\n\nIf the log shows:\n\n\n```\nruntime: epollwait on fd 4 failed with 38\nfatal error: runtime: netpoll failed\n```\n\n\nYour device's Linux kernel is too old for the current Syncthing binary.\nOpen **Setup → Legacy Syncthing → Set up Legacy mode…**.  The plugin detects\nthe right version for your kernel, downloads it, and offers to start Syncthing.\nSee [Legacy Syncthing support](#legacy-syncthing-support) for background.\n\n### Syncthing won't start\n\nIf Syncthing refuses to start:\n- Verify the binary is installed: **Maintenance → Check for updates**.\n- Check that your KOReader home directory is set (Settings → Home folder).\n- Check the logs under **Maintenance → View logs** for specific errors.\n\n\u003c/details\u003e\n\n---\n\n## Acknowledgements\n\nThis plugin would not exist without the work of those who came before:\n\n**[jasonchoimtt](https://github.com/jasonchoimtt)** and contributors to [koreader-syncthing](https://github.com/jasonchoimtt/koreader-syncthing)\n\n**[bps](https://github.com/bps)** and contributors to [syncthing.koplugin](https://github.com/bps/syncthing.koplugin)\n\n**[The Anarcat](https://anarc.at/hardware/tablet/kobo-clara-hd/#install-syncthing)** — excellent blog post.\n\n**The [Syncthing](https://syncthing.net/) project** — for building a private, encrypted, serverless sync engine that runs happily on 32-bit ARM hardware with 64 MB of RAM.\n\n**The [KOReader](https://koreader.rocks/) project** — for an open, extensible e-reader platform that makes plugins like this possible.\n\n---\n\n## License\n\nAGPL-3.0 — see [LICENSE](LICENSE)\n\nLicensed under the same terms as KOReader itself.\n\nCopyright © 2026 [d0nizam](https://github.com/d0nizam), and the upstream\n[koreader-syncthing](https://github.com/jasonchoimtt/koreader-syncthing) and\n[syncthing.koplugin](https://github.com/bps/syncthing.koplugin) contributors\nwhose work KOSyncthing+ builds upon (both also AGPL-3.0).\n\nThe Syncthing binary downloaded by this plugin is licensed under the\nMozilla Public License 2.0 (MPL-2.0). See https://syncthing.net/ for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd0nizam%2Fkosyncthing_plus.koplugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fd0nizam%2Fkosyncthing_plus.koplugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd0nizam%2Fkosyncthing_plus.koplugin/lists"}