Fedora 上使用硬體加密的 SSH 與 PGP

從一開始入手 Yubikey 4,後來換成 Yubikey 5 NFC,我已經很久沒有把 SSH 跟 PGP 的密鑰放在檔案系統裡過了。我最近新買了一隻 Yubikey 5 Nano,簡單設定後紀錄分享一下怎麼使用硬體加密的 SSH 和 PGP 簽章。

閱讀全文 Fedora 上使用硬體加密的 SSH 與 PGP

用 podman-kube 來跑 WordPress

上一篇寫了要怎麼用 podman 的 systemd generator 來跑單一的 container,其實 podman 還有另外一個 pod 模式,也就是類似 Kubernetes 的 pod 一樣,可以把多個 container 跑在同一個 namespace 裡面。Podman 的 podman-kube-play(1) 指令可以接受一個 Kubernetes 的 YAML 設定檔然後跑起單一個 container 或是一整個 pod 與多個 container。同樣的,systemd generator 也可以用來管理整個 pod,變成 systemd service unit。

閱讀全文 用 podman-kube 來跑 WordPress

用 systemd 管理 podman containers

Podman 原本有提供指令可以產生 systemd service 的設定檔,但是產生出來的檔案內容感覺有很多改善的空間,例如要怎麼讓 podman 可以乖乖的把 log 送到 journald 就不是產生的範本有提供的。

podman generate systemd wordpress-container

在研究這個問題的時候發現這個指令已經被 Quadlet 取代了。Quadlet 是在 podman 4.4 的時候整合進去的,利用 systemd generator 的方式把類似於 systemd 的設定檔直接轉換為 systemd 可以使用的格式。

# cat /etc/containers/systemd/wordpress.container
[Unit]
Description=Wordpress Container
After=network-online.target
Wants=network-online.target

[Container]
Image=docker.io/library/wordpress:6
Volume=/var/srv/wordpress:/var/www/html:Z

[Service]
Restart=always
TimeoutStartSec=900

[Install]
WantedBy=multi-user.target

如此一來就可以簡單的設定 container 以及讓 container 自動更新了。詳細的設定可以參考 podman-systemd.unit(5) 的內容。

把設定放到 /etc/containers/systemd 目錄下以後,記得要讓 systemd 重新讀取:

# sudo systemctl daemon-reload
# sudo systemctl start wordpress
# systemctl status wordpress
● wordpress.service - WordPress Container
     Loaded: loaded (/etc/containers/systemd/wordpress.container; generated)
    Drop-In: /usr/lib/systemd/system/service.d
             └─10-timeout-abort.conf
     Active: active (running) since Sun 2023-11-05 03:07:06 UTC; 20h ago
   Main PID: 57888 (conmon)
      Tasks: 5 (limit: 2032)
     Memory: 212.2M
        CPU: 4min 34.751s
     CGroup: /system.slice/wordpress.service
             ├─libpod-payload-739aab67986c96c0a76034f2af5203530442e8e492b3a453b7a193b76623f0d5
             │ ├─57890 apache2 -DFOREGROUND
             │ ├─74394 apache2 -DFOREGROUND
             │ ├─74399 apache2 -DFOREGROUND
             │ └─74407 apache2 -DFOREGROUND
             └─runtime
               └─57888 /usr/bin/conmon --api-version 1 -c 739aab67986c96c0a76034f2af5203530442e8e492b3a453b7a193b76623f0d5

這一種設定方式比原本的 systemd 簡單太多了。

Fedora Silverblue 如何新增硬碟到 Btrfs 陣列

我目前的主力電腦是一台 2020 年購入的 System76 的 Lemur Pro (lemp9),前陣子無線網路卡掛點,自己換了新的 Intel AX210 模組。

今天有感於最初設定的規格 500 GB 的 SSD 儲存空間有點不夠用了,想要加裝新的 SSD 來擴增容量。lemp9 內部有兩個 M.2 NVMe 的空間,所以我還有一個空位可以用。

