<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-TW">
	<title>Hackz</title>
	<subtitle>Let the hacking began.</subtitle>
	<link rel="self" type="application/atom+xml" href="https://blog.2ac.io/posts/feed.xml"/>
  <link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/"/>
  
	<updated>2026-10-03T00:00:00+08:00</updated>
	
	<id>https://blog.2ac.io/posts/feed.xml</id>
	<entry xml:lang="zh-TW">
		<title>fast_xlsx：用 Rust 寫的 Ruby xlsx 產生器，比 caxlsx 快 9 倍</title>
		<published>2026-10-03T00:00:00+08:00</published>
		<updated>2026-10-03T00:00:00+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/fast-xlsx/"/>
		<id>https://blog.2ac.io/posts/fast-xlsx/</id>
    
    <summary>&lt;figure&gt;
&lt;img src=&quot;/images/fast-xlsx-og.webp&quot; alt=&quot;fast_xlsx：Blazing Fast Excel for Ruby&quot; height=&quot;670&quot; width=&quot;1200&quot;&gt;&lt;/figure&gt;
&lt;p&gt;Rails 專案幾乎都逃不掉「匯出 Excel」這個需求。最常見的選擇是 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/caxlsx/caxlsx&quot;&gt;caxlsx&lt;/a&gt;，功能齊全，但資料一多就慢，記憶體也跟著暴衝。要快的話有 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/Paxa/fast_excel&quot;&gt;fast_excel&lt;/a&gt;，底層是 C 的 libxlsxwriter，速度確實好，只是安裝要編 C、走 FFI，日期不給格式就會顯示成一串數字，而且最後一次更新停在 2025 年 4 月。&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/fast_xlsx&quot;&gt;fast_xlsx&lt;/a&gt; 是 fast_excel 的精神續作：一樣主打「快速逐列寫入、吃很少記憶體」，底層換成 Rust 的 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/jmcnamara/rust_xlsxwriter&quot;&gt;rust_xlsxwriter&lt;/a&gt;，再用 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/matsadler/magnus&quot;&gt;magnus&lt;/a&gt; 包成 Ruby native extension。20,000 列的匯出只要 &lt;strong&gt;75 ms&lt;/strong&gt;，比 caxlsx 快將近 9 倍。&lt;/p&gt;</summary>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/fast-xlsx/">&lt;figure&gt;
&lt;img src=&quot;/images/fast-xlsx-og.webp&quot; alt=&quot;fast_xlsx：Blazing Fast Excel for Ruby&quot; height=&quot;670&quot; width=&quot;1200&quot;&gt;&lt;/figure&gt;
&lt;p&gt;Rails 專案幾乎都逃不掉「匯出 Excel」這個需求。最常見的選擇是 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/caxlsx/caxlsx&quot;&gt;caxlsx&lt;/a&gt;，功能齊全，但資料一多就慢，記憶體也跟著暴衝。要快的話有 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/Paxa/fast_excel&quot;&gt;fast_excel&lt;/a&gt;，底層是 C 的 libxlsxwriter，速度確實好，只是安裝要編 C、走 FFI，日期不給格式就會顯示成一串數字，而且最後一次更新停在 2025 年 4 月。&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/fast_xlsx&quot;&gt;fast_xlsx&lt;/a&gt; 是 fast_excel 的精神續作：一樣主打「快速逐列寫入、吃很少記憶體」，底層換成 Rust 的 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/jmcnamara/rust_xlsxwriter&quot;&gt;rust_xlsxwriter&lt;/a&gt;，再用 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/matsadler/magnus&quot;&gt;magnus&lt;/a&gt; 包成 Ruby native extension。20,000 列的匯出只要 &lt;strong&gt;75 ms&lt;/strong&gt;，比 caxlsx 快將近 9 倍。&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;pao-fen&quot;&gt;跑分&lt;a class=&quot;zola-anchor&quot; href=&quot;#pao-fen&quot; aria-label=&quot;Anchor link for: pao-fen&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Apple Silicon、Ruby 4.0.5，每個 gem 都用它自己最慣用的逐列寫入 API。20,000 列 × 5 欄（整數、字串、整數、&lt;code&gt;Time&lt;/code&gt;、浮點數），建好並輸出成 String，取 7 次的中位數：&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Library&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;時間&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;倍數&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;配置的 Ruby 物件&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;fast_xlsx&lt;/strong&gt;（&lt;code&gt;memory: :constant&lt;/code&gt;）&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;75 ms&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.0x&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;7&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;fast_xlsx&lt;/strong&gt;（預設）&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;85 ms&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.1x&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;10&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;xlsxtream 3.1&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;173 ms&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.3x&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;561,740&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;fast_excel 0.5（constant_memory）&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;204 ms&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.7x&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;20,079&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;fast_excel 0.5&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;238 ms&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3.2x&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;320,076&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;write_xlsx 1.15&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;592 ms&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;7.9x&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1,483,899&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;caxlsx 4.5&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;671 ms&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;8.9x&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;745,122&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;rubyXL 3.4&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2571 ms&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;34.1x&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;8,700,448&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;時間之外，最右邊那欄更值得看：整份檔案只配置了 7 個 Ruby 物件。每一列的值在 Rust 那邊就直接轉成儲存格，不會在 Ruby 這端產生一堆中間物件，GC 幾乎沒事做。&lt;/p&gt;
&lt;p&gt;記憶體則是 200,000 列存成檔案，量峰值 RSS 比只建資料的 process 多了多少：&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Library&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;每列字串都不同&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;字串大量重複&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;fast_xlsx&lt;/strong&gt;（&lt;code&gt;memory: :constant&lt;/code&gt;）&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;+1 MB&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;+1 MB&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;fast_xlsx&lt;/strong&gt;（&lt;code&gt;memory: :low&lt;/code&gt;）&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+61 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;+1 MB&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;fast_xlsx&lt;/strong&gt;（預設）&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+271 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+217 MB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;fast_excel 0.5（constant_memory）&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+10 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+9 MB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;fast_excel 0.5&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+182 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+151 MB&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;預設模式反而比 fast_excel 吃記憶體，原因是 rust_xlsxwriter 存檔時會在記憶體裡把每張工作表的 XML 組好（這樣多張工作表可以平行處理），不是從暫存檔串流出去。所以大量匯出請直接用 &lt;code&gt;:constant&lt;/code&gt; 或 &lt;code&gt;:low&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id=&quot;san-zhong-ji-yi-ti-mo-shi&quot;&gt;三種記憶體模式&lt;a class=&quot;zola-anchor&quot; href=&quot;#san-zhong-ji-yi-ti-mo-shi&quot; aria-label=&quot;Anchor link for: san-zhong-ji-yi-ti-mo-shi&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;FastXlsx&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Workbook&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                     #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; :standard，全部放記憶體，可以任意順序寫入&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;FastXlsx&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Workbook&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;constant&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 寫完的列丟到暫存檔，字串直接存在儲存格裡&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;FastXlsx&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Workbook&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;low&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;       #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 寫完的列丟到暫存檔，字串放 Excel 的 shared string table&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;怎麼選：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;:standard&lt;/code&gt;&lt;/strong&gt;：一般報表、需要回頭改前面的列，或要用 &lt;code&gt;autofit&lt;/code&gt; 自動算欄寬。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;:constant&lt;/code&gt;&lt;/strong&gt;：大量匯出、逐列往下寫。記憶體不管資料多大都是平的，也是最快的模式。缺點是 inline string 不是每個讀取工具都支援（例如 xsv）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;:low&lt;/code&gt;&lt;/strong&gt;：大量匯出、而且檔案要給其他程式讀。地區、狀態這種重複字串多的資料，記憶體一樣很低，輸出也是標準格式。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;兩種存到磁碟的模式都只能由上往下寫，回頭寫已經存到磁碟的列會直接丟 &lt;code&gt;FastXlsx::Error&lt;/code&gt;，不會默默吞掉。&lt;/p&gt;
&lt;h2 id=&quot;ji-ben-yong-fa&quot;&gt;基本用法&lt;a class=&quot;zola-anchor&quot; href=&quot;#ji-ben-yong-fa&quot; aria-label=&quot;Anchor link for: ji-ben-yong-fa&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;fast_xlsx&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;wb&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; FastXlsx&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Workbook&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;new&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;ws&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; wb&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;add_worksheet&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Sales&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; FastXlsx&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Format&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;bold&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; bg_color&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;#DDEBF7&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; border_bottom&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;thin&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;money&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; FastXlsx&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Format&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;num_format&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;#,##0.00&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ws&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Region&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Product&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Units&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Price&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Sold on&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; format&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span&gt; header&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sales&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; do&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ws&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;region&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;product&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;units&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;price&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; s&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sold_on&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;            format&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;nil&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; money&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ws&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;autofit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ws&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;freeze_panes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;A2&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ws&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;autofilter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;A1:E&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;sales&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; +&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;wb&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;save&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;sales.xlsx&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 或 wb.to_xlsx 拿到 binary String&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;格式也可以直接傳 Hash，相同內容的 Hash 會共用同一個格式：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ws&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Total&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1_234&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; format&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; bold&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; header&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;merge&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;font_size&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 16&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rails controller 裡大概長這樣：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; export&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;  wb&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; FastXlsx&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Workbook&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;constant&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;  ws&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; wb&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;add_worksheet&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Orders&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ws&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Customer&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Total&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Placed at&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; format&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; bold&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  Order&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;includes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;customer&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;find_each&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; ws &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;number&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; o&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;customer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;name&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; o&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;total&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; o&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;created_at&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  send_data wb&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;to_xlsx&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; filename&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;orders.xlsx&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;                        type&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;除了寫資料，條件式格式、資料驗證（下拉選單）、表格、圖表、圖片、註解、工作表保護、列印設定、大綱群組也都有對應的 Ruby 方法，完整清單看 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/fast_xlsx#guide&quot;&gt;README&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id=&quot;ji-ge-ke-yi-de-she-ji&quot;&gt;幾個刻意的設計&lt;a class=&quot;zola-anchor&quot; href=&quot;#ji-ge-ke-yi-de-she-ji&quot; aria-label=&quot;Anchor link for: ji-ge-ke-yi-de-she-ji&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id=&quot;ri-qi-bu-yong-she-ge-shi-jiu-shi-ri-qi&quot;&gt;日期不用設格式就是日期&lt;a class=&quot;zola-anchor&quot; href=&quot;#ri-qi-bu-yong-she-ge-shi-jiu-shi-ri-qi&quot; aria-label=&quot;Anchor link for: ri-qi-bu-yong-she-ge-shi-jiu-shi-ri-qi&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;用 fast_excel 時最常踩的坑：&lt;code&gt;created_at&lt;/code&gt; 沒給 date format，打開 Excel 看到的是 &lt;code&gt;46298.5&lt;/code&gt; 這種序號。fast_xlsx 對 &lt;code&gt;Date&lt;/code&gt; 預設套 &lt;code&gt;yyyy-mm-dd&lt;/code&gt;，&lt;code&gt;Time&lt;/code&gt; 套 &lt;code&gt;yyyy-mm-dd hh:mm:ss&lt;/code&gt;。如果你給的格式只有粗體沒有 &lt;code&gt;num_format&lt;/code&gt;，也會自動補上日期格式，不會因為加了粗體就變回數字。&lt;/p&gt;
&lt;p&gt;Rails 的 &lt;code&gt;ActiveSupport::TimeWithZone&lt;/code&gt; 不是真正的 &lt;code&gt;Time&lt;/code&gt;，0.8.1 之前會被當成字串寫出去，現在已經修好，&lt;code&gt;created_at&lt;/code&gt; 直接丟進去就對了。&lt;/p&gt;
&lt;h3 id=&quot;cun-dang-bu-qia-gvl&quot;&gt;存檔不卡 GVL&lt;a class=&quot;zola-anchor&quot; href=&quot;#cun-dang-bu-qia-gvl&quot; aria-label=&quot;Anchor link for: cun-dang-bu-qia-gvl&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;匯出時大部分時間其實花在存檔：組 XML、壓縮。&lt;code&gt;to_xlsx&lt;/code&gt; 和 &lt;code&gt;save&lt;/code&gt; 做這段時會釋放 Ruby 的 global lock，所以在 Puma 或 Sidekiq 這種多執行緒環境下，一個大匯出不會把同一個 process 裡的其他 request 卡住。代價是 Ctrl-C、&lt;code&gt;Timeout&lt;/code&gt; 要等存檔結束才會生效。&lt;/p&gt;
&lt;h3 id=&quot;cuo-wu-yi-lu-zao-dian-diu&quot;&gt;錯誤一律早點丟&lt;a class=&quot;zola-anchor&quot; href=&quot;#cuo-wu-yi-lu-zao-dian-diu&quot; aria-label=&quot;Anchor link for: cuo-wu-yi-lu-zao-dian-diu&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;選項打錯字、顏色格式不對、超過 Excel 上限（單格 32,767 字、1,048,576 列、16,384 欄）都會在寫入前就丟例外，不會寫到一半才壞，也不會默默忽略。錯誤類別也照規則分：型別錯丟 &lt;code&gt;TypeError&lt;/code&gt;、超出工作表範圍丟 &lt;code&gt;RangeError&lt;/code&gt;、值不合法丟 &lt;code&gt;ArgumentError&lt;/code&gt;，工作簿本身做不到的事才丟 &lt;code&gt;FastXlsx::Error&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&quot;yu-xian-bian-hao-de-gem&quot;&gt;預先編好的 gem&lt;a class=&quot;zola-anchor&quot; href=&quot;#yu-xian-bian-hao-de-gem&quot; aria-label=&quot;Anchor link for: yu-xian-bian-hao-de-gem&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Linux（x86_64、aarch64，glibc 和 musl）、macOS（arm64、x86_64）、Windows x64 都有預編譯的 gem，&lt;code&gt;bundle add fast_xlsx&lt;/code&gt; 就好，不用裝 Rust toolchain 也不用 C compiler。需要 CRuby 3.3 以上。&lt;/p&gt;
&lt;h2 id=&quot;cong-fast-excel-ban-guo-lai&quot;&gt;從 fast_excel 搬過來&lt;a class=&quot;zola-anchor&quot; href=&quot;#cong-fast-excel-ban-guo-lai&quot; aria-label=&quot;Anchor link for: cong-fast-excel-ban-guo-lai&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;大部分只是改名：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; fast_excel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;workbook&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; FastExcel&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;open&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;constant_memory&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;worksheet&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; workbook&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;add_worksheet&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Orders&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;worksheet&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;append_row&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Total&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Placed at&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; workbook&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;bold_format&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;send_data workbook&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;read_string&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; filename&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;orders.xlsx&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; fast_xlsx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;workbook&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; FastXlsx&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Workbook&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;constant&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;worksheet&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; workbook&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;add_worksheet&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Orders&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;worksheet&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Total&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Placed at&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; format&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; bold&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;send_data workbook&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;to_xlsx&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; filename&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;orders.xlsx&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完整的對照表和行為差異整理在 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/fast_xlsx/blob/main/docs/MIGRATING_FROM_FAST_EXCEL.md&quot;&gt;Migrating from fast_excel&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id=&quot;ce-shi-lian-rust-duan-du-pao-mutation-test&quot;&gt;測試：連 Rust 端都跑 mutation test&lt;a class=&quot;zola-anchor&quot; href=&quot;#ce-shi-lian-rust-duan-du-pao-mutation-test&quot; aria-label=&quot;Anchor link for: ce-shi-lian-rust-duan-du-pao-mutation-test&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Ruby 測試只能從外面打 API，Rust extension 裡的邏輯到底有沒有被測到不太好確認。所以除了用 cargo-llvm-cov 量 Ruby 測試跑過 Rust 程式碼的覆蓋率，還加上 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://mutants.rs&quot;&gt;cargo-mutants&lt;/a&gt;：它一次改 Rust 原始碼的一小處（把 &lt;code&gt;&amp;lt;&lt;/code&gt; 換成 &lt;code&gt;&amp;lt;=&lt;/code&gt;、把回傳值換掉之類），再跑整套 Ruby 測試，確認每個改動都會讓某個測試失敗。沒抓到的 mutant 就代表有行為沒人測，每週在 CI 上自動跑一次。&lt;/p&gt;
&lt;h2 id=&quot;shou-wei&quot;&gt;收尾&lt;a class=&quot;zola-anchor&quot; href=&quot;#shou-wei&quot; aria-label=&quot;Anchor link for: shou-wei&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;目前版本是 0.8.1，API 從 0.8 起凍結，0.8.x 只修 bug，實際用一陣子沒需要改動就會發 1.0。如果你的 Rails 專案還在用 caxlsx 匯出大報表，或卡在 fast_excel 的日期和安裝問題，可以換換看。&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>換掉 glibc malloc 真的能省 Rails 記憶體嗎？八條 allocator 實測</title>
		<published>2026-09-13T00:00:00+08:00</published>
		<updated>2026-09-13T00:00:00+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/ruby-allocator-bench/"/>
		<id>https://blog.2ac.io/posts/ruby-allocator-bench/</id>
    
    <summary>&lt;figure&gt;
