QNAP Nas 架設 Calibre-Web 自建個人書城簡單但是很複雜的過程

因為有點年紀了,所以現在有一些閱讀的方式會盡量避免手機或平板而改採 E-INK 閱讀器比較不傷眼,我手上有一台 Hyread Gaze Pro XC 的閱讀器,看漫畫或圖書館借閱基本上都用這台,由於 Hyread 的書,不會提供匯出,所以不能編輯書檔,因此有一大票閱讀器的用戶反而可能會去找可提供書檔的平台,例如 Kobo,下載來的書檔,可以用一些電子書管理軟體來進行修改與儲存,我個人都是用 Calibre 這套軟體。

但自己管理的書籍達到一定的量的時候,不可能把全部的書放到有限空間的閱讀器,就連自己電腦的 SSD 容量可能也不足以存放所有的書,我現在算是老宅男動漫迷,漫畫也有很多本,一套漫畫隨便就 10 來本,10 套就要上百本,需要的儲存空間其實很大。

所以有些人可能會另外搭配 One Drive 這類雲端空間可以同步或用 NAS 儲存,因為家裡有一台 QNAP NAS,所以就不浪費錢了,我個人是在 Windows 上以 Calibre 編輯書庫,並且採用 QNAP Qsync 來同步至 NAS,QSync 類似 One Drive,若當 Windows 空間不足時,會自動釋放空間,但檔案總管打開時檔案會都在,需要用時,會自動從 NAS 同步回來,所以就不用擔心 SSD 不夠用,這就幾乎跟 One Drive 一樣。

而玩了一陣子後,發現閱讀器要閱讀書是有點麻煩的,因為要將書籍傳到閱讀器,雖不難但步驟可能比較繁瑣,於是想架起 OPDS 的服務,OPDS 是一個電子書出版 Feed 標準,有很多閱讀軟體或看漫畫軟體支援 OPDS , 只要連接上,就有點像在看書城的感覺,可以進行分類瀏覽/下載與搜尋,這樣在閱讀的體驗上會簡化許多。

至於要如何架 OPDS ,我個人試過以下幾種

  • Kavita : 這是我覺得提供的 WEB UI 最漂亮的,支援多書庫的管理,試完後卻發現一個大問題,如果是掃描自 Calibre 的書庫,若一本書籍同時含有兩種格式,例如同時有 cbz 及 epub,則會被視為兩本書,所以我就沒有用,不然這款真的超棒。
  • Calibre Content Server : 這是 Calibre 內帶的,不管是 Winodws 或 Linux 版,都會內含 Server,也提供簡單 WEB UI , 但真的很簡單,由於我是要在 QNAP NAS 下架設純 Server,但linuxserver.io 包的 docker 版本是內含 UI 的(要用 VNC),因為這樣記憶體吃比較大,NAS 安裝的 RAM 本就少得可憐,我也就先不用,雖然我自己有包一個不含 UI 的,但 Calibre 更新版本還挺頻繁的,我就嫌麻煩,也不用了。
  • Calibre Web : 這是另一個專案,專門支援 Calibre 書庫資料庫提供 Web 介面,有個小缺點,就是不支援 Calibre 的多書庫功能,但可以透過跑多個 Container 來分別讀取 Calibre 多書庫不同的目錄,等於架設兩套的時候,管理使用者也就要分兩套,但由於我要自用,這種小缺點我能接受,就先採用。
  • COPS : 以 PHP 撰寫的小型 OPDS 服務,WEB 功能比 Calibre Web 簡單,但由於我試過有點不太穩定,所以暫時不用觀察後續。

上述四種我最後選擇了 Calibre Web,不過 Calibre Web 有個致命的缺點,就是當你成功以 Docker 架設好後,若更新書籍,由 PC Sync 到 NAS , 會發現網站沒有更新資料,要手動進管理介面點選 Reconnect Database 才會更新,這個小缺點其實有另外的人包成 Calibre Web automated 提供更多進階功能,包含了自動更新資料庫,不過我沒打算用 Calibre Web automated,因為我想要追蹤原版的 Calibre Web,所以我等下會介紹我的方法。

