教老狗學新把戲:Forgejo + XMPP

我最近決定離開 Codeberg,改為自行架設 Forgejo 實例。完成初步設定後,我意識到自己架設的程式碼託管平台,就是可以隨心打造的平台,不再需要遷就別人認為合理的功能組合。現在,我可以讓它真正屬於自己,並啟用那些我一直希望 Git 託管平台能實現的功能。
首先想處理的是註冊機制。目前的 Forgejo 和其他絕大多數託管平台一樣,要求提供電子郵件地址並寄送驗證連結,因為顯然點擊連結仍被視為對方是真人的證明。然而,我更希望讓使用者直接以 XMPP 的 JID 來註冊。一方面,能夠接收連結的電子郵件地址早已不再是自動化垃圾訊息的真正障礙,因此電子郵件驗證的效益不如人們想像的那麼大;另一方面,XMPP 是更優秀的協定,我也更希望使用者一開始就透過它、而非電子郵件來加入。
可惜的是,Forgejo 並沒有原生的 XMPP 支援。此外,它也沒有利用 Go 的 shared objects(共享物件),而這項機制本可讓擴充功能在很大程度上獨立於核心之外建置,對於這種規模的單體式架構來說其實非常合理。若不深入核心,就沒有乾淨的方式可以為它加入新的協定。
不過,我並不打算在 Forgejo 內部實作真正的 JID 註冊與驗證。那無異於與風車作戰,這話我是憑經驗說的。過去我嘗試過更輕量的修改,也都被諸如設計討論與使用案例蒐集這類官僚要求所扼殺,過程中大概還會冒出一張laissez-passer A38。所以我做了任何理智卻又有點瘋狂的人都會做的事:決定將 Forgejo 現有的電子郵件整合橋接至 XMPP。
介紹 Switchyard
Switchyard 是一個小型的常駐程式,一端講 SMTP,另一端講 XMPP。電子郵件與 XMPP 恰好共用相同的地址形式 [email protected],因此收件者可以直接對應,寄給 [email protected] 的訊息會以聊天訊息的形式發送至 JID [email protected]。它會接收服務透過 SMTP 提交的郵件,將每封郵件轉為佇列中的工作,再由工作處理程序透過一條常駐的 XMPP 連線發送;當伺服器斷線時,該連線會自行重新連接。
因為它的 SMTP 監聽方式與任何郵件伺服器相同,Forgejo 不需要任何修補就能將郵件交給它。只要像指向真正的郵件伺服器那樣,將它設定為 mailer 端點即可:
[mailer]
ENABLED = true
FROM = [email protected]
PROTOCOL = smtp
SMTP_ADDR = 127.0.0.1
SMTP_PORT = 587
USER = forgejo
PASSWD = `change-me`另一端,簡短的 switchyard.toml 提供了它用來驗證 Forgejo 的帳密、提交埠(submission port)所需的憑證,以及它用來發送所有訊息的單一 XMPP 帳號:
[redis]
addrs = ["127.0.0.1:6379"]
[smtp]
allowed_ips = ["127.0.0.1/32"]
username = "forgejo"
password = "change-me"
[smtp.tls]
enable = true
cert = "/etc/switchyard/tls/cert.pem"
key = "/etc/switchyard/tls/key.pem"
[xmpp]
server = "xmpp.myhost.tld:5222"
username = "[email protected]"
password = "change-me"完成這些設定後,Forgejo 原本會透過電子郵件發送的每一封訊息——例如註冊確認、密碼重設或新議題通知——都會改為透過 XMPP 發送至使用者在電子郵件欄位中輸入的 JID。Switchyard 也會處理 IDNs(國際化域名),因為,你知道的,這本來就是我的強項。:-)
SMTP 傳遞的網域一律是 punycode 形式,而 XMPP 使用的是原生的 IDN,因此它會在轉送過程中將收件者的網域解碼;在 Forgejo 中註冊為 [email protected] 的使用者,在 XMPP 上會以 user@マリウス.com 收到訊息。
不只 Forgejo
這個輕量級服務並非 Forgejo 專屬。Switchyard 適用於任何可指向 SMTP 伺服器的軟體,能讓該軟體在自身毫無察覺的情況下開始使用 XMPP。另一端唯一需要調整的,是將 HTML 樣板中「Email address」欄位重新標示為「JID」,讓使用者知道該填入什麼。以 Forgejo 為例,這只是一個小小的樣板修補,你可以輕鬆自行維護。這與維護一套上游極可能永遠不會接受的完整 XMPP 實作相比,所需投入的成本截然不同。
缺點是這麼做會讓你暫時失去電子郵件功能(目前如此!)。一旦 Forgejo 開始與 Switchyard 對接,它發出的所有訊息都會走 XMPP,而當初填寫電子郵件地址的使用者將收不到任何東西。我已經在著手修正這個問題,做法是讓 Switchyard 先檢查目標主機是否真的是 XMPP 伺服器,若不是,則退回並將訊息轉發至你所設定的一般郵件伺服器。如此一來,一種協定(SMTP)進來,會在可用時分派至偏好的協定(XMPP),否則就轉發至備援的協定(SMTP)。輕輕鬆鬆,簡簡單單。
這算是乾淨優雅的工程設計嗎?當然不是。它就是把一個 SMTP 伺服器硬接到一個 XMPP 客戶端上(中間夾著一個工作佇列),但它能運作,也確實完成了任務。透過 Switchyard,我得到了本該屬於上游、卻幾乎永遠不可能被上游接受的功能,對我而言,這比一個或許能在選美比賽中獲勝、卻可能終生都只是個修補程式的乾淨實作更有價值。一個我必須在每次 Forgejo 發布新版本時不斷合併與維護的修補程式。
隨機一篇部落格