修好 Trac 1.6 上的 SlackIntegration

續上篇「修好 Trac 1.6 上的 TracSubtickets」提到的,反正 Trac 這個 community 的東西如果爛掉的話,自己修會比較快...

SlackIntegration 從字面上的意思就知道,是把 Trac 這邊的更新同步到 Slack 上的工具。

這包主要的問題是 Python 2 語法,所以在修的過程中間就不斷的在想起「啊,這是當初 Python 3 的 break-BC 改動」。

從「Comparing 72bec82..master · gslin/trac-slack-plugin」這邊可以可以到修正的東西,主要修正的都是跟 unicode 有關的程式碼。

一塊是 2to3 修正的,把 u'...' 這種字串直接變成 '...',不需要額外指定,另外是 unicode(...) 變成 str(...)

另外一個也還是 unicode 相關的,但因為是邏輯上的部分,2to3 沒有抓出來的,是 encode('utf-8') 的部分看起來就用不到了,這在 Python 3 裡面 requests 會自動處理掉,只要丟標準的 string (unicode string) 進去就可以了。

這樣又弄好一個套件了...

修好 Trac 1.6 上的 TracSubtickets

Trac 1.6 總算從死了三年的 Python 2.7 換成了 Python 3,所以算是蠻強大的升級動力,但也可以想像到相關的 plugin 其實因此爛了不少,加上 Trac 現再用的人愈來愈少,沒有人會修這些問題,所以你就得當「沒有人」跳下去修...

標題上提到的 TracSubtickets 算是這樣的一個套件,他的概念很好用,但大概從 Trac 1.2 以後就沒什麼更新了,先前有遇到 MySQL 8.0 的資料庫搭配起來會撞到關鍵字而出錯,得自己修。

而這次遇到的問題是 TracSubtickets 在頁面輸出子票資訊時用到 ITemplateStreamFilter 這個功能,而從官方文件開頭也可以看到問題:Trac 1.4 的時候內部的 template engine 就從自家研發的 Genshi 換成了 Python 社群用的更廣泛的 Jinja2,但當時只是先標成 deprecated,還沒到不能用,直到 Trac 1.5.1 時拔掉了,所以接下來的 Trac 1.6 就沒得用了。

在官方的「Replacing the ITemplateStreamFilter interface」有提出建議的方法,是用 JavaScript 改 DOM:

The only way left to alter the generated content is to perform these modifications dynamically on client-side using JavaScript.

我看了半天 Trac 1.6 的程式碼,看起來的確沒有什麼比較好的方法可以處理... 只能回來照官方的方法走,後續的問題就是看要處理的多乾淨 (或是多髒)。

因為 Trac 本身沒有 client template engine (像是 React 或是 Vue 之類的),我決定這邊還是讓 server 端全部把 html string 都產生出來,再由 client 端生一個 div 直接用 innerHTML 塞進去:這樣就不用傳一包 JSON 到 client 端慢慢組了...

於是就出現了這包 diff/patch:「Comparing cae40fb..master · gslin/trac-subtickets-plugin」。

基本的思路是,既然以前的 filter_stream() 是產生 html tree 的程式碼,那就重複拿來用,把結果輸出轉成 html string,用 add_script_data 丟到 window 下的 global variable (喔耶),再寫一段 javascript 把這串東西塞進本來在的 DOM 位置。

這樣至少就能動了...

MariaDB 以及 Trac 在 arm64 上的安裝

把一台本來跑在 Vultr 上的機器搬到 AWSus-east-1 上面,除了剛好把 Ubuntu 18.04 換成 Ubuntu 22.04 外,也把本來用 x86-64 架構的機器換成用 ARMt4g.micro (都是 1GB RAM)。

就效能上來說,t4g 機器的效能很不錯,這兩年 blog 跑的也都還算順,先前公司用起來感覺也很好,然後價錢更便宜,另外加上 AWS 的三年 RI 折扣大約是 4 折的價錢,算是會想要換的主因。

在確認應用跑得起來後,買三年 RI 是 $87.15/3y,所以機器本身的費用大約是 $29.05/y,就算加上 8GB 的 EBS (gp3) 空間費用,整體比本來在 Vultr 的 $6/mo 低不少。