原本內裝的是 Samsung 的 970 EVO Plus 500 GB,所以這次就添購了同樣是 Samsung 的 970 EVO Plus 2 TB,一次加好加滿。

我大約半年前把主力作業系統換成 Fedora 並且裝了 Fedora Silverblue 這個版本,是一種新型的 immutable 作業系統安裝方式,安裝基底系統後,額外的軟體都是用 Flatpak 或是 container 的形式疊加上去的。

有如其他 Fedora Workstation 系統,Silverblue 已經預設使用 Btrfs 為主要檔案系統。這次我要新增一個 SSD 並且加到硬碟陣列中。主要的一個問題是我有用 LUKS 形式的全硬碟加密,增加了新的 SSD 之後要改些設定讓他在開機的時候會自動解鎖。

1. 安裝新的 SSD

按照 System76 網站上的維修手冊就可以輕鬆安裝。

2. 格式化新的 SSD

使用 GNOME 內建的 Disks 工具軟體,幫新的 SSD 建立 GPT 分割表,然後產生一個分割區,不要建立檔案系統但是要加密。

3. 新增到 Btrfs 陣列

參考這篇詳細的說明,首先把解鎖後的裝置加入

sudo btrfs device add /dev/mapper/luks-d56dd585-059c-45b7-8eab-4e1df1e3d769 /mnt/btr_pool/

然後把 metadata 轉成 RAID1

sudo btrfs balance start -mconvert=raid1 /mnt/btr_pool/

接下來就可以確定新增完成:

sudo btrfs fi usage /
Overall:
    Device size:		   2.27TiB
    Device allocated:		 432.07GiB
    Device unallocated:		   1.85TiB
    Device missing:		     0.00B
    Device slack:		     0.00B
    Used:			 287.40GiB
    Free (estimated):		   1.99TiB	(min: 1.06TiB)
    Free (statfs, df):		   1.99TiB
    Data ratio:			      1.00
    Metadata ratio:		      2.00
    Global reserve:		 512.00MiB	(used: 0.00B)
    Multiple profiles:		        no

Data,single: Size:416.01GiB, Used:274.19GiB (65.91%)
   /dev/mapper/luks-4845f8f9-cfdf-4488-9611-68a7789869dd	 416.01GiB

Metadata,RAID1: Size:8.00GiB, Used:6.60GiB (82.56%)
   /dev/mapper/luks-4845f8f9-cfdf-4488-9611-68a7789869dd	   8.00GiB
   /dev/mapper/luks-d56dd585-059c-45b7-8eab-4e1df1e3d769	   8.00GiB

System,RAID1: Size:32.00MiB, Used:80.00KiB (0.24%)
   /dev/mapper/luks-4845f8f9-cfdf-4488-9611-68a7789869dd	  32.00MiB
   /dev/mapper/luks-d56dd585-059c-45b7-8eab-4e1df1e3d769	  32.00MiB

Unallocated:
   /dev/mapper/luks-4845f8f9-cfdf-4488-9611-68a7789869dd	  40.12GiB
   /dev/mapper/luks-d56dd585-059c-45b7-8eab-4e1df1e3d769	   1.81TiB

4. 修改開機參數

為了要在開機的時候自動解鎖兩個 SSD 一定要記得修改開機參數,不然開機後只有一個硬碟解鎖,會卡在開機掛載系統分割區。

在 Silverblue 可以用 rpm-ostree 指令管理開機參數:

rpm-ostree kargs --append=rd.luks.uuid=luks-d56dd585-059c-45b7-8eab-4e1df1e3d769

重新開機後就可以驗證宜一切都設定好了!

JSON Web Tokens

JSON Web Tokens 是什麼?它真的可以用來取代傳統的 session cookies 嗎? 本篇為研究 JWT 時所做的筆記。

閱讀全文 JSON Web Tokens

Simpler way to load CSS asynchronously

Smashing Newsletter 看到的,只要 <link> 標籤就可以簡單把 CSS 變成非同步讀取:

<link rel="stylesheet" href="/path/to/my.css"
        media="print" onload="this.media='all'">

有用過 Google PageSpeed Insight 分析網站的,應該都有看過如下的結果:

PageSpeed screenshot

原因是因為瀏覽器在處理 CSS 與 JavaScript 檔案的時候會阻擋其他資源的載入。解決的 方法裡,JavaScript 類型的資源可以標示 defer 或是 async 屬性讓瀏覽器知道我們 希望可以非同步的載入執行此 JavaScript 檔案,可惜 CSS 並沒有類似的屬性可以使用。

一般處理 CSS 的載入有兩個策略:

  1. 盡量把最重要的樣式內嵌(inline)在 HTML 內,
  2. 用 JavaScript 動態的插入 <link> 標籤。

策略 1. 應該不難理解,因為都已經要下載 HTML 了,不如把少部份最重要的樣式直接也寫 在裡面,可以省下一次網路請求。策略 2. 則是把其他剩餘的樣式集中在一起,用 JavaScript 在網頁讀取完畢後再動態的插入 <link> 標籤讓瀏覽器套用。LoadCSS 似乎是個常用的 library 但是我沒有用過,因為我不喜歡這種需要很多 JavaScript 的解 法。

這次看到的這個方法非常簡單,原本的 CSS 標籤如下:

<link rel="stylesheet" href="/path/to/my.css">

只要再加上 media="print" 以及 onload="this.media='all'" 就可以讓瀏覽器非同步載入 CSS 了!

原理是,瀏覽器在遇到非使用中的 media 時,只會下載但是不會套用樣式,所以設定 mediaprint 可以避免套用 CSS 的時候延遲其他的資源的載入,而 onload 的部 份則是在等到其他資源都載入完畢後,才把 media 切回 all 或是 screen 讓瀏覽器 套用樣式。雖然還是需要一點點 CSS,整體看起來清爽許多,也不需要額外的 library!

如果需要支援沒有開啟 JavaScript 的瀏覽器,記得加上 fallback:

<noscript><link rel="stylesheet" href="/path/to/my.css"></noscript>

完整的範例如下:

<link rel="stylesheet" href="/path/to/my.css"
        media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="/path/to/my.css"></noscript>

我迫不及待的立刻試用了? ∎

Not Again, MingLiu!

長話短說,從 FreeType 2.4.4 (2010-11-28 release) 開始,只要編譯的時候有啟用 BCI (Byte Code Interpreter) ,新細明體就不應該會破掉了,只是因為一些原因 (下面詳述),一直到 2.4.5 (2011-06-24 release) 才算是完全解決。

這個問題牽扯到許多有趣知識,所以我決定作個筆記以免以後忘掉。

TrueType

首先是一些名詞解釋,TrueType 是常見的字型 (font) 格式,由 Apple 發展,後來被 Microsoft 採用並發揚光大;雖然沒有經過任何國際組織的標準化,在當時儼然已經成為業界標準 (de facto standard)。

TrueType 使用的檔案格式又稱為 sfnt 格式,由許多不同的表格 (table) 組成,每個表格由四個字的標籤 (tag) 表示,其中我們最關心的是 glyf 這個表格,因為所有的字符 (glyph) 都儲存在這個表格內。每個字符僅僅紀錄了形狀,要知道某個編碼的某個字元 (character) 是對應到哪個形狀,還要透過 cmap這個表格來查詢;也有一個字沒有相對應的字符,或是沒有任何字對應到某個字符的情況。

參考資料:

Hinting (Instructing)

TrueType 是一種外框字型 (Outline Font),也就是說每一個字符都是以向量圖形的方式呈現,理論上可以在各種不同解析度下呈現相同的形狀。

DejaVu.tiff O Rasterize
M M Rasterize