我把目前我處理電子書的流程用架構圖畫出如下 :

流程

這張圖的架構應該不難懂,原本於 Windows 操作 Calibre 的習慣不用變更,編輯書籍的方式完全沒變,透過 QSync 可以與 NAS 進行雙向同步,如果那天,電腦掛了,也可以重新安裝 QSync 把檔案同步回來,而 Calibre Web 則透過 Docker 來提供服務。由於前面有提到,Calibre Web 不能自動偵測檔案異動,所以我想到用 inotifywait 這個指令來監測,而 Calibre Web 提供一個 API GET /reconnect,透過這個 API ,則可以讓 Calibre Web 重新連接資料庫就能更新網站內容。

我這邊提供一段適用於 QNAP Nas 搭配 Container Station 管理介面適用的 Docker compose YML

services:
  calibre-web-comic:
    image: lscr.io/linuxserver/calibre-web:latest
    container_name: calibre-web-comic
    restart: unless-stopped
    environment:
      - PUID=500
      - PGID=500
      - TZ=Asia/Taipei
      - CALIBRE_RECONNECT=1
      # - DOCKER_MODS=linuxserver/mods:universal-calibre #optional
      # - OAUTHLIB_RELAX_TOKEN_SCOPE=1 #optional
    volumes:
      - config-comic:/config
      - "/share/homes/pigo/.Qsync/calibre 書庫/漫畫:/books:ro"
    ports:
      - 8083:8083

  calibre-web-comic-upscale:
    image: lscr.io/linuxserver/calibre-web:latest
    container_name: calibre-web-comic-upscale
    restart: unless-stopped
    environment:
      - PUID=500
      - PGID=500
      - TZ=Asia/Taipei
      - CALIBRE_RECONNECT=1
      # - DOCKER_MODS=linuxserver/mods:universal-calibre #optional
      # - OAUTHLIB_RELAX_TOKEN_SCOPE=1 #optional
    volumes:
      - config-comic-upscale:/config
      - "/share/homes/pigo/.Qsync/calibre 書庫/漫畫-升頻版:/books:ro"
    ports:
      - 8084:8083

  intofy:
    container_name: calibre-monitor
    image: inotify-curl-qnap:0.0.1
    build:
      context: ./
      dockerfile_inline: |
            FROM alpine
            RUN apk --no-cache add inotify-tools curl bash
    restart: unless-stopped
    user: "500:500"
    depends_on:
      - calibre-web-comic
      - calibre-web-comic-upscale

    entrypoint: >
      bash -c '
        declare -A file_to_api=(
            ["/books/漫畫/metadata.db"]="http://calibre-web-comic:8083/reconnect"
            ["/books/漫畫-升頻版/metadata.db"]="http://calibre-web-comic-upscale:8084/reconnect"
        )

        files=("$${!file_to_api[@]}")

        while true; do
          inotifywait -e attrib "$${files[@]}" |
          while read -r file event; do
            now=$$(date "+%Y-%m-%d %H:%M:%S")
            echo "Detect $$file changed at $$now"
            echo "Call API: $${file_to_api[$$file]}"
            curl "$${file_to_api[$$file]}"
            sleep 1
          done
        done
      '
    volumes:
      - "/share/homes/pigo/.Qsync/calibre 書庫:/books:ro"

volumes:
  config-comic:
  config-comic-upscale:

在 Yaml 範例中,我將漫畫分成兩個書庫來管理,一個是普通版本,一個是升頻版本,升頻版本是我利用 https://upscayl.org/ 的 AI 影像放大模型進行解析度提升以讓閱讀時更清晰,我很建議這套,不同於傳統圖片放大的演算法,用這套放大後的文字完全不會模糊。

由於書庫分兩套管理,因此前兩個 service Calibre Web 的部分也啟用了兩個 Container , 分別對應 Port 8083 與 Port 8084,大部分閱讀軟體只要支援 OPDS 的,應該都能加入數個 OPDS Server , 就可以當成兩個書城來看待。