上面跑的是我自己的 Trac,想搬到 AWS 上一陣子了,但有幾個不確定的因素,所以連假期間才有空多花一些時間確認。

第一個是 MySQL 的部份,我自己習慣用 Percona Server 的版本,但目前還沒有 arm64 的套件可以直接裝,要用的話就得自己編以及升級。

在 2021 年的時候 blog 搬到 AWS 的時候就遇過了,本來以為這次有機會,但看了一下還是沒支援,所以還是得用 MariaDB

第二個是 Trac 1.4 只能跑在 Python 2.7 上 (mailing list 上有在討論轉到 Python 3 的事情,但看起來官方的動力也不大...),這在 18.04 的時代是沒什麼問題,但 22.04 下面不知道會爛掉多少東西。

所以只能繼續用 pyenv 扛著,但已經有預期會遇到問題,加上這次又從 MySQL 轉到 MariaDB,應該也會有些地雷...

所以跳下去後遇到的問題就跟上面提到的類似,分成兩塊。

在 MariaDB 這邊第一個遇到問題是,雖然官方有提供 APT server,但沒有在 HTTPS server 上放新的 public key,所以一定得從 key server 撈。

GnuPG 就是沒有直接從 key server 下載變成檔案的功能,一定要先塞到 keystore 裡面再 export 出來,就覺得很...

所以就冒出利用 mktemp -d/tmp 下產生暫存目錄這樣的寫法,讓 GnuPG 把 keystore 放進去,這樣至少在重開機後就會消失:

export GNUPGHOME=$(mktemp -d); gpg --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0x177F4010FE56CA3336300305F1656F24C74CD1D8; gpg --export 0x177F4010FE56CA3336300305F1656F24C74CD1D8 | sudo tee /etc/apt/trusted.gpg.d/mariadb.gpg > /dev/null; unset GNUPGHOME

這邊為了安全性,還得把官方提供的 0xF1656F24C74CD1D8 換成 0x177F4010FE56CA3336300305F1656F24C74CD1D8

另外就是整理 MariaDB 需要的 my.cnf 內容,我是拿 Percona Server 5.7 的設定檔來改,只刪掉了跟 GTID 相關的設定就會動了。

而其他 MariaDB 遇到的問題主要是設計改變的問題,在 wiki 上有提到。

接下來是 Trac 1.4 的問題,本來的安裝是用 libmysqlclient-dev,然後再安裝 mysql-python

sudo apt install -y libmysqlclient-dev
pip install mysql-python PyMySQL Pygments Trac

但單純把 libmysqlclient-dev 換成 libmariadb-dev 後,mysql-python 還是編不動,照著錯誤訊息試著 workaround (像是試著把 /usr/bin/mysql_config 指到 /usr/bin/mariadb_config) 半天還是不過,最後找資料發現要改用 mysqlclient

sudo apt install -y libmariadb-dev
pip install mysqlclient PyMySQL Pygments Trac

搞定後後續就一路看錯誤訊息解就可以了...

Trac 開發版 1.5.1 對 Python 3 的說明

Trac 開發版 1.5.1 的 Change Log 裡可以看到說明:

This will be the only release in the 1.5.x release line that supports Python 2.7. Future releases will support Python 3.5+.

先前在 mailing list 上有看到 Python 3 的計畫,不過好像是第一次在 changelog 裡面看到說明了...

把自己的 Trac 換到 1.4

八月底的時候就已經出 Trac 1.4 了 (參考當時寫的「Trac 1.4」),不過當時升級一直失敗:

$ trac-admin ./ upgrade
Error: Unable to check for upgrade of trac.db.api.DatabaseManager: TracError: Unsupported database type "mysql"

後來在「TracError: Unsupported database type “mysql”」這邊找到解法,下面有人提到解法是裝系統的 python-pymysql,那麼對應到 pip 的套件安裝時,裝 PyMySQL 就可以了。

另外一個比較麻煩的就是 Trac 的 XmlRpcPlugin 套件,這個套件其實一直都很 hack,常常是自己硬幹底層的東西跳過現有機制 (於是像是透過他開 ticket 時,就不會發通知信之類的問題),但目前暫時又不能不用他...