但是越是在低解析度的時候,由於可用的像素較少,越容易遇到邊界的表達能力問題,如右圖的 M 字,若沒有對齊像素點的話,呈現出來的圖案就會比較瘦長且左右不對稱,如果稍微向右移動並加寬一點,呈現出來的圖案就會比較接近原本向量圖形所預期的形狀;這個動作正是 Hinting 中的對齊格線 (gridfitting) 功能。

在 TrueType 檔案中,描述 Hinting 的方式其實是儲存在字型的 fpgmprep 表格中的小程式,或是內嵌在字符中。看起來像是這樣:

    PUSHB[000]   # Push one byte onto the stack.
    3            # Value for loop variable.
    SLOOP[]      # Set the loop count to 3.
    PUSHB[002]   # Push three bytes onto the stack.
    5            # Point number.
    46           # Point number.
    43           # Point number.
    ALIGNRP[]    # Align the points 5, 46 and 43 with rp0 (point 8). This will
                 # align the height of the two lower serifs. 

使用這個小程式的方法是 Apple 的專利,但是因為 TrueType 一開始只是業界標準,Apple 並沒有在規格書裡面提及專利的相關資訊,造成後來使用 FreeType啟用 BCI (Byte Code Interpreter) 可能會侵犯專利的問題。(相關專利已經在 2010 年 4 月全數過期)

對每個字符做 Hinting 是個耗時耗力的工作,就算是只有數百個字符的西文字型也要花上數倍於繪圖的時間來做 Hinting,因此除了少數高品質的字型外,多數的字型是沒有 Hinting 的。而隨著顯示裝置的解析度越來越高,對於Hinting 的需求也越來越少,在較新的作業系統上可能會忽略部份 Hinting 的資訊或是完全關閉 Hinting。而在自由軟體的世界中,大多使用 FreeType的 Auto-Hinting 技術來支援解析度較低的顯示裝置。

參考資料:

MingLiu

細明體 (MingLiu) 與新細明體 (PMingLiu) 是華康 (DynaLab) 設計的字型,內建於 Microsoft 的作業系統中,使用非常廣泛。華康的字型使用了特殊的組字方式,似乎是為了從自有的字型資料轉換到 TrueType 才會使用這樣的方法。

首先,定義一組筆劃的字符,只用來組字,不對應到任何的編碼:

PMingLiu Strokes

然後在每一個個別的字符中用參照的方式把筆劃匯入,這時筆劃間的相對位置是亂的,最後再用 Hinting 的功能把筆劃移動到對的位置去:

PMingLiu uni65B0
PMingLiu Hinting

這樣濫用 Hinting 的方式,也導致如果不開啟 Hinting 就會看到破碎的字符:

PMingLiu Broken Glyph

為什麼說是濫用?因為只要沒有打開 Hinting 就會顯示錯誤,表示用錯地方了;有些時候是不需要,或是沒辦法使用 Hinting 的。

參考資料:

Tricky Fonts

除了新細明體,華康還有一些字型也是使用類似的方式組字,同樣很多人使用的標楷體就是其中之一,還有一些日本字型也是濫用 Hinting 的功能來繪製字符,所以 FreeType 其實有內建黑名單來對付這樣的字型,遇到了就強制開啟Hinting。一開始這個名單是用字型名稱來列表的:

    static const char trick_names[TRICK_NAMES_COUNT]
                                 [TRICK_NAMES_MAX_CHARACTERS + 1] =
    {
      "DFKaiSho-SB",     /* dfkaisb.ttf */
      "DFKaiShu",
      "DFKai-SB",        /* kaiu.ttf */
      "HuaTianKaiTi?",   /* htkt2.ttf */
      "HuaTianSongTi?",  /* htst3.ttf */
      "MingLiU",         /* mingliu.ttf & mingliu.ttc */
      "PMingLiU",        /* mingliu.ttc */
      "MingLi43",        /* mingli.ttf */
    };