第三個 service 則是跑 inotifywait 去監控書庫的 metadata.db 是否異動,而我的用法比較奇怪,因為 QNAP Container station 的介面不提供 Dockerfile 上傳,我就用了行內方式,去建立一個以 alpine OS 為基礎的版本,把 CURL 與 inotify-tools 安裝進去。而這邊也提一下,我監控的參數是 -e attrib 而非 modified,因為我實測後發現,同步檔案時,Qsync 會先刪除原本的檔案,而非改寫原本的檔案,所以不能把檔案視為更新,要視為刪除重建,因此我的寫法是只要有異動就結束,再重新監控,如果使用 -m 的參數,則監控完畢後,那個原本的檔案不知道會被回收到那邊去了,不會再有任何事件。

再來特別提醒一下,因為每個人環境不同,所以有很多地方要注意

  1. QSync 資料存那 ? 由於我是透過 QNAP NAS 上我自己的帳號 pigo 來進行同步的,所以 QSync 會將資料放在家目錄中的 .qsync,各位可以看範例中 Volume 的部分,可以知道 QNAP 家目錄的絕對位置。
  2. inotify-wait 只能監控本機的檔案系統,我試過在 WSL2 上掛載 /mnt/c 的方式會失效,在 QNAP NAS 上我的方式是沒問題的。
  3. inotify-wait 那邊的 entrypoint 有定義陣列 declare -A file_to_api 那對,是監控的檔案對應的 API , 那邊必須進行修改符合自己的目錄,同時各 container 的 volume 也都要寫對。
  4. Calibre-Web 有個環境變數 CALIBRE_RECONNECT,這個是關鍵,必須設定為 1 才能開啟 /reconnect 這個 API。
  5. inotifywait 那邊由於程式碼比較長,必須要注意修改時,$ 的變數符號必須以 $$ 來 escape 才能驗證通過。
  6. 最重要一點必須特別注意,calibweb 的環境變數的 PUID/PGID ,還有 inotify 的 user,都必須符合同步的帳號,不然 volume 掛載後沒權限,Calibre Web 就找不到書庫,inotifywait 也監控不到檔案,那查詢同步帳號的 UID/GID 可以在 NAS 管理介面的用戶管裡那邊有,或者自己進 SSH 看看 /etc/passwd

上述的設定若跑起來,接下來就是使用部分就不多說,基本上要先進去 Calibre-Web 設定一下書庫與用戶才能開始用 opds,以我的範例來說Calibre-Web 提供 OPDS 的方式如下

http://IP:8083/opdshttp://IP:8084/opds,只要閱讀器加進去,通過帳密認證就行了。

由於是自架,最好別對外開放,盡量在家中使用,要看的書下載下來,到外面也能看,如果真的要外面用,固定 IP或 DDNS 開放 port 最好搭配 VPN 使用,NAS 被攻擊的機會太多了。

這裡也提供幾個我試過可以加入 OPDS 的閱讀軟體

  1. Android : 靜讀天下(Moon+ Reader),FBReader
  2. ios : 可達閱讀器,KyBook
  3. Android/iOS 都有的 Calibre Sync : 這並非閱讀器而且要付費買,除了可連接 OPDS,也支援雲端硬碟,主要功能就是同步元數據,例如書名/作者/封面等等,將主要資料同步到閱讀器搭配其UI就是一個書城了,速度極快,也能進行搜尋與下載書本完整資料,之後就可以用閱讀器內建閱讀軟體來看書,如果不想像我一樣自架 OPDS , 那就乖乖付錢買 One Drive ,搭配這套也不錯。我個人是使用這套搭配閱讀器內建的閱讀軟體。
  4. 如果閱讀軟體都不喜歡,其實就直接用瀏覽器開 Calibre-Web 直接找到書本後下載到閱讀器,用自己的閱讀器去看書就好了。

發佈留言