Anyway,這東西在升級後也爛掉了:「Plugin fails on Trac 1.4」,然後下面看到 Gasol 修了一版出來:「Gasol/trac-xmlrpcplugin」。

另外是我自己有掛 custom theme 的套件 ThemeEnginePlugin 也爛掉了,不過這個暫時移除就好,之後再排進去修...

最後就是把本來的 css/js 都依照「Customizing the Trac Interface」的說明,porting 到 Jinja2 下。

主要就是把本來的 site.html 裡面的片段搬到 site_head.htmlsite_footer.html 裡,然後 CSS 的部份照著建議拆到 style.css 裡 (因為 css selector 的 id selector # 在一行開頭的位置會被 Jinja2 當作命令處理)。

後續繼續看看還有什麼要修正的,另外 wiki 也得等摸索差不多後更新...

用 Amazon SES 發 Trac 通知信的問題

Trac 在發 ticket 的通知信時,會定義自己的 Message-ID,另外後續變更的通知信件會增加 References 欄位,讓 mail client 可以配對起來 (變成一個 thread)。

Amazon SES 會把原來的 Message-ID 改掉,使用自己的 Message-ID 欄位,可是 References 欄位仍然維持不變... 這就導致 mail client 無法將第一封信 (只有被改過的 Message-ID) 與後續的信件 (References 所指到的信件不存在) 配對起來,只剩下後續的信件因為有相同的 References,所以 mail client 可以正確的配對起來。

所以我就決定生一個 workaround plugin,只要是沒有 References 的信件 (像是每張 ticket 的第一封信),就從 Message-ID 複製一份到 References 裡,這樣就可以讓後續的通知信件與第一封也連結起來了。另外評估這個 workaround 的副作用應該還好,所以就不判斷是不是 ticket 的通知信了...

這就是 trac-references-mail-decorator 這個套件的由來...

Trac 1.4

剛剛看到 Trac 出了 1.4 的消息,雖然還是在用 Python 2:「Release Notes for Trac 1.4 Jinja Release」。

其中一個比較大的改變是 template engine 換掉了,從 Edgewall 自家的 Genshi 換到 Jinja2 上,目前看起來兩者都可以用,但可以預期現有的 template 要找時間換過去:「5 times speed-up when rendering query results, thanks to the migration from Genshi to Jinja2.」。

其他的改變應該還好,找個週末來把自己的 Trac 備份起來測試好了...

在手機上看 Trac 的套件

這邊講的是 Safari 這些瀏覽器看 Trac,而不是講 app...

這次從高雄回來,在高鐵上想說用手機看看 Trac,發現桌機版的界面是會動,但在手機上不太好用,所以找看看有什麼套件可以改善...

回到家後找到 BlueFlatTheme 這套 (需要透過 ThemeEnginePlugin 啟用),標語是「A responsive, flat, blue theme using Bootstrap」,拿官方的 screenshot 可以看出來有特地為了寬度比較窄的情況調整:

裝好後用手機測了一下,還是有不滿意的地方,不過改善不少了,先這樣撐著...

在 DHCP 的情境下強制指定 DNS servers

我在 Vultr 上的 Trac 自動開票程式有時候會爛掉,沒把票開出來,把 stderr 輸出到檔案後發現是找不到 hostname:

socket.gaierror: [Errno -3] Temporary failure in name resolution

看了看 /etc/resolv.conf 發現系統使用的 DNS server 設定是透過 DHCP 取得設定的。但 Vultr 只有提供一組 DNS server,當查不到東西時就爆掉了... 所以找了一下,看到「How to override the DHCP-provided nameserver?」這篇,但裡面是用「增加到前面的方式」,跟我想要改成只用 1.1.1.11.0.0.1 不太一樣。

知道目錄後拿關鍵字去 dhclient.conf 的 manpage 裡面找,就可以看到四種設定方式:

        default [ option declaration ] ;
        supersede [ option declaration ] ;
        prepend [ option declaration ] ;
        append [ option declaration ] ;

這四組看名字就大概知道用途了。接下來就是把對應的 interface 查出來以後,用 supersede 就搞定了:

    supersede domain-name-servers 1.1.1.1, 1.0.0.1;

重開機測試可以確認 /etc/resolv.conf 的內容改變了。接下來再來觀察看看還會不會有狀況...