試 Readest KOReader 同步插件:SSH 空密碼配方與統(tǒng)計推送排障實戰(zhàn))
桌面應(yīng)用跨平臺前端【免費下載鏈接】readestReadest is a modern, feature-rich ebook reader designed for avid readers offering seamless cross-platform access, powerful tools, and an intuitive interface to elevate your reading experience.項目地址https://gitcode.com/gh_mirrors/re/readest點擊查看免費下載本指南完整還原 Readest 項目在 Kindle VoyageKOReader v2026.07.2、固件 5.13.6上的一次真實運維實踐如何繞過本機 SSH key 認(rèn)證、以純命令行方式把readest.koplugin安裝包推送到設(shè)備并替換舊版本以及當(dāng)Failed to push reading statistics這類同步失敗發(fā)生時如何繞過無用的crash.log從插件狀態(tài)表、統(tǒng)計數(shù)據(jù)庫和網(wǎng)絡(luò)連通性三個層面逐層定位根因。讀完本文你將掌握一套可復(fù)用的 Kindle KOReader 插件無 GUI 部署 設(shè)備端排障完整流程并能直接對照倉庫源碼apps/readest.koplugin理解每一步背后的實現(xiàn)機制。背景這份配方解決什么問題本文內(nèi)容源自倉庫中的一條實戰(zhàn)記憶記錄 kindle-ssh-deploy-debug-recipe.md它記錄了 2026-08-14 的一次真實任務(wù)向 Kindle Voyage 部署Readest-0.12.1-1.koplugin.zip并排查一次閱讀統(tǒng)計推送失敗stats-push failure。其核心結(jié)論有三條也是本文的三條主線認(rèn)證與傳輸KOReader 的 SSH 插件dropbear監(jiān)聽 2222 端口登錄是空密碼、拒絕 Mac 本機id_rsa必須用SSH_ASKPASS非交互式完成登錄上傳文件不能用 scp/sftp要用標(biāo)準(zhǔn)輸入管道。設(shè)備端排障crash.log對同步類失敗基本無用真正的線索在 KOReader 的settings.reader.lua插件狀態(tài)、statistics.sqlite3統(tǒng)計積壓和一條 curl 網(wǎng)絡(luò)探測命令里。已知坑KOReader 2026.07 會對_meta.lua里的name字段發(fā)出棄用警告無害本地構(gòu)建 zip 時容易把源碼目錄里遺留的舊 zip 一起打進(jìn)去。一、無頭 SSH 認(rèn)證空密碼 dropbear 與 SSH_ASKPASS1.1 連接參數(shù)與環(huán)境確認(rèn)目標(biāo)設(shè)備與服務(wù)的連接參數(shù)參數(shù)值設(shè)備Kindle VoyageKOReader 版本v2026.07.2Kindle 固件5.13.6SSH 服務(wù)KOReader SSH 插件內(nèi)置的 dropbear主機 / 端口root192.168.2.180 -p 2222注意端口是2222而非標(biāo)準(zhǔn)的22這是 KOReader SSH 插件dropbear的默認(rèn)監(jiān)聽端口賬號是root。IP192.168.2.180是該設(shè)備在局域網(wǎng)內(nèi)的地址實際使用時應(yīng)替換為你自己設(shè)備的地址。1.2 為什么 Mac 的 id_rsa 會被拒絕記錄明確指出本機Mac的id_rsa被服務(wù)端拒絕REJECTED登錄方式是空密碼。這與 KOReader SSH 插件的默認(rèn)配置一致——它沒有配置公鑰授權(quán)只接受密碼認(rèn)證。因此在自動化和腳本化場景下必須顯式聲明使用密碼認(rèn)證而不是依賴 SSH 默認(rèn)的 key 協(xié)商順序。1.3 非交互空密碼認(rèn)證配方SSH 客戶端出于安全考慮默認(rèn)不會從標(biāo)準(zhǔn)輸入讀取密碼且在沒有 TTY 的 headless 環(huán)境中根本不會彈出密碼提示。解決方案是利用SSH_ASKPASS機制SSH 會調(diào)用該腳本把腳本的標(biāo)準(zhǔn)輸出當(dāng)作密碼。完整配方分兩步。首先寫一個 askpass 腳本內(nèi)容只有一個空回顯#!/bin/bash echo 然后執(zhí)行 SSH 連接關(guān)鍵環(huán)境變量與選項缺一不可DISPLAYnone \ SSH_ASKPASS腳本絕對路徑 \ SSH_ASKPASS_REQUIREforce \ ssh \ -o PreferredAuthenticationspassword \ -o NumberOfPasswordPrompts1 \ -p 2222 \ root192.168.2.180各參數(shù)含義DISPLAYnone讓 SSH 認(rèn)為沒有圖形環(huán)境從而轉(zhuǎn)入 askpass 路徑。SSH_ASKPASSscript指向上面那個輸出空行的腳本即密碼就是空字符串。SSH_ASKPASS_REQUIREforce強制啟用 askpass即使有可用的 TTY 或環(huán)境變量缺失也走該路徑較新版本 OpenSSH 的開關(guān)。-o PreferredAuthenticationspassword顯式只使用密碼認(rèn)證跳過 publickey 協(xié)商否則會先被 id_rsa 拒絕。-o NumberOfPasswordPrompts1只允許一次密碼嘗試避免腳本化場景下多次重試卡住。1.4 從源碼看插件側(cè)的認(rèn)證鏈路設(shè)備端插件保存的認(rèn)證字段與這條 SSH 配方的空密碼沒有直接關(guān)系但理解它們能幫你把連上了但同步失敗與根本沒法登錄區(qū)分開。在 main.lua 中插件默認(rèn)設(shè)置表ReadestSync.default_settings定義了與云端認(rèn)證相關(guān)的字段ReadestSync.default_settings { supabase_url https://readest.supabase.co, supabase_anon_key sha2.base64_to_bin(SUPABAE_ANON_KEY_BASE64), auto_sync false, user_email nil, user_name nil, user_id nil, access_token nil, refresh_token nil, expires_at nil, expires_in nil, last_sync_at nil, localsend_enabled false, localsend_alias nil, }這些設(shè)置通過G_reader_settings:readSetting(readest_sync, self.default_settings)持久化見 main.lua。也就是說SSH 只是運維通道插件與 Readest 云端之間的認(rèn)證走的是 Supabase JWTaccess_token/refresh_token/expires_at后續(xù)排障中我們要檢查的expires_at就來自這張表。二、插件包部署管道從構(gòu)建到替換一氣呵成2.1 構(gòu)建安裝包readest.koplugin的安裝包是標(biāo)準(zhǔn) zip文件名形如Readest-version-1.koplugin.zip。倉庫內(nèi)置了構(gòu)建腳本 build-koplugin.mjs用法node apps/readest.koplugin/scripts/build-koplugin.mjs --version 0.12.1腳本關(guān)鍵行為--version會把版本號注入_meta.lua形如version 0.12.1,構(gòu)建完成后自動還原除非傳--keep-meta見 build-koplugin.mjs未指定--version時使用dev-git-sha占位便于識別已安裝插件的來源打包時排除了scripts/、docs/、spec/、.busted、native/以及*.koplugin.zip通配見 build-koplugin.mjs依賴系統(tǒng)zip命令并會檢查bin/目錄是否存在 LocalSend helper 靜態(tài)二進(jìn)制否則給出警告LocalSend receive will be unavailable。2.2 通過管道上傳并校驗不用 scp/sftp設(shè)備端 dropbear 環(huán)境受限配方明確要求不要用 scp/sftp而是把 zip 內(nèi)容通過 SSH 標(biāo)準(zhǔn)輸入管道直接寫入目標(biāo)文件cat Readest-0.12.1-1.koplugin.zip | \ ssh askpass 參數(shù)同上 root192.168.2.180 \ cat /var/local/x.zip先放到/var/local/這個臨時位置而不是直接解壓到插件目錄是為了讓上傳與安裝兩個階段可分開校驗、可回滾。上傳完成后在兩端分別計算 md5 并比對確認(rèn)傳輸完整# 本機 md5 Readest-0.12.1-1.koplugin.zip # 設(shè)備端通過同樣的 ssh 通道執(zhí)行 ssh ... md5sum /var/local/x.zip2.3 替換舊插件并處理 LocalSend helper 進(jìn)程上傳校驗通過后按以下順序執(zhí)行安裝# 1. 停掉正在運行的 LocalSend 接收進(jìn)程 killall localsend-helper-armv7 # 2. 刪除舊插件目錄 rm -rf /mnt/us/koreader/plugins/readest.koplugin # 3. 解壓新包到 plugins/ 目錄 unzip -oq /var/local/x.zip -d /mnt/us/koreader/plugins/ # 4. 落盤 sync兩條容易踩坑的細(xì)節(jié)必須用killall絕不能pgrep -fpgrep -f的匹配串本身也可能匹配到執(zhí)行它的進(jìn)程自匹配導(dǎo)致誤殺或行為異常killall按精確進(jìn)程名匹配更安全。為什么一定要重啟 LocalSend helper插件升級時舊的 helper 靜態(tài)二進(jìn)制進(jìn)程可能仍在監(jiān)聽控制 socket。從 readest_localsend.lua 可以看到helper 是作為獨立后臺進(jìn)程運行的bin/localsend-helper-*其二進(jìn)制名由設(shè)備平臺決定Helper.binaryNameFor根據(jù)is_android、jit.os、jit.arch解析Kindle 這類 ARM 設(shè)備對應(yīng)的正是localsend-helper-armv7。不殺掉舊進(jìn)程新插件啟動后會與舊進(jìn)程的 socket 狀態(tài)不一致。最后重啟 KOReader 讓新插件被加載KOReader 退出重進(jìn)即可。安裝后建議立刻回到 2.2 的步驟確認(rèn)/mnt/us/koreader/plugins/readest.koplugin目錄結(jié)構(gòu)與版本號_meta.lua中的version正確。2.4 busybox unzip 的差異設(shè)備端是 Kindle 的 busybox 環(huán)境其unzip與 GNU 工具集有明顯差異配方記錄了三點沒有-ttest選項無法用unzip -t校驗壓縮包完整性所以必須走 2.2 的 md5sum 雙端比對-xexcludeglob 模式可用如果需要在解壓時排除某些文件-x 某模式是可行的使用-oq-o覆蓋已存在文件避免交互詢問-q靜默輸出。三、設(shè)備端同步與統(tǒng)計推送排障三板斧當(dāng)部署完成后出現(xiàn) Failed to push reading statistics 之類的問題時按下面的順序排查切忌一上來就懷疑插件本身。3.1 crash.log 為什么沒用logger.dbg 與 INFO 級別記錄明確指出crash.log對同步/統(tǒng)計類失敗是無效的USELESS。原因是插件相關(guān)日志readest_syncstats.lua 與 sync client大量使用logger.dbgdebug 級別而 KOReader 默認(rèn)日志級別是 INFOdbg級別的輸出根本不會落盤。Failed to push reading statistics 這條提示對應(yīng)的是 readest_syncstats.lua 中 pushChanges 回調(diào)successfalse的分支——此時body/status里往往也沒有更多細(xì)節(jié)屬于典型的靜默失敗。因此排障的第一步不是翻 crash.log而是跳到 3.23.4 的三條實證路徑。如果確實需要 debug 日志可以在 KOReader 中調(diào)高日志級別后重試推送但本配方給出了更高效的設(shè)備端探針。3.2 插件狀態(tài)表settings.reader.lua 里的 readest_sync插件的全部狀態(tài)保存在 KOReader 全局設(shè)置文件中/mnt/us/koreader/settings.reader.lua其中的[readest_sync]表對應(yīng) main.lua 的default_settings。排障時需要重點檢查的字段字段單位 / 語義檢查方法access_tokenJWT非空即已登錄為空說明未登錄推送自然失敗expires_atUnix 秒用date %s得到當(dāng)前時間戳若expires_at已過或接近當(dāng)前值token 過期stats_push_cursor秒push 游標(biāo)記錄已推送到哪條統(tǒng)計事件stats_pull_cursor毫秒pull 游標(biāo)記錄已拉取到哪個更新點auto_sync布爾是否開啟自動同步注意 push 與 pull 游標(biāo)單位不一致秒 vs 毫秒排查游標(biāo)推進(jìn)異常時不要被這個差異迷惑。關(guān)于過期判定readest_syncauth.lua 的needsLogin邏輯是expires_at os.time() 60——即剩余不足 60 秒就視為需要重新登錄而 tryRefreshToken 會在剩余時間低于 TTL 一半時用refresh_token靜默刷新。如果expires_at明顯過期而refresh_token又失效就會表現(xiàn)為按鈕點了沒反應(yīng)或持續(xù)推送失敗。3.3 統(tǒng)計積壓分析把 statistics.sqlite3 拉到本地查KOReader 的閱讀統(tǒng)計庫是設(shè)備上的 SQLite 文件/mnt/us/koreader/settings/statistics.sqlite3Failed to push reading statistics 的常見原因之一是游標(biāo)停滯、積壓過大——比如一次全新 pull 把多設(shè)備歷史全部拉到本地后push 游標(biāo)仍停在很舊的位置。排障手法是把這個數(shù)據(jù)庫文件拷到本地用 SQL 直接看積壓量。設(shè)備端先拷貝ssh ... cp /mnt/us/koreader/settings/statistics.sqlite3 /var/local/ ssh ... cat /var/local/statistics.sqlite3 statistics.sqlite3然后本地執(zhí)行與插件 collectSince 完全一致的 SQL統(tǒng)計游標(biāo)之后還有多少條未推送事件SELECT b.md5, b.title, b.authors, p.page, p.start_time, p.duration, p.total_pages FROM page_stat_data p JOIN book b ON b.id p.id_book WHERE p.start_time stats_push_cursor ORDER BY p.start_time ASC;把stats_push_cursor換成 3.2 中讀到的stats_push_cursor值。如果返回行數(shù)很大說明積壓確實存在。這背后還有一層實現(xiàn)細(xì)節(jié)值得知道推送是分塊進(jìn)行的readest_syncstats.lua 定義PUSH_CHUNK 500每次請求最多 500 條 page 事件拉取側(cè)PULL_PAGE 1000見 readest_syncstats.lua這是為了適配 sync client 的 10 秒總超時。若單次推送超過 chunk 大小插件會通過UIManager:nextTick串行續(xù)推見 readest_syncstats.lua但前提是每一塊都要在超時內(nèi)成功——所以一次 push 卡死、游標(biāo)永遠(yuǎn)不推進(jìn)正是要優(yōu)先懷疑大積壓的理由。3.4 網(wǎng)絡(luò)探測先于懷疑插件的一步配方特別強調(diào)Check this FIRST before suspecting the plugin.在動插件之前先從設(shè)備端發(fā)一條網(wǎng)絡(luò)探針curl -sS -o /dev/null -w %{http_code} https://web.readest.com/api/sync預(yù)期結(jié)果是401未認(rèn)證——這恰恰說明網(wǎng)絡(luò)鏈路通暢、服務(wù)器可達(dá)401 表示到達(dá)了應(yīng)用層并被鑒權(quán)攔截而不是連接失敗。2026-08-14 的真實案例正是如此當(dāng)時 DNS 能解析但 curl 報curl: (7) Couldnt connect說明 Kindle 的 WiFi 已經(jīng)丟失了 WAN 出口而 LAN/SSH 仍然可用——局域網(wǎng)內(nèi) SSH 正常不代表設(shè)備能出公網(wǎng)。用戶重新連接 WiFi 后push 立即成功。這類設(shè)備看著在線、同步卻失敗的假象只有靠這條探測命令才能第一時間戳穿。3.5 把排障鏈條接到源碼push 與 pull 的游標(biāo)推進(jìn)邏輯最后用源碼把整個鏈路串起來。推送入口 SyncStats:push 的邏輯是讀settings.stats_push_cursor秒調(diào)用collectSince(cursor)收集start_time cursor的書籍與 page 事件按PUSH_CHUNK分塊通過client:pushChanges({ books {}, notes {}, configs {}, statBooks ..., statPages ... })發(fā)送回調(diào)successtrue時把stats_push_cursor推進(jìn)到本塊最后一條事件的start_time并保存readest_syncstats.luasuccessfalse時游標(biāo)保持不變、重試會從原地繼續(xù)readest_syncstats.lua。對照這張圖Failed to push reading statistics 且游標(biāo)不動的診斷順序就清晰了先做 3.4 的網(wǎng)絡(luò)探測排除 WAN 丟失再核對 3.2 的access_token/expires_at排除鑒權(quán)失效最后用 3.3 的 SQL 確認(rèn)是否存在大積壓。三者都正常而問題依舊才輪到檢查插件版本與日志級別。四、已知注意事項與易踩的坑4.1_meta.lua的棄用警告無害但要知道KOReader 2026.07 在加載插件時會輸出PluginLoader: readest name in _meta.lua, is deprecated and will be ignored這是無害警告插件照常加載。原因可以從 _meta.lua 的源碼看出來——該文件目前只返回name、fullname、description三字段而新版 KOReader 已棄用_meta.lua里的name字段插件標(biāo)識以目錄名為準(zhǔn)。插件自身在 main.lua 中讀取_meta.lua只是為了拿到version字段用于檢查更新菜單不受該警告影響。4.2 構(gòu)建殘留 zip 會被吞進(jìn)新包從工作目錄插件源碼目錄本地構(gòu)建發(fā)布 zip 時如果源碼目錄里殘留著歷史構(gòu)建產(chǎn)物如Readest-dev-sha-1.koplugin.zip它們會被卷進(jìn)下一次打包導(dǎo)致包體膨脹甚至嵌套。構(gòu)建腳本 build-koplugin.mjs 已經(jīng)用-x ${PLUGIN_NAME}/*.koplugin.zip做了排除注釋明確寫著這是為了避免megabytes of stale nested copies。但配方仍建議雙保險安裝到設(shè)備時排除這類文件同時定期清理源碼目錄——因為腳本排除只在構(gòu)建側(cè)生效歷史遺留產(chǎn)物仍可能通過其他打包路徑混入。五、源碼索引與延伸閱讀本次排障涉及的核心文件均可在倉庫中繼續(xù)深挖主題文件插件入口、默認(rèn)設(shè)置、菜單與同步編排main.lua統(tǒng)計推送/拉取、游標(biāo)與分塊策略readest_syncstats.lua閱讀進(jìn)度/元數(shù)據(jù)指紋同步readest_syncconfig.luaSupabase 登錄、token 刷新與 Spore 客戶端readest_syncauth.luaLocalSend 接收服務(wù)與 helper 進(jìn)程管理readest_localsend.lua插件元信息_meta.lua的 name/fullname/description_meta.lua本地打包腳本版本注入與排除規(guī)則build-koplugin.mjs插件整體設(shè)計文檔圖書館視圖、schema、同步流程library-design.md同步 API 契約Spore 規(guī)格readest-sync-api.json需要說明的是本指南中的設(shè)備 IP、版本號與日期來自記錄本身屬于特定時刻的實測環(huán)境root192.168.2.180:2222是當(dāng)時的局域網(wǎng)地址實際部署時請?zhí)鎿Q為你自己的設(shè)備地址并確認(rèn) KOReader SSH 插件已啟用、設(shè)備與電腦處于同一局域網(wǎng)。掌握這套空密碼 SSH 管道 狀態(tài)表核查 數(shù)據(jù)庫積壓分析 網(wǎng)絡(luò)探針的組合拳后Kindle 上的 KOReader 插件部署與同步排障就不再依賴圖形界面或反復(fù)重啟實驗了。贊分享桌面應(yīng)用跨平臺前端【免費下載鏈接】readestReadest is a modern, feature-rich ebook reader designed for avid readers offering seamless cross-platform access, powerful tools, and an intuitive interface to elevate your reading experience.項目地址https://gitcode.com/gh_mirrors/re/readest點擊查看免費下載相關(guān)推薦ToolJet GitSync SSH 配置實戰(zhàn)在 GitHub、GitLab 與 Gitea 上部署 ToolJet 生成的 SSH 密鑰ToolJet GitSync SSH 配置實戰(zhàn)在 GitHub、GitLab 與 Gitea 上部署 ToolJet 生成的 SSH 密鑰 本文圍繞 Too低代碼后端前端AI 應(yīng)用MCP 服務(wù)Algorithm-Implementations 數(shù)學(xué)算法寶典階乘、斐波那契、歐幾里得算法詳解Algorithm Implementations 數(shù)學(xué)算法寶典階乘、斐波那契、歐幾里得算法詳解 Algorithm Implementations 是一個專kubeasz 實戰(zhàn)基于 KubeBlocks 在 Kubernetes 上部署 MySQL 半同步主備集群與故障演練kubeasz 實戰(zhàn)基于 KubeBlocks 在 Kubernetes 上部署 MySQL 半同步主備集群與故障演練 本文以 kubeasz 倉庫中 rol云原生集群管理運維上一篇RuboCop 1.16.1 補丁版解析五個關(guān)鍵 Bug 修復(fù)的源碼級原理與配置實戰(zhàn)下一篇如何在Tuist中集成Unity Shader Graph創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考