&lt;img src=&quot;/images/ruby-allocator-bench-og.webp&quot; alt=&quot;換掉 glibc malloc 真的能省 Rails 記憶體嗎？八條 Allocator 實測&quot; height=&quot;669&quot; width=&quot;1200&quot;&gt;&lt;/figure&gt;
&lt;p&gt;「Rails 要不要換 jemalloc」是個講了十年的老問題，標準答案是「換，我們省了 30% RSS」。但那個數字大多來自很舊的 Ruby，也很少有人交代當時開幾個執行緒，或有沒有先動過 glibc 自己的參數。&lt;/p&gt;
&lt;p&gt;我原本只想確認一件事：&lt;strong&gt;在 Ruby 4.0 上，jemalloc 到底還剩多少效果。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;不過量測環境架都架了，就順手把其他 allocator 一起拉進來 —— tcmalloc、mimalloc、snmalloc 在 Ruby 上幾乎找不到有人實測過的數字，多跑幾組設定的代價只是多等幾個小時。&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/ruby-alloc-bench&quot;&gt;ruby-alloc-bench&lt;/a&gt; 就是這個實驗：同一台機器、同一個 workload、八組 allocator 設定，只用 &lt;code&gt;LD_PRELOAD&lt;/code&gt; 替換，跑滿 5 小時。&lt;/p&gt;</summary>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/ruby-allocator-bench/">&lt;figure&gt;
&lt;img src=&quot;/images/ruby-allocator-bench-og.webp&quot; alt=&quot;換掉 glibc malloc 真的能省 Rails 記憶體嗎？八條 Allocator 實測&quot; height=&quot;669&quot; width=&quot;1200&quot;&gt;&lt;/figure&gt;
&lt;p&gt;「Rails 要不要換 jemalloc」是個講了十年的老問題，標準答案是「換，我們省了 30% RSS」。但那個數字大多來自很舊的 Ruby，也很少有人交代當時開幾個執行緒，或有沒有先動過 glibc 自己的參數。&lt;/p&gt;
&lt;p&gt;我原本只想確認一件事：&lt;strong&gt;在 Ruby 4.0 上，jemalloc 到底還剩多少效果。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;不過量測環境架都架了，就順手把其他 allocator 一起拉進來 —— tcmalloc、mimalloc、snmalloc 在 Ruby 上幾乎找不到有人實測過的數字，多跑幾組設定的代價只是多等幾個小時。&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/ruby-alloc-bench&quot;&gt;ruby-alloc-bench&lt;/a&gt; 就是這個實驗：同一台機器、同一個 workload、八組 allocator 設定，只用 &lt;code&gt;LD_PRELOAD&lt;/code&gt; 替換，跑滿 5 小時。&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;ba-zu-she-ding&quot;&gt;八組設定&lt;a class=&quot;zola-anchor&quot; href=&quot;#ba-zu-she-ding&quot; aria-label=&quot;Anchor link for: ba-zu-she-ding&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;標籤&lt;/th&gt;&lt;th&gt;內容&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;glibc-default&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;ruby:4.0-slim-trixie&lt;/code&gt; 內建的 glibc malloc&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;glibc-arena2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;同上，加 &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;glibc-trim&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt; + &lt;code&gt;MALLOC_TRIM_THRESHOLD_=131072&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;jemalloc&lt;/code&gt;&lt;/td&gt;&lt;td&gt;5.3.1，原始碼編譯&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;tcmalloc&lt;/code&gt;&lt;/td&gt;&lt;td&gt;gperftools 2.18.1，&lt;code&gt;libtcmalloc_minimal.so&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;mimalloc&lt;/code&gt;&lt;/td&gt;&lt;td&gt;v3.5.1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;mimalloc-v2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;v2.5.2&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;snmalloc&lt;/code&gt;&lt;/td&gt;&lt;td&gt;0.7.5&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;會特別放 &lt;code&gt;glibc-arena2&lt;/code&gt; 是有原因的：多數「換 jemalloc 省 30%」的案例，真正的變因很可能是 glibc 的 per-thread arena。glibc 的 arena 數量預設上限是 &lt;code&gt;8 × 核心數&lt;/code&gt;，執行緒一多就各自開 arena，記憶體用量自然被撐開。如果一個環境變數就能追平差距，那答案可能就是那個變數，而不是多一個 C 函式庫的相依套件。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;glibc-trim&lt;/code&gt; 則是另一個常被忽略的旋鈕。RSS 降不下來的 Rails process，有不少情況並不是碎片化，而是 glibc 把 free 掉的記憶體留著不還給 OS，&lt;code&gt;MALLOC_TRIM_THRESHOLD_&lt;/code&gt; 決定的就是它什麼時候放手。&lt;/p&gt;
&lt;h2 id=&quot;jie-guo&quot;&gt;結果&lt;a class=&quot;zola-anchor&quot; href=&quot;#jie-guo&quot; aria-label=&quot;Anchor link for: jie-guo&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Vultr dedicated vCPU、2 核、Debian 13、glibc 2.41、Ruby 4.0.6、YJIT 開啟。1200 秒 × 3 輪 × 8 組設定，約 4800 萬次 request。全程 CPU steal 維持在 0.003%，確認機器的確是獨佔的。&lt;/p&gt;
&lt;figure role=&quot;img&quot; aria-label=&quot;八組 allocator 設定的 RSS 隨時間變化&quot;&gt;
&lt;svg id=&quot;rss-chart&quot; style=&quot;width:100%;height:auto;display:block&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 940 460&quot; font-family=&quot;ui-monospace,SFMono-Regular,Menlo,monospace&quot; font-size=&quot;12&quot;&gt;&lt;style&gt;
#rss-chart [fill=&quot;#e8e8e4&quot;] { fill: currentColor }
#rss-chart [fill=&quot;#9c9ca0&quot;] { fill: currentColor; opacity: .62 }
#rss-chart [stroke=&quot;#3a4050&quot;] { stroke: currentColor; opacity: .18 }
#rss-chart [fill=&quot;#ffffff&quot;] { fill: currentColor; opacity: .05 }
&lt;/style&gt;&lt;text x=&quot;64&quot; y=&quot;22&quot; fill=&quot;#e8e8e4&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot;&gt;Resident set size over 1204s — rails&lt;/text&gt;&lt;rect x=&quot;595.5&quot; y=&quot;40&quot; width=&quot;176.5&quot; height=&quot;364&quot; fill=&quot;#ffffff&quot; opacity=&quot;0.05&quot;/&gt;&lt;text x=&quot;684&quot; y=&quot;32&quot; text-anchor=&quot;middle&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;10&quot;&gt;scoring window&lt;/text&gt;&lt;line x1=&quot;64&quot; y1=&quot;404.0&quot; x2=&quot;772&quot; y2=&quot;404.0&quot; stroke=&quot;#3a4050&quot;/&gt;&lt;text x=&quot;56&quot; y=&quot;408.0&quot; text-anchor=&quot;end&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;11&quot;&gt;108 MB&lt;/text&gt;&lt;line x1=&quot;64&quot; y1=&quot;313.0&quot; x2=&quot;772&quot; y2=&quot;313.0&quot; stroke=&quot;#3a4050&quot;/&gt;&lt;text x=&quot;56&quot; y=&quot;317.0&quot; text-anchor=&quot;end&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;11&quot;&gt;132 MB&lt;/text&gt;&lt;line x1=&quot;64&quot; y1=&quot;222.0&quot; x2=&quot;772&quot; y2=&quot;222.0&quot; stroke=&quot;#3a4050&quot;/&gt;&lt;text x=&quot;56&quot; y=&quot;226.0&quot; text-anchor=&quot;end&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;11&quot;&gt;155 MB&lt;/text&gt;&lt;line x1=&quot;64&quot; y1=&quot;131.0&quot; x2=&quot;772&quot; y2=&quot;131.0&quot; stroke=&quot;#3a4050&quot;/&gt;&lt;text x=&quot;56&quot; y=&quot;135.0&quot; text-anchor=&quot;end&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;11&quot;&gt;179 MB&lt;/text&gt;&lt;line x1=&quot;64&quot; y1=&quot;40.0&quot; x2=&quot;772&quot; y2=&quot;40.0&quot; stroke=&quot;#3a4050&quot;/&gt;&lt;text x=&quot;56&quot; y=&quot;44.0&quot; text-anchor=&quot;end&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;11&quot;&gt;202 MB&lt;/text&gt;&lt;text x=&quot;64&quot; y=&quot;430&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;11&quot;&gt;0s&lt;/text&gt;&lt;text x=&quot;772&quot; y=&quot;430&quot; text-anchor=&quot;end&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;11&quot;&gt;1204s&lt;/text&gt;&lt;text x=&quot;64&quot; y=&quot;448&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;10&quot;&gt;y axis does not start at zero&lt;/text&gt;&lt;polyline fill=&quot;none&quot; stroke=&quot;#2a78d6&quot; stroke-width=&quot;2&quot; stroke-linejoin=&quot;round&quot; points=&quot;64.0,377.9 67.0,309.4 69.9,306.8 72.9,304.4 75.8,299.1 78.8,286.1 81.8,275.7 84.7,267.7 87.6,261.8 90.6,256.6 93.5,253.5 96.5,247.6 99.4,243.2 102.4,239.6 105.3,234.4 108.3,232.9 111.2,230.5 114.2,228.5 117.1,226.1 120.1,224.8 123.0,222.1 126.0,218.7 128.9,215.7 131.9,214.1 134.8,211.8 137.8,210.1 140.7,206.3 143.7,205.2 146.6,203.1 149.6,201.1 152.5,199.0 155.5,197.3 158.4,195.4 161.3,191.2 164.3,189.5 167.3,185.5 170.2,182.5 173.2,180.0 176.1,179.2 179.0,176.7 182.0,175.7 185.0,171.8 187.9,170.1 190.9,169.1 193.8,167.0 196.8,165.6 199.7,165.2 202.7,160.0 205.6,158.4 208.6,157.4 211.5,152.9 214.5,150.2 217.4,149.4 220.3,147.5 223.3,146.2 226.3,145.5 229.2,143.1 232.2,142.4 235.1,141.2 238.0,140.0 241.0,139.4 244.0,138.9 246.9,138.1 249.9,136.3 252.8,135.7 255.7,134.7 258.7,134.1 261.6,133.6 264.6,133.1 267.6,132.2 270.5,130.7 273.5,130.5 276.4,130.2 279.3,130.1 282.3,130.0 285.3,128.8 288.2,128.1 291.2,127.5 294.1,125.9 297.0,125.6 300.0,124.9 303.0,124.6 305.9,124.4 308.9,124.4 311.8,123.6 314.7,123.1 317.7,122.4 320.6,121.6 323.6,121.6 326.6,121.9 329.5,121.6 332.4,121.0 335.4,121.0 338.3,121.2 341.3,120.9 344.3,121.2 347.2,121.1 350.2,121.4 353.1,120.9 356.0,120.5 359.0,120.5 362.0,121.0 364.9,119.7 367.9,118.4 370.8,118.2 373.7,115.8 376.7,115.6 379.7,115.8 382.6,116.1 385.6,116.6 388.5,116.6 391.4,116.3 394.4,116.3 397.4,116.3 400.3,116.3 403.3,116.3 406.2,115.7 409.1,115.7 412.1,115.4 415.0,115.4 418.0,114.4 421.0,114.7 423.9,114.7 426.9,114.9 429.8,114.7 432.7,113.8 435.7,113.5 438.7,113.5 441.6,113.7 444.6,113.7 447.5,113.5 450.4,114.0 453.4,113.7 456.3,113.7 459.3,113.7 462.3,113.7 465.2,114.0 468.1,114.0 471.1,113.7 474.0,113.7 477.0,113.5 480.0,114.0 482.9,113.5 485.8,113.2 488.8,113.5 491.7,113.5 494.7,113.5 497.6,113.2 500.6,113.2 503.6,112.9 506.5,113.1 509.4,113.1 512.4,113.1 515.3,113.1 518.3,112.9 521.3,112.4 524.2,112.2 527.1,112.6 530.1,112.9 533.0,112.9 536.0,112.6 538.9,112.2 541.8,112.2 544.8,112.2 547.8,112.4 550.7,112.6 553.7,112.2 556.6,112.4 559.5,112.2 562.5,112.2 565.4,112.4 568.4,112.2 571.4,111.9 574.3,111.7 577.3,112.2 580.2,112.4 583.1,112.4 586.1,112.4 589.1,112.2 592.0,112.1 595.0,112.4 597.9,112.1 600.8,111.7 603.8,111.7 606.7,111.9 609.7,111.9 612.7,111.9 615.6,111.7 618.5,111.7 621.5,111.5 624.4,111.5 627.4,111.5 630.3,110.7 633.3,111.0 636.2,111.2 639.2,111.2 642.1,111.2 645.1,111.2 648.0,111.2 651.0,111.2 653.9,111.0 656.9,111.2 659.8,111.5 662.8,111.5 665.7,111.4 668.7,111.7 671.6,112.2 674.5,111.7 677.5,111.7 680.5,111.4 683.4,110.6 686.4,110.9 689.3,110.9 692.2,110.9 695.2,110.6 698.2,110.9 701.1,110.9 704.1,110.6 707.0,110.4 709.9,110.4 712.9,110.4 715.8,110.4 718.8,110.6 721.8,110.6 724.7,110.9 727.7,110.9 730.6,110.6 733.5,110.6 736.5,110.9 739.5,110.6 742.4,110.4 745.4,110.2 748.3,110.2 751.2,110.4 754.2,109.9 757.2,109.7 760.1,110.2 763.1,110.2 766.0,110.2 768.9,109.9 771.9,110.4&quot;/&gt;&lt;line x1=&quot;782&quot; y1=&quot;44&quot; x2=&quot;800&quot; y2=&quot;44&quot; stroke=&quot;#2a78d6&quot; stroke-width=&quot;3&quot; stroke-linecap=&quot;round&quot;/&gt;&lt;text x=&quot;806&quot; y=&quot;48&quot; fill=&quot;#e8e8e4&quot; font-size=&quot;11&quot;&gt;glibc-default&lt;/text&gt;&lt;text x=&quot;806&quot; y=&quot;60&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;10&quot;&gt;183.9 MB&lt;/text&gt;&lt;polyline fill=&quot;none&quot; stroke=&quot;#eb6834&quot; stroke-width=&quot;2&quot; stroke-linejoin=&quot;round&quot; points=&quot;64.0,378.9 67.0,324.7 69.9,321.2 72.9,319.7 75.8,318.5 78.8,317.6 81.8,313.3 84.7,301.0 87.6,289.9 90.6,283.4 93.5,279.2 96.5,275.9 99.4,274.1 102.4,271.9 105.3,269.2 108.3,264.9 111.2,261.6 114.2,259.6 117.1,256.9 120.1,253.1 123.1,249.3 126.0,246.9 128.9,245.0 131.9,242.5 134.8,240.9 137.8,239.0 140.7,234.3 143.6,231.1 146.6,229.6 149.6,227.8 152.5,225.1 155.5,224.0 158.4,220.0 161.4,218.8 164.3,218.0 167.3,216.5 170.2,214.7 173.2,212.7 176.1,212.5 179.0,212.0 182.0,210.6 185.0,208.7 187.9,206.6 190.9,205.2 193.8,203.3 196.8,202.5 199.8,199.6 202.7,198.7 205.6,197.3 208.6,194.8 211.5,191.9 214.5,190.6 217.5,188.7 220.4,186.4 223.3,185.7 226.3,184.7 229.2,183.2 232.2,182.7 235.2,182.0 238.1,181.8 241.0,181.5 244.0,180.6 246.9,179.4 249.9,178.5 252.9,176.4 255.8,174.7 258.7,173.0 261.7,171.7 264.6,171.0 267.6,170.0 270.6,168.5 273.5,166.7 276.5,165.9 279.4,165.2 282.3,164.9 285.3,164.2 288.3,162.1 291.2,161.6 294.2,160.9 297.1,160.2 300.0,159.2 303.0,157.8 305.9,157.8 308.9,157.2 311.9,156.7 314.8,155.4 317.7,155.1 320.7,155.1 323.7,154.6 326.6,153.0 329.6,152.6 332.5,152.2 335.4,151.7 338.4,151.5 341.3,151.2 344.3,150.4 347.3,150.1 350.2,149.9 353.2,149.6 356.1,149.6 359.0,149.6 362.0,147.5 364.9,146.7 367.9,146.2 370.9,146.2 373.8,146.0 376.7,145.9 379.7,145.2 382.6,145.2 385.6,144.9 388.5,144.9 391.5,144.9 394.4,144.9 397.4,143.7 400.3,143.6 403.3,143.4 406.2,142.8 409.1,142.6 412.1,142.5 415.1,142.5 418.0,141.4 421.0,141.6 423.9,141.6 426.9,141.6 429.9,141.8 432.8,142.1 435.7,141.6 438.7,141.8 441.6,142.1 444.6,142.1 447.5,142.1 450.4,142.1 453.4,141.6 456.4,141.6 459.3,141.3 462.3,141.3 465.2,141.3 468.1,141.1 471.1,141.1 474.0,141.1 477.0,141.1 480.0,140.6 482.9,140.6 485.8,140.6 488.8,140.8 491.7,140.8 494.7,140.6 497.7,140.6 500.6,140.6 503.6,139.9 506.5,140.1 509.5,140.3 512.4,140.6 515.4,140.6 518.3,140.6 521.3,140.6 524.3,140.3 527.2,140.6 530.1,140.8 533.1,140.6 536.0,140.8 539.0,140.8 542.0,140.6 544.9,140.1 547.8,140.4 550.8,140.4 553.7,140.1 556.7,140.3 559.6,140.3 562.5,140.1 565.5,139.6 568.5,140.1 571.4,140.1 574.4,139.6 577.3,139.8 580.3,140.3 583.2,139.7 586.1,139.7 589.1,139.4 592.1,139.4 595.0,139.7 598.0,139.2 600.9,139.4 603.9,139.7 606.8,139.7 609.8,140.2 612.7,140.4 615.7,140.4 618.6,138.9 621.6,138.6 624.5,138.4 627.5,138.6 630.4,138.4 633.4,138.6 636.3,138.6 639.2,139.1 642.2,139.1 645.2,138.9 648.1,138.9 651.1,138.9 654.0,138.9 657.0,138.4 659.9,138.1 662.9,137.9 665.8,137.6 668.8,137.2 671.7,137.6 674.7,137.4 677.6,137.4 680.5,137.7 683.5,137.7 686.5,137.7 689.4,137.2 692.4,137.4 695.3,137.2 698.2,137.2 701.2,137.2 704.2,137.2 707.1,137.7 710.1,137.2 713.0,137.4 715.9,135.6 718.9,135.8 721.8,136.0 724.8,135.8 727.8,135.5 730.7,135.0 733.7,135.0 736.6,135.3 739.5,135.0 742.5,135.5 745.4,135.5 748.4,135.5 751.4,134.8 754.3,135.1 757.2,134.8 760.2,135.1 763.1,134.8 766.1,134.6 769.1,134.8 772.0,135.0&quot;/&gt;&lt;line x1=&quot;782&quot; y1=&quot;74&quot; x2=&quot;800&quot; y2=&quot;74&quot; stroke=&quot;#eb6834&quot; stroke-width=&quot;3&quot; stroke-linecap=&quot;round&quot;/&gt;&lt;text x=&quot;806&quot; y=&quot;78&quot; fill=&quot;#e8e8e4&quot; font-size=&quot;11&quot;&gt;glibc-arena2&lt;/text&gt;&lt;text x=&quot;806&quot; y=&quot;90&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;10&quot;&gt;177.0 MB&lt;/text&gt;&lt;polyline fill=&quot;none&quot; stroke=&quot;#1baf7a&quot; stroke-width=&quot;2&quot; stroke-linejoin=&quot;round&quot; points=&quot;64.0,377.9 67.0,322.3 69.9,318.8 72.9,310.2 75.8,294.2 78.8,286.0 81.7,282.6 84.6,280.3 87.6,277.9 90.6,274.3 93.5,271.3 96.5,267.9 99.4,261.2 102.3,260.3 105.3,256.5 108.2,253.5 111.2,250.5 114.2,249.2 117.1,245.9 120.1,245.1 123.0,241.8 125.9,241.3 128.9,239.4 131.9,234.7 134.8,235.8 137.8,229.2 140.7,229.2 143.6,227.1 146.6,224.1 149.5,223.1 152.5,220.5 155.5,214.0 158.4,213.6 161.3,210.6 164.3,211.1 167.2,210.4 170.2,208.8 173.1,207.0 176.0,205.4 179.0,203.1 182.0,201.6 184.9,200.8 187.9,200.3 190.8,199.4 193.8,197.5 196.7,196.4 199.7,193.1 202.6,191.4 205.6,190.7 208.5,189.5 211.5,186.1 214.4,183.4 217.4,181.7 220.3,180.9 223.3,178.3 226.2,176.8 229.2,174.0 232.1,172.1 235.1,170.9 238.0,169.0 241.0,165.7 243.9,165.3 246.9,164.1 249.9,161.2 252.8,161.5 255.7,161.3 258.7,160.5 261.6,160.1 264.6,159.0 267.6,159.1 270.5,156.3 273.5,155.2 276.4,154.6 279.3,153.3 282.3,153.9 285.2,150.0 288.2,151.4 291.2,151.0 294.1,150.7 297.0,150.0 300.0,150.2 302.9,150.4 305.9,148.3 308.8,147.7 311.8,146.6 314.7,145.4 317.7,145.3 320.6,146.6 323.6,146.6 326.5,146.7 329.5,145.2 332.4,146.0 335.4,146.2 338.3,143.7 341.3,144.0 344.2,143.6 347.2,144.2 350.2,145.6 353.1,145.2 356.0,143.4 359.0,142.5 361.9,143.3 364.9,144.9 367.9,145.4 370.8,145.3 373.7,145.3 376.7,145.2 379.6,146.1 382.6,146.2 385.5,144.5 388.5,144.3 391.4,145.3 394.4,144.8 397.3,144.4 400.3,144.1 403.2,144.0 406.1,144.3 409.1,143.5 412.1,144.1 415.0,144.1 418.0,143.8 420.9,141.8 423.9,141.3 426.9,141.2 429.8,142.1 432.7,142.4 435.7,142.1 438.6,143.8 441.6,143.0 444.5,143.2 447.4,142.7 450.4,143.2 453.4,143.0 456.3,141.6 459.3,143.0 462.2,142.8 465.1,142.1 468.1,142.4 471.1,142.6 474.0,142.6 477.0,142.0 479.9,141.3 482.8,141.1 485.8,141.7 488.8,141.5 491.7,141.9 494.7,142.0 497.6,142.7 500.6,142.6 503.5,143.0 506.4,142.1 509.4,140.5 512.4,141.0 515.3,141.5 518.3,142.1 521.2,142.7 524.1,143.0 527.1,143.0 530.0,142.8 533.0,141.7 536.0,143.0 538.9,142.8 541.8,142.5 544.8,143.3 547.7,143.0 550.7,142.8 553.7,142.7 556.6,141.9 559.5,140.6 562.5,140.6 565.4,141.0 568.4,140.9 571.4,140.9 574.3,140.7 577.3,141.3 580.2,141.0 583.1,140.3 586.1,140.6 589.1,141.8 592.0,141.8 595.0,141.8 597.9,141.2 600.8,140.7 603.8,141.4 606.7,141.5 609.7,139.8 612.7,140.8 615.6,141.7 618.5,141.5 621.5,141.8 624.4,140.2 627.4,141.1 630.3,140.4 633.3,141.3 636.2,140.8 639.2,141.3 642.1,140.9 645.1,141.0 648.0,140.7 651.0,140.4 654.0,140.6 656.9,140.1 659.8,139.2 662.8,140.2 665.7,140.6 668.7,140.2 671.6,139.5 674.5,140.2 677.5,140.4 680.5,140.7 683.4,140.2 686.4,140.2 689.3,137.9 692.2,136.9 695.2,137.2 698.2,139.2 701.1,138.9 704.1,139.9 707.0,139.5 709.9,140.0 712.9,138.8 715.8,140.2 718.8,138.4 721.7,138.8 724.7,138.6 727.7,139.0 730.6,139.2 733.5,139.3 736.5,139.6 739.4,139.9 742.4,137.8 745.3,136.8 748.3,137.5 751.2,137.7 754.2,136.9 757.1,138.1 760.1,138.1 763.1,138.3 766.0,138.3 768.9,138.3 771.9,135.3&quot;/&gt;&lt;line x1=&quot;782&quot; y1=&quot;104&quot; x2=&quot;800&quot; y2=&quot;104&quot; stroke=&quot;#1baf7a&quot; stroke-width=&quot;3&quot; stroke-linecap=&quot;round&quot;/&gt;&lt;text x=&quot;806&quot; y=&quot;108&quot; fill=&quot;#e8e8e4&quot; font-size=&quot;11&quot;&gt;glibc-trim&lt;/text&gt;&lt;text x=&quot;806&quot; y=&quot;120&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;10&quot;&gt;176.4 MB&lt;/text&gt;&lt;polyline fill=&quot;none&quot; stroke=&quot;#eda100&quot; stroke-width=&quot;2&quot; stroke-linejoin=&quot;round&quot; points=&quot;64.0,377.7 67.1,308.7 70.0,298.3 72.9,292.3 75.9,288.1 78.8,283.7 81.8,281.8 84.8,278.4 87.7,277.0 90.6,272.9 93.6,271.0 96.5,266.8 99.5,262.6 102.4,260.6 105.3,258.5 108.3,254.0 111.3,252.4 114.2,248.8 117.2,246.0 120.1,244.0 123.1,239.4 126.0,237.0 128.9,234.2 131.9,232.8 134.9,232.2 137.8,228.4 140.8,227.6 143.7,222.7 146.6,223.0 149.6,224.0 152.5,221.9 155.5,219.0 158.5,218.1 161.4,216.0 164.3,213.2 167.3,210.3 170.2,207.8 173.2,212.8 176.2,209.6 179.1,206.3 182.0,203.2 185.0,202.0 187.9,201.6 190.9,200.9 193.8,198.3 196.8,198.3 199.8,196.3 202.7,190.7 205.6,191.5 208.6,190.4 211.5,189.8 214.5,187.4 217.5,186.4 220.4,183.8 223.3,181.4 226.3,179.8 229.2,180.4 232.2,178.6 235.2,175.2 238.1,172.9 241.0,173.3 244.0,172.7 246.9,170.3 249.9,168.7 252.8,167.4 255.8,167.6 258.7,165.8 261.7,166.9 264.6,165.9 267.6,166.0 270.6,164.4 273.5,164.4 276.5,163.0 279.4,161.2 282.3,161.8 285.3,162.6 288.2,162.2 291.2,161.4 294.2,160.8 297.1,158.9 300.0,158.2 303.0,158.9 305.9,159.0 308.9,160.4 311.9,158.0 314.8,156.5 317.7,157.0 320.7,156.2 323.6,154.6 326.6,152.9 329.6,156.7 332.5,155.0 335.4,154.6 338.4,155.7 341.3,151.7 344.3,153.5 347.2,155.2 350.2,152.0 353.2,154.1 356.1,153.4 359.0,154.3 362.0,152.7 364.9,151.6 367.9,153.6 370.9,153.7 373.8,153.6 376.7,151.0 379.7,150.2 382.6,152.3 385.6,151.9 388.6,153.9 391.5,155.7 394.4,154.8 397.4,154.7 400.3,155.7 403.3,152.2 406.3,150.4 409.2,152.7 412.1,153.4 415.1,150.7 418.0,151.8 421.0,153.8 423.9,150.3 426.9,152.4 429.9,150.7 432.8,150.8 435.7,150.2 438.7,150.0 441.6,149.6 444.6,151.1 447.6,149.5 450.5,149.9 453.4,150.7 456.4,150.0 459.3,149.6 462.3,147.9 465.2,148.0 468.2,150.0 471.1,150.4 474.1,148.6 477.0,152.9 480.0,150.3 482.9,150.0 485.8,152.0 488.8,150.1 491.8,150.5 494.7,151.7 497.7,150.8 500.6,149.4 503.6,151.7 506.5,151.0 509.5,152.4 512.4,151.7 515.4,149.7 518.3,149.2 521.3,149.2 524.2,151.0 527.1,150.3 530.1,150.0 533.1,149.5 536.0,148.7 539.0,150.6 541.9,147.6 544.8,147.5 547.8,150.0 550.8,148.2 553.7,150.7 556.7,152.0 559.6,150.3 562.5,149.9 565.5,146.6 568.4,148.0 571.4,147.1 574.4,150.1 577.3,147.9 580.3,148.7 583.2,149.3 586.1,148.0 589.1,149.8 592.1,148.4 595.0,150.5 598.0,147.3 600.9,146.7 603.9,151.3 606.8,147.5 609.8,149.3 612.7,148.2 615.7,148.7 618.6,148.1 621.5,147.1 624.5,147.3 627.5,146.8 630.4,146.4 633.4,148.5 636.3,149.3 639.2,149.9 642.2,148.0 645.2,146.8 648.1,146.0 651.1,148.5 654.0,148.6 657.0,147.2 659.9,147.5 662.8,144.6 665.8,145.9 668.8,146.4 671.7,146.2 674.7,146.7 677.6,147.3 680.5,148.1 683.5,147.8 686.5,146.6 689.4,149.4 692.4,150.4 695.3,147.8 698.2,146.6 701.2,146.7 704.2,148.2 707.1,148.1 710.1,147.3 713.0,147.0 715.9,141.5 718.9,146.6 721.8,145.9 724.8,145.7 727.7,147.7 730.7,148.7 733.7,148.9 736.6,146.3 739.5,145.9 742.5,147.1 745.4,144.9 748.4,146.5 751.4,147.0 754.3,146.9 757.2,147.2 760.2,145.1 763.1,146.8 766.1,147.6 769.0,146.4 771.9,146.9&quot;/&gt;&lt;line x1=&quot;782&quot; y1=&quot;134&quot; x2=&quot;800&quot; y2=&quot;134&quot; stroke=&quot;#eda100&quot; stroke-width=&quot;3&quot; stroke-linecap=&quot;round&quot;/&gt;&lt;text x=&quot;806&quot; y=&quot;138&quot; fill=&quot;#e8e8e4&quot; font-size=&quot;11&quot;&gt;jemalloc&lt;/text&gt;&lt;text x=&quot;806&quot; y=&quot;150&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;10&quot;&gt;174.5 MB&lt;/text&gt;&lt;polyline fill=&quot;none&quot; stroke=&quot;#e87ba4&quot; stroke-width=&quot;2&quot; stroke-linejoin=&quot;round&quot; points=&quot;64.0,365.5 67.1,295.4 70.0,275.0 72.9,264.3 75.9,255.8 78.8,250.9 81.8,242.3 84.8,237.3 87.7,235.5 90.6,232.1 93.6,222.6 96.5,214.7 99.5,208.3 102.5,206.7 105.4,203.6 108.3,198.8 111.3,194.0 114.2,191.6 117.2,190.0 120.1,184.5 123.1,182.1 126.1,180.5 129.0,179.7 131.9,178.7 134.9,177.7 137.8,174.6 140.8,164.9 143.8,162.3 146.7,161.5 149.6,158.0 152.6,154.4 155.5,152.9 158.5,151.0 161.5,149.5 164.4,144.2 167.3,142.0 170.3,140.2 173.2,137.1 176.2,135.9 179.2,133.9 182.1,130.9 185.0,129.5 188.0,128.0 190.9,126.7 193.9,125.1 196.9,124.1 199.8,122.9 202.8,122.2 205.7,120.7 208.6,119.2 211.6,118.5 214.5,117.7 217.5,115.9 220.5,114.6 223.4,112.5 226.3,111.8 229.3,111.0 232.2,109.9 235.2,108.0 238.2,107.3 241.1,105.0 244.0,104.2 247.0,103.9 250.0,103.2 252.9,102.7 255.9,102.2 258.8,101.9 261.7,101.4 264.7,100.7 267.6,99.9 270.6,99.6 273.6,99.4 276.5,98.6 279.5,95.6 282.4,95.2 285.3,94.8 288.3,93.9 291.2,92.7 294.2,92.4 297.2,92.1 300.1,91.4 303.0,91.3 306.0,90.5 308.9,90.5 311.9,89.6 314.8,89.4 317.8,89.1 320.7,87.9 323.7,86.6 326.6,86.6 329.6,85.4 332.5,85.1 335.4,84.9 338.4,84.6 341.4,84.5 344.3,84.5 347.3,84.0 350.2,83.5 353.2,83.0 356.1,83.0 359.1,83.1 362.0,83.4 365.0,83.1 367.9,82.9 370.9,82.6 373.8,82.9 376.7,83.1 379.7,83.1 382.6,82.3 385.6,82.3 388.6,82.3 391.5,82.6 394.4,82.0 397.4,82.0 400.3,81.8 403.3,81.8 406.3,81.6 409.2,80.9 412.1,80.7 415.1,80.5 418.0,79.8 421.0,79.5 424.0,79.0 426.9,79.5 429.9,79.3 432.8,79.1 435.7,78.8 438.7,78.8 441.6,78.8 444.6,78.8 447.6,76.4 450.5,76.4 453.4,75.9 456.4,75.9 459.3,76.3 462.3,76.5 465.2,76.8 468.2,76.5 471.1,76.5 474.1,76.5 477.0,76.5 480.0,76.5 482.9,76.7 485.8,76.7 488.8,76.7 491.8,76.3 494.7,76.3 497.7,76.3 500.6,76.3 503.6,76.3 506.5,75.8 509.5,76.1 512.4,74.7 515.4,73.7 518.3,73.5 521.3,73.5 524.2,73.7 527.2,74.0 530.1,73.7 533.1,73.4 536.0,73.4 539.0,73.1 541.9,73.3 544.8,73.5 547.8,72.8 550.8,73.0 553.7,73.0 556.7,72.8 559.6,72.5 562.5,73.0 565.5,73.0 568.4,73.2 571.4,72.9 574.4,73.2 577.3,73.4 580.3,73.1 583.2,72.6 586.1,72.3 589.1,71.7 592.0,71.7 595.0,71.9 598.0,71.9 600.9,71.9 603.8,71.9 606.8,71.6 609.7,71.6 612.7,71.1 615.7,71.4 618.6,71.6 621.5,71.4 624.5,71.6 627.4,71.8 630.4,71.3 633.3,69.9 636.3,69.6 639.2,69.8 642.2,69.6 645.1,68.9 648.1,69.1 651.0,69.3 654.0,69.8 657.0,69.6 659.9,69.6 662.8,69.3 665.8,69.3 668.7,69.3 671.7,69.3 674.7,69.3 677.6,69.3 680.5,69.1 683.5,69.0 686.4,69.0 689.4,68.5 692.4,68.2 695.3,68.5 698.2,68.0 701.2,68.7 704.1,68.5 707.1,68.4 710.0,68.2 713.0,68.2 715.9,67.8 718.9,68.2 721.8,68.0 724.8,68.0 727.7,68.0 730.7,67.7 733.7,67.7 736.6,67.9 739.5,68.2 742.5,67.9 745.4,67.9 748.4,67.9 751.4,67.5 754.3,67.5 757.2,67.7 760.2,67.9 763.2,67.4 766.1,67.7 769.1,67.9 772.0,67.7&quot;/&gt;&lt;line x1=&quot;782&quot; y1=&quot;164&quot; x2=&quot;800&quot; y2=&quot;164&quot; stroke=&quot;#e87ba4&quot; stroke-width=&quot;3&quot; stroke-linecap=&quot;round&quot;/&gt;&lt;text x=&quot;806&quot; y=&quot;168&quot; fill=&quot;#e8e8e4&quot; font-size=&quot;11&quot;&gt;tcmalloc&lt;/text&gt;&lt;text x=&quot;806&quot; y=&quot;180&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;10&quot;&gt;194.8 MB&lt;/text&gt;&lt;polyline fill=&quot;none&quot; stroke=&quot;#3cb54a&quot; stroke-width=&quot;2&quot; stroke-linejoin=&quot;round&quot; points=&quot;64.0,357.8 67.0,273.6 70.0,256.3 72.9,245.3 75.9,245.6 78.8,236.3 81.8,231.3 84.7,229.7 87.7,227.4 90.6,223.2 93.6,219.5 96.5,214.3 99.5,210.7 102.5,208.2 105.4,205.4 108.3,203.4 111.3,202.2 114.2,200.4 117.2,197.1 120.1,192.8 123.1,191.1 126.0,188.3 129.0,186.0 131.9,186.2 134.9,184.0 137.8,182.3 140.8,179.2 143.7,178.5 146.6,176.8 149.6,177.5 152.6,173.0 155.5,168.3 158.5,164.7 161.4,162.3 164.3,161.9 167.3,158.3 170.3,154.9 173.2,152.7 176.2,153.8 179.1,150.5 182.0,147.9 185.0,150.6 187.9,148.7 190.9,147.2 193.9,144.0 196.8,141.3 199.8,141.1 202.7,138.7 205.6,135.6 208.6,135.2 211.5,132.7 214.5,132.0 217.5,131.2 220.4,130.6 223.3,129.3 226.3,127.2 229.2,125.9 232.2,127.9 235.1,122.2 238.1,120.4 241.0,116.5 244.0,118.0 246.9,118.1 249.9,114.6 252.8,116.1 255.7,113.5 258.7,114.5 261.7,111.4 264.6,110.9 267.6,107.7 270.5,104.8 273.5,105.6 276.4,108.2 279.3,108.3 282.3,103.9 285.3,102.4 288.2,100.3 291.2,100.8 294.1,107.7 297.0,104.9 300.0,102.9 302.9,100.3 305.9,102.9 308.9,101.8 311.8,100.8 314.7,99.4 317.7,96.3 320.6,95.9 323.6,96.5 326.6,94.5 329.5,94.2 332.4,96.0 335.4,100.0 338.3,99.4 341.3,101.6 344.2,98.8 347.2,99.1 350.2,96.4 353.1,95.8 356.0,95.8 359.0,95.0 361.9,93.9 364.9,94.8 367.8,94.5 370.8,93.2 373.7,93.0 376.7,93.8 379.6,96.9 382.6,94.2 385.5,92.6 388.5,90.2 391.4,90.5 394.4,91.7 397.3,91.4 400.3,91.8 403.2,93.5 406.1,95.4 409.1,92.6 412.0,91.3 415.0,92.4 418.0,91.6 420.9,89.3 423.9,94.1 426.8,93.0 429.7,92.1 432.7,90.1 435.6,91.2 438.6,91.6 441.6,90.7 444.5,92.5 447.4,93.5 450.4,91.8 453.3,88.3 456.3,84.6 459.3,85.6 462.2,87.3 465.1,87.7 468.1,86.4 471.0,85.8 474.0,87.6 477.0,86.3 479.9,89.9 482.8,91.5 485.8,91.7 488.7,96.2 491.7,92.3 494.6,94.1 497.6,91.5 500.5,91.0 503.5,91.1 506.4,90.5 509.4,90.2 512.3,91.3 515.3,90.5 518.2,89.8 521.1,91.4 524.1,87.8 527.0,89.8 530.0,87.7 533.0,89.2 535.9,91.0 538.8,89.2 541.8,92.1 544.7,89.9 547.7,89.6 550.7,87.7 553.6,86.1 556.5,86.1 559.5,85.1 562.4,86.7 565.4,87.8 568.3,88.4 571.3,89.9 574.3,89.0 577.2,87.4 580.1,86.2 583.1,85.1 586.1,85.5 589.0,85.7 592.0,85.4 594.9,85.3 597.8,87.0 600.8,86.9 603.8,84.5 606.7,85.7 609.7,86.4 612.6,87.5 615.5,87.3 618.5,86.9 621.4,85.8 624.4,86.0 627.3,85.4 630.2,86.0 633.2,87.5 636.2,86.3 639.1,86.3 642.1,86.2 645.0,86.2 648.0,86.6 651.0,86.0 653.9,84.7 656.8,83.1 659.8,83.8 662.7,84.9 665.7,85.5 668.6,84.1 671.5,84.4 674.5,84.3 677.5,86.1 680.4,85.2 683.4,84.5 686.3,84.8 689.2,83.6 692.2,84.6 695.1,83.9 698.1,83.1 701.1,81.5 704.0,82.2 706.9,81.4 709.9,81.6 712.8,84.4 715.8,84.6 718.8,85.0 721.7,84.7 724.7,83.3 727.6,82.8 730.5,82.5 733.5,81.6 736.5,80.6 739.4,81.9 742.4,82.2 745.3,82.3 748.2,81.3 751.2,80.5 754.1,79.3 757.1,82.1 760.1,83.6 763.0,81.1 765.9,84.4 768.9,83.4 771.8,84.5&quot;/&gt;&lt;line x1=&quot;782&quot; y1=&quot;194&quot; x2=&quot;800&quot; y2=&quot;194&quot; stroke=&quot;#3cb54a&quot; stroke-width=&quot;3&quot; stroke-linecap=&quot;round&quot;/&gt;&lt;text x=&quot;806&quot; y=&quot;198&quot; fill=&quot;#e8e8e4&quot; font-size=&quot;11&quot;&gt;mimalloc&lt;/text&gt;&lt;text x=&quot;806&quot; y=&quot;210&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;10&quot;&gt;190.7 MB&lt;/text&gt;&lt;polyline fill=&quot;none&quot; stroke=&quot;#8b7ce8&quot; stroke-width=&quot;2&quot; stroke-linejoin=&quot;round&quot; points=&quot;64.0,378.4 67.0,303.4 70.0,299.1 72.9,293.4 75.9,284.1 78.8,280.1 81.8,277.8 84.7,276.4 87.7,273.1 90.6,270.5 93.6,265.6 96.5,260.1 99.5,260.2 102.4,257.3 105.4,254.8 108.3,253.5 111.3,249.0 114.2,245.7 117.2,243.1 120.1,241.7 123.1,240.2 126.1,236.0 129.0,235.7 131.9,234.0 134.9,231.2 137.8,234.3 140.8,234.2 143.7,231.0 146.6,228.6 149.6,224.8 152.6,226.0 155.5,220.1 158.5,222.0 161.4,217.0 164.3,218.9 167.3,213.6 170.2,213.4 173.2,214.7 176.2,208.3 179.1,211.0 182.0,206.2 185.0,203.7 187.9,205.4 190.9,200.9 193.9,199.7 196.8,198.7 199.8,198.3 202.7,196.6 205.6,193.1 208.6,193.4 211.6,189.0 214.5,182.4 217.5,187.4 220.4,184.1 223.3,181.8 226.3,180.6 229.3,181.2 232.2,176.5 235.2,177.1 238.1,177.6 241.0,177.4 244.0,172.6 246.9,175.0 249.9,171.5 252.9,173.4 255.8,174.1 258.7,169.5 261.7,167.8 264.6,170.1 267.6,166.4 270.5,163.9 273.5,164.4 276.5,160.9 279.4,164.5 282.3,161.7 285.3,161.2 288.2,165.2 291.2,164.7 294.1,161.4 297.1,161.5 300.0,159.4 303.0,160.7 305.9,159.6 308.9,156.0 311.8,157.9 314.7,158.1 317.7,156.8 320.7,156.7 323.6,153.6 326.6,157.6 329.5,154.1 332.4,157.6 335.4,151.8 338.4,157.0 341.3,155.1 344.3,153.9 347.2,150.7 350.2,153.5 353.1,151.5 356.1,149.8 359.0,154.2 362.0,152.7 364.9,149.6 367.9,150.8 370.8,153.3 373.7,151.2 376.7,155.1 379.7,150.5 382.6,152.4 385.6,148.7 388.5,148.4 391.4,151.0 394.4,150.6 397.3,147.9 400.3,153.1 403.3,146.7 406.2,150.6 409.1,152.3 412.1,150.3 415.0,151.1 418.0,148.9 421.0,149.9 423.9,147.0 426.9,148.7 429.8,145.5 432.7,151.3 435.7,151.3 438.7,148.3 441.6,150.4 444.6,150.1 447.5,148.7 450.4,150.3 453.4,150.0 456.4,151.9 459.3,146.7 462.3,148.8 465.2,149.5 468.1,151.5 471.1,148.8 474.0,150.3 477.0,150.6 480.0,149.5 482.9,150.8 485.8,147.9 488.8,148.3 491.7,149.3 494.7,147.4 497.7,148.2 500.6,148.7 503.6,149.1 506.5,150.4 509.4,149.8 512.4,146.5 515.3,149.4 518.3,147.9 521.3,150.0 524.2,146.7 527.1,145.7 530.1,148.3 533.0,148.4 536.0,149.8 539.0,150.1 541.9,149.4 544.8,147.1 547.8,148.8 550.7,148.1 553.7,150.0 556.7,146.8 559.6,150.0 562.5,146.7 565.5,147.4 568.4,148.2 571.4,145.9 574.3,149.3 577.3,149.8 580.3,146.9 583.2,144.4 586.1,148.0 589.1,148.7 592.0,146.6 595.0,146.2 597.9,148.3 600.9,147.5 603.8,149.5 606.8,148.5 609.7,147.5 612.7,148.6 615.6,147.6 618.6,145.3 621.5,149.1 624.5,147.0 627.4,150.3 630.4,146.2 633.3,147.8 636.2,145.7 639.2,148.7 642.2,149.9 645.1,144.5 648.1,144.6 651.0,143.4 654.0,148.2 656.9,150.8 659.9,146.5 662.8,146.1 665.8,146.2 668.7,147.0 671.7,147.0 674.7,145.4 677.6,148.8 680.5,147.2 683.5,143.5 686.4,145.7 689.4,148.7 692.3,147.4 695.3,147.9 698.2,144.5 701.2,146.1 704.1,146.5 707.1,141.9 710.0,147.5 712.9,146.4 715.9,145.5 718.9,146.8 721.8,144.1 724.8,147.1 727.7,142.3 730.7,149.3 733.6,144.6 736.5,143.8 739.5,144.7 742.4,144.9 745.4,146.7 748.4,149.3 751.3,147.1 754.2,146.9 757.2,147.5 760.1,147.4 763.1,143.3 766.1,146.8 769.0,145.4 771.9,144.4&quot;/&gt;&lt;line x1=&quot;782&quot; y1=&quot;224&quot; x2=&quot;800&quot; y2=&quot;224&quot; stroke=&quot;#8b7ce8&quot; stroke-width=&quot;3&quot; stroke-linecap=&quot;round&quot;/&gt;&lt;text x=&quot;806&quot; y=&quot;228&quot; fill=&quot;#e8e8e4&quot; font-size=&quot;11&quot;&gt;mimalloc-v2&lt;/text&gt;&lt;text x=&quot;806&quot; y=&quot;240&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;10&quot;&gt;174.6 MB&lt;/text&gt;&lt;polyline fill=&quot;none&quot; stroke=&quot;#e34948&quot; stroke-width=&quot;2&quot; stroke-linejoin=&quot;round&quot; points=&quot;64.0,354.5 67.0,256.7 69.9,238.2 72.9,233.6 75.9,227.3 78.8,220.8 81.8,216.9 84.7,211.7 87.6,208.3 90.6,204.4 93.6,202.2 96.5,198.7 99.5,196.7 102.4,193.8 105.3,191.4 108.3,187.7 111.3,183.3 114.2,180.0 117.2,178.6 120.1,176.1 123.1,173.5 126.0,171.0 129.0,169.6 131.9,167.0 134.9,164.2 137.8,160.6 140.8,158.1 143.7,154.7 146.6,153.4 149.6,151.4 152.5,150.7 155.5,149.0 158.5,147.2 161.4,145.1 164.3,143.8 167.3,142.2 170.2,141.3 173.2,138.3 176.2,134.8 179.1,132.5 182.0,130.5 185.0,129.3 187.9,126.4 190.9,124.7 193.8,123.2 196.8,122.2 199.8,119.3 202.7,117.4 205.6,115.7 208.6,114.7 211.6,113.9 214.5,112.4 217.5,110.5 220.4,109.7 223.3,108.7 226.3,107.8 229.2,106.8 232.2,106.3 235.2,105.0 238.1,103.8 241.0,102.6 244.0,99.2 246.9,97.0 249.9,96.4 252.9,94.5 255.8,93.8 258.7,92.2 261.7,90.4 264.6,88.9 267.6,88.1 270.6,87.1 273.5,86.9 276.5,86.6 279.4,86.6 282.3,86.6 285.3,86.6 288.3,86.1 291.2,85.7 294.2,85.7 297.1,85.5 300.0,84.5 303.0,83.8 305.9,83.8 308.9,82.8 311.9,81.8 314.8,81.6 317.7,80.8 320.7,79.9 323.6,78.6 326.6,78.3 329.5,78.0 332.5,78.0 335.4,78.0 338.4,76.8 341.3,76.6 344.3,76.5 347.3,76.5 350.2,76.5 353.2,76.5 356.1,76.1 359.0,76.1 362.0,76.3 364.9,76.3 367.9,76.3 370.9,76.8 373.8,76.2 376.7,76.2 379.7,76.2 382.6,75.9 385.6,75.4 388.6,75.9 391.5,75.7 394.4,75.9 397.4,76.2 400.3,75.6 403.3,75.3 406.2,74.8 409.1,74.8 412.1,74.8 415.1,75.3 418.0,75.0 421.0,75.0 423.9,74.8 426.9,74.8 429.8,72.4 432.8,72.4 435.7,72.1 438.7,70.1 441.6,69.9 444.6,69.9 447.5,69.9 450.4,70.1 453.4,70.1 456.4,69.9 459.3,69.6 462.3,69.6 465.2,69.4 468.1,69.6 471.1,69.9 474.1,69.9 477.0,70.1 480.0,70.1 482.9,69.9 485.8,69.6 488.8,69.6 491.7,69.6 494.7,69.7 497.7,69.7 500.6,69.5 503.6,69.2 506.5,69.5 509.4,69.2 512.4,69.7 515.4,69.7 518.3,69.7 521.3,69.5 524.2,69.5 527.1,69.5 530.1,69.0 533.1,69.0 536.0,68.5 539.0,68.7 541.9,69.0 544.8,69.0 547.8,69.0 550.7,68.7 553.7,69.2 556.7,68.7 559.6,68.7 562.5,68.5 565.5,68.5 568.4,68.2 571.4,68.0 574.4,68.2 577.3,68.5 580.3,68.5 583.2,68.7 586.1,68.7 589.1,68.4 592.0,68.2 595.0,67.9 598.0,67.9 600.9,68.1 603.8,67.9 606.8,67.9 609.7,68.1 612.7,67.6 615.7,67.4 618.6,67.6 621.5,67.9 624.5,67.6 627.4,67.2 630.4,67.4 633.3,67.6 636.3,67.2 639.2,67.2 642.2,66.9 645.1,67.6 648.1,67.6 651.0,67.2 654.0,66.2 657.0,66.0 659.9,66.3 662.8,66.3 665.8,66.6 668.7,66.3 671.7,66.3 674.6,66.1 677.6,66.1 680.5,66.2 683.5,66.2 686.4,66.4 689.4,65.8 692.3,65.8 695.2,65.8 698.2,65.6 701.2,65.8 704.1,65.8 707.1,66.1 710.0,66.3 712.9,66.1 715.9,65.8 718.8,66.1 721.8,65.1 724.8,65.3 727.7,65.6 730.7,65.3 733.6,65.3 736.5,65.6 739.5,65.6 742.4,65.8 745.4,66.3 748.4,65.8 751.3,65.8 754.2,65.6 757.2,65.6 760.1,65.3 763.1,65.3 766.0,65.3 769.0,65.3 771.9,65.3&quot;/&gt;&lt;line x1=&quot;782&quot; y1=&quot;254&quot; x2=&quot;800&quot; y2=&quot;254&quot; stroke=&quot;#e34948&quot; stroke-width=&quot;3&quot; stroke-linecap=&quot;round&quot;/&gt;&lt;text x=&quot;806&quot; y=&quot;258&quot; fill=&quot;#e8e8e4&quot; font-size=&quot;11&quot;&gt;snmalloc&lt;/text&gt;&lt;text x=&quot;806&quot; y=&quot;270&quot; fill=&quot;#9c9ca0&quot; font-size=&quot;10&quot;&gt;195.5 MB&lt;/text&gt;&lt;/svg&gt;
&lt;figcaption&gt;八組設定的 RSS 隨時間變化，灰色區塊是計分視窗&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;allocator&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;RSS 中位數&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;vs glibc&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;req/s&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;p99 ms&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;major GC&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;三輪離散度&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;jemalloc&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;174.5 MB&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;-5.1%&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;1751&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;17.79&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;36&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.33%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;mimalloc-v2&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;174.6 MB&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;-5.0%&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1695&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;17.52&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;39&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.96%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;glibc-trim&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;176.4 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-4.1%&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1651&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;18.86&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;63&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.33%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;glibc-arena2&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;177.0 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-3.7%&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1639&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;17.89&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;49&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.45%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;glibc-default&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;183.9 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+0.0%&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1662&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;18.04&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;52&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.04%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;mimalloc (v3)&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;190.7 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+3.7%&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1681&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;17.29&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;33&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.77%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;tcmalloc&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;194.8 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+5.9%&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1727&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;19.24&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;38&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.65%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;snmalloc&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;195.5 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+6.3%&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1750&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;16.65&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;40&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.57%&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;三輪之間的離散度最大 1.33%，而名次之間的落差橫跨 11.4 個百分點。以本次量測來說，這個排序比較像是訊號，而不是隨機波動。&lt;/p&gt;
&lt;p&gt;幾個可以直接帶走的結論。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;前兩名並列。&lt;/strong&gt; jemalloc 和 mimalloc v2 差 0.1 MB，落在雜訊範圍內，記憶體上可視為平手，要分勝負得看其他欄位：&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;jemalloc&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;mimalloc-v2&lt;/th&gt;&lt;th&gt;誰贏&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;RSS 中位數&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;174.5 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;174.6 MB&lt;/td&gt;&lt;td&gt;平手（差距 &amp;lt; 雜訊）&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;req/s&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1751&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1695&lt;/td&gt;&lt;td&gt;jemalloc（+3.3%）&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;p99 ms&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;17.79&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;17.52&lt;/td&gt;&lt;td&gt;mimalloc-v2（差距很小）&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;major GC&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;36&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;39&lt;/td&gt;&lt;td&gt;jemalloc&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;三輪離散度&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.33%&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.96%&lt;/td&gt;&lt;td&gt;mimalloc-v2&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;tiebreaker 落在 jemalloc：吞吐量在本次量測中最高（比 glibc 多 5.4%），major GC 次數也偏低。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;同一個名字，兩種 allocator。&lt;/strong&gt; mimalloc v3 重寫了 free-list sharding，結果和 v2 差 16 MB：&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;mimalloc-v2 (2.5.2)&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;mimalloc (3.5.1)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;RSS 中位數&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;174.6 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;190.7 MB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;vs glibc&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;-5.0%&lt;/strong&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;+3.7%&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;major GC&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;39&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;33&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;差距不是版本新舊，而是設計不同。你用套件管理程式裝到的是哪一個，就決定了實際跑起來是哪一種行為 —— 請明確 pin 版本。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;glibc 自己的旋鈕已經吃掉大半好處，而且其中一個是陷阱。&lt;/strong&gt; 三組 glibc 設定的差別只在環境變數，沒有新增任何相依套件：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; glibc-arena2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;MALLOC_ARENA_MAX&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; glibc-trim&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;MALLOC_ARENA_MAX&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;2&lt;/span&gt;&lt;span&gt; MALLOC_TRIM_THRESHOLD_&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;設定&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;RSS&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;vs glibc&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;major GC&lt;/th&gt;&lt;th&gt;評價&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;預設&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;183.9 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+0.0%&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;52&lt;/td&gt;&lt;td&gt;基準線&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;arena 上限設 2&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;177.0 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-3.7%&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;49&lt;/td&gt;&lt;td&gt;CP 值最高的一步&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;再加 trim 門檻 128 KB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;176.4 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;-4.1%&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;63&lt;/td&gt;&lt;td&gt;多 0.4% 換 11 次 major GC&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;把記憶體還給 OS 會推高 &lt;code&gt;malloc_increase&lt;/code&gt;，Ruby 因此更常觸發回收。最後那 0.4% 等於是用 CPU 換來的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;墊底的三組在本次量測中並不是被 GC 拖累，而是單純佔用較多記憶體：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;allocator&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;RSS&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;vs glibc&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;major GC&lt;/th&gt;&lt;th&gt;是 GC 造成的嗎&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;mimalloc (v3)&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;190.7 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+3.7%&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;33&lt;/td&gt;&lt;td&gt;否，本次量測中最少&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;tcmalloc&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;194.8 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+5.9%&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;38&lt;/td&gt;&lt;td&gt;否，低於 glibc 的 52&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;snmalloc&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;195.5 MB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+6.3%&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;40&lt;/td&gt;&lt;td&gt;否，低於 glibc 的 52&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;比較可能的原因是這台機器的 THP 設成 &lt;code&gt;always&lt;/code&gt;：這三者都大量使用 mmap，huge page 的粒度會把用量向上取整。在 &lt;code&gt;madvise&lt;/code&gt; 的主機上結果可能不同，所以報告的 header 會記錄你量到的是哪一種。&lt;/p&gt;
&lt;h2 id=&quot;wei-shen-mo-shi-5-ge-zhi-xing-xu&quot;&gt;為什麼是 5 個執行緒&lt;a class=&quot;zola-anchor&quot; href=&quot;#wei-shen-mo-shi-5-ge-zhi-xing-xu&quot; aria-label=&quot;Anchor link for: wei-shen-mo-shi-5-ge-zhi-xing-xu&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;這是本次實驗中最關鍵的變數之一。&lt;code&gt;THREADS=5&lt;/code&gt; 是 Rails &lt;code&gt;RAILS_MAX_THREADS&lt;/code&gt; 的預設值，也決定了 glibc arena 的比較看不看得出差異：glibc 會在每個執行緒第一次 &lt;code&gt;malloc&lt;/code&gt; 時配一個 arena 給它，GVL 擋不住這件事 —— GVL 只是不讓它們並行執行而已。核心數只決定總量上限（&lt;code&gt;8 × 核心&lt;/code&gt;），5 個執行緒並不會碰到那個上限。&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: right&quot;&gt;執行緒數&lt;/th&gt;&lt;th&gt;典型情境&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;glibc arena 上限（2 核）&lt;/th&gt;&lt;th&gt;會不會碰到上限&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;1&lt;/td&gt;&lt;td&gt;單執行緒腳本&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;16&lt;/td&gt;&lt;td&gt;不會，這個比較看不出差異&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;5&lt;/td&gt;&lt;td&gt;Rails &lt;code&gt;RAILS_MAX_THREADS&lt;/code&gt; 預設&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;16&lt;/td&gt;&lt;td&gt;不會，本次實測的位置&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;25&lt;/td&gt;&lt;td&gt;Sidekiq &lt;code&gt;concurrency: 25&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;16&lt;/td&gt;&lt;td&gt;會，&lt;code&gt;MALLOC_ARENA_MAX&lt;/code&gt; 才比較有發揮空間&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;所以如果 production 跑的是更寬的設定，就得自己掃一遍：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;THREADS&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; ./bench/run.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那可能才是 &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt; 真正發揮價值的地方，5 執行緒的結果看不到。&lt;/p&gt;
&lt;h2 id=&quot;liang-ce-she-ji-shang-de-ji-ge-jue-ding&quot;&gt;量測設計上的幾個決定&lt;a class=&quot;zola-anchor&quot; href=&quot;#liang-ce-she-ji-shang-de-ji-ge-jue-ding&quot; aria-label=&quot;Anchor link for: liang-ce-she-ji-shang-de-ji-ge-jue-ding&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;決定&lt;/th&gt;&lt;th&gt;為什麼&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Workload 用 yjit-bench 的 railsbench（Rails 8.1 + sqlite，SHA 有 pin），透過 &lt;code&gt;Rack::MockRequest&lt;/code&gt; 在 process 內驅動&lt;/td&gt;&lt;td&gt;量到的是真實 Rails 的記憶體配置行為，不是自己模擬出來的形狀&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5 個執行緒，刻意採用多執行緒&lt;/td&gt;&lt;td&gt;單執行緒不會出現多個 arena 的情況，而那正是整個比較的主題&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;不用 Puma cluster mode&lt;/td&gt;&lt;td&gt;&lt;code&gt;fork&lt;/code&gt; 之後 parent 和 child 共用 page，RSS 會重複計算，那得改量 PSS，屬於另一個 benchmark&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;主要指標是暖機後的 RSS 中位數&lt;/td&gt;&lt;td&gt;Peak RSS、req/s、p99 都有記錄，但決策不掛在那上面&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;每次取樣都記 &lt;code&gt;GC.stat&lt;/code&gt;&lt;/td&gt;&lt;td&gt;才分得出「用比較少記憶體」和「讓 Ruby GC 更辛苦」&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;p99 用 pre-allocated 的 per-thread ring buffer，跑完才排序&lt;/td&gt;&lt;td&gt;中途計算百分位數，等於在被量測的 process 裡面配記憶體&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;用 &lt;code&gt;--cpuset-cpus=0-1&lt;/code&gt; 而不是 &lt;code&gt;--cpus=2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;--cpus&lt;/code&gt; 是 CFS quota，不會改變 &lt;code&gt;nproc&lt;/code&gt;，glibc 仍會照實體核心數計算 arena 上限&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;每組設定先 preflight，grep &lt;code&gt;/proc/self/maps&lt;/code&gt;&lt;/td&gt;&lt;td&gt;被默默忽略的 &lt;code&gt;LD_PRELOAD&lt;/code&gt; 會產出八份一模一樣的 glibc 結果&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;開跑前先印 CPU steal&lt;/td&gt;&lt;td&gt;超過 1% 通常代表是共享 vCPU，不管產品頁怎麼寫，timing 欄位都不可信&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;每輪獨立跑，中間有 cooldown&lt;/td&gt;&lt;td&gt;避免前一輪的殘留狀態影響下一輪&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;pao-qi-lai&quot;&gt;跑起來&lt;a class=&quot;zola-anchor&quot; href=&quot;#pao-qi-lai&quot; aria-label=&quot;Anchor link for: pao-qi-lai&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;./bench/run.sh&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                          #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 1200s × 3 rounds × 8 lines ≈ 5h&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;DURATION&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;0&lt;/span&gt;&lt;span&gt; ROUNDS&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; ./bench/run.sh&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 快速驗證&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;WORKLOAD&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; ./bench/run.sh&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;           #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 不跑 Rails，改用模擬的記憶體配置負載&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 補跑新的設定，不重跑已經量過的&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ONLY&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;glibc-trim mimalloc-v2 snmalloc&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; ./bench/run.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;WORKLOAD=synth&lt;/code&gt; 是不跑 Rails 的版本：自己產生大量短命的字串和 hash，再把其中一小部分留進固定大小的 pool，模擬出 Rails 那種「大量配置、少量長壽」的記憶體使用形狀。適合在沒辦法裝 Rails 的環境裡做比較。&lt;/p&gt;
&lt;p&gt;執行結果會輸出到 &lt;code&gt;results/&lt;/code&gt;：每輪一個 CSV，加上 &lt;code&gt;REPORT.md&lt;/code&gt; 和 &lt;code&gt;rss-&amp;lt;workload&amp;gt;.svg&lt;/code&gt;。原始 CSV 刻意不進 repo —— harness 本身才是產出物，懷疑數字的人可以在自己的硬體上跑一次，那比相信我這張表更有價值。&lt;/p&gt;
&lt;h2 id=&quot;zhe-fen-shi-ce-mei-you-shuo-de-shi&quot;&gt;這份實測沒有說的事&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhe-fen-shi-ce-mei-you-shuo-de-shi&quot; aria-label=&quot;Anchor link for: zhe-fen-shi-ce-mei-you-shuo-de-shi&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;限制&lt;/th&gt;&lt;th&gt;可能的影響&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;只有一種 workload（railsbench）、5 執行緒、單一 process&lt;/td&gt;&lt;td&gt;Sidekiq 那種 &lt;code&gt;concurrency: 25&lt;/code&gt; 的形狀會給 glibc arena 大得多的發揮空間，差距可能拉開&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;跑到 1200 秒，曲線還在非常緩慢地往上爬&lt;/td&gt;&lt;td&gt;接近穩態但不是完全穩態；名次比絕對數字更早收斂&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;THP 設成 &lt;code&gt;always&lt;/code&gt;&lt;/td&gt;&lt;td&gt;在 &lt;code&gt;madvise&lt;/code&gt; 的主機上，墊底那三組未必是這個名次&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;沒有測 Ruby 端的 GC 調校&lt;/td&gt;&lt;td&gt;&lt;code&gt;RUBY_GC_OLDMALLOC_LIMIT_MAX&lt;/code&gt;、compaction 可能比換 allocator 更划算，而且不用多一個相依套件&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;suo-yi-gai-huan-ma&quot;&gt;所以該換嗎&lt;a class=&quot;zola-anchor&quot; href=&quot;#suo-yi-gai-huan-ma&quot; aria-label=&quot;Anchor link for: suo-yi-gai-huan-ma&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;你的情況&lt;/th&gt;&lt;th&gt;該做什麼&lt;/th&gt;&lt;th&gt;拿到什麼&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;從沒動過 glibc 參數&lt;/td&gt;&lt;td&gt;先設 &lt;code&gt;MALLOC_ARENA_MAX=2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-3.7%，不新增相依套件，本次量測中最划算的一步&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;已經設過 arena，還想再擠&lt;/td&gt;&lt;td&gt;換 jemalloc&lt;/td&gt;&lt;td&gt;再多 1.4 個百分點，加上本次量測中最高的吞吐量&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;打算用 mimalloc&lt;/td&gt;&lt;td&gt;明確 pin v2&lt;/td&gt;&lt;td&gt;不要讓套件管理程式替你決定跑的是 v2 還是 v3&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;想加 &lt;code&gt;MALLOC_TRIM_THRESHOLD_&lt;/code&gt;&lt;/td&gt;&lt;td&gt;先量 CPU&lt;/td&gt;&lt;td&gt;0.4% 記憶體換 11 次 major GC，不一定划算&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Sidekiq / 較寬的執行緒設定&lt;/td&gt;&lt;td&gt;自己用 &lt;code&gt;THREADS=25&lt;/code&gt; 跑一次&lt;/td&gt;&lt;td&gt;5 執行緒的結論在那裡不適用&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;5 執行緒的 Rails 差距大約 5%，25 執行緒的 Sidekiq 是另一個故事，而那個故事得你自己跑一次才知道。&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>Oracle Cloud 不重建 Instance 直接重灌系統</title>
		<published>2026-07-15T00:00:00+08:00</published>
		<updated>2026-07-15T00:00:00+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/oracle-cloud-reinstall-ubuntu/"/>
		<id>https://blog.2ac.io/posts/oracle-cloud-reinstall-ubuntu/</id>
    
    <summary>&lt;figure&gt;