但是這樣還不夠,因為很多 PDF 輸出軟體會把字型的名稱亂改:

    % pdffonts ~/pdf31yNwCi71d.pdf
    name                            type              emb sub uni object ID
    ------------------------------- ----------------- --- --- --- ---------
    AAAAAA+TimesNewRoman            TrueType          yes yes yes      9  0
    AAAAAB+___                      TrueType          yes yes yes     16  0
    AAAAAC+___                      TrueType          yes yes yes     17  0
    AAAAAE+____                     TrueType          yes yes yes     18  0
    AAAAAD+____                     TrueType          yes yes yes     19  0

遇到這樣的情況就破功了,所以很多人在看 PDF 的時候會遇到字破掉的問題,我先在中提出可能的解法,後來發現 Suzuki Toshiya 在 2010-11-22 的時候加入用 cvt/fpgm/prep 表格的加總檢查來比對黑名單,因為這三個表格是只要是有使用 Hinting 的字型都會有,也不太會改變的。

    static const tt_sfnt_id_rec sfnt_id[TRICK_SFNT_IDS_NUM_FACES]
                                       [TRICK_SFNT_IDS_PER_FACE] = {
      ...
      { /* MingLiU 1995 */
        { 0x05bcf058, 0x000002e4 }, /* cvt  */
        { 0x28233bf1, 0x000087c4 }, /* fpgm */
        { 0xa344a1ea, 0x000001e1 }  /* prep */
      },
      { /* MingLiU 1996- */
        { 0x05bcf058, 0x000002e4 }, /* cvt  */
        { 0x28233bf1, 0x000087c4 }, /* fpgm */
        { 0xa344a1eb, 0x000001e1 }  /* prep */
      },
      ...

只是原先這個方法只有在字型沒有名稱的時候才會啟用,我加了 patch 改成無條件檢查黑名單,從此就應該不會有漏網之魚了。

參考資料:

Sign Extension

只是後來我在我的機器上面仍然發現某些 PDF 仍然顯示破碎字型,難道是加總檢查也會出錯?因為牽扯到加總的問題,第一個想到的就是可能跟機器的整數長度有關,立刻改用 32-bit 的環境測試同一個 PDF,果然就好了。但是計算檢查碼的演算法很簡單,也沒有溢位的疑慮,那是哪裡出了問題?好在 IRC上有大大給了提示:

    kanru> hum.. checksum *: 0xa344a1eb
    kanru> checksum #: 0xffffffffa344a1eb
    j5g> kanru: 哈,因為 init checksum = 0xffffffff, sign extension 變
                成 0xffffffffffffffff?
    j5g> 然後之後都只處理後面 32bit
    kanru> j5g: 不過有可能是 font 裡面是錯的
    kanru> 0xffffffffa344a1eb 是直接從 font table 讀出來的
    j5g> 喔,那就是 0xa344a1eb sign extended to 0xffffffffa344a1eb

原來是 FreeType 在讀入字型資訊的時候,強制把 int 轉型為 uint 結果發生sign extension 錯誤,之後比對的時候就跟著錯了。送了 patch 之後,應該天下太平了吧?

參考資料:

PDF correctness

有人問說:那為什麼不乾脆隨時打開 Hinting 就好?據我所知目前的自由 PDFViewer 都是預設關閉 Hinting 的,除了上述的理由外,我猜大概是因為Hinting 有可能大幅度的改變一個字符的寬度,如下圖:

FtDiff DejaVu

可以發現三種顯示方式的寬度都不一樣,而 PDF 是一種接近印刷品質的檔案格式,有可能在輸出 PDF 的時候已經丟失動態改變寬度的能力,kern 的修正也已經沒辦法修改,這時候如過還讓 Hinting 去改變字符的寬度的話,很有可能得到不理想的結果。不過以上只是我的猜想,還沒有找各 PDF Viewer 的作者證實。至於封閉的商業 PDF Viewer 又是怎麼做的就更不得而知了。

結論:快升級到最新的 FreeType 吧!如果還有遇到顯示錯誤的 PDF 可以寄給我,如果以上筆記有疏漏錯誤的地方歡迎指正 ?