Zeka 的记事本

2021 年 7 月
使用 Docker 部署 anki-sync-server
2021730 日说: Docker Nginx Linux

本来 Docker 部署应该是一件很舒服的事情,并没有什么好说的。但是我遇到了无数迷惑的问题。

总结为:除非脑子不好,否则不要自己用 docker 部署这玩意。

如果你的安装环境有 python3.8,那么推荐直接在 virtualenv 安装。

这版本高的有点迷了,但是我不确定是否真的用上了 3.8 的特性,所以不好说什么。但是显然直接在大部分 LTS 上直接安装的期望破灭了。

如果你使用的是 LTS 的自带 python,而且不打算更新(比如我 Debian10 的 3.7),那么你可能打算使用 Docker 安装。

坑一,arm 无法使用

在 arm 环境下,anki 似乎落后了几个版本,这导致很难安装。此外,官方暂时没有考虑提供 arm 容器,如果自己修改难度可能比较高。对于我们这种想快速使用的用户,就不打这个主意了。

无敌的树莓派受挫

坑二,官方 docker-compose.yml 过时无法使用

当你打开 官方 docker 安装指南,并找到官方写好的 compose 文件时,你可能认为已经结束了。

但是,如果你在 dockerhub 上搜索它 使用的镜像,你会发现它的最后更新时间为一年前。这直接导致它与新版本的 AnkiDroid 不兼容。如果你不打算在安卓手机上使用,那么你可以忽略这个过问题。

因此,自己构建镜像成为不得不做的事情。

坑三,在强国构建镜像

为了部署速度,我选择在国内部署 anki。但是 anki 部署需要从 github 上拉取仓库,同时还需要使用 pip 安装环境。

可选方法有:

  1. 从可以正常访问的服务器构建,并保存镜像上传到部署服务器
  2. 像我一样忘记了方法二,修改部署文件构建。

自己构建,官方有一个专门的 构建仓库应用仓库.