&lt;img src=&quot;/images/oracle-cloud-reinstall-ubuntu-og.webp&quot; alt=&quot;Oracle Cloud 不重建 Instance 直接重灌系統&quot; height=&quot;800&quot; width=&quot;1200&quot;&gt;&lt;/figure&gt;
&lt;p&gt;OCI Console 上沒有「重灌」按鈕，這件事困擾我很久。一般雲端環境要重灌就 terminate 再開一台，沒什麼大不了。但 OCI Always Free 不一樣：免費額度有限，ARM instance 尤其容量難搶，terminate 之後不一定開得回來，很多人就是卡在 &quot;Out of capacity&quot; 反覆刷好幾天都搶不到。就算搶到了，IP 會變、環境要重弄，等於白費。所以能不 terminate 就不 terminate。&lt;/p&gt;
&lt;p&gt;後來發現 OCI 其實有個 Replace Boot Volume 功能，可以直接換掉開機磁碟（boot volume），instance 不用終止，OCI 層的網路設定也會保留。整個過程在 Console 上點幾下就搞定。不管是想乾淨重灌 Ubuntu、升級到新版 image，還是先把系統弄乾淨再轉 Debian，第一步都是 Replace Boot Volume。&lt;/p&gt;</summary>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/oracle-cloud-reinstall-ubuntu/">&lt;figure&gt;
&lt;img src=&quot;/images/oracle-cloud-reinstall-ubuntu-og.webp&quot; alt=&quot;Oracle Cloud 不重建 Instance 直接重灌系統&quot; height=&quot;800&quot; width=&quot;1200&quot;&gt;&lt;/figure&gt;
&lt;p&gt;OCI Console 上沒有「重灌」按鈕，這件事困擾我很久。一般雲端環境要重灌就 terminate 再開一台，沒什麼大不了。但 OCI Always Free 不一樣：免費額度有限，ARM instance 尤其容量難搶，terminate 之後不一定開得回來，很多人就是卡在 &quot;Out of capacity&quot; 反覆刷好幾天都搶不到。就算搶到了，IP 會變、環境要重弄，等於白費。所以能不 terminate 就不 terminate。&lt;/p&gt;
&lt;p&gt;後來發現 OCI 其實有個 Replace Boot Volume 功能，可以直接換掉開機磁碟（boot volume），instance 不用終止，OCI 層的網路設定也會保留。整個過程在 Console 上點幾下就搞定。不管是想乾淨重灌 Ubuntu、升級到新版 image，還是先把系統弄乾淨再轉 Debian，第一步都是 Replace Boot Volume。&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;replace-boot-volume&quot;&gt;Replace Boot Volume&lt;a class=&quot;zola-anchor&quot; href=&quot;#replace-boot-volume&quot; aria-label=&quot;Anchor link for: replace-boot-volume&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;這個功能在 instance 詳情頁的 &lt;strong&gt;More Actions&lt;/strong&gt; → &lt;strong&gt;Replace Boot Volume&lt;/strong&gt;。原理很簡單：OCI 會把 instance 停掉，換掉 boot volume，再開機回來。&lt;/p&gt;
&lt;p&gt;這裡要先分清楚兩件事，不然很容易誤會：&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;會保留&lt;/th&gt;&lt;th&gt;不會保留&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;instance 本身（不用 terminate，OCID 不變）&lt;/td&gt;&lt;td&gt;舊 boot volume 裡的作業系統&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Public / Private IP、VNIC、VCN、subnet、security list&lt;/td&gt;&lt;td&gt;你在系統裡裝的套件、改過的設定檔&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;shape（CPU / RAM 配置）&lt;/td&gt;&lt;td&gt;使用者帳號、SSH port 等自訂內容&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Block Volume 等額外掛載的磁碟&lt;/td&gt;&lt;td&gt;存在 boot volume 上的資料&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;&lt;strong&gt;OCI 資源與網路層設定會保留，但舊 boot volume 裡的作業系統設定與資料不會保留。&lt;/strong&gt; 開機回來是一個全新的系統，該備份的東西請先備份。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;官方文件寫了一堆限制，其中一條是只能換成同一個 Linux 發行版（distro）。Windows 和 Marketplace image 則是真的換不了。&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-warning&quot;&gt;
&lt;p&gt;實測上只要拿到目標 image 的 OCID 填進去，跨發行版也可行，但&lt;strong&gt;這不在官方支援範圍內&lt;/strong&gt;。能不能順利開機依 image 與架構而定，有相容性風險，請自行評估。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;cha-image-ocid&quot;&gt;查 Image OCID&lt;a class=&quot;zola-anchor&quot; href=&quot;#cha-image-ocid&quot; aria-label=&quot;Anchor link for: cha-image-ocid&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Replace Boot Volume 最關鍵的一步是拿到目標 image 的 OCID。最簡單的方式是直接去 OCI 的 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.oracle.com/en-us/iaas/images/&quot;&gt;Image 目錄頁&lt;/a&gt;，這頁列出所有 platform image，每個 image 點進去下方都有各 region 對應的 OCID 表格，找到你 instance 所在的 region 複製就行，不用裝任何東西。&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-important&quot;&gt;
&lt;p&gt;&lt;strong&gt;挑 image 前先確認 CPU 架構對得上。&lt;/strong&gt; OCI 的 Ampere A1 是 ARM64（aarch64），E2 / E3 / E4 這類是 x86_64，兩者的 image 不能互換。同一個發行版在目錄頁通常會分開列出 aarch64 與 x86_64 版本，複製 OCID 之前先看清楚是哪一個，架構不符會直接開不起來。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果習慣用 CLI，也可以查：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;oci&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; compute&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; image&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; list&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-compartment-id&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;compartment_oci&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-operating-system&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Canonical Ubuntu&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-all&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-query&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;data[*].{name:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;display-name&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;, id:id}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-output&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; table&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;想換其他發行版就把 &lt;code&gt;--operating-system&lt;/code&gt; 改掉。compartment OCID 在 Console 的 Identity → Compartments 裡找。&lt;/p&gt;
&lt;h2 id=&quot;console-cao-zuo&quot;&gt;Console 操作&lt;a class=&quot;zola-anchor&quot; href=&quot;#console-cao-zuo&quot; aria-label=&quot;Anchor link for: console-cao-zuo&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;進入 instance 詳情頁，點 &lt;strong&gt;More Actions&lt;/strong&gt; → &lt;strong&gt;Replace Boot Volume&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Replace by 選 &lt;strong&gt;Image&lt;/strong&gt;，Apply image by 選 &lt;strong&gt;Input OCID&lt;/strong&gt;，貼上剛查到的 OCID&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Preserve Boot Volume&lt;/strong&gt; 建議先開 Enabled，舊 boot volume 會保留下來，萬一新系統有問題還能掛回去救資料&lt;/li&gt;
&lt;li&gt;點 &lt;strong&gt;Replace&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote class=&quot;markdown-alert-caution&quot;&gt;
&lt;p&gt;第 3 步要留意成本：Preserve 起來的舊 boot volume 會繼續以 Block Volume 的形式占用容量。Always Free 的 Block Volume 總額度有限（含 boot volume 在內），留著兩顆可能就超出額度，超出的部分會開始計費。確認新系統沒問題之後，記得回 Console 把舊的 boot volume 刪掉。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;OCI 會自動停機、換 volume、再開機，大概 5 到 10 分鐘。開完之後 cloud-init 會自動設定好 SSH 金鑰和網路，用原本的 key 就能連回去。&lt;/p&gt;
&lt;h2 id=&quot;cli-cao-zuo&quot;&gt;CLI 操作&lt;a class=&quot;zola-anchor&quot; href=&quot;#cli-cao-zuo&quot; aria-label=&quot;Anchor link for: cli-cao-zuo&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;不想點介面的話，CLI 也行。先 &lt;code&gt;oci setup config&lt;/code&gt; 設好，然後準備一個 JSON：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;cat&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; replace.json&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;  &amp;quot;sourceDetails&amp;quot;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;quot;sourceType&amp;quot;: &amp;quot;image&amp;quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;quot;imageId&amp;quot;: &amp;quot;ocid1.image.oc1.ap-tokyo-1.yyyyy&amp;quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;quot;bootVolumeVpusPerGB&amp;quot;: 10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;EOF&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;oci&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; compute&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; instance&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; update&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-instance-id&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;instance_oci&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-from-json&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; file://replace.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;instance OCID 在 instance 詳情頁的 Overview 裡。&lt;/p&gt;
&lt;h2 id=&quot;zhong-guan-hou&quot;&gt;重灌後&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhong-guan-hou&quot; aria-label=&quot;Anchor link for: zhong-guan-hou&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;系統是全新的，cloud-init 會把 SSH 金鑰和網路設好，連上去第一件事先更新：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; update&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; upgrade&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之前改過的 SSH port 或其他自訂設定，因為都在舊的 boot volume 上，全部會還原成 image 預設值，要重新設。到這裡如果只是要重灌 Ubuntu 或升級 image，就搞定了。但如果目標是 Debian，OCI 沒提供 Debian platform image，Replace Boot Volume 換不上去，接下來要靠 debi。&lt;/p&gt;
&lt;h2 id=&quot;yong-debi-zhuan-debian&quot;&gt;用 debi 轉 Debian&lt;a class=&quot;zola-anchor&quot; href=&quot;#yong-debi-zhuan-debian&quot; aria-label=&quot;Anchor link for: yong-debi-zhuan-debian&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;OCI 的 platform image 只有 Ubuntu、Oracle Linux、CentOS 這些，偏偏沒有 Debian。想裝 Debian 的話，先用 Replace Boot Volume 灌一個乾淨的 Ubuntu（或任何 OCI 有的 Linux image），再用 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/bohanwood/debi&quot;&gt;debi&lt;/a&gt; 把它轉成 Debian。debi 的原理是把 Debian installer 注入 GRUB，重開機後全自動安裝成 minimal Debian，README 直接寫了「Perfect for converting Oracle Cloud&#39;s Ubuntu images to Debian」。&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-warning&quot;&gt;
&lt;p&gt;&lt;strong&gt;debi 是第三方腳本，不是 OCI 官方支援的 Debian 安裝方式&lt;/strong&gt;，OCI 也不保證轉換後的系統可以正常運作。下面的步驟是實測上可行的做法，但成功與否依 image、架構與 debi 版本而定，請當成有風險的操作來看待。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;來源 OS 不限 Ubuntu，只要是 KVM 環境、有 GRUB 2、有 root 權限就能跑。&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;curl&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;fLO&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; https://raw.githubusercontent.com/bohanwood/debi/master/debi.sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;chmod&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; +x&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; debi.sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ./debi.sh&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-cloudflare&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-authorized-keys-url&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; https://github.com/yourusername.keys&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; reboot&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重開機後 GRUB 自動進入 Debian installer，全自動跑完大概 5 到 10 分鐘。預設裝 Debian 13 (trixie)，建一個 &lt;code&gt;debian&lt;/code&gt; 帶 sudo 的使用者。想改版本或設定就加參數：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ./debi.sh&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-version&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 12&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-user&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; root&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-bbr&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-ssh-port&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2222&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安裝過程想看進度的話加 &lt;code&gt;--network-console&lt;/code&gt;，重開機後等兩三分鐘用 &lt;code&gt;ssh installer@your-ip&lt;/code&gt; 連進去看。跑出去想取消也行，重開機前把 GRUB 改回來就好：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; rm&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;rf&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/default/grub.d/zz-debi.cfg&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /boot/debian-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; update-grub&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;debi-zai-oci-shang-de-keng&quot;&gt;debi 在 OCI 上的坑&lt;a class=&quot;zola-anchor&quot; href=&quot;#debi-zai-oci-shang-de-keng&quot; aria-label=&quot;Anchor link for: debi-zai-oci-shang-de-keng&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;debi 能用，但在 OCI 上踩過不少坑，用的時候要有心理準備：&lt;/p&gt;
&lt;p&gt;UEFI 是最大的問題。debi 官方把 AWS EC2 標為「BIOS only，UEFI 尚未支援」，而 OCI 是 UEFI 開機，AMD 和 ARM 的 shape 都是。雖然有 &lt;code&gt;--efi&lt;/code&gt; 參數可以強制 EFI，但 UEFI 環境下翻車的機率比 BIOS 高不少。&lt;/p&gt;
&lt;p&gt;Serial Console 裝完可能會壞掉（&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/bohanwood/debi/issues/152&quot;&gt;Issue #152&lt;/a&gt;，截至撰文時尚未修復）。instance 本身正常，SSH 能連，但 OCI 的 Console Connection 看不到開機輸出，等於失去一條救援管道。解法是手動改 GRUB：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; /etc/default/grub&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;GRUB_TERMINAL_INPUT&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;console serial&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;GRUB_TERMINAL_OUTPUT&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;gfxterm serial&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;GRUB_SERIAL_COMMAND&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;serial --unit=0 --speed=115200&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;GRUB_CMDLINE_LINUX_DEFAULT&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;splash console=tty0 console=ttyS0,115200&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;改完跑 &lt;code&gt;sudo update-grub&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-caution&quot;&gt;
&lt;p&gt;上面那組設定是 x86_64 的寫法。&lt;strong&gt;serial device 的名稱依架構與平台而定&lt;/strong&gt;，&lt;code&gt;ttyS0&lt;/code&gt; 不是 ARM 與 x86 通用的裝置名稱，ARM64 平台上也可能是 &lt;code&gt;ttyAMA0&lt;/code&gt; 之類的其他名稱。動手改之前，先在還能開機的系統上確認實際的 serial device（例如看 &lt;code&gt;dmesg&lt;/code&gt; 或 &lt;code&gt;/proc/consoles&lt;/code&gt;），填錯等於白改，也可能把原本還能用的 Console 輸出弄丟。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;AMD micro（&lt;code&gt;VM.Standard.E2.1.Micro&lt;/code&gt;，1GB RAM）上也多人回報 debootstrap 報錯（&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/bohanwood/debi/issues/97&quot;&gt;Issue #97&lt;/a&gt;），作者在較大規格上無法重現，可能跟低資源有關，可以試 &lt;code&gt;--force-lowmem 1&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;ARM 以前也有過裝完重開機黑屏的問題（&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/bohanwood/debi/issues/7&quot;&gt;#7&lt;/a&gt;、&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/bohanwood/debi/issues/14&quot;&gt;#14&lt;/a&gt;），部分已修，但 ARM + UEFI 的組合仍不算穩定。另外 IPv6 可能要額外處理（&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/bohanwood/debi/issues/88&quot;&gt;Issue #88&lt;/a&gt;），LVM/RAID 不支援（&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/bohanwood/debi/issues/134&quot;&gt;Issue #134&lt;/a&gt;）。&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-tip&quot;&gt;
&lt;p&gt;跑 debi 之前先開 &lt;code&gt;--network-console&lt;/code&gt;，出問題至少還能 SSH 進去救。第一步的 Replace Boot Volume 記得開 Preserve Boot Volume，萬一 debi 失敗了還能換回乾淨的 Ubuntu 重新來過。&lt;/p&gt;
&lt;/blockquote&gt;
</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>重灌 macOS 之後，我一定會裝回來的套件（2026）</title>
		<published>2026-07-07T00:00:00+08:00</published>
		<updated>2026-07-07T00:00:00+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/macos-reinstall-essentials/"/>
		<id>https://blog.2ac.io/posts/macos-reinstall-essentials/</id>
    
    <summary>&lt;p&gt;每次重灌 macOS 最煩的不是灌系統，是灌系統之後把一堆軟體一個一個裝回來。以前都是憑記憶，裝到一半才想起漏了什麼。後來養成習慣，把整台機器在用的套件全部丟進一份 &lt;code&gt;Brewfile&lt;/code&gt;，重灌完只要一行 &lt;code&gt;brew bundle install&lt;/code&gt; 就復原九成。&lt;/p&gt;
&lt;p&gt;這篇整理目前這台機器實際在跑的 Brewfile，順便記一下怎麼產生跟還原，還有幾個容易漏掉的地方。&lt;/p&gt;</summary>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/macos-reinstall-essentials/">&lt;p&gt;每次重灌 macOS 最煩的不是灌系統，是灌系統之後把一堆軟體一個一個裝回來。以前都是憑記憶，裝到一半才想起漏了什麼。後來養成習慣，把整台機器在用的套件全部丟進一份 &lt;code&gt;Brewfile&lt;/code&gt;，重灌完只要一行 &lt;code&gt;brew bundle install&lt;/code&gt; 就復原九成。&lt;/p&gt;
&lt;p&gt;這篇整理目前這台機器實際在跑的 Brewfile，順便記一下怎麼產生跟還原，還有幾個容易漏掉的地方。&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;chan-sheng-zi-ji-de-brewfile&quot;&gt;產生自己的 Brewfile&lt;a class=&quot;zola-anchor&quot; href=&quot;#chan-sheng-zi-ji-de-brewfile&quot; aria-label=&quot;Anchor link for: chan-sheng-zi-ji-de-brewfile&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;不用手動維護清單，&lt;code&gt;brew bundle dump&lt;/code&gt; 會直接把目前裝好的 formula、cask、tap、npm 套件全部掃出來寫成檔案：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;brew&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; bundle&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; dump&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-file=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;/Brewfile&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重灌新機之後，先裝 Homebrew，順手把分析回報關掉，再把這份 &lt;code&gt;Brewfile&lt;/code&gt; 丟回去：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;brew&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; analytics&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; off&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;brew&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; bundle&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-file=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;/Brewfile&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CLI 工具、GUI App（cask）、甚至 &lt;code&gt;npm install -g&lt;/code&gt; 的套件都會照著清單一次裝完，不用一個一個回想裝過什麼。&lt;/p&gt;
&lt;h2 id=&quot;mu-qian-zai-yong-de-brewfile&quot;&gt;目前在用的 Brewfile&lt;a class=&quot;zola-anchor&quot; href=&quot;#mu-qian-zai-yong-de-brewfile&quot; aria-label=&quot;Anchor link for: mu-qian-zai-yong-de-brewfile&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tap &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;7a6163/tap&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; trusted&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; formulae&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;rubyfast&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; CLI 工具&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;atuin&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;       #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 改良版 shell history，支援搜尋、同步&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;bat&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;         #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; cat 的替代品，有語法高亮&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;bottom&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;      #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 跨平台系統資源監控&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;broot&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;       #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 樹狀瀏覽 + 互動式檔案管理&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;btop&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; htop 的替代品，圖形化系統資源監控&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;dust&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; du 的替代品，磁碟用量一目瞭然&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;eza&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;         #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ls 的現代化替代品&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;          #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; find 的更快替代品&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;fzf&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;         #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 命令列模糊搜尋&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;gping&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;       #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ping 的替代品，即時繪製延遲折線圖&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;helix&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;       #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 模態編輯器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;hyperfine&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;   #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 指令效能 benchmark 工具&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;jq&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;          #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; JSON 處理&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;libyaml&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;mas&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;         #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Mac App Store CLI，讓 brew bundle 也能抓 App Store 裝的軟體&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;mise&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 語言版本管理（asdf 的 Rust 版）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;nomino&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;      #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 批次檔案重新命名，用 regex 或 CSV 對照表一次改一整批&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;procs&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;       #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ps 的替代品，輸出更好讀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ripgrep&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;      #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; grep 的更快替代品&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;starship&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 跨 shell 的 prompt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;tldr&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 精簡版 man page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;zellij&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;      #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; terminal multiplexer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;zimfw&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;       #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; zsh plugin manager&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;zoxide&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;      #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; cd 的更快替代品&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;7a6163/tap/sigilo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; trusted&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; SSH agent，簽章要 Touch ID&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; GUI App&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;alfred&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                       #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 應用程式啟動器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;bitwarden&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 密碼管理&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;font-jetbrains-mono-nerd-font&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ghostty&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                      #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; GPU 加速的終端機&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;iina&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                         #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 影片播放器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;keka&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                         #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 解壓縮工具&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;microsoft-teams&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;mole-app&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                     #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 系統清理最佳化&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;navicat-premium&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;              #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 資料庫管理工具&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;obsidian&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                     #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 筆記&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;orbstack&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                     #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Docker Desktop 替代品&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;plex&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                         #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 家庭媒體伺服器播放端&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;setapp&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;shottr&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                       #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 截圖標註工具&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;squirrel-app&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                 #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Rime 輸入法引擎&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;steam&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;sublime-text&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                 #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 文字編輯器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;surge&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                        #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 網路工具箱&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;telegram&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;thebrowsercompany-dia&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 瀏覽器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;zed&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                          #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 多人協作編輯器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cask &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;zen&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                          #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Gecko 核心瀏覽器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Mac App Store 軟體（裝了 mas 之後自動掃出來）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mas &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Bob&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; id&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1630034110&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mas &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;LINE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; id&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 539883307&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mas &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;OpenCat&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; id&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 6445999201&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mas &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;PasteNow&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; id&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1552536109&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mas &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Things&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; id&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 904280696&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mas &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;UpNote&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; id&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1398373917&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mas &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Wipr&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; id&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1662217862&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; npm 全域套件&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;npm &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;aicommits&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                     #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; AI 產生 commit message&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;shell-zimfw-starship-huan-you-ji-ge-alias&quot;&gt;Shell：zimfw、starship，還有幾個 alias&lt;a class=&quot;zola-anchor&quot; href=&quot;#shell-zimfw-starship-huan-you-ji-ge-alias&quot; aria-label=&quot;Anchor link for: shell-zimfw-starship-huan-you-ji-ge-alias&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;zsh 的 plugin 交給 &lt;code&gt;zimfw&lt;/code&gt; 管，設定寫在 &lt;code&gt;~/.zimrc&lt;/code&gt;，只列真的會用到的模組：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ~/.zimrc（節錄）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zmodule&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; environment&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zmodule&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zmodule&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; input&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zmodule&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; termtitle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zmodule&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; utility&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zmodule&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; zsh-users/zsh-completions&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-fpath&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; src&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zmodule&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; completion&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zmodule&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; zsh-users/zsh-syntax-highlighting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zmodule&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; zsh-users/zsh-history-substring-search&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zmodule&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; zsh-users/zsh-autosuggestions&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;~/.zshrc&lt;/code&gt; 裡接進來只要幾行，&lt;code&gt;init.zsh&lt;/code&gt; 會依 &lt;code&gt;.zimrc&lt;/code&gt; 的修改時間自動判斷要不要重建：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ZIM_HOME&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;ZDOTDIR&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:-&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;HOME&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;m&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;if&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;ZIM_HOME&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/init.zsh &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-nt&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;ZIM_CONFIG_FILE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:-&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;ZDOTDIR&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:-&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;HOME&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;/&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;zimrc&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; ]]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  source&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /opt/homebrew/opt/zimfw/share/zimfw.zsh&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; init&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;source&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;ZIM_HOME&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/init.zsh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;prompt 交給 &lt;code&gt;starship&lt;/code&gt;，一行 &lt;code&gt;eval&lt;/code&gt; 就解決：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;starship&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; init&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; zsh&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;沒另外寫 &lt;code&gt;~/.config/starship.toml&lt;/code&gt;，吃預設 preset 就夠用，git 分支狀態、指令執行時間都內建。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;eza&lt;/code&gt;、&lt;code&gt;bat&lt;/code&gt; 裝上之後，順手把原生的 &lt;code&gt;ls&lt;/code&gt;、&lt;code&gt;cat&lt;/code&gt; 蓋掉，反正這幾行只在互動式 shell 生效，script 用 &lt;code&gt;/bin/sh&lt;/code&gt; 跑不會被影響：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;alias&lt;/span&gt;&lt;span&gt; ls&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;eza --icons&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;alias&lt;/span&gt;&lt;span&gt; ll&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;eza -l --icons&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;alias&lt;/span&gt;&lt;span&gt; la&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;eza -la --icons&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;alias&lt;/span&gt;&lt;span&gt; lt&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;eza --tree --icons&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;alias&lt;/span&gt;&lt;span&gt; cat&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;bat&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;broot&lt;/code&gt; 比較特別，裝完還要接一行 shell hook 才能用 &lt;code&gt;br&lt;/code&gt; 直接切換目錄，跑過 &lt;code&gt;broot --install&lt;/code&gt; 它會自動幫你加進 &lt;code&gt;~/.zshrc&lt;/code&gt;：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /Users/zac/.config/broot/launcher/bash/br&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;gong-ju-qu-she&quot;&gt;工具取捨&lt;a class=&quot;zola-anchor&quot; href=&quot;#gong-ju-qu-she&quot; aria-label=&quot;Anchor link for: gong-ju-qu-she&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;CLI 工具能找到 Rust 重寫版的話幾乎都會換：&lt;code&gt;bat&lt;/code&gt;、&lt;code&gt;eza&lt;/code&gt;、&lt;code&gt;ripgrep&lt;/code&gt;、&lt;code&gt;zoxide&lt;/code&gt;、&lt;code&gt;fd&lt;/code&gt;、&lt;code&gt;dust&lt;/code&gt;、&lt;code&gt;procs&lt;/code&gt;、&lt;code&gt;gping&lt;/code&gt;、&lt;code&gt;nomino&lt;/code&gt;，速度跟體驗都比原生好一截，指令幾乎不用改習慣。&lt;code&gt;hyperfine&lt;/code&gt; 拿來比較指令效能，改完設定或寫腳本想知道有沒有變快，跑一次就有統計數字，不用自己土法煉鋼算時間。系統監控用 &lt;code&gt;btop&lt;/code&gt;，圖形化的 CPU、記憶體、網路流量一目瞭然，比 &lt;code&gt;htop&lt;/code&gt; 好看也好讀。&lt;/p&gt;
&lt;p&gt;終端機編輯器換成 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://helix-editor.com/&quot;&gt;Helix&lt;/a&gt;，內建 LSP、多游標、tree-sitter，開箱即用幾乎不用額外設定。&lt;code&gt;mise&lt;/code&gt; 取代掉以前用的 &lt;code&gt;asdf&lt;/code&gt;，管 Ruby / Node 版本快很多。容器這塊用 &lt;code&gt;orbstack&lt;/code&gt; 直接換掉 Docker Desktop，開機資源佔用差很多。中文輸入法靠 &lt;code&gt;squirrel-app&lt;/code&gt;（Rime），這個不裝的話中文輸入會很痛苦。&lt;/p&gt;
&lt;p&gt;私有的小工具像 &lt;code&gt;sigilo&lt;/code&gt; 直接放進自己的 tap，一起被 &lt;code&gt;brew bundle&lt;/code&gt; 管起來，不用另外裝。裝了 &lt;code&gt;mas&lt;/code&gt; 之後，只要登入過 App Store，&lt;code&gt;brew bundle dump&lt;/code&gt; 會自動把裝過的 App Store 軟體列成 &lt;code&gt;mas &quot;App 名稱&quot;, id: 12345&lt;/code&gt;，重灌時一起裝回來，不用再手動一個個回想以前裝過什麼。&lt;code&gt;brew analytics off&lt;/code&gt; 這行也不能漏，裝完 Homebrew 就先跑一次，不然每次 &lt;code&gt;brew install&lt;/code&gt; 都會送統計資料出去。&lt;/p&gt;
&lt;p&gt;git 的部分也順手處理掉：&lt;code&gt;~/.gitconfig&lt;/code&gt; 的 email 用 GitHub 配發的 noreply 信箱，commit 紀錄不會外洩真實 email；另外設一份全域 ignore，&lt;code&gt;.DS_Store&lt;/code&gt;、&lt;code&gt;.idea/&lt;/code&gt;、&lt;code&gt;.vscode/&lt;/code&gt; 這類跟專案無關的檔案不用每個 repo 各自寫一次：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; config&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-global&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; core.excludesfile&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.gitignore_global&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;~/.gitignore_global&lt;/code&gt; 內容：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# macOS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.DS_Store&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.AppleDouble&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.LSOverride&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Editor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.idea/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.vscode/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;*.swp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# OS generated files&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Thumbs.db&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;zhong-guan-hou-rong-yi-lou-diao-de-di-fang&quot;&gt;重灌後容易漏掉的地方&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhong-guan-hou-rong-yi-lou-diao-de-di-fang&quot; aria-label=&quot;Anchor link for: zhong-guan-hou-rong-yi-lou-diao-de-di-fang&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;brew bundle install&lt;/code&gt; 能復原軟體本身，但有幾件事它幫不上忙。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mas install &amp;lt;id&amp;gt;&lt;/code&gt; 只能重裝同一個 Apple ID 已經買過的 App，&lt;code&gt;mas&lt;/code&gt; 本身不處理登入，重灌後得先在 App Store.app 裡登入帳號，它才抓得到東西。&lt;code&gt;setapp&lt;/code&gt;、&lt;code&gt;navicat-premium&lt;/code&gt; 這類裝好還要手動登入訂閱或輸入序號，Brewfile 只負責把 App 本體裝上。自己的 tap（&lt;code&gt;sigilo&lt;/code&gt;、&lt;code&gt;rubyfast&lt;/code&gt;）放在 GitHub，重灌新機時要先確認網路跟 DNS 正常，&lt;code&gt;brew tap&lt;/code&gt; 才抓得到 formula。&lt;/p&gt;
&lt;p&gt;最容易漏掉的其實是 dotfiles：&lt;code&gt;.zshrc&lt;/code&gt;、&lt;code&gt;.zimrc&lt;/code&gt;、&lt;code&gt;.gitconfig&lt;/code&gt;、&lt;code&gt;.gitignore_global&lt;/code&gt;、Ghostty 的 &lt;code&gt;config.ghostty&lt;/code&gt;，這些設定檔完全不在 &lt;code&gt;brew bundle&lt;/code&gt; 的管理範圍內，得另外用 git 版控或 chezmoi / stow 這類工具備份，不然軟體裝完，這些細部設定還是一片空白。Ghostty 的 &lt;code&gt;font-family&lt;/code&gt; 要填 &lt;code&gt;JetBrainsMono Nerd Font&lt;/code&gt;，跟 &lt;code&gt;~/Library/Fonts&lt;/code&gt; 裡實際的字體家族名稱對上，字型才吃得到。&lt;/p&gt;
&lt;h2 id=&quot;shou-wei&quot;&gt;收尾&lt;a class=&quot;zola-anchor&quot; href=&quot;#shou-wei&quot; aria-label=&quot;Anchor link for: shou-wei&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;重灌機器最花時間的往往不是裝系統，是把環境調回原本習慣的樣子。有了 &lt;code&gt;Brewfile&lt;/code&gt;，這件事變成一個指令的事，dotfiles 另外顧好，剩下的細節就不多了。清單也會隨著實際使用調整，之後重灌前先跑一次 &lt;code&gt;brew bundle dump&lt;/code&gt; 更新，永遠是當下這台機器最新的狀態。&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>tapwarden：金鑰放 Bitwarden 或自架 Vaultwarden，簽 SSH 一樣要 Touch ID</title>
		<published>2026-07-05T00:00:00+08:00</published>
		<updated>2026-07-05T00:00:00+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/tapwarden/"/>
		<id>https://blog.2ac.io/posts/tapwarden/</id>
    
    <summary>&lt;figure&gt;