构建时,拉取构建仓库(devops)并执行 scripts 目录下的 build-devops.sh。这个脚本会自动 clone 应用仓库(sync-server),并执行安装指令。我们需要修改构建仓库的 Dockerfile 来防止网络问题 (路径位于 anki-devops-services/services/anki-sync-server/images/Dockerfile

修改 Dockerfile 如下:

# -- BUILDER --
FROM library/python:3.9-buster as builder

ARG ANKISYNCD_ROOT=/opt/ankisyncd
RUN mkdir -p ${ANKISYNCD_ROOT}
WORKDIR ${ANKISYNCD_ROOT}

COPY bin/download-release.sh ./bin/download-release.sh
COPY bin/pip.conf /root/.pip/pip.conf
COPY bin/src /root/src

ARG PYTHONUSERBASE=/opt/venv
RUN sh ./bin/download-release.sh && \
        pip3 install --upgrade pip && \
    pip3 install --user -r ./release/requirements.txt

# -- DEPLOYER --
FROM python:3.9-slim-buster

# Copy Python dependencies
ARG PYTHONUSERBASE=/opt/venv
ENV PYTHONUSERBASE=${PYTHONUSERBASE}
COPY --from=builder ${PYTHONUSERBASE} ${PYTHONUSERBASE}

# Copy Anki Sync Server release and scripts
ARG ANKISYNCD_ROOT=/opt/ankisyncd
COPY --from=builder ${ANKISYNCD_ROOT}/release ${ANKISYNCD_ROOT}
WORKDIR ${ANKISYNCD_ROOT}

# Create data volume.
ARG ANKISYNCD_DATA_ROOT=/srv/ankisyncd
RUN mkdir -p ${ANKISYNCD_DATA_ROOT}
VOLUME ${ANKISYNCD_DATA_ROOT}

# Set default environment variables.
ARG ANKISYNCD_PORT=27701
ARG ANKISYNCD_BASE_URL=/sync/
ARG ANKISYNCD_BASE_MEDIA_URL=/msync/
ARG ANKISYNCD_AUTH_DB_PATH=./auth.db
ARG ANKISYNCD_SESSION_DB_PATH=./session.db

ENV ANKISYNCD_HOST=0.0.0.0 \
        ANKISYNCD_PORT=${ANKISYNCD_PORT} \
        ANKISYNCD_DATA_ROOT=${ANKISYNCD_DATA_ROOT} \
        ANKISYNCD_BASE_URL=${ANKISYNCD_BASE_URL} \
        ANKISYNCD_BASE_MEDIA_URL=${ANKISYNCD_BASE_MEDIA_URL} \
        ANKISYNCD_AUTH_DB_PATH=${ANKISYNCD_AUTH_DB_PATH} \
        ANKISYNCD_SESSION_DB_PATH=${ANKISYNCD_SESSION_DB_PATH}

COPY bin/entrypoint.sh ./bin/entrypoint.sh

EXPOSE ${ANKISYNCD_PORT}

# TODO: Change to ENTRYPOINT. Currently CMD to allow shell access if needed.
CMD ["/bin/sh", "./bin/entrypoint.sh"]

HEALTHCHECK --interval=60s --timeout=3s CMD wget -q -O - http://127.0.0.1:27701/ || exit 1

同时提前准备好 anki-sync-serversrc 文件夹。在 bin 目录提前放置,并放置一个 pip.conf 文件:

[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple

构建完成后,修改镜像为自己之前构建的即可。

坑四,修改 Nginx 配置

要在 Ankidroid 上正常使用,必须 “正确” 配置 Nginx。 Ankidroid 似乎对 http2 支持有一定问题,同步媒体文件时会跳出 okhttp http2 NO_ERROR 错误。但是 Ankidroid 又强制要求 ssl 证书,所以 Nginx 是不可避免的。

要正确关闭 Nginx http2 有一个小问题。一个端口不能同时使用 http1.1 与 http2.所以如果你打算在 80 443 端口开启 http2(其他服务) ,并同时使用 anki-sync-server 的话,你的唯一选择是更换端口(比如 444)。

推测是 http2 与 websocket 不兼容导致,还需要之后测试。

坑五,文件持久化

自己构建的镜像无法实现文件持久化,因为文件目录已经发生了改变。所以 README 中提到的 docker-compose.yml 挂载是有问题的。

一个很大的问题是,虽然容器内有 ankisyncd.conf 文件,但是不论如何修改这个文件都无法使得目录更改起效。

简单翻阅了下源码没有找到问题,所以唯一方法是手动一个个的挂载。

最少要挂载的文件有两个,一个是位于 /opt/ankisyncd/ 目录下的 auth.db 文件,其中保存了用户的用户名与密码信息。

另一个是位于 /srv/ankisyncd 文件下的媒体文件。

一个问题在于 auth.db 最开始无法创建,所以可行的办法是首先挂载目录,等进入容器添加用户后再移动道挂载目录中(也就是从容器中拿到宿主机环境中),然后再修改 docker-compose.yml 文件启动。

/opt/ankisyncd/ 下还有一个 session.db 文件,用于记录登录凭证,如果有必要也可以照前面拿出 auth.db 步骤拿出来。但是这个是用于登录 session 会话,不挂载不影响使用。唯一影响是重启容器所有客户端需要重新登录。

坑六,修复未合并小 BUG

在写完这篇踩坑的时候,Anki 桌面版可能出现 NoneType object is not subscriptable 的问题,由于 未合并,所以只能手动修复。

好在是 python,所以直接修改源码并挂载到容器里面即可。

文件在容器内的 /opt/ankisyncd/ankisyncd/sync_app.py,我们 加上 commit 内容,然后在容器内挂载这个文件即可。

i3 桌面簡單使用體驗
202171 日说: Linux KDE

由於已經安裝了 KDE,而且最近沒用切換桌面系統的打算,所以採用折中的方法,將 KDE 自帶的 kwm 替換爲 i3wm。相比原生的自然有一定問題,但是整體差別不大。

配置可以直接看 這篇 完成。需要注意最新的 KDE 桌面界面不一樣,需要按照 Note 才能正常設置。

在教程裏面提到的這些參數對遷移很有幫助:

# kill KDE's desktop, so that no wierd `half desktop` show up
for_window [title="Desktop — Plasma"] kill; floating enable; border none
for_window [class="plasmashell"] floating enable;
for_window [class="Plasma"] floating enable; border none
for_window [title="plasma-desktop"] floating enable; border none

# config some other application
for_window [title="win7"] floating enable; border none
for_window [class="krunner"] floating enable; border none
for_window [class="Kmix"] floating enable; border none
for_window [class="Klipper"] floating enable; border none
for_window [class="Plasmoidviewer"] floating enable; border none
for_window [class="(?i)*nextcloud*"] floating disable

# change notification layout, you may need to change `move pixel` to proper value
for_window [class="plasmashell" window_type="notification"] floating enable, border none, move right 700px, move down 450px
no_focus [class="plasmashell" window_type="notification"] 

重啓後就能直接進入 i3 環境了。

以下僅僅是個人使用習慣,僅供參考。

不管怎樣,過一邊 User Guide。初期不記得可以查看 Ref Card

界面與操作

由於顯示器分辨率比較高而顯卡只有核顯,所以直接使用發現存在畫面撕裂情況,添加 picom有所緩解。雖然感覺有點違背初衷,因爲這樣就不是“完全平鋪”了 但是我們要取長補短

在 i3 配置文件加入:

exec_always --no-startup-id killall picom; sleep 0.2; picom -b --no-f    ading-openclose

使用 feh 替代原有桌面,這個只是展示一張圖片而已了, 不再具有桌面功能。反正之前 KDE 就沒怎麼使用桌面,所以丟掉也不算可惜。

在 i3 中加入:

exec --no-startup-id feh --bg-scale "/path/to/pic"

使用 rofi 替代 dmenudmenu 默認顯示在最頂上有點反人類,而且無法搜索到 Steam 安裝的遊戲。rofi 默認顯示在中間,而且有很多可選皮膚,並且可以搜到 Steam 遊戲,免掉了添加路徑的麻煩,更加開箱即用。

在 i3 中加入:

# change dpi for your own screen
bindsym $mod+d exec --no-startup-id rofi -dpi 240 -show drun

丟棄 yakuake。因爲 i3 默認提供了很好的命令行呼出功能。如果你還是懷念帥氣的下拉 panel,你可以使用 scrachpad 實現一個(按 $mod+u 呼出):

默認使用 konsole,你也可以根據你的喜好修改爲 i3 用戶喜歡的 rxvt。尺寸與快捷鍵也許要修改。

# config dropdown-like terminal
for_window [instance="dropdown"] floating enable
for_window [instance="dropdown"] resize set 2800 1600
for_window [instance="dropdown"] move scratchpad
for_window [instance="dropdown"] border pixel 5
exec --no-startup-id konsole --name dropdown --hide-tabbar -e tmux
bindsym $mod+u [instance="dropdown"] scratchpad show; [instance="dropdown"] move position center

對 VIM 用戶來說基礎操作正好偏移了一個鍵位,按照需要修改。

同時添加打開應用快捷鍵。

# open app in current workspace
bindsym $mod+Ctrl+k exec --no-startup-id /path/to/your/app/or/name
# jump to workspace 1, and open app
bindsym $mod+Ctrl+j exec --no-startup-id workspace $ws1; path/to/app

i3status 按照個人想法設置即可。

比較流暢的使用

每個人理念不同,i3 沒有所謂最佳實踐的說法,更沒有所謂完美配置的說法。

i3 最大的特點就是窗口會自動平鋪。可惜這樣會導致窗口頻繁縮放,讓我眼花不說,對我那可憐的核顯也是不小的衝擊。好在 i3 有三種模式,默認的是 split,我們可選 tabbed 與 stack。stack 會導致橫向空間快速被佔據,所以我一般不使用。

默認打開窗口爲 tabbed,這樣使用後你會發現你的“屏幕大了很多”,因爲相當於任意應用總是佔滿全部空間:

workspace_layout tabbed

一個按鍵在 tabbed 與 split 間切換(toggle):

bindsym $mod+e layout toggle tabbed split

對小顯示器(筆記本內屏)來說,空間十分寶貴。split 一般只適用與需要對照的情況,比如對照示例一行行敲代碼。

之後再考慮 i3 中重要的 workspace 概念。這個概念不論是 win 還是 KDE 都有,但是我很少看到有使用的(包括我自己)。但是在 i3 中這成爲了一條核心使用定律。

操作 $mod+num 可以進入不同工作空間(workspace),由於有 10 個數字,所以最簡單獲取的工作區間就是 10 個。其中 1-4,7-0 距離手指近,是比較好利用的工作區間,應該優先分配。

一個有用的小技巧:將工作區間命名爲 num:Job 的格式,可以同時了結編號與分類。比如我將第二個工作區間命名爲 2:Browser,表明這裏存放瀏覽器窗口。這樣在 i3status 中可以同時瞭解到編號與作用。

我的簡單分配爲:

  1. Shell,系統性維護。比如更新,修改系統參數等等。任何時候我想打開一個 shell 優先想到這個 workspace。
  2. Browser。將瀏覽器開在這裏。
  3. Code。自然就是工作區間了,寫代碼,文字等等工作都能丟在這裏。
  4. SSH。遠程訪問窗口都丟在這裏,通過 i3 的 split 功能能很方便的管理多個 SSH。
  5. 5 閒置,作爲機動窗口。
  6. 6 閒置,作爲機動窗口。
  7. 7 閒置,作爲機動窗口。
  8. Media。媒體文件,比如想看視頻等等。
  9. Music。音樂。由於比較常用,我專門分配了一個工作區間
  10. IM。將聊天應用放在這裏,比如 Telegram 等。

分配完畢其實還不能寫代碼。對我來說最頭疼的是寫網頁。我至少需要:

  • 一個窗口放置 IDE
  • 一個窗口放置命令行,用來執行 node 相關指令或者一些相關指令
  • 一個窗口放置調試瀏覽器,測試頁面
  • 一個窗口放置 Dev Tools,用作頁面測試
  • 一個瀏覽器窗口查詢資料或者文檔

經過一些簡單使用經驗,得出了一個比較合理的設計。

首先注意到 2 個東西是最好管理的。

兩個 workspace 我們可以添加很方便的快捷鍵完成切換,這恰好就是 KDE 與 win 中的 switcher:

bindsym $mod+Tab workspace back_and_forth

這樣我們就能很方便的在兩個 workspace 中切換。

兩個 tab 也最好管理,tabbed 模式下任意切換 tab 都會切換到另一個,同時 split 模式左右對照非常舒服。

因此安排爲:

IDE 與命令行組成 tab,組成一個 workspace

查資料瀏覽器單獨一個 workspace。因爲很可能打開太多瀏覽器 tab,所以本身已經很複雜了,需要減少複雜度。

調試瀏覽器與 DevTools 放在一個 workspace,split 與 tabbed 靈活切換。

這樣就沒問題了。

小結

有了 workspace 相當於多了一個維度,所以很多事情感覺比之前更有邏輯,因爲分類可以更細節化,同時使用也更靈活。

一個直觀的感受是內存佔用確實有所下降。即使沒有去掉 KDE 的全部內容,開機內存也壓縮到了 1 G 內,內存佔用體感少了很多。可惜這是一個內存容量不敏感的時代。

成功得進一步拋棄了鼠標,這完全是 side effect。在使用過 i3 後你會承認鼠標操作很多時候比較緩慢,並且讓人不舒服。代價自然是初期成本較高。

此外,與無腦點點點相比,i3 桌面相對比較耗腦子。鼠標的邏輯是我慢慢挪總能弄到我想弄到的,而 i3 強調目的性,你的一個快捷鍵應黨取得你期望的效果。

i3 使用感受是屏幕變大了(不用 i3-gaps),因爲應用會鋪滿屏幕,靈活在 workspace 切換非常爽。

個人感覺 i3 暫時使用很愉快,以後有計劃 dwm 等等也是可以嘗試的。

對我這種小顯示器這樣就足夠了,如果是大顯示器,考慮 i3-gaps。如果是多顯示器,邏輯則要複雜許多。