&lt;img src=&quot;/images/tapwarden-og.webp&quot; alt=&quot;tapwarden：自架 Vaultwarden 簽 SSH 也要 Touch ID&quot; height=&quot;800&quot; width=&quot;1200&quot;&gt;&lt;/figure&gt;
&lt;p&gt;自己平常簽 SSH、簽 git commit 用的是 1Password 的 agent，金鑰放進 1Password 的 vault，每次要簽章都跳一次 Touch ID，體驗很好。剛好 1Password 到期，想說乾脆整個密碼管理都換成自架的 Vaultwarden，SSH 簽章這塊也得跟著搬過去。&lt;/p&gt;
&lt;p&gt;Bitwarden 官方沒有對應的 agent。網路上找得到的替代方案（vault-conductor、bw-agent 這類）能從 Secrets Manager 或 secure notes 撈金鑰，但少了 1Password 那個「每次簽章都要按一下」的關卡：同一個使用者跑的程式，只要能連上 agent 的 socket，就能靜靜簽過去，不會有任何提示。&lt;/p&gt;
&lt;p&gt;於是寫了 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/tapwarden&quot;&gt;tapwarden&lt;/a&gt;：SSH agent 本體，金鑰來源是 Bitwarden Secrets Manager 或自架的 Vaultwarden，每一次簽章都會跳出 Touch ID 提示，金鑰只存在記憶體，從不落地。&lt;/p&gt;</summary>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/tapwarden/">&lt;figure&gt;
&lt;img src=&quot;/images/tapwarden-og.webp&quot; alt=&quot;tapwarden：自架 Vaultwarden 簽 SSH 也要 Touch ID&quot; height=&quot;800&quot; width=&quot;1200&quot;&gt;&lt;/figure&gt;
&lt;p&gt;自己平常簽 SSH、簽 git commit 用的是 1Password 的 agent，金鑰放進 1Password 的 vault，每次要簽章都跳一次 Touch ID，體驗很好。剛好 1Password 到期，想說乾脆整個密碼管理都換成自架的 Vaultwarden，SSH 簽章這塊也得跟著搬過去。&lt;/p&gt;
&lt;p&gt;Bitwarden 官方沒有對應的 agent。網路上找得到的替代方案（vault-conductor、bw-agent 這類）能從 Secrets Manager 或 secure notes 撈金鑰，但少了 1Password 那個「每次簽章都要按一下」的關卡：同一個使用者跑的程式，只要能連上 agent 的 socket，就能靜靜簽過去，不會有任何提示。&lt;/p&gt;
&lt;p&gt;於是寫了 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/tapwarden&quot;&gt;tapwarden&lt;/a&gt;：SSH agent 本體，金鑰來源是 Bitwarden Secrets Manager 或自架的 Vaultwarden，每一次簽章都會跳出 Touch ID 提示，金鑰只存在記憶體，從不落地。&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;xian-you-xuan-xiang-ge-que-yi-jiao&quot;&gt;現有選項各缺一角&lt;a class=&quot;zola-anchor&quot; href=&quot;#xian-you-xuan-xiang-ge-que-yi-jiao&quot; aria-label=&quot;Anchor link for: xian-you-xuan-xiang-ge-que-yi-jiao&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;README 裡的比較表格說得很直白：&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;金鑰來源&lt;/th&gt;&lt;th&gt;每次簽章都要驗證&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1Password agent&lt;/td&gt;&lt;td&gt;1Password vault&lt;/td&gt;&lt;td&gt;✅ Touch ID&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bitwarden GUI agent&lt;/td&gt;&lt;td&gt;個人 vault&lt;/td&gt;&lt;td&gt;❌ 手動解鎖一次就好&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;vault-conductor / bw-agent&lt;/td&gt;&lt;td&gt;Secrets Manager / secure notes&lt;/td&gt;&lt;td&gt;❌ 靜默簽章&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;tapwarden&lt;/td&gt;&lt;td&gt;Secrets Manager 或 Vaultwarden（可限縮範圍）&lt;/td&gt;&lt;td&gt;✅ 每次簽章都要 Touch ID&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;tapwarden 想要的東西，其他選項都只能給一半：金鑰的存取權限要收得夠小，環境裡的憑證只能讀到你授權的那幾把 SSH 金鑰（一個只綁定單一專案的 BWS machine token，或是一個除了金鑰什麼都沒有的 Vaultwarden 帳號），不是你整個密碼庫；而且每一次要簽章，都得有人真的在電腦前面按一下 Touch ID，就算是同一個使用者跑的程式碰到 agent 的 socket，也沒辦法靜默簽過去。&lt;/p&gt;
&lt;h2 id=&quot;jin-yao-cong-na-lai-liang-zhong-hou-duan&quot;&gt;金鑰從哪來：兩種後端&lt;a class=&quot;zola-anchor&quot; href=&quot;#jin-yao-cong-na-lai-liang-zhong-hou-duan&quot; aria-label=&quot;Anchor link for: jin-yao-cong-na-lai-liang-zhong-hou-duan&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Bitwarden Secrets Manager 是官方產品，雲端版或自架版都支援：在 web vault 開通 Secrets Manager，建立 project，一把 SSH 金鑰對應一個 secret，再開一個只讀該 project 的 machine account，拿到 access token。設定檔裡只填 secret 的 UUID 和「哪個環境變數放 token」，token 本身永遠不會寫進設定檔。&lt;/p&gt;
&lt;p&gt;Vaultwarden 沒有做 Secrets Manager，那是 Bitwarden 專有授權的功能，所以 tapwarden 對 Vaultwarden 走另一條路：讀「SSH 金鑰」類型的 vault item（cipher type 5），從一個只放金鑰、什麼都不做的專用帳號讀。伺服器端要加 &lt;code&gt;EXPERIMENTAL_CLIENT_FEATURE_FLAGS=ssh-key-vault-item&lt;/code&gt; 這個 flag 才能建立這類 item。設定這條路徑不用手動寫 YAML，跑 &lt;code&gt;tapwarden setup&lt;/code&gt; 就好：它會幫你登入一次、列出可選的金鑰、寫好設定檔，預設把憑證存進 macOS Keychain，之後每次 agent 要讀這組憑證，也一樣要過 Touch ID。想留在 CI 或 Linux 上跑的話，&lt;code&gt;setup&lt;/code&gt; 也可以選擇把憑證留在環境變數。&lt;/p&gt;
&lt;h2 id=&quot;zen-mo-shou-quan-touch-id-huo-fido2-an-quan-jin-yao&quot;&gt;怎麼授權：Touch ID 或 FIDO2 安全金鑰&lt;a class=&quot;zola-anchor&quot; href=&quot;#zen-mo-shou-quan-touch-id-huo-fido2-an-quan-jin-yao&quot; aria-label=&quot;Anchor link for: zen-mo-shou-quan-touch-id-huo-fido2-an-quan-jin-yao&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;授權因子可以從 Touch ID 換成 FIDO2 安全金鑰（YubiKey 之類），在設定裡選 &lt;code&gt;factor: touch_id&lt;/code&gt;（預設）或 &lt;code&gt;factor: yubikey&lt;/code&gt;，&lt;strong&gt;兩者是二選一，不能同時開&lt;/strong&gt;。YubiKey 扮演的角色跟 Touch ID 一樣：每次簽章都要有人真的去碰一下那把金鑰。&lt;/p&gt;
&lt;p&gt;註冊用 &lt;code&gt;tapwarden register-yubikey&lt;/code&gt; 跑一次：插上金鑰、輸入 PIN（如果金鑰有設）、碰一下，它會印出 credential ID 和 base64 的公鑰，自己貼進設定檔。PIN 只有註冊時要，之後簽章只需要碰。註冊和簽章的時候都只能插一把 FIDO2 金鑰。另外 v0.2.0 建立的設定檔要重新註冊一次，才能啟用對 assertion 的密碼學驗證。&lt;/p&gt;
&lt;p&gt;要注意這不會讓它變成跨平台 —— FIDO2 這塊的實作是綁 macOS API 的，換成 YubiKey 一樣只能在 macOS 上跑。&lt;/p&gt;
&lt;h2 id=&quot;bei-jing-chang-zhu-yu-launchd-de-xiao-di-lei&quot;&gt;背景常駐與 launchd 的小地雷&lt;a class=&quot;zola-anchor&quot; href=&quot;#bei-jing-chang-zhu-yu-launchd-de-xiao-di-lei&quot; aria-label=&quot;Anchor link for: bei-jing-chang-zhu-yu-launchd-de-xiao-di-lei&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;tapwarden start&lt;/code&gt; 把自己裝成 LaunchAgent，開機自動啟動、crash 會重啟，指令印出 socket 路徑，&lt;code&gt;~/.ssh/config&lt;/code&gt; 裡的 &lt;code&gt;IdentityAgent&lt;/code&gt; 指過去就不用管了：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Host *&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  IdentityAgent &amp;lt;tapwarden socket-path 的輸出&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ssh-add -L&lt;/code&gt; 列公鑰不會跳提示，實際 &lt;code&gt;ssh&lt;/code&gt; 連線簽章才會跳 Touch ID。&lt;/p&gt;
&lt;p&gt;這裡有一個踩過的坑：launchd 啟動的程式看不到你 shell 裡 export 的環境變數。如果設定走的是環境變數這條路（BWS backend，或 Vaultwarden 選了 &lt;code&gt;credentials: env&lt;/code&gt;），背景執行的 agent 讀不到那些變數，直接會失敗。解法有幾個：BWS backend 可以用 &lt;code&gt;tapwarden store-token&lt;/code&gt;，把 access token 一次存進 macOS Keychain，之後讀取一樣要過 Touch ID，背景 agent 就不必依賴 shell 環境；Vaultwarden 那邊換成 &lt;code&gt;credentials: keychain&lt;/code&gt;（&lt;code&gt;tapwarden setup&lt;/code&gt; 會幫你設好）；想先確認問題出在哪，用 &lt;code&gt;tapwarden start --fg&lt;/code&gt; 在有 export 過的 shell 裡跑一次；真的想留在環境變數，就自己去 LaunchAgent 的 plist 裡加 &lt;code&gt;EnvironmentVariables&lt;/code&gt;。踩過一次才知道這是背景服務常見的坑，不是 tapwarden 特有的問題。&lt;/p&gt;
&lt;p&gt;出問題的時候 &lt;code&gt;tapwarden doctor&lt;/code&gt; 會直接告訴你卡在哪一關：它會檢查設定檔、憑證讀不讀得到、Touch ID／YubiKey 有沒有偵測到、LaunchAgent 狀態、socket 路徑、&lt;code&gt;~/.ssh/config&lt;/code&gt; 有沒有接上，任何一項失敗就以非零 exit code 結束。加 &lt;code&gt;--check-backend&lt;/code&gt; 會多做一步，實際去後端把每一把設定好的金鑰抓一次，等於端到端驗證 —— 代價是這個模式需要後端憑證真的可用。&lt;/p&gt;
&lt;h2 id=&quot;zhi-ling-yi-lan&quot;&gt;指令一覽&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhi-ling-yi-lan&quot; aria-label=&quot;Anchor link for: zhi-ling-yi-lan&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;前面提過的和沒提過的都收在這裡，當作速查表：&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;指令&lt;/th&gt;&lt;th&gt;做什麼&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;tapwarden setup&lt;/code&gt;&lt;/td&gt;&lt;td&gt;互動式設定 Vaultwarden，登入一次、列出金鑰、寫好設定檔&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;tapwarden start&lt;/code&gt;&lt;/td&gt;&lt;td&gt;裝成 LaunchAgent 背景常駐，&lt;code&gt;--fg&lt;/code&gt; 改成前景跑&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;tapwarden stop&lt;/code&gt;&lt;/td&gt;&lt;td&gt;停掉 agent，但 LaunchAgent 定義保留，之後還能再啟動&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;tapwarden socket-path&lt;/code&gt;&lt;/td&gt;&lt;td&gt;印出 agent socket 路徑，填進 &lt;code&gt;~/.ssh/config&lt;/code&gt; 用的&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;tapwarden doctor&lt;/code&gt;&lt;/td&gt;&lt;td&gt;唯讀診斷，&lt;code&gt;--check-backend&lt;/code&gt; 會實際去後端抓一次金鑰&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;tapwarden logs&lt;/code&gt;&lt;/td&gt;&lt;td&gt;看 &lt;code&gt;~/Library/Logs/tapwarden.log&lt;/code&gt; 的最後 50 行&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;tapwarden store-token&lt;/code&gt;&lt;/td&gt;&lt;td&gt;把 BWS access token 存進 Keychain，僅 BWS backend 適用&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;tapwarden register-yubikey&lt;/code&gt;&lt;/td&gt;&lt;td&gt;註冊 FIDO2 安全金鑰當作授權因子&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;tapwarden uninstall&lt;/code&gt;&lt;/td&gt;&lt;td&gt;停掉 agent 並移除 LaunchAgent plist&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;stop&lt;/code&gt; 和 &lt;code&gt;uninstall&lt;/code&gt; 差在前者只是暫停、plist 還在，後者會把 &lt;code&gt;~/Library/LaunchAgents/com.tapwarden.agent.plist&lt;/code&gt; 一起刪掉，要再用得重裝一次。&lt;/p&gt;
&lt;p&gt;socket 路徑不是固定的，會落在 &lt;code&gt;$XDG_RUNTIME_DIR/tapwarden&lt;/code&gt; 或一個帶 uid 後綴的暫存目錄下，依系統設定而定，所以 &lt;code&gt;~/.ssh/config&lt;/code&gt; 裡請照 &lt;code&gt;socket-path&lt;/code&gt; 印出來的填，不要自己猜。&lt;/p&gt;
&lt;h2 id=&quot;qian-shu-er-jin-wei-dang-an&quot;&gt;簽署二進位檔案&lt;a class=&quot;zola-anchor&quot; href=&quot;#qian-shu-er-jin-wei-dang-an&quot; aria-label=&quot;Anchor link for: qian-shu-er-jin-wei-dang-an&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;沒簽署的 binary 沒有穩定的 code identity，結果就是每次重新編譯，macOS 的 Keychain 都會當成新程式重新問一次授權。免費的解法是自己開一張 self-signed 的 code signing 憑證，每次 build 完簽一下：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;codesign&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; tapwarden-dev&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-force&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; target/release/tapwarden&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要做到 distribution 等級（Developer ID + 公證，Gatekeeper 和系統層級的 keychain ACL 都要靠這個）需要付費的 Apple Developer 帳號，目前還在 roadmap 上，還沒做。&lt;/p&gt;
&lt;h2 id=&quot;an-quan-mo-xing-li-zai-yi-de-xi-jie&quot;&gt;安全模型裡在意的細節&lt;a class=&quot;zola-anchor&quot; href=&quot;#an-quan-mo-xing-li-zai-yi-de-xi-jie&quot; aria-label=&quot;Anchor link for: an-quan-mo-xing-li-zai-yi-de-xi-jie&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;grace&lt;/code&gt; 模式（簽過一次之後，同一把金鑰在 &lt;code&gt;grace_seconds&lt;/code&gt; 內免提示）只影響簽章本身，不會連帶讓 Keychain 裡的 Vaultwarden 憑證跳過 Touch ID，這兩個關卡是分開算的，approval 不會互相借用。&lt;/p&gt;
&lt;p&gt;Vaultwarden 那邊的加密實作（EncString 用 AES-256-CBC + HMAC-SHA256，KDF 推導）是照著官方 Bitwarden SDK 的原始碼和它公開的測試向量對著寫的，MAC 一定是在 decrypt 之前、用 constant-time 比對驗證完才繼續，避免 timing side channel。專案本身沒有依賴官方 SDK，REST 呼叫直接建在 reqwest 和 rustls 上，為的是讓依賴樹小一點、好稽核（&lt;code&gt;cargo audit&lt;/code&gt; 目前乾淨，唯一忽略的 advisory 也寫在 &lt;code&gt;.cargo/audit.toml&lt;/code&gt; 裡註明原因）。&lt;/p&gt;
&lt;p&gt;Agent 的 socket 放在每個使用者專屬、權限 0700 的目錄下，會檢查有沒有被 symlink 攻擊，bind 之前也會先收緊 umask。&lt;/p&gt;
&lt;h2 id=&quot;xian-kuang&quot;&gt;現況&lt;a class=&quot;zola-anchor&quot; href=&quot;#xian-kuang&quot; aria-label=&quot;Anchor link for: xian-kuang&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;目前是能用的階段：Touch ID 已經在沒簽署的 binary 上驗證過可以跳出提示，兩種後端都對著官方 SDK 的協定和測試向量實作完，也自己做過一輪 security review。BWS access token 收進系統 keystore 這件事後來也做掉了，就是上面的 &lt;code&gt;store-token&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;macOS 是硬需求 —— 授權因子雖然多了 FIDO2 安全金鑰這個選項，但那塊實作一樣綁 macOS API，所以 Linux 支援還在 roadmap 上，跟 Developer ID 簽署加公證、上架 Homebrew 排在一起。&lt;/p&gt;
&lt;p&gt;1Password 那套 Touch ID 體驗確實很好用，換過來之後才發現，這其實才是我一直想要的：金鑰收在自己控制的 Vaultwarden 裡，簽章一樣得先過 Touch ID 才放行。如果你也想把密碼管理整個搬到自架 Vaultwarden，又捨不得 1Password 那種簽章體驗，這是一個選項。&lt;/p&gt;
&lt;p&gt;專案原始碼：&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/tapwarden&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>用 Cloudflare Workers 幫 Vaultwarden 代理 Favicon，伺服器 IP 不外洩</title>
		<published>2026-07-04T00:00:00+08:00</published>
		<updated>2026-07-04T00:00:00+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/vaultwarden-icons-worker/"/>
		<id>https://blog.2ac.io/posts/vaultwarden-icons-worker/</id>
    
    <summary>&lt;figure&gt;
&lt;img src=&quot;/images/vaultwarden-icons-worker-og.webp&quot; alt=&quot;用 Cloudflare Workers 幫 Vaultwarden 代理 Favicon&quot; height=&quot;669&quot; width=&quot;1200&quot;&gt;&lt;/figure&gt;
&lt;p&gt;自建 Vaultwarden 的人大概都沒細想過一件事：那些帳號旁邊漂亮的網站 icon 是怎麼來的。預設的 &lt;code&gt;ICON_SERVICE=internal&lt;/code&gt; 其實是伺服器自己出去抓，你存了多少個網域，伺服器就對多少個網域發過一次請求，等於把伺服器的對外 IP 送給每一個存過密碼的網站。內建的替代方案 &lt;code&gt;duckduckgo&lt;/code&gt;、&lt;code&gt;google&lt;/code&gt;、&lt;code&gt;bitwarden&lt;/code&gt; 不會洩漏伺服器 IP，但換成把使用者查詢的網域清單整批交給那些第三方。&lt;/p&gt;
&lt;p&gt;自己也在跑 Vaultwarden，想了想這兩個選項都不滿意，於是寫了 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/vaultwarden-icons-worker&quot;&gt;vaultwarden-icons-worker&lt;/a&gt;：圖示代理自己架，架在 Cloudflare 的邊緣節點上，伺服器和客戶端的 IP 都不會被目標網站看到。&lt;/p&gt;</summary>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/vaultwarden-icons-worker/">&lt;figure&gt;
&lt;img src=&quot;/images/vaultwarden-icons-worker-og.webp&quot; alt=&quot;用 Cloudflare Workers 幫 Vaultwarden 代理 Favicon&quot; height=&quot;669&quot; width=&quot;1200&quot;&gt;&lt;/figure&gt;
&lt;p&gt;自建 Vaultwarden 的人大概都沒細想過一件事：那些帳號旁邊漂亮的網站 icon 是怎麼來的。預設的 &lt;code&gt;ICON_SERVICE=internal&lt;/code&gt; 其實是伺服器自己出去抓，你存了多少個網域，伺服器就對多少個網域發過一次請求，等於把伺服器的對外 IP 送給每一個存過密碼的網站。內建的替代方案 &lt;code&gt;duckduckgo&lt;/code&gt;、&lt;code&gt;google&lt;/code&gt;、&lt;code&gt;bitwarden&lt;/code&gt; 不會洩漏伺服器 IP，但換成把使用者查詢的網域清單整批交給那些第三方。&lt;/p&gt;
&lt;p&gt;自己也在跑 Vaultwarden，想了想這兩個選項都不滿意，於是寫了 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/vaultwarden-icons-worker&quot;&gt;vaultwarden-icons-worker&lt;/a&gt;：圖示代理自己架，架在 Cloudflare 的邊緣節點上，伺服器和客戶端的 IP 都不會被目標網站看到。&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;xie-lou-de-lu-jing-he-worker-zen-mo-dang&quot;&gt;洩漏的路徑，和 Worker 怎麼擋&lt;a class=&quot;zola-anchor&quot; href=&quot;#xie-lou-de-lu-jing-he-worker-zen-mo-dang&quot; aria-label=&quot;Anchor link for: xie-lou-de-lu-jing-he-worker-zen-mo-dang&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Vaultwarden 原本的流程很直接：伺服器看到一筆帳號要顯示 icon，就自己發 request 去對方網站抓 favicon。這裡有兩個資訊會外流：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;伺服器的 egress IP，暴露給每一個存過密碼的網站&lt;/li&gt;
&lt;li&gt;如果換成 &lt;code&gt;duckduckgo&lt;/code&gt;／&lt;code&gt;google&lt;/code&gt;／&lt;code&gt;bitwarden&lt;/code&gt;，變成把整批網域查詢丟給第三方&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我的做法是插入一層代理，而且刻意不是「重新導向」而是「代理抓取」：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Client (web vault / 瀏覽器套件 / App)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   │  GET /icons/github.com/icon.png&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   ▼&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Vaultwarden ──302/307 redirect──▶ https://vaultwarden-icons.example.com/github.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   │  （伺服器本身完全不發出對外請求）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   ▼  client 跟著 redirect 走&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Cloudflare Worker ──fetch──▶ github.com（解析 &amp;lt;link rel=icon&amp;gt; + /favicon.ico）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   │  回傳的是圖片的 bytes，不是第二次 redirect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   ▼                                 ▲ 目標網站只看得到 Cloudflare 的 IP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Client 顯示 icon&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;關鍵在最後一步：Worker 是把抓到的圖片內容直接回傳給 client，而不是再轉一次導向讓 client 自己去對方網站抓。這樣目標網站從頭到尾只看得到 Cloudflare 的 IP，伺服器和使用者的 IP 都沒有曝光。它不是 forward proxy（不能當 &lt;code&gt;HTTPS_PROXY&lt;/code&gt; 用，Cloudflare Worker 做不到那種層級的轉發），單純是換掉 Vaultwarden 的 &lt;code&gt;ICON_SERVICE&lt;/code&gt; 指向。&lt;/p&gt;
&lt;h2 id=&quot;zen-mo-zhua-tu-shi&quot;&gt;怎麼抓圖示&lt;a class=&quot;zola-anchor&quot; href=&quot;#zen-mo-zhua-tu-shi&quot; aria-label=&quot;Anchor link for: zen-mo-zhua-tu-shi&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;對 &lt;code&gt;/&amp;lt;domain&amp;gt;&lt;/code&gt; 的請求，Worker 走四個步驟：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;驗證 host（&lt;code&gt;src/ssrf.ts&lt;/code&gt;），擋掉 IP 字面量、&lt;code&gt;localhost&lt;/code&gt;、單一標籤網域、&lt;code&gt;.local&lt;/code&gt;／&lt;code&gt;.internal&lt;/code&gt; 這類保留 TLD，還有自訂的封鎖清單。Worker 是公開端點，每次都要重新驗證，不能假設呼叫端已經過濾過。&lt;/li&gt;
&lt;li&gt;找候選圖示（&lt;code&gt;src/favicon.ts&lt;/code&gt;），抓目標網站的 HTML，用 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/&quot;&gt;&lt;code&gt;HTMLRewriter&lt;/code&gt;&lt;/a&gt; 解析 &lt;code&gt;&amp;lt;link rel=&quot;icon&quot;&amp;gt;&lt;/code&gt;／&lt;code&gt;apple-touch-icon&lt;/code&gt;，按宣告的尺寸排序，找不到就 fallback 到 &lt;code&gt;/favicon.ico&lt;/code&gt;。每一個解析出來的 href 都要再過一次 SSRF 驗證，不會因為是網站自己說的連結就跳過檢查。&lt;/li&gt;
&lt;li&gt;驗證回應內容（&lt;code&gt;src/sanitize.ts&lt;/code&gt;），點陣圖直接回傳；SVG 用 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://crates.io/crates/svg-hush&quot;&gt;&lt;code&gt;svg-hush&lt;/code&gt;&lt;/a&gt; 編譯成 WASM 做消毒（跟 Vaultwarden 內部用的是同一套 allowlist 邏輯），再包一層嚴格的 &lt;code&gt;Content-Security-Policy&lt;/code&gt; sandbox 送出去，同時限制檔案大小上限與逾時秒數。&lt;/li&gt;
&lt;li&gt;邊緣快取，用 Cloudflare 的 Cache API 存下結果並附上 &lt;code&gt;Cache-Control&lt;/code&gt;。找不到圖示或 host 被擋下時，回傳一個可被快取的 404，讓各家 client 顯示自己內建的預設圖示，而不是硬塞一張來路不明或空白的圖。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;SVG 那一步值得多說一句：favicon 來源是不受信任的外部內容，SVG 又能夾帶 script 和外部參照，直接回傳給瀏覽器等於幫使用者開了一個小型 XSS 入口。用 WASM 消毒加上 sandbox CSP 是防禦縱深，兩層都要中才會出事。&lt;/p&gt;
&lt;h2 id=&quot;she-ding&quot;&gt;設定&lt;a class=&quot;zola-anchor&quot; href=&quot;#she-ding&quot; aria-label=&quot;Anchor link for: she-ding&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;wrangler.jsonc&lt;/code&gt; 的 &lt;code&gt;vars&lt;/code&gt; 裡可調：&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;變數&lt;/th&gt;&lt;th&gt;預設值&lt;/th&gt;&lt;th&gt;說明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;MAX_ICON_BYTES&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;524288&lt;/code&gt;&lt;/td&gt;&lt;td&gt;超過這個大小的 favicon 直接拒絕&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;FETCH_TIMEOUT_MS&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;5000&lt;/code&gt;&lt;/td&gt;&lt;td&gt;對外抓取的逾時（毫秒）&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;CACHE_TTL_SECONDS&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;604800&lt;/code&gt;&lt;/td&gt;&lt;td&gt;邊緣快取存活時間（預設 7 天）&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;BLOCKED_SUFFIXES&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&quot;&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;額外要擋的網域後綴，逗號分隔&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;FALLBACK_ICON_SERVICE&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&quot;&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;預設關閉。自家解析失敗時，由 Worker（不是 client）向第三方要圖：&lt;code&gt;duckduckgo&lt;/code&gt;、&lt;code&gt;google&lt;/code&gt;、&lt;code&gt;bitwarden&lt;/code&gt;，或自訂 &lt;code&gt;https://…/{}&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;FALLBACK_ICON_SERVICE&lt;/code&gt; 這個設計挺務實：預設不用任何第三方，但保留一個逃生口。就算開了，也是 Worker 在伺服器端幫你去查，client 的 IP 一樣不會外流，犧牲的只是「這個網域曾經被查過圖示」這件事本身的隱私。&lt;/p&gt;
&lt;h2 id=&quot;bu-shu&quot;&gt;部署&lt;a class=&quot;zola-anchor&quot; href=&quot;#bu-shu&quot; aria-label=&quot;Anchor link for: bu-shu&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;npm&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;npx&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; wrangler&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; login&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 想綁自訂網域的話，在 wrangler.jsonc 加：&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;   &amp;quot;routes&amp;quot;: [{ &amp;quot;pattern&amp;quot;: &amp;quot;vaultwarden-icons.example.com&amp;quot;, &amp;quot;custom_domain&amp;quot;: true }]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;npm&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; deploy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;部署完，Vaultwarden 端只要改環境變數，完全不用碰程式碼：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;ICON_SERVICE&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;https://vaultwarden-icons.example.com/{}   &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 恰好一個 {} 預留位置&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;ICON_REDIRECT_CODE&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;307                                   &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 想讓 client 快取這個 redirect 就用 308&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vaultwarden 會自動根據這個 URL 前綴推導出 CSP 的 &lt;code&gt;img-src&lt;/code&gt;，web vault 不用再額外設定就能正常載入圖示。&lt;/p&gt;
&lt;h2 id=&quot;an-quan-she-ji&quot;&gt;安全設計&lt;a class=&quot;zola-anchor&quot; href=&quot;#an-quan-she-ji&quot; aria-label=&quot;Anchor link for: an-quan-she-ji&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;專案 README 把安全模型攤開講：只接受公開、以名稱解析的 http(s) host，IP 字面量與內部／保留網域在進入端與每個解析出來的 icon href 都會再擋一次；SVG 用 &lt;code&gt;svg-hush&lt;/code&gt;（WASM）去除 script 與外部參照，再包 &lt;code&gt;default-src &#39;none&#39;; sandbox&lt;/code&gt; 的 CSP，兩層防禦；端點本身是公開的（client 透過 redirect 到達，沒辦法要求帶認證標頭），回應只允許圖片型別並限制大小，README 也建議在 Worker route 上另外掛一條 Cloudflare WAF rate-limit 規則擋濫用。&lt;/p&gt;
&lt;p&gt;它把能力邊界說得很白：能藏住伺服器和 client 的 IP，也能不依賴第三方圖示服務，但不能取代真正的 forward proxy。Vaultwarden 內部的 fetcher 如果要走 &lt;code&gt;HTTPS_PROXY&lt;/code&gt;，還是得靠 VPS、Tor 或 WARP 那類真正的代理，Cloudflare Worker 在這個角色上幫不上忙。&lt;/p&gt;
&lt;h2 id=&quot;shou-wei&quot;&gt;收尾&lt;a class=&quot;zola-anchor&quot; href=&quot;#shou-wei&quot; aria-label=&quot;Anchor link for: shou-wei&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;我的取捨很清楚：不想把伺服器 IP 送給每個存過密碼的網站，也不想把查詢過的網域清單交給 DuckDuckGo 或 Google，所以自己在邊緣節點跑一層薄薄的代理。整個服務只靠 &lt;code&gt;wrangler.jsonc&lt;/code&gt; 的環境變數設定，不需要 KV 或 D1，Vaultwarden 端也只是改一行 &lt;code&gt;ICON_SERVICE&lt;/code&gt;，不用動到程式碼。&lt;/p&gt;
&lt;p&gt;對其他自建 Vaultwarden 的人來說，應該也是目前 favicon 隱私問題裡最省事的解法，歡迎直接拿去用。&lt;/p&gt;
&lt;p&gt;專案原始碼：&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/vaultwarden-icons-worker&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>用 MTools BLE 一鍵升級 GAT562 的 Meshtastic 韌體</title>
		<published>2026-06-02T00:00:00+08:00</published>
		<updated>2026-06-02T00:00:00+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/mtools-ble-gat562/"/>
		<id>https://blog.2ac.io/posts/mtools-ble-gat562/</id>
    
    <summary>&lt;figure&gt;
&lt;img src=&quot;/images/mtools-ble-gat562-og.webp&quot; alt=&quot;用 MTools BLE 一鍵升級 GAT562 的 Meshtastic 韌體&quot; height=&quot;669&quot; width=&quot;1200&quot;&gt;&lt;/figure&gt;
&lt;p&gt;GAT562 是基於 nRF52 的 Meshtastic 節點，韌體升級走 Legacy DFU。傳統做法是去 Meshtastic 官網翻對應的 UF2 / Zip，再開 nRF Connect 配對、進 DFU mode、推 package。每次升級重複一遍，挺煩。&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://apps.apple.com/jp/app/mtools-ble-rfid-reader/id1531345398&quot;&gt;MTools BLE&lt;/a&gt; 把整個流程壓成一鍵，從打開 App 到升完大概 3 分鐘。&lt;/p&gt;</summary>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/mtools-ble-gat562/">&lt;figure&gt;
&lt;img src=&quot;/images/mtools-ble-gat562-og.webp&quot; alt=&quot;用 MTools BLE 一鍵升級 GAT562 的 Meshtastic 韌體&quot; height=&quot;669&quot; width=&quot;1200&quot;&gt;&lt;/figure&gt;
&lt;p&gt;GAT562 是基於 nRF52 的 Meshtastic 節點，韌體升級走 Legacy DFU。傳統做法是去 Meshtastic 官網翻對應的 UF2 / Zip，再開 nRF Connect 配對、進 DFU mode、推 package。每次升級重複一遍，挺煩。&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://apps.apple.com/jp/app/mtools-ble-rfid-reader/id1531345398&quot;&gt;MTools BLE&lt;/a&gt; 把整個流程壓成一鍵，從打開 App 到升完大概 3 分鐘。&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;sheng-ji-bu-zou&quot;&gt;升級步驟&lt;a class=&quot;zola-anchor&quot; href=&quot;#sheng-ji-bu-zou&quot; aria-label=&quot;Anchor link for: sheng-ji-bu-zou&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;開 MTools BLE，進「工具 → Meshtastic®」&lt;/li&gt;
&lt;li&gt;從清單裡選 GAT562，App 自己會抓對應的韌體&lt;/li&gt;
&lt;li&gt;用藍牙連上你的 GAT562&lt;/li&gt;
&lt;li&gt;點「開始上傳固件」，等它跑完&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;裝置會自己切到 Legacy DFU mode，傳完重啟就好了。UF2 不用準備，region 也不用記。&lt;/p&gt;
&lt;h2 id=&quot;xia-zai&quot;&gt;下載&lt;a class=&quot;zola-anchor&quot; href=&quot;#xia-zai&quot; aria-label=&quot;Anchor link for: xia-zai&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;iOS：&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://apps.apple.com/jp/app/mtools-ble-rfid-reader/id1531345398&quot;&gt;App Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Android：&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://play.google.com/store/apps/details?id=com.mtoolstec.mtoolsLite&amp;amp;hl=ja&quot;&gt;Google Play&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MTools 原本是個 RFID / BLE 通用工具，Meshtastic 是後來才加進去的功能模組。其他 nRF52 系列的 Meshtastic 板子也吃，操作流程都一樣。&lt;/p&gt;
&lt;p&gt;我手上的 GAT562 拿這個 App 升過幾次，目前都沒中過。除非有特別理由要走電腦端 DFU，不然手機開一下就好。&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>用 Cloudflare Workers 自動簽到 PTT 並推送到 Telegram</title>
		<published>2026-05-06T00:00:00+08:00</published>
		<updated>2026-05-06T00:00:00+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/ptt-autosign-worker/"/>
		<id>https://blog.2ac.io/posts/ptt-autosign-worker/</id>
    
    <summary>&lt;p&gt;PTT 的鄉民大概都遇過：太久沒登入帳號就被砍。社群早期有 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/tasi788/PTTAutoSign&quot;&gt;PTTAutoSign&lt;/a&gt; 這類工具，跑在 VPS 或 Raspberry Pi 上排程登入。可是為了一個每天只跑一次的小腳本去顧一台機器，實在不太划算。&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/ptt-autosign-worker&quot;&gt;ptt-autosign-worker&lt;/a&gt; 把這件事搬到 Cloudflare Workers 上：每天 02:30 UTC 由 Cron Trigger 觸發，自動登入 PTT、抓登入資訊、丟到 Telegram。不用 VPS，不用 Docker，crontab 也省了，Free plan 就跑得起來。&lt;/p&gt;</summary>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/ptt-autosign-worker/">&lt;p&gt;PTT 的鄉民大概都遇過：太久沒登入帳號就被砍。社群早期有 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/tasi788/PTTAutoSign&quot;&gt;PTTAutoSign&lt;/a&gt; 這類工具，跑在 VPS 或 Raspberry Pi 上排程登入。可是為了一個每天只跑一次的小腳本去顧一台機器，實在不太划算。&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/ptt-autosign-worker&quot;&gt;ptt-autosign-worker&lt;/a&gt; 把這件事搬到 Cloudflare Workers 上：每天 02:30 UTC 由 Cron Trigger 觸發，自動登入 PTT、抓登入資訊、丟到 Telegram。不用 VPS，不用 Docker，crontab 也省了，Free plan 就跑得起來。&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;wei-shen-mo-shi-cloudflare-workers&quot;&gt;為什麼是 Cloudflare Workers&lt;a class=&quot;zola-anchor&quot; href=&quot;#wei-shen-mo-shi-cloudflare-workers&quot; aria-label=&quot;Anchor link for: wei-shen-mo-shi-cloudflare-workers&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;原版的 PTTAutoSign 是 Python 寫的，吃 &lt;code&gt;PyPtt&lt;/code&gt; 這類函式庫，內部會起一個 terminal emulator 處理 PTT 的 ANSI escape code 和狀態切換。要搬到 Workers 有幾個阻礙：Workers 沒有 Node.js 完整 runtime，&lt;code&gt;nodejs_compat&lt;/code&gt; 也包不到 terminal emulator；相依太重的話 cold start 會明顯變慢；而且每次執行有 wall-clock 上限，Free plan 30 秒就要結束。&lt;/p&gt;
&lt;p&gt;所以這版乾脆不用 terminal emulator，直接打 PTT 的 WebSocket gateway (&lt;code&gt;wss://ws.ptt.cc/bbs/&lt;/code&gt;)，靠一個小型的 login state machine 推進流程。整個 Worker 零 runtime 依賴，部署檔很小，cold start 幾乎感覺不到。&lt;/p&gt;
&lt;h2 id=&quot;ta-zuo-liao-shen-mo&quot;&gt;它做了什麼&lt;a class=&quot;zola-anchor&quot; href=&quot;#ta-zuo-liao-shen-mo&quot; aria-label=&quot;Anchor link for: ta-zuo-liao-shen-mo&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;每天排程觸發後，Worker 會對 &lt;code&gt;PTT_ACCOUNTS&lt;/code&gt; 中每個帳號執行：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;連上 &lt;code&gt;wss://ws.ptt.cc/bbs/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;送出 &lt;code&gt;username,\rpassword\r&lt;/code&gt;（後面那個逗號是 PTT 的 UTF-8 協商觸發字元）&lt;/li&gt;
&lt;li&gt;走過 login state machine，處理重複登入踢人、any-key 提示等&lt;/li&gt;
&lt;li&gt;進入 &lt;code&gt;T → Q → username&lt;/code&gt; 抓「登入次數」、「信箱狀況」，並從歡迎橫幅抓「上次登入 IP」&lt;/li&gt;
&lt;li&gt;送 &lt;code&gt;G\rY\r&lt;/code&gt; 登出&lt;/li&gt;
&lt;li&gt;把結果丟到 Telegram&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;成功的訊息長這樣：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;✅ PTT alice 已成功簽到&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;📆 已登入 1234 天&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;📫 信箱中已無新信件&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;🌐 上次登入來源 172.69.194.125&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#ptt #20260504&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;失敗時會帶上 reason，方便排查：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;❌ PTT alice 簽到失敗&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;原因：wrong_password&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#ptt #20260504&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;big5-yu-utf-8-bing-xing-jie-ma&quot;&gt;Big5 與 UTF-8 並行解碼&lt;a class=&quot;zola-anchor&quot; href=&quot;#big5-yu-utf-8-bing-xing-jie-ma&quot; aria-label=&quot;Anchor link for: big5-yu-utf-8-bing-xing-jie-ma&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;PTT 的編碼是個老問題：使用者可以切 Big5 或 UTF-8 模式，連登入過程中都可能切換。傳統做法是先猜編碼、解錯了再 fallback，但這在串流式的 WebSocket 上特別麻煩，狀態機可能因為解錯字就錯過某個關鍵 prompt。&lt;/p&gt;
&lt;p&gt;這版的解法很土砲：直接開兩個 TextDecoder，Big5 跟 UTF-8 並行解碼，state machine 對兩條解碼結果各比對一次，誰先看到 prompt 就誰過。PTT 當下用什麼編碼都能正確推進。&lt;/p&gt;
&lt;h2 id=&quot;free-plan-de-shi-jian-yu-suan&quot;&gt;Free Plan 的時間預算&lt;a class=&quot;zola-anchor&quot; href=&quot;#free-plan-de-shi-jian-yu-suan&quot; aria-label=&quot;Anchor link for: free-plan-de-shi-jian-yu-suan&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Free plan 的 scheduled handler 有 30 秒 wall-clock 上限。每個帳號的時間預算抓起來大概是 10–17 秒，最壞情況 22 秒。所以 2 個帳號剛好塞進 Free plan，3 個以上比較建議升 Paid plan。&lt;/p&gt;
&lt;p&gt;PTT 那邊改了 prompt 文字導致狀態機卡住的話，超過 15 秒會直接 timeout，不會把整個 handler 拖死。&lt;/p&gt;
&lt;h2 id=&quot;lu-you&quot;&gt;路由&lt;a class=&quot;zola-anchor&quot; href=&quot;#lu-you&quot; aria-label=&quot;Anchor link for: lu-you&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Worker 開了三個路由方便操作和除錯：&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Route&lt;/th&gt;&lt;th&gt;用途&lt;/th&gt;&lt;th&gt;認證&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;GET /spike&lt;/code&gt;&lt;/td&gt;&lt;td&gt;開 WS 連到 ws.ptt.cc，把歡迎橫幅原文 dump 出來&lt;/td&gt;&lt;td&gt;無&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;GET /run?secret=&amp;lt;RUN_TOKEN&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;手動觸發一次（等同 cron）&lt;/td&gt;&lt;td&gt;RUN_TOKEN&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;scheduled()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;每天 02:30 UTC&lt;/td&gt;&lt;td&gt;Cloudflare Cron Trigger&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;/spike&lt;/code&gt; 在 PTT 改版時特別有用，直接看橫幅原文比較容易判斷 prompt 字串怎麼變。&lt;code&gt;/run&lt;/code&gt; 則是部署後的 smoke test 入口：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;curl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;https://ptt-autosign.&amp;lt;sub&amp;gt;.workers.dev/run?secret=&amp;lt;RUN_TOKEN&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; jq&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;bu-shu&quot;&gt;部署&lt;a class=&quot;zola-anchor&quot; href=&quot;#bu-shu&quot; aria-label=&quot;Anchor link for: bu-shu&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;不需要 KV，也不需要 D1，純粹靠 secrets：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;npm&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;npx&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; wrangler&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; login&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;npx&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; wrangler&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; deploy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;npx&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; wrangler&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; secret&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; put&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;npx&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; wrangler&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; secret&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; put&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; TELEGRAM_CHAT_ID&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;npx&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; wrangler&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; secret&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; put&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; PTT_ACCOUNTS&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; JSON: [{&amp;quot;id&amp;quot;:&amp;quot;alice&amp;quot;,&amp;quot;password&amp;quot;:&amp;quot;...&amp;quot;}]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;npx&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; wrangler&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; secret&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; put&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; RUN_TOKEN&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;       #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 任意亂數字串&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;PTT_ACCOUNTS&lt;/code&gt; 是 JSON 陣列，一次部署就能幫一整批帳號簽到。&lt;/p&gt;
&lt;h2 id=&quot;yi-zhi-de-ruan-le&quot;&gt;已知的軟肋&lt;a class=&quot;zola-anchor&quot; href=&quot;#yi-zhi-de-ruan-le&quot; aria-label=&quot;Anchor link for: yi-zhi-de-ruan-le&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;幾個 PTT 改版時可能會踩到的點，README 也直接列了出來：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;getUser()&lt;/code&gt; 用的 regex &lt;code&gt;登入次數》(\d+)&lt;/code&gt; 和 &lt;code&gt;信箱[^》]*》&lt;/code&gt; 是根據經典版 user card 推測的，PTT 換 layout 就抓不到。不過抓不到只會讓 Telegram 訊息少幾行，登入本身不會失敗。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;lastLoginIp&lt;/code&gt; 同樣是 best-effort。第一次登入沒有 IP（PTT 顯示「歡迎您第一次拜訪本站」），那行 &lt;code&gt;🌐&lt;/code&gt; 會直接省掉。另外，這裡抓到的 IP 是 PTT 看到的入站 IP，對 Cloudflare Workers 來說會是 CF 的 egress IP（&lt;code&gt;172.69.x.x&lt;/code&gt; 那類），不是你家的對外 IP。&lt;/p&gt;
&lt;p&gt;Cron 精準度方面，Cloudflare 的 cron 會在指定時間的數秒內觸發，沒有保證準時。在意被偵測規律的話，可以在 &lt;code&gt;scheduled()&lt;/code&gt; 裡自己加 jitter。&lt;/p&gt;
&lt;h2 id=&quot;shou-wei&quot;&gt;收尾&lt;a class=&quot;zola-anchor&quot; href=&quot;#shou-wei&quot; aria-label=&quot;Anchor link for: shou-wei&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;把每天跑一次的小工作搬到 Cloudflare Workers 上挺合理：cron trigger、secrets 管理都內建，不用自己顧伺服器，Free plan 足夠。比起再開一台 VPS 或佔用家裡的 Raspberry Pi，維運成本基本上是零。&lt;/p&gt;
&lt;p&gt;對 PTTAutoSign 的使用者來說，這個 port 保留了原版的核心行為（登入 → 抓資訊 → 推 Telegram），但所有 runtime 依賴都拿掉了。整個專案只有三個 TypeScript 檔案：&lt;code&gt;index.ts&lt;/code&gt;、&lt;code&gt;ptt.ts&lt;/code&gt;、&lt;code&gt;telegram.ts&lt;/code&gt;。要客製化或加新功能都不難。&lt;/p&gt;
&lt;p&gt;專案原始碼：&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/ptt-autosign-worker&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>gem-audit — 用 Rust 打造的 Ruby 相依套件安全稽核工具</title>
		<published>2026-04-15T00:00:00+08:00</published>
		<updated>2026-04-15T00:00:00+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/gem-audit/"/>
		<id>https://blog.2ac.io/posts/gem-audit/</id>
    
    <summary>&lt;p&gt;Ruby 專案要做相依套件安全稽核，大多數人第一個想到的就是 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/rubysec/bundler-audit&quot;&gt;bundler-audit&lt;/a&gt;，1.27 億次下載算是 Ruby 生態系的標配。不過用久了會發現幾個痛點：要完整的 Ruby 執行環境、要系統 git、啟動慢，而且 exit code 只有兩種，沒辦法分「有漏洞」跟「指令掛了」。&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/gem-audit&quot;&gt;gem-audit&lt;/a&gt; 是 Rust 重寫的版本，編譯成單一 binary，Ruby、Bundler、git 都不用，掃描速度快了 &lt;strong&gt;15 到 41 倍&lt;/strong&gt;。&lt;/p&gt;</summary>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/gem-audit/">&lt;p&gt;Ruby 專案要做相依套件安全稽核，大多數人第一個想到的就是 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/rubysec/bundler-audit&quot;&gt;bundler-audit&lt;/a&gt;，1.27 億次下載算是 Ruby 生態系的標配。不過用久了會發現幾個痛點：要完整的 Ruby 執行環境、要系統 git、啟動慢，而且 exit code 只有兩種，沒辦法分「有漏洞」跟「指令掛了」。&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/gem-audit&quot;&gt;gem-audit&lt;/a&gt; 是 Rust 重寫的版本，編譯成單一 binary，Ruby、Bundler、git 都不用，掃描速度快了 &lt;strong&gt;15 到 41 倍&lt;/strong&gt;。&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;yu-bundler-audit-de-bi-jiao&quot;&gt;與 bundler-audit 的比較&lt;a class=&quot;zola-anchor&quot; href=&quot;#yu-bundler-audit-de-bi-jiao&quot; aria-label=&quot;Anchor link for: yu-bundler-audit-de-bi-jiao&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;兩者的差異：&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;gem-audit&lt;/th&gt;&lt;th&gt;bundler-audit&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;語言&lt;/td&gt;&lt;td&gt;Rust&lt;/td&gt;&lt;td&gt;Ruby&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;執行環境&lt;/td&gt;&lt;td&gt;單一 binary，零依賴&lt;/td&gt;&lt;td&gt;需要 Ruby + Bundler + git&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;授權&lt;/td&gt;&lt;td&gt;MIT&lt;/td&gt;&lt;td&gt;GPL-3.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;輸出格式&lt;/td&gt;&lt;td&gt;text、json&lt;/td&gt;&lt;td&gt;text、json、junit&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Ruby 版本掃描&lt;/td&gt;&lt;td&gt;有（含 JRuby、mRuby）&lt;/td&gt;&lt;td&gt;無&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;自動修復&lt;/td&gt;&lt;td&gt;&lt;code&gt;--fix&lt;/code&gt; 內建&lt;/td&gt;&lt;td&gt;需額外安裝 bundler-audit-fix&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Exit codes&lt;/td&gt;&lt;td&gt;4 種（正常/漏洞/錯誤/資料庫過期）&lt;/td&gt;&lt;td&gt;2 種（正常/異常）&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;設定檔&lt;/td&gt;&lt;td&gt;&lt;code&gt;.gem-audit.yml&lt;/code&gt;（相容 &lt;code&gt;.bundler-audit.yml&lt;/code&gt;）&lt;/td&gt;&lt;td&gt;&lt;code&gt;.bundler-audit.yml&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Inline 忽略理由&lt;/td&gt;&lt;td&gt;支援 YAML 註解&lt;/td&gt;&lt;td&gt;不支援&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;資料庫管理&lt;/td&gt;&lt;td&gt;內建 git 操作（gix）&lt;/td&gt;&lt;td&gt;依賴系統 git&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;xiao-neng-chai-yi&quot;&gt;效能差異&lt;a class=&quot;zola-anchor&quot; href=&quot;#xiao-neng-chai-yi&quot; aria-label=&quot;Anchor link for: xiao-neng-chai-yi&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;在 Apple Silicon 上的實測數據：&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;操作&lt;/th&gt;&lt;th&gt;gem-audit&lt;/th&gt;&lt;th&gt;bundler-audit&lt;/th&gt;&lt;th&gt;倍數&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;掃描（有漏洞）&lt;/td&gt;&lt;td&gt;7.0 ms&lt;/td&gt;&lt;td&gt;216.5 ms&lt;/td&gt;&lt;td&gt;&lt;strong&gt;31x&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;掃描（無漏洞）&lt;/td&gt;&lt;td&gt;16.6 ms&lt;/td&gt;&lt;td&gt;250.2 ms&lt;/td&gt;&lt;td&gt;&lt;strong&gt;15x&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;啟動時間&lt;/td&gt;&lt;td&gt;4.6 ms&lt;/td&gt;&lt;td&gt;188.4 ms&lt;/td&gt;&lt;td&gt;&lt;strong&gt;41x&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;bundler-audit 的啟動成本來自 Ruby 直譯器、gem 載入和 Bundler 的 &lt;code&gt;LockfileParser&lt;/code&gt;。gem-audit 編譯成原生碼，啟動幾乎沒額外開銷。&lt;/p&gt;
&lt;h3 id=&quot;wei-shen-mo-bundler-audit-xu-yao-git&quot;&gt;為什麼 bundler-audit 需要 git&lt;a class=&quot;zola-anchor&quot; href=&quot;#wei-shen-mo-bundler-audit-xu-yao-git&quot; aria-label=&quot;Anchor link for: wei-shen-mo-bundler-audit-xu-yao-git&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;bundler-audit 的漏洞資料庫 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/rubysec/ruby-advisory-db&quot;&gt;ruby-advisory-db&lt;/a&gt; 是一個 git repository。下載和更新都呼叫系統的 &lt;code&gt;git&lt;/code&gt; 指令。所以在精簡的 Docker image 或沒裝 git 的 CI 環境中，得另外把 git 補上才能用。&lt;/p&gt;
&lt;p&gt;gem-audit 改用 Rust 的 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/Byron/gitoxide&quot;&gt;gix&lt;/a&gt; 直接處理 git 操作，系統沒 git 也照跑。&lt;/p&gt;
&lt;h2 id=&quot;gong-neng-jie-shao&quot;&gt;功能介紹&lt;a class=&quot;zola-anchor&quot; href=&quot;#gong-neng-jie-shao&quot; aria-label=&quot;Anchor link for: gong-neng-jie-shao&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id=&quot;lou-dong-sao-miao&quot;&gt;漏洞掃描&lt;a class=&quot;zola-anchor&quot; href=&quot;#lou-dong-sao-miao&quot; aria-label=&quot;Anchor link for: lou-dong-sao-miao&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;掃描當前目錄的 &lt;code&gt;Gemfile.lock&lt;/code&gt;：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gem-audit&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;輸出會列出每個有問題的套件、CVE 編號、嚴重等級和修復建議：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Name: rails&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Version: 5.2.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Advisory: CVE-2023-1234&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Criticality: High&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;URL: https://...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Title: Remote Code Execution&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Solution: Update to &amp;gt;= 6.0.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;ruby-ban-ben-sao-miao&quot;&gt;Ruby 版本掃描&lt;a class=&quot;zola-anchor&quot; href=&quot;#ruby-ban-ben-sao-miao&quot; aria-label=&quot;Anchor link for: ruby-ban-ben-sao-miao&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;bundler-audit 沒有這個功能。gem-audit 會額外檢查 &lt;code&gt;Gemfile.lock&lt;/code&gt; 裡記錄的 Ruby 版本本身是否有已知漏洞，CRuby、JRuby、mRuby 都涵蓋。&lt;/p&gt;
&lt;h3 id=&quot;bu-an-quan-lai-yuan-zhen-ce&quot;&gt;不安全來源偵測&lt;a class=&quot;zola-anchor&quot; href=&quot;#bu-an-quan-lai-yuan-zhen-ce&quot; aria-label=&quot;Anchor link for: bu-an-quan-lai-yuan-zhen-ce&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Gemfile.lock&lt;/code&gt; 裡如果有 &lt;code&gt;http://&lt;/code&gt; 或 &lt;code&gt;git://&lt;/code&gt; 這種未加密來源，gem-audit 會發警告。&lt;/p&gt;
&lt;h3 id=&quot;yan-zhong-deng-ji-shai-xuan&quot;&gt;嚴重等級篩選&lt;a class=&quot;zola-anchor&quot; href=&quot;#yan-zhong-deng-ji-shai-xuan&quot; aria-label=&quot;Anchor link for: yan-zhong-deng-ji-shai-xuan&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;只想看高風險以上的：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gem-audit&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; check&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-severity&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; high&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;支援的等級：none、low、medium、high、critical。&lt;/p&gt;
&lt;h3 id=&quot;zi-dong-xiu-fu&quot;&gt;自動修復&lt;a class=&quot;zola-anchor&quot; href=&quot;#zi-dong-xiu-fu&quot; aria-label=&quot;Anchor link for: zi-dong-xiu-fu&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 預覽修復內容&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gem-audit&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; check&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-fix&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-dry-run&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 直接修復&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gem-audit&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; check&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-fix&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;gem-audit 會產生對應的 &lt;code&gt;bundle update&lt;/code&gt; 指令，並直接修補 &lt;code&gt;Gemfile.lock&lt;/code&gt;。bundler-audit 本身沒有修復功能，得另外裝 &lt;code&gt;bundler-audit-fix&lt;/code&gt; gem。&lt;/p&gt;
&lt;h3 id=&quot;yu-yi-hua-exit-codes&quot;&gt;語意化 Exit Codes&lt;a class=&quot;zola-anchor&quot; href=&quot;#yu-yi-hua-exit-codes&quot; aria-label=&quot;Anchor link for: yu-yi-hua-exit-codes&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Exit Code&lt;/th&gt;&lt;th&gt;意義&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;安全，無漏洞&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;發現漏洞&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;執行錯誤&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;資料庫過期&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;bundler-audit 只有 0 和 1，分不出「有漏洞」和「指令掛了」，CI 想做精準錯誤處理就不太順。&lt;/p&gt;
&lt;h2 id=&quot;she-ding-dang&quot;&gt;設定檔&lt;a class=&quot;zola-anchor&quot; href=&quot;#she-ding-dang&quot; aria-label=&quot;Anchor link for: she-ding-dang&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;.gem-audit.yml&lt;/code&gt;、bundler-audit 的 &lt;code&gt;.bundler-audit.yml&lt;/code&gt; 都吃：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;gnore&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; C&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;VE-2023-1234&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 已評估，不影響我們的使用情境&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; G&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;HSA-xxxx-yyyy-zzzz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ax_db_age_days&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 7&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;忽略清單可以加行內註解，順手把為什麼忽略某個 CVE 寫下來。&lt;/p&gt;
&lt;p&gt;懶得手動維護忽略清單的話，&lt;code&gt;--write-ignore&lt;/code&gt; 會把目前偵測到的漏洞直接寫入設定檔：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gem-audit&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; check&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-write-ignore&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;ci-zheng-he&quot;&gt;CI 整合&lt;a class=&quot;zola-anchor&quot; href=&quot;#ci-zheng-he&quot; aria-label=&quot;Anchor link for: ci-zheng-he&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id=&quot;github-actions&quot;&gt;GitHub Actions&lt;a class=&quot;zola-anchor&quot; href=&quot;#github-actions&quot; aria-label=&quot;Anchor link for: github-actions&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt; u&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ses&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; 7&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;a6163/gem-audit-action@v1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;docker&quot;&gt;Docker&lt;a class=&quot;zola-anchor&quot; href=&quot;#docker&quot; aria-label=&quot;Anchor link for: docker&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-rm&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;v&lt;/span&gt;&lt;span&gt; $(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;pwd&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;:/workspace&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ghcr.io/7a6163/gem-audit:latest&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; check&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-update&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;yan-ge-mo-shi&quot;&gt;嚴格模式&lt;a class=&quot;zola-anchor&quot; href=&quot;#yan-ge-mo-shi&quot; aria-label=&quot;Anchor link for: yan-ge-mo-shi&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;CI 上比較推薦的組合是 &lt;code&gt;--strict&lt;/code&gt; + &lt;code&gt;--fail-on-stale&lt;/code&gt;：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gem-audit&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; check&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-update&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-max-db-age&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-fail-on-stale&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-strict&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;資料庫超過一天沒更新、或有任何解析警告，pipeline 就會失敗。再加 &lt;code&gt;--format json --output results.json&lt;/code&gt; 可以把結果存檔做後續分析。&lt;/p&gt;
&lt;h2 id=&quot;cong-bundler-audit-qian-yi&quot;&gt;從 bundler-audit 遷移&lt;a class=&quot;zola-anchor&quot; href=&quot;#cong-bundler-audit-qian-yi&quot; aria-label=&quot;Anchor link for: cong-bundler-audit-qian-yi&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;gem-audit 刻意維持向下相容：照吃 &lt;code&gt;.bundler-audit.yml&lt;/code&gt;、用同一份 ruby-advisory-db、CLI 介面風格也一致。&lt;/p&gt;
&lt;p&gt;基本上就是裝好 binary、把 CI 裡的 &lt;code&gt;bundle-audit&lt;/code&gt; 換成 &lt;code&gt;gem-audit&lt;/code&gt;。原本有 &lt;code&gt;.bundler-audit.yml&lt;/code&gt; 的話，連設定檔都不用改。&lt;/p&gt;
&lt;h2 id=&quot;shou-wei&quot;&gt;收尾&lt;a class=&quot;zola-anchor&quot; href=&quot;#shou-wei&quot; aria-label=&quot;Anchor link for: shou-wei&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;CI 還在跑 bundler-audit 的 Ruby 專案，gem-audit 算是一個方便的替代品。最實際的差別其實不是速度，是不用 Ruby — multi-stage Docker build 或非 Ruby 的 CI runner 上會差很多。授權是 MIT，要嵌進商業專案也比 GPL-3.0 好處理。&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>rubyfast — 用 Rust 重寫的 Ruby 效能 Linter，比原版快 162 倍</title>
		<published>2026-04-14T00:00:00+08:00</published>
		<updated>2026-04-14T00:00:00+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/rubyfast/"/>
		<id>https://blog.2ac.io/posts/rubyfast/</id>
    
    <summary>&lt;p&gt;寫 Ruby 的人大概都認識 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/DamirSvrtan/fasterer&quot;&gt;fasterer&lt;/a&gt; 這個 gem，會幫你找出常見的效能反模式。但專案越長越大之後，fasterer 自己的執行速度反而變成痛點。&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/rubyfast&quot;&gt;rubyfast&lt;/a&gt; 是用 Rust 把 fasterer 重寫了一次，19 條偵測規則照搬，但速度差異很有感：2,235 個 Ruby 檔案，原本 34.1 秒，現在 0.21 秒，&lt;strong&gt;快了 162 倍&lt;/strong&gt;。&lt;/p&gt;</summary>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/rubyfast/">&lt;p&gt;寫 Ruby 的人大概都認識 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/DamirSvrtan/fasterer&quot;&gt;fasterer&lt;/a&gt; 這個 gem，會幫你找出常見的效能反模式。但專案越長越大之後，fasterer 自己的執行速度反而變成痛點。&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/rubyfast&quot;&gt;rubyfast&lt;/a&gt; 是用 Rust 把 fasterer 重寫了一次，19 條偵測規則照搬，但速度差異很有感：2,235 個 Ruby 檔案，原本 34.1 秒，現在 0.21 秒，&lt;strong&gt;快了 162 倍&lt;/strong&gt;。&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;wei-shen-mo-yao-yong-rust-zhong-xie&quot;&gt;為什麼要用 Rust 重寫&lt;a class=&quot;zola-anchor&quot; href=&quot;#wei-shen-mo-yao-yong-rust-zhong-xie&quot; aria-label=&quot;Anchor link for: wei-shen-mo-yao-yong-rust-zhong-xie&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;fasterer 的瓶頸在 Ruby 的 parser 和單執行緒架構。rubyfast 換掉了這兩件事：parser 改用 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/ruby/prism&quot;&gt;ruby-prism&lt;/a&gt;（Ruby 3.3+ 之後的官方 parser）解析 AST，掃描則交給 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/rayon-rs/rayon&quot;&gt;rayon&lt;/a&gt; 平行跑。順便 Ruby runtime 也不用了，一個 binary 就能執行。&lt;/p&gt;
&lt;h2 id=&quot;neng-zhen-ce-shen-mo&quot;&gt;能偵測什麼&lt;a class=&quot;zola-anchor&quot; href=&quot;#neng-zhen-ce-shen-mo&quot; aria-label=&quot;Anchor link for: neng-zhen-ce-shen-mo&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;rubyfast 支援 19 種效能反模式偵測，以下列出幾個常見的：&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;反模式&lt;/th&gt;&lt;th&gt;建議替代&lt;/th&gt;&lt;th style=&quot;text-align: center&quot;&gt;可自動修復&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.shuffle.first&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;.sample&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;是&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.select{}.first&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;.detect{}&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;是&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.reverse.each&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;.reverse_each&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;是&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.keys.each&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;.each_key&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;是&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.map{}.flatten(1)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;.flat_map{}&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;是&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.gsub(&quot;x&quot;,&quot;y&quot;)&lt;/code&gt;（單字元）&lt;/td&gt;&lt;td&gt;&lt;code&gt;.tr(&quot;x&quot;,&quot;y&quot;)&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;是&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;for x in arr&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;arr.each&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;是&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.sort { |a,b| ... }&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;.sort_by&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;否&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;rescue NoMethodError&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;respond_to?&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;否&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;getter/setter 方法&lt;/td&gt;&lt;td&gt;&lt;code&gt;attr_reader&lt;/code&gt; / &lt;code&gt;attr_writer&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: center&quot;&gt;否&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;其中 8 種支援 &lt;code&gt;--fix&lt;/code&gt; 自動修復。修補完會再 parse 一次確認沒語法錯，才把檔案寫回去。&lt;/p&gt;
&lt;h2 id=&quot;an-zhuang-yu-shi-yong&quot;&gt;安裝與使用&lt;a class=&quot;zola-anchor&quot; href=&quot;#an-zhuang-yu-shi-yong&quot; aria-label=&quot;Anchor link for: an-zhuang-yu-shi-yong&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 透過 Cargo 安裝&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;cargo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; rubyfast&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 掃描目前目錄&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;rubyfast&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 掃描特定路徑&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;rubyfast&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; path/to/file.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;rubyfast&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; path/to/directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 自動修復&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;rubyfast&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-fix&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也有 Docker image 可以直接使用：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-rm&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;v&lt;/span&gt;&lt;span&gt; $(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;pwd&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;:/app&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ghcr.io/7a6163/rubyfast:latest&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;shu-chu-ge-shi&quot;&gt;輸出格式&lt;a class=&quot;zola-anchor&quot; href=&quot;#shu-chu-ge-shi&quot; aria-label=&quot;Anchor link for: shu-chu-ge-shi&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;三種格式對應不同情境：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 依檔案分組（預設）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;rubyfast&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-format&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; file&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; .&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 依規則分組，附數量統計&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;rubyfast&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-format&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; rule&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; .&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 一行一個 offense，適合 CI / grep / reviewdog&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;rubyfast&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-format&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; plain&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;she-ding-dang&quot;&gt;設定檔&lt;a class=&quot;zola-anchor&quot; href=&quot;#she-ding-dang&quot; aria-label=&quot;Anchor link for: she-ding-dang&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;支援 &lt;code&gt;.rubyfast.yml&lt;/code&gt; 或 &lt;code&gt;.fasterer.yml&lt;/code&gt;，可以停用特定規則或排除路徑：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;peedups&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;huffle_first_vs_sample&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;xclude_paths&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; v&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;endor/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; d&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;b/migrate/**&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;設定檔會自動往上層目錄搜尋，跟 &lt;code&gt;.rubocop.yml&lt;/code&gt; 的行為一致。&lt;/p&gt;
&lt;h2 id=&quot;inline-disable&quot;&gt;Inline Disable&lt;a class=&quot;zola-anchor&quot; href=&quot;#inline-disable&quot; aria-label=&quot;Anchor link for: inline-disable&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;跟 RuboCop 類似，可以用註解在程式碼中局部停用規則：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 單行停用&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;arr&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;shuffle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; rubyfast:disable shuffle_first_vs_sample&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 下一行停用&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; rubyfast:disable-next-line shuffle_first_vs_sample&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;arr&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;shuffle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;first&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 區塊停用&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; rubyfast:disable shuffle_first_vs_sample&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;arr&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;shuffle&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;first&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; rubyfast:enable shuffle_first_vs_sample&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也相容舊版 &lt;code&gt;fasterer:disable&lt;/code&gt; 語法。&lt;/p&gt;
&lt;h2 id=&quot;ci-zheng-he&quot;&gt;CI 整合&lt;a class=&quot;zola-anchor&quot; href=&quot;#ci-zheng-he&quot; aria-label=&quot;Anchor link for: ci-zheng-he&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;在 GitHub Actions 中可以搭配 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/rubyfast-action&quot;&gt;rubyfast-action&lt;/a&gt; 使用：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt; u&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ses&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; 7&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;a6163/rubyfast-action@v1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;搭配 &lt;code&gt;--format plain&lt;/code&gt; 輸出和 reviewdog，可以在 PR 上直接顯示 inline comments。&lt;/p&gt;
&lt;p&gt;GitLab CI 也能透過 Docker image 整合。&lt;/p&gt;
&lt;h2 id=&quot;ji-ge-shi-zuo-xi-jie&quot;&gt;幾個實作細節&lt;a class=&quot;zola-anchor&quot; href=&quot;#ji-ge-shi-zuo-xi-jie&quot; aria-label=&quot;Anchor link for: ji-ge-shi-zuo-xi-jie&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;修復是會互相位移的，所以多個 patch 會按 byte offset 反向排序再依序套用，免得後面的修補蓋掉前面的。byte → line 轉換用 binary search 查預先建好的 newline 陣列，O(log n)。掃描是 rayon 的 parallel iterator，每個檔案獨立 parse + 掃描。修復寫回前會再 parse 一次驗證語法。&lt;/p&gt;
&lt;h2 id=&quot;shou-wei&quot;&gt;收尾&lt;a class=&quot;zola-anchor&quot; href=&quot;#shou-wei&quot; aria-label=&quot;Anchor link for: shou-wei&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Ruby 專案還在用 fasterer 的話，rubyfast 規則和設定檔都相容，可以直接抽換掉。多了自動修復功能。丟進 CI pipeline 也幾乎感覺不到等待時間。&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>用 Cloudflare Workers 打造 1min.ai API Relay</title>
		<published>2026-04-12T00:00:00+08:00</published>
		<updated>2026-04-12T00:00:00+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/1min-relay-worker/"/>
		<id>https://blog.2ac.io/posts/1min-relay-worker/</id>
    
    <summary>&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://1min.ai/&quot;&gt;1min.ai&lt;/a&gt; 是一個 AI 聚合平台，一個訂閱就能用 GPT、Claude、Mistral 等多家模型。可惜它的 API 格式跟 OpenAI 不完全相容，所以現成的工具和 SDK 多半沒辦法直接串。&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/1min-relay-worker&quot;&gt;1min-relay-worker&lt;/a&gt; 就是針對這件事的 workaround：跑在 Cloudflare Workers 上的中繼服務，把 1min.ai 的 API 包成 OpenAI API 格式。&lt;/p&gt;</summary>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/1min-relay-worker/">&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://1min.ai/&quot;&gt;1min.ai&lt;/a&gt; 是一個 AI 聚合平台，一個訂閱就能用 GPT、Claude、Mistral 等多家模型。可惜它的 API 格式跟 OpenAI 不完全相容，所以現成的工具和 SDK 多半沒辦法直接串。&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/1min-relay-worker&quot;&gt;1min-relay-worker&lt;/a&gt; 就是針對這件事的 workaround：跑在 Cloudflare Workers 上的中繼服務，把 1min.ai 的 API 包成 OpenAI API 格式。&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;&lt;h2 id=&quot;wei-shen-mo-xu-yao-relay&quot;&gt;為什麼需要 Relay&lt;a class=&quot;zola-anchor&quot; href=&quot;#wei-shen-mo-xu-yao-relay&quot; aria-label=&quot;Anchor link for: wei-shen-mo-xu-yao-relay&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;直接用 1min.ai 的原生 API 幾個痛點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API 格式跟 OpenAI SDK 不相容，沒辦法直接用 &lt;code&gt;openai&lt;/code&gt; 套件串&lt;/li&gt;
&lt;li&gt;沒有內建的 Rate Limiting&lt;/li&gt;
&lt;li&gt;Token 用量算不準&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;過 Relay 之後，把 &lt;code&gt;base_url&lt;/code&gt; 指到自己的 Worker，原本熟的 OpenAI SDK 就可以直接打 1min.ai 的所有模型。&lt;/p&gt;
&lt;h2 id=&quot;zhi-yuan-de-gong-neng&quot;&gt;支援的功能&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhi-yuan-de-gong-neng&quot; aria-label=&quot;Anchor link for: zhi-yuan-de-gong-neng&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Chat Completions（Streaming、Vision）&lt;/li&gt;
&lt;li&gt;Responses API（Structured Outputs，JSON Object / JSON Schema）&lt;/li&gt;
&lt;li&gt;Image Generation（DALL-E 等）&lt;/li&gt;
&lt;li&gt;Audio Transcription / Translation（Whisper）&lt;/li&gt;
&lt;li&gt;動態模型列表：不用手動維護清單，Worker 會自己打 1min.ai API 拉最新可用的模型。兩層快取（記憶體 5 分鐘 + KV 1 小時），1min.ai 那邊上下架什麼，Relay 會自己跟上。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;bu-shu&quot;&gt;部署&lt;a class=&quot;zola-anchor&quot; href=&quot;#bu-shu&quot; aria-label=&quot;Anchor link for: bu-shu&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id=&quot;qian-zhi-xu-qiu&quot;&gt;前置需求&lt;a class=&quot;zola-anchor&quot; href=&quot;#qian-zhi-xu-qiu&quot; aria-label=&quot;Anchor link for: qian-zhi-xu-qiu&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Node.js 18+&lt;/li&gt;
&lt;li&gt;Wrangler CLI&lt;/li&gt;
&lt;li&gt;Cloudflare 帳號（需啟用 Workers 和 KV）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;kuai-su-kai-shi&quot;&gt;快速開始&lt;a class=&quot;zola-anchor&quot; href=&quot;#kuai-su-kai-shi&quot; aria-label=&quot;Anchor link for: kuai-su-kai-shi&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; clone&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; https://github.com/7a6163/1min-relay-worker.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;cd&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; 1min-relay-worker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;npm&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;建立 KV namespace：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;wrangler&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; kv:namespace&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; create&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;RATE_LIMIT_STORE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;wrangler&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; kv:namespace&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; create&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;MODEL_CACHE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把回傳的 namespace ID 填入 &lt;code&gt;wrangler.jsonc&lt;/code&gt;，然後部署：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;npm&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; deploy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;shi-yong-fang-shi&quot;&gt;使用方式&lt;a class=&quot;zola-anchor&quot; href=&quot;#shi-yong-fang-shi&quot; aria-label=&quot;Anchor link for: shi-yong-fang-shi&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;部署完成後，把 &lt;code&gt;base_url&lt;/code&gt; 指向你的 Worker 即可：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;from&lt;/span&gt;&lt;span&gt; openai&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; import&lt;/span&gt;&lt;span&gt; OpenAI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;client&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; OpenAI&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;    base_url&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;https://your-worker.workers.dev/v1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;    api_key&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;YOUR_1MIN_API_KEY&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;response&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; client&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;chat&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;completions&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;    model&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;gpt-4o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;    messages&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Hello!&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Streaming 也完全支援：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;curl&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; POST&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; https://your-worker.workers.dev/v1/chat/completions&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Content-Type: application/json&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Authorization: Bearer YOUR_API_KEY&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;quot;model&amp;quot;: &amp;quot;gpt-4o&amp;quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;quot;messages&amp;quot;: [{&amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: &amp;quot;Tell me a story&amp;quot;}],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;quot;stream&amp;quot;: true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;  }&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;rate-limiting&quot;&gt;Rate Limiting&lt;a class=&quot;zola-anchor&quot; href=&quot;#rate-limiting&quot; aria-label=&quot;Anchor link for: rate-limiting&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;內建分散式限流，靠 Cloudflare KV 在所有 Worker 實例之間同步：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每 IP 每分鐘 60 次請求&lt;/li&gt;
&lt;li&gt;每 IP 每分鐘 10,000 tokens&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;超過會回 HTTP 429，附上 Rate Limit headers。&lt;/p&gt;
&lt;h2 id=&quot;ji-shu-xi-jie&quot;&gt;技術細節&lt;a class=&quot;zola-anchor&quot; href=&quot;#ji-shu-xi-jie&quot; aria-label=&quot;Anchor link for: ji-shu-xi-jie&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;整個服務 TypeScript 寫的，跑在 Cloudflare Workers 上，限流和模型快取放 KV，token 計算用 &lt;code&gt;gpt-tokenizer&lt;/code&gt;。Cloudflare 免費方案就夠用，延遲低，也不用自己顧伺服器。&lt;/p&gt;
&lt;p&gt;專案原始碼：&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/7a6163/1min-relay-worker&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>Reset navicat trial</title>
		<published>2024-05-16T03:11:27+08:00</published>
		<updated>2024-05-16T03:11:27+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/reset-navicat-trial/"/>
		<id>https://blog.2ac.io/posts/reset-navicat-trial/</id>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/reset-navicat-trial/">&lt;p&gt;Reset the Navicat trial time on Mac.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#!&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;uo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; pipefail&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ==========================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 變數定義 / Variables Definition&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ==========================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;readonly&lt;/span&gt;&lt;span&gt; APP_NAME&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Navicat Premium&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;readonly&lt;/span&gt;&lt;span&gt; APP_SUPPORT_DIR&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;HOME&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/Library/Application Support/PremiumSoft CyberTech/Navicat CC/Navicat Premium&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;readonly&lt;/span&gt;&lt;span&gt; PLIST_FILE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;HOME&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/Library/Preferences/com.navicat.NavicatPremium.plist&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;readonly&lt;/span&gt;&lt;span&gt; KEYCHAIN_SERVICE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;com.navicat.NavicatPremium&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;readonly&lt;/span&gt;&lt;span&gt; HASH_REGEX&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;^[0-9A-F]{32}$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ==========================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 日誌函式 / Logging Functions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ==========================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;log_info&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;    {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; echo&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;[\033[34mINFO\033[0m] &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;log_success&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; echo&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;[\033[32mSUCCESS\033[0m] &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;log_warn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;    {&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; echo&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;[\033[33mWARN\033[0m] &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ==========================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 功能函式 / Functional Modules&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ==========================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 1. 終止應用程式 / Terminate Application&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;terminate_app&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    log_info&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;正在檢查並終止 &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;APP_NAME&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; 程序... / Checking and terminating &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;APP_NAME&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; process...&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; pkill&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;APP_NAME&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/dev/null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        log_success&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;已成功終止正在執行的 &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;APP_NAME&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; 程序。/ Successfully terminated running process.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        log_info&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;APP_NAME&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; 程序未在執行。/ Process is not running.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 2. 清理應用程式支援目錄的雜湊檔案 / Clean hash files in App Support&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;clean_support_files&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    log_info&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;清理應用程式支援目錄的雜湊檔案... / Cleaning hash files in app support directory...&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -d&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;APP_SUPPORT_DIR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt; ]]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        log_warn&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;目錄不存在，跳過: &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;APP_SUPPORT_DIR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; / Directory not found, skipping.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    local&lt;/span&gt;&lt;span&gt; deleted_count&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    while&lt;/span&gt;&lt;span&gt; IFS&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; read&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; file&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        local&lt;/span&gt;&lt;span&gt; filename&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        filename&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;basename&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 匹配以 . 開頭且後面跟著 32 位大寫十六進位字元的檔案&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        if&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; echo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;{&lt;/span&gt;&lt;span&gt;filename&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; grep&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Eq&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;HASH_REGEX&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;            rm&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;            log_info&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;已刪除雜湊檔案: &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;filename&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; / Deleted hash file: &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;filename&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            ((&lt;/span&gt;&lt;span&gt;deleted_count&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;++&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    done&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;lt;(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;APP_SUPPORT_DIR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;maxdepth&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; f&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;.[0-9A-F]*&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/dev/null&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    log_success&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;應用程式支援目錄清理完成，共刪除 &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;deleted_count&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; 個檔案。/ Cleaned &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;deleted_count&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; files.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 3. 處理偏好設定檔案 / Clean preferences plist file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;clean_plist_keys&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    log_info&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;處理偏好設定檔案... / Processing preferences plist file...&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -f&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;PLIST_FILE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt; ]]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        log_warn&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;偏好設定檔案不存在: &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;PLIST_FILE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; / Plist file not found.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 獲取所有符合 32 位元雜湊格式的頂層鍵值&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    local&lt;/span&gt;&lt;span&gt; keys_to_delete&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    keys_to_delete&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;/usr/libexec/PlistBuddy&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Print&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;PLIST_FILE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/dev/null&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; grep&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Eoa&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;^\s{4}[0-9A-F]{32}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; tr&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -z&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;keys_to_delete&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt; ]]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        log_info&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;未找到需要刪除的 32 位元雜湊鍵值。/ No 32-character hash keys found.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    local&lt;/span&gt;&lt;span&gt; deleted_count&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    while&lt;/span&gt;&lt;span&gt; IFS&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; read&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; key&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        if&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; /usr/libexec/PlistBuddy&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Delete :&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;PLIST_FILE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/dev/null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;            log_info&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;已刪除 Plist 鍵值: &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; / Deleted Plist key: &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            ((&lt;/span&gt;&lt;span&gt;deleted_count&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;++&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    done&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;keys_to_delete&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    log_success&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;偏好設定清理完成，共刪除 &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;deleted_count&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; 個鍵值。/ Cleaned &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;deleted_count&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; keys.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 4. 清理鑰匙圈中的試用期追蹤項目 / Clean Keychain entries&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;clean_keychain&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    log_info&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;清理鑰匙圈中的試用期追蹤項目... / Cleaning trial tracking entries in Keychain...&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    local&lt;/span&gt;&lt;span&gt; keychain_accounts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    keychain_accounts&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;security&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; dump-keychain&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/Library/Keychains/login.keychain-db&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/dev/null&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        awk&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/0x00000007.*&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;KEYCHAIN_SERVICE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/{found=1} found &amp;amp;&amp;amp; /&amp;quot;acct&amp;quot;/{print; found=0}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        sed&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;s/.*&amp;lt;blob&amp;gt;=&amp;quot;\([^&amp;quot;]*\)&amp;quot;.*/\1/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -z&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;keychain_accounts&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt; ]]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        log_info&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;未找到需要刪除的鑰匙圈項目。/ No keychain entries found.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    local&lt;/span&gt;&lt;span&gt; deleted_count&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    while&lt;/span&gt;&lt;span&gt; IFS&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; read&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; account&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        if&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; echo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;account&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; grep&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Eq&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;HASH_REGEX&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;            if&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; security&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; delete-generic-password&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;KEYCHAIN_SERVICE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;account&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/dev/null&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&amp;amp;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;                log_info&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;已刪除鑰匙圈項目: &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;account&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; / Deleted keychain entry: &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;account&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                ((&lt;/span&gt;&lt;span&gt;deleted_count&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;++&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;            fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    done&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;keychain_accounts&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    log_success&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;鑰匙圈清理完成，共刪除 &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;deleted_count&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; 個項目。/ Deleted &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;deleted_count&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; keychain entries.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ==========================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 主程式執行 / Main Execution&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ==========================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    echo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;==================================================&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    echo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;  Navicat Premium 重設腳本 / Reset Script&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    echo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;==================================================&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    terminate_app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    clean_support_files&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    clean_plist_keys&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    clean_keychain&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    echo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;==================================================&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    log_success&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;所有清理任務已完成！/ All cleaning tasks completed!&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 執行主程式&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;main&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>picoCTF - Super Serial</title>
		<published>2022-01-18T03:11:27+08:00</published>
		<updated>2022-01-18T03:11:27+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/picoctf-super-serial/"/>
		<id>https://blog.2ac.io/posts/picoctf-super-serial/</id>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/picoctf-super-serial/">&lt;p&gt;先檢查 robots.txt 發現有 &lt;code&gt;Disallow: /admin.phps&lt;/code&gt;
接下來就檢查 &lt;code&gt;/index.phps&lt;/code&gt; ，可以看到 Source Code，接下檢查發現他引用了兩隻 php
&lt;code&gt;authentication.php&lt;/code&gt; &lt;code&gt;cookie.php&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;一樣先檢查他們的 Source code，會發現有一隻從 Cookie 可以反序列化的函式&lt;/p&gt;
&lt;p&gt;以下是 payload，將得到的 Token 塞進 Cookie 就拿到 Flag。&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;php&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;php&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; access_log&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	public&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;log_file&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	function&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; __construct&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;lf&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;		$&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;log_file&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;lf&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	function&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; __toString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; $&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;read_log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	function&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; read_log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; file_get_contents&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;log_file&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;perm&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; new&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; access_log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;../flag&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;echo&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; base64_encode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;serialize&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;perm&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>picoCTF - caas</title>
		<published>2021-07-03T23:11:27+08:00</published>
		<updated>2021-07-03T23:11:27+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/picoctf-caas/"/>
		<id>https://blog.2ac.io/posts/picoctf-caas/</id>
    
    <summary>&lt;p&gt;&lt;img src=&quot;https://7a6163.github.io/assets/images/20210703230955.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;https://picoctf/cowsay/{message}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;蠻簡單的一題，一隻牛會說出您輸入的訊息。這題有提供 index.js，不過我沒注意到就直接解了。&lt;/p&gt;</summary>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/picoctf-caas/">&lt;p&gt;&lt;img src=&quot;https://7a6163.github.io/assets/images/20210703230955.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;https://picoctf/cowsay/{message}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;蠻簡單的一題，一隻牛會說出您輸入的訊息。這題有提供 index.js，不過我沒注意到就直接解了。&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;
&lt;p&gt;首先思維要猜 Flag 在哪，從牛會講話的模式來看， Flag 應該是一個檔案，要藉由牛的嘴巴說出來。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;`ls`&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;會看到這個目錄的資料夾，然後會看到 flag 的檔案，還故意拼錯檔名。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;`cat falg.txt`&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Flag 就到手了，有興趣可以看一下他們的題目應該都是用 Docker 包裝，是不是可以 container escape attack 好像可以研究看看！&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>picoCTF - More Cookies 150 points</title>
		<published>2021-07-03T03:11:27+08:00</published>
		<updated>2021-07-03T03:11:27+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/picoctf-more-cookies/"/>
		<id>https://blog.2ac.io/posts/picoctf-more-cookies/</id>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/picoctf-more-cookies/">&lt;p&gt;Cookies 的進階題，還有一個同名版本是 2021 年的 More Cookies 90 points 但那題感覺難上很多。&lt;/p&gt;
&lt;p&gt;這次作者有提供 server 的 source code ，簡單來說就是直接 Open Book，他使用了一套叫做 Flask 的 Web framework。&lt;/p&gt;
&lt;p&gt;服務啟動會隨機挑選餅乾名當作 Secret key，當使用 Session 時，去產生出加密後的 Cookies。&lt;/p&gt;
&lt;p&gt;所以我們先照慣例，直接搜尋 snickerdoodle，從開發者模式取得 Cookie，原本想自己照他的模式，寫類似的 python 來跑，不過從 Google 發現這個有人已經寫出加解密套件。&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/noraj/flask-session-cookie-manager&quot;&gt;GitHub - noraj/flask-session-cookie-manager: Flask Session Cookie Decoder/Encoder&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cookies&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; b&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; c&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;for&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;{&lt;/span&gt;&lt;span&gt;cookies&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; do&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; echo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; python&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ./flask_session_cookie_manager3.py&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; decode&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;c&lt;/span&gt;&lt;span&gt; [cookies&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;順利的話會找到對應的 Secret key，也可以稍微看一下解密出來的明文的樣子，再把內文改成 admin，再用 Key 拿去加密，組成 Cookies 回寫瀏覽器就解了。&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>picoCTF - Cookies</title>
		<published>2021-07-01T20:09:27+08:00</published>
		<updated>2021-07-01T20:09:27+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/picoctf-cookies/"/>
		<id>https://blog.2ac.io/posts/picoctf-cookies/</id>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/picoctf-cookies/">&lt;p&gt;這一題標題就非常明示的，這是跟 Cookies 有關係。
打開網址首先會看到一個輸入框跟搜尋按鈕，輸入框裡還有 placeholder 寫著 snickerdoodle。直接輸入 snickerdoodle&lt;/p&gt;
&lt;p&gt;會出現 I love snickerdoodle cookies!&lt;/p&gt;
&lt;p&gt;打開瀏覽器開發工具，找出 Cookies 會發現寫了一筆 &lt;code&gt;name = 0&lt;/code&gt; 的 Cookie ，以此類推，可能會有 1, 2, 3... 的 cookies。&lt;/p&gt;
&lt;p&gt;接下來 curl 是工程師的好朋友，寫一隻 Shell Script。因為 Flag 有某些特徵，所以我用 Grep 去過濾掉不要的資訊。&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#!&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;for&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;0..28}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;  curl&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-silent&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;http://picoctf/check&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Accept-Language: en-US,en;q=0.5&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-compressed&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;DNT: 1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Connection: keep-alive&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Referer: http://picoctf/check&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Cookie: name=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Upgrade-Insecure-Requests: 1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Pragma: no-cache&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Cache-Control: no-cache&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&amp;amp;1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; grep&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; pico&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;這題分數雖然不高，其實也真的不難，但寫 Shell Script 蠻有趣的。&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>自行升級 Docker</title>
		<published>2021-06-27T20:09:27+08:00</published>
		<updated>2021-06-27T20:09:27+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/docker-force-upgrade/"/>
		<id>https://blog.2ac.io/posts/docker-force-upgrade/</id>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/docker-force-upgrade/">&lt;p&gt;最近遇到一個 Bug，實際追下去後才發現是 AWS 提供的 Docker 版本不是最新的。Docker 網站有提供編譯好的執行檔，可以直接下載覆蓋原有執行檔，之後套件更新也會一併升上去。&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; wget&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; https://download.docker.com/linux/static/stable/x86_64/docker-20.10.7.tgz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; tar&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; zxvf&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker-20.10.7.tgz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; cp&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker-20.10.7/docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /usr/bin/docker&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>暴力破解加密的 dmg</title>
		<published>2021-06-19T20:09:27+08:00</published>
		<updated>2021-06-19T20:09:27+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/brute-force-to-crack-dmg/"/>
		<id>https://blog.2ac.io/posts/brute-force-to-crack-dmg/</id>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/brute-force-to-crack-dmg/">&lt;p&gt;首先先安裝 john-jumbo&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; brew&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; john-jumbo&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先產生 hashes，注意程式版本是否不同&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /usr/local/Cellar/john-jumbo/1.9.0/share/john/dmg2john&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; target.dmg&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; target.dmg.hash&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;開始暴力破解&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; john&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; target.dmg.hash&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果明確知道密碼組成，可以使用 &lt;code&gt;--mask&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; john&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-mask=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;[0-9a-z][0-9a-z][0-9a-z]&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; target.dmg.hash&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果要多開 processes，可以使用 &lt;code&gt;--fork&lt;/code&gt;，最高不要超過 CPU Core 數&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; john&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-fork=8&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; target.dmg.hash&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>DNS Override in DHCP Mode</title>
		<published>2018-11-06T00:37:27+08:00</published>
		<updated>2018-11-06T00:37:27+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/dns-override-in-dhcp-mode/"/>
		<id>https://blog.2ac.io/posts/dns-override-in-dhcp-mode/</id>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/dns-override-in-dhcp-mode/">&lt;p&gt;原因是 Vultr DNS 會不定期爛掉，一個新的 instance 預設是 DHCP。發 Ticket 去問客服，他會教你如何採用固定 IP 配置。
不過如果不想跟客服周旋，可以自己 Override DNS 設定。&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; cat&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/network/interfaces&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 確認 interface&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# This file describes the network interfaces available on your system&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# and how to activate them. For more information, see interfaces(5).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#source /etc/network/interfaces.d/*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;auto lo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;iface lo inet loopback&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;allow-hotplug ens3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;iface ens3 inet dhcp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;iface ens3 inet6 auto&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; vi&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/dhcp/dhclient.conf&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 修改 DHCP 設定&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;interface &amp;quot;ens3&amp;quot; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    supersede domain-name-servers 1.1.1.1, 1.0.0.1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; reboot&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; cat&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/resolv.conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;nameserver 2001:19f0:300:1704::6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;nameserver 1.1.1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;nameserver 1.0.0.1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content>
	</entry>
	<entry xml:lang="zh-TW">
		<title>Miniflux 自建 RSS 閱讀器服務</title>
		<published>2018-11-04T13:52:07+08:00</published>
		<updated>2018-11-04T13:52:07+08:00</updated>
		<link rel="alternate" type="text/html" href="https://blog.2ac.io/posts/miniflux/"/>
		<id>https://blog.2ac.io/posts/miniflux/</id>
    
    <summary>&lt;figure&gt;
&lt;img src=&quot;/images/miniflux-og.webp&quot; alt=&quot;Miniflux 自建 RSS 閱讀器服務&quot; height=&quot;670&quot; width=&quot;1200&quot;&gt;&lt;/figure&gt;
&lt;p&gt;在 Google Reader 收攤後，我後來嘗試了不少的 RSS 閱讀器服務，&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://feedly.com/&quot;&gt;Feedly&lt;/a&gt; 、&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://flipboard.com/&quot;&gt;Flipboard&lt;/a&gt; 和 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.feedspot.com/&quot;&gt;Feedspot&lt;/a&gt;。嚴格來說 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://flipboard.com/&quot;&gt;Flipboard&lt;/a&gt; 不算是 RSS 閱讀器，比較像是社群內容雜誌，但不可否認的它是個好 APP，以前甚至為了 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://flipboard.com/&quot;&gt;Flipboard&lt;/a&gt; 買 iPad。&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.feedspot.com/&quot;&gt;Feedspot&lt;/a&gt; 感覺介面很像 Google Reader，又有計劃推出 iOS APP，中間有推出終生 Gold 方案，我也購買了。Gold 只差在通知，可以自訂時間區間而已，真的蠻雞肋的，有些 Bug 回報，官方似乎沒在維護，iOS APP. 也難產，讓我一直有想出走的想法，爾後又接觸到 Tiny Tiny RSS、&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://freshrss.org/&quot;&gt;FreshRSS&lt;/a&gt; 和 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://miniflux.app/&quot;&gt;Miniflux&lt;/a&gt;。&lt;/p&gt;</summary>
    
		<content type="html" xml:base="https://blog.2ac.io/posts/miniflux/">&lt;figure&gt;
&lt;img src=&quot;/images/miniflux-og.webp&quot; alt=&quot;Miniflux 自建 RSS 閱讀器服務&quot; height=&quot;670&quot; width=&quot;1200&quot;&gt;&lt;/figure&gt;
&lt;p&gt;在 Google Reader 收攤後，我後來嘗試了不少的 RSS 閱讀器服務，&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://feedly.com/&quot;&gt;Feedly&lt;/a&gt; 、&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://flipboard.com/&quot;&gt;Flipboard&lt;/a&gt; 和 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.feedspot.com/&quot;&gt;Feedspot&lt;/a&gt;。嚴格來說 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://flipboard.com/&quot;&gt;Flipboard&lt;/a&gt; 不算是 RSS 閱讀器，比較像是社群內容雜誌，但不可否認的它是個好 APP，以前甚至為了 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://flipboard.com/&quot;&gt;Flipboard&lt;/a&gt; 買 iPad。&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.feedspot.com/&quot;&gt;Feedspot&lt;/a&gt; 感覺介面很像 Google Reader，又有計劃推出 iOS APP，中間有推出終生 Gold 方案，我也購買了。Gold 只差在通知，可以自訂時間區間而已，真的蠻雞肋的，有些 Bug 回報，官方似乎沒在維護，iOS APP. 也難產，讓我一直有想出走的想法，爾後又接觸到 Tiny Tiny RSS、&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://freshrss.org/&quot;&gt;FreshRSS&lt;/a&gt; 和 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://miniflux.app/&quot;&gt;Miniflux&lt;/a&gt;。&lt;/p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;/span&gt;
&lt;p&gt;最近 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://reederapp.com/&quot;&gt;Reeder&lt;/a&gt; 因為計畫出新版，將舊版限時免費，注意到來源設定裡面有一個自建服務叫做 Fever，上網查看後發現，原來作者已經停止維護了，真的是晴天霹靂。不過有網友幫 Tiny Tiny RSS，額外寫支援 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/dasmurphy/tinytinyrss-fever-plugin&quot;&gt;Fever Plugin&lt;/a&gt;，不過我一直對 TTRSS 的早期介面有點卻步，好像沒在更新，加上額外加入 Plugin 方式，有可能造成不相容的問題。&lt;/p&gt;
&lt;p&gt;然後 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://freshrss.org/&quot;&gt;FreshRSS&lt;/a&gt; 有內建 Fever API，可是嘗試架設後，&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://freshrss.org/&quot;&gt;FreshRSS&lt;/a&gt; 對 RSS 的標準十分嚴格，無法通過 RSS 標準驗證的，就無法加入，速度上有點緩慢，可能跟架構是 PHP + Apache 有關。&lt;/p&gt;
&lt;p&gt;最後發現了 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://miniflux.app/&quot;&gt;Miniflux&lt;/a&gt;，GO 語言撰寫出來的，容量很小內建 Web Server，內建 Fever API，對 RSS 沒這麼挑嘴，反應也快的沒話說。作者對簡潔似乎有點偏執，不過剛好我也喜歡這樣的偏執，十分推薦使用。&lt;/p&gt;
</content>
	</entry>
</feed>
