<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Linux on Tarragon</title><link>https://tarrragon.github.io/blog/tags/linux/</link><description>Recent content in Linux on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Thu, 02 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/linux/index.xml" rel="self" type="application/rss+xml"/><item><title>CLI 環境工具</title><link>https://tarrragon.github.io/blog/linux/tools/cli/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/tools/cli/</guid><description>&lt;p>在純文字終端機下做事的工具分兩個家族。第一個是&lt;strong>日常指令的現代替代品&lt;/strong>：&lt;code>grep&lt;/code> / &lt;code>find&lt;/code> / &lt;code>cat&lt;/code> / &lt;code>ls&lt;/code> 這些預設指令的現代版（&lt;code>ripgrep&lt;/code> / &lt;code>fd&lt;/code> / &lt;code>bat&lt;/code> / &lt;code>eza&lt;/code> + 互動式的 &lt;code>fzf&lt;/code>），在每天重複幾十次的搜尋與瀏覽上更快更省力。第二個是&lt;strong>終端機圖形化介面（TUI）&lt;/strong>：用 ASCII 與 Unicode 製圖字元做出的監控、圖表、多視窗、資料庫操作介面，只傳純文字、不依賴影像協定，在 SSH、手機平板、低頻寬連線下最穩。&lt;/p>
&lt;h2 id="日常指令的現代替代品">日常指令的現代替代品&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>&lt;a href="modern-cli-replacements/">現代 CLI 替代工具&lt;/a>&lt;/strong> — &lt;code>grep&lt;/code> → &lt;code>ripgrep&lt;/code>、&lt;code>find&lt;/code> → &lt;code>fd&lt;/code>、&lt;code>cat&lt;/code> → &lt;code>bat&lt;/code>、&lt;code>ls&lt;/code> → &lt;code>eza&lt;/code>，加上互動式模糊搜尋 &lt;code>fzf&lt;/code>。什麼情境值得換、換了要注意什麼、為什麼別在腳本裡依賴它們。&lt;/li>
&lt;/ul>
&lt;h2 id="終端機圖形化介面tui">終端機圖形化介面（TUI）&lt;/h2>
&lt;p>這一類「圖形化」是用製圖字元畫出來的介面，而不是把 PNG／JPG 渲染進終端機，所以傳輸量小、在低頻寬與手機連線下最穩。大致分六類：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>TUI 監控與儀表板&lt;/strong> — &lt;code>btop&lt;/code> / &lt;code>htop&lt;/code> / &lt;code>k9s&lt;/code> / &lt;code>ncdu&lt;/code> 等系統監控的全螢幕互動介面；版控專用的 git 線圖工具（&lt;code>tig&lt;/code> / &lt;code>lazygit&lt;/code> / &lt;code>gitui&lt;/code>）是同類 TUI 但獨立的子題。&lt;/li>
&lt;li>&lt;strong>ASCII 與文字圖表&lt;/strong> — &lt;code>gnuplot&lt;/code> / &lt;code>termgraph&lt;/code> / &lt;code>plotext&lt;/code> 等把資料畫成終端機圖表的工具。&lt;/li>
&lt;li>&lt;strong>終端機多工器&lt;/strong> — &lt;code>tmux&lt;/code> / &lt;code>zellij&lt;/code>，分割畫面、連線斷了 session 還在。&lt;/li>
&lt;li>&lt;strong>檔案管理器&lt;/strong> — &lt;code>broot&lt;/code>（樹狀）/ &lt;code>yazi&lt;/code> / &lt;code>ranger&lt;/code>（Miller 欄狀），像 IDE 側邊欄那樣瀏覽目錄與預覽檔案。&lt;/li>
&lt;li>&lt;strong>SQL 客戶端&lt;/strong> — &lt;code>harlequin&lt;/code>（IDE 風）/ &lt;code>lazysql&lt;/code>（瀏覽器風）/ &lt;code>pgcli&lt;/code>、&lt;code>litecli&lt;/code>（增強 REPL），在終端機連資料庫跑查詢。&lt;/li>
&lt;li>&lt;strong>訊息佇列客戶端&lt;/strong> — Kafka 的 &lt;code>kaskade&lt;/code> / &lt;code>yozefu&lt;/code> / &lt;code>ktea&lt;/code>（全螢幕 TUI）、Redis 的 &lt;code>iredis&lt;/code>（增強 REPL），在終端機連 broker 瀏覽 topic 與訊息。&lt;/li>
&lt;/ul>
&lt;p>這個系列的每篇文章都用實機驗證導向的流程生產（裝起來實跑、TUI 交人互動驗、驗不了的標 caveat）。要擴展新類別時，照 &lt;a href="https://tarrragon.github.io/blog/posts/%E9%A9%97%E8%AD%89%E5%B0%8E%E5%90%91%E7%9A%84-cli-%E5%B7%A5%E5%85%B7%E6%96%87%E7%AB%A0%E5%AE%98%E6%96%B9-docs-%E6%9F%A5%E6%A0%B8%E6%94%BE%E9%81%8E%E7%9A%84%E8%90%BD%E5%B7%AE%E9%A1%9E%E5%9E%8B/" data-link-title="驗證導向的 CLI 工具文章：官方 docs 查核放過的落差類型" data-link-desc="CLI 工具教學的指令正確性不能只靠官方文件查核、要實機驗證時回來。官方文件驗的是「文件說的是否正確」、驗不了「文件沒說的是否存在」。">驗證導向的 CLI 工具文章生產流程&lt;/a> 走。&lt;/p>
&lt;hr>
&lt;h2 id="跟工具選單其他子系列的邊界">跟工具選單其他子系列的邊界&lt;/h2>
&lt;p>CLI 環境工具跟 tools 底下另外兩個子系列的分界：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>情境&lt;/th>
 &lt;th>該看&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>純終端機下的指令替代與 TUI&lt;/td>
 &lt;td>本系列&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>有圖形環境、要挑桌面 app（檔案管理員、DE）&lt;/td>
 &lt;td>&lt;a href="../gui/">圖形桌面工具&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遠端操作、把 session 留在遠端不掉&lt;/td>
 &lt;td>&lt;a href="../remote/">遠端工具&lt;/a>（多工器的遠端應用面）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>多工器（&lt;code>tmux&lt;/code> / &lt;code>zellij&lt;/code>）與檔案瀏覽（&lt;code>broot&lt;/code> / &lt;code>yazi&lt;/code>）這類工具兩邊都會提到：本系列講它們在純終端機下的用法與選型，遠端 / 圖形的情境應用則由 remote / gui 兩系列承接。這些工具的配置檔怎麼版控、跨機器同步，見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/" data-link-title="Dotfile 工作環境配置指南" data-link-desc="個人開發環境的配置管理 — dotfile 結構設計、同步策略、shell 與終端機配置、平鋪式視窗管理、桌面客製化，從個人工具鏈延伸到團隊環境標準化">Dotfile 管理&lt;/a> 的&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/03-terminal-ecosystem/" data-link-title="模組三：終端機與編輯器" data-link-desc="終端機相關工具的配置檔散落在不同位置、不確定哪些該進 dotfile repo 時回來讀">終端機與編輯器&lt;/a>模組。&lt;/p>
&lt;hr>
&lt;p>底下自動列出本系列的所有文章、依日期排序。&lt;/p></description><content:encoded><![CDATA[<p>在純文字終端機下做事的工具分兩個家族。第一個是<strong>日常指令的現代替代品</strong>：<code>grep</code> / <code>find</code> / <code>cat</code> / <code>ls</code> 這些預設指令的現代版（<code>ripgrep</code> / <code>fd</code> / <code>bat</code> / <code>eza</code> + 互動式的 <code>fzf</code>），在每天重複幾十次的搜尋與瀏覽上更快更省力。第二個是<strong>終端機圖形化介面（TUI）</strong>：用 ASCII 與 Unicode 製圖字元做出的監控、圖表、多視窗、資料庫操作介面，只傳純文字、不依賴影像協定，在 SSH、手機平板、低頻寬連線下最穩。</p>
<h2 id="日常指令的現代替代品">日常指令的現代替代品</h2>
<ul>
<li><strong><a href="modern-cli-replacements/">現代 CLI 替代工具</a></strong> — <code>grep</code> → <code>ripgrep</code>、<code>find</code> → <code>fd</code>、<code>cat</code> → <code>bat</code>、<code>ls</code> → <code>eza</code>，加上互動式模糊搜尋 <code>fzf</code>。什麼情境值得換、換了要注意什麼、為什麼別在腳本裡依賴它們。</li>
</ul>
<h2 id="終端機圖形化介面tui">終端機圖形化介面（TUI）</h2>
<p>這一類「圖形化」是用製圖字元畫出來的介面，而不是把 PNG／JPG 渲染進終端機，所以傳輸量小、在低頻寬與手機連線下最穩。大致分六類：</p>
<ul>
<li><strong>TUI 監控與儀表板</strong> — <code>btop</code> / <code>htop</code> / <code>k9s</code> / <code>ncdu</code> 等系統監控的全螢幕互動介面；版控專用的 git 線圖工具（<code>tig</code> / <code>lazygit</code> / <code>gitui</code>）是同類 TUI 但獨立的子題。</li>
<li><strong>ASCII 與文字圖表</strong> — <code>gnuplot</code> / <code>termgraph</code> / <code>plotext</code> 等把資料畫成終端機圖表的工具。</li>
<li><strong>終端機多工器</strong> — <code>tmux</code> / <code>zellij</code>，分割畫面、連線斷了 session 還在。</li>
<li><strong>檔案管理器</strong> — <code>broot</code>（樹狀）/ <code>yazi</code> / <code>ranger</code>（Miller 欄狀），像 IDE 側邊欄那樣瀏覽目錄與預覽檔案。</li>
<li><strong>SQL 客戶端</strong> — <code>harlequin</code>（IDE 風）/ <code>lazysql</code>（瀏覽器風）/ <code>pgcli</code>、<code>litecli</code>（增強 REPL），在終端機連資料庫跑查詢。</li>
<li><strong>訊息佇列客戶端</strong> — Kafka 的 <code>kaskade</code> / <code>yozefu</code> / <code>ktea</code>（全螢幕 TUI）、Redis 的 <code>iredis</code>（增強 REPL），在終端機連 broker 瀏覽 topic 與訊息。</li>
</ul>
<p>這個系列的每篇文章都用實機驗證導向的流程生產（裝起來實跑、TUI 交人互動驗、驗不了的標 caveat）。要擴展新類別時，照 <a href="/blog/posts/%E9%A9%97%E8%AD%89%E5%B0%8E%E5%90%91%E7%9A%84-cli-%E5%B7%A5%E5%85%B7%E6%96%87%E7%AB%A0%E5%AE%98%E6%96%B9-docs-%E6%9F%A5%E6%A0%B8%E6%94%BE%E9%81%8E%E7%9A%84%E8%90%BD%E5%B7%AE%E9%A1%9E%E5%9E%8B/" data-link-title="驗證導向的 CLI 工具文章：官方 docs 查核放過的落差類型" data-link-desc="CLI 工具教學的指令正確性不能只靠官方文件查核、要實機驗證時回來。官方文件驗的是「文件說的是否正確」、驗不了「文件沒說的是否存在」。">驗證導向的 CLI 工具文章生產流程</a> 走。</p>
<hr>
<h2 id="跟工具選單其他子系列的邊界">跟工具選單其他子系列的邊界</h2>
<p>CLI 環境工具跟 tools 底下另外兩個子系列的分界：</p>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>該看</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>純終端機下的指令替代與 TUI</td>
          <td>本系列</td>
      </tr>
      <tr>
          <td>有圖形環境、要挑桌面 app（檔案管理員、DE）</td>
          <td><a href="../gui/">圖形桌面工具</a></td>
      </tr>
      <tr>
          <td>遠端操作、把 session 留在遠端不掉</td>
          <td><a href="../remote/">遠端工具</a>（多工器的遠端應用面）</td>
      </tr>
  </tbody>
</table>
<p>多工器（<code>tmux</code> / <code>zellij</code>）與檔案瀏覽（<code>broot</code> / <code>yazi</code>）這類工具兩邊都會提到：本系列講它們在純終端機下的用法與選型，遠端 / 圖形的情境應用則由 remote / gui 兩系列承接。這些工具的配置檔怎麼版控、跨機器同步，見 <a href="/blog/linux/dotfile/" data-link-title="Dotfile 工作環境配置指南" data-link-desc="個人開發環境的配置管理 — dotfile 結構設計、同步策略、shell 與終端機配置、平鋪式視窗管理、桌面客製化，從個人工具鏈延伸到團隊環境標準化">Dotfile 管理</a> 的<a href="/blog/linux/dotfile/03-terminal-ecosystem/" data-link-title="模組三：終端機與編輯器" data-link-desc="終端機相關工具的配置檔散落在不同位置、不確定哪些該進 dotfile repo 時回來讀">終端機與編輯器</a>模組。</p>
<hr>
<p>底下自動列出本系列的所有文章、依日期排序。</p>
]]></content:encoded></item><item><title>桌面環境選型：整合度與組裝自由度的取捨</title><link>https://tarrragon.github.io/blog/linux/tools/gui/desktop-environment-selection/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/tools/gui/desktop-environment-selection/</guid><description>&lt;p>桌面環境（desktop environment，DE）是一整套讓你能用圖形介面操作 Linux 的元件集合——它同時提供視窗管理、面板/工作列、應用啟動器、設定中心、通知、檔案管理員、鎖屏這些功能，並保證它們彼此整合、開箱即用。這跟只負責「畫視窗、管理視窗位置」的 window manager 或 compositor 是不同層次的東西：DE 通常內含一個 window manager，再把上面那一整圈桌面服務組裝好交給你。理解這個責任邊界，是選型的起點——你在選的不是「哪個比較漂亮」，是「別人幫你整合到什麼程度、你自己要組裝多少」。&lt;/p>
&lt;h2 id="選型的真正軸線整合度-vs-組裝自由度">選型的真正軸線：整合度 vs 組裝自由度&lt;/h2>
&lt;p>桌面環境的選擇，核心不是「輕或重」，是&lt;strong>別人幫你整合好多少、你保留多少自己組裝的自由&lt;/strong>。這條軸線的一端是 GNOME 這種高整合方案：面板、設定、通知、檔案管理全部設計成一致的整體，你開機就有一台能用的機器，代價是想改動它預設的行為要對抗它的設計哲學。另一端是 Hyprland 這種 compositor：它只負責畫面與視窗，面板、啟動器、鎖屏、通知你全部自己挑自己接，代價是要花時間組裝、每個元件都要自己維護。&lt;/p>
&lt;p>「輕/重」只是這條軸線的副產品。高整合方案因為要保證所有元件協同，通常帶較多常駐服務、吃較多記憶體；自己組裝的方案可以只裝你要的，所以輕——但如果你把面板、啟動器、通知、鎖屏一個個補齊，最後的資源佔用未必比一個現成 DE 省多少。所以判選型別問「哪個輕」，要問「我想花多少時間在組裝與維護、換到多少客製自由」。&lt;/p>
&lt;h2 id="五個主流選項的定位">五個主流選項的定位&lt;/h2>
&lt;p>下表是常見選項在「整合度」這條軸線上的位置與代價。每個選項底下有延伸說明，因為同樣一句「適合客製」在不同方案裡的實際體驗差很多：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>方案&lt;/th>
 &lt;th>定位&lt;/th>
 &lt;th>整合度&lt;/th>
 &lt;th>資源&lt;/th>
 &lt;th>客製自由&lt;/th>
 &lt;th>預設顯示協定&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>GNOME&lt;/td>
 &lt;td>高整合、意見鮮明的現代桌面&lt;/td>
 &lt;td>高&lt;/td>
 &lt;td>較高&lt;/td>
 &lt;td>低（要對抗設計）&lt;/td>
 &lt;td>Wayland&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>KDE Plasma&lt;/td>
 &lt;td>高整合但高度可調&lt;/td>
 &lt;td>高&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>高（內建設定深）&lt;/td>
 &lt;td>Wayland&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>XFCE&lt;/td>
 &lt;td>輕量傳統桌面&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>低&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>X11&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cinnamon&lt;/td>
 &lt;td>傳統桌面隱喻、易上手&lt;/td>
 &lt;td>中高&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>X11&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Hyprland（WM）&lt;/td>
 &lt;td>自己組裝的平鋪式 compositor&lt;/td>
 &lt;td>無（自組）&lt;/td>
 &lt;td>最低（裸）&lt;/td>
 &lt;td>最高&lt;/td>
 &lt;td>Wayland&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="gnome把選擇替你做完的現代桌面">GNOME：把選擇替你做完的現代桌面&lt;/h3>
&lt;p>GNOME 的定位是「一套有明確設計主張的完整桌面」——它預設一個不同於 Windows/macOS 的工作流（頂欄 + Activities 總覽 + 動態工作區），並且刻意收斂可調選項，讓多數人不用設定就有一致體驗。從 Windows/macOS 轉來、想要「裝好就能用、不想折騰」的人，GNOME 是穩妥選擇：它的整合度最高，通知、設定、線上帳號、檔案管理彼此協調。&lt;/p>
&lt;p>代價在客製。GNOME 把很多設定收進 extension 或需要另裝 &lt;code>gnome-tweaks&lt;/code>（Arch：&lt;code>pacman -S gnome-tweaks&lt;/code>）才改得動的角落，想把它調成傳統工作列風格是在對抗它的設計方向，而 extension 又會隨 GNOME 大版本更新而失效。所以 GNOME 適合「接受它的工作流」的人，不適合「想按自己習慣重排一切」的人。資源上它是這幾個裡偏吃的，老硬體上會感覺得到。&lt;/p>
&lt;h3 id="kde-plasma整合度高但幾乎每個角落都能調">KDE Plasma：整合度高、但幾乎每個角落都能調&lt;/h3>
&lt;p>KDE Plasma 少見地同時做到高整合與高可調：它像 GNOME 一樣開箱即用、元件協調，但幾乎每個行為都攤在設定介面裡讓你改——面板可以拆解重組、視窗規則、快捷鍵、視覺效果都有深度選項。從 Windows 轉來的人會覺得它的預設隱喻（底部工作列 + 開始選單）親切，又保留了往下鑽的空間。&lt;/p>
&lt;p>它的代價不在資源（現代 Plasma 已相當精實，中階機器順暢），在「選項多到需要自己收斂」——設定深意味著你可能花很多時間在調整上。想要「高整合又想保留大量客製、但不想從零組裝」的人，Plasma 通常是比 GNOME 和 Hyprland 都平衡的落點。它的 Wayland session 近年已是預設且成熟。&lt;/p>
&lt;h3 id="xfce老硬體與要傳統要穩要輕的預設">XFCE：老硬體與「要傳統、要穩、要輕」的預設&lt;/h3>
&lt;p>XFCE 的定位是輕量而傳統：它給你熟悉的桌面隱喻（工作列、選單、系統匣），資源佔用在這幾個裡最低，且以穩定少變著稱——它不追新，多年來介面與行為變動小。老硬體、低階 VM、或「我只要一個不吵不鬧、能穩定工作的桌面」的場景，XFCE 是可靠預設。&lt;/p>
&lt;p>它的取捨是現代感與顯示協定：XFCE 目前仍以 X11 為主，Wayland 化在進行但尚未是預設，所以想要 Wayland 的分數效益（見下節）目前要往別的方案找。客製自由度中等——比 GNOME 開放、但沒有 KDE 那種深度設定，也沒有 Hyprland 那種完全重組的自由。&lt;/p>
&lt;h3 id="cinnamon給要-windows-式熟悉感的轉移者">Cinnamon：給「要 Windows 式熟悉感」的轉移者&lt;/h3>
&lt;p>Cinnamon 出身 Linux Mint，定位是把傳統桌面隱喻做得順手好上手——底部工作列、開始選單式的應用選單、系統匣，對從 Windows 轉來的人幾乎零學習曲線。它比 XFCE 現代一點、視覺完整度高一些，整合度也高（自帶檔案管理員 Nemo、設定中心、特效）。&lt;/p>
&lt;p>代價與 XFCE 類似：以 X11 為主，資源比 XFCE 略高。它適合「要一台立刻上手、像 Windows 但是 Linux」的工作機，不適合追求 Wayland 或極致輕量的場景。附帶一提，Cinnamon 的檔案管理員 Nemo 假設 Cinnamon 桌面服務在旁邊，把它單獨裝進裸 window manager 會拖進整套 Cinnamon 元件——這正是&lt;a href="../gui-file-manager-dependencies/">加圖形檔案管理員那篇&lt;/a>講的桌面環境耦合。&lt;/p>
&lt;h3 id="hyprland不是-de是你自己組一個桌面">Hyprland：不是 DE，是你自己組一個桌面&lt;/h3>
&lt;p>Hyprland 嚴格說不是桌面環境，是一個平鋪式（tiling）Wayland compositor——它只負責畫面合成與視窗排列，面板、啟動器、鎖屏、通知、桌布、音量控制全部不含，要你自己挑元件接上去。選它意味著你接受「從零組裝一個桌面」這件事，換來的是最高的客製自由（每個元件都你選、佈局規則完全你定）和最低的裸資源佔用。&lt;/p>
&lt;p>它適合「把配置桌面當成一種投入、想要一套完全長成自己樣子的環境」的人，不適合「只想裝好開始工作」的人。組裝過程本身有相當的學習曲線與維護成本——這也是為什麼本系列用一整個 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">Rice 設計模組&lt;/a>談「選了 Hyprland 之後怎麼把它組起來」。如果你還在「要不要走這條路」的階段，這篇就是那個模組的上游：先確認你要的是組裝自由、而不是開箱即用。&lt;/p>
&lt;h2 id="wayland-vs-x11選型時避不開的底層判斷">Wayland vs X11：選型時避不開的底層判斷&lt;/h2>
&lt;p>顯示協定是選型時的一條隱形軸線，因為它決定了一部分未來相容性。Wayland 是較新的顯示協定，設計上更安全、對高 DPI 與多螢幕不同刷新率支援更好，是 Linux 桌面的方向；X11 是沿用數十年的舊協定，相容性最廣但架構老舊、社群維護逐漸收斂到維持模式。GNOME、KDE Plasma、Hyprland 都已預設或原生 Wayland；XFCE、Cinnamon 目前仍以 X11 為主。&lt;/p>
&lt;p>實務判讀：如果你用 NVIDIA 專有驅動、或依賴某些只支援 X11 的老工具（部分螢幕錄製、遠端桌面、自動化工具），X11 方案目前可能更少驚喜；如果你要高 DPI 筆電、多螢幕混合刷新率、或想跟上長期方向，優先選 Wayland 方案。這不是非此即彼的道德選擇，是看你的硬體與工具鏈落在哪邊。&lt;/p></description><content:encoded><![CDATA[<p>桌面環境（desktop environment，DE）是一整套讓你能用圖形介面操作 Linux 的元件集合——它同時提供視窗管理、面板/工作列、應用啟動器、設定中心、通知、檔案管理員、鎖屏這些功能，並保證它們彼此整合、開箱即用。這跟只負責「畫視窗、管理視窗位置」的 window manager 或 compositor 是不同層次的東西：DE 通常內含一個 window manager，再把上面那一整圈桌面服務組裝好交給你。理解這個責任邊界，是選型的起點——你在選的不是「哪個比較漂亮」，是「別人幫你整合到什麼程度、你自己要組裝多少」。</p>
<h2 id="選型的真正軸線整合度-vs-組裝自由度">選型的真正軸線：整合度 vs 組裝自由度</h2>
<p>桌面環境的選擇，核心不是「輕或重」，是<strong>別人幫你整合好多少、你保留多少自己組裝的自由</strong>。這條軸線的一端是 GNOME 這種高整合方案：面板、設定、通知、檔案管理全部設計成一致的整體，你開機就有一台能用的機器，代價是想改動它預設的行為要對抗它的設計哲學。另一端是 Hyprland 這種 compositor：它只負責畫面與視窗，面板、啟動器、鎖屏、通知你全部自己挑自己接，代價是要花時間組裝、每個元件都要自己維護。</p>
<p>「輕/重」只是這條軸線的副產品。高整合方案因為要保證所有元件協同，通常帶較多常駐服務、吃較多記憶體；自己組裝的方案可以只裝你要的，所以輕——但如果你把面板、啟動器、通知、鎖屏一個個補齊，最後的資源佔用未必比一個現成 DE 省多少。所以判選型別問「哪個輕」，要問「我想花多少時間在組裝與維護、換到多少客製自由」。</p>
<h2 id="五個主流選項的定位">五個主流選項的定位</h2>
<p>下表是常見選項在「整合度」這條軸線上的位置與代價。每個選項底下有延伸說明，因為同樣一句「適合客製」在不同方案裡的實際體驗差很多：</p>
<table>
  <thead>
      <tr>
          <th>方案</th>
          <th>定位</th>
          <th>整合度</th>
          <th>資源</th>
          <th>客製自由</th>
          <th>預設顯示協定</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>GNOME</td>
          <td>高整合、意見鮮明的現代桌面</td>
          <td>高</td>
          <td>較高</td>
          <td>低（要對抗設計）</td>
          <td>Wayland</td>
      </tr>
      <tr>
          <td>KDE Plasma</td>
          <td>高整合但高度可調</td>
          <td>高</td>
          <td>中</td>
          <td>高（內建設定深）</td>
          <td>Wayland</td>
      </tr>
      <tr>
          <td>XFCE</td>
          <td>輕量傳統桌面</td>
          <td>中</td>
          <td>低</td>
          <td>中</td>
          <td>X11</td>
      </tr>
      <tr>
          <td>Cinnamon</td>
          <td>傳統桌面隱喻、易上手</td>
          <td>中高</td>
          <td>中</td>
          <td>中</td>
          <td>X11</td>
      </tr>
      <tr>
          <td>Hyprland（WM）</td>
          <td>自己組裝的平鋪式 compositor</td>
          <td>無（自組）</td>
          <td>最低（裸）</td>
          <td>最高</td>
          <td>Wayland</td>
      </tr>
  </tbody>
</table>
<h3 id="gnome把選擇替你做完的現代桌面">GNOME：把選擇替你做完的現代桌面</h3>
<p>GNOME 的定位是「一套有明確設計主張的完整桌面」——它預設一個不同於 Windows/macOS 的工作流（頂欄 + Activities 總覽 + 動態工作區），並且刻意收斂可調選項，讓多數人不用設定就有一致體驗。從 Windows/macOS 轉來、想要「裝好就能用、不想折騰」的人，GNOME 是穩妥選擇：它的整合度最高，通知、設定、線上帳號、檔案管理彼此協調。</p>
<p>代價在客製。GNOME 把很多設定收進 extension 或需要另裝 <code>gnome-tweaks</code>（Arch：<code>pacman -S gnome-tweaks</code>）才改得動的角落，想把它調成傳統工作列風格是在對抗它的設計方向，而 extension 又會隨 GNOME 大版本更新而失效。所以 GNOME 適合「接受它的工作流」的人，不適合「想按自己習慣重排一切」的人。資源上它是這幾個裡偏吃的，老硬體上會感覺得到。</p>
<h3 id="kde-plasma整合度高但幾乎每個角落都能調">KDE Plasma：整合度高、但幾乎每個角落都能調</h3>
<p>KDE Plasma 少見地同時做到高整合與高可調：它像 GNOME 一樣開箱即用、元件協調，但幾乎每個行為都攤在設定介面裡讓你改——面板可以拆解重組、視窗規則、快捷鍵、視覺效果都有深度選項。從 Windows 轉來的人會覺得它的預設隱喻（底部工作列 + 開始選單）親切，又保留了往下鑽的空間。</p>
<p>它的代價不在資源（現代 Plasma 已相當精實，中階機器順暢），在「選項多到需要自己收斂」——設定深意味著你可能花很多時間在調整上。想要「高整合又想保留大量客製、但不想從零組裝」的人，Plasma 通常是比 GNOME 和 Hyprland 都平衡的落點。它的 Wayland session 近年已是預設且成熟。</p>
<h3 id="xfce老硬體與要傳統要穩要輕的預設">XFCE：老硬體與「要傳統、要穩、要輕」的預設</h3>
<p>XFCE 的定位是輕量而傳統：它給你熟悉的桌面隱喻（工作列、選單、系統匣），資源佔用在這幾個裡最低，且以穩定少變著稱——它不追新，多年來介面與行為變動小。老硬體、低階 VM、或「我只要一個不吵不鬧、能穩定工作的桌面」的場景，XFCE 是可靠預設。</p>
<p>它的取捨是現代感與顯示協定：XFCE 目前仍以 X11 為主，Wayland 化在進行但尚未是預設，所以想要 Wayland 的分數效益（見下節）目前要往別的方案找。客製自由度中等——比 GNOME 開放、但沒有 KDE 那種深度設定，也沒有 Hyprland 那種完全重組的自由。</p>
<h3 id="cinnamon給要-windows-式熟悉感的轉移者">Cinnamon：給「要 Windows 式熟悉感」的轉移者</h3>
<p>Cinnamon 出身 Linux Mint，定位是把傳統桌面隱喻做得順手好上手——底部工作列、開始選單式的應用選單、系統匣，對從 Windows 轉來的人幾乎零學習曲線。它比 XFCE 現代一點、視覺完整度高一些，整合度也高（自帶檔案管理員 Nemo、設定中心、特效）。</p>
<p>代價與 XFCE 類似：以 X11 為主，資源比 XFCE 略高。它適合「要一台立刻上手、像 Windows 但是 Linux」的工作機，不適合追求 Wayland 或極致輕量的場景。附帶一提，Cinnamon 的檔案管理員 Nemo 假設 Cinnamon 桌面服務在旁邊，把它單獨裝進裸 window manager 會拖進整套 Cinnamon 元件——這正是<a href="../gui-file-manager-dependencies/">加圖形檔案管理員那篇</a>講的桌面環境耦合。</p>
<h3 id="hyprland不是-de是你自己組一個桌面">Hyprland：不是 DE，是你自己組一個桌面</h3>
<p>Hyprland 嚴格說不是桌面環境，是一個平鋪式（tiling）Wayland compositor——它只負責畫面合成與視窗排列，面板、啟動器、鎖屏、通知、桌布、音量控制全部不含，要你自己挑元件接上去。選它意味著你接受「從零組裝一個桌面」這件事，換來的是最高的客製自由（每個元件都你選、佈局規則完全你定）和最低的裸資源佔用。</p>
<p>它適合「把配置桌面當成一種投入、想要一套完全長成自己樣子的環境」的人，不適合「只想裝好開始工作」的人。組裝過程本身有相當的學習曲線與維護成本——這也是為什麼本系列用一整個 <a href="/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">Rice 設計模組</a>談「選了 Hyprland 之後怎麼把它組起來」。如果你還在「要不要走這條路」的階段，這篇就是那個模組的上游：先確認你要的是組裝自由、而不是開箱即用。</p>
<h2 id="wayland-vs-x11選型時避不開的底層判斷">Wayland vs X11：選型時避不開的底層判斷</h2>
<p>顯示協定是選型時的一條隱形軸線，因為它決定了一部分未來相容性。Wayland 是較新的顯示協定，設計上更安全、對高 DPI 與多螢幕不同刷新率支援更好，是 Linux 桌面的方向；X11 是沿用數十年的舊協定，相容性最廣但架構老舊、社群維護逐漸收斂到維持模式。GNOME、KDE Plasma、Hyprland 都已預設或原生 Wayland；XFCE、Cinnamon 目前仍以 X11 為主。</p>
<p>實務判讀：如果你用 NVIDIA 專有驅動、或依賴某些只支援 X11 的老工具（部分螢幕錄製、遠端桌面、自動化工具），X11 方案目前可能更少驚喜；如果你要高 DPI 筆電、多螢幕混合刷新率、或想跟上長期方向，優先選 Wayland 方案。這不是非此即彼的道德選擇，是看你的硬體與工具鏈落在哪邊。</p>
<h2 id="依情境選從你的處境倒推">依情境選：從你的處境倒推</h2>
<p>選型的最後一步是把上面的定位對回你自己的處境，不是背「哪個最好」：</p>
<ul>
<li><strong>剛從 Windows/macOS 轉來、只想要能用</strong>：KDE Plasma（熟悉隱喻 + 之後可深調）或 GNOME（接受新工作流、要最省心）。要極致熟悉感就 Cinnamon。</li>
<li><strong>老硬體 / 低階 VM / 要穩定不折騰的工作機</strong>：XFCE。資源最低、變動最少。</li>
<li><strong>想把桌面調成完全自己的樣子、願意投入組裝</strong>：Hyprland（或其他 WM）。先讀 <a href="/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">Rice 設計模組</a> 確認你要的是這條路。</li>
<li><strong>要高整合又要大量客製、不想從零組裝</strong>：KDE Plasma，是這兩個需求的平衡點。</li>
<li><strong>硬體是 NVIDIA 或依賴 X11 工具</strong>：優先 X11 成熟的方案（XFCE / Cinnamon），或確認你的 Wayland 方案在該硬體上的狀況再決定。</li>
</ul>
<p>判準是把「我願意花多少時間在桌面本身、我對客製的需求有多強、我的硬體與工具落在 Wayland 還是 X11」三個問題答清楚，選項自然收斂。沒有一個 DE 對所有人最好——選錯最常見的原因是拿別人的推薦套自己不同的處境。</p>
<h2 id="桌面環境的擴充生態">桌面環境的擴充生態</h2>
<p>選好 DE 之後，各方案還有自己的擴充路徑，這是「選項之下還有選項」的一層：</p>
<ul>
<li><strong>GNOME</strong>：透過 GNOME Extensions 加面板、工作流、狀態列元件，但要注意 extension 綁 GNOME 版本、大版本更新可能失效。</li>
<li><strong>KDE Plasma</strong>：內建的 widget（plasmoid）、全域主題、視窗規則系統，多數擴充不需要離開設定介面。</li>
<li><strong>XFCE / Cinnamon</strong>：panel plugin、applet、主題，擴充幅度中等但穩定。</li>
<li><strong>Hyprland</strong>：因為本來就是自己組，擴充等於換元件——換 bar（waybar/其他）、換啟動器、換通知 daemon，自由度最高也最需要自己維護。這一整套怎麼組，是 <a href="/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">Rice 設計模組</a> 的主題。</li>
</ul>
<h2 id="下一步">下一步</h2>
<ul>
<li>選了 Hyprland、要開始組裝：<a href="/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">Dotfile 管理：桌面 Rice 設計</a> 是「選了之後怎麼把它長成自己的樣子」的完整模組。</li>
<li>桌面選好、要裝檔案管理員這類 GUI app 時的相依判讀：<a href="../gui-file-manager-dependencies/">在 Hyprland 加圖形檔案管理員</a>。</li>
<li>純終端機環境不需要桌面環境、但需要對應的 CLI 工具：<a href="../../cli/">CLI 環境工具</a>。</li>
<li>桌面裝好後遇到顯示、鎖屏、服務層的問題怎麼診斷：<a href="../../../debug/">除錯與診斷</a>。</li>
</ul>
]]></content:encoded></item><item><title>現代 CLI 替代工具：grep、find、cat 之外的選擇</title><link>https://tarrragon.github.io/blog/linux/tools/cli/modern-cli-replacements/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/tools/cli/modern-cli-replacements/</guid><description>&lt;p>Linux 的預設指令（&lt;code>grep&lt;/code>、&lt;code>find&lt;/code>、&lt;code>cat&lt;/code>、&lt;code>ls&lt;/code>）能用，但它們是幾十年前為當時的環境設計的。過去十年出現一批用現代語言（多為 Rust）重寫的替代品，在同樣的工作上更快、預設行為更貼近日常開發、輸出更好讀。認識這些替代品的價值不在「預設的錯了」，而在你能在對的情境用更省力的工具——尤其是每天重複幾十次的搜尋與瀏覽。&lt;/p>
&lt;p>這篇按「你原本用什麼」對照現代替代品，講清楚每個替代品解掉原工具的什麼痛點、什麼情境值得換、以及換了要注意的地方。核心的搜尋三件套（&lt;code>ripgrep&lt;/code> / &lt;code>fd&lt;/code> / &lt;code>fzf&lt;/code>）值得優先掌握，其餘按需採用。&lt;/p>
&lt;h2 id="搜尋文字內容grep--ripgreprg">搜尋文字內容：grep → ripgrep（rg）&lt;/h2>
&lt;p>&lt;code>ripgrep&lt;/code>（指令是 &lt;code>rg&lt;/code>）是 &lt;code>grep&lt;/code> 的現代替代品，最大差別在它預設就做了開發者幾乎每次都要的事：遞迴搜尋當前目錄、尊重 &lt;code>.gitignore&lt;/code>（不會翻進 &lt;code>node_modules&lt;/code>、&lt;code>.git&lt;/code>、build 產物）、自動跳過二進位檔、輸出帶顏色與行號。用 &lt;code>grep&lt;/code> 搜整個專案常要寫 &lt;code>grep -rn --exclude-dir=node_modules pattern .&lt;/code>，用 &lt;code>rg pattern&lt;/code> 一句就到位且更快（Rust 實作 + 平行搜尋）。&lt;/p>
&lt;p>什麼情境值得換：在專案目錄裡找程式碼、找字串出現在哪些檔案——這是 &lt;code>rg&lt;/code> 最省力的地方。什麼時候還是用 &lt;code>grep&lt;/code>：處理 pipe 進來的串流（&lt;code>command | grep x&lt;/code>）兩者差不多；寫要在任何機器都能跑的腳本時 &lt;code>grep&lt;/code> 是保證存在的（&lt;code>rg&lt;/code> 要另外裝）。注意點：&lt;code>rg&lt;/code> 預設跳過 &lt;code>.gitignore&lt;/code> 忽略的檔，要搜被忽略的檔案得加 &lt;code>-u&lt;/code>（或 &lt;code>-uu&lt;/code> 連隱藏檔一起）。&lt;/p>
&lt;h2 id="找檔案find--fd">找檔案：find → fd&lt;/h2>
&lt;p>&lt;code>fd&lt;/code> 是 &lt;code>find&lt;/code> 的現代替代品，把 &lt;code>find&lt;/code> 那套冗長語法換成直覺的用法。&lt;code>find . -name '*.md' -type f&lt;/code> 用 &lt;code>fd&lt;/code> 是 &lt;code>fd -e md&lt;/code>；&lt;code>fd pattern&lt;/code> 直接對檔名做模糊比對，一樣預設遞迴、尊重 &lt;code>.gitignore&lt;/code>、跳過隱藏檔、輸出帶色。速度也快得多。&lt;/p>
&lt;p>什麼情境值得換：日常找檔案（「這個 config 檔在哪」「有哪些 .test.ts」）。什麼時候還是用 &lt;code>find&lt;/code>：&lt;code>find&lt;/code> 的 &lt;code>-exec&lt;/code>、複雜的時間 / 權限 / 大小條件組合仍然更全面，寫可攜腳本時 &lt;code>find&lt;/code> 保證存在。注意點：&lt;code>fd&lt;/code> 的正則 / glob 預設是 smart case（有大寫才區分大小寫），跟 &lt;code>find&lt;/code> 的精確比對行為不同。&lt;/p>
&lt;h2 id="互動式模糊搜尋fzf不是替代是加一層">互動式模糊搜尋：fzf（不是替代，是加一層）&lt;/h2>
&lt;p>&lt;code>fzf&lt;/code> 不替代任何工具，它是一層&lt;strong>互動式模糊選擇器&lt;/strong>，把「一堆候選 → 你挑一個」這件事變得極快。它從 stdin 吃候選清單、開一個可即時模糊過濾的介面、把你選的印到 stdout，所以能跟任何產生清單的指令組合：&lt;code>fd -e md | fzf&lt;/code> 挑一個 markdown 檔、&lt;code>git branch | fzf&lt;/code> 挑分支、&lt;code>rg --files | fzf&lt;/code> 挑檔案開。&lt;/p>
&lt;p>最高價值的兩個內建整合：&lt;code>Ctrl+R&lt;/code> 覆寫 shell 的歷史搜尋（模糊搜尋整個命令歷史，比預設的反向搜尋強太多）、&lt;code>Ctrl+T&lt;/code> 把檔案路徑插進當前命令列。這兩個 key binding &lt;strong>不是裝完自動生效的&lt;/strong>，要在 shell 設定裡啟用——新版 &lt;code>fzf&lt;/code> 直接在 &lt;code>.zshrc&lt;/code> / &lt;code>.bashrc&lt;/code> 加一行 &lt;code>eval &amp;quot;$(fzf --zsh)&amp;quot;&lt;/code>（bash 用 &lt;code>--bash&lt;/code>）；舊版則 source 套件附的整合檔（Arch 在 &lt;code>/usr/share/fzf/key-bindings.zsh&lt;/code> 與 &lt;code>completion.zsh&lt;/code>，路徑隨發行版不同）。沒加這行，&lt;code>Ctrl+R&lt;/code> 不會有反應。&lt;code>fd&lt;/code> 跟 &lt;code>rg&lt;/code> 可以當 &lt;code>fzf&lt;/code> 的預設來源，三者是一組。&lt;/p>
&lt;h2 id="看檔案內容cat--bat">看檔案內容：cat → bat&lt;/h2>
&lt;p>&lt;code>bat&lt;/code> 是 &lt;code>cat&lt;/code> 加上語法高亮、行號、Git 修改標記、自動分頁。看程式碼 / 設定檔時比 &lt;code>cat&lt;/code> 好讀很多。&lt;code>bat file.py&lt;/code> 直接帶高亮顯示。&lt;/p>
&lt;p>什麼情境值得換：人在終端機讀檔案內容。什麼時候還是用 &lt;code>cat&lt;/code>：把檔案內容 pipe 給其他程式、或在腳本裡——這時要純內容，用 &lt;code>cat&lt;/code>（&lt;code>bat&lt;/code> 在偵測到輸出不是終端機時會自動退化成類 &lt;code>cat&lt;/code> 行為，但明確用 &lt;code>cat&lt;/code> 更穩）。注意點：&lt;code>bat&lt;/code> 預設會分頁（走 &lt;code>less&lt;/code>），在腳本 / pipe 情境要加 &lt;code>--paging=never&lt;/code> 或 &lt;code>-pp&lt;/code>。&lt;/p>
&lt;h2 id="列目錄ls--eza">列目錄：ls → eza&lt;/h2>
&lt;p>&lt;code>eza&lt;/code>（&lt;code>exa&lt;/code> 的維護接棒者）是 &lt;code>ls&lt;/code> 的現代替代品，預設輸出帶顏色與圖示、更好的欄位對齊、內建 Git 狀態欄、&lt;code>--tree&lt;/code> 直接畫樹狀。&lt;code>eza -la --git&lt;/code> 一眼看到權限、大小、修改時間、Git 狀態。&lt;/p>
&lt;p>什麼情境值得換：人在終端機瀏覽目錄。什麼時候還是用 &lt;code>ls&lt;/code>：腳本裡解析輸出用 &lt;code>ls&lt;/code> 更穩定（現代替代品的欄位格式可能隨版本變），可攜腳本 &lt;code>ls&lt;/code> 保證存在。&lt;/p></description><content:encoded><![CDATA[<p>Linux 的預設指令（<code>grep</code>、<code>find</code>、<code>cat</code>、<code>ls</code>）能用，但它們是幾十年前為當時的環境設計的。過去十年出現一批用現代語言（多為 Rust）重寫的替代品，在同樣的工作上更快、預設行為更貼近日常開發、輸出更好讀。認識這些替代品的價值不在「預設的錯了」，而在你能在對的情境用更省力的工具——尤其是每天重複幾十次的搜尋與瀏覽。</p>
<p>這篇按「你原本用什麼」對照現代替代品，講清楚每個替代品解掉原工具的什麼痛點、什麼情境值得換、以及換了要注意的地方。核心的搜尋三件套（<code>ripgrep</code> / <code>fd</code> / <code>fzf</code>）值得優先掌握，其餘按需採用。</p>
<h2 id="搜尋文字內容grep--ripgreprg">搜尋文字內容：grep → ripgrep（rg）</h2>
<p><code>ripgrep</code>（指令是 <code>rg</code>）是 <code>grep</code> 的現代替代品，最大差別在它預設就做了開發者幾乎每次都要的事：遞迴搜尋當前目錄、尊重 <code>.gitignore</code>（不會翻進 <code>node_modules</code>、<code>.git</code>、build 產物）、自動跳過二進位檔、輸出帶顏色與行號。用 <code>grep</code> 搜整個專案常要寫 <code>grep -rn --exclude-dir=node_modules pattern .</code>，用 <code>rg pattern</code> 一句就到位且更快（Rust 實作 + 平行搜尋）。</p>
<p>什麼情境值得換：在專案目錄裡找程式碼、找字串出現在哪些檔案——這是 <code>rg</code> 最省力的地方。什麼時候還是用 <code>grep</code>：處理 pipe 進來的串流（<code>command | grep x</code>）兩者差不多；寫要在任何機器都能跑的腳本時 <code>grep</code> 是保證存在的（<code>rg</code> 要另外裝）。注意點：<code>rg</code> 預設跳過 <code>.gitignore</code> 忽略的檔，要搜被忽略的檔案得加 <code>-u</code>（或 <code>-uu</code> 連隱藏檔一起）。</p>
<h2 id="找檔案find--fd">找檔案：find → fd</h2>
<p><code>fd</code> 是 <code>find</code> 的現代替代品，把 <code>find</code> 那套冗長語法換成直覺的用法。<code>find . -name '*.md' -type f</code> 用 <code>fd</code> 是 <code>fd -e md</code>；<code>fd pattern</code> 直接對檔名做模糊比對，一樣預設遞迴、尊重 <code>.gitignore</code>、跳過隱藏檔、輸出帶色。速度也快得多。</p>
<p>什麼情境值得換：日常找檔案（「這個 config 檔在哪」「有哪些 .test.ts」）。什麼時候還是用 <code>find</code>：<code>find</code> 的 <code>-exec</code>、複雜的時間 / 權限 / 大小條件組合仍然更全面，寫可攜腳本時 <code>find</code> 保證存在。注意點：<code>fd</code> 的正則 / glob 預設是 smart case（有大寫才區分大小寫），跟 <code>find</code> 的精確比對行為不同。</p>
<h2 id="互動式模糊搜尋fzf不是替代是加一層">互動式模糊搜尋：fzf（不是替代，是加一層）</h2>
<p><code>fzf</code> 不替代任何工具，它是一層<strong>互動式模糊選擇器</strong>，把「一堆候選 → 你挑一個」這件事變得極快。它從 stdin 吃候選清單、開一個可即時模糊過濾的介面、把你選的印到 stdout，所以能跟任何產生清單的指令組合：<code>fd -e md | fzf</code> 挑一個 markdown 檔、<code>git branch | fzf</code> 挑分支、<code>rg --files | fzf</code> 挑檔案開。</p>
<p>最高價值的兩個內建整合：<code>Ctrl+R</code> 覆寫 shell 的歷史搜尋（模糊搜尋整個命令歷史，比預設的反向搜尋強太多）、<code>Ctrl+T</code> 把檔案路徑插進當前命令列。這兩個 key binding <strong>不是裝完自動生效的</strong>，要在 shell 設定裡啟用——新版 <code>fzf</code> 直接在 <code>.zshrc</code> / <code>.bashrc</code> 加一行 <code>eval &quot;$(fzf --zsh)&quot;</code>（bash 用 <code>--bash</code>）；舊版則 source 套件附的整合檔（Arch 在 <code>/usr/share/fzf/key-bindings.zsh</code> 與 <code>completion.zsh</code>，路徑隨發行版不同）。沒加這行，<code>Ctrl+R</code> 不會有反應。<code>fd</code> 跟 <code>rg</code> 可以當 <code>fzf</code> 的預設來源，三者是一組。</p>
<h2 id="看檔案內容cat--bat">看檔案內容：cat → bat</h2>
<p><code>bat</code> 是 <code>cat</code> 加上語法高亮、行號、Git 修改標記、自動分頁。看程式碼 / 設定檔時比 <code>cat</code> 好讀很多。<code>bat file.py</code> 直接帶高亮顯示。</p>
<p>什麼情境值得換：人在終端機讀檔案內容。什麼時候還是用 <code>cat</code>：把檔案內容 pipe 給其他程式、或在腳本裡——這時要純內容，用 <code>cat</code>（<code>bat</code> 在偵測到輸出不是終端機時會自動退化成類 <code>cat</code> 行為，但明確用 <code>cat</code> 更穩）。注意點：<code>bat</code> 預設會分頁（走 <code>less</code>），在腳本 / pipe 情境要加 <code>--paging=never</code> 或 <code>-pp</code>。</p>
<h2 id="列目錄ls--eza">列目錄：ls → eza</h2>
<p><code>eza</code>（<code>exa</code> 的維護接棒者）是 <code>ls</code> 的現代替代品，預設輸出帶顏色與圖示、更好的欄位對齊、內建 Git 狀態欄、<code>--tree</code> 直接畫樹狀。<code>eza -la --git</code> 一眼看到權限、大小、修改時間、Git 狀態。</p>
<p>什麼情境值得換：人在終端機瀏覽目錄。什麼時候還是用 <code>ls</code>：腳本裡解析輸出用 <code>ls</code> 更穩定（現代替代品的欄位格式可能隨版本變），可攜腳本 <code>ls</code> 保證存在。</p>
<h2 id="其他常見替代">其他常見替代</h2>
<p>按需採用，痛點對上了再裝：</p>
<table>
  <thead>
      <tr>
          <th>原工具</th>
          <th>替代品</th>
          <th>解的痛點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>cd</code></td>
          <td><code>zoxide</code></td>
          <td>記住常去的目錄，<code>z proj</code> 跳到最常用的（需 <code>eval &quot;$(zoxide init zsh)&quot;</code> 進 shell 才攔截 <code>z</code>）</td>
      </tr>
      <tr>
          <td><code>ps</code></td>
          <td><code>procs</code></td>
          <td>帶色、預設欄位更實用、支援樹狀與關鍵字過濾</td>
      </tr>
      <tr>
          <td><code>du</code></td>
          <td><code>ncdu</code> / <code>dust</code></td>
          <td>找誰佔空間：<code>ncdu</code> 互動逐層鑽 + 就地刪，<code>dust</code> 一次性樹狀視覺（見下）</td>
      </tr>
      <tr>
          <td><code>df</code></td>
          <td><code>duf</code></td>
          <td>表格化、帶色、好讀的磁碟用量</td>
      </tr>
      <tr>
          <td><code>top</code></td>
          <td><code>htop</code> / <code>btop</code></td>
          <td>互動式監控：<code>htop</code> 輕量近乎必備，<code>btop</code> 更完整的全螢幕儀表（見下）</td>
      </tr>
      <tr>
          <td><code>git diff</code></td>
          <td><code>delta</code> / <code>difftastic</code></td>
          <td>diff pager：<code>delta</code> 語法高亮 + 並排，<code>difftastic</code> 語法感知（比 AST 不比行；執行檔是 <code>difft</code>）</td>
      </tr>
      <tr>
          <td><code>man</code></td>
          <td><code>tldr</code></td>
          <td>只給常用範例，不用讀完整 man page</td>
      </tr>
      <tr>
          <td><code>sed</code>（簡單替換）</td>
          <td><code>sd</code></td>
          <td>直覺的 <code>sd 舊 新</code>，不用記 <code>sed</code> 的跳脫規則</td>
      </tr>
  </tbody>
</table>
<p>有兩個替代品的那幾格，差別不是「新舊」而是不同的使用模型，值得分清楚：</p>
<ul>
<li><strong><code>du</code> 的 <code>ncdu</code> vs <code>dust</code></strong>：兩者解的動作不同。<code>ncdu</code> 是互動式的——它掃一次目錄，開一個能逐層往下鑽、當場按鍵刪檔的介面，是「磁碟滿了、要找出並清掉大檔」這個任務的正典（清空間邊找邊刪）。<code>dust</code> 是一次性的——跑完印一張樹狀 + 長條的靜態報告，適合「快速看一眼誰佔空間」而不打算就地刪。要清理用 <code>ncdu</code>，要瞄一眼用 <code>dust</code>。</li>
<li><strong><code>top</code> 的 <code>htop</code> vs <code>btop</code></strong>：<code>htop</code> 是輕量、幾乎每台機器都該有的安全預設——彩色、可捲動、能直接對 process 送訊號，資源佔用極低，SSH 進一台陌生機器臨時看負載首選它。<code>btop</code> 是更完整的全螢幕儀表（CPU / 記憶體 / 網路 / 磁碟的圖形化面板），好看資訊多，但相對重、依賴多；當常駐監控台用很讚，臨時排查用 <code>htop</code> 更快到位。TUI 監控工具的完整比較見 <a href="../tui-monitoring-tools/">TUI 監控工具</a>。</li>
</ul>
<h2 id="採用策略與注意事項">採用策略與注意事項</h2>
<p><strong>優先順序</strong>：搜尋三件套（<code>rg</code> / <code>fd</code> / <code>fzf</code>）投報率最高，每天用幾十次、學習成本低，先裝這三個。<code>bat</code> / <code>eza</code> 是體驗提升，其餘按痛點採用。</p>
<p><strong>跨機器 / CI 的腳本別依賴它們</strong>：要在別台機器或 CI 跑的腳本，用保證存在的 <code>grep</code> / <code>find</code> / <code>cat</code>，因為現代替代品不一定裝了。專案內部、已宣告了 dev 依賴的腳本（devcontainer、Makefile）另當別論——那裡明確裝了 <code>rg</code> / <code>fd</code>，用它們（甚至還因為 <code>rg</code> 尊重 <code>.gitignore</code> 的語意才選它）很合理。分界是「這段腳本會在沒裝這些工具的環境跑嗎」，不是「腳本一律不能用」。</p>
<p><strong>安裝</strong>：多數在各發行版套件庫直接有。Arch：<code>pacman -S ripgrep fd fzf bat eza zoxide</code>，按需再加 <code>procs ncdu htop git-delta</code>（<code>delta</code> 的套件名是 <code>git-delta</code>）。Debian / Ubuntu 有兩個要注意的改名坑——<code>fd</code> 的執行檔在 apt 上叫 <code>fdfind</code>、<code>bat</code> 叫 <code>batcat</code>（都是為了避免跟既有套件撞名），要自己 alias 回 <code>fd</code> / <code>bat</code>；<code>eza</code> 較舊的 apt 源可能還沒有，用 cargo 或官方 repo 裝。macOS 用 <code>brew install ripgrep fd fzf bat eza zoxide</code>。裝好後它們該進你的 <a href="/blog/linux/dotfile/08-sync-bootstrap/bootstrap-script-packages/" data-link-title="Bootstrap Script 與套件清單管理" data-link-desc="寫 dotfile 的 install script、或整理「這台機器裝了什麼」的套件清單時回來讀">dotfile 套件清單</a>，新機器 bootstrap 時一次裝齊。</p>
<h2 id="相關">相關</h2>
<ul>
<li>這篇是「日常指令替代」；全螢幕 TUI 工具（監控、Git、檔案瀏覽、資料庫）見 <a href="../">CLI 環境工具</a> 的其他文章。</li>
<li>為什麼把工具當成「有取捨的選項」而非唯一答案，見 <a href="../">工具選單總覽</a>。</li>
<li>這些工具進 dotfile 套件清單、一鍵安裝的做法，見 <a href="/blog/linux/dotfile/08-sync-bootstrap/bootstrap-script-packages/" data-link-title="Bootstrap Script 與套件清單管理" data-link-desc="寫 dotfile 的 install script、或整理「這台機器裝了什麼」的套件清單時回來讀">模組八：Bootstrap script 與套件清單</a>。</li>
</ul>
]]></content:encoded></item><item><title>診斷心法：讀權威狀態，不靠肉眼猜表象</title><link>https://tarrragon.github.io/blog/linux/debug/diagnosis-read-authoritative-state/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/debug/diagnosis-read-authoritative-state/</guid><description>&lt;p>診斷一個 Linux 問題時，第一個動作不是猜「這看起來像什麼」，而是問「這件事的權威狀態在哪裡、我怎麼去讀它」。畫面上的現象、終端機捲過的輸出、一個視窗長什麼樣，都是表象；表象會騙人。真正能定案的是系統裡記錄這件事的那個權威來源——程式自己的 log、服務註冊表、核心與 systemd 的狀態、資源用量。把判斷建立在權威狀態上，而不是肉眼看到的樣子，是快速且不猜錯的除錯的核心。&lt;/p>
&lt;p>這篇講的是一套判讀紀律，不是某個特定工具。後面幾篇（&lt;a href="../ssh-and-terminal-troubleshooting/">遠端連線與終端機問題&lt;/a>、&lt;a href="../machine-unreachable/">機器連不到或起不來&lt;/a>、&lt;a href="../process-service-state-diagnosis/">程序、服務與狀態怎麼判&lt;/a>）是這套紀律在各種具體情境的應用。&lt;/p>
&lt;h2 id="表象會騙人一個判斷被畫面帶偏兩次的實例">表象會騙人：一個判斷被畫面帶偏兩次的實例&lt;/h2>
&lt;p>一個具體案例最能說明為什麼不能靠肉眼。在一次桌面 shell（畫桌面 UI 的圖形程式，不是 bash/zsh 那種命令列 shell）的除錯裡，畫面中央出現一個「輸入密碼」的覆蓋層，配著時鐘、天氣、通知的整片儀表板。第一眼的判斷很自然：螢幕被鎖住了。&lt;/p>
&lt;p>接著幾個看似合理的檢查反而把判斷帶得更偏：&lt;code>loginctl&lt;/code> 查不到這個 session 的 &lt;code>LockedHint&lt;/code>、&lt;code>pgrep&lt;/code> 找不到任何獨立的鎖屏程式、那個 shell 的 CLI 也沒有 lock 指令。三個訊號湊起來，得出一個「更正」的結論：這不是真的鎖，只是一個長得像鎖屏的儀表板面板。&lt;/p>
&lt;p>這個「更正」是錯的。真正定案是靠讀那個 shell &lt;strong>自己寫的 log&lt;/strong>：log 裡明明白白有鎖屏模組被載入、有 idle 計時器在數秒數、時間到就觸發鎖定。它是一個真的螢幕鎖，走的是 Wayland 的 session-lock 協議。&lt;/p>
&lt;p>為什麼前面三個檢查會誤導？因為它們讀的是&lt;strong>錯的權威來源&lt;/strong>。&lt;code>loginctl&lt;/code> 的 &lt;code>LockedHint&lt;/code> 是 logind（systemd 的登入管理）那一層的鎖定狀態，而這個鎖走的是 Wayland 合成器（compositor，負責把視窗合成到螢幕、管輸入輸出的核心程式）那一層的協議，兩者是獨立機制——查 logind 對合成器層的鎖天生查不到，不是「沒鎖」，是查錯地方。&lt;code>pgrep&lt;/code> 找不到獨立程式，是因為鎖屏畫面由 shell 主程式在自己的行程內畫，本來就沒有另一個可執行檔可抓。真正記錄「有沒有鎖、為什麼鎖」的權威來源，是那個 shell 的 log；讀到它，一次就定案。&lt;/p>
&lt;p>肉眼加上讀錯層的檢查，猜錯了兩次；讀對權威來源，一次就對。教訓不是「那些工具沒用」，是&lt;strong>要先確認你讀的是不是這件事的權威狀態&lt;/strong>。&lt;/p>
&lt;h2 id="每種問題都有它的權威狀態來源">每種問題都有它的權威狀態來源&lt;/h2>
&lt;p>除錯的第一步，是為眼前的現象找到記錄它的權威來源。不同類別的問題，權威來源不同：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>問題類別&lt;/th>
 &lt;th>權威狀態來源&lt;/th>
 &lt;th>讀它的工具&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>某程式的行為&lt;/td>
 &lt;td>那個程式自己的 log 檔&lt;/td>
 &lt;td>程式的 log 路徑、&lt;code>journalctl -u &amp;lt;服務&amp;gt;&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務由誰提供&lt;/td>
 &lt;td>D-Bus / socket 的服務註冊&lt;/td>
 &lt;td>&lt;code>busctl&lt;/code>、&lt;code>ss&lt;/code>、&lt;code>lsof&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>登入 / 鎖定狀態&lt;/td>
 &lt;td>logind&lt;/td>
 &lt;td>&lt;code>loginctl show-session&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務有沒有在跑&lt;/td>
 &lt;td>systemd unit 狀態&lt;/td>
 &lt;td>&lt;code>systemctl status&lt;/code>、&lt;code>systemctl is-active&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>程式有沒有活著&lt;/td>
 &lt;td>行程表（比對正確的 comm 名）&lt;/td>
 &lt;td>&lt;code>pgrep -x&lt;/code>、&lt;code>ps&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>網路通不通&lt;/td>
 &lt;td>介面 / 路由 / 鄰居表&lt;/td>
 &lt;td>&lt;code>ip -brief a&lt;/code>、&lt;code>ip neigh&lt;/code>、&lt;code>ss&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>磁碟 / 記憶體&lt;/td>
 &lt;td>檔案系統與記憶體用量&lt;/td>
 &lt;td>&lt;code>df -h&lt;/code>、&lt;code>du -sh&lt;/code>、&lt;code>free&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>核心 / 硬體 / 被殺行程&lt;/td>
 &lt;td>kernel ring buffer&lt;/td>
 &lt;td>&lt;code>dmesg&lt;/code>、&lt;code>journalctl -k&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>程式 log 沉默時的 syscall&lt;/td>
 &lt;td>系統呼叫層&lt;/td>
 &lt;td>&lt;code>strace -f -e trace=file&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這張表的用法不是背它，是養成一個反射：看到現象先問「這件事的權威狀態記在哪張表裡」，再去讀那張表，而不是從畫面推測。下面幾個常見的判錯，都是讀了表象而不是權威來源。&lt;/p>
&lt;h3 id="讀對權威來源但查詢條件要對">讀對權威來源、但查詢條件要對&lt;/h3>
&lt;p>有時權威來源對了，還是會被誤導——因為查詢的條件寫錯。判程式活著沒，行程表是對的權威、&lt;code>pgrep&lt;/code> 是對的工具，但你得比對它&lt;strong>實際的行程名&lt;/strong>：一個程式可能以 symlink 的短名在跑，用你以為的名字 &lt;code>pgrep&lt;/code> 就掃不到、誤判成掛了。判服務由誰提供，權威是服務註冊表而非畫面（送一則通知看畫面有沒有跳不可靠——沒跳可能是勿擾吃掉或根本沒送出）。這兩類的具體查法（&lt;code>pgrep -x&lt;/code>、&lt;code>busctl&lt;/code> 查 D-Bus 擁有者）見 &lt;a href="../process-service-state-diagnosis/">程序、服務與狀態怎麼判&lt;/a>。重點是：權威來源對，還要問對地方、用對條件。&lt;/p>
&lt;h3 id="卡住是資源問題還是相容問題先看資源別先怪相容性">卡住是資源問題還是相容問題：先看資源，別先怪相容性&lt;/h3>
&lt;p>一個耗時的操作中途停住時，很容易直接跳到「是不是這個平台不相容 / 這個東西在這台機器上跑不起來」。但這個結論成本很高（可能讓你放棄一條其實可行的路），而它的權威狀態很好查。一次原始碼編譯跑到一半停住，第一個該看的是資源：&lt;code>df -h&lt;/code> 看磁碟是不是滿了、記憶體是不是被吃光——一次實際的案例就是主機磁碟寫滿把編譯中途打斷，清出空間後同一份原始碼接著編就過，跟平台相容性完全無關。先讀資源狀態排除掉最廉價的解釋，再去懷疑相容性這種昂貴的結論。&lt;/p>
&lt;h2 id="讀程式自己的-log從症狀往上游找">讀程式自己的 log：從症狀往上游找&lt;/h2>
&lt;p>當現象是「某個程式行為不對」，它自己的 log 幾乎總是比終端機捲過的畫面更接近真相。很多程式在終端機只印一段摘要，卻同時把詳細執行紀錄寫進一個 log 檔或系統日誌；當畫面上的訊息不足以定位時，那份 log 裡往往就有明確答案。&lt;/p>
&lt;p>找 log 的常見去處：程式自己的 log 檔（常在 &lt;code>~/.local/state/&amp;lt;程式&amp;gt;/&lt;/code> 或 &lt;code>~/.cache/&amp;lt;程式&amp;gt;/&lt;/code> 底下）、systemd 服務的 &lt;code>journalctl -u &amp;lt;服務名&amp;gt;&lt;/code>、或程式啟動時印出的 log 路徑。找到之後，關鍵是&lt;strong>用症狀當關鍵字往上游搜&lt;/strong>——&lt;code>grep -iE 'error|fail|not found|does not exist' &amp;lt;log&amp;gt;&lt;/code> 挑出異常行，或在 &lt;code>less&lt;/code> 裡用 &lt;code>?pattern&lt;/code> 往回找「第一個」異常（不是停在最後一個下游錯）。一個指令因為前面某個檔案不存在而失敗，終端機可能只報一個看似無關的下游錯誤，但 log 裡會有那句 &lt;code>File does not exist&lt;/code> 直指源頭。一個實際案例：某 shell 換了配色卻沒生效，畫面上什麼錯都沒有，是它的 log 裡一句「讀取 scheme 檔失敗：檔案不存在」點出根因——原來那個檔在 shell 啟動當下還沒被建出來。畫面沉默，log 說話。&lt;/p>
&lt;p>這一層跟 &lt;a href="../../install/observable-bootstrap/">可除錯的 bootstrap&lt;/a> 是一體兩面：那篇談怎麼讓你自己寫的腳本&lt;strong>產生&lt;/strong>一份可診斷的 log，這裡談除錯時怎麼&lt;strong>去讀&lt;/strong>程式自己的 log。兩邊的共同紀律是：不要只盯著終端機捲動，去找那份持久的、詳細的權威紀錄。&lt;/p></description><content:encoded><![CDATA[<p>診斷一個 Linux 問題時，第一個動作不是猜「這看起來像什麼」，而是問「這件事的權威狀態在哪裡、我怎麼去讀它」。畫面上的現象、終端機捲過的輸出、一個視窗長什麼樣，都是表象；表象會騙人。真正能定案的是系統裡記錄這件事的那個權威來源——程式自己的 log、服務註冊表、核心與 systemd 的狀態、資源用量。把判斷建立在權威狀態上，而不是肉眼看到的樣子，是快速且不猜錯的除錯的核心。</p>
<p>這篇講的是一套判讀紀律，不是某個特定工具。後面幾篇（<a href="../ssh-and-terminal-troubleshooting/">遠端連線與終端機問題</a>、<a href="../machine-unreachable/">機器連不到或起不來</a>、<a href="../process-service-state-diagnosis/">程序、服務與狀態怎麼判</a>）是這套紀律在各種具體情境的應用。</p>
<h2 id="表象會騙人一個判斷被畫面帶偏兩次的實例">表象會騙人：一個判斷被畫面帶偏兩次的實例</h2>
<p>一個具體案例最能說明為什麼不能靠肉眼。在一次桌面 shell（畫桌面 UI 的圖形程式，不是 bash/zsh 那種命令列 shell）的除錯裡，畫面中央出現一個「輸入密碼」的覆蓋層，配著時鐘、天氣、通知的整片儀表板。第一眼的判斷很自然：螢幕被鎖住了。</p>
<p>接著幾個看似合理的檢查反而把判斷帶得更偏：<code>loginctl</code> 查不到這個 session 的 <code>LockedHint</code>、<code>pgrep</code> 找不到任何獨立的鎖屏程式、那個 shell 的 CLI 也沒有 lock 指令。三個訊號湊起來，得出一個「更正」的結論：這不是真的鎖，只是一個長得像鎖屏的儀表板面板。</p>
<p>這個「更正」是錯的。真正定案是靠讀那個 shell <strong>自己寫的 log</strong>：log 裡明明白白有鎖屏模組被載入、有 idle 計時器在數秒數、時間到就觸發鎖定。它是一個真的螢幕鎖，走的是 Wayland 的 session-lock 協議。</p>
<p>為什麼前面三個檢查會誤導？因為它們讀的是<strong>錯的權威來源</strong>。<code>loginctl</code> 的 <code>LockedHint</code> 是 logind（systemd 的登入管理）那一層的鎖定狀態，而這個鎖走的是 Wayland 合成器（compositor，負責把視窗合成到螢幕、管輸入輸出的核心程式）那一層的協議，兩者是獨立機制——查 logind 對合成器層的鎖天生查不到，不是「沒鎖」，是查錯地方。<code>pgrep</code> 找不到獨立程式，是因為鎖屏畫面由 shell 主程式在自己的行程內畫，本來就沒有另一個可執行檔可抓。真正記錄「有沒有鎖、為什麼鎖」的權威來源，是那個 shell 的 log；讀到它，一次就定案。</p>
<p>肉眼加上讀錯層的檢查，猜錯了兩次；讀對權威來源，一次就對。教訓不是「那些工具沒用」，是<strong>要先確認你讀的是不是這件事的權威狀態</strong>。</p>
<h2 id="每種問題都有它的權威狀態來源">每種問題都有它的權威狀態來源</h2>
<p>除錯的第一步，是為眼前的現象找到記錄它的權威來源。不同類別的問題，權威來源不同：</p>
<table>
  <thead>
      <tr>
          <th>問題類別</th>
          <th>權威狀態來源</th>
          <th>讀它的工具</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>某程式的行為</td>
          <td>那個程式自己的 log 檔</td>
          <td>程式的 log 路徑、<code>journalctl -u &lt;服務&gt;</code></td>
      </tr>
      <tr>
          <td>服務由誰提供</td>
          <td>D-Bus / socket 的服務註冊</td>
          <td><code>busctl</code>、<code>ss</code>、<code>lsof</code></td>
      </tr>
      <tr>
          <td>登入 / 鎖定狀態</td>
          <td>logind</td>
          <td><code>loginctl show-session</code></td>
      </tr>
      <tr>
          <td>服務有沒有在跑</td>
          <td>systemd unit 狀態</td>
          <td><code>systemctl status</code>、<code>systemctl is-active</code></td>
      </tr>
      <tr>
          <td>程式有沒有活著</td>
          <td>行程表（比對正確的 comm 名）</td>
          <td><code>pgrep -x</code>、<code>ps</code></td>
      </tr>
      <tr>
          <td>網路通不通</td>
          <td>介面 / 路由 / 鄰居表</td>
          <td><code>ip -brief a</code>、<code>ip neigh</code>、<code>ss</code></td>
      </tr>
      <tr>
          <td>磁碟 / 記憶體</td>
          <td>檔案系統與記憶體用量</td>
          <td><code>df -h</code>、<code>du -sh</code>、<code>free</code></td>
      </tr>
      <tr>
          <td>核心 / 硬體 / 被殺行程</td>
          <td>kernel ring buffer</td>
          <td><code>dmesg</code>、<code>journalctl -k</code></td>
      </tr>
      <tr>
          <td>程式 log 沉默時的 syscall</td>
          <td>系統呼叫層</td>
          <td><code>strace -f -e trace=file</code></td>
      </tr>
  </tbody>
</table>
<p>這張表的用法不是背它，是養成一個反射：看到現象先問「這件事的權威狀態記在哪張表裡」，再去讀那張表，而不是從畫面推測。下面幾個常見的判錯，都是讀了表象而不是權威來源。</p>
<h3 id="讀對權威來源但查詢條件要對">讀對權威來源、但查詢條件要對</h3>
<p>有時權威來源對了，還是會被誤導——因為查詢的條件寫錯。判程式活著沒，行程表是對的權威、<code>pgrep</code> 是對的工具，但你得比對它<strong>實際的行程名</strong>：一個程式可能以 symlink 的短名在跑，用你以為的名字 <code>pgrep</code> 就掃不到、誤判成掛了。判服務由誰提供，權威是服務註冊表而非畫面（送一則通知看畫面有沒有跳不可靠——沒跳可能是勿擾吃掉或根本沒送出）。這兩類的具體查法（<code>pgrep -x</code>、<code>busctl</code> 查 D-Bus 擁有者）見 <a href="../process-service-state-diagnosis/">程序、服務與狀態怎麼判</a>。重點是：權威來源對，還要問對地方、用對條件。</p>
<h3 id="卡住是資源問題還是相容問題先看資源別先怪相容性">卡住是資源問題還是相容問題：先看資源，別先怪相容性</h3>
<p>一個耗時的操作中途停住時，很容易直接跳到「是不是這個平台不相容 / 這個東西在這台機器上跑不起來」。但這個結論成本很高（可能讓你放棄一條其實可行的路），而它的權威狀態很好查。一次原始碼編譯跑到一半停住，第一個該看的是資源：<code>df -h</code> 看磁碟是不是滿了、記憶體是不是被吃光——一次實際的案例就是主機磁碟寫滿把編譯中途打斷，清出空間後同一份原始碼接著編就過，跟平台相容性完全無關。先讀資源狀態排除掉最廉價的解釋，再去懷疑相容性這種昂貴的結論。</p>
<h2 id="讀程式自己的-log從症狀往上游找">讀程式自己的 log：從症狀往上游找</h2>
<p>當現象是「某個程式行為不對」，它自己的 log 幾乎總是比終端機捲過的畫面更接近真相。很多程式在終端機只印一段摘要，卻同時把詳細執行紀錄寫進一個 log 檔或系統日誌；當畫面上的訊息不足以定位時，那份 log 裡往往就有明確答案。</p>
<p>找 log 的常見去處：程式自己的 log 檔（常在 <code>~/.local/state/&lt;程式&gt;/</code> 或 <code>~/.cache/&lt;程式&gt;/</code> 底下）、systemd 服務的 <code>journalctl -u &lt;服務名&gt;</code>、或程式啟動時印出的 log 路徑。找到之後，關鍵是<strong>用症狀當關鍵字往上游搜</strong>——<code>grep -iE 'error|fail|not found|does not exist' &lt;log&gt;</code> 挑出異常行，或在 <code>less</code> 裡用 <code>?pattern</code> 往回找「第一個」異常（不是停在最後一個下游錯）。一個指令因為前面某個檔案不存在而失敗，終端機可能只報一個看似無關的下游錯誤，但 log 裡會有那句 <code>File does not exist</code> 直指源頭。一個實際案例：某 shell 換了配色卻沒生效，畫面上什麼錯都沒有，是它的 log 裡一句「讀取 scheme 檔失敗：檔案不存在」點出根因——原來那個檔在 shell 啟動當下還沒被建出來。畫面沉默，log 說話。</p>
<p>這一層跟 <a href="../../install/observable-bootstrap/">可除錯的 bootstrap</a> 是一體兩面：那篇談怎麼讓你自己寫的腳本<strong>產生</strong>一份可診斷的 log，這裡談除錯時怎麼<strong>去讀</strong>程式自己的 log。兩邊的共同紀律是：不要只盯著終端機捲動，去找那份持久的、詳細的權威紀錄。</p>
<h2 id="遠端除錯反而逼出好紀律">遠端除錯反而逼出好紀律</h2>
<p>透過 SSH 遠端除錯時，你看不到那台機器的畫面——這個限制反而是好事。看不到畫面，你就沒得靠肉眼猜，只能去讀權威狀態：查 log、查服務註冊、查行程表、查資源。很多在本地會犯的「看畫面就下結論」的錯，在遠端因為根本沒畫面可看而自動被避開。</p>
<p>反過來說，在本地（或看得到畫面的 VM）除錯時，畫面的存在是個誘惑：它讓你以為看到了就懂了。前面那個鎖屏誤判，正是發生在「看得到畫面」的情境——畫面上的密碼框太有說服力，反而蓋過了去讀 log 的動作。把遠端那套「沒有畫面、只信權威狀態」的紀律，也用在本地，就不會被畫面帶偏。</p>
<h2 id="判讀紀律四步">判讀紀律：四步</h2>
<p>把上面的東西收成一套每次都能跑的流程：</p>
<ol>
<li><strong>描述症狀</strong>：現象是什麼，先講清楚，不要在這步就急著下結論（「畫面出現密碼框」，不是「螢幕鎖了」）。</li>
<li><strong>定位權威來源</strong>：這件事的權威狀態記在哪——log、服務註冊、logind / systemd、行程表、資源用量（用上面那張表對照）。</li>
<li><strong>用對的工具讀它</strong>：讀那個權威來源，不是讀畫面、不是讀終端機捲過的殘影。</li>
<li><strong>權威跟表象矛盾時，信權威</strong>：如果讀到的權威狀態跟你肉眼的第一印象打架，信權威狀態、回頭修正第一印象——那個矛盾點通常就是你原本會猜錯的地方。</li>
</ol>
<p>這套流程的價值不在任何單一工具，在於它讓你的判斷有一個可回溯的依據，而不是一串越猜越偏的直覺。</p>
<h2 id="下一步">下一步</h2>
<ul>
<li>這套心法在遠端連線與終端機情境的應用，見 <a href="../ssh-and-terminal-troubleshooting/">遠端連線與終端機問題</a>。</li>
<li>機器連不到、或根本起不來時怎麼從權威狀態往下查，見 <a href="../machine-unreachable/">機器連不到或起不來</a>。</li>
<li>程序在不在、服務歸誰、狀態怎麼判的具體招式，見 <a href="../process-service-state-diagnosis/">程序、服務與狀態怎麼判</a>。</li>
<li>怎麼讓你自己的 bootstrap 腳本產生可讀的 log，見 <a href="../../install/observable-bootstrap/">可除錯的 bootstrap</a>。</li>
</ul>
]]></content:encoded></item><item><title>遠端連線與同步工具選型：連得穩、斷得起、檔案一致</title><link>https://tarrragon.github.io/blog/linux/tools/remote/connection-and-sync-tools/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/tools/remote/connection-and-sync-tools/</guid><description>&lt;p>遠端工作有三塊彼此獨立的能力：&lt;strong>保住 session&lt;/strong>（多工器讓遠端的工作不隨連線消失）、&lt;strong>連線層&lt;/strong>（決定你怎麼接上遠端、斷了怎麼辦）、&lt;strong>同步層&lt;/strong>（決定本地與遠端的檔案怎麼保持一致）。多工器是地基、已在&lt;a href="../">遠端工具總覽&lt;/a>談過；這篇補另外兩塊——連線用什麼、檔案怎麼同步。這三塊要分開看，因為它們解的是不同問題，混在一起挑會挑錯：session 掉了是多工器的事，打字延遲高是連線層的事，本地改了遠端沒更新是同步層的事。&lt;/p>
&lt;h2 id="連線層從-ssh-出發按弱點往上補">連線層：從 SSH 出發，按弱點往上補&lt;/h2>
&lt;p>連線層的基準是 SSH——它是遠端登入的通用標準，加密、認證、port forwarding 都靠它，多數情況直接用 SSH 就夠。往上補工具的時機，是 SSH 在特定弱點上讓你難受的時候，而不是「有更潮的工具就換」。SSH 的兩個典型弱點是「網路一換就斷」（筆電休眠、Wi-Fi 換點、行動網路切換）和「連線中斷後要手動重連」，mosh 與 autossh 各補一個。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>工具&lt;/th>
 &lt;th>解的問題&lt;/th>
 &lt;th>代價&lt;/th>
 &lt;th>何時值得換&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>SSH&lt;/td>
 &lt;td>通用遠端登入基準&lt;/td>
 &lt;td>網路一換 IP 就斷、休眠喚醒常要重連&lt;/td>
 &lt;td>預設就用它&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>mosh&lt;/td>
 &lt;td>漫遊不斷線、高延遲下打字順&lt;/td>
 &lt;td>走 UDP 要開額外 port、不支援 port forwarding&lt;/td>
 &lt;td>行動網路 / Wi-Fi 換點 / 高延遲&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>autossh&lt;/td>
 &lt;td>SSH 斷線自動重連&lt;/td>
 &lt;td>只是重連、session 內容還是靠多工器保住&lt;/td>
 &lt;td>需要一條長期自動維持的隧道&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="mosh換網路不掉線高延遲下還能打字">mosh：換網路不掉線、高延遲下還能打字&lt;/h3>
&lt;p>mosh（mobile shell）解的是「連線的存活與手感」：它在 SSH 之上用 UDP 維持一個跟客戶端 IP 無關的 session，所以你的筆電從家裡 Wi-Fi 換到行動網路、或休眠喚醒換了 IP，連線不會斷。它還做本地回顯預測，高延遲鏈路上打字不會有一個字一個字等回應的黏滯感。從咖啡廳、通勤、跨國高延遲連遠端時，mosh 的體驗明顯優於裸 SSH。&lt;/p>
&lt;p>它的代價是走 UDP，要在遠端開一段 UDP port 範圍（防火牆/雲端 security group 要放行），且不做 SSH 的 port forwarding——需要轉發本地端口時還是得另開一條 SSH。所以 mosh 通常跟多工器搭配用：mosh 保住連線手感、多工器保住 session 內容，兩者互補。&lt;/p>
&lt;h3 id="autossh維持一條會自己重連的隧道">autossh：維持一條會自己重連的隧道&lt;/h3>
&lt;p>autossh 解的是「隧道的自動存活」：它監控一條 SSH 連線，斷了就自動重建，適合需要長期維持的場景——例如把遠端某個服務 port forward 回本地、或維持一條反向隧道讓 NAT 後的機器可被連入。它本身只負責「重連」這個動作，重連後你原本的工作是否還在，取決於你有沒有用多工器把 session 保住。&lt;/p>
&lt;p>判讀：autossh 是「基礎設施型」工具，用在你要一條無人值守、掉了要自己回來的隧道；日常互動式登入用 mosh 的漫遊能力更順。兩者不衝突。&lt;/p>
&lt;h2 id="網路層機器根本連不到時先解可達性">網路層：機器根本連不到時，先解可達性&lt;/h2>
&lt;p>前面的連線工具都假設「遠端機器的 IP 你連得到」。當遠端機器在 NAT 或防火牆後面、沒有公開 IP 時，連不到是可達性問題，要在網路層解，而不是換 SSH 客戶端。WireGuard 是現代的輕量 VPN 協定，讓兩台機器像在同一個私網裡直接互連；Tailscale 建在 WireGuard 之上，把「交換金鑰、打洞穿透 NAT、管理裝置清單」這些麻煩事自動化，通常裝好登入就能讓你的所有裝置互相 SSH，不必自己配 VPN。&lt;/p>
&lt;p>判讀：家裡的機器、公司內網的開發機、雲端私網裡的主機，想從外面連進去又不想開公網 port 暴露 SSH，用 Tailscale（要省事）或自建 WireGuard（要完全自主、不依賴第三方協調伺服器）在網路層打通，之後 SSH/mosh 照常用。這一層跟連線層是疊加關係：先有可達性，上面才談連線手感。（機器連不到的診斷——是網路層、服務層還是機器沒起——見&lt;a href="../../../debug/machine-unreachable/">機器連不到或起不來&lt;/a>。）&lt;/p>
&lt;h2 id="同步層三種語義依-workflow-選">同步層：三種語義，依 workflow 選&lt;/h2>
&lt;p>檔案同步不是一個問題，是三種不同語義的問題，挑錯工具會很痛。核心差異在「同步是單向還是雙向、是一次性快照還是持續即時、檔案存在本地還是遠端」。rsync、sshfs、mutagen 各自代表一種語義：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>工具&lt;/th>
 &lt;th>語義&lt;/th>
 &lt;th>檔案實際在哪&lt;/th>
 &lt;th>適合的 workflow&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>rsync&lt;/td>
 &lt;td>單向、一次性快照、增量傳輸&lt;/td>
 &lt;td>兩邊各一份&lt;/td>
 &lt;td>部署、備份、把成果拉回來&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>sshfs&lt;/td>
 &lt;td>把遠端目錄掛載成本地路徑&lt;/td>
 &lt;td>只在遠端&lt;/td>
 &lt;td>偶爾存取遠端檔案、當本地資料夾用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>mutagen&lt;/td>
 &lt;td>雙向、持續、即時同步&lt;/td>
 &lt;td>兩邊各一份即時&lt;/td>
 &lt;td>本地編輯、遠端執行的開發迴圈&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="rsync單向增量部署與拉回成果的正典">rsync：單向增量，部署與拉回成果的正典&lt;/h3>
&lt;p>rsync 解的是「把一批檔案有效率地從 A 複製到 B」：它只傳有變動的部分（增量），可保留權限與時間戳，是單向的一次性動作——你下指令、它同步一次、結束。它適合明確的「推上去」或「拉回來」：把本地建好的東西部署到遠端、把遠端跑完的產出拉回本地、定期備份。它不做「持續盯著兩邊、誰改了就同步」，所以拿它當即時開發同步用會很累（每次改都要手動跑）。&lt;/p>
&lt;p>因為單向且明確，rsync 也是三者裡最可預測、最不會意外覆蓋的——你清楚知道哪邊是來源、哪邊被更新。無人值守的成果回收（遠端跑完長任務、把結果 rsync 回本地）用它最穩。&lt;/p>
&lt;h3 id="sshfs把遠端目錄當本地資料夾掛載">sshfs：把遠端目錄當本地資料夾掛載&lt;/h3>
&lt;p>sshfs 解的是「我想用本地的工具存取遠端的檔案、但不想先複製下來」：它透過 SSH 把遠端目錄掛載成本地的一個路徑，你用本地的編輯器、檔案管理員直接開，實際檔案仍只在遠端。適合偶爾存取、檔案不宜落地到本地的場景。&lt;/p>
&lt;p>它的代價是脆與慢：每次存取都走網路，延遲高時開大目錄、跑 &lt;code>git status&lt;/code> 這種大量小檔操作會很卡；連線一斷，掛載點就進入壞狀態要重掛。所以 sshfs 適合「輕度、偶爾」的遠端存取，不適合當重度開發的主力——重度開發本地要有一份真的檔案，那是 mutagen 的場景。&lt;/p>
&lt;h3 id="mutagen雙向即時本地編輯遠端執行的開發迴圈">mutagen：雙向即時，本地編輯遠端執行的開發迴圈&lt;/h3>
&lt;p>mutagen 解的是現代遠端開發最常見的迴圈：「在本地用順手的編輯器改、在遠端（有算力、有環境、有相依）執行」，它在兩邊各保留一份實體檔案並持續雙向即時同步——你本地存檔，遠端幾乎同時更新；遠端產生的檔案也同步回本地。因為兩邊都是本地檔案，&lt;code>git status&lt;/code>、搜尋、建置都快，沒有 sshfs 那種每次存取走網路的黏滯。&lt;/p>
&lt;p>它的代價是多一個常駐同步 daemon 與初次設定，且雙向同步要處理衝突（兩邊同時改同一檔）。適合「本地機器弱/環境不對、但要在強遠端上跑」的長期開發關係。如果你的需求只是「偶爾拉個檔」，mutagen 是殺雞用牛刀，rsync 或 sshfs 更省。&lt;/p>
&lt;h2 id="ide-remote把編輯器的執行環境整個搬到遠端">IDE remote：把編輯器的執行環境整個搬到遠端&lt;/h2>
&lt;p>VS Code Remote（SSH/Containers/WSL）與 JetBrains Gateway 是另一條路線：它們不同步檔案，而是把編輯器的後端（語言伺服器、終端機、除錯器）整個跑在遠端，本地只留 UI。你在本地視窗編輯，但索引、建置、執行全發生在遠端那台機器上，檔案也只在遠端。這解掉了同步的衝突問題（沒有兩份檔案要對齊），代價是綁定該編輯器、且需要一條夠穩的連線維持 UI 與後端的通訊。&lt;/p>
&lt;p>判讀：如果你本來就用 VS Code / JetBrains，remote 模式通常比自己接 mutagen + SSH 更省事、體驗更整合；如果你用終端機編輯器（Vim/Neovim/Emacs）或要編輯器無關的方案，走 mutagen（雙向同步）或直接在遠端多工器裡編輯（檔案只在遠端、靠多工器保住 session）。&lt;/p></description><content:encoded><![CDATA[<p>遠端工作有三塊彼此獨立的能力：<strong>保住 session</strong>（多工器讓遠端的工作不隨連線消失）、<strong>連線層</strong>（決定你怎麼接上遠端、斷了怎麼辦）、<strong>同步層</strong>（決定本地與遠端的檔案怎麼保持一致）。多工器是地基、已在<a href="../">遠端工具總覽</a>談過；這篇補另外兩塊——連線用什麼、檔案怎麼同步。這三塊要分開看，因為它們解的是不同問題，混在一起挑會挑錯：session 掉了是多工器的事，打字延遲高是連線層的事，本地改了遠端沒更新是同步層的事。</p>
<h2 id="連線層從-ssh-出發按弱點往上補">連線層：從 SSH 出發，按弱點往上補</h2>
<p>連線層的基準是 SSH——它是遠端登入的通用標準，加密、認證、port forwarding 都靠它，多數情況直接用 SSH 就夠。往上補工具的時機，是 SSH 在特定弱點上讓你難受的時候，而不是「有更潮的工具就換」。SSH 的兩個典型弱點是「網路一換就斷」（筆電休眠、Wi-Fi 換點、行動網路切換）和「連線中斷後要手動重連」，mosh 與 autossh 各補一個。</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>解的問題</th>
          <th>代價</th>
          <th>何時值得換</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SSH</td>
          <td>通用遠端登入基準</td>
          <td>網路一換 IP 就斷、休眠喚醒常要重連</td>
          <td>預設就用它</td>
      </tr>
      <tr>
          <td>mosh</td>
          <td>漫遊不斷線、高延遲下打字順</td>
          <td>走 UDP 要開額外 port、不支援 port forwarding</td>
          <td>行動網路 / Wi-Fi 換點 / 高延遲</td>
      </tr>
      <tr>
          <td>autossh</td>
          <td>SSH 斷線自動重連</td>
          <td>只是重連、session 內容還是靠多工器保住</td>
          <td>需要一條長期自動維持的隧道</td>
      </tr>
  </tbody>
</table>
<h3 id="mosh換網路不掉線高延遲下還能打字">mosh：換網路不掉線、高延遲下還能打字</h3>
<p>mosh（mobile shell）解的是「連線的存活與手感」：它在 SSH 之上用 UDP 維持一個跟客戶端 IP 無關的 session，所以你的筆電從家裡 Wi-Fi 換到行動網路、或休眠喚醒換了 IP，連線不會斷。它還做本地回顯預測，高延遲鏈路上打字不會有一個字一個字等回應的黏滯感。從咖啡廳、通勤、跨國高延遲連遠端時，mosh 的體驗明顯優於裸 SSH。</p>
<p>它的代價是走 UDP，要在遠端開一段 UDP port 範圍（防火牆/雲端 security group 要放行），且不做 SSH 的 port forwarding——需要轉發本地端口時還是得另開一條 SSH。所以 mosh 通常跟多工器搭配用：mosh 保住連線手感、多工器保住 session 內容，兩者互補。</p>
<h3 id="autossh維持一條會自己重連的隧道">autossh：維持一條會自己重連的隧道</h3>
<p>autossh 解的是「隧道的自動存活」：它監控一條 SSH 連線，斷了就自動重建，適合需要長期維持的場景——例如把遠端某個服務 port forward 回本地、或維持一條反向隧道讓 NAT 後的機器可被連入。它本身只負責「重連」這個動作，重連後你原本的工作是否還在，取決於你有沒有用多工器把 session 保住。</p>
<p>判讀：autossh 是「基礎設施型」工具，用在你要一條無人值守、掉了要自己回來的隧道；日常互動式登入用 mosh 的漫遊能力更順。兩者不衝突。</p>
<h2 id="網路層機器根本連不到時先解可達性">網路層：機器根本連不到時，先解可達性</h2>
<p>前面的連線工具都假設「遠端機器的 IP 你連得到」。當遠端機器在 NAT 或防火牆後面、沒有公開 IP 時，連不到是可達性問題，要在網路層解，而不是換 SSH 客戶端。WireGuard 是現代的輕量 VPN 協定，讓兩台機器像在同一個私網裡直接互連；Tailscale 建在 WireGuard 之上，把「交換金鑰、打洞穿透 NAT、管理裝置清單」這些麻煩事自動化，通常裝好登入就能讓你的所有裝置互相 SSH，不必自己配 VPN。</p>
<p>判讀：家裡的機器、公司內網的開發機、雲端私網裡的主機，想從外面連進去又不想開公網 port 暴露 SSH，用 Tailscale（要省事）或自建 WireGuard（要完全自主、不依賴第三方協調伺服器）在網路層打通，之後 SSH/mosh 照常用。這一層跟連線層是疊加關係：先有可達性，上面才談連線手感。（機器連不到的診斷——是網路層、服務層還是機器沒起——見<a href="../../../debug/machine-unreachable/">機器連不到或起不來</a>。）</p>
<h2 id="同步層三種語義依-workflow-選">同步層：三種語義，依 workflow 選</h2>
<p>檔案同步不是一個問題，是三種不同語義的問題，挑錯工具會很痛。核心差異在「同步是單向還是雙向、是一次性快照還是持續即時、檔案存在本地還是遠端」。rsync、sshfs、mutagen 各自代表一種語義：</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>語義</th>
          <th>檔案實際在哪</th>
          <th>適合的 workflow</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>rsync</td>
          <td>單向、一次性快照、增量傳輸</td>
          <td>兩邊各一份</td>
          <td>部署、備份、把成果拉回來</td>
      </tr>
      <tr>
          <td>sshfs</td>
          <td>把遠端目錄掛載成本地路徑</td>
          <td>只在遠端</td>
          <td>偶爾存取遠端檔案、當本地資料夾用</td>
      </tr>
      <tr>
          <td>mutagen</td>
          <td>雙向、持續、即時同步</td>
          <td>兩邊各一份即時</td>
          <td>本地編輯、遠端執行的開發迴圈</td>
      </tr>
  </tbody>
</table>
<h3 id="rsync單向增量部署與拉回成果的正典">rsync：單向增量，部署與拉回成果的正典</h3>
<p>rsync 解的是「把一批檔案有效率地從 A 複製到 B」：它只傳有變動的部分（增量），可保留權限與時間戳，是單向的一次性動作——你下指令、它同步一次、結束。它適合明確的「推上去」或「拉回來」：把本地建好的東西部署到遠端、把遠端跑完的產出拉回本地、定期備份。它不做「持續盯著兩邊、誰改了就同步」，所以拿它當即時開發同步用會很累（每次改都要手動跑）。</p>
<p>因為單向且明確，rsync 也是三者裡最可預測、最不會意外覆蓋的——你清楚知道哪邊是來源、哪邊被更新。無人值守的成果回收（遠端跑完長任務、把結果 rsync 回本地）用它最穩。</p>
<h3 id="sshfs把遠端目錄當本地資料夾掛載">sshfs：把遠端目錄當本地資料夾掛載</h3>
<p>sshfs 解的是「我想用本地的工具存取遠端的檔案、但不想先複製下來」：它透過 SSH 把遠端目錄掛載成本地的一個路徑，你用本地的編輯器、檔案管理員直接開，實際檔案仍只在遠端。適合偶爾存取、檔案不宜落地到本地的場景。</p>
<p>它的代價是脆與慢：每次存取都走網路，延遲高時開大目錄、跑 <code>git status</code> 這種大量小檔操作會很卡；連線一斷，掛載點就進入壞狀態要重掛。所以 sshfs 適合「輕度、偶爾」的遠端存取，不適合當重度開發的主力——重度開發本地要有一份真的檔案，那是 mutagen 的場景。</p>
<h3 id="mutagen雙向即時本地編輯遠端執行的開發迴圈">mutagen：雙向即時，本地編輯遠端執行的開發迴圈</h3>
<p>mutagen 解的是現代遠端開發最常見的迴圈：「在本地用順手的編輯器改、在遠端（有算力、有環境、有相依）執行」，它在兩邊各保留一份實體檔案並持續雙向即時同步——你本地存檔，遠端幾乎同時更新；遠端產生的檔案也同步回本地。因為兩邊都是本地檔案，<code>git status</code>、搜尋、建置都快，沒有 sshfs 那種每次存取走網路的黏滯。</p>
<p>它的代價是多一個常駐同步 daemon 與初次設定，且雙向同步要處理衝突（兩邊同時改同一檔）。適合「本地機器弱/環境不對、但要在強遠端上跑」的長期開發關係。如果你的需求只是「偶爾拉個檔」，mutagen 是殺雞用牛刀，rsync 或 sshfs 更省。</p>
<h2 id="ide-remote把編輯器的執行環境整個搬到遠端">IDE remote：把編輯器的執行環境整個搬到遠端</h2>
<p>VS Code Remote（SSH/Containers/WSL）與 JetBrains Gateway 是另一條路線：它們不同步檔案，而是把編輯器的後端（語言伺服器、終端機、除錯器）整個跑在遠端，本地只留 UI。你在本地視窗編輯，但索引、建置、執行全發生在遠端那台機器上，檔案也只在遠端。這解掉了同步的衝突問題（沒有兩份檔案要對齊），代價是綁定該編輯器、且需要一條夠穩的連線維持 UI 與後端的通訊。</p>
<p>判讀：如果你本來就用 VS Code / JetBrains，remote 模式通常比自己接 mutagen + SSH 更省事、體驗更整合；如果你用終端機編輯器（Vim/Neovim/Emacs）或要編輯器無關的方案，走 mutagen（雙向同步）或直接在遠端多工器裡編輯（檔案只在遠端、靠多工器保住 session）。</p>
<h2 id="在-arch-上的安裝與依賴實測-aarch64">在 Arch 上的安裝與依賴（實測 aarch64）</h2>
<p>這些工具多數在官方 repo，但有幾個安裝陷阱與部署前提是實機才看得出來的，選好工具後要一起確認：</p>
<ul>
<li><strong>mosh</strong>：官方 repo（<code>pacman -S mosh</code>）。同一套件同時含 client（<code>mosh</code>）與 server（<code>mosh-server</code>），所以<strong>遠端機器也要裝 mosh</strong>——遠端是別人的伺服器時這是前提；另外 UDP 埠範圍（預設 60000–61000）要在防火牆 / security group 放行。</li>
<li><strong>autossh</strong> / <strong>rsync</strong> / <strong>sshfs</strong>：都在官方 repo（<code>pacman -S autossh rsync sshfs</code>）。<code>sshfs</code> 會自動拉 <code>fuse3</code> 相依，不用手動裝 FUSE；掛載需要 <code>/dev/fuse</code> 可存取（一般環境已就緒）。</li>
<li><strong>tailscale</strong>：官方 repo（<code>pacman -S tailscale</code>），但<strong>裝完 daemon 預設沒起</strong>——要 <code>sudo systemctl enable --now tailscaled</code> 之後才能 <code>tailscale up</code>。少了這步，<code>tailscale</code> 指令會因 daemon 未運行而失敗。</li>
<li><strong>wireguard</strong>：裝 <code>wireguard-tools</code>（<code>wg</code> / <code>wg-quick</code>）。核心模組在多數發行版是 loadable module，<code>wg-quick</code> 會在需要時自動 <code>modprobe wireguard</code>，日常不用手動處理。</li>
<li><strong>mutagen</strong>：<strong>不在官方 repo</strong>，且 <code>pacman -S mutagen</code> 會裝到完全無關的 <code>python-mutagen</code>（音訊 metadata 函式庫）。正確安裝是 AUR 的 <code>mutagen.io-bin</code>（<code>paru -S mutagen.io-bin</code>），提供 <code>mutagen</code> 執行檔，aarch64 有官方 binary。這是「以為一句 pacman 就有、實際會裝錯東西」的典型坑。</li>
<li><strong>VS Code Remote / JetBrains Gateway</strong>：綁各自的 IDE，不透過套件管理器單獨裝，隨 IDE 的 remote 擴充啟用。</li>
</ul>
<h2 id="依情境選">依情境選</h2>
<p>把上面的工具對回你的實際處境：</p>
<ul>
<li><strong>日常從筆電連遠端、常換網路</strong>：mosh（漫遊）+ 多工器（保 session）。</li>
<li><strong>要一條長期自動維持的隧道 / 反向連接</strong>：autossh + 多工器。</li>
<li><strong>遠端機器在 NAT 後、根本連不到</strong>：先用 Tailscale 或 WireGuard 在網路層打通，再照常 SSH/mosh。</li>
<li><strong>部署上去、或把遠端跑完的成果拉回來</strong>：rsync（單向、可預測）。</li>
<li><strong>偶爾存取遠端幾個檔、不想複製下來</strong>：sshfs（掛載）。</li>
<li><strong>本地編輯、遠端執行的長期開發迴圈</strong>：mutagen（雙向即時）或你的 IDE 的 remote 模式。</li>
<li><strong>無人值守跑長任務、跑完自動回收成果</strong>：多工器保住任務 + rsync 拉回產出，見<a href="../../../install/unattended-remote-work/">讓機器跑無人值守的長任務</a>。</li>
</ul>
<p>判準是先分清「你缺的是連線存活、可達性、還是檔案一致」，再在對應那一層挑工具——三層各挑各的，不要拿同步工具去解連線問題。</p>
<h2 id="下一步">下一步</h2>
<ul>
<li>保住遠端 session 的多工器（tmux / zellij）配置與比較：<a href="../">遠端工具總覽</a> 與 <a href="../../cli/">CLI 環境工具</a> 的多工器篇。</li>
<li>連不上、終端機噴亂碼、要從 SSH 操控圖形桌面等連線本身的問題：<a href="../../../debug/ssh-and-terminal-troubleshooting/">除錯與診斷：遠端連線與終端機問題</a>。</li>
<li>機器完全沒回應、域名解析不了、虛擬機起不來：<a href="../../../debug/machine-unreachable/">機器連不到或起不來</a>。</li>
<li>把遠端機器設成無人值守、離開後自己跑完長任務送回成果：<a href="../../../install/unattended-remote-work/">讓機器跑無人值守的長任務</a>。</li>
</ul>
]]></content:encoded></item><item><title>Linux 安裝與機器初始化</title><link>https://tarrragon.github.io/blog/linux/install/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/install/</guid><description>&lt;p>這個系列處理一件編號模組預設你已經完成的事：把一台機器從「空的」變成「能接收 dotfile 的」。模組零到九教你怎麼用 code 管理工作環境，但它們都假設你手上已經有一台裝好 Linux、裝了基本工具、能從外部連入的機器。這個系列補的就是那段地基——OS 怎麼裝、裝完缺什麼、怎麼連進去跑 bootstrap。&lt;/p>
&lt;p>這段地基平常被跳過，是因為多數人是在一台早就裝好的機器上開始管理 dotfile。但只要你換到全新環境——開一台 VM、租一台雲端主機、拿到一台空機器——就會直接撞上這層：安裝程式問你十幾個選項該怎麼選、裝完發現連 &lt;code>sudo&lt;/code> 都沒有、想從本機連進去卻還沒有 SSH key。這些都不在編號模組的範圍，卻是跑 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/08-sync-bootstrap/bootstrap-script-packages/" data-link-title="Bootstrap Script 與套件清單管理" data-link-desc="寫 dotfile 的 install script、或整理「這台機器裝了什麼」的套件清單時回來讀">模組八的 bootstrap script&lt;/a> 之前必須先過的關。&lt;/p>
&lt;p>本系列的內容來自一次完整的 VM 實測：在 Apple Silicon 的 UTM 上從 archboot（Arch 的獨立網路安裝環境 ISO）裝 Arch Linux ARM、跑 dotfile 的 &lt;code>install.sh&lt;/code>、一路把 Hyprland 桌面拉起來。實測中每個卡關點都被記錄下來，這裡把它們蒸餾成可重用的判讀與決策，不綁特定發行版或虛擬化軟體。&lt;/p>
&lt;h2 id="在學習路徑中的位置">在學習路徑中的位置&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>階段&lt;/th>
 &lt;th>對應內容&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>地基（本系列）&lt;/td>
 &lt;td>OS 安裝決策、工具驗證、外部連入與 bootstrap 前置&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>為什麼管理 dotfile&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/00-dotfile-mindset/" data-link-title="模組零：Dotfile 心智模型" data-link-desc="換機器、開 VM、重灌系統時需要快速還原開發環境，或想釐清哪些配置該版控、哪些該排除時回來讀">模組零：心智模型&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>怎麼管理&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/" data-link-title="模組一：管理工具與目錄結構" data-link-desc="要把散落在家目錄的配置檔集中版控時，選 bare repo、stow 還是 chezmoi、目錄該怎麼組織">模組一到七&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>怎麼一鍵還原&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/08-sync-bootstrap/" data-link-title="模組八：同步、Bootstrap 與環境重建" data-link-desc="換機器或重灌時怎麼還原工作環境 — bootstrap script 設計、套件清單管理、跨機器同步策略、secret 排除，以及 VM 快照和 dotfile 重建兩種思路的場景判讀">模組八：同步與 Bootstrap&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/00-dotfile-mindset/setup-order-guide/" data-link-title="環境建置的操作順序" data-link-desc="第一次從零建立 Linux 或 macOS 開發環境、不確定先做什麼後做什麼時讀 — 依賴順序路線圖，每一步附對應模組連結">模組零的操作順序指引&lt;/a> 列出從 OS 安裝到桌面就緒的完整依賴鏈，但只把「安裝作業系統」標成一步。本系列是那一步的展開：安裝程式每個選項背後的取捨、裝完之後的驗證、以及連入機器的幾種路徑。&lt;/p>
&lt;h2 id="文章">文章&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>文章&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;th>回答什麼問題&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="basic-operations/">安裝過程用到的基礎操作&lt;/a>&lt;/td>
 &lt;td>系列用到的基礎操作：&lt;code>su -&lt;/code>、nano 編輯、檔名/指令大小寫、shell 符號（已熟可跳過）&lt;/td>
 &lt;td>照做時撞到沒見過的基礎指令怎麼辦&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="install-option-decisions/">Linux 安裝選項判讀&lt;/a>&lt;/td>
 &lt;td>安裝程式各選項的決策方針：locale、網路、鏡像、磁碟分割、檔案系統、bootloader&lt;/td>
 &lt;td>安裝程式問我這個選項，我該根據什麼選&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="minimal-install-verify/">最小安裝後的工具驗證與補足&lt;/a>&lt;/td>
 &lt;td>最小系統缺哪些必要工具、怎麼驗證、怎麼補&lt;/td>
 &lt;td>為什麼裝完連 sudo 都沒有、bootstrap 跑不起來&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="package-and-network-troubleshooting/">安裝期套件與網路故障排除&lt;/a>&lt;/td>
 &lt;td>第一次抓套件就失敗：分「連不到（DNS/mirror）」vs「連得到但被拒（db lock/簽章/partial/404）」&lt;/td>
 &lt;td>剛裝好跑 pacman 就報錯，是網路還是套件管理器的問題&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="ssh-keyless-bootstrap/">外部連入、SSH key 與無 key 的 bootstrap 路徑&lt;/a>&lt;/td>
 &lt;td>啟用 sshd、從本機連入、設 SSH key，以及還沒有 key 時怎麼把 dotfile 弄進去&lt;/td>
 &lt;td>怎麼從舒適的本機終端機操作新機器、沒有 key 時怎麼辦&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="observable-bootstrap/">可除錯的 bootstrap：把可觀測性內建進安裝腳本&lt;/a>&lt;/td>
 &lt;td>bootstrap 失敗時怎麼留下可診斷的痕跡：log 落地、錯誤定位、狀態 dump&lt;/td>
 &lt;td>安裝腳本失敗時，為什麼我只能瞎找&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="unattended-remote-work/">讓機器跑無人值守的長任務&lt;/a>&lt;/td>
 &lt;td>無人值守執行的三個障礙與解：NOPASSWD sudo、終端機多工器、推送認證，以及 agent 權限放行的取捨&lt;/td>
 &lt;td>怎麼讓機器在我離開後自己跑完任務、把成果送回來&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="platform-divergence-map/">平台與發行版差異的判讀地圖&lt;/a>&lt;/td>
 &lt;td>差異的四層（套件管理器 / 套件名 / 存在性 / 版本節奏）、除錯前先定平台、bootstrap 分歧判準&lt;/td>
 &lt;td>跨平台的清單與腳本該怎麼切、除錯時先確認什麼&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="gui-apps-install-verify/">GUI 應用的安裝驗證&lt;/a>&lt;/td>
 &lt;td>拆包生態（本體與功能模組分離）、首跑同意對話框、播放驗證鏈、VM 硬體解碼回退&lt;/td>
 &lt;td>GUI 應用裝了打不開 / 無聲 / 不能播該查哪一層&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>機器裝好、能連入之後若出問題（連不上、終端機亂、程式行為怪），除錯與診斷自成一組，見同層的 &lt;a href="../debug/">Linux 除錯與診斷&lt;/a>。&lt;/p>
&lt;h2 id="依情境的讀法">依情境的讀法&lt;/h2>
&lt;p>主線那幾篇照「安裝 → 驗證 → 連入 → 可除錯 → 無人值守」的順序，是「從零開一台新機器、到讓它自己跑活」的完整路線，但不是每個讀者都從零開始：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>接手一台別人已裝好的機器&lt;/strong>：OS 已經在，從 &lt;a href="minimal-install-verify/">最小安裝後的工具驗證與補足&lt;/a> 切入，確認它缺不缺你流程要的工具。&lt;/li>
&lt;li>&lt;strong>雲端主機初始化&lt;/strong>：雲端主機多半已附 OS image、已有 sudo 與注入的 key，適用的是 &lt;a href="ssh-keyless-bootstrap/">外部連入、SSH key 與無 key 的 bootstrap 路徑&lt;/a> 跟 &lt;a href="observable-bootstrap/">可除錯的 bootstrap&lt;/a>，前兩篇的 ISO 安裝可略過。&lt;/li>
&lt;li>&lt;strong>bootstrap 失敗來 debug&lt;/strong>：直接讀 &lt;a href="observable-bootstrap/">可除錯的 bootstrap&lt;/a>，它也涵蓋「腳本不是你寫的、只想 debug 一次失敗」的情況。&lt;/li>
&lt;li>&lt;strong>讓機器無人值守跑活&lt;/strong>：機器已能連入操作，只想設好讓它在你離開後自己跑長任務或 agent，直接讀 &lt;a href="unattended-remote-work/">讓機器跑無人值守的長任務&lt;/a>。&lt;/li>
&lt;li>&lt;strong>遇到問題要除錯&lt;/strong>：機器已在跑但出狀況（連不上、終端機亂、程式行為怪），進 &lt;a href="../debug/">Linux 除錯與診斷&lt;/a>，從 &lt;a href="../debug/diagnosis-read-authoritative-state/">診斷心法&lt;/a> 建立判讀紀律，再依症狀分流。&lt;/li>
&lt;li>&lt;strong>裝好後想讓它自己顧&lt;/strong>：服務跑起來後，主動確認這台有沒有在監控自己的服務死活（&lt;code>systemctl show sshd -p OnFailure&lt;/code>），沒有就從最簡單的 OnFailure + ntfy 建起——遠端機器至少把 sshd 掛上，掛了就失聯。見 &lt;a href="../debug/service-failure-monitoring/">服務掛了怎麼自動知道&lt;/a>。&lt;/li>
&lt;/ul>
&lt;h2 id="跟其他模組的交叉引用">跟其他模組的交叉引用&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/08-sync-bootstrap/bootstrap-script-packages/" data-link-title="Bootstrap Script 與套件清單管理" data-link-desc="寫 dotfile 的 install script、或整理「這台機器裝了什麼」的套件清單時回來讀">模組八：Bootstrap script 設計&lt;/a>——本系列是它的前置；bootstrap 假設套件清單完整、機器可連入，本系列補「在那之前」。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/hyprland-vm-setup/" data-link-title="Hyprland VM 環境設定與測試矩陣" data-link-desc="要在 VM 裡測試 Hyprland 配置、或判斷某個設定該在 VM 還是實機驗證時回來讀">模組五：Hyprland VM 測試&lt;/a>——本系列的 VM 安裝是它的下游前置；裝好機器才能測 Hyprland。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/log-reading-diagnostic-tools/" data-link-title="日誌判讀與診斷工具" data-link-desc="知道桌面出了問題但不確定原因時回來讀 — journalctl、dmesg、hyprctl、systemctl 的使用方式和常見 log pattern">模組七：日誌判讀與診斷工具&lt;/a>——「可除錯的 bootstrap」與它呼應：前者談怎麼產生可診斷的 log，後者談怎麼讀。&lt;/li>
&lt;li>&lt;a href="../debug/">Linux 除錯與診斷&lt;/a>——本系列裝好、連入之後的下游；出問題時的判讀紀律與情境分流。&lt;/li>
&lt;li>&lt;a href="../tools/">Linux 工具選單&lt;/a>——安裝與除錯要用的工具（CLI / 圖形桌面 / 遠端）有哪些選擇、推薦用哪些。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/infra/00-infra-mindset/first-day-with-cloud-account/" data-link-title="拿到雲端帳號的第一天" data-link-desc="被指派 infra 工作、拿到 AWS 或 GCP 帳號、不確定該先做什麼時讀 — 第一小時安全底線、帳號現況判讀、後續學習路線分流">Infra 心智模型：拿到雲端帳號的第一天&lt;/a>——雲端主機的機器初始化是本系列的上游情境；被指派 infra 工作、拿到一台雲端主機後，先過本系列的 OS 連入與 bootstrap 前置，再進 infra 的資源管理。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個系列處理一件編號模組預設你已經完成的事：把一台機器從「空的」變成「能接收 dotfile 的」。模組零到九教你怎麼用 code 管理工作環境，但它們都假設你手上已經有一台裝好 Linux、裝了基本工具、能從外部連入的機器。這個系列補的就是那段地基——OS 怎麼裝、裝完缺什麼、怎麼連進去跑 bootstrap。</p>
<p>這段地基平常被跳過，是因為多數人是在一台早就裝好的機器上開始管理 dotfile。但只要你換到全新環境——開一台 VM、租一台雲端主機、拿到一台空機器——就會直接撞上這層：安裝程式問你十幾個選項該怎麼選、裝完發現連 <code>sudo</code> 都沒有、想從本機連進去卻還沒有 SSH key。這些都不在編號模組的範圍，卻是跑 <a href="/blog/linux/dotfile/08-sync-bootstrap/bootstrap-script-packages/" data-link-title="Bootstrap Script 與套件清單管理" data-link-desc="寫 dotfile 的 install script、或整理「這台機器裝了什麼」的套件清單時回來讀">模組八的 bootstrap script</a> 之前必須先過的關。</p>
<p>本系列的內容來自一次完整的 VM 實測：在 Apple Silicon 的 UTM 上從 archboot（Arch 的獨立網路安裝環境 ISO）裝 Arch Linux ARM、跑 dotfile 的 <code>install.sh</code>、一路把 Hyprland 桌面拉起來。實測中每個卡關點都被記錄下來，這裡把它們蒸餾成可重用的判讀與決策，不綁特定發行版或虛擬化軟體。</p>
<h2 id="在學習路徑中的位置">在學習路徑中的位置</h2>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>對應內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>地基（本系列）</td>
          <td>OS 安裝決策、工具驗證、外部連入與 bootstrap 前置</td>
      </tr>
      <tr>
          <td>為什麼管理 dotfile</td>
          <td><a href="/blog/linux/dotfile/00-dotfile-mindset/" data-link-title="模組零：Dotfile 心智模型" data-link-desc="換機器、開 VM、重灌系統時需要快速還原開發環境，或想釐清哪些配置該版控、哪些該排除時回來讀">模組零：心智模型</a></td>
      </tr>
      <tr>
          <td>怎麼管理</td>
          <td><a href="/blog/linux/dotfile/01-dotfile-management/" data-link-title="模組一：管理工具與目錄結構" data-link-desc="要把散落在家目錄的配置檔集中版控時，選 bare repo、stow 還是 chezmoi、目錄該怎麼組織">模組一到七</a></td>
      </tr>
      <tr>
          <td>怎麼一鍵還原</td>
          <td><a href="/blog/linux/dotfile/08-sync-bootstrap/" data-link-title="模組八：同步、Bootstrap 與環境重建" data-link-desc="換機器或重灌時怎麼還原工作環境 — bootstrap script 設計、套件清單管理、跨機器同步策略、secret 排除，以及 VM 快照和 dotfile 重建兩種思路的場景判讀">模組八：同步與 Bootstrap</a></td>
      </tr>
  </tbody>
</table>
<p><a href="/blog/linux/dotfile/00-dotfile-mindset/setup-order-guide/" data-link-title="環境建置的操作順序" data-link-desc="第一次從零建立 Linux 或 macOS 開發環境、不確定先做什麼後做什麼時讀 — 依賴順序路線圖，每一步附對應模組連結">模組零的操作順序指引</a> 列出從 OS 安裝到桌面就緒的完整依賴鏈，但只把「安裝作業系統」標成一步。本系列是那一步的展開：安裝程式每個選項背後的取捨、裝完之後的驗證、以及連入機器的幾種路徑。</p>
<h2 id="文章">文章</h2>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>主題</th>
          <th>回答什麼問題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="basic-operations/">安裝過程用到的基礎操作</a></td>
          <td>系列用到的基礎操作：<code>su -</code>、nano 編輯、檔名/指令大小寫、shell 符號（已熟可跳過）</td>
          <td>照做時撞到沒見過的基礎指令怎麼辦</td>
      </tr>
      <tr>
          <td><a href="install-option-decisions/">Linux 安裝選項判讀</a></td>
          <td>安裝程式各選項的決策方針：locale、網路、鏡像、磁碟分割、檔案系統、bootloader</td>
          <td>安裝程式問我這個選項，我該根據什麼選</td>
      </tr>
      <tr>
          <td><a href="minimal-install-verify/">最小安裝後的工具驗證與補足</a></td>
          <td>最小系統缺哪些必要工具、怎麼驗證、怎麼補</td>
          <td>為什麼裝完連 sudo 都沒有、bootstrap 跑不起來</td>
      </tr>
      <tr>
          <td><a href="package-and-network-troubleshooting/">安裝期套件與網路故障排除</a></td>
          <td>第一次抓套件就失敗：分「連不到（DNS/mirror）」vs「連得到但被拒（db lock/簽章/partial/404）」</td>
          <td>剛裝好跑 pacman 就報錯，是網路還是套件管理器的問題</td>
      </tr>
      <tr>
          <td><a href="ssh-keyless-bootstrap/">外部連入、SSH key 與無 key 的 bootstrap 路徑</a></td>
          <td>啟用 sshd、從本機連入、設 SSH key，以及還沒有 key 時怎麼把 dotfile 弄進去</td>
          <td>怎麼從舒適的本機終端機操作新機器、沒有 key 時怎麼辦</td>
      </tr>
      <tr>
          <td><a href="observable-bootstrap/">可除錯的 bootstrap：把可觀測性內建進安裝腳本</a></td>
          <td>bootstrap 失敗時怎麼留下可診斷的痕跡：log 落地、錯誤定位、狀態 dump</td>
          <td>安裝腳本失敗時，為什麼我只能瞎找</td>
      </tr>
      <tr>
          <td><a href="unattended-remote-work/">讓機器跑無人值守的長任務</a></td>
          <td>無人值守執行的三個障礙與解：NOPASSWD sudo、終端機多工器、推送認證，以及 agent 權限放行的取捨</td>
          <td>怎麼讓機器在我離開後自己跑完任務、把成果送回來</td>
      </tr>
      <tr>
          <td><a href="platform-divergence-map/">平台與發行版差異的判讀地圖</a></td>
          <td>差異的四層（套件管理器 / 套件名 / 存在性 / 版本節奏）、除錯前先定平台、bootstrap 分歧判準</td>
          <td>跨平台的清單與腳本該怎麼切、除錯時先確認什麼</td>
      </tr>
      <tr>
          <td><a href="gui-apps-install-verify/">GUI 應用的安裝驗證</a></td>
          <td>拆包生態（本體與功能模組分離）、首跑同意對話框、播放驗證鏈、VM 硬體解碼回退</td>
          <td>GUI 應用裝了打不開 / 無聲 / 不能播該查哪一層</td>
      </tr>
  </tbody>
</table>
<p>機器裝好、能連入之後若出問題（連不上、終端機亂、程式行為怪），除錯與診斷自成一組，見同層的 <a href="../debug/">Linux 除錯與診斷</a>。</p>
<h2 id="依情境的讀法">依情境的讀法</h2>
<p>主線那幾篇照「安裝 → 驗證 → 連入 → 可除錯 → 無人值守」的順序，是「從零開一台新機器、到讓它自己跑活」的完整路線，但不是每個讀者都從零開始：</p>
<ul>
<li><strong>接手一台別人已裝好的機器</strong>：OS 已經在，從 <a href="minimal-install-verify/">最小安裝後的工具驗證與補足</a> 切入，確認它缺不缺你流程要的工具。</li>
<li><strong>雲端主機初始化</strong>：雲端主機多半已附 OS image、已有 sudo 與注入的 key，適用的是 <a href="ssh-keyless-bootstrap/">外部連入、SSH key 與無 key 的 bootstrap 路徑</a> 跟 <a href="observable-bootstrap/">可除錯的 bootstrap</a>，前兩篇的 ISO 安裝可略過。</li>
<li><strong>bootstrap 失敗來 debug</strong>：直接讀 <a href="observable-bootstrap/">可除錯的 bootstrap</a>，它也涵蓋「腳本不是你寫的、只想 debug 一次失敗」的情況。</li>
<li><strong>讓機器無人值守跑活</strong>：機器已能連入操作，只想設好讓它在你離開後自己跑長任務或 agent，直接讀 <a href="unattended-remote-work/">讓機器跑無人值守的長任務</a>。</li>
<li><strong>遇到問題要除錯</strong>：機器已在跑但出狀況（連不上、終端機亂、程式行為怪），進 <a href="../debug/">Linux 除錯與診斷</a>，從 <a href="../debug/diagnosis-read-authoritative-state/">診斷心法</a> 建立判讀紀律，再依症狀分流。</li>
<li><strong>裝好後想讓它自己顧</strong>：服務跑起來後，主動確認這台有沒有在監控自己的服務死活（<code>systemctl show sshd -p OnFailure</code>），沒有就從最簡單的 OnFailure + ntfy 建起——遠端機器至少把 sshd 掛上，掛了就失聯。見 <a href="../debug/service-failure-monitoring/">服務掛了怎麼自動知道</a>。</li>
</ul>
<h2 id="跟其他模組的交叉引用">跟其他模組的交叉引用</h2>
<ul>
<li><a href="/blog/linux/dotfile/08-sync-bootstrap/bootstrap-script-packages/" data-link-title="Bootstrap Script 與套件清單管理" data-link-desc="寫 dotfile 的 install script、或整理「這台機器裝了什麼」的套件清單時回來讀">模組八：Bootstrap script 設計</a>——本系列是它的前置；bootstrap 假設套件清單完整、機器可連入，本系列補「在那之前」。</li>
<li><a href="/blog/linux/dotfile/05-hyprland-config/hyprland-vm-setup/" data-link-title="Hyprland VM 環境設定與測試矩陣" data-link-desc="要在 VM 裡測試 Hyprland 配置、或判斷某個設定該在 VM 還是實機驗證時回來讀">模組五：Hyprland VM 測試</a>——本系列的 VM 安裝是它的下游前置；裝好機器才能測 Hyprland。</li>
<li><a href="/blog/linux/dotfile/07-desktop-maintenance/log-reading-diagnostic-tools/" data-link-title="日誌判讀與診斷工具" data-link-desc="知道桌面出了問題但不確定原因時回來讀 — journalctl、dmesg、hyprctl、systemctl 的使用方式和常見 log pattern">模組七：日誌判讀與診斷工具</a>——「可除錯的 bootstrap」與它呼應：前者談怎麼產生可診斷的 log，後者談怎麼讀。</li>
<li><a href="../debug/">Linux 除錯與診斷</a>——本系列裝好、連入之後的下游；出問題時的判讀紀律與情境分流。</li>
<li><a href="../tools/">Linux 工具選單</a>——安裝與除錯要用的工具（CLI / 圖形桌面 / 遠端）有哪些選擇、推薦用哪些。</li>
<li><a href="/blog/infra/00-infra-mindset/first-day-with-cloud-account/" data-link-title="拿到雲端帳號的第一天" data-link-desc="被指派 infra 工作、拿到 AWS 或 GCP 帳號、不確定該先做什麼時讀 — 第一小時安全底線、帳號現況判讀、後續學習路線分流">Infra 心智模型：拿到雲端帳號的第一天</a>——雲端主機的機器初始化是本系列的上游情境；被指派 infra 工作、拿到一台雲端主機後，先過本系列的 OS 連入與 bootstrap 前置，再進 infra 的資源管理。</li>
</ul>
]]></content:encoded></item><item><title>安裝過程用到的基礎操作</title><link>https://tarrragon.github.io/blog/linux/install/basic-operations/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/install/basic-operations/</guid><description>&lt;p>這篇是「Linux 安裝與機器初始化」系列的基礎操作篇，系列用一套最小化的 Arch Linux 安裝當貫穿例子。在安裝、補工具、設定 SSH 的過程中，會用到一小撮基礎操作——成為 root、用 nano 改設定檔、shell 的幾個符號。Linux 的指令入門教學網路上已經很豐富，這篇不重複那些，只挑「這個系列實際用到、而且沒太多 Linux 實作經驗的人容易卡住」的那幾個介紹清楚，讓你照著操作時不會被一個沒見過的指令擋在半路。已經熟的，直接跳到 &lt;a href="../install-option-decisions/">安裝選項判讀&lt;/a>。&lt;/p>
&lt;h2 id="成為-rootsu--">成為 root：su -&lt;/h2>
&lt;p>&lt;code>su -&lt;/code> 讓你從一般使用者切換成 root（系統管理員），整個 session 都以 root 身分操作。這個系列用到它，是因為在還沒有 &lt;code>sudo&lt;/code> 的最小系統上，要裝 sudo、改系統設定，得先成為 root——而成為 root 的方式就是 &lt;code>su -&lt;/code>，輸入 root 密碼後進入 root 的 shell。&lt;/p>
&lt;p>那個 &lt;code>-&lt;/code> 決定你載入哪一套環境。&lt;code>su -&lt;/code> 啟動一個 login shell（模擬從頭登入、會跑完整的登入環境初始化），載入 root 自己的完整環境——把 &lt;code>PATH&lt;/code>（shell 搜尋指令的目錄清單）換成 root 的（會包含 &lt;code>/usr/sbin&lt;/code> 這類放管理工具的目錄）、工作目錄切到 root 的家。&lt;code>su&lt;/code>（不加 &lt;code>-&lt;/code>）則不啟動 login shell，環境多半沿用你呼叫時的、可能少掉那些管理工具目錄，於是有些管理指令會因為不在 &lt;code>PATH&lt;/code> 裡而「找不到」，明明你已經是 root。所以要做系統管理，習慣用 &lt;code>su -&lt;/code>。&lt;/p>
&lt;p>&lt;code>su -&lt;/code> 跟 &lt;code>sudo&lt;/code> 解決的是不同情境。&lt;code>sudo&lt;/code> 是「以 root 身分跑單一一條指令」，跑完就回到你自己；&lt;code>su -&lt;/code> 是「整段都當 root」。這個系列先用 &lt;code>su -&lt;/code> 是因為 sudo 還沒裝——一旦 sudo 裝好、wheel 群組（慣例上被授權可用 sudo 的群組）的授權設好，後面就改用 &lt;code>sudo &amp;lt;指令&amp;gt;&lt;/code>，不再整段切 root。做完 root 的事，打 &lt;code>exit&lt;/code> 回到原本的使用者。&lt;/p>
&lt;h2 id="用-nano-改設定檔">用 nano 改設定檔&lt;/h2>
&lt;p>nano 是一個對照 vi 更直覺的文字編輯器，安裝過程改 &lt;code>locale.gen&lt;/code>、&lt;code>hostname&lt;/code>、&lt;code>sudoers&lt;/code> 這些設定檔時會用到它。它的好處是所有快捷鍵都列在畫面最下面兩行，不必背。&lt;/p>
&lt;p>那兩行裡的 &lt;code>^&lt;/code> 符號代表 Ctrl 鍵。&lt;code>^O&lt;/code> 就是 Ctrl+O、&lt;code>^X&lt;/code> 就是 Ctrl+X。這個系列用到的幾個：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>按鍵&lt;/th>
 &lt;th>畫面標示&lt;/th>
 &lt;th>作用&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Ctrl+O&lt;/td>
 &lt;td>&lt;code>^O Write Out&lt;/code>&lt;/td>
 &lt;td>存檔（會問檔名，按 Enter 確認）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Ctrl+X&lt;/td>
 &lt;td>&lt;code>^X Exit&lt;/code>&lt;/td>
 &lt;td>離開（有未存變更會問要不要存）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Ctrl+W&lt;/td>
 &lt;td>&lt;code>^W Where Is&lt;/code>&lt;/td>
 &lt;td>搜尋——在長檔案裡跳到某個字串&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Ctrl+K&lt;/td>
 &lt;td>&lt;code>^K Cut&lt;/code>&lt;/td>
 &lt;td>剪掉游標所在的整行&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Ctrl+U&lt;/td>
 &lt;td>&lt;code>^U Paste&lt;/code>&lt;/td>
 &lt;td>把剪掉的內容貼回來&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>把這幾個串起來就是一次典型的設定檔編輯。以這個系列裡「解開 &lt;code>locale.gen&lt;/code> 某一行的註解」為例：按 Ctrl+W 搜 &lt;code>en_US.UTF-8 UTF-8&lt;/code> 跳到那行、用 Backspace 刪掉行首的 &lt;code>#&lt;/code>、按 Ctrl+O 再 Enter 存檔、按 Ctrl+X 離開。改 &lt;code>hostname&lt;/code> 則是 Ctrl+K 剪掉預設那行、打上新主機名、Ctrl+O、Ctrl+X。Ctrl+K 加 Ctrl+U 合起來就是「剪下再貼上」，是搬移整行的常見組合。&lt;/p>
&lt;h2 id="檔名與指令的大小寫">檔名與指令的大小寫&lt;/h2>
&lt;p>Linux 把大小寫當成不同的字元，這對檔名跟指令都成立。&lt;code>Setup&lt;/code> 跟 &lt;code>setup&lt;/code> 是兩個不同的東西、&lt;code>Documents&lt;/code> 跟 &lt;code>documents&lt;/code> 是兩個不同的資料夾、打 &lt;code>Sudo&lt;/code> 不會執行到 &lt;code>sudo&lt;/code>。這條規則貫穿整個系列：執行 archboot 的安裝程式是 &lt;code>setup&lt;/code>（全小寫），啟動 Hyprland 桌面的指令是 &lt;code>Hyprland&lt;/code>（首字母大寫），兩者差一個字母的大小寫就是不同的目標。&lt;/p>
&lt;p>這對從 macOS 或 Windows 過來的人尤其常見，因為那兩個系統的預設檔案系統不分大小寫——在 Mac 上 &lt;code>File.txt&lt;/code> 跟 &lt;code>file.txt&lt;/code> 指向同一個檔，到了 Linux 就是兩個檔。同一個專案在 Mac 上跑得好好的，搬到 Linux 卻出現「檔案找不到」，常常就是某處大小寫對不上、而 Mac 的不分大小寫把問題藏了起來。&lt;/p>
&lt;p>判讀方式很簡單：在 Linux 上，指令、檔名、路徑一律照原樣的大小寫打。錯誤訊息 &lt;code>command not found&lt;/code> 或 &lt;code>No such file or directory&lt;/code> 在你確定東西明明就在時，先懷疑大小寫。&lt;/p></description><content:encoded><![CDATA[<p>這篇是「Linux 安裝與機器初始化」系列的基礎操作篇，系列用一套最小化的 Arch Linux 安裝當貫穿例子。在安裝、補工具、設定 SSH 的過程中，會用到一小撮基礎操作——成為 root、用 nano 改設定檔、shell 的幾個符號。Linux 的指令入門教學網路上已經很豐富，這篇不重複那些，只挑「這個系列實際用到、而且沒太多 Linux 實作經驗的人容易卡住」的那幾個介紹清楚，讓你照著操作時不會被一個沒見過的指令擋在半路。已經熟的，直接跳到 <a href="../install-option-decisions/">安裝選項判讀</a>。</p>
<h2 id="成為-rootsu--">成為 root：su -</h2>
<p><code>su -</code> 讓你從一般使用者切換成 root（系統管理員），整個 session 都以 root 身分操作。這個系列用到它，是因為在還沒有 <code>sudo</code> 的最小系統上，要裝 sudo、改系統設定，得先成為 root——而成為 root 的方式就是 <code>su -</code>，輸入 root 密碼後進入 root 的 shell。</p>
<p>那個 <code>-</code> 決定你載入哪一套環境。<code>su -</code> 啟動一個 login shell（模擬從頭登入、會跑完整的登入環境初始化），載入 root 自己的完整環境——把 <code>PATH</code>（shell 搜尋指令的目錄清單）換成 root 的（會包含 <code>/usr/sbin</code> 這類放管理工具的目錄）、工作目錄切到 root 的家。<code>su</code>（不加 <code>-</code>）則不啟動 login shell，環境多半沿用你呼叫時的、可能少掉那些管理工具目錄，於是有些管理指令會因為不在 <code>PATH</code> 裡而「找不到」，明明你已經是 root。所以要做系統管理，習慣用 <code>su -</code>。</p>
<p><code>su -</code> 跟 <code>sudo</code> 解決的是不同情境。<code>sudo</code> 是「以 root 身分跑單一一條指令」，跑完就回到你自己；<code>su -</code> 是「整段都當 root」。這個系列先用 <code>su -</code> 是因為 sudo 還沒裝——一旦 sudo 裝好、wheel 群組（慣例上被授權可用 sudo 的群組）的授權設好，後面就改用 <code>sudo &lt;指令&gt;</code>，不再整段切 root。做完 root 的事，打 <code>exit</code> 回到原本的使用者。</p>
<h2 id="用-nano-改設定檔">用 nano 改設定檔</h2>
<p>nano 是一個對照 vi 更直覺的文字編輯器，安裝過程改 <code>locale.gen</code>、<code>hostname</code>、<code>sudoers</code> 這些設定檔時會用到它。它的好處是所有快捷鍵都列在畫面最下面兩行，不必背。</p>
<p>那兩行裡的 <code>^</code> 符號代表 Ctrl 鍵。<code>^O</code> 就是 Ctrl+O、<code>^X</code> 就是 Ctrl+X。這個系列用到的幾個：</p>
<table>
  <thead>
      <tr>
          <th>按鍵</th>
          <th>畫面標示</th>
          <th>作用</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Ctrl+O</td>
          <td><code>^O Write Out</code></td>
          <td>存檔（會問檔名，按 Enter 確認）</td>
      </tr>
      <tr>
          <td>Ctrl+X</td>
          <td><code>^X Exit</code></td>
          <td>離開（有未存變更會問要不要存）</td>
      </tr>
      <tr>
          <td>Ctrl+W</td>
          <td><code>^W Where Is</code></td>
          <td>搜尋——在長檔案裡跳到某個字串</td>
      </tr>
      <tr>
          <td>Ctrl+K</td>
          <td><code>^K Cut</code></td>
          <td>剪掉游標所在的整行</td>
      </tr>
      <tr>
          <td>Ctrl+U</td>
          <td><code>^U Paste</code></td>
          <td>把剪掉的內容貼回來</td>
      </tr>
  </tbody>
</table>
<p>把這幾個串起來就是一次典型的設定檔編輯。以這個系列裡「解開 <code>locale.gen</code> 某一行的註解」為例：按 Ctrl+W 搜 <code>en_US.UTF-8 UTF-8</code> 跳到那行、用 Backspace 刪掉行首的 <code>#</code>、按 Ctrl+O 再 Enter 存檔、按 Ctrl+X 離開。改 <code>hostname</code> 則是 Ctrl+K 剪掉預設那行、打上新主機名、Ctrl+O、Ctrl+X。Ctrl+K 加 Ctrl+U 合起來就是「剪下再貼上」，是搬移整行的常見組合。</p>
<h2 id="檔名與指令的大小寫">檔名與指令的大小寫</h2>
<p>Linux 把大小寫當成不同的字元，這對檔名跟指令都成立。<code>Setup</code> 跟 <code>setup</code> 是兩個不同的東西、<code>Documents</code> 跟 <code>documents</code> 是兩個不同的資料夾、打 <code>Sudo</code> 不會執行到 <code>sudo</code>。這條規則貫穿整個系列：執行 archboot 的安裝程式是 <code>setup</code>（全小寫），啟動 Hyprland 桌面的指令是 <code>Hyprland</code>（首字母大寫），兩者差一個字母的大小寫就是不同的目標。</p>
<p>這對從 macOS 或 Windows 過來的人尤其常見，因為那兩個系統的預設檔案系統不分大小寫——在 Mac 上 <code>File.txt</code> 跟 <code>file.txt</code> 指向同一個檔，到了 Linux 就是兩個檔。同一個專案在 Mac 上跑得好好的，搬到 Linux 卻出現「檔案找不到」，常常就是某處大小寫對不上、而 Mac 的不分大小寫把問題藏了起來。</p>
<p>判讀方式很簡單：在 Linux 上，指令、檔名、路徑一律照原樣的大小寫打。錯誤訊息 <code>command not found</code> 或 <code>No such file or directory</code> 在你確定東西明明就在時，先懷疑大小寫。</p>
<h2 id="shell-的幾個符號">shell 的幾個符號</h2>
<p>這個系列的指令裡出現了幾個 shell 符號，它們是 shell 本身的語法、不是某個程式的參數，認得它們才讀得懂那些指令在做什麼。</p>
<p><code>&gt;</code> 跟 <code>&gt;&gt;</code> 是重導向，把本來會印到畫面的輸出改寫進檔案。<code>&gt;</code> 覆蓋、<code>&gt;&gt;</code> 追加。系列裡 <code>echo '%wheel ALL=(ALL:ALL) ALL' &gt; /etc/sudoers.d/10-wheel</code> 就是把那行文字寫進一個新檔，而設 <code>authorized_keys</code> 時用 <code>&gt;&gt;</code> 是為了追加、不洗掉既有的 key。</p>
<p><code>|</code>（管線）把左邊指令的輸出，直接餵給右邊指令當輸入。傳 dotfile 進 VM 的 <code>tar czf - . | ssh host 'tar xzf -'</code> 就是把 tar 打包的資料流，不落地直接透過 ssh 送到對面解開。</p>
<p><code>&amp;&amp;</code> 串接兩條指令，而且只有左邊成功才跑右邊。<code>cd ~/dotfiles &amp;&amp; ./scripts/install.sh</code> 的意思是「先切到目錄，切成功了才跑腳本」——如果目錄不存在、<code>cd</code> 失敗，後面的腳本就不會在錯的目錄下執行。</p>
<p><code>$(...)</code> 是命令替換，把括號裡指令的輸出，當成值填進當下這條指令。<code>chsh -s &quot;$(command -v zsh)&quot;</code> 會先跑 <code>command -v zsh</code> 得到 <code>/usr/bin/zsh</code>，再把這個路徑填給 <code>chsh</code>。理解這個語法，也才看得懂 <a href="../minimal-install-verify/">工具驗證篇</a> 講的 <code>which</code> 地雷：當 <code>which</code> 不存在、<code>$(which zsh)</code> 算出空字串，整條指令就拿到一個空值。</p>
<p>跟重導向相關的還有一個經典陷阱：<code>sudo echo '內容' &gt; /etc/某個-root-檔</code> 會失敗。原因是重導向 <code>&gt;</code> 由你的 shell 以你的身分執行，不是被 <code>sudo</code> 提權的那條指令——被提權的只有 <code>echo</code>，真正寫檔的還是你，而你沒權限寫那個 root 檔。解法是 <code>echo '內容' | sudo tee /etc/某個-root-檔</code>：<code>tee</code> 把流進它的 stdin 寫進檔案，而 <code>tee</code> 才是被 <code>sudo</code> 提權的那支，所以寫得進去。這個系列補 <code>sudoers</code> 檔時用的就是這個寫法。</p>
<p>權限對某些檔是硬要求，所以系列裡出現了 <code>chmod</code>。<code>chmod</code> 改檔案權限，那串數字是八進位的權限碼，三位分別代表擁有者/群組/其他人；每一位是讀（4）+ 寫（2）+ 執行（1）的和，所以 <code>7</code> 是全權、<code>6</code> 是可讀寫、<code>4</code> 是只讀、<code>0</code> 是無權。系列裡的 <code>chmod 440</code>（擁有者與群組可讀、其他人無權，給 sudoers 檔）、<code>chmod 600</code>（只有擁有者可讀寫，給私鑰）、<code>chmod 700</code>（只有擁有者全權，給 <code>.ssh</code> 目錄）就是這樣算出來的。這些檔對權限有要求——sudoers 必須是 440、私鑰必須只有自己讀得到，否則對應的工具會拒絕使用它們，所以那幾個 <code>chmod</code> 不是裝飾、是讓 sudo 跟 ssh 願意接受那些檔的條件。</p>
<h2 id="下一步">下一步</h2>
<p>認得這些基礎操作之後，就可以從 <a href="../install-option-decisions/">安裝選項判讀</a> 開始走完整的安裝流程，過程中再遇到這幾個操作就不會卡。</p>
]]></content:encoded></item><item><title>Linux 桌面的故障隔離模型</title><link>https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/fault-isolation-model/</link><pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/fault-isolation-model/</guid><description>&lt;p>Linux 桌面環境的故障隔離建立在一個結構性的設計決策上：&lt;strong>顯示合成器（compositor）是 userspace process，不是 kernel 的一部分&lt;/strong>。這意味著 Hyprland 掛了只是桌面消失，作業系統核心還在正常運作。&lt;/p>
&lt;p>本文用「桌面環境」泛指使用者看到的圖形介面整體。技術上，Hyprland 是 Wayland compositor——負責視窗合成和輸入管理，不含完整桌面環境（DE）的套件管理、設定面板等元件。GNOME、KDE Plasma 才是完整的 DE。但在故障隔離的討論中，關鍵區分是 kernel space vs userspace，compositor 和 DE 都在 userspace 這一側。&lt;/p>
&lt;h2 id="kernel-與-userspace-的隔離邊界">Kernel 與 Userspace 的隔離邊界&lt;/h2>
&lt;p>作業系統分成兩個執行空間。Kernel space 負責硬體驅動、記憶體管理、process 排程這些基礎設施。Userspace 跑所有應用程式，包括桌面環境本身。&lt;/p>
&lt;p>兩個空間之間有硬體層級的隔離——CPU 的保護環機制（ring 0 是核心層級、ring 3 是應用程式層級，硬體強制限制 ring 3 的程式碼存取 ring 0 的記憶體）。Userspace 的 process 不管怎麼崩潰，都不會直接影響 kernel。Kernel 會清理掉崩潰的 process、回收它佔用的記憶體，然後繼續運作。&lt;/p>
&lt;p>這個隔離機制解釋了一個關鍵差異：為什麼 Linux 上一個 app crash 通常只是那個視窗消失，而不會拖垮整台機器。&lt;/p>
&lt;h2 id="為什麼-windows-會藍屏">為什麼 Windows 會藍屏&lt;/h2>
&lt;p>Windows 的藍屏（Blue Screen of Death, BSOD）是 kernel panic 的表現——作業系統核心本身遇到無法恢復的錯誤，只能停機。&lt;/p>
&lt;p>Windows 藍屏頻率較高的結構性原因在於&lt;strong>顯示驅動的執行位置&lt;/strong>。Windows 把 GPU 驅動放在 kernel mode（WDDM 架構），NVIDIA 或 AMD 的驅動程式碼直接跑在核心空間。驅動有 bug 時，錯誤發生在 kernel space，清理掉再繼續的選項不存在——繼續執行可能造成資料損壞，只能停機。&lt;/p>
&lt;p>藍屏頻率高是架構選擇的代價。把驅動放在 kernel mode 可以減少 context switch 的效能開銷，GPU 效能更好。代價是驅動 bug 的爆炸半徑從「app crash」升級成「整台停機」。Windows 10/11 已加入 TDR（Timeout Detection and Recovery）機制——GPU driver hang 時系統嘗試 reset driver 而非直接藍屏，大幅降低了 GPU 導致的 BSOD 頻率。但架構上 driver 仍在 kernel mode，藍屏的可能性仍然存在。&lt;/p>
&lt;h2 id="linux-桌面的架構差異">Linux 桌面的架構差異&lt;/h2>
&lt;p>Linux 桌面環境的顯示合成器（Hyprland、Sway、KDE Plasma 的 KWin）跑在 userspace。它們透過 DRM/KMS（Direct Rendering Manager / Kernel Mode Setting，Linux 的顯示子系統介面）跟 kernel 的 GPU 驅動溝通，但合成器本身的程式碼不在 kernel space 裡。&lt;/p>
&lt;p>這個架構選擇的效果：&lt;/p>
&lt;p>&lt;strong>Compositor crash&lt;/strong>。Hyprland 如果遇到 segfault 或其他 fatal error，kernel 終止這個 userspace process。所有由它管理的視窗消失，螢幕回到 TTY 登入畫面或黑屏。但 kernel 還在跑——其他 TTY 可以登入，SSH 可以連線，背景的 service 繼續運作。&lt;/p>
&lt;p>&lt;strong>GPU driver bug&lt;/strong>。Linux 的 GPU 驅動分兩層：kernel module（可動態載入的核心擴充模組，如 &lt;code>nvidia.ko&lt;/code>、&lt;code>amdgpu.ko&lt;/code>）負責硬體操作，userspace 的 Mesa / NVIDIA userspace driver 負責 OpenGL/Vulkan 實作。Kernel module 出問題理論上可以 kernel panic，但實際行為取決於驅動。AMD 的開源 &lt;code>amdgpu&lt;/code> 通常會嘗試 reset GPU 而非直接 panic，常見的表現是畫面凍結幾秒後恢復。NVIDIA 的閉源 &lt;code>nvidia.ko&lt;/code> 是隔離模型的主要例外——kernel 社群無法審查或修復其程式碼，hang 時恢復能力遠弱於 amdgpu，經常拖垮整個 session 且 TTY 切換也受影響。這是後續&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/common-failures-recovery/" data-link-title="常見故障場景與恢復操作" data-link-desc="Hyprland 黑屏、waybar 消失、畫面凍結、記憶體爆掉或 config 寫錯導致進不了桌面時，按症狀查恢復操作">故障場景&lt;/a>中 NVIDIA 相關 caveat 的根源。&lt;/p></description><content:encoded><![CDATA[<p>Linux 桌面環境的故障隔離建立在一個結構性的設計決策上：<strong>顯示合成器（compositor）是 userspace process，不是 kernel 的一部分</strong>。這意味著 Hyprland 掛了只是桌面消失，作業系統核心還在正常運作。</p>
<p>本文用「桌面環境」泛指使用者看到的圖形介面整體。技術上，Hyprland 是 Wayland compositor——負責視窗合成和輸入管理，不含完整桌面環境（DE）的套件管理、設定面板等元件。GNOME、KDE Plasma 才是完整的 DE。但在故障隔離的討論中，關鍵區分是 kernel space vs userspace，compositor 和 DE 都在 userspace 這一側。</p>
<h2 id="kernel-與-userspace-的隔離邊界">Kernel 與 Userspace 的隔離邊界</h2>
<p>作業系統分成兩個執行空間。Kernel space 負責硬體驅動、記憶體管理、process 排程這些基礎設施。Userspace 跑所有應用程式，包括桌面環境本身。</p>
<p>兩個空間之間有硬體層級的隔離——CPU 的保護環機制（ring 0 是核心層級、ring 3 是應用程式層級，硬體強制限制 ring 3 的程式碼存取 ring 0 的記憶體）。Userspace 的 process 不管怎麼崩潰，都不會直接影響 kernel。Kernel 會清理掉崩潰的 process、回收它佔用的記憶體，然後繼續運作。</p>
<p>這個隔離機制解釋了一個關鍵差異：為什麼 Linux 上一個 app crash 通常只是那個視窗消失，而不會拖垮整台機器。</p>
<h2 id="為什麼-windows-會藍屏">為什麼 Windows 會藍屏</h2>
<p>Windows 的藍屏（Blue Screen of Death, BSOD）是 kernel panic 的表現——作業系統核心本身遇到無法恢復的錯誤，只能停機。</p>
<p>Windows 藍屏頻率較高的結構性原因在於<strong>顯示驅動的執行位置</strong>。Windows 把 GPU 驅動放在 kernel mode（WDDM 架構），NVIDIA 或 AMD 的驅動程式碼直接跑在核心空間。驅動有 bug 時，錯誤發生在 kernel space，清理掉再繼續的選項不存在——繼續執行可能造成資料損壞，只能停機。</p>
<p>藍屏頻率高是架構選擇的代價。把驅動放在 kernel mode 可以減少 context switch 的效能開銷，GPU 效能更好。代價是驅動 bug 的爆炸半徑從「app crash」升級成「整台停機」。Windows 10/11 已加入 TDR（Timeout Detection and Recovery）機制——GPU driver hang 時系統嘗試 reset driver 而非直接藍屏，大幅降低了 GPU 導致的 BSOD 頻率。但架構上 driver 仍在 kernel mode，藍屏的可能性仍然存在。</p>
<h2 id="linux-桌面的架構差異">Linux 桌面的架構差異</h2>
<p>Linux 桌面環境的顯示合成器（Hyprland、Sway、KDE Plasma 的 KWin）跑在 userspace。它們透過 DRM/KMS（Direct Rendering Manager / Kernel Mode Setting，Linux 的顯示子系統介面）跟 kernel 的 GPU 驅動溝通，但合成器本身的程式碼不在 kernel space 裡。</p>
<p>這個架構選擇的效果：</p>
<p><strong>Compositor crash</strong>。Hyprland 如果遇到 segfault 或其他 fatal error，kernel 終止這個 userspace process。所有由它管理的視窗消失，螢幕回到 TTY 登入畫面或黑屏。但 kernel 還在跑——其他 TTY 可以登入，SSH 可以連線，背景的 service 繼續運作。</p>
<p><strong>GPU driver bug</strong>。Linux 的 GPU 驅動分兩層：kernel module（可動態載入的核心擴充模組，如 <code>nvidia.ko</code>、<code>amdgpu.ko</code>）負責硬體操作，userspace 的 Mesa / NVIDIA userspace driver 負責 OpenGL/Vulkan 實作。Kernel module 出問題理論上可以 kernel panic，但實際行為取決於驅動。AMD 的開源 <code>amdgpu</code> 通常會嘗試 reset GPU 而非直接 panic，常見的表現是畫面凍結幾秒後恢復。NVIDIA 的閉源 <code>nvidia.ko</code> 是隔離模型的主要例外——kernel 社群無法審查或修復其程式碼，hang 時恢復能力遠弱於 amdgpu，經常拖垮整個 session 且 TTY 切換也受影響。這是後續<a href="/blog/linux/dotfile/07-desktop-maintenance/common-failures-recovery/" data-link-title="常見故障場景與恢復操作" data-link-desc="Hyprland 黑屏、waybar 消失、畫面凍結、記憶體爆掉或 config 寫錯導致進不了桌面時，按症狀查恢復操作">故障場景</a>中 NVIDIA 相關 caveat 的根源。</p>
<p><strong>應用程式 crash</strong>。Firefox、VS Code、任何 GUI 程式崩潰，只有那個視窗消失。Compositor 繼續管理剩下的視窗，桌面環境不受影響。</p>
<h2 id="ttykernel-存活時的首選救生通道">TTY：kernel 存活時的首選救生通道</h2>
<p>TTY（TeleTYpewriter）是 Linux 核心直接提供的純文字終端機介面，獨立於任何桌面環境。systemd 預設配置下有 6 個 virtual console（TTY1-TTY6）。Wayland compositor（如 Hyprland）通常佔用 TTY1，其餘可用。</p>
<p>切換方式：<code>Ctrl+Alt+F2</code>（切到 TTY2）到 <code>Ctrl+Alt+F6</code>（切到 TTY6）。</p>
<p>TTY 的重要性在於它<strong>不依賴 compositor</strong>。Hyprland 掛了、compositor crash 導致桌面消失——只要 kernel 還活著、GPU driver 仍能處理 VT switch，TTY 就能切過去登入操作：</p>
<ul>
<li>用 <code>htop</code> 或 <code>ps</code> 查看哪個 process 出問題</li>
<li><code>kill</code> 有問題的 process</li>
<li>用 <code>vim</code> 或 <code>nano</code> 修改配置檔</li>
<li>重新啟動 Hyprland（<code>Hyprland</code> 指令）</li>
<li>如果需要，正常 <code>reboot</code></li>
</ul>
<p>TTY 切換失效的情境有兩種：kernel panic（極罕見）和 GPU 完全 hang 導致 VT switch 本身卡住（NVIDIA 閉源驅動在 Wayland 上較常見，需確保 <code>nvidia_drm.modeset=1</code>）。後者的替代手段是 SSH 遠端登入或 Magic SysRq 鍵（見<a href="/blog/linux/dotfile/07-desktop-maintenance/common-failures-recovery/" data-link-title="常見故障場景與恢復操作" data-link-desc="Hyprland 黑屏、waybar 消失、畫面凍結、記憶體爆掉或 config 寫錯導致進不了桌面時，按症狀查恢復操作">常見故障場景</a>的場景三）。</p>
<h2 id="記憶體耗盡oom的處理機制">記憶體耗盡（OOM）的處理機制</h2>
<p>Linux kernel 有 OOM Killer（Out of Memory Killer）機制——當記憶體和 swap 都用完、kernel 無法再分配新頁面時，自動挑選佔用記憶體最多、重要性最低的 process 強制終止，釋放記憶體讓系統繼續運作。</p>
<p>OOM Killer 的行為有時超出使用者的預期——它可能直接終止 Hyprland（因為 compositor 通常佔用不少記憶體），導致桌面突然消失。但關鍵是：<strong>系統沒有崩潰</strong>。Kernel 還在、TTY 還在、SSH 還在。</p>
<p>預防 OOM 的常見做法：</p>
<ul>
<li>設定 swap（即使用 SSD，2-4GB 的 swap 也能在記憶體壓力大時提供緩衝）</li>
<li>啟用 <code>systemd-oomd</code>（userspace 的 OOM 管理，比 kernel OOM Killer 更早介入、更可控）</li>
<li>監控記憶體用量（<code>btop</code> 或 <code>htop</code> 可以看即時狀態）</li>
</ul>
<h2 id="故障層級速查">故障層級速查</h2>
<table>
  <thead>
      <tr>
          <th>故障層級</th>
          <th>症狀</th>
          <th>系統影響</th>
          <th>恢復手段</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>應用程式 crash</td>
          <td>單一視窗消失</td>
          <td>無</td>
          <td>重開該程式</td>
      </tr>
      <tr>
          <td>工具 crash（waybar 等）</td>
          <td>狀態列 / 通知 / 啟動器消失</td>
          <td>無</td>
          <td>重啟該工具</td>
      </tr>
      <tr>
          <td>Compositor crash</td>
          <td>所有視窗消失、黑屏</td>
          <td>桌面環境不可用</td>
          <td>TTY 登入、重啟 compositor</td>
      </tr>
      <tr>
          <td>GPU driver hang</td>
          <td>畫面凍結</td>
          <td>桌面環境不可用</td>
          <td>TTY 或 SSH、kill compositor</td>
      </tr>
      <tr>
          <td>OOM</td>
          <td>系統極慢或桌面被殺</td>
          <td>部分 process 被終止</td>
          <td>TTY 登入、清理 process</td>
      </tr>
      <tr>
          <td>Kernel panic</td>
          <td>完全停機</td>
          <td>全機不可用</td>
          <td>只能重開機</td>
      </tr>
  </tbody>
</table>
<p>前五個層級都有恢復手段，只有 kernel panic 需要重開機。日常使用中遇到的故障多數落在前三層。</p>
]]></content:encoded></item><item><title>桌面 Shell 元件：狀態列、啟動器與通知</title><link>https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/desktop-shell-components/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/desktop-shell-components/</guid><description>&lt;p>完整桌面環境（GNOME、KDE）把這些元件整合在一起出貨。平鋪式 WM 的桌面是拼裝的——每個位置自己選工具：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>功能&lt;/th>
 &lt;th>常見工具&lt;/th>
 &lt;th>配置格式&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>狀態列&lt;/td>
 &lt;td>Waybar, Eww, AGS&lt;/td>
 &lt;td>JSON/JSONC, Yuck, JS&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>啟動器&lt;/td>
 &lt;td>Wofi, Rofi (wayland), Fuzzel&lt;/td>
 &lt;td>CSS + 設定檔&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>通知&lt;/td>
 &lt;td>Mako, Dunst, SwayNC&lt;/td>
 &lt;td>INI/TOML&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>鎖屏&lt;/td>
 &lt;td>Hyprlock, Swaylock&lt;/td>
 &lt;td>自定義格式&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>桌布&lt;/td>
 &lt;td>Hyprpaper, Swww, Mpvpaper&lt;/td>
 &lt;td>自定義格式&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>剪貼簿&lt;/td>
 &lt;td>wl-clipboard + Cliphist&lt;/td>
 &lt;td>CLI&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>螢幕截圖&lt;/td>
 &lt;td>Grimblast, Grim + Slurp&lt;/td>
 &lt;td>CLI&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Caelestia 這類「desktop shell 專案」做的就是把上述元件統一設計、統一配色、統一出貨，省去自己一個個挑的功夫。它用的是 Quickshell（QML 框架）把所有元件包成一套風格一致的桌面。本模組教的是自己組裝的方式——理解各元件的配置，之後要用 Caelestia 或自己拼都能做。&lt;/p>
&lt;h2 id="waybar狀態列">Waybar：狀態列&lt;/h2>
&lt;p>Waybar 是 Hyprland 桌面最常用的狀態列。配置在 &lt;code>~/.config/waybar/&lt;/code>，分兩個檔案：&lt;code>config.jsonc&lt;/code>（結構和模組）和 &lt;code>style.css&lt;/code>（外觀）。&lt;/p>
&lt;h3 id="結構配置">結構配置&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsonc" data-lang="jsonc">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1">// ~/.config/waybar/config.jsonc
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;layer&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;top&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;position&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;top&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;height&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">36&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;spacing&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 左中右三區塊各放哪些模組
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nt">&amp;#34;modules-left&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;hyprland/workspaces&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;hyprland/window&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;modules-center&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;clock&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;modules-right&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;pulseaudio&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;network&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;cpu&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;memory&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;battery&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;tray&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 各模組設定
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nt">&amp;#34;hyprland/workspaces&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;format&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;{id}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;on-click&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;activate&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;clock&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;format&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;{:%H:%M}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;format-alt&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;{:%Y-%m-%d %H:%M}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;tooltip-format&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;tt&amp;gt;{calendar}&amp;lt;/tt&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;battery&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;format&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;{capacity}% {icon}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;format-icons&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;states&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;warning&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">30&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;critical&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">15&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;network&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;format-wifi&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;{essid} ({signalStrength}%)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;format-ethernet&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;{ifname}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;format-disconnected&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Disconnected&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;pulseaudio&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;format&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;{volume}%&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;on-click&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;pavucontrol&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>format&lt;/code> 欄位裡的 icon 字元來自 Nerd Font——&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/03-terminal-ecosystem/" data-link-title="模組三：終端機與編輯器" data-link-desc="終端機相關工具的配置檔散落在不同位置、不確定哪些該進 dotfile repo 時回來讀">終端機與編輯器&lt;/a>提到的字型安裝是這裡正常顯示的前提。&lt;/p>
&lt;h3 id="外觀-css">外觀 CSS&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-css" data-lang="css">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c">/* ~/.config/waybar/style.css */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="o">*&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">font-family&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;MesloLGS Nerd Font&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">monospace&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">font-size&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">13&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="nt">window&lt;/span>&lt;span class="p">#&lt;/span>&lt;span class="nn">waybar&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">background-color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">rgba&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">30&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">30&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">46&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0.85&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="k">color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#cdd6f4&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="p">#&lt;/span>&lt;span class="nn">workspaces&lt;/span> &lt;span class="nt">button&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="k">padding&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">8&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#6c7086&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">border-radius&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">6&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="p">#&lt;/span>&lt;span class="nn">workspaces&lt;/span> &lt;span class="nt">button&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nc">active&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="k">color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#cdd6f4&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">background&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">rgba&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">137&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">180&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">250&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0.2&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="p">#&lt;/span>&lt;span class="nn">clock&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="p">#&lt;/span>&lt;span class="nn">battery&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="p">#&lt;/span>&lt;span class="nn">network&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="p">#&lt;/span>&lt;span class="nn">pulseaudio&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">padding&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="kt">px&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="p">#&lt;/span>&lt;span class="nn">battery&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nc">warning&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="k">color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#f9e2af&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="p">#&lt;/span>&lt;span class="nn">battery&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nc">critical&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="k">color&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mh">#f38ba8&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>CSS 裡的色碼（&lt;code>#cdd6f4&lt;/code>、&lt;code>#89b4fa&lt;/code>、&lt;code>#f38ba8&lt;/code>）來自配色方案（這個範例用的是 Catppuccin Mocha）。統一使用同一套色碼是 rice 視覺協調的基礎。&lt;/p>
&lt;p>Config 裡的字族名必須跟系統實際安裝的字族逐字相符。Nerd Font 的圖示落在專屬碼位範圍，只有對應的那支字帶這些 glyph——指定一個沒裝的字族名，文字排版引擎找不到 glyph 就會顯示豆腐方塊。確認實裝字族名的方式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">fc-list &lt;span class="p">|&lt;/span> grep -i meslo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1"># MesloLGSNerdFont-Regular.ttf: &amp;#34;MesloLGS Nerd Font&amp;#34;:style=Regular&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>引號內的字串是 config 該填的字族名。&lt;/p>
&lt;p>同一份 Waybar config 能同時服務筆電、桌機與 VM，靠的是模組對缺少硬體的自動退化：&lt;code>battery&lt;/code> 在沒有電池的機器直接隱藏該模組、不報錯也不留空位；&lt;code>pulseaudio&lt;/code> 在沒有音訊服務時顯示為空；&lt;code>network&lt;/code> 顯示當下實際在用的介面。不必為不同機器維護多份 config——把可能用到的模組都列上，用不到的那台自己消失。&lt;/p></description><content:encoded><![CDATA[<p>完整桌面環境（GNOME、KDE）把這些元件整合在一起出貨。平鋪式 WM 的桌面是拼裝的——每個位置自己選工具：</p>
<table>
  <thead>
      <tr>
          <th>功能</th>
          <th>常見工具</th>
          <th>配置格式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>狀態列</td>
          <td>Waybar, Eww, AGS</td>
          <td>JSON/JSONC, Yuck, JS</td>
      </tr>
      <tr>
          <td>啟動器</td>
          <td>Wofi, Rofi (wayland), Fuzzel</td>
          <td>CSS + 設定檔</td>
      </tr>
      <tr>
          <td>通知</td>
          <td>Mako, Dunst, SwayNC</td>
          <td>INI/TOML</td>
      </tr>
      <tr>
          <td>鎖屏</td>
          <td>Hyprlock, Swaylock</td>
          <td>自定義格式</td>
      </tr>
      <tr>
          <td>桌布</td>
          <td>Hyprpaper, Swww, Mpvpaper</td>
          <td>自定義格式</td>
      </tr>
      <tr>
          <td>剪貼簿</td>
          <td>wl-clipboard + Cliphist</td>
          <td>CLI</td>
      </tr>
      <tr>
          <td>螢幕截圖</td>
          <td>Grimblast, Grim + Slurp</td>
          <td>CLI</td>
      </tr>
  </tbody>
</table>
<p>Caelestia 這類「desktop shell 專案」做的就是把上述元件統一設計、統一配色、統一出貨，省去自己一個個挑的功夫。它用的是 Quickshell（QML 框架）把所有元件包成一套風格一致的桌面。本模組教的是自己組裝的方式——理解各元件的配置，之後要用 Caelestia 或自己拼都能做。</p>
<h2 id="waybar狀態列">Waybar：狀態列</h2>
<p>Waybar 是 Hyprland 桌面最常用的狀態列。配置在 <code>~/.config/waybar/</code>，分兩個檔案：<code>config.jsonc</code>（結構和模組）和 <code>style.css</code>（外觀）。</p>
<h3 id="結構配置">結構配置</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsonc" data-lang="jsonc"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// ~/.config/waybar/config.jsonc
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nt">&#34;layer&#34;</span><span class="p">:</span> <span class="s2">&#34;top&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nt">&#34;position&#34;</span><span class="p">:</span> <span class="s2">&#34;top&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nt">&#34;height&#34;</span><span class="p">:</span> <span class="mi">36</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nt">&#34;spacing&#34;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1">// 左中右三區塊各放哪些模組
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span>    <span class="nt">&#34;modules-left&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;hyprland/workspaces&#34;</span><span class="p">,</span> <span class="s2">&#34;hyprland/window&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="nt">&#34;modules-center&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;clock&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="nt">&#34;modules-right&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;pulseaudio&#34;</span><span class="p">,</span> <span class="s2">&#34;network&#34;</span><span class="p">,</span> <span class="s2">&#34;cpu&#34;</span><span class="p">,</span> <span class="s2">&#34;memory&#34;</span><span class="p">,</span> <span class="s2">&#34;battery&#34;</span><span class="p">,</span> <span class="s2">&#34;tray&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1">// 各模組設定
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span>    <span class="nt">&#34;hyprland/workspaces&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="s2">&#34;{id}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="nt">&#34;on-click&#34;</span><span class="p">:</span> <span class="s2">&#34;activate&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="nt">&#34;clock&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="s2">&#34;{:%H:%M}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="nt">&#34;format-alt&#34;</span><span class="p">:</span> <span class="s2">&#34;{:%Y-%m-%d %H:%M}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="nt">&#34;tooltip-format&#34;</span><span class="p">:</span> <span class="s2">&#34;&lt;tt&gt;{calendar}&lt;/tt&gt;&#34;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="nt">&#34;battery&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="s2">&#34;{capacity}% {icon}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="nt">&#34;format-icons&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="nt">&#34;states&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="nt">&#34;warning&#34;</span><span class="p">:</span> <span class="mi">30</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="nt">&#34;critical&#34;</span><span class="p">:</span> <span class="mi">15</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="nt">&#34;network&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="nt">&#34;format-wifi&#34;</span><span class="p">:</span> <span class="s2">&#34;{essid} ({signalStrength}%)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="nt">&#34;format-ethernet&#34;</span><span class="p">:</span> <span class="s2">&#34;{ifname}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="nt">&#34;format-disconnected&#34;</span><span class="p">:</span> <span class="s2">&#34;Disconnected&#34;</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="nt">&#34;pulseaudio&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="s2">&#34;{volume}%&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="nt">&#34;on-click&#34;</span><span class="p">:</span> <span class="s2">&#34;pavucontrol&#34;</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>format</code> 欄位裡的 icon 字元來自 Nerd Font——<a href="/blog/linux/dotfile/03-terminal-ecosystem/" data-link-title="模組三：終端機與編輯器" data-link-desc="終端機相關工具的配置檔散落在不同位置、不確定哪些該進 dotfile repo 時回來讀">終端機與編輯器</a>提到的字型安裝是這裡正常顯示的前提。</p>
<h3 id="外觀-css">外觀 CSS</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c">/* ~/.config/waybar/style.css */</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="o">*</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">font-family</span><span class="p">:</span> <span class="s2">&#34;MesloLGS Nerd Font&#34;</span><span class="p">,</span> <span class="kc">monospace</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">font-size</span><span class="p">:</span> <span class="mi">13</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nt">window</span><span class="p">#</span><span class="nn">waybar</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">background-color</span><span class="p">:</span> <span class="nb">rgba</span><span class="p">(</span><span class="mi">30</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">46</span><span class="p">,</span> <span class="mf">0.85</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">color</span><span class="p">:</span> <span class="mh">#cdd6f4</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">#</span><span class="nn">workspaces</span> <span class="nt">button</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">padding</span><span class="p">:</span> <span class="mi">0</span> <span class="mi">8</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">color</span><span class="p">:</span> <span class="mh">#6c7086</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">border-radius</span><span class="p">:</span> <span class="mi">6</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="p">#</span><span class="nn">workspaces</span> <span class="nt">button</span><span class="p">.</span><span class="nc">active</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">color</span><span class="p">:</span> <span class="mh">#cdd6f4</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">background</span><span class="p">:</span> <span class="nb">rgba</span><span class="p">(</span><span class="mi">137</span><span class="p">,</span> <span class="mi">180</span><span class="p">,</span> <span class="mi">250</span><span class="p">,</span> <span class="mf">0.2</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="p">#</span><span class="nn">clock</span><span class="o">,</span> <span class="p">#</span><span class="nn">battery</span><span class="o">,</span> <span class="p">#</span><span class="nn">network</span><span class="o">,</span> <span class="p">#</span><span class="nn">pulseaudio</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">padding</span><span class="p">:</span> <span class="mi">0</span> <span class="mi">10</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="p">#</span><span class="nn">battery</span><span class="p">.</span><span class="nc">warning</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">color</span><span class="p">:</span> <span class="mh">#f9e2af</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="p">#</span><span class="nn">battery</span><span class="p">.</span><span class="nc">critical</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="k">color</span><span class="p">:</span> <span class="mh">#f38ba8</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>CSS 裡的色碼（<code>#cdd6f4</code>、<code>#89b4fa</code>、<code>#f38ba8</code>）來自配色方案（這個範例用的是 Catppuccin Mocha）。統一使用同一套色碼是 rice 視覺協調的基礎。</p>
<p>Config 裡的字族名必須跟系統實際安裝的字族逐字相符。Nerd Font 的圖示落在專屬碼位範圍，只有對應的那支字帶這些 glyph——指定一個沒裝的字族名，文字排版引擎找不到 glyph 就會顯示豆腐方塊。確認實裝字族名的方式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">fc-list <span class="p">|</span> grep -i meslo
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># MesloLGSNerdFont-Regular.ttf: &#34;MesloLGS Nerd Font&#34;:style=Regular</span></span></span></code></pre></div><p>引號內的字串是 config 該填的字族名。</p>
<p>同一份 Waybar config 能同時服務筆電、桌機與 VM，靠的是模組對缺少硬體的自動退化：<code>battery</code> 在沒有電池的機器直接隱藏該模組、不報錯也不留空位；<code>pulseaudio</code> 在沒有音訊服務時顯示為空；<code>network</code> 顯示當下實際在用的介面。不必為不同機器維護多份 config——把可能用到的模組都列上，用不到的那台自己消失。</p>
<h2 id="wofi--rofi啟動器">Wofi / Rofi：啟動器</h2>
<p>啟動器是按快捷鍵彈出的搜尋框，用來啟動應用程式、執行指令、搜尋檔案。</p>
<p>Wofi（Wayland 原生）配置：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># ~/.config/wofi/config</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="na">show</span><span class="o">=</span><span class="s">drun</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="na">width</span><span class="o">=</span><span class="s">600</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="na">height</span><span class="o">=</span><span class="s">400</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="na">prompt</span><span class="o">=</span><span class="s">Search...</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="na">insensitive</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="na">allow_markup</span><span class="o">=</span><span class="s">true</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c">/* ~/.config/wofi/style.css */</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nt">window</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">background-color</span><span class="p">:</span> <span class="mh">#1e1e2e</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">border</span><span class="p">:</span> <span class="mi">2</span><span class="kt">px</span> <span class="kc">solid</span> <span class="mh">#89b4fa</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">border-radius</span><span class="p">:</span> <span class="mi">12</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">#</span><span class="nn">input</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">background-color</span><span class="p">:</span> <span class="mh">#313244</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">color</span><span class="p">:</span> <span class="mh">#cdd6f4</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">border-radius</span><span class="p">:</span> <span class="mi">8</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">margin</span><span class="p">:</span> <span class="mi">10</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">padding</span><span class="p">:</span> <span class="mi">8</span><span class="kt">px</span> <span class="mi">12</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="p">#</span><span class="nn">entry</span><span class="p">:</span><span class="nd">selected</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">background-color</span><span class="p">:</span> <span class="nb">rgba</span><span class="p">(</span><span class="mi">137</span><span class="p">,</span> <span class="mi">180</span><span class="p">,</span> <span class="mi">250</span><span class="p">,</span> <span class="mf">0.2</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Rofi（需要 wayland fork rofi-lbonn-wayland）功能更豐富——支援多種 mode（drun、window、ssh、自定義 script）、主題系統更完整。如果需要進階功能（例如 emoji picker、密碼管理器整合），Rofi 是更好的選擇。</p>
<h2 id="mako--dunst通知">Mako / Dunst：通知</h2>
<p>Mako 是 Wayland 原生的通知 daemon，負責<strong>顯示</strong>通知——它監聽 D-Bus 的 <code>org.freedesktop.Notifications</code> 介面、把收到的通知畫出來。產生通知的是應用程式，透過 <code>libnotify</code> 送上 D-Bus。所以一套能用的通知鏈需要兩半：daemon（顯示）和 <code>libnotify</code>（產生與遞送）。缺了 <code>libnotify</code>，連命令列自測用的 <code>notify-send</code> 都沒有。套件清單要同時列 <code>mako</code> 和 <code>libnotify</code>。</p>
<p>配置簡潔：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># ~/.config/mako/config</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="na">font</span><span class="o">=</span><span class="s">MesloLGS Nerd Font 11</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="na">background-color</span><span class="o">=</span><span class="s">#1e1e2e</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="na">text-color</span><span class="o">=</span><span class="s">#cdd6f4</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="na">border-color</span><span class="o">=</span><span class="s">#89b4fa</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="na">border-radius</span><span class="o">=</span><span class="s">8</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="na">border-size</span><span class="o">=</span><span class="s">2</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="na">padding</span><span class="o">=</span><span class="s">12</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="na">default-timeout</span><span class="o">=</span><span class="s">5000</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="na">max-visible</span><span class="o">=</span><span class="s">3</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">[urgency=critical]</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="na">border-color</span><span class="o">=</span><span class="s">#f38ba8</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="na">default-timeout</span><span class="o">=</span><span class="s">0</span></span></span></code></pre></div><p>通知的視覺風格（圓角、配色、字型）要跟 waybar 和啟動器一致，這是整體 rice 不散的關鍵。</p>
<p>Nerd Font 的字符集只含 Latin、圖示與 Powerline 符號，不含中日韓。任何 CJK 文字（通知內文、視窗標題）若系統沒有 CJK 字型可 fallback 會變豆腐方塊。修法是安裝 CJK fallback（如 <code>noto-fonts-cjk</code>），fontconfig 會自動補字、不需改各工具的 config。另外，中途補裝字型後已在跑的 daemon 需重啟才看得到——<code>reload</code> 類指令只重讀設定檔、不重建記憶體中的字型快照（原理見 <a href="/blog/linux/dotfile/knowledge-cards/font-availability-at-startup/" data-link-title="字型的可用集合在 process 啟動時決定" data-link-desc="裝了字型但應用程式 / 狀態列 / 通知還是看不到、還是豆腐時回來讀">字型的可用集合在 process 啟動時決定</a>）。</p>
<h2 id="grim--slurp截圖">Grim + Slurp：截圖</h2>
<p>Grim 負責截圖、Slurp 負責框選區域，兩者搭配使用。截圖結果透過 <code>wl-copy</code> 送進剪貼簿時，需要明確指定 MIME 型別：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 全螢幕截圖到剪貼簿</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">grim - <span class="p">|</span> wl-copy --type image/png
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 框選區域截圖到剪貼簿</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">grim -g <span class="s2">&#34;</span><span class="k">$(</span>slurp<span class="k">)</span><span class="s2">&#34;</span> - <span class="p">|</span> wl-copy --type image/png
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 全螢幕截圖存檔</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">grim ~/screenshot.png</span></span></code></pre></div><p><code>wl-copy</code> 不帶 <code>--type</code> 時會嘗試透過 <code>xdg-utils</code>（<code>xdg-mime</code>）推斷 stdin 的型別。最小安裝環境沒有 <code>xdg-utils</code> 的情況下，PNG bytes 會被誤標成 <code>text/plain</code>，貼進影像應用程式就拿不到圖。明確帶 <code>--type image/png</code> 讓行為不依賴環境是否安裝了 <code>xdg-utils</code>。確認剪貼簿內容型別：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">wl-paste --list-types
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 應顯示 image/png</span></span></span></code></pre></div><p>Hyprland keybind 範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">bind</span> <span class="o">=</span> , Print, exec, grim - <span class="p">|</span> wl-copy --type image/png
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">bind</span> <span class="o">=</span> SHIFT, Print, exec, grim -g <span class="s2">&#34;</span><span class="k">$(</span>slurp<span class="k">)</span><span class="s2">&#34;</span> - <span class="p">|</span> wl-copy --type image/png</span></span></code></pre></div>]]></content:encoded></item><item><title>Linux</title><link>https://tarrragon.github.io/blog/linux/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/</guid><description>&lt;p>這個分類收攏所有 Linux 相關的內容：把一台機器從空的裝起來、出問題時怎麼判、每個情境有哪些工具可用、以及怎麼把整套工作環境用 code 管理起來。Linux 的特點是靈活——同一件事往往有很多種工具、桌面環境也有多種版本與各自的擴充，所以「知道有哪些選擇、該用哪個」本身就是重要的能力，這個分類把這些一起整理。&lt;/p>
&lt;h2 id="子分類">子分類&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>子分類&lt;/th>
 &lt;th>內容&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="install/">安裝與機器初始化&lt;/a>&lt;/td>
 &lt;td>從 ISO 裝好 OS、判讀安裝選項、驗證最小系統、外部連入與 bootstrap 前置&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="debug/">除錯與診斷&lt;/a>&lt;/td>
 &lt;td>遠端 / 本地除錯的判讀紀律：讀權威狀態不靠肉眼、按症狀分流到對的檢查與工具&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="tools/">工具選單&lt;/a>&lt;/td>
 &lt;td>各情境推薦用哪些工具：CLI 環境、圖形桌面、遠端；同一件事的多種選擇怎麼挑&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="dotfile/">Dotfile 管理&lt;/a>&lt;/td>
 &lt;td>用 code 管理工作環境的完整方法論：從心智模型、shell、終端機、桌面到同步與團隊&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="建議的路線">建議的路線&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>拿到一台新機器&lt;/strong>：&lt;a href="install/">安裝與機器初始化&lt;/a> → 裝好後用 &lt;a href="dotfile/">Dotfile 管理&lt;/a> 把環境一鍵還原。&lt;/li>
&lt;li>&lt;strong>系統出問題&lt;/strong>：&lt;a href="debug/">除錯與診斷&lt;/a>，從診斷心法建立判讀紀律再依症狀分流。&lt;/li>
&lt;li>&lt;strong>想升級工具流&lt;/strong>：&lt;a href="tools/">工具選單&lt;/a>，看你天天用的 CLI / 桌面 / 遠端工具有沒有更好的選擇。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個分類收攏所有 Linux 相關的內容：把一台機器從空的裝起來、出問題時怎麼判、每個情境有哪些工具可用、以及怎麼把整套工作環境用 code 管理起來。Linux 的特點是靈活——同一件事往往有很多種工具、桌面環境也有多種版本與各自的擴充，所以「知道有哪些選擇、該用哪個」本身就是重要的能力，這個分類把這些一起整理。</p>
<h2 id="子分類">子分類</h2>
<table>
  <thead>
      <tr>
          <th>子分類</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="install/">安裝與機器初始化</a></td>
          <td>從 ISO 裝好 OS、判讀安裝選項、驗證最小系統、外部連入與 bootstrap 前置</td>
      </tr>
      <tr>
          <td><a href="debug/">除錯與診斷</a></td>
          <td>遠端 / 本地除錯的判讀紀律：讀權威狀態不靠肉眼、按症狀分流到對的檢查與工具</td>
      </tr>
      <tr>
          <td><a href="tools/">工具選單</a></td>
          <td>各情境推薦用哪些工具：CLI 環境、圖形桌面、遠端；同一件事的多種選擇怎麼挑</td>
      </tr>
      <tr>
          <td><a href="dotfile/">Dotfile 管理</a></td>
          <td>用 code 管理工作環境的完整方法論：從心智模型、shell、終端機、桌面到同步與團隊</td>
      </tr>
  </tbody>
</table>
<h2 id="建議的路線">建議的路線</h2>
<ul>
<li><strong>拿到一台新機器</strong>：<a href="install/">安裝與機器初始化</a> → 裝好後用 <a href="dotfile/">Dotfile 管理</a> 把環境一鍵還原。</li>
<li><strong>系統出問題</strong>：<a href="debug/">除錯與診斷</a>，從診斷心法建立判讀紀律再依症狀分流。</li>
<li><strong>想升級工具流</strong>：<a href="tools/">工具選單</a>，看你天天用的 CLI / 桌面 / 遠端工具有沒有更好的選擇。</li>
</ul>
]]></content:encoded></item><item><title>Linux 除錯與診斷</title><link>https://tarrragon.github.io/blog/linux/debug/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/debug/</guid><description>&lt;p>這個系列處理機器裝好、能連入之後出問題時怎麼判。核心是一套判讀紀律：先讀權威狀態，不靠肉眼猜表象——因為 Linux 上一個現象看起來像 A 卻常常是 B，看畫面就下結論容易猜錯。系列特別涵蓋遠端使用與本地除錯兩種情境，因為遠端看不到畫面，反而逼出「只信權威狀態」的好紀律。&lt;/p>
&lt;p>內容來自一次完整的 Arch Linux / Hyprland VM 實測與除錯：SSH 連不上、終端機噴亂碼、虛擬機開不起來、鎖屏狀態判錯、服務歸屬搞混——每個卡關點都被記錄下來，蒸餾成可重用的判讀路由，不綁特定發行版。&lt;/p>
&lt;h2 id="從哪篇開始">從哪篇開始&lt;/h2>
&lt;p>先讀 &lt;a href="diagnosis-read-authoritative-state/">診斷心法&lt;/a> 建立判讀紀律（讀權威狀態、四步流程），再依症狀進對應情境。&lt;/p>
&lt;h2 id="文章">文章&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>文章&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;th>回答什麼問題&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="diagnosis-read-authoritative-state/">診斷心法：讀權威狀態，不靠肉眼猜表象&lt;/a>&lt;/td>
 &lt;td>貫穿所有除錯的判讀紀律：每種問題的權威狀態來源、讀程式自己的 log、四步流程&lt;/td>
 &lt;td>一個現象看起來像 A 卻可能是 B，怎麼不猜錯&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="ssh-and-terminal-troubleshooting/">遠端連線與終端機問題&lt;/a>&lt;/td>
 &lt;td>SSH 斷線後終端機噴亂碼、遠端打字亂碼（locale/terminfo）、從 SSH 操控圖形桌面&lt;/td>
 &lt;td>連上了但終端機或 session 狀態不對怎麼修&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="machine-unreachable/">機器連不到或起不來&lt;/a>&lt;/td>
 &lt;td>SSH 突然連不上（ARP 診斷）、虛擬機開不起來（guest vs 宿主側）、磁碟滿的連鎖&lt;/td>
 &lt;td>一台機器連不到或開不了機，從哪一層往下查&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="process-service-state-diagnosis/">程序、服務與狀態怎麼判&lt;/a>&lt;/td>
 &lt;td>程式活著沒（pgrep 陷阱）、服務由誰提供（busctl）、session 鎖沒鎖、多工器 session 存活&lt;/td>
 &lt;td>判某個東西的狀態時，該讀哪個權威來源&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="service-failure-monitoring/">服務掛了怎麼自動知道&lt;/a>&lt;/td>
 &lt;td>從手動 systemctl 到 OnFailure 主動告警、先重啟才告警、hung 偵測、canary、機器死掉的體外心跳&lt;/td>
 &lt;td>不想肉眼盯服務死活，怎麼自動監控並推播&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="ntfy-push-notification-service/">ntfy：推送通知服務&lt;/a>&lt;/td>
 &lt;td>ntfy 的 pub-sub 模型、開源 vs 標準、公共站 vs 自架、topic 就是密碼的安全模型、同類對照&lt;/td>
 &lt;td>用 ntfy 推告警、想搞懂它是什麼、該不該自架&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="依症狀的讀法">依症狀的讀法&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>連不上、開不了機&lt;/strong>：機器 SSH 連不到、或虛擬機開不起來 → &lt;a href="machine-unreachable/">機器連不到或起不來&lt;/a>。&lt;/li>
&lt;li>&lt;strong>終端機行為怪&lt;/strong>：SSH 斷線後終端機噴亂碼、遠端打字亂碼、要從 SSH 操控圖形桌面 → &lt;a href="ssh-and-terminal-troubleshooting/">遠端連線與終端機問題&lt;/a>。&lt;/li>
&lt;li>&lt;strong>某個狀態判不準&lt;/strong>：程式活著沒、服務歸誰、鎖沒鎖、session 還在不在 → &lt;a href="process-service-state-diagnosis/">程序、服務與狀態怎麼判&lt;/a>。&lt;/li>
&lt;li>&lt;strong>不想手動盯服務死活&lt;/strong>：想讓 service 掛掉時主動推播、或擔心整台機器當掉沒人知道 → &lt;a href="service-failure-monitoring/">服務掛了怎麼自動知道&lt;/a>。&lt;/li>
&lt;li>&lt;strong>想建立通用紀律&lt;/strong>：想要一套適用各種症狀的「不猜錯」判讀方法 → &lt;a href="diagnosis-read-authoritative-state/">診斷心法&lt;/a>。&lt;/li>
&lt;/ul>
&lt;h2 id="跟其他模組的交叉引用">跟其他模組的交叉引用&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="../install/">Linux 安裝與機器初始化&lt;/a>——本系列的上游；把機器裝好、連入之後才輪到除錯。其中 &lt;a href="../install/observable-bootstrap/">可除錯的 bootstrap&lt;/a> 談怎麼讓腳本產生可診斷的 log，與診斷心法的「讀程式自己的 log」一體兩面。&lt;/li>
&lt;li>&lt;a href="../tools/">Linux 工具選單&lt;/a>——除錯要用的工具（CLI / 圖形桌面 / 遠端）有哪些選擇。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/log-reading-diagnostic-tools/" data-link-title="日誌判讀與診斷工具" data-link-desc="知道桌面出了問題但不確定原因時回來讀 — journalctl、dmesg、hyprctl、systemctl 的使用方式和常見 log pattern">模組七：日誌判讀與診斷工具&lt;/a>——桌面環境層的日誌判讀，與這裡的通用診斷紀律呼應。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個系列處理機器裝好、能連入之後出問題時怎麼判。核心是一套判讀紀律：先讀權威狀態，不靠肉眼猜表象——因為 Linux 上一個現象看起來像 A 卻常常是 B，看畫面就下結論容易猜錯。系列特別涵蓋遠端使用與本地除錯兩種情境，因為遠端看不到畫面，反而逼出「只信權威狀態」的好紀律。</p>
<p>內容來自一次完整的 Arch Linux / Hyprland VM 實測與除錯：SSH 連不上、終端機噴亂碼、虛擬機開不起來、鎖屏狀態判錯、服務歸屬搞混——每個卡關點都被記錄下來，蒸餾成可重用的判讀路由，不綁特定發行版。</p>
<h2 id="從哪篇開始">從哪篇開始</h2>
<p>先讀 <a href="diagnosis-read-authoritative-state/">診斷心法</a> 建立判讀紀律（讀權威狀態、四步流程），再依症狀進對應情境。</p>
<h2 id="文章">文章</h2>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>主題</th>
          <th>回答什麼問題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="diagnosis-read-authoritative-state/">診斷心法：讀權威狀態，不靠肉眼猜表象</a></td>
          <td>貫穿所有除錯的判讀紀律：每種問題的權威狀態來源、讀程式自己的 log、四步流程</td>
          <td>一個現象看起來像 A 卻可能是 B，怎麼不猜錯</td>
      </tr>
      <tr>
          <td><a href="ssh-and-terminal-troubleshooting/">遠端連線與終端機問題</a></td>
          <td>SSH 斷線後終端機噴亂碼、遠端打字亂碼（locale/terminfo）、從 SSH 操控圖形桌面</td>
          <td>連上了但終端機或 session 狀態不對怎麼修</td>
      </tr>
      <tr>
          <td><a href="machine-unreachable/">機器連不到或起不來</a></td>
          <td>SSH 突然連不上（ARP 診斷）、虛擬機開不起來（guest vs 宿主側）、磁碟滿的連鎖</td>
          <td>一台機器連不到或開不了機，從哪一層往下查</td>
      </tr>
      <tr>
          <td><a href="process-service-state-diagnosis/">程序、服務與狀態怎麼判</a></td>
          <td>程式活著沒（pgrep 陷阱）、服務由誰提供（busctl）、session 鎖沒鎖、多工器 session 存活</td>
          <td>判某個東西的狀態時，該讀哪個權威來源</td>
      </tr>
      <tr>
          <td><a href="service-failure-monitoring/">服務掛了怎麼自動知道</a></td>
          <td>從手動 systemctl 到 OnFailure 主動告警、先重啟才告警、hung 偵測、canary、機器死掉的體外心跳</td>
          <td>不想肉眼盯服務死活，怎麼自動監控並推播</td>
      </tr>
      <tr>
          <td><a href="ntfy-push-notification-service/">ntfy：推送通知服務</a></td>
          <td>ntfy 的 pub-sub 模型、開源 vs 標準、公共站 vs 自架、topic 就是密碼的安全模型、同類對照</td>
          <td>用 ntfy 推告警、想搞懂它是什麼、該不該自架</td>
      </tr>
  </tbody>
</table>
<h2 id="依症狀的讀法">依症狀的讀法</h2>
<ul>
<li><strong>連不上、開不了機</strong>：機器 SSH 連不到、或虛擬機開不起來 → <a href="machine-unreachable/">機器連不到或起不來</a>。</li>
<li><strong>終端機行為怪</strong>：SSH 斷線後終端機噴亂碼、遠端打字亂碼、要從 SSH 操控圖形桌面 → <a href="ssh-and-terminal-troubleshooting/">遠端連線與終端機問題</a>。</li>
<li><strong>某個狀態判不準</strong>：程式活著沒、服務歸誰、鎖沒鎖、session 還在不在 → <a href="process-service-state-diagnosis/">程序、服務與狀態怎麼判</a>。</li>
<li><strong>不想手動盯服務死活</strong>：想讓 service 掛掉時主動推播、或擔心整台機器當掉沒人知道 → <a href="service-failure-monitoring/">服務掛了怎麼自動知道</a>。</li>
<li><strong>想建立通用紀律</strong>：想要一套適用各種症狀的「不猜錯」判讀方法 → <a href="diagnosis-read-authoritative-state/">診斷心法</a>。</li>
</ul>
<h2 id="跟其他模組的交叉引用">跟其他模組的交叉引用</h2>
<ul>
<li><a href="../install/">Linux 安裝與機器初始化</a>——本系列的上游；把機器裝好、連入之後才輪到除錯。其中 <a href="../install/observable-bootstrap/">可除錯的 bootstrap</a> 談怎麼讓腳本產生可診斷的 log，與診斷心法的「讀程式自己的 log」一體兩面。</li>
<li><a href="../tools/">Linux 工具選單</a>——除錯要用的工具（CLI / 圖形桌面 / 遠端）有哪些選擇。</li>
<li><a href="/blog/linux/dotfile/07-desktop-maintenance/log-reading-diagnostic-tools/" data-link-title="日誌判讀與診斷工具" data-link-desc="知道桌面出了問題但不確定原因時回來讀 — journalctl、dmesg、hyprctl、systemctl 的使用方式和常見 log pattern">模組七：日誌判讀與診斷工具</a>——桌面環境層的日誌判讀，與這裡的通用診斷紀律呼應。</li>
</ul>
]]></content:encoded></item><item><title>圖形桌面工具</title><link>https://tarrragon.github.io/blog/linux/tools/gui/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/tools/gui/</guid><description>&lt;p>有圖形環境時，桌面應用的選擇跟 CLI 工具是不同的判斷：同一個功能（檔案管理、桌面 shell）不同實作拖進來的相依可以差一個數量級，因為有些應用假設某個桌面環境的服務就在旁邊、有些刻意做成桌面無關的獨立程式。在 Hyprland 這類不預設桌面環境的 window manager 上，「加一個 GUI app 的真正成本是它的相依樹」這個判斷特別重要。&lt;/p>
&lt;p>這個系列講的不只是「有哪些桌面工具」，而是選它們時該看什麼：相依足跡、桌面環境耦合、功能相依（掛載 / 縮圖 / 網路這些側欄功能背後各是一個額外套件）。&lt;/p>
&lt;h2 id="文章">文章&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>文章&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="desktop-environment-selection/">桌面環境選型：整合度與組裝自由度的取捨&lt;/a>&lt;/td>
 &lt;td>GNOME / KDE / Hyprland / XFCE / Cinnamon 的定位、資源與客製代價、Wayland 判斷&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="gui-file-manager-dependencies/">加圖形檔案管理員：依賴足跡與桌面環境耦合&lt;/a>&lt;/td>
 &lt;td>Thunar / PCManFM-Qt / Nemo 的相依樹實測對照、gvfs 與縮圖的功能相依、選型判準&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="相關">相關&lt;/h2>
&lt;ul>
&lt;li>純終端機下的檔案管理器（&lt;code>yazi&lt;/code> / &lt;code>broot&lt;/code> / &lt;code>ranger&lt;/code>）是另一條路，見 &lt;a href="../cli/">CLI 環境工具&lt;/a> 的檔案管理器段。&lt;/li>
&lt;li>桌面環境本身（GNOME / KDE / Hyprland）怎麼選、各自的擴充生態，見 &lt;a href="desktop-environment-selection/">桌面環境選型&lt;/a>；選了 Hyprland 之後怎麼組裝，見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">Dotfile 管理：桌面 Rice 設計&lt;/a>。&lt;/li>
&lt;li>在圖形桌面上安裝這些工具前的環境與相依判讀，跟 &lt;a href="../../debug/">除錯與診斷&lt;/a> 的權威狀態紀律同源。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>有圖形環境時，桌面應用的選擇跟 CLI 工具是不同的判斷：同一個功能（檔案管理、桌面 shell）不同實作拖進來的相依可以差一個數量級，因為有些應用假設某個桌面環境的服務就在旁邊、有些刻意做成桌面無關的獨立程式。在 Hyprland 這類不預設桌面環境的 window manager 上，「加一個 GUI app 的真正成本是它的相依樹」這個判斷特別重要。</p>
<p>這個系列講的不只是「有哪些桌面工具」，而是選它們時該看什麼：相依足跡、桌面環境耦合、功能相依（掛載 / 縮圖 / 網路這些側欄功能背後各是一個額外套件）。</p>
<h2 id="文章">文章</h2>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>主題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="desktop-environment-selection/">桌面環境選型：整合度與組裝自由度的取捨</a></td>
          <td>GNOME / KDE / Hyprland / XFCE / Cinnamon 的定位、資源與客製代價、Wayland 判斷</td>
      </tr>
      <tr>
          <td><a href="gui-file-manager-dependencies/">加圖形檔案管理員：依賴足跡與桌面環境耦合</a></td>
          <td>Thunar / PCManFM-Qt / Nemo 的相依樹實測對照、gvfs 與縮圖的功能相依、選型判準</td>
      </tr>
  </tbody>
</table>
<h2 id="相關">相關</h2>
<ul>
<li>純終端機下的檔案管理器（<code>yazi</code> / <code>broot</code> / <code>ranger</code>）是另一條路，見 <a href="../cli/">CLI 環境工具</a> 的檔案管理器段。</li>
<li>桌面環境本身（GNOME / KDE / Hyprland）怎麼選、各自的擴充生態，見 <a href="desktop-environment-selection/">桌面環境選型</a>；選了 Hyprland 之後怎麼組裝，見 <a href="/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">Dotfile 管理：桌面 Rice 設計</a>。</li>
<li>在圖形桌面上安裝這些工具前的環境與相依判讀，跟 <a href="../../debug/">除錯與診斷</a> 的權威狀態紀律同源。</li>
</ul>
]]></content:encoded></item><item><title>遠端連線與終端機問題</title><link>https://tarrragon.github.io/blog/linux/debug/ssh-and-terminal-troubleshooting/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/debug/ssh-and-terminal-troubleshooting/</guid><description>&lt;p>遠端操作 Linux 時，很多問題出在「你的終端機」與「遠端 session」之間那條連線的狀態，而不在遠端那台機器本身。終端機被上一個程式留在奇怪的模式、字元編碼與終端機能力沒對上、或你想從一條純文字的 SSH 連線去驅動一個需要實體螢幕的圖形桌面——這些問題的共同點是：現象發生在連線的某一層，判斷對是哪一層，修復就很直接。&lt;/p>
&lt;p>SSH「連不上」本身（&lt;code>Permission denied&lt;/code>、&lt;code>Host key verification failed&lt;/code>、&lt;code>Connection refused&lt;/code>）的判讀與修復，見 &lt;a href="../../install/ssh-keyless-bootstrap/">外部連入與無 key 的 bootstrap 路徑&lt;/a> 的重連段落。這篇處理的是「連上了、但終端機或 session 的狀態不對」的那些情況。&lt;/p>
&lt;h2 id="ssh-斷線後本機終端機噴亂碼狂跳字元">SSH 斷線後本機終端機噴亂碼、狂跳字元&lt;/h2>
&lt;p>一個嚇人但無害的情況：SSH 連線被中斷後，你本機的終端機開始瘋狂輸出像 &lt;code>&amp;lt;35;80;24M&lt;/code> 這樣的序列，尤其在你移動滑鼠時狂跳。這不是遠端機器在打字，是&lt;strong>你本機的終端機被卡在滑鼠回報模式&lt;/strong>。&lt;/p>
&lt;p>判讀關鍵在「什麼時候噴」：如果那串亂碼只在你移動滑鼠時出現、而且形如 &lt;code>數字;數字M&lt;/code>，那就是滑鼠座標回報。成因是遠端跑的某個全螢幕程式（TUI、編輯器、終端機多工器）啟動時對終端機開了滑鼠追蹤模式，SSH 被硬斷時它來不及送出「關閉滑鼠模式」的序列就死了，於是你本機終端機還停在回報模式，滑鼠一動就把游標座標當輸入送進來。&lt;/p>
&lt;p>修復是重置終端機的模式，跟遠端機器無關：&lt;/p>
&lt;ul>
&lt;li>最快：開一個新的終端機分頁 / 視窗。模式是「那個終端機 session」的狀態，新視窗是乾淨的。&lt;/li>
&lt;li>救現有視窗：先把滑鼠移開別動（洪流會停），盲打 &lt;code>reset&lt;/code> 再 Enter，送出終端機重置。&lt;/li>
&lt;li>若 &lt;code>reset&lt;/code> 沒清掉，補送關閉滑鼠回報的序列：&lt;code>printf '\033[?1000l\033[?1002l\033[?1003l\033[?1006l'&lt;/code>。&lt;/li>
&lt;/ul>
&lt;p>同一類的還有「alternate screen 沒還原」——遠端的全螢幕程式異常結束時，本機終端機可能卡在替代畫面緩衝區，看起來像畫面清空或凍結。&lt;code>reset&lt;/code> 同樣能救。歸納起來：&lt;strong>SSH 被硬斷後本機終端機行為異常，先懷疑「對端程式來不及還原終端機模式」，用 &lt;code>reset&lt;/code> 或開新視窗處理本機終端機狀態，不必急著重連遠端。&lt;/strong>&lt;/p>
&lt;h2 id="遠端打字變亂碼重複位置錯亂">遠端打字變亂碼、重複、位置錯亂&lt;/h2>
&lt;p>連上遠端後，如果互動式輸入變得不對——打一個字出現好幾個、游標位置錯亂、畫面重繪殘影——通常是兩層問題之一，判讀方式是分開排除。&lt;/p>
&lt;p>第一層是&lt;strong>字元編碼（locale）&lt;/strong>。從某些本機（例如 macOS）SSH 進 Linux 時，本機會把 &lt;code>LC_CTYPE&lt;/code> 之類的變數帶過去；如果遠端沒有對應的 locale、就會退回 POSIX/C locale，讓終端機的行編輯（ZLE、readline）對多位元組字元的寬度判斷出錯，表現為輸入重複或錯位。判斷方式是在遠端 &lt;code>locale&lt;/code> 看目前值、&lt;code>locale -a&lt;/code> 看有沒有裝對應的 UTF-8 locale。修法是在遠端明確設好 &lt;code>LANG&lt;/code> / &lt;code>LC_CTYPE&lt;/code> 到一個實際存在的 UTF-8 locale，而不是讓它繼承一個遠端不認得的值。&lt;/p>
&lt;p>第二層是&lt;strong>終端機能力資料庫（terminfo）&lt;/strong>。你本機終端機的 &lt;code>TERM&lt;/code> 值（例如某些新終端機用 &lt;code>xterm-ghostty&lt;/code> 之類的自訂值）如果在遠端沒有對應的 terminfo 條目，遠端程式就不知道怎麼正確地清行、移動游標、重繪，畫面就會亂。判斷方式是在遠端 &lt;code>echo $TERM&lt;/code> 看值、&lt;code>infocmp $TERM&lt;/code> 看遠端認不認得。修法是把本機的 terminfo 條目送過去讓遠端安裝：&lt;code>infocmp -x $TERM | ssh &amp;lt;遠端&amp;gt; 'tic -x -'&lt;/code>。&lt;/p>
&lt;p>先分清是 locale 還是 terminfo，兩者症狀相似但修法不同：locale 是編碼寬度、terminfo 是繪製指令。查 &lt;code>locale&lt;/code> 跟查 &lt;code>$TERM&lt;/code> + &lt;code>infocmp&lt;/code> 就能分開。&lt;/p>
&lt;h2 id="從-ssh-操控遠端的圖形桌面">從 SSH 操控遠端的圖形桌面&lt;/h2>
&lt;p>想從一條純文字的 SSH 連線去操作遠端的 Wayland 圖形桌面（例如啟動應用、截圖、送 IPC 指令）時，會撞到兩類界線，判斷對是哪一類就知道怎麼繞。&lt;/p>
&lt;p>第一類是&lt;strong>圖形程式需要知道連到哪個顯示&lt;/strong>。SSH 進來的 shell 預設沒有圖形環境的環境變數，直接跑圖形程式會找不到 display。要對著遠端那個已經在跑的 Wayland session 操作，得補上它的環境變數：&lt;code>XDG_RUNTIME_DIR&lt;/code>（通常 &lt;code>/run/user/&amp;lt;uid&amp;gt;&lt;/code>）、&lt;code>WAYLAND_DISPLAY&lt;/code>（socket 名，如 &lt;code>wayland-1&lt;/code>）、必要時還有該 compositor 的 instance 變數與 &lt;code>DBUS_SESSION_BUS_ADDRESS&lt;/code>。這些值怎麼撈：socket 名用 &lt;code>ls /run/user/$(id -u)/wayland-*&lt;/code> 看；其餘變數直接從那個圖形 session 既有行程的環境複製最準——&lt;code>cat /proc/&amp;lt;compositor-pid&amp;gt;/environ | tr '\0' '\n' | grep -E 'WAYLAND_DISPLAY|XDG_RUNTIME_DIR|DBUS_SESSION|_INSTANCE_'&lt;/code>（&lt;code>&amp;lt;compositor-pid&amp;gt;&lt;/code> 用 &lt;code>pgrep -x Hyprland&lt;/code> 之類找）。撈到後 &lt;code>export&lt;/code> 進當前 SSH shell，這條連線就能對遠端的圖形 session 下指令、&lt;code>grim&lt;/code> 截圖。&lt;/p>
&lt;p>第二類是&lt;strong>有些東西必須從實體圖形終端機（VT，即 &lt;code>Ctrl+Alt+F1&lt;/code>~&lt;code>F6&lt;/code> 切換的那些文字主控台）啟動，SSH 的 pty 起不來&lt;/strong>。Wayland 的合成器（compositor，畫桌面、把視窗合成到螢幕、管輸入輸出的核心程式，如 Hyprland）需要一個真正的圖形 VT 上的登入 session，拿到 DRM master（對顯示卡的獨佔繪圖控制權）與 logind seat（一組綁在一起的實體螢幕／鍵鼠裝置）才能啟動；從 SSH 的 pty 起它的&lt;strong>預設 backend&lt;/strong> 會直接失敗（例如報 backend 建立失敗），因為預設 backend 要的 DRM master 與 seat 在 SSH 這條連線上不存在。判讀訊號：合成器一啟動就報 seat / DRM / backend 相關的錯，而你是從 SSH 起的——那就是這個界線。（例外：合成器多半有 headless backend，例如設 &lt;code>WLR_BACKENDS=headless&lt;/code> 就不要 DRM master、不需 VT，專給 CI、雲端、自動化測試用；nested（跑在另一個 Wayland session 裡）也不需要。所以精確說是「預設 backend 需要圖形 VT」，不是「合成器一定起不來」。）&lt;/p></description><content:encoded><![CDATA[<p>遠端操作 Linux 時，很多問題出在「你的終端機」與「遠端 session」之間那條連線的狀態，而不在遠端那台機器本身。終端機被上一個程式留在奇怪的模式、字元編碼與終端機能力沒對上、或你想從一條純文字的 SSH 連線去驅動一個需要實體螢幕的圖形桌面——這些問題的共同點是：現象發生在連線的某一層，判斷對是哪一層，修復就很直接。</p>
<p>SSH「連不上」本身（<code>Permission denied</code>、<code>Host key verification failed</code>、<code>Connection refused</code>）的判讀與修復，見 <a href="../../install/ssh-keyless-bootstrap/">外部連入與無 key 的 bootstrap 路徑</a> 的重連段落。這篇處理的是「連上了、但終端機或 session 的狀態不對」的那些情況。</p>
<h2 id="ssh-斷線後本機終端機噴亂碼狂跳字元">SSH 斷線後本機終端機噴亂碼、狂跳字元</h2>
<p>一個嚇人但無害的情況：SSH 連線被中斷後，你本機的終端機開始瘋狂輸出像 <code>&lt;35;80;24M</code> 這樣的序列，尤其在你移動滑鼠時狂跳。這不是遠端機器在打字，是<strong>你本機的終端機被卡在滑鼠回報模式</strong>。</p>
<p>判讀關鍵在「什麼時候噴」：如果那串亂碼只在你移動滑鼠時出現、而且形如 <code>數字;數字M</code>，那就是滑鼠座標回報。成因是遠端跑的某個全螢幕程式（TUI、編輯器、終端機多工器）啟動時對終端機開了滑鼠追蹤模式，SSH 被硬斷時它來不及送出「關閉滑鼠模式」的序列就死了，於是你本機終端機還停在回報模式，滑鼠一動就把游標座標當輸入送進來。</p>
<p>修復是重置終端機的模式，跟遠端機器無關：</p>
<ul>
<li>最快：開一個新的終端機分頁 / 視窗。模式是「那個終端機 session」的狀態，新視窗是乾淨的。</li>
<li>救現有視窗：先把滑鼠移開別動（洪流會停），盲打 <code>reset</code> 再 Enter，送出終端機重置。</li>
<li>若 <code>reset</code> 沒清掉，補送關閉滑鼠回報的序列：<code>printf '\033[?1000l\033[?1002l\033[?1003l\033[?1006l'</code>。</li>
</ul>
<p>同一類的還有「alternate screen 沒還原」——遠端的全螢幕程式異常結束時，本機終端機可能卡在替代畫面緩衝區，看起來像畫面清空或凍結。<code>reset</code> 同樣能救。歸納起來：<strong>SSH 被硬斷後本機終端機行為異常，先懷疑「對端程式來不及還原終端機模式」，用 <code>reset</code> 或開新視窗處理本機終端機狀態，不必急著重連遠端。</strong></p>
<h2 id="遠端打字變亂碼重複位置錯亂">遠端打字變亂碼、重複、位置錯亂</h2>
<p>連上遠端後，如果互動式輸入變得不對——打一個字出現好幾個、游標位置錯亂、畫面重繪殘影——通常是兩層問題之一，判讀方式是分開排除。</p>
<p>第一層是<strong>字元編碼（locale）</strong>。從某些本機（例如 macOS）SSH 進 Linux 時，本機會把 <code>LC_CTYPE</code> 之類的變數帶過去；如果遠端沒有對應的 locale、就會退回 POSIX/C locale，讓終端機的行編輯（ZLE、readline）對多位元組字元的寬度判斷出錯，表現為輸入重複或錯位。判斷方式是在遠端 <code>locale</code> 看目前值、<code>locale -a</code> 看有沒有裝對應的 UTF-8 locale。修法是在遠端明確設好 <code>LANG</code> / <code>LC_CTYPE</code> 到一個實際存在的 UTF-8 locale，而不是讓它繼承一個遠端不認得的值。</p>
<p>第二層是<strong>終端機能力資料庫（terminfo）</strong>。你本機終端機的 <code>TERM</code> 值（例如某些新終端機用 <code>xterm-ghostty</code> 之類的自訂值）如果在遠端沒有對應的 terminfo 條目，遠端程式就不知道怎麼正確地清行、移動游標、重繪，畫面就會亂。判斷方式是在遠端 <code>echo $TERM</code> 看值、<code>infocmp $TERM</code> 看遠端認不認得。修法是把本機的 terminfo 條目送過去讓遠端安裝：<code>infocmp -x $TERM | ssh &lt;遠端&gt; 'tic -x -'</code>。</p>
<p>先分清是 locale 還是 terminfo，兩者症狀相似但修法不同：locale 是編碼寬度、terminfo 是繪製指令。查 <code>locale</code> 跟查 <code>$TERM</code> + <code>infocmp</code> 就能分開。</p>
<h2 id="從-ssh-操控遠端的圖形桌面">從 SSH 操控遠端的圖形桌面</h2>
<p>想從一條純文字的 SSH 連線去操作遠端的 Wayland 圖形桌面（例如啟動應用、截圖、送 IPC 指令）時，會撞到兩類界線，判斷對是哪一類就知道怎麼繞。</p>
<p>第一類是<strong>圖形程式需要知道連到哪個顯示</strong>。SSH 進來的 shell 預設沒有圖形環境的環境變數，直接跑圖形程式會找不到 display。要對著遠端那個已經在跑的 Wayland session 操作，得補上它的環境變數：<code>XDG_RUNTIME_DIR</code>（通常 <code>/run/user/&lt;uid&gt;</code>）、<code>WAYLAND_DISPLAY</code>（socket 名，如 <code>wayland-1</code>）、必要時還有該 compositor 的 instance 變數與 <code>DBUS_SESSION_BUS_ADDRESS</code>。這些值怎麼撈：socket 名用 <code>ls /run/user/$(id -u)/wayland-*</code> 看；其餘變數直接從那個圖形 session 既有行程的環境複製最準——<code>cat /proc/&lt;compositor-pid&gt;/environ | tr '\0' '\n' | grep -E 'WAYLAND_DISPLAY|XDG_RUNTIME_DIR|DBUS_SESSION|_INSTANCE_'</code>（<code>&lt;compositor-pid&gt;</code> 用 <code>pgrep -x Hyprland</code> 之類找）。撈到後 <code>export</code> 進當前 SSH shell，這條連線就能對遠端的圖形 session 下指令、<code>grim</code> 截圖。</p>
<p>第二類是<strong>有些東西必須從實體圖形終端機（VT，即 <code>Ctrl+Alt+F1</code>~<code>F6</code> 切換的那些文字主控台）啟動，SSH 的 pty 起不來</strong>。Wayland 的合成器（compositor，畫桌面、把視窗合成到螢幕、管輸入輸出的核心程式，如 Hyprland）需要一個真正的圖形 VT 上的登入 session，拿到 DRM master（對顯示卡的獨佔繪圖控制權）與 logind seat（一組綁在一起的實體螢幕／鍵鼠裝置）才能啟動；從 SSH 的 pty 起它的<strong>預設 backend</strong> 會直接失敗（例如報 backend 建立失敗），因為預設 backend 要的 DRM master 與 seat 在 SSH 這條連線上不存在。判讀訊號：合成器一啟動就報 seat / DRM / backend 相關的錯，而你是從 SSH 起的——那就是這個界線。（例外：合成器多半有 headless backend，例如設 <code>WLR_BACKENDS=headless</code> 就不要 DRM master、不需 VT，專給 CI、雲端、自動化測試用；nested（跑在另一個 Wayland session 裡）也不需要。所以精確說是「預設 backend 需要圖形 VT」，不是「合成器一定起不來」。）</p>
<p>繞法是回到那台機器的實體圖形終端機去啟動 compositor，但「回到 VT」這件事也可以從 SSH 遠端做：</p>
<ul>
<li><code>sudo chvt &lt;N&gt;</code> 從 SSH 切換那台機器目前顯示的虛擬終端機到第 N 個，比在虛擬機視窗裡跟宿主的 <code>Ctrl+Alt+Fn</code> 快捷鍵搏鬥穩定。</li>
<li>切過去卻是空白、沒有登入提示，通常是那個 VT 上沒有 getty 在跑：<code>sudo systemctl start getty@tty&lt;N&gt;</code>（開機時 <code>enabled</code> 但 <code>inactive</code> 是常見狀態，logind 的 autovt 沒觸發）。</li>
<li><code>sudo fgconsole</code> 確認目前是哪個 VT 在前景。</li>
</ul>
<p>還有一個容易混淆的點：一台虛擬機可能同時有「序列主控台」跟「圖形顯示」兩個獨立輸出。在 guest 內 <code>chvt</code> 只切圖形那側，序列主控台看到的畫面不會變。如果你在虛擬機軟體裡看的是序列主控台，圖形桌面得切到顯示輸出那個 view 才看得到。判讀：切了 VT 但畫面沒反應，先確認你正在看的是哪個輸出。</p>
<h2 id="判讀路由">判讀路由</h2>
<p>遠端 / 終端機問題的分流：</p>
<ul>
<li>本機終端機噴亂碼、只在動滑鼠時噴 → 滑鼠回報模式沒關（本機終端機狀態），<code>reset</code> 或開新視窗。</li>
<li>遠端打字重複 / 錯位 → 分 locale（查 <code>locale</code>）與 terminfo（查 <code>$TERM</code> + <code>infocmp</code>）。</li>
<li>圖形程式在 SSH 下找不到 display → 補 <code>WAYLAND_DISPLAY</code> / <code>XDG_RUNTIME_DIR</code> 等環境變數。</li>
<li>compositor 從 SSH 起不來、報 seat/DRM 錯 → 它需要實體 VT，用 <code>chvt</code> + <code>getty@tty&lt;N&gt;</code> 回到圖形 VT 啟動。</li>
<li>SSH 連不上（拒絕 / host key / refused）→ 見 <a href="../../install/ssh-keyless-bootstrap/">外部連入與無 key 的 bootstrap 路徑</a>。</li>
</ul>
<p>這幾種分流的共同底線是先讀權威狀態（<code>locale</code>、<code>$TERM</code>、runtime 目錄、<code>loginctl</code>、<code>fgconsole</code>）再下判斷；背後的方法論見 <a href="../diagnosis-read-authoritative-state/">診斷心法</a>。</p>
]]></content:encoded></item><item><title>Linux 安裝選項判讀</title><link>https://tarrragon.github.io/blog/linux/install/install-option-decisions/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/install/install-option-decisions/</guid><description>&lt;p>Linux 安裝程式的每個選項都是一個會往後傳遞代價的決策。選錯的後果不會在當下浮現，而是在重開機進不了系統、磁碟某個分區先爆掉、或裝好的機器一重開就斷網時才出現。判斷一個選項該怎麼選，靠的是同一個問題：這台機器是用來幹嘛的。一台用完即丟的測試 VM、一台跑三年的主力機、一台對外服務的伺服器，同一個選項的正確答案可能完全不同。&lt;/p>
&lt;p>這篇給每個關鍵選項一條判斷軸，而不是一份「照著點」的步驟。底下用一次具體的安裝當作貫穿例子：在 Apple Silicon 的 UTM 上用 archboot ISO 裝 Arch Linux ARM，目標是一台演練 dotfile 部署的測試 VM。例子是具體的，但判斷軸跨發行版通用——locale、分割、bootloader 這些抉擇在多數 Linux 安裝程式裡都會以類似形式出現。&lt;/p>
&lt;p>怎麼建立 VM、燒錄並開機到安裝程式，是跟環境綁定的前置——UTM、VirtualBox、實體機各不相同——不在這條判讀軸的範圍；這篇從「安裝程式已經跑起來、開始問你選項」接手，給的是每個選項的判斷軸，不逐頁帶某個安裝程式的選單怎麼點。底下的指令與選單名稱以 archboot / Arch 呈現，換 Ubuntu（Subiquity 安裝程式）或 Fedora 時，選項的判斷軸一樣成立，但選單長相、套件組名稱、指令會不同。&lt;/p>
&lt;h2 id="系統語系與時間">系統語系與時間&lt;/h2>
&lt;p>系統語系決定的是錯誤訊息、log、系統工具輸出用哪種語言，不是你日常打字的語言。這兩件事容易混為一談。日常輸入中文是桌面層的字型與輸入法問題，跟系統 locale 無關；系統 locale 影響的是當某個服務崩潰、你在 &lt;code>journalctl&lt;/code> 裡讀它吐出來的那行訊息時，那行字是英文還是被翻譯過。&lt;/p>
&lt;p>把系統 locale 留在 &lt;code>en_US.UTF-8&lt;/code> 的理由是可搜尋性。當你把一段錯誤訊息貼到搜尋引擎或問別人，英文訊息能對上絕大多數的文件、issue、Stack Overflow 答案；翻譯過的訊息往往一個結果都搜不到。這條判斷軸對伺服器、開發機、任何你預期會除錯的機器都成立。會選非英文 locale 的情境通常是給終端使用者的桌面，且該使用者不除錯——那是另一種機器。&lt;/p>
&lt;p>時區的選擇影響 log 的時間戳跟排程任務的觸發時刻，挑你所在地即可。另一個相關的決策是「硬體時鐘存 UTC 還是本地時間」：選 UTC。Linux 慣例是硬體時鐘存 UTC、顯示時再換算成時區，這樣跨時區搬機器、或 NTP 校時都不會錯亂。會需要存本地時間的唯一常見情境是跟 Windows 雙開——Windows 預設把硬體時鐘當本地時間——而 VM 或純 Linux 機器沒有這個包袱。&lt;/p>
&lt;h2 id="網路">網路&lt;/h2>
&lt;p>安裝階段的網路設定要回答兩個層次：當下能不能連、以及這份設定會不會帶進裝好的系統。第一層通常很直覺——選到對的網卡、用 DHCP 讓它自動拿 IP。虛擬機的 NAT 網路會自動發 IP，所以選 DHCP、不要手動設 static，省去算網段的麻煩。&lt;/p>
&lt;p>第二層是真正會咬人的地方：安裝程式裡設好的網路，不保證會出現在重開機後的系統裡。這是 VM 裝 Linux 常見的斷網點——安裝時明明能上網裝套件，重開機後卻連不出去，因為安裝環境的網路設定沒被複製到目標系統。判讀方式是裝好首次開機後立刻驗證：看網卡有沒有拿到 IP、能不能解析一個域名。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">ip -brief a &lt;span class="c1"># 網卡有沒有 IP、狀態是不是 UP&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">ping -c &lt;span class="m">3&lt;/span> archlinux.org &lt;span class="c1"># 解析成功就證明對外連線 + DNS 都通&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>DNS 能把域名解析成 IP，本身就證明對外連線是通的（DNS 查詢就是一次網路往返），所以這條 &lt;code>ping&lt;/code> 即使對方不回 ICMP 也已經給了答案。在前述的 archboot 例子裡，網路設定確實有被複製進目標系統並由 systemd-networkd 接手，重開機後免再手動設——但這是該安裝程式的行為，不能假設每個安裝程式都這樣。把「首次開機驗網路」當成固定動作，比預設它一定會通安全。&lt;/p>
&lt;h2 id="套件鏡像">套件鏡像&lt;/h2>
&lt;p>鏡像源決定你從哪裡下載套件，挑地理上接近的那個。基礎系統加上一套桌面動輒上 GB，選對岸的鏡像跟選同城的鏡像，下載時間差好幾倍。安裝程式給的鏡像清單通常按國家排，往下捲找你所在地區的；找不到完全同國的，就退而求其次選同區域、且穩定的大型鏡像。&lt;/p>
&lt;p>這個選擇的另一個作用是順帶確認你裝的是哪個發行版分支。前述例子的鏡像清單全是 &lt;code>archlinuxarm.org&lt;/code>，這證實了 archboot 的 aarch64 ISO 裝出來的是 Arch Linux ARM（ARM 移植版），而不是 x86 的 Arch——同一條安裝路徑產出的是哪個分支，鏡像來源會洩漏給你看。&lt;/p>
&lt;h2 id="磁碟分割">磁碟分割&lt;/h2>
&lt;p>磁碟分割是整個安裝裡選項最多、也最不可逆的一段，但判斷軸只有一條：這台機器需不需要在分區層面做隔離。需要隔離的情境——多系統共存、加密、資料與系統分離以便重灌不丟資料——每多一個，分割就多一層結構。不需要的情境，多切一刀都只是增加「一邊爆一邊空」的風險。下面逐項拆解，但它們服務的是同一個判斷。&lt;/p>
&lt;h3 id="自動分割-vs-手動分割">自動分割 vs 手動分割&lt;/h3>
&lt;p>自動分割（清空整碟、安裝程式幫你建標準佈局）適用於整碟專屬、沒有要保留任何既有資料的機器。測試 VM 的磁碟是全新的、整碟給這個系統用，自動分割沒有任何代價，還省去手動算 EFI 大小、root 大小、對齊、格式化、掛載這一連串容易錯的步驟。&lt;/p>
&lt;p>手動分割的價值在你需要非標準佈局時才浮現：多系統共用某個分區、要留一塊不格式化的資料區、要套 LVM 或 LUKS。這些是真實主力機與伺服器會遇到的需求，但對一台「目標是驗證 dotfile 部署」的 VM 是純雜訊——它們屬於另一個主題，不該混進這次的安裝。判讀訊號很簡單：你說得出一個具體的隔離需求，才手動分割；說不出來，自動分割就是對的。&lt;/p>
&lt;h3 id="分區識別方式partuuid">分區識別方式（PARTUUID）&lt;/h3>
&lt;p>分區識別方式決定 &lt;code>fstab&lt;/code>（開機時決定哪個分區掛到哪的設定檔）跟 bootloader 怎麼指涉每個分區，在 GPT（現代 UEFI 機器的分區表格式）磁碟上選 PARTUUID。這個選擇的後果是「重開機後系統找不找得到自己的分區」。PARTUUID 綁在分區本身、跨重開機穩定，而且重新格式化檔案系統也不會變；相對地，檔案系統層級的 UUID 一重格就變，會讓 &lt;code>fstab&lt;/code> 失效，而 &lt;code>/dev/vda1&lt;/code> 那種 kernel 名稱會隨偵測順序浮動，最不穩。穩定性的排序是 PARTUUID 優於 FSUUID 優於 kernel 名稱，GPT 磁碟用最穩的那個（這三種識別方式的細節見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/partition-identification/" data-link-title="分區識別（PARTUUID / FSUUID）" data-link-desc="在 fstab 或 bootloader 設定要指定一個分區、不確定該用 PARTUUID、UUID 還是 /dev/sda1、或重格式化後系統開不了機時讀 — 分區的穩定識別方式">分區識別卡&lt;/a>）。&lt;/p>
&lt;h3 id="efi-分區的掛載點與大小">EFI 分區的掛載點與大小&lt;/h3>
&lt;p>EFI 系統分區（ESP）放開機載入器與 kernel，掛載點的選擇取決於這台機器是不是單一作業系統。把 ESP 掛在 &lt;code>/boot&lt;/code>（單系統佈局）讓 kernel 跟開機檔住在同一個分區、維護最單純；把 ESP 掛在 &lt;code>/efi&lt;/code>、kernel 另放（多系統佈局）是為了多個 OS 共用同一個 ESP 才需要的結構。單系統的機器選多系統佈局，只是憑空多一層目錄。&lt;/p>
&lt;p>ESP 大小在單系統佈局下要算進 kernel 與 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/initramfs/" data-link-title="initramfs" data-link-desc="看到 ESP 大小要算進 initramfs、或開機卡在掛載 root 之前、不知道 initramfs 是什麼時讀 — 開機初期掛真 root 之前的臨時根檔系統">initramfs&lt;/a>（開機初期把真正的 root 掛起來之前、用來載入驅動的小型臨時根檔系統）。一個 kernel 加上它的 initramfs（含 fallback）大約一兩百 MB，再加上 FAT32 ESP 約 260 MiB 的實務下限，512 MiB 是在下限之上留餘裕。會需要更大的情境是你要同時保留多個 kernel 版本——但單 kernel 的 VM 用不到，給太大只是浪費。&lt;/p></description><content:encoded><![CDATA[<p>Linux 安裝程式的每個選項都是一個會往後傳遞代價的決策。選錯的後果不會在當下浮現，而是在重開機進不了系統、磁碟某個分區先爆掉、或裝好的機器一重開就斷網時才出現。判斷一個選項該怎麼選，靠的是同一個問題：這台機器是用來幹嘛的。一台用完即丟的測試 VM、一台跑三年的主力機、一台對外服務的伺服器，同一個選項的正確答案可能完全不同。</p>
<p>這篇給每個關鍵選項一條判斷軸，而不是一份「照著點」的步驟。底下用一次具體的安裝當作貫穿例子：在 Apple Silicon 的 UTM 上用 archboot ISO 裝 Arch Linux ARM，目標是一台演練 dotfile 部署的測試 VM。例子是具體的，但判斷軸跨發行版通用——locale、分割、bootloader 這些抉擇在多數 Linux 安裝程式裡都會以類似形式出現。</p>
<p>怎麼建立 VM、燒錄並開機到安裝程式，是跟環境綁定的前置——UTM、VirtualBox、實體機各不相同——不在這條判讀軸的範圍；這篇從「安裝程式已經跑起來、開始問你選項」接手，給的是每個選項的判斷軸，不逐頁帶某個安裝程式的選單怎麼點。底下的指令與選單名稱以 archboot / Arch 呈現，換 Ubuntu（Subiquity 安裝程式）或 Fedora 時，選項的判斷軸一樣成立，但選單長相、套件組名稱、指令會不同。</p>
<h2 id="系統語系與時間">系統語系與時間</h2>
<p>系統語系決定的是錯誤訊息、log、系統工具輸出用哪種語言，不是你日常打字的語言。這兩件事容易混為一談。日常輸入中文是桌面層的字型與輸入法問題，跟系統 locale 無關；系統 locale 影響的是當某個服務崩潰、你在 <code>journalctl</code> 裡讀它吐出來的那行訊息時，那行字是英文還是被翻譯過。</p>
<p>把系統 locale 留在 <code>en_US.UTF-8</code> 的理由是可搜尋性。當你把一段錯誤訊息貼到搜尋引擎或問別人，英文訊息能對上絕大多數的文件、issue、Stack Overflow 答案；翻譯過的訊息往往一個結果都搜不到。這條判斷軸對伺服器、開發機、任何你預期會除錯的機器都成立。會選非英文 locale 的情境通常是給終端使用者的桌面，且該使用者不除錯——那是另一種機器。</p>
<p>時區的選擇影響 log 的時間戳跟排程任務的觸發時刻，挑你所在地即可。另一個相關的決策是「硬體時鐘存 UTC 還是本地時間」：選 UTC。Linux 慣例是硬體時鐘存 UTC、顯示時再換算成時區，這樣跨時區搬機器、或 NTP 校時都不會錯亂。會需要存本地時間的唯一常見情境是跟 Windows 雙開——Windows 預設把硬體時鐘當本地時間——而 VM 或純 Linux 機器沒有這個包袱。</p>
<h2 id="網路">網路</h2>
<p>安裝階段的網路設定要回答兩個層次：當下能不能連、以及這份設定會不會帶進裝好的系統。第一層通常很直覺——選到對的網卡、用 DHCP 讓它自動拿 IP。虛擬機的 NAT 網路會自動發 IP，所以選 DHCP、不要手動設 static，省去算網段的麻煩。</p>
<p>第二層是真正會咬人的地方：安裝程式裡設好的網路，不保證會出現在重開機後的系統裡。這是 VM 裝 Linux 常見的斷網點——安裝時明明能上網裝套件，重開機後卻連不出去，因為安裝環境的網路設定沒被複製到目標系統。判讀方式是裝好首次開機後立刻驗證：看網卡有沒有拿到 IP、能不能解析一個域名。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">ip -brief a          <span class="c1"># 網卡有沒有 IP、狀態是不是 UP</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">ping -c <span class="m">3</span> archlinux.org   <span class="c1"># 解析成功就證明對外連線 + DNS 都通</span></span></span></code></pre></div><p>DNS 能把域名解析成 IP，本身就證明對外連線是通的（DNS 查詢就是一次網路往返），所以這條 <code>ping</code> 即使對方不回 ICMP 也已經給了答案。在前述的 archboot 例子裡，網路設定確實有被複製進目標系統並由 systemd-networkd 接手，重開機後免再手動設——但這是該安裝程式的行為，不能假設每個安裝程式都這樣。把「首次開機驗網路」當成固定動作，比預設它一定會通安全。</p>
<h2 id="套件鏡像">套件鏡像</h2>
<p>鏡像源決定你從哪裡下載套件，挑地理上接近的那個。基礎系統加上一套桌面動輒上 GB，選對岸的鏡像跟選同城的鏡像，下載時間差好幾倍。安裝程式給的鏡像清單通常按國家排，往下捲找你所在地區的；找不到完全同國的，就退而求其次選同區域、且穩定的大型鏡像。</p>
<p>這個選擇的另一個作用是順帶確認你裝的是哪個發行版分支。前述例子的鏡像清單全是 <code>archlinuxarm.org</code>，這證實了 archboot 的 aarch64 ISO 裝出來的是 Arch Linux ARM（ARM 移植版），而不是 x86 的 Arch——同一條安裝路徑產出的是哪個分支，鏡像來源會洩漏給你看。</p>
<h2 id="磁碟分割">磁碟分割</h2>
<p>磁碟分割是整個安裝裡選項最多、也最不可逆的一段，但判斷軸只有一條：這台機器需不需要在分區層面做隔離。需要隔離的情境——多系統共存、加密、資料與系統分離以便重灌不丟資料——每多一個，分割就多一層結構。不需要的情境，多切一刀都只是增加「一邊爆一邊空」的風險。下面逐項拆解，但它們服務的是同一個判斷。</p>
<h3 id="自動分割-vs-手動分割">自動分割 vs 手動分割</h3>
<p>自動分割（清空整碟、安裝程式幫你建標準佈局）適用於整碟專屬、沒有要保留任何既有資料的機器。測試 VM 的磁碟是全新的、整碟給這個系統用，自動分割沒有任何代價，還省去手動算 EFI 大小、root 大小、對齊、格式化、掛載這一連串容易錯的步驟。</p>
<p>手動分割的價值在你需要非標準佈局時才浮現：多系統共用某個分區、要留一塊不格式化的資料區、要套 LVM 或 LUKS。這些是真實主力機與伺服器會遇到的需求，但對一台「目標是驗證 dotfile 部署」的 VM 是純雜訊——它們屬於另一個主題，不該混進這次的安裝。判讀訊號很簡單：你說得出一個具體的隔離需求，才手動分割；說不出來，自動分割就是對的。</p>
<h3 id="分區識別方式partuuid">分區識別方式（PARTUUID）</h3>
<p>分區識別方式決定 <code>fstab</code>（開機時決定哪個分區掛到哪的設定檔）跟 bootloader 怎麼指涉每個分區，在 GPT（現代 UEFI 機器的分區表格式）磁碟上選 PARTUUID。這個選擇的後果是「重開機後系統找不找得到自己的分區」。PARTUUID 綁在分區本身、跨重開機穩定，而且重新格式化檔案系統也不會變；相對地，檔案系統層級的 UUID 一重格就變，會讓 <code>fstab</code> 失效，而 <code>/dev/vda1</code> 那種 kernel 名稱會隨偵測順序浮動，最不穩。穩定性的排序是 PARTUUID 優於 FSUUID 優於 kernel 名稱，GPT 磁碟用最穩的那個（這三種識別方式的細節見 <a href="/blog/linux/dotfile/knowledge-cards/partition-identification/" data-link-title="分區識別（PARTUUID / FSUUID）" data-link-desc="在 fstab 或 bootloader 設定要指定一個分區、不確定該用 PARTUUID、UUID 還是 /dev/sda1、或重格式化後系統開不了機時讀 — 分區的穩定識別方式">分區識別卡</a>）。</p>
<h3 id="efi-分區的掛載點與大小">EFI 分區的掛載點與大小</h3>
<p>EFI 系統分區（ESP）放開機載入器與 kernel，掛載點的選擇取決於這台機器是不是單一作業系統。把 ESP 掛在 <code>/boot</code>（單系統佈局）讓 kernel 跟開機檔住在同一個分區、維護最單純；把 ESP 掛在 <code>/efi</code>、kernel 另放（多系統佈局）是為了多個 OS 共用同一個 ESP 才需要的結構。單系統的機器選多系統佈局，只是憑空多一層目錄。</p>
<p>ESP 大小在單系統佈局下要算進 kernel 與 <a href="/blog/linux/dotfile/knowledge-cards/initramfs/" data-link-title="initramfs" data-link-desc="看到 ESP 大小要算進 initramfs、或開機卡在掛載 root 之前、不知道 initramfs 是什麼時讀 — 開機初期掛真 root 之前的臨時根檔系統">initramfs</a>（開機初期把真正的 root 掛起來之前、用來載入驅動的小型臨時根檔系統）。一個 kernel 加上它的 initramfs（含 fallback）大約一兩百 MB，再加上 FAT32 ESP 約 260 MiB 的實務下限，512 MiB 是在下限之上留餘裕。會需要更大的情境是你要同時保留多個 kernel 版本——但單 kernel 的 VM 用不到，給太大只是浪費。</p>
<h3 id="swap">Swap</h3>
<p>Swap 是記憶體不足時的安全墊，大小取決於這台機器的記憶體壓力型態，不是一個固定公式。對一台只有 4 GB RAM、且要在上面從原始碼編譯套件的 VM，編譯瞬間的記憶體尖峰很容易把實體記憶體吃爆、觸發 OOM 把進程殺掉。給 2 GB swap 當緩衝，擋住這種尖峰、避免安裝跑到一半被中斷。</p>
<p>swap 分區的磁碟成本不是一次付清的。<code>mkswap</code> 只寫一個 header，實際沒被換出的頁不會佔用宿主磁碟空間（在稀疏配置的虛擬磁碟上尤其明顯），用到才寫。所以「為了保險多給一點 swap」的代價，比直覺以為的小。判讀軸是看工作負載：會編譯、會跑吃記憶體的服務、RAM 又緊，就給足 swap；純文書、RAM 寬裕，小一點或不給都行。</p>
<p>swap 還有形態的選擇，這裡用分區 swap 是因為在安裝程式階段一併切好最省事，不代表它比另兩種好。swapfile（一個檔案，事後可隨時調大小或移除）避開了「分割最不可逆」的痛點；zram（壓縮記憶體 swap，不碰磁碟）對低 RAM 加編譯尖峰正是設計情境，現代發行版很多預設用它。換句話說，2 GB 這個量是看編譯尖峰定的，而「切成分區」只是配合安裝當下一次到位——若你跳過安裝期的 swap、事後用 swapfile 或 zram 補，是等價的可逆路徑。</p>
<h3 id="檔案系統">檔案系統</h3>
<p>檔案系統的選擇是在「簡單可靠」與「進階功能」之間取捨，預設往簡單那邊靠。<code>ext4</code> 簡單、穩、在各平台行為一致、修復工具成熟，對一台只要求「可靠地存取檔案」的機器是零驚喜的選擇。<code>btrfs</code> 提供快照、subvolume、透明壓縮，但代價是要規劃 subvolume 佈局、還要理解它寫時複製（CoW）的一些行為差異；這些功能在你會用快照回滾的主力機上很有價值，在演練 VM 上是雜訊。<code>xfs</code> 同樣穩定，但對這類用途相對 ext4 沒有決定性優勢。更特定的 <code>zfs</code>、<code>f2fs</code> 不在一般 VM 的考慮範圍——zfs 在 Arch / ARM 上是非主線 kernel 的 out-of-tree 模組、授權與維護成本高，f2fs 是為快閃裝置設計、VM 用不到。</p>
<p>判讀軸是你會不會用到進階功能。會固定用快照回滾系統狀態——選 btrfs 並接受它的佈局複雜度；只是要一個可靠的檔案系統——ext4。快照這類能力跟下面的獨立 <code>/home</code> 一樣，是真實機器的儲存規劃主題，值得另外深入，但別為了「聽起來比較強」就把它的複雜度帶進一台用完即丟的機器。</p>
<h3 id="獨立-home-vs-單一-root">獨立 /home vs 單一 root</h3>
<p>獨立 <code>/home</code> 分區的價值是「重灌系統不丟個人資料」，這是主力機的需求，不是每台機器都需要。把 <code>/home</code> 切成獨立分區，重裝系統時可以只格式化 root、保留 <code>/home</code> 裡的設定與檔案。一台用完即丟的演練 VM 沒有這個需求——它的整個生命週期就是裝起來、驗證、丟掉。如果你想跨多次實驗保留狀態，VM 情境更貼切的手段是宿主層的快照或共享資料夾，而不是在 guest 裡切獨立 <code>/home</code>——後者解決的是「重灌 OS 保資料」，不是「保留實驗狀態」。</p>
<p>把空間切兩半的隱性代價是失衡風險。假設總共十幾 GB，照預設切一塊給 <code>/</code>、剩下給 <code>/home</code>，所有系統套件（桌面全套依賴都裝在 <code>/usr</code>、算 <code>/</code>）擠在前者、容易先滿，而 <code>/home</code> 那邊空著——典型的一邊爆一邊空。把全部空間當一個池子用的單一 root 佈局，不會人為卡死任一邊，對不需要「重灌保資料」的機器最不容易出事。</p>
<h2 id="bootloader">Bootloader</h2>
<p>開機載入器決定韌體怎麼找到並載入 kernel，在虛擬機上選 GRUB 而非直接用 EFISTUB，理由是可靠性（韌體到 kernel 的整條交棒過程見 <a href="/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/" data-link-title="UEFI 開機鏈" data-link-desc="在 bootloader 選型（GRUB / EFISTUB / systemd-boot）卡住、或機器重開後找不到 kernel、需要理解韌體怎麼找到並載入系統時讀 — 韌體到 kernel 的交棒過程">UEFI 開機鏈卡</a>）。EFISTUB 讓 UEFI 韌體直接載入 kernel、不經過獨立的 bootloader，最精簡，但它完全依賴寫進 UEFI NVRAM（韌體用來存開機項的非揮發記憶體）的開機項。問題在於 QEMU 系的虛擬機（UTM 底層即是）對 EFI 變數的儲存有時不穩，一旦 NVRAM 裡的開機項掉了，韌體就找不到 kernel、機器開不了——這在 VM 環境是會實際踩到的坑。</p>
<p>GRUB 的容錯來自它（以 removable 模式安裝時）多寫了一份。除了 NVRAM 開機項，<code>grub-install --removable</code> 會在 ESP 的標準 fallback 路徑（aarch64 是 <code>\EFI\BOOT\BOOTAA64.EFI</code>）也放一份，就算 NVRAM 開機項丟了，韌體仍會從 fallback 路徑找到 GRUB；VM 環境的安裝程式通常以這個模式裝 GRUB，正是看上這層保險。它還附帶一個開機選單，當 kernel 或 initramfs 出問題時，可以進選單救援、加開機參數除錯——演練時的容錯空間大很多。</p>
<p>判讀軸是環境的 NVRAM 可靠度。在 NVRAM 穩定的實體機上，EFISTUB 的極簡是漂亮的選擇；在 NVRAM 可能不穩的 VM 上，可靠性優先，GRUB 的「多寫一份 fallback + 救援選單」更穩妥。GRUB 不是唯一的可靠選擇——systemd-boot 同樣是有開機選單、能裝到 fallback 路徑的獨立 bootloader，又比 GRUB 輕，在 VM / 單系統同樣站得住；這裡落在 GRUB 是因為 archboot 安裝程式預設以 removable 模式裝它，不是 GRUB 獨佔可靠性。GRUB 自己的設定檔在 VM 上用預設值即可，不需要額外的 kernel 參數。</p>
<h2 id="下一步">下一步</h2>
<p>選項選完、系統裝好、重開機進得去之後，先別急著開始用——「裝好了」跟「能用了」之間往往還缺一截：這台最小系統不一定有你需要的基本工具。<a href="../minimal-install-verify/">最小安裝後的工具驗證與補足</a> 就是在補那一截。</p>
<p>從安裝到桌面就緒的完整依賴順序，見 <a href="/blog/linux/dotfile/00-dotfile-mindset/setup-order-guide/" data-link-title="環境建置的操作順序" data-link-desc="第一次從零建立 Linux 或 macOS 開發環境、不確定先做什麼後做什麼時讀 — 依賴順序路線圖，每一步附對應模組連結">模組零的操作順序指引</a>；本篇是它「安裝作業系統」那一步的展開。</p>
]]></content:encoded></item><item><title>在 Hyprland 加圖形檔案管理員：依賴足跡與桌面環境耦合</title><link>https://tarrragon.github.io/blog/linux/tools/gui/gui-file-manager-dependencies/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/tools/gui/gui-file-manager-dependencies/</guid><description>&lt;p>在一個最小化的 Hyprland 環境加一個圖形應用，真正的成本不是那個 app 本身，是它拖進來的相依樹。Hyprland 這類 window manager 刻意不預設桌面環境，所以你手上是一台「只有合成器、沒有 GNOME/KDE/Cinnamon 那層服務」的機器。這時候裝一個看似單純的圖形檔案管理員，不同實作拖進來的東西可以差一個數量級——因為有些檔案管理員假設某個桌面環境的服務就在旁邊，有些則刻意做成桌面無關的獨立程式。&lt;/p>
&lt;p>這篇用「圖形檔案管理員」當具體案例，但判讀方式適用於任何你想加進最小環境的桌面 app：先看它的相依樹拖進什麼，再決定值不值得。&lt;/p>
&lt;h2 id="三種實作的實測相依">三種實作的實測相依&lt;/h2>
&lt;p>同樣是「有側欄、有選單、能瀏覽掛載裝置」的圖形檔案管理員，三個主流實作在一台已有 GTK3 的 Arch ARM 機器上，各自會&lt;strong>新裝&lt;/strong>的套件數量如下（&lt;code>pacman -S --needed --print&lt;/code> 實測）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>檔案管理員&lt;/th>
 &lt;th>出身&lt;/th>
 &lt;th>新裝套件數&lt;/th>
 &lt;th>會拖進的關鍵相依&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Thunar&lt;/td>
 &lt;td>XFCE&lt;/td>
 &lt;td>8&lt;/td>
 &lt;td>xfce4 基礎庫（exo、libxfce4util/ui、xfconf、startup-notification、libgtop）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>PCManFM-Qt&lt;/td>
 &lt;td>LXQt&lt;/td>
 &lt;td>7&lt;/td>
 &lt;td>libfm-qt、layer-shell-qt、menu-cache、lxqt-menu-data&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Nemo&lt;/td>
 &lt;td>Cinnamon&lt;/td>
 &lt;td>36&lt;/td>
 &lt;td>cinnamon-desktop、xapp、整套 gvfs + udisks2（libblockdev 主 + 8 子模組、mdadm、parted…）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>差異的來源不是「檔案管理員本身多大」，Thunar 跟 Nemo 的主程式都在 7–10 MB 量級。差的是後面那條相依鏈。&lt;/p>
&lt;h3 id="thunar-與-pcmanfm-qt桌面無關的獨立程式">Thunar 與 PCManFM-Qt：桌面無關的獨立程式&lt;/h3>
&lt;p>Thunar 與 PCManFM-Qt 都是刻意做成「不依賴完整桌面環境」的檔案管理員。Thunar 雖然出身 XFCE，但它拖進的 8 個套件是 XFCE 的&lt;strong>基礎函式庫&lt;/strong>（設定系統 xfconf、工具庫 libxfce4util、UI 庫 libxfce4ui），不是 XFCE 桌面本身——你不會因此裝到 XFCE 的面板、視窗管理器或 session。PCManFM-Qt 走 Qt 棧，帶的 &lt;code>layer-shell-qt&lt;/code> 反而是 Wayland 原生整合的加分項。這兩個裝下去，機器還是那台只有 Hyprland 的機器，只是多了一個能開的檔案管理員。&lt;/p>
&lt;h3 id="nemo為-cinnamon-而生假設-cinnamon-在旁邊">Nemo：為 Cinnamon 而生，假設 Cinnamon 在旁邊&lt;/h3>
&lt;p>Nemo 是 Cinnamon 桌面的檔案管理員，它的相依反映了這個出身：&lt;code>cinnamon-desktop&lt;/code> 提供背景與顯示設定的整合、&lt;code>xapp&lt;/code> 是 Cinnamon 系列跨桌面的整合層。即使你只想要「開一個視窗看檔案」，這些桌面元件也會一起裝上，因為 Nemo 在程式碼層面就假設它們存在。這不是 Nemo 寫得差，是它本來就不是設計給「裸 window manager」用的——它預期自己跑在 Cinnamon session 裡。&lt;/p>
&lt;p>Nemo 那 36 個套件裡，還有一大塊來自它把 &lt;code>gvfs&lt;/code> 列成硬相依（下一節說明 gvfs 是什麼），而 gvfs 又拖進整套磁碟管理棧（udisks2、libblockdev 的 8 個子模組、mdadm、parted、volume_key）。所以 Nemo 的相依樹是「Cinnamon 桌面元件」加「完整磁碟/檔案系統管理」兩層疊起來的結果。&lt;/p>
&lt;h2 id="gvfs側欄那些功能不是檔案管理員自己做的">gvfs：側欄那些功能不是檔案管理員自己做的&lt;/h2>
&lt;p>截圖裡檔案管理員側欄常見的「Devices（掛載裝置）」「Network（瀏覽網路芳鄰）」「Trash（垃圾桶）」，多半不是檔案管理員自己實作的，是 &lt;strong>gvfs（GNOME Virtual File System）&lt;/strong> 這個後端提供的。gvfs 用一層虛擬檔案系統把「掛載 USB 隨身碟」「連 SMB 網路分享」「把檔案丟垃圾桶」這些操作抽象成統一介面，讓檔案管理員不必自己處理每一種裝置與協定。&lt;/p>
&lt;p>這帶出一個重要判讀：&lt;strong>輕量不是免費的，當功能對等時，相依會靠攏。&lt;/strong> Thunar 與 PCManFM-Qt 把 gvfs 列成 optional dependency——不裝也能開檔案管理員，但側欄就沒有掛載、垃圾桶、網路那些功能。要讓輕量檔案管理員有截圖裡那種完整側欄，你得自己補 gvfs，而補上 gvfs 就會連帶拖進它的相依（udisks2、polkit、fuse3、libsecret 等）。Nemo 把 gvfs 設成硬相依，只是把這個選擇替你做了。&lt;/p>
&lt;p>所以公平的比較不是「Thunar 8 個 vs Nemo 36 個」，而是「Thunar + gvfs + 縮圖 vs Nemo」。補齊功能後，Thunar 這條路線仍然省下的是 Nemo 獨有的那層——&lt;code>cinnamon-desktop&lt;/code>、&lt;code>xapp&lt;/code>、&lt;code>xapp-symbolic-icons&lt;/code> 這些桌面環境耦合元件。那層，才是「為了一個檔案管理員裝半個 Cinnamon」真正可以省掉的部分。&lt;/p>
&lt;h2 id="tumbler縮圖也是一個額外套件">tumbler：縮圖也是一個額外套件&lt;/h2>
&lt;p>檔案管理員顯示圖片/影片縮圖，同樣不是內建的，靠的是縮圖服務。Thunar 家族用 &lt;code>tumbler&lt;/code>，影片縮圖再另外需要 &lt;code>ffmpegthumbnailer&lt;/code>。這是「一個功能對應一個額外套件」的又一個例子——最小環境裡，縮圖、掛載、網路瀏覽每一項都是你明確選擇要不要付相依成本的功能，而不是預設就有。&lt;/p>
&lt;h2 id="wayland--hyprland-下的注意事項">Wayland / Hyprland 下的注意事項&lt;/h2>
&lt;p>這些檔案管理員多數是 X11 時代的 GTK/Qt 程式，在 Wayland 下會透過 XWayland 或原生 Wayland 後端執行。PCManFM-Qt 帶的 &lt;code>layer-shell-qt&lt;/code> 是 Wayland 的 layer-shell 整合；GTK 的 Thunar/Nemo 在 Wayland 下一般走 GTK 自己的 Wayland 後端。開啟/儲存檔案對話框、拖放、縮圖預覽在裸 Hyprland（沒有完整 portal 服務）下的實際行為，取決於有沒有裝 &lt;code>xdg-desktop-portal&lt;/code> 與對應的後端。&lt;/p></description><content:encoded><![CDATA[<p>在一個最小化的 Hyprland 環境加一個圖形應用，真正的成本不是那個 app 本身，是它拖進來的相依樹。Hyprland 這類 window manager 刻意不預設桌面環境，所以你手上是一台「只有合成器、沒有 GNOME/KDE/Cinnamon 那層服務」的機器。這時候裝一個看似單純的圖形檔案管理員，不同實作拖進來的東西可以差一個數量級——因為有些檔案管理員假設某個桌面環境的服務就在旁邊，有些則刻意做成桌面無關的獨立程式。</p>
<p>這篇用「圖形檔案管理員」當具體案例，但判讀方式適用於任何你想加進最小環境的桌面 app：先看它的相依樹拖進什麼，再決定值不值得。</p>
<h2 id="三種實作的實測相依">三種實作的實測相依</h2>
<p>同樣是「有側欄、有選單、能瀏覽掛載裝置」的圖形檔案管理員，三個主流實作在一台已有 GTK3 的 Arch ARM 機器上，各自會<strong>新裝</strong>的套件數量如下（<code>pacman -S --needed --print</code> 實測）：</p>
<table>
  <thead>
      <tr>
          <th>檔案管理員</th>
          <th>出身</th>
          <th>新裝套件數</th>
          <th>會拖進的關鍵相依</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Thunar</td>
          <td>XFCE</td>
          <td>8</td>
          <td>xfce4 基礎庫（exo、libxfce4util/ui、xfconf、startup-notification、libgtop）</td>
      </tr>
      <tr>
          <td>PCManFM-Qt</td>
          <td>LXQt</td>
          <td>7</td>
          <td>libfm-qt、layer-shell-qt、menu-cache、lxqt-menu-data</td>
      </tr>
      <tr>
          <td>Nemo</td>
          <td>Cinnamon</td>
          <td>36</td>
          <td>cinnamon-desktop、xapp、整套 gvfs + udisks2（libblockdev 主 + 8 子模組、mdadm、parted…）</td>
      </tr>
  </tbody>
</table>
<p>差異的來源不是「檔案管理員本身多大」，Thunar 跟 Nemo 的主程式都在 7–10 MB 量級。差的是後面那條相依鏈。</p>
<h3 id="thunar-與-pcmanfm-qt桌面無關的獨立程式">Thunar 與 PCManFM-Qt：桌面無關的獨立程式</h3>
<p>Thunar 與 PCManFM-Qt 都是刻意做成「不依賴完整桌面環境」的檔案管理員。Thunar 雖然出身 XFCE，但它拖進的 8 個套件是 XFCE 的<strong>基礎函式庫</strong>（設定系統 xfconf、工具庫 libxfce4util、UI 庫 libxfce4ui），不是 XFCE 桌面本身——你不會因此裝到 XFCE 的面板、視窗管理器或 session。PCManFM-Qt 走 Qt 棧，帶的 <code>layer-shell-qt</code> 反而是 Wayland 原生整合的加分項。這兩個裝下去，機器還是那台只有 Hyprland 的機器，只是多了一個能開的檔案管理員。</p>
<h3 id="nemo為-cinnamon-而生假設-cinnamon-在旁邊">Nemo：為 Cinnamon 而生，假設 Cinnamon 在旁邊</h3>
<p>Nemo 是 Cinnamon 桌面的檔案管理員，它的相依反映了這個出身：<code>cinnamon-desktop</code> 提供背景與顯示設定的整合、<code>xapp</code> 是 Cinnamon 系列跨桌面的整合層。即使你只想要「開一個視窗看檔案」，這些桌面元件也會一起裝上，因為 Nemo 在程式碼層面就假設它們存在。這不是 Nemo 寫得差，是它本來就不是設計給「裸 window manager」用的——它預期自己跑在 Cinnamon session 裡。</p>
<p>Nemo 那 36 個套件裡，還有一大塊來自它把 <code>gvfs</code> 列成硬相依（下一節說明 gvfs 是什麼），而 gvfs 又拖進整套磁碟管理棧（udisks2、libblockdev 的 8 個子模組、mdadm、parted、volume_key）。所以 Nemo 的相依樹是「Cinnamon 桌面元件」加「完整磁碟/檔案系統管理」兩層疊起來的結果。</p>
<h2 id="gvfs側欄那些功能不是檔案管理員自己做的">gvfs：側欄那些功能不是檔案管理員自己做的</h2>
<p>截圖裡檔案管理員側欄常見的「Devices（掛載裝置）」「Network（瀏覽網路芳鄰）」「Trash（垃圾桶）」，多半不是檔案管理員自己實作的，是 <strong>gvfs（GNOME Virtual File System）</strong> 這個後端提供的。gvfs 用一層虛擬檔案系統把「掛載 USB 隨身碟」「連 SMB 網路分享」「把檔案丟垃圾桶」這些操作抽象成統一介面，讓檔案管理員不必自己處理每一種裝置與協定。</p>
<p>這帶出一個重要判讀：<strong>輕量不是免費的，當功能對等時，相依會靠攏。</strong> Thunar 與 PCManFM-Qt 把 gvfs 列成 optional dependency——不裝也能開檔案管理員，但側欄就沒有掛載、垃圾桶、網路那些功能。要讓輕量檔案管理員有截圖裡那種完整側欄，你得自己補 gvfs，而補上 gvfs 就會連帶拖進它的相依（udisks2、polkit、fuse3、libsecret 等）。Nemo 把 gvfs 設成硬相依，只是把這個選擇替你做了。</p>
<p>所以公平的比較不是「Thunar 8 個 vs Nemo 36 個」，而是「Thunar + gvfs + 縮圖 vs Nemo」。補齊功能後，Thunar 這條路線仍然省下的是 Nemo 獨有的那層——<code>cinnamon-desktop</code>、<code>xapp</code>、<code>xapp-symbolic-icons</code> 這些桌面環境耦合元件。那層，才是「為了一個檔案管理員裝半個 Cinnamon」真正可以省掉的部分。</p>
<h2 id="tumbler縮圖也是一個額外套件">tumbler：縮圖也是一個額外套件</h2>
<p>檔案管理員顯示圖片/影片縮圖，同樣不是內建的，靠的是縮圖服務。Thunar 家族用 <code>tumbler</code>，影片縮圖再另外需要 <code>ffmpegthumbnailer</code>。這是「一個功能對應一個額外套件」的又一個例子——最小環境裡，縮圖、掛載、網路瀏覽每一項都是你明確選擇要不要付相依成本的功能，而不是預設就有。</p>
<h2 id="wayland--hyprland-下的注意事項">Wayland / Hyprland 下的注意事項</h2>
<p>這些檔案管理員多數是 X11 時代的 GTK/Qt 程式，在 Wayland 下會透過 XWayland 或原生 Wayland 後端執行。PCManFM-Qt 帶的 <code>layer-shell-qt</code> 是 Wayland 的 layer-shell 整合；GTK 的 Thunar/Nemo 在 Wayland 下一般走 GTK 自己的 Wayland 後端。開啟/儲存檔案對話框、拖放、縮圖預覽在裸 Hyprland（沒有完整 portal 服務）下的實際行為，取決於有沒有裝 <code>xdg-desktop-portal</code> 與對應的後端。</p>
<blockquote>
<p><strong>[待實機驗證]</strong> 以下行為尚未在本系列的 Hyprland 實機環境確認，先標記待驗證：(1) Thunar 補上 gvfs 後，側欄的 Devices/Network/Trash 是否如預期出現並可用；(2) tumbler + ffmpegthumbnailer 的縮圖在 Wayland 下是否正常產生；(3) 三者在裸 Hyprland（無完整桌面 portal）下的檔案對話框與拖放行為；(4) Nemo 在沒有 Cinnamon session 的情況下，桌面圖示、設定整合等功能是否失效或報錯。這些是「相依裝了之後實際好不好用」的問題，相依數量本身（上表）已是實測確定值。</p></blockquote>
<h2 id="風險與注意事項">風險與注意事項</h2>
<p><strong>移除後的孤兒套件</strong>：裝了 Nemo 再反悔移除時，<code>cinnamon-desktop</code>、<code>xapp</code> 那一票被拖進來的相依會變成沒人依賴的孤兒（<code>pacman -Qtd</code> 可列出）。用 <code>pacman -Rns nemo</code> 移除時帶走遞迴相依，或定期清孤兒，否則那半個 Cinnamon 會留在系統裡。輕量檔案管理員因為拖進的東西少，這個問題也小。</p>
<p><strong>桌面環境服務未跑的副作用</strong>：把為某個桌面環境寫的 app 裝進裸 window manager，它預期的那些服務不在時，部分功能可能靜默失效或在啟動時報錯。這類問題不會在相依解析階段出現——套件裝得起來，是執行時才發現某個整合功能沒作用。（Nemo 在無 Cinnamon 下的具體表現，見上方待驗證項。）</p>
<p><strong>選型判準</strong>：最小化的 Hyprland 想要一個圖形檔案管理員，優先考慮桌面無關的 Thunar 或 PCManFM-Qt；需要截圖那種完整側欄功能時，明確補上 <code>gvfs</code>（掛載/垃圾桶/網路）與 <code>tumbler</code>（縮圖），把相依成本花在你真的要用的功能上。以 Thunar 為例，完整一套是 <code>pacman -S thunar gvfs tumbler ffmpegthumbnailer</code>（<code>gvfs</code> 給掛載/垃圾桶/網路、<code>tumbler</code> + <code>ffmpegthumbnailer</code> 給圖片與影片縮圖）。除非你本來就跑 Cinnamon，否則不建議為了單一檔案管理員把 Nemo 的整套桌面元件裝進來——那是付了桌面環境耦合的代價，卻沒用到那個桌面環境。</p>
<h2 id="待實機驗證清單">待實機驗證清單</h2>
<p>這篇的相依數量與相依樹是實測確定的；以下「裝了之後實際體驗」的部分待在 Hyprland 實機補驗證：</p>
<ul>
<li>Thunar + gvfs + tumbler + ffmpegthumbnailer 的完整側欄與縮圖行為</li>
<li>三種檔案管理員在裸 Hyprland（XWayland vs 原生 Wayland、portal 有無）下的差異</li>
<li>Nemo 脫離 Cinnamon session 的功能缺損範圍</li>
<li>加進 <code>packages-arch.txt</code> 後，bootstrap 一鍵安裝這條路線的實際落地結果</li>
</ul>
<h2 id="為什麼拿-nemo-當重的代表">為什麼拿 Nemo 當「重」的代表</h2>
<p>上表用 Nemo 當桌面環境耦合的代表，是因為它把耦合展示得最乾淨——<code>cinnamon-desktop</code> + <code>xapp</code> 那層桌面元件加上 gvfs 硬相依，剛好對照出 Thunar / PCManFM-Qt 省掉的是什麼。但它不是最重的：GNOME 的 Nautilus（<code>nautilus</code>）與 KDE 的 Dolphin（<code>dolphin</code>）是更 canonical 的「裝檔案管理員就拖進半個桌面」例子。Nautilus 深度綁 GNOME 的 GTK4 / libadwaita / tracker 索引棧，在非 GNOME 環境裝它會拉進一串 GNOME 平台庫；Dolphin 綁 KDE 的 Frameworks（KIO、Baloo 等），在非 KDE 環境同理。判讀方式跟 Nemo 一節相同：檔案管理員的相依樹反映它預期跑在哪個桌面裡。真要在裸 Hyprland 上驗，<code>pacman -S --needed --print nautilus</code> / <code>dolphin</code> 會列出各自那串平台庫——數量通常比 Nemo 更可觀（實測 Nautilus 72、Dolphin 91，約 Nemo 的 2 到 2.5 倍）。</p>
<h2 id="零相依對照純終端機檔案管理員">零相依對照：純終端機檔案管理員</h2>
<p>如果你在意的正是相依足跡，還有一條完全繞開圖形棧的路：終端機檔案管理員（<code>yazi</code> / <code>lf</code> / <code>nnn</code> / <code>ranger</code>）在終端機裡跑，不需要 GTK / Qt / gvfs / 桌面環境那一整套。<code>yazi</code>（Rust、內建預覽與非同步 I/O）與 <code>lf</code>、<code>nnn</code>（C、極小）在裸 Hyprland 或純 SSH 環境下都是近乎零額外相依的選擇——你已經有終端機，它們就能跑。代價是純鍵盤操作、沒有圖形檔案管理員那種拖放與縮圖牆。這是「圖形 vs 終端機」的取捨，不是同一條路上的輕重之分：要滑鼠拖放、縮圖預覽走圖形檔案管理員；要零相依、鍵盤流、SSH 下也能用走 TUI。TUI 檔案管理員的比較見 <a href="../../cli/file-manager-tuis/">CLI 環境工具的檔案管理器 TUI</a>。</p>
<h2 id="下一步">下一步</h2>
<ul>
<li>這篇談的是「加桌面 app 時怎麼判讀相依成本」，套件清單本身怎麼設計、怎麼被 bootstrap 一鍵安裝，見 <a href="/blog/linux/dotfile/08-sync-bootstrap/bootstrap-script-packages/" data-link-title="Bootstrap Script 與套件清單管理" data-link-desc="寫 dotfile 的 install script、或整理「這台機器裝了什麼」的套件清單時回來讀">模組八：Bootstrap script 與套件清單</a>。</li>
<li>Hyprland 本體與配套工具的安裝，見 <a href="/blog/linux/dotfile/05-hyprland-config/hyprland-installation/" data-link-title="Hyprland 安裝與環境建置" data-link-desc="要在 Arch Linux 上從零安裝 Hyprland 桌面環境時回來讀">模組五：安裝與環境建置</a>。</li>
<li>這台 Hyprland 是在 VM 上建起來測的，VM 能測什麼、什麼要留到實機，見 <a href="/blog/linux/dotfile/05-hyprland-config/hyprland-vm-setup/" data-link-title="Hyprland VM 環境設定與測試矩陣" data-link-desc="要在 VM 裡測試 Hyprland 配置、或判斷某個設定該在 VM 還是實機驗證時回來讀">VM 環境設定與測試矩陣</a>。</li>
</ul>
]]></content:encoded></item><item><title>常見故障場景與恢復操作</title><link>https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/common-failures-recovery/</link><pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/common-failures-recovery/</guid><description>&lt;p>這篇按故障場景組織，每個場景列出症狀、原因、恢復步驟和預防措施。出問題時按症狀找到對應場景，照步驟操作。&lt;/p>
&lt;h2 id="場景一hyprland-compositor-crash">場景一：Hyprland compositor crash&lt;/h2>
&lt;p>&lt;strong>症狀&lt;/strong>：所有視窗同時消失，螢幕變黑或回到 TTY 登入畫面。滑鼠鍵盤有反應（可以切 TTY），但沒有桌面。&lt;/p>
&lt;p>&lt;strong>原因&lt;/strong>：Compositor process 遇到 fatal error 被 kernel 終止。常見觸發條件包括 plugin 相容性問題、特定 Wayland 協議操作觸發的 bug、GPU driver 回傳異常狀態。&lt;/p>
&lt;p>&lt;strong>恢復步驟&lt;/strong>：&lt;/p>
&lt;p>注意：以下步驟中 &lt;code>killall Hyprland&lt;/code> 或重啟 Hyprland 會終止所有由 compositor 管理的視窗，未存檔的工作會遺失。如果可能，先透過 TTY 或 SSH 嘗試存檔（如 &lt;code>kill -USR1 &amp;lt;pid&amp;gt;&lt;/code> 對支援的應用程式觸發存檔）。&lt;/p>
&lt;ol>
&lt;li>&lt;code>Ctrl+Alt+F2&lt;/code> 切到 TTY2&lt;/li>
&lt;li>用你的帳號登入&lt;/li>
&lt;li>檢查 Hyprland 的最後錯誤訊息：&lt;/li>
&lt;/ol>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 通用方式（不管 Hyprland 怎麼啟動都有效）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">journalctl -b &lt;span class="p">|&lt;/span> grep -i hypr &lt;span class="p">|&lt;/span> tail -30
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 如果 Hyprland 是 systemd user unit，可以更精準地查：&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">journalctl --user -u hyprland -n &lt;span class="m">50&lt;/span> --no-pager&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="4">
&lt;li>重新啟動 Hyprland：&lt;/li>
&lt;/ol>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">Hyprland&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="5">
&lt;li>如果反覆 crash，檢查最近改過的 config：&lt;/li>
&lt;/ol>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ~/.config/hypr
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">git diff &lt;span class="c1"># 如果 dotfile 有版控&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>預防&lt;/strong>：config 改動後用 &lt;code>hyprctl reload&lt;/code> 測試，不要直接重啟。啟用 plugin 前確認版本跟 Hyprland 版本相容。&lt;/p>
&lt;h2 id="場景二單一桌面工具掛了">場景二：單一桌面工具掛了&lt;/h2>
&lt;p>&lt;strong>症狀&lt;/strong>：狀態列（waybar）消失、啟動器（wofi/rofi）叫不出來、通知（mako/dunst）不跳了。桌面其他功能正常，視窗可以操作。&lt;/p>
&lt;p>&lt;strong>原因&lt;/strong>：這些工具各自是獨立的 process。掛了只影響自己的功能，不影響 compositor 或其他工具。常見原因是 config 語法錯誤（改完 config 後觸發）、記憶體洩漏（長時間運作後）、或外部服務連線異常（如 waybar 的某個 module 連不到系統匯流排）。&lt;/p>
&lt;p>&lt;strong>恢復步驟&lt;/strong>：&lt;/p>
&lt;p>判斷啟動方式：如果工具是在 Hyprland config 裡用 &lt;code>exec-once&lt;/code>（Hyprland 的自動啟動指令，compositor 啟動時執行一次）啟動的，用 &lt;code>killall&lt;/code> + 手動重啟；如果是 systemd user unit，用 &lt;code>systemctl --user&lt;/code>。&lt;/p>
&lt;p>&lt;code>exec-once&lt;/code> 啟動方式（多數 Hyprland 安裝的預設做法）：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># waybar 掛了&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">killall waybar&lt;span class="p">;&lt;/span> waybar &lt;span class="p">&amp;amp;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># wofi 掛了&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">killall wofi
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="c1"># wofi 只在需要時啟動，不用常駐&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="c1"># mako 掛了&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">killall mako&lt;span class="p">;&lt;/span> mako &lt;span class="p">&amp;amp;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>systemd user unit 啟動方式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">systemctl --user restart waybar
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">systemctl --user restart mako&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>確認工具是否在跑&lt;/strong>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">pgrep waybar &lt;span class="c1"># 有輸出 = 在跑&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">pgrep mako &lt;span class="c1"># 沒輸出 = 沒在跑&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>預防&lt;/strong>：改 config 後重啟對應的工具確認語法正確。Waybar 的 config 是 JSON 格式，語法錯誤會導致它無法啟動——改完後先用 &lt;code>waybar&lt;/code> 前台跑一次看有沒有錯誤訊息。&lt;/p>
&lt;h2 id="場景二點五鎖屏卡死hyprlock-異常結束">場景二點五：鎖屏卡死（hyprlock 異常結束）&lt;/h2>
&lt;p>&lt;strong>症狀&lt;/strong>：鎖屏畫面消失但桌面沒回來，螢幕顯示 Hyprland 的失效保護訊息（「it looks like you locked your screen but the lockscreen app died」），或畫面全黑但系統有回應（SSH 能連、TTY 可能切得到也可能切不到）。&lt;/p>
&lt;p>&lt;strong>原因&lt;/strong>：鎖屏工具（Hyprlock、Swaylock）透過 Wayland 的 ext-session-lock 協議向 compositor 請求鎖定。鎖定狀態由 compositor 持有，唯一正常解鎖動作是鎖屏 client 通過認證後呼叫 unlock_and_destroy。如果鎖屏 client 在持鎖狀態下被殺（&lt;code>pkill&lt;/code>、crash），compositor 沒收到認證信號，會維持鎖定並顯示失效保護畫面。這跟殺 waybar/mako 不同——那些是普通 process，殺了重啟就好；鎖屏 client 持有安全狀態，殺了反而卡住。&lt;/p></description><content:encoded><![CDATA[<p>這篇按故障場景組織，每個場景列出症狀、原因、恢復步驟和預防措施。出問題時按症狀找到對應場景，照步驟操作。</p>
<h2 id="場景一hyprland-compositor-crash">場景一：Hyprland compositor crash</h2>
<p><strong>症狀</strong>：所有視窗同時消失，螢幕變黑或回到 TTY 登入畫面。滑鼠鍵盤有反應（可以切 TTY），但沒有桌面。</p>
<p><strong>原因</strong>：Compositor process 遇到 fatal error 被 kernel 終止。常見觸發條件包括 plugin 相容性問題、特定 Wayland 協議操作觸發的 bug、GPU driver 回傳異常狀態。</p>
<p><strong>恢復步驟</strong>：</p>
<p>注意：以下步驟中 <code>killall Hyprland</code> 或重啟 Hyprland 會終止所有由 compositor 管理的視窗，未存檔的工作會遺失。如果可能，先透過 TTY 或 SSH 嘗試存檔（如 <code>kill -USR1 &lt;pid&gt;</code> 對支援的應用程式觸發存檔）。</p>
<ol>
<li><code>Ctrl+Alt+F2</code> 切到 TTY2</li>
<li>用你的帳號登入</li>
<li>檢查 Hyprland 的最後錯誤訊息：</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 通用方式（不管 Hyprland 怎麼啟動都有效）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">journalctl -b <span class="p">|</span> grep -i hypr <span class="p">|</span> tail -30
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 如果 Hyprland 是 systemd user unit，可以更精準地查：</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">journalctl --user -u hyprland -n <span class="m">50</span> --no-pager</span></span></code></pre></div><ol start="4">
<li>重新啟動 Hyprland：</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">Hyprland</span></span></code></pre></div><ol start="5">
<li>如果反覆 crash，檢查最近改過的 config：</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">cd</span> ~/.config/hypr
</span></span><span class="line"><span class="ln">2</span><span class="cl">git diff  <span class="c1"># 如果 dotfile 有版控</span></span></span></code></pre></div><p><strong>預防</strong>：config 改動後用 <code>hyprctl reload</code> 測試，不要直接重啟。啟用 plugin 前確認版本跟 Hyprland 版本相容。</p>
<h2 id="場景二單一桌面工具掛了">場景二：單一桌面工具掛了</h2>
<p><strong>症狀</strong>：狀態列（waybar）消失、啟動器（wofi/rofi）叫不出來、通知（mako/dunst）不跳了。桌面其他功能正常，視窗可以操作。</p>
<p><strong>原因</strong>：這些工具各自是獨立的 process。掛了只影響自己的功能，不影響 compositor 或其他工具。常見原因是 config 語法錯誤（改完 config 後觸發）、記憶體洩漏（長時間運作後）、或外部服務連線異常（如 waybar 的某個 module 連不到系統匯流排）。</p>
<p><strong>恢復步驟</strong>：</p>
<p>判斷啟動方式：如果工具是在 Hyprland config 裡用 <code>exec-once</code>（Hyprland 的自動啟動指令，compositor 啟動時執行一次）啟動的，用 <code>killall</code> + 手動重啟；如果是 systemd user unit，用 <code>systemctl --user</code>。</p>
<p><code>exec-once</code> 啟動方式（多數 Hyprland 安裝的預設做法）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># waybar 掛了</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">killall waybar<span class="p">;</span> waybar <span class="p">&amp;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># wofi 掛了</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">killall wofi
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># wofi 只在需要時啟動，不用常駐</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># mako 掛了</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">killall mako<span class="p">;</span> mako <span class="p">&amp;</span></span></span></code></pre></div><p>systemd user unit 啟動方式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">systemctl --user restart waybar
</span></span><span class="line"><span class="ln">2</span><span class="cl">systemctl --user restart mako</span></span></code></pre></div><p><strong>確認工具是否在跑</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">pgrep waybar  <span class="c1"># 有輸出 = 在跑</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">pgrep mako    <span class="c1"># 沒輸出 = 沒在跑</span></span></span></code></pre></div><p><strong>預防</strong>：改 config 後重啟對應的工具確認語法正確。Waybar 的 config 是 JSON 格式，語法錯誤會導致它無法啟動——改完後先用 <code>waybar</code> 前台跑一次看有沒有錯誤訊息。</p>
<h2 id="場景二點五鎖屏卡死hyprlock-異常結束">場景二點五：鎖屏卡死（hyprlock 異常結束）</h2>
<p><strong>症狀</strong>：鎖屏畫面消失但桌面沒回來，螢幕顯示 Hyprland 的失效保護訊息（「it looks like you locked your screen but the lockscreen app died」），或畫面全黑但系統有回應（SSH 能連、TTY 可能切得到也可能切不到）。</p>
<p><strong>原因</strong>：鎖屏工具（Hyprlock、Swaylock）透過 Wayland 的 ext-session-lock 協議向 compositor 請求鎖定。鎖定狀態由 compositor 持有，唯一正常解鎖動作是鎖屏 client 通過認證後呼叫 unlock_and_destroy。如果鎖屏 client 在持鎖狀態下被殺（<code>pkill</code>、crash），compositor 沒收到認證信號，會維持鎖定並顯示失效保護畫面。這跟殺 waybar/mako 不同——那些是普通 process，殺了重啟就好；鎖屏 client 持有安全狀態，殺了反而卡住。</p>
<p><strong>恢復步驟</strong>：</p>
<ol>
<li>嘗試切到另一個 TTY（<code>Ctrl+Alt+F2</code>）。注意：ext-session-lock 的安全語意允許 compositor 攔截 VT 切換快捷鍵，此時 TTY 切不過去，改用 SSH 從另一台機器連入</li>
<li>允許新的鎖屏 client 接管既有的鎖：</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">hyprctl --instance <span class="m">0</span> <span class="s1">&#39;keyword misc:allow_session_lock_restore 1&#39;</span></span></span></code></pre></div><ol start="3">
<li>重新拉一個鎖屏 client：</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">hyprctl --instance <span class="m">0</span> <span class="s1">&#39;dispatch exec hyprlock&#39;</span></span></span></code></pre></div><ol start="4">
<li>回到鎖屏畫面，用密碼正常解鎖</li>
</ol>
<p><strong>判讀</strong>：<code>loginctl show-session &lt;id&gt; -p LockedHint</code> 可能顯示 <code>LockedHint=no</code>（logind 層認為沒鎖），但畫面仍進不去——因為擋住畫面的是 compositor 的 ext-session-lock，跟 logind 的提示是獨立的兩層。判斷畫面鎖定狀態看 compositor 層，不看 logind。</p>
<p><strong>預防</strong>：測試鎖屏時備好恢復路徑（知道密碼、或預先開 SSH）。不要用殺 process 的方式結束鎖屏——要結束就走認證解鎖。自動化流程若會啟動鎖屏，把「需要人工解鎖」算進代價。鎖屏安全模型的完整說明見 <a href="/blog/linux/dotfile/knowledge-cards/session-lock/" data-link-title="Wayland Session Lock（鎖屏安全狀態）" data-link-desc="hyprlock / swaylock 畫面卡住、pkill 後進不了桌面、或要在 VM / 自動化環境測試鎖屏時回來讀">Session Lock</a>。</p>
<h2 id="場景二點六桌面-shell-畫得出來但互動死掉進程活著卻-wedged">場景二點六：桌面 shell 畫得出來但互動死掉（進程活著卻 wedged）</h2>
<p><strong>症狀</strong>：bar / 狀態列還在螢幕上、看起來一切正常，但點它的按鈕（工作區切換、系統匣圖示）沒反應，keybind 叫不出啟動器（wofi / 內建 launcher）。同時焦點視窗（例如終端機）打字完全正常——鍵盤到得了應用程式，只是桌面 shell 的互動死了。</p>
<p><strong>原因</strong>：這是跟場景二（工具掛了）不同的一類故障，關鍵差別在<strong>進程還活著</strong>。場景二是 process 崩潰退出（<code>pgrep</code> 沒輸出），殺了重啟就好；這裡的桌面 shell（如 caelestia / Quickshell）進程還在跑（<code>pgrep</code> 找得到、STAT 是正常的 <code>S</code>、在 <code>poll</code> 等事件、CPU 不高），但它內部的某個子系統初始化失敗了——常見是 QML scene 的某個物件因為上游錯誤沒建起來、變成 null，於是負責「keybind → 開抽屜」「bar 按鈕互動」的模組對 null 讀屬性、整條互動接線死掉。bar 之所以還畫得出來，是它還停在初始化失敗前那一幀的畫面：<strong>畫得出來不等於還活著</strong>，跟鎖屏那課（畫面有密碼框不等於真的在鎖）是同一個陷阱。</p>
<p>上游觸發常是渲染層。實測案例：VM 的 GPU 只提供到 GLSL 1.20，而 shell 的 shader 需要 GLES 100/300/330，pipeline 建不起來（log 狂噴 <code>Failed to build graphics pipeline state</code>），這次渲染失敗把 scene 初始化打斷，drawers 狀態物件變 null。</p>
<p><strong>診斷（別看 pgrep，讀 shell 自己的 log）</strong>：</p>
<p><code>pgrep</code> 在這裡會騙你——它回報「在跑」，但那不等於「在運作」。權威來源是 shell 自己的 log，而且這種 log 常常<strong>不在 journalctl、也不在你猜的路徑</strong>，要用該 shell 專屬的 log 指令：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># caelestia 的例子：用它自己的 CLI 印 shell log</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">caelestia shell -l 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">|</span> tail -40
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 看的是 QML 的 TypeError：對 null 讀屬性 = 那個子系統死了</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">#   scene: @modules/Shortcuts.qml: TypeError: Cannot read property &#39;launcher&#39; of null</span></span></span></code></pre></div><p>另一個活性探針是 shell 的 <strong>IPC 回不回真實狀態</strong>：正常時查抽屜列表會回傳名字，子系統死掉時回空——這比「進程在不在」精準得多：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 子系統活著 → 列出 bar/launcher/session…；死掉 → 回空</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">caelestia shell ipc call drawers list</span></span></code></pre></div><p><strong>恢復步驟</strong>：重啟 shell 讓 scene 重建。以 caelestia 為例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">caelestia shell -k     <span class="c1"># 殺掉卡住的 shell</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">caelestia shell -d     <span class="c1"># 重新啟動（detached）</span></span></span></code></pre></div><p><strong>驗證修好了，看子系統回來、不是看 pgrep</strong>：重啟後 process 一定在（<code>pgrep</code> 本來就會有），要確認的是接線恢復——<code>caelestia shell ipc call drawers list</code> 從「回空」變成列出真實抽屜名、log 不再噴 null 的 TypeError。這對應「重啟成功要驗子系統狀態、不是驗 process 存在」的通用紀律。</p>
<p><strong>判讀與其他場景的界線</strong>：<code>pgrep</code> 有輸出 + bar 畫得出來 → 別急著判「正常」；點不動 / keybind 死掉就是 wedged 的訊號，往 shell 自己的 log 查。這跟場景二（process 真的沒了、<code>pgrep</code> 空）、場景三（compositor 整個凍結、連終端機打字都不行）都不同——這裡 compositor 正常、焦點視窗鍵盤正常，只有 shell 的互動接線死。判「進程活著到底有沒有在運作」的通用招式，見 <a href="/blog/linux/debug/process-service-state-diagnosis/" data-link-title="程序、服務與狀態怎麼判" data-link-desc="要判斷一個程式活著沒、某個系統服務現在由誰提供、桌面 session 有沒有被鎖、或終端機多工器的 session 還在不在時，用對的權威來源而不是靠畫面或猜的名字">程序、服務與狀態怎麼判</a>。</p>
<p><strong>預防</strong>：留意 shell log 裡持續出現的 shader / 渲染 pipeline 錯誤——在 VM 或 GL 支援不足的環境，這類錯誤可能非致命地存在（shell 大致能用），但一次渲染失敗就可能打斷 scene 初始化、把互動接線弄死。VM 環境要確認 GPU 提供的 GL / GLSL 版本足夠（virtio-gpu 走 mesa/zink 提供 GL 3.3+），或在 shell 設定關掉需要高階 shader 的效果。</p>
<h2 id="場景三gpu-driver-hang畫面凍結">場景三：GPU driver hang（畫面凍結）</h2>
<p><strong>症狀</strong>：桌面畫面完全凍結——滑鼠不動、鍵盤不回應、<code>Ctrl+Alt+F2</code> 切 TTY 也沒反應或延遲很久才回應。但如果從另一台機器 SSH 進來，系統是活的，process 都在跑。</p>
<p><strong>原因</strong>：GPU driver 進入異常狀態。NVIDIA 閉源驅動在 Linux 上的穩定性不如 AMD 開源驅動（amdgpu），特別是在 Wayland 環境下。常見觸發條件包括 suspend/resume 之後 GPU 沒正確恢復、某些 OpenGL/Vulkan 操作觸發 driver bug、顯示輸出切換（接上或拔掉外接螢幕）。</p>
<p><strong>恢復步驟</strong>：</p>
<p>方法 A — 如果 TTY 能切過去：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 切到 TTY2</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">Ctrl+Alt+F2
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 殺掉 Hyprland（它會帶走所有視窗）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">killall Hyprland
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 重新啟動</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">Hyprland</span></span></code></pre></div><p>方法 B — 如果 TTY 也凍結、但 SSH 能連：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 從另一台機器 SSH 進來（需事先知道 IP，見下方預防段）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">ssh user@machine-ip
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 殺掉 compositor</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">killall Hyprland
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 如果需要 reset GPU（NVIDIA，且 driver 仍回應）</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 前提：所有使用 GPU 的 process 已停止（compositor 已 kill）</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">sudo nvidia-smi --gpu-reset
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 切回機器前面重啟 Hyprland</span></span></span></code></pre></div><p>方法 C — 如果完全無回應，先嘗試 Magic SysRq：</p>
<p>Magic SysRq 是 kernel 層級的緊急操作介面，即使 userspace 完全卡死也能回應。按住 <code>Alt+SysRq</code>（筆電通常是 <code>Alt+Fn+SysRq</code>），然後依序按 <code>R E I S U B</code>（每個鍵間隔幾秒）：</p>
<ul>
<li><code>R</code> — 把鍵盤從 raw mode 搶回來</li>
<li><code>E</code> — 對所有 process 送 SIGTERM</li>
<li><code>I</code> — 對所有 process 送 SIGKILL</li>
<li><code>S</code> — sync 所有檔案系統</li>
<li><code>U</code> — remount 所有檔案系統為 read-only</li>
<li><code>B</code> — 立即 reboot</li>
</ul>
<p>這比直接斷電安全——sync + unmount 步驟會盡量保護磁碟上的資料。Arch Linux 預設可能停用 SysRq，需在 <code>/etc/sysctl.d/</code> 設定 <code>kernel.sysrq=1</code> 啟用。</p>
<p>方法 D — 如果 SysRq 也無效，按住電源鍵強制關機：</p>
<p>這是最後手段。Linux 的 ext4/btrfs 檔案系統有 journal 保護，強制關機通常不會損壞<strong>檔案系統結構</strong>。但 journal 保護的是 metadata 一致性，正在寫入的使用者資料（未存檔的文件、正在下載的檔案）仍然可能遺失或損壞。重開機後正常登入 TTY、啟動 Hyprland 即可。如果開機過程有異常，用 <code>journalctl -b -1 -p err</code> 查看上次開機的錯誤訊息，確認是否有檔案系統修復紀錄。</p>
<p><strong>預防</strong>：</p>
<ul>
<li>NVIDIA 用戶：關注 driver 版本的 release notes，已知有 Wayland 問題的版本避開</li>
<li>配置 suspend 後的 GPU 恢復：在 Hyprland config 或 systemd sleep hook 裡加入 GPU reset 操作</li>
<li>事先記錄機器的 IP 位址（<code>ip addr show</code>）或設定固定 hostname（如 mDNS 的 <code>machine.local</code>），桌面凍結時才有辦法從另一台機器 SSH 進來</li>
<li>考慮開啟 SSH server，出問題時可以遠端救援。開啟後應配置 key-based authentication 並停用密碼登入（<code>PasswordAuthentication no</code>），避免在網路上暴露密碼登入通道：</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">sudo systemctl <span class="nb">enable</span> sshd
</span></span><span class="line"><span class="ln">2</span><span class="cl">sudo systemctl start sshd
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 安全配置：停用密碼登入（確保已設好 SSH key 再改）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 編輯 /etc/ssh/sshd_config，設定 PasswordAuthentication no</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 然後 sudo systemctl restart sshd</span></span></span></code></pre></div><h2 id="場景四記憶體耗盡oom">場景四：記憶體耗盡（OOM）</h2>
<p><strong>症狀</strong>：系統變得極慢，操作有明顯延遲（幾秒到幾十秒）。隨後可能某些 process 突然被殺掉——瀏覽器分頁消失、IDE 視窗關閉，嚴重時 Hyprland 本身被 OOM Killer 終止導致桌面消失。</p>
<p><strong>原因</strong>：實體記憶體和 swap 都用完了。常見觸發者是瀏覽器（Chrome/Firefox 的分頁越開越多）、IDE（大型專案的 language server）、Docker container、或應用程式的記憶體洩漏。</p>
<p><strong>恢復步驟</strong>：</p>
<p>如果還能操作：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 找出誰在吃記憶體</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">top -o %MEM
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 或用 htop/btop 的互動介面</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 殺掉佔最多記憶體的 process</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="nb">kill</span> &lt;PID&gt;</span></span></code></pre></div><p>如果桌面已經被殺、在 TTY 裡：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 看 OOM Killer 殺了誰</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">journalctl -b <span class="p">|</span> grep -i <span class="s2">&#34;out of memory&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">journalctl -b <span class="p">|</span> grep -i <span class="s2">&#34;oom&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 清理完後重啟桌面</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">Hyprland</span></span></code></pre></div><p><strong>預防</strong>：</p>
<p>設定 swap（即使 RAM 夠大，swap 提供 OOM 前的緩衝時間讓你有機會手動清理 process）。RAM 16GB 以上的機器，2-4GB swap 作緩衝通常足夠：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 查看是否有 swap</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">swapon --show
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 如果沒有，建立一個 4GB 的 swap file（ext4 檔案系統）</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">sudo fallocate -l 4G /swapfile
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">sudo chmod <span class="m">600</span> /swapfile
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">sudo mkswap /swapfile
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">sudo swapon /swapfile
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 永久生效：加入 /etc/fstab</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nb">echo</span> <span class="s1">&#39;/swapfile none swap defaults 0 0&#39;</span> <span class="p">|</span> sudo tee -a /etc/fstab</span></span></code></pre></div><p>Btrfs 檔案系統不支援 <code>fallocate</code> 建立 swap file。Btrfs 用戶需改用 <code>btrfs filesystem mkswapfile</code> 或建立專屬的 swap subvolume，具體做法參考 Arch Wiki 的 Btrfs swap 段落。</p>
<p>啟用 systemd-oomd（比 kernel OOM Killer 更早介入、更可控）。systemd-oomd 在 cgroup 的記憶體壓力達到閾值時就開始清理，預設配置對多數桌面場景足夠。進階調整可透過 <code>/etc/systemd/oomd.conf</code> 設定：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">sudo systemctl <span class="nb">enable</span> systemd-oomd
</span></span><span class="line"><span class="ln">2</span><span class="cl">sudo systemctl start systemd-oomd</span></span></code></pre></div><h2 id="場景五config-寫錯導致-hyprland-啟動失敗">場景五：Config 寫錯導致 Hyprland 啟動失敗</h2>
<p><strong>症狀</strong>：從 display manager（圖形登入畫面，如 SDDM、GDM）登入後立刻黑屏又回到登入畫面，或直接回到 TTY。如果從 TTY 手動執行 <code>Hyprland</code>，看到錯誤訊息後立即退出。</p>
<p><strong>原因</strong>：Hyprland config 有語法錯誤或引用了不存在的資源。常見錯誤包括 <code>source</code> 指定的檔案不存在、keybind 語法寫錯、monitor 設定格式錯誤。</p>
<p><strong>恢復步驟</strong>：</p>
<ol>
<li>切到 TTY（<code>Ctrl+Alt+F2</code>）</li>
<li>登入後直接跑 Hyprland 看錯誤訊息：</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 看 Hyprland 的啟動錯誤（也可用 journalctl -b | grep -i hypr）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">Hyprland
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># Hyprland 如果因 config 錯誤無法啟動，會直接印出錯誤訊息後退出</span></span></span></code></pre></div><ol start="3">
<li>根據錯誤訊息修改 config：</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Hyprland 的主 config</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">vim ~/.config/hypr/hyprland.conf
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 如果用了 source 拆分，錯誤訊息會指出是哪個檔案</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">vim ~/.config/hypr/keybinds.conf</span></span></code></pre></div><ol start="4">
<li>修完後重新啟動：</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">Hyprland</span></span></code></pre></div><p><strong>常見 config 錯誤</strong>：</p>
<p><code>source</code> 路徑錯誤——檔案不存在或路徑拼錯：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 確認 source 指定的檔案都存在</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">grep <span class="s2">&#34;^source&#34;</span> ~/.config/hypr/hyprland.conf
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 逐一檢查每個路徑</span></span></span></code></pre></div><p>Monitor 設定錯誤——指定了不存在的螢幕名稱：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 查看系統實際的螢幕名稱</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 在能進桌面時記下來，或用 wlr-randr</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">wlr-randr</span></span></code></pre></div><p>Keybind 語法錯誤——dispatcher 名稱拼錯或參數格式不對。Hyprland 的 keybind 格式是 <code>bind = MOD, key, dispatcher, params</code>，少一個欄位或 dispatcher 拼錯就會報錯。</p>
<p><strong>預防</strong>：config 改動時用 <code>hyprctl reload</code> 即時測試，不要改完 config 就直接重啟 Hyprland。如果 dotfile 用 Git 管理，改壞了可以 <code>git checkout</code> 回退。</p>
<h2 id="場景六suspendresume-後桌面異常">場景六：Suspend/resume 後桌面異常</h2>
<p><strong>症狀</strong>：筆電蓋上或手動 suspend 後喚醒，出現以下任一情況——螢幕黑屏但系統有反應（鍵盤背光亮）、解析度跑掉、多螢幕配置丟失（所有視窗擠到一個螢幕）、compositor 直接 crash 回到 TTY。</p>
<p><strong>原因</strong>：GPU driver 在 suspend/resume 過程中需要保存和恢復 GPU 狀態。NVIDIA 閉源驅動在 Wayland 上的 suspend/resume 支援不如 AMD 開源驅動穩定，特別是多螢幕配置和高刷新率模式下容易出問題。</p>
<p><strong>恢復步驟</strong>：</p>
<p>如果螢幕黑屏但系統有反應：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 切到 TTY</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">Ctrl+Alt+F2
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 檢查 Hyprland 是否還在跑</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">pgrep Hyprland
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 如果在跑但沒畫面，kill 再重啟</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">killall Hyprland
</span></span><span class="line"><span class="ln">9</span><span class="cl">Hyprland</span></span></code></pre></div><p>如果解析度或螢幕配置跑掉：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 在 Hyprland 內重新套用 monitor 設定</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">hyprctl reload</span></span></code></pre></div><p>如果 compositor 已經 crash：按場景一的步驟從 TTY 重啟。</p>
<p><strong>預防</strong>：</p>
<ul>
<li>NVIDIA 用戶：在 <code>/etc/modprobe.d/nvidia.conf</code> 啟用 preserve video memory allocations：</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># /etc/modprobe.d/nvidia.conf</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">options nvidia <span class="nv">NVreg_PreserveVideoMemoryAllocations</span><span class="o">=</span><span class="m">1</span></span></span></code></pre></div><p>同時啟用 NVIDIA 的 suspend/resume systemd service：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">sudo systemctl <span class="nb">enable</span> nvidia-suspend
</span></span><span class="line"><span class="ln">2</span><span class="cl">sudo systemctl <span class="nb">enable</span> nvidia-resume
</span></span><span class="line"><span class="ln">3</span><span class="cl">sudo systemctl <span class="nb">enable</span> nvidia-hibernate</span></span></code></pre></div><ul>
<li>AMD 用戶：amdgpu driver 的 suspend/resume 通常開箱即用，遇到問題先更新 kernel（<code>pacman -Syu linux</code>）。</li>
</ul>
]]></content:encoded></item><item><title>Hyprland 核心配置</title><link>https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/hyprland-core-config/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/hyprland-core-config/</guid><description>&lt;p>Hyprland v0.55+ 使用 Lua 作為配置語言。Lua 語法基礎見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/lua-scripting-language/" data-link-title="Lua 腳本語言" data-link-desc="在 Hyprland 或 Neovim 配置檔遇到 Lua 語法看不懂時回來讀 — 配置檔需要的最小 Lua 知識">Lua 腳本語言&lt;/a>。&lt;/p>
&lt;h2 id="配置檔位置與格式">配置檔位置與格式&lt;/h2>
&lt;p>Hyprland v0.55 起，配置格式從 hyprlang（&lt;code>.conf&lt;/code>）遷移到 &lt;strong>Lua&lt;/strong>（&lt;code>.lua&lt;/code>）。主配置檔是 &lt;code>~/.config/hypr/hyprland.lua&lt;/code>。如果 &lt;code>.lua&lt;/code> 不存在但 &lt;code>.conf&lt;/code> 存在，Hyprland 會回退讀取舊格式，但 hyprlang 已標記為棄用，後續版本會移除支援。&lt;/p>
&lt;p>修改後即時生效（不需要重新登入或重啟）。第一次啟動時如果沒有配置檔，Hyprland 會自動產生範例配置。即使配置有語法錯誤，緊急快捷鍵（SUPER+Q 開 terminal、SUPER+M 離開）仍然可用。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>[VM 可測試]&lt;/strong> 配置檔格式和載入行為在 VM 裡跟實機完全相同。&lt;/p>&lt;/blockquote>
&lt;h3 id="模組化拆分">模組化拆分&lt;/h3>
&lt;p>用 Lua 原生的 &lt;code>require()&lt;/code> 拆分配置：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-lua" data-lang="lua">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">-- ~/.config/hypr/hyprland.lua — 主入口&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="n">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;monitors&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">-- 載入 monitors.lua&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="n">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;keybinds&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">-- 載入 keybinds.lua&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="n">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;rules&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">-- 載入 rules.lua&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="n">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;autostart&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">-- 載入 autostart.lua&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="n">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;appearance&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">-- 載入 appearance.lua&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="n">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;env&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">-- 載入 env.lua&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>require(&amp;quot;monitors&amp;quot;)&lt;/code> 會從同目錄載入 &lt;code>monitors.lua&lt;/code>。拆分的理由是職責分離——改 keybind 不用在千行配置裡找位置，每個子檔案可以獨立 diff 和 rollback。&lt;/p>
&lt;h3 id="從舊格式遷移">從舊格式遷移&lt;/h3>
&lt;p>Hyprland 官方提供遷移工具：&lt;code>hyprlang2lua&lt;/code>（Go，也有瀏覽器版）和 &lt;code>hypr-migrate&lt;/code>。如果你是從零開始，直接用 Lua 格式。&lt;/p>
&lt;h2 id="monitor-設定">Monitor 設定&lt;/h2>
&lt;p>Monitor 設定是硬體相關的核心配置，每台機器都不同。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-lua" data-lang="lua">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1">-- ~/.config/hypr/monitors.lua&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="n">hl.config&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="n">monitor&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="c1">-- 語法：&amp;#34;name, resolution@refreshrate, position, scale&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;DP-1, 2560x1440@144, 0x0, 1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;HDMI-A-1, 1920x1080@60, 2560x0, 1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="c1">-- 筆電內建螢幕&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;eDP-1, preferred, auto, 1.5&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="c1">-- 預設規則（未明確列出的螢幕）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;, preferred, auto, 1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="c1">-- 鏡像模式（DP-2 鏡像 DP-1 的畫面）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="c1">-- &amp;#34;DP-2, preferred, auto, 1, mirror, DP-1&amp;#34;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="p">})&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>position&lt;/code> 決定多螢幕的空間排列——&lt;code>0x0&lt;/code> 是左上角原點，&lt;code>2560x0&lt;/code> 表示在第一個螢幕的右邊。&lt;code>scale&lt;/code> 處理 HiDPI 顯示（1.5 表示 150% 縮放）。&lt;/p>
&lt;p>查詢可用的 monitor 名稱：&lt;code>hyprctl monitors&lt;/code>&lt;/p>
&lt;p>其他選項：&lt;/p>
&lt;ul>
&lt;li>&lt;code>transform, 1&lt;/code> — 旋轉（0=正常、1=90 度、2=180 度、3=270 度）&lt;/li>
&lt;li>&lt;code>vrr, 1&lt;/code> — 可變更新率（1=開啟、2=僅全螢幕）&lt;/li>
&lt;li>&lt;code>bitdepth, 10&lt;/code> — 10-bit 色彩&lt;/li>
&lt;/ul>
&lt;h3 id="筆電合蓋行為">筆電合蓋行為&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-lua" data-lang="lua">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">-- 合蓋時關閉內建螢幕、開蓋時恢復&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1">-- bindl = 即使輸入鎖定也能觸發（合蓋時需要）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="n">hl.bindl&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;switch:on:Lid Switch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;exec&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;hyprctl keyword monitor &amp;#39;eDP-1, disable&amp;#39;&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="n">hl.bindl&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;switch:off:Lid Switch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;exec&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;hyprctl keyword monitor &amp;#39;eDP-1, preferred, auto, 1&amp;#39;&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;strong>[需實機測試]&lt;/strong> Monitor 位置排列、HiDPI 縮放、VRR、旋轉、多螢幕熱插拔、合蓋行為。VM 通常只有一個虛擬螢幕。&lt;/p></description><content:encoded><![CDATA[<p>Hyprland v0.55+ 使用 Lua 作為配置語言。Lua 語法基礎見 <a href="/blog/linux/dotfile/knowledge-cards/lua-scripting-language/" data-link-title="Lua 腳本語言" data-link-desc="在 Hyprland 或 Neovim 配置檔遇到 Lua 語法看不懂時回來讀 — 配置檔需要的最小 Lua 知識">Lua 腳本語言</a>。</p>
<h2 id="配置檔位置與格式">配置檔位置與格式</h2>
<p>Hyprland v0.55 起，配置格式從 hyprlang（<code>.conf</code>）遷移到 <strong>Lua</strong>（<code>.lua</code>）。主配置檔是 <code>~/.config/hypr/hyprland.lua</code>。如果 <code>.lua</code> 不存在但 <code>.conf</code> 存在，Hyprland 會回退讀取舊格式，但 hyprlang 已標記為棄用，後續版本會移除支援。</p>
<p>修改後即時生效（不需要重新登入或重啟）。第一次啟動時如果沒有配置檔，Hyprland 會自動產生範例配置。即使配置有語法錯誤，緊急快捷鍵（SUPER+Q 開 terminal、SUPER+M 離開）仍然可用。</p>
<blockquote>
<p><strong>[VM 可測試]</strong> 配置檔格式和載入行為在 VM 裡跟實機完全相同。</p></blockquote>
<h3 id="模組化拆分">模組化拆分</h3>
<p>用 Lua 原生的 <code>require()</code> 拆分配置：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">-- ~/.config/hypr/hyprland.lua — 主入口</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;monitors&#34;</span><span class="p">)</span>      <span class="c1">-- 載入 monitors.lua</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;keybinds&#34;</span><span class="p">)</span>      <span class="c1">-- 載入 keybinds.lua</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;rules&#34;</span><span class="p">)</span>         <span class="c1">-- 載入 rules.lua</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;autostart&#34;</span><span class="p">)</span>     <span class="c1">-- 載入 autostart.lua</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;appearance&#34;</span><span class="p">)</span>    <span class="c1">-- 載入 appearance.lua</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;env&#34;</span><span class="p">)</span>           <span class="c1">-- 載入 env.lua</span></span></span></code></pre></div><p><code>require(&quot;monitors&quot;)</code> 會從同目錄載入 <code>monitors.lua</code>。拆分的理由是職責分離——改 keybind 不用在千行配置裡找位置，每個子檔案可以獨立 diff 和 rollback。</p>
<h3 id="從舊格式遷移">從舊格式遷移</h3>
<p>Hyprland 官方提供遷移工具：<code>hyprlang2lua</code>（Go，也有瀏覽器版）和 <code>hypr-migrate</code>。如果你是從零開始，直接用 Lua 格式。</p>
<h2 id="monitor-設定">Monitor 設定</h2>
<p>Monitor 設定是硬體相關的核心配置，每台機器都不同。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- ~/.config/hypr/monitors.lua</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">hl.config</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">monitor</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="c1">-- 語法：&#34;name, resolution@refreshrate, position, scale&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="s2">&#34;DP-1, 2560x1440@144, 0x0, 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="s2">&#34;HDMI-A-1, 1920x1080@60, 2560x0, 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="c1">-- 筆電內建螢幕</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="s2">&#34;eDP-1, preferred, auto, 1.5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="c1">-- 預設規則（未明確列出的螢幕）</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="s2">&#34;, preferred, auto, 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="c1">-- 鏡像模式（DP-2 鏡像 DP-1 的畫面）</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="c1">-- &#34;DP-2, preferred, auto, 1, mirror, DP-1&#34;,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><p><code>position</code> 決定多螢幕的空間排列——<code>0x0</code> 是左上角原點，<code>2560x0</code> 表示在第一個螢幕的右邊。<code>scale</code> 處理 HiDPI 顯示（1.5 表示 150% 縮放）。</p>
<p>查詢可用的 monitor 名稱：<code>hyprctl monitors</code></p>
<p>其他選項：</p>
<ul>
<li><code>transform, 1</code> — 旋轉（0=正常、1=90 度、2=180 度、3=270 度）</li>
<li><code>vrr, 1</code> — 可變更新率（1=開啟、2=僅全螢幕）</li>
<li><code>bitdepth, 10</code> — 10-bit 色彩</li>
</ul>
<h3 id="筆電合蓋行為">筆電合蓋行為</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">-- 合蓋時關閉內建螢幕、開蓋時恢復</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">-- bindl = 即使輸入鎖定也能觸發（合蓋時需要）</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">hl.bindl</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;switch:on:Lid Switch&#34;</span><span class="p">,</span> <span class="s2">&#34;exec&#34;</span><span class="p">,</span> <span class="s2">&#34;hyprctl keyword monitor &#39;eDP-1, disable&#39;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">hl.bindl</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;switch:off:Lid Switch&#34;</span><span class="p">,</span> <span class="s2">&#34;exec&#34;</span><span class="p">,</span> <span class="s2">&#34;hyprctl keyword monitor &#39;eDP-1, preferred, auto, 1&#39;&#34;</span><span class="p">)</span></span></span></code></pre></div><blockquote>
<p><strong>[需實機測試]</strong> Monitor 位置排列、HiDPI 縮放、VRR、旋轉、多螢幕熱插拔、合蓋行為。VM 通常只有一個虛擬螢幕。</p></blockquote>
<h3 id="dotfile-管理策略">Dotfile 管理策略</h3>
<p>Monitor 設定是典型的「機器專屬」配置。如果用 chezmoi，可以用 template 依機器名稱切換；如果用 stow，可以把 <code>monitors.lua</code> 排除在 Git 外、每台機器手動寫。</p>
<h2 id="輸入裝置配置">輸入裝置配置</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- ~/.config/hypr/hyprland.lua 或拆到獨立檔案</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">hl.config</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">input</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="n">kb_layout</span> <span class="o">=</span> <span class="s2">&#34;us&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="c1">-- 多語系：kb_layout = &#34;us,de&#34;,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="c1">-- 切換：kb_options = &#34;grp:alt_shift_toggle&#34;,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="c1">-- Caps Lock 當 Escape：kb_options = &#34;caps:escape&#34;,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">kb_options</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">follow_mouse</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span>         <span class="c1">-- 焦點跟隨滑鼠</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">sensitivity</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>          <span class="c1">-- -1.0 到 1.0（0 = 不修改）</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">accel_profile</span> <span class="o">=</span> <span class="s2">&#34;flat&#34;</span><span class="p">,</span>   <span class="c1">-- flat 或 adaptive</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">repeat_rate</span> <span class="o">=</span> <span class="mi">25</span><span class="p">,</span>         <span class="c1">-- 長按時每秒重複幾次</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">repeat_delay</span> <span class="o">=</span> <span class="mi">600</span><span class="p">,</span>       <span class="c1">-- 按住多久開始重複（毫秒）</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">touchpad</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="n">natural_scroll</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">disable_while_typing</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;tap-to-click&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            <span class="n">drag_lock</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="n">scroll_factor</span> <span class="o">=</span> <span class="mf">1.0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><p>可用的 XKB options 查詢：<code>/usr/share/X11/xkb/rules/evdev.lst</code> 或 <code>localectl list-x11-keymap-options</code>。</p>
<blockquote>
<p><strong>[VM 可測試]</strong> 鍵盤 layout、repeat rate。<br>
<strong>[需實機測試]</strong> 觸控板設定、手勢、滑鼠靈敏度/加速曲線、多鍵盤配置。</p></blockquote>
<h2 id="keybind-設計">Keybind 設計</h2>
<h3 id="修飾鍵modifiersuper-是哪顆鍵">修飾鍵（Modifier）：<code>SUPER</code> 是哪顆鍵</h3>
<p>keybind 由「修飾鍵 + 按鍵」組成，Hyprland 配置裡最常當主修飾鍵的是 <code>SUPER</code>。<code>SUPER</code> 是 X11/Wayland 對 <strong>Meta 鍵</strong>的稱呼，在不同鍵盤上是不同的實體鍵：</p>
<ul>
<li><strong>PC 鍵盤</strong>：<strong>Windows 鍵</strong>（⊞，通常在 Ctrl 與 Alt 之間）。</li>
<li><strong>Mac 鍵盤</strong>：<strong>Command 鍵（⌘）</strong>。</li>
</ul>
<p>其他常見修飾鍵：<code>SHIFT</code>、<code>CTRL</code>（Control）、<code>ALT</code>（Mac 上是 Option ⌥）。組合寫法用空格串，例如 <code>&quot;SUPER SHIFT&quot;</code>。選 <code>SUPER</code> 當主修飾鍵是慣例——它幾乎不跟應用程式本身的快捷鍵衝突（應用多用 Ctrl / Alt）。</p>
<p>在 Mac 上跑虛擬機時，實體 ⌘ 會對應到 guest 的 <code>SUPER</code>，但 macOS 會先攔截部分 ⌘ 組合——這層 VM 專屬的坑見 <a href="../hyprland-vm-setup/">VM 環境設定</a>。</p>
<h3 id="bind-類型">Bind 類型</h3>
<p>Hyprland 提供多種 bind 函式，處理不同互動模式：</p>
<table>
  <thead>
      <tr>
          <th>函式</th>
          <th>行為</th>
          <th>適用場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>hl.bind()</code></td>
          <td>按一次觸發</td>
          <td>多數快捷鍵</td>
      </tr>
      <tr>
          <td><code>hl.binde()</code></td>
          <td>長按連續觸發</td>
          <td>調整視窗大小、音量</td>
      </tr>
      <tr>
          <td><code>hl.bindm()</code></td>
          <td>滑鼠綁定</td>
          <td>拖曳移動 / 調整視窗</td>
      </tr>
      <tr>
          <td><code>hl.bindr()</code></td>
          <td>放開時觸發</td>
          <td>切換模式</td>
      </tr>
      <tr>
          <td><code>hl.bindl()</code></td>
          <td>輸入鎖定也觸發</td>
          <td>合蓋開關、鎖屏時的媒體鍵</td>
      </tr>
      <tr>
          <td><code>hl.bindel()</code></td>
          <td>連續 + 鎖定</td>
          <td>鎖屏時的音量鍵（長按連續、鎖定可用）</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p><strong>[VM 可測試]</strong> 所有 keybind 邏輯和配置語法。</p></blockquote>
<h3 id="基本操作">基本操作</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- ~/.config/hypr/keybinds.lua</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">-- 基本操作</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;Return&#34;</span><span class="p">,</span> <span class="s2">&#34;exec&#34;</span><span class="p">,</span> <span class="s2">&#34;kitty&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;Q&#34;</span><span class="p">,</span> <span class="s2">&#34;killactive&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;D&#34;</span><span class="p">,</span> <span class="s2">&#34;exec&#34;</span><span class="p">,</span> <span class="s2">&#34;wofi --show drun&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;F&#34;</span><span class="p">,</span> <span class="s2">&#34;fullscreen&#34;</span><span class="p">,</span> <span class="s2">&#34;0&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER SHIFT&#34;</span><span class="p">,</span> <span class="s2">&#34;Space&#34;</span><span class="p">,</span> <span class="s2">&#34;togglefloating&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">-- 焦點移動（vim 風格）</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;H&#34;</span><span class="p">,</span> <span class="s2">&#34;movefocus&#34;</span><span class="p">,</span> <span class="s2">&#34;l&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;J&#34;</span><span class="p">,</span> <span class="s2">&#34;movefocus&#34;</span><span class="p">,</span> <span class="s2">&#34;d&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;K&#34;</span><span class="p">,</span> <span class="s2">&#34;movefocus&#34;</span><span class="p">,</span> <span class="s2">&#34;u&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;L&#34;</span><span class="p">,</span> <span class="s2">&#34;movefocus&#34;</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1">-- 視窗移動</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER SHIFT&#34;</span><span class="p">,</span> <span class="s2">&#34;H&#34;</span><span class="p">,</span> <span class="s2">&#34;movewindow&#34;</span><span class="p">,</span> <span class="s2">&#34;l&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER SHIFT&#34;</span><span class="p">,</span> <span class="s2">&#34;J&#34;</span><span class="p">,</span> <span class="s2">&#34;movewindow&#34;</span><span class="p">,</span> <span class="s2">&#34;d&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER SHIFT&#34;</span><span class="p">,</span> <span class="s2">&#34;K&#34;</span><span class="p">,</span> <span class="s2">&#34;movewindow&#34;</span><span class="p">,</span> <span class="s2">&#34;u&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER SHIFT&#34;</span><span class="p">,</span> <span class="s2">&#34;L&#34;</span><span class="p">,</span> <span class="s2">&#34;movewindow&#34;</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1">-- 視窗大小調整（長按連續觸發）</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="n">hl.binde</span><span class="p">(</span><span class="s2">&#34;SUPER CTRL&#34;</span><span class="p">,</span> <span class="s2">&#34;H&#34;</span><span class="p">,</span> <span class="s2">&#34;resizeactive&#34;</span><span class="p">,</span> <span class="s2">&#34;-20 0&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="n">hl.binde</span><span class="p">(</span><span class="s2">&#34;SUPER CTRL&#34;</span><span class="p">,</span> <span class="s2">&#34;J&#34;</span><span class="p">,</span> <span class="s2">&#34;resizeactive&#34;</span><span class="p">,</span> <span class="s2">&#34;0 20&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="n">hl.binde</span><span class="p">(</span><span class="s2">&#34;SUPER CTRL&#34;</span><span class="p">,</span> <span class="s2">&#34;K&#34;</span><span class="p">,</span> <span class="s2">&#34;resizeactive&#34;</span><span class="p">,</span> <span class="s2">&#34;0 -20&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="n">hl.binde</span><span class="p">(</span><span class="s2">&#34;SUPER CTRL&#34;</span><span class="p">,</span> <span class="s2">&#34;L&#34;</span><span class="p">,</span> <span class="s2">&#34;resizeactive&#34;</span><span class="p">,</span> <span class="s2">&#34;20 0&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1">-- 滑鼠綁定：SUPER + 左鍵拖曳移動、右鍵拖曳調整大小</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="n">hl.bindm</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;mouse:272&#34;</span><span class="p">,</span> <span class="s2">&#34;movewindow&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="n">hl.bindm</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;mouse:273&#34;</span><span class="p">,</span> <span class="s2">&#34;resizewindow&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="c1">-- Workspace 切換</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;1&#34;</span><span class="p">,</span> <span class="s2">&#34;workspace&#34;</span><span class="p">,</span> <span class="s2">&#34;1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;2&#34;</span><span class="p">,</span> <span class="s2">&#34;workspace&#34;</span><span class="p">,</span> <span class="s2">&#34;2&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;3&#34;</span><span class="p">,</span> <span class="s2">&#34;workspace&#34;</span><span class="p">,</span> <span class="s2">&#34;3&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;4&#34;</span><span class="p">,</span> <span class="s2">&#34;workspace&#34;</span><span class="p">,</span> <span class="s2">&#34;4&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;5&#34;</span><span class="p">,</span> <span class="s2">&#34;workspace&#34;</span><span class="p">,</span> <span class="s2">&#34;5&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;6&#34;</span><span class="p">,</span> <span class="s2">&#34;workspace&#34;</span><span class="p">,</span> <span class="s2">&#34;6&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;7&#34;</span><span class="p">,</span> <span class="s2">&#34;workspace&#34;</span><span class="p">,</span> <span class="s2">&#34;7&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;8&#34;</span><span class="p">,</span> <span class="s2">&#34;workspace&#34;</span><span class="p">,</span> <span class="s2">&#34;8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;9&#34;</span><span class="p">,</span> <span class="s2">&#34;workspace&#34;</span><span class="p">,</span> <span class="s2">&#34;9&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="c1">-- 把視窗送到指定 workspace</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER SHIFT&#34;</span><span class="p">,</span> <span class="s2">&#34;1&#34;</span><span class="p">,</span> <span class="s2">&#34;movetoworkspace&#34;</span><span class="p">,</span> <span class="s2">&#34;1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER SHIFT&#34;</span><span class="p">,</span> <span class="s2">&#34;2&#34;</span><span class="p">,</span> <span class="s2">&#34;movetoworkspace&#34;</span><span class="p">,</span> <span class="s2">&#34;2&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER SHIFT&#34;</span><span class="p">,</span> <span class="s2">&#34;3&#34;</span><span class="p">,</span> <span class="s2">&#34;movetoworkspace&#34;</span><span class="p">,</span> <span class="s2">&#34;3&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="c1">-- 以此類推</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="c1">-- 螢幕截圖</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;Print&#34;</span><span class="p">,</span> <span class="s2">&#34;exec&#34;</span><span class="p">,</span> <span class="s2">&#34;grimblast copy area&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;Print&#34;</span><span class="p">,</span> <span class="s2">&#34;exec&#34;</span><span class="p">,</span> <span class="s2">&#34;grimblast copy output&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="c1">-- 多螢幕焦點切換</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;comma&#34;</span><span class="p">,</span> <span class="s2">&#34;focusmonitor&#34;</span><span class="p">,</span> <span class="s2">&#34;l&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;period&#34;</span><span class="p">,</span> <span class="s2">&#34;focusmonitor&#34;</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER SHIFT&#34;</span><span class="p">,</span> <span class="s2">&#34;comma&#34;</span><span class="p">,</span> <span class="s2">&#34;movewindow&#34;</span><span class="p">,</span> <span class="s2">&#34;mon:l&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER SHIFT&#34;</span><span class="p">,</span> <span class="s2">&#34;period&#34;</span><span class="p">,</span> <span class="s2">&#34;movewindow&#34;</span><span class="p">,</span> <span class="s2">&#34;mon:r&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="設計原則">設計原則</h3>
<ul>
<li><strong>SUPER</strong>（Windows/Command 鍵）當 modifier，避免跟應用程式快捷鍵衝突</li>
<li><strong>方向操作</strong>統一用 vim 的 HJKL，降低記憶負擔</li>
<li><strong>modifier 分層</strong>：SUPER 是焦點、SUPER+SHIFT 是移動、SUPER+CTRL 是調整大小</li>
<li>常用操作（開 terminal、關視窗、切 workspace）放在最順手的位置</li>
</ul>
<h3 id="submap模態快捷鍵">Submap：模態快捷鍵</h3>
<p>Submap 類似 vim 的模式切換——進入某個 submap 後，快捷鍵的意義改變，直到離開。適合需要連續操作的場景（如調整大小）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- 按 SUPER+R 進入 resize 模式</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;SUPER&#34;</span><span class="p">,</span> <span class="s2">&#34;R&#34;</span><span class="p">,</span> <span class="s2">&#34;submap&#34;</span><span class="p">,</span> <span class="s2">&#34;resize&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">-- 定義 resize 模式的快捷鍵</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">hl.submap</span><span class="p">(</span><span class="s2">&#34;resize&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">hl.binde</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;H&#34;</span><span class="p">,</span> <span class="s2">&#34;resizeactive&#34;</span><span class="p">,</span> <span class="s2">&#34;-20 0&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">hl.binde</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;L&#34;</span><span class="p">,</span> <span class="s2">&#34;resizeactive&#34;</span><span class="p">,</span> <span class="s2">&#34;20 0&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">hl.binde</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;K&#34;</span><span class="p">,</span> <span class="s2">&#34;resizeactive&#34;</span><span class="p">,</span> <span class="s2">&#34;0 -20&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">hl.binde</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;J&#34;</span><span class="p">,</span> <span class="s2">&#34;resizeactive&#34;</span><span class="p">,</span> <span class="s2">&#34;0 20&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">hl.bind</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;escape&#34;</span><span class="p">,</span> <span class="s2">&#34;submap&#34;</span><span class="p">,</span> <span class="s2">&#34;reset&#34;</span><span class="p">)</span>   <span class="c1">-- Escape 離開</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">hl.submap</span><span class="p">(</span><span class="s2">&#34;reset&#34;</span><span class="p">)</span></span></span></code></pre></div><p>進入 resize 模式後，直接按 HJKL（不需要 modifier）就能持續調整大小，按 Escape 回到正常模式。</p>
<h3 id="媒體鍵與硬體鍵">媒體鍵與硬體鍵</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- 音量（鎖屏時也能用、長按連續觸發）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">hl.bindel</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;XF86AudioRaiseVolume&#34;</span><span class="p">,</span> <span class="s2">&#34;exec&#34;</span><span class="p">,</span> <span class="s2">&#34;wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">hl.bindel</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;XF86AudioLowerVolume&#34;</span><span class="p">,</span> <span class="s2">&#34;exec&#34;</span><span class="p">,</span> <span class="s2">&#34;wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">hl.bindl</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;XF86AudioMute&#34;</span><span class="p">,</span> <span class="s2">&#34;exec&#34;</span><span class="p">,</span> <span class="s2">&#34;wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">hl.bindl</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;XF86AudioMicMute&#34;</span><span class="p">,</span> <span class="s2">&#34;exec&#34;</span><span class="p">,</span> <span class="s2">&#34;wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">-- 亮度</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">hl.bindel</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;XF86MonBrightnessUp&#34;</span><span class="p">,</span> <span class="s2">&#34;exec&#34;</span><span class="p">,</span> <span class="s2">&#34;brightnessctl s 10%+&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">hl.bindel</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;XF86MonBrightnessDown&#34;</span><span class="p">,</span> <span class="s2">&#34;exec&#34;</span><span class="p">,</span> <span class="s2">&#34;brightnessctl s 10%-&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1">-- 媒體播放</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">hl.bindl</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;XF86AudioPlay&#34;</span><span class="p">,</span> <span class="s2">&#34;exec&#34;</span><span class="p">,</span> <span class="s2">&#34;playerctl play-pause&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">hl.bindl</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;XF86AudioNext&#34;</span><span class="p">,</span> <span class="s2">&#34;exec&#34;</span><span class="p">,</span> <span class="s2">&#34;playerctl next&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">hl.bindl</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;XF86AudioPrev&#34;</span><span class="p">,</span> <span class="s2">&#34;exec&#34;</span><span class="p">,</span> <span class="s2">&#34;playerctl previous&#34;</span><span class="p">)</span></span></span></code></pre></div><blockquote>
<p><strong>[需實機測試]</strong> 媒體鍵（XF86Audio*）、亮度鍵（XF86MonBrightness*）、合蓋開關——這些依賴實體硬體送出正確的 keycode。VM 鍵盤通常沒有這些鍵。</p></blockquote>
<h2 id="環境變數">環境變數</h2>
<p>環境變數在 <code>hl.config()</code> 的 <code>env</code> 區段設定，影響 Hyprland 自身和其下執行的應用程式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- ~/.config/hypr/env.lua</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">hl.config</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">env</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="c1">-- 游標主題</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="s2">&#34;XCURSOR_SIZE, 24&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="s2">&#34;XCURSOR_THEME, Bibata-Modern-Classic&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="s2">&#34;HYPRCURSOR_SIZE, 24&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="s2">&#34;HYPRCURSOR_THEME, Bibata-Modern-Classic&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="c1">-- Qt 應用程式</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="s2">&#34;QT_AUTO_SCREEN_SCALE_FACTOR, 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="s2">&#34;QT_QPA_PLATFORM, wayland;xcb&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="s2">&#34;QT_QPA_PLATFORMTHEME, qt5ct&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="s2">&#34;QT_WAYLAND_DISABLE_WINDOWDECORATION, 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="c1">-- GTK 應用程式</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="s2">&#34;GDK_BACKEND, wayland,x11,*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="c1">-- XDG session</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="s2">&#34;XDG_CURRENT_DESKTOP, Hyprland&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="s2">&#34;XDG_SESSION_TYPE, wayland&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="s2">&#34;XDG_SESSION_DESKTOP, Hyprland&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="c1">-- Electron 應用程式（VS Code, Discord 等）</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="s2">&#34;ELECTRON_OZONE_PLATFORM_HINT, auto&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="c1">-- Firefox</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="s2">&#34;MOZ_ENABLE_WAYLAND, 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><h3 id="nvidia-專用環境變數">NVIDIA 專用環境變數</h3>
<p>如果使用 NVIDIA 顯卡，需要額外設定（AMD 和 Intel 不需要）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- 只在 NVIDIA 機器上加這些</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">hl.config</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">env</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="s2">&#34;LIBVA_DRIVER_NAME, nvidia&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="s2">&#34;GBM_BACKEND, nvidia-drm&#34;</span><span class="p">,</span>             <span class="c1">-- Firefox 若崩潰就移除這行</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="s2">&#34;__GLX_VENDOR_LIBRARY_NAME, nvidia&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="s2">&#34;NVD_BACKEND, direct&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">cursor</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">no_hardware_cursors</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>             <span class="c1">-- NVIDIA 常見的游標問題修正</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">allow_dumb_copy</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><blockquote>
<p><strong>[VM 不適用]</strong> NVIDIA 環境變數和驅動設定在 VM 裡無意義——VM 使用 virtio-gpu 或軟體渲染。這些設定只在實體 NVIDIA 機器上測試。</p></blockquote>
<h3 id="vm-專用環境變數">VM 專用環境變數</h3>
<p>在 VM（UTM/QEMU）裡跑 Hyprland 需要額外的回退設定：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">-- 只在 VM 裡加，實機要移除</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">hl.config</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">env</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="s2">&#34;WLR_RENDERER_ALLOW_SOFTWARE, 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="s2">&#34;LIBGL_ALWAYS_SOFTWARE, 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="s2">&#34;WLR_NO_HARDWARE_CURSORS, 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><blockquote>
<p><strong>[已驗證]</strong> Hyprland 0.55（Aquamarine 渲染後端）+ UTM virtio-gpu-gl-pci 實測：使用 GPU 加速時不需要 <code>WLR_RENDERER</code> 或 <code>LIBGL_ALWAYS_SOFTWARE</code>，Hyprland 自動走 VirGL/Venus 硬體路徑。<code>WLR_NO_HARDWARE_CURSORS</code> 仍需要（virtio-gpu 不支援硬體 cursor）。完整 VM 設定見 <a href="/blog/linux/dotfile/05-hyprland-config/hyprland-vm-setup/" data-link-title="Hyprland VM 環境設定與測試矩陣" data-link-desc="要在 VM 裡測試 Hyprland 配置、或判斷某個設定該在 VM 還是實機驗證時回來讀">VM 環境設定與測試矩陣</a>。</p></blockquote>
]]></content:encoded></item><item><title>配色系統、鎖屏與 GTK 主題</title><link>https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/color-system-theming/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/color-system-theming/</guid><description>&lt;h2 id="hyprlock鎖屏">Hyprlock：鎖屏&lt;/h2>
&lt;p>Hyprlock 是 Hyprland 配套的鎖屏工具，支援模糊背景、自定義佈局：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># ~/.config/hypr/hyprlock.conf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">background &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="nv">monitor&lt;/span> &lt;span class="o">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="nv">path&lt;/span> &lt;span class="o">=&lt;/span> screenshot
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="nv">blur_passes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="nv">blur_size&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">8&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">input-field &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="nv">monitor&lt;/span> &lt;span class="o">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="nv">size&lt;/span> &lt;span class="o">=&lt;/span> 250, &lt;span class="m">50&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="nv">outline_thickness&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="nv">outer_color&lt;/span> &lt;span class="o">=&lt;/span> rgba&lt;span class="o">(&lt;/span>137, 180, 250, 1&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="nv">inner_color&lt;/span> &lt;span class="o">=&lt;/span> rgba&lt;span class="o">(&lt;/span>30, 30, 46, 1&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="nv">font_color&lt;/span> &lt;span class="o">=&lt;/span> rgba&lt;span class="o">(&lt;/span>205, 214, 244, 1&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="nv">placeholder_text&lt;/span> &lt;span class="o">=&lt;/span> &amp;lt;i&amp;gt;Password...&amp;lt;/i&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="nv">fade_on_empty&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="nv">position&lt;/span> &lt;span class="o">=&lt;/span> 0, -50
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="nv">halign&lt;/span> &lt;span class="o">=&lt;/span> center
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="nv">valign&lt;/span> &lt;span class="o">=&lt;/span> center
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">label &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="nv">monitor&lt;/span> &lt;span class="o">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="nv">text&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$TIME&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="nv">font_size&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">64&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="nv">font_family&lt;/span> &lt;span class="o">=&lt;/span> MesloLGS Nerd Font
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="nv">color&lt;/span> &lt;span class="o">=&lt;/span> rgba&lt;span class="o">(&lt;/span>205, 214, 244, 1&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="nv">position&lt;/span> &lt;span class="o">=&lt;/span> 0, &lt;span class="m">80&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="nv">halign&lt;/span> &lt;span class="o">=&lt;/span> center
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="nv">valign&lt;/span> &lt;span class="o">=&lt;/span> center
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="o">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>鎖屏一旦啟動，桌面的「鎖定」狀態由 compositor 透過 Wayland 的 ext-session-lock 協議持有，殺掉鎖屏 process 不等於解鎖。鎖屏的安全模型與測試時的注意事項見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/session-lock/" data-link-title="Wayland Session Lock（鎖屏安全狀態）" data-link-desc="hyprlock / swaylock 畫面卡住、pkill 後進不了桌面、或要在 VM / 自動化環境測試鎖屏時回來讀">Session Lock&lt;/a>。&lt;/p>
&lt;h2 id="配色系統的統一管理">配色系統的統一管理&lt;/h2>
&lt;p>Rice 的視覺品質取決於配色一致性。散亂的色碼（waybar 用一套、wofi 用另一套、mako 又一套）是桌面看起來「雜」的最常見原因。&lt;/p>
&lt;p>管理方式：&lt;/p>
&lt;p>&lt;strong>選定一套配色方案&lt;/strong>。Catppuccin、Tokyo Night、Gruvbox、Nord 是目前最多 Linux ricer 使用的方案，每一套都有完整的色彩定義和各工具的預設配置。&lt;/p>
&lt;p>&lt;strong>建立配色變數檔&lt;/strong>。把色碼集中到一個檔案，其他配置引用它：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># ~/.config/hypr/colors.conf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="nv">$rosewater&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>f5e0dc&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="nv">$flamingo&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>f2cdcd&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="nv">$pink&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>f5c2e7&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="nv">$mauve&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>cba6f7&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="nv">$red&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>f38ba8&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="nv">$maroon&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>eba0ac&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="nv">$peach&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>fab387&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="nv">$yellow&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>f9e2af&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="nv">$green&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>a6e3a1&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="nv">$teal&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>94e2d5&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="nv">$sky&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>89dceb&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="nv">$sapphire&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>74c7ec&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="nv">$blue&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>89b4fa&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="nv">$lavender&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>b4befe&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="nv">$text&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>cdd6f4&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="nv">$subtext1&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>bac2de&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="nv">$overlay0&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>6c7086&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="nv">$surface0&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>313244&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="nv">$base&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>1e1e2e&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="nv">$mantle&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>181825&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="nv">$crust&lt;/span> &lt;span class="o">=&lt;/span> rgb&lt;span class="o">(&lt;/span>11111b&lt;span class="o">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Hyprland 的 &lt;code>source&lt;/code> 可以引用這些變數。Waybar 和 Wofi 的 CSS 無法直接引用 Hyprland 變數，但可以用 build script 或 template 工具（如 pywal、flavours）從一份主設定產生各工具的配色。&lt;/p>
&lt;p>&lt;strong>換配色方案&lt;/strong>時需要改的檔案清單：&lt;/p>
&lt;ul>
&lt;li>Hyprland appearance.conf（邊框、陰影顏色）&lt;/li>
&lt;li>Waybar style.css&lt;/li>
&lt;li>Wofi/Rofi style.css&lt;/li>
&lt;li>Mako config&lt;/li>
&lt;li>Hyprlock config&lt;/li>
&lt;li>Terminal emulator 配色&lt;/li>
&lt;li>Neovim colorscheme&lt;/li>
&lt;li>GTK theme（影響 GUI 應用程式的外觀）&lt;/li>
&lt;/ul>
&lt;p>把這個清單記在 dotfile repo 的 README 裡，換主題時有對照不會漏改。&lt;/p></description><content:encoded><![CDATA[<h2 id="hyprlock鎖屏">Hyprlock：鎖屏</h2>
<p>Hyprlock 是 Hyprland 配套的鎖屏工具，支援模糊背景、自定義佈局：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># ~/.config/hypr/hyprlock.conf</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">background <span class="o">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nv">monitor</span> <span class="o">=</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nv">path</span> <span class="o">=</span> screenshot
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nv">blur_passes</span> <span class="o">=</span> <span class="m">3</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="nv">blur_size</span> <span class="o">=</span> <span class="m">8</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">input-field <span class="o">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="nv">monitor</span> <span class="o">=</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="nv">size</span> <span class="o">=</span> 250, <span class="m">50</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="nv">outline_thickness</span> <span class="o">=</span> <span class="m">2</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="nv">outer_color</span> <span class="o">=</span> rgba<span class="o">(</span>137, 180, 250, 1<span class="o">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nv">inner_color</span> <span class="o">=</span> rgba<span class="o">(</span>30, 30, 46, 1<span class="o">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="nv">font_color</span> <span class="o">=</span> rgba<span class="o">(</span>205, 214, 244, 1<span class="o">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="nv">placeholder_text</span> <span class="o">=</span> &lt;i&gt;Password...&lt;/i&gt;
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="nv">fade_on_empty</span> <span class="o">=</span> <span class="nb">true</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="nv">position</span> <span class="o">=</span> 0, -50
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="nv">halign</span> <span class="o">=</span> center
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="nv">valign</span> <span class="o">=</span> center
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">label <span class="o">{</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="nv">monitor</span> <span class="o">=</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="nv">text</span> <span class="o">=</span> <span class="nv">$TIME</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="nv">font_size</span> <span class="o">=</span> <span class="m">64</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="nv">font_family</span> <span class="o">=</span> MesloLGS Nerd Font
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="nv">color</span> <span class="o">=</span> rgba<span class="o">(</span>205, 214, 244, 1<span class="o">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="nv">position</span> <span class="o">=</span> 0, <span class="m">80</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="nv">halign</span> <span class="o">=</span> center
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="nv">valign</span> <span class="o">=</span> center
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="o">}</span></span></span></code></pre></div><p>鎖屏一旦啟動，桌面的「鎖定」狀態由 compositor 透過 Wayland 的 ext-session-lock 協議持有，殺掉鎖屏 process 不等於解鎖。鎖屏的安全模型與測試時的注意事項見 <a href="/blog/linux/dotfile/knowledge-cards/session-lock/" data-link-title="Wayland Session Lock（鎖屏安全狀態）" data-link-desc="hyprlock / swaylock 畫面卡住、pkill 後進不了桌面、或要在 VM / 自動化環境測試鎖屏時回來讀">Session Lock</a>。</p>
<h2 id="配色系統的統一管理">配色系統的統一管理</h2>
<p>Rice 的視覺品質取決於配色一致性。散亂的色碼（waybar 用一套、wofi 用另一套、mako 又一套）是桌面看起來「雜」的最常見原因。</p>
<p>管理方式：</p>
<p><strong>選定一套配色方案</strong>。Catppuccin、Tokyo Night、Gruvbox、Nord 是目前最多 Linux ricer 使用的方案，每一套都有完整的色彩定義和各工具的預設配置。</p>
<p><strong>建立配色變數檔</strong>。把色碼集中到一個檔案，其他配置引用它：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># ~/.config/hypr/colors.conf</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nv">$rosewater</span> <span class="o">=</span> rgb<span class="o">(</span>f5e0dc<span class="o">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nv">$flamingo</span>  <span class="o">=</span> rgb<span class="o">(</span>f2cdcd<span class="o">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nv">$pink</span>      <span class="o">=</span> rgb<span class="o">(</span>f5c2e7<span class="o">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nv">$mauve</span>     <span class="o">=</span> rgb<span class="o">(</span>cba6f7<span class="o">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nv">$red</span>       <span class="o">=</span> rgb<span class="o">(</span>f38ba8<span class="o">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nv">$maroon</span>    <span class="o">=</span> rgb<span class="o">(</span>eba0ac<span class="o">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nv">$peach</span>     <span class="o">=</span> rgb<span class="o">(</span>fab387<span class="o">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nv">$yellow</span>    <span class="o">=</span> rgb<span class="o">(</span>f9e2af<span class="o">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nv">$green</span>     <span class="o">=</span> rgb<span class="o">(</span>a6e3a1<span class="o">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nv">$teal</span>      <span class="o">=</span> rgb<span class="o">(</span>94e2d5<span class="o">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nv">$sky</span>       <span class="o">=</span> rgb<span class="o">(</span>89dceb<span class="o">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nv">$sapphire</span>  <span class="o">=</span> rgb<span class="o">(</span>74c7ec<span class="o">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nv">$blue</span>      <span class="o">=</span> rgb<span class="o">(</span>89b4fa<span class="o">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="nv">$lavender</span>  <span class="o">=</span> rgb<span class="o">(</span>b4befe<span class="o">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nv">$text</span>      <span class="o">=</span> rgb<span class="o">(</span>cdd6f4<span class="o">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="nv">$subtext1</span>  <span class="o">=</span> rgb<span class="o">(</span>bac2de<span class="o">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="nv">$overlay0</span>  <span class="o">=</span> rgb<span class="o">(</span>6c7086<span class="o">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="nv">$surface0</span>  <span class="o">=</span> rgb<span class="o">(</span>313244<span class="o">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="nv">$base</span>      <span class="o">=</span> rgb<span class="o">(</span>1e1e2e<span class="o">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="nv">$mantle</span>    <span class="o">=</span> rgb<span class="o">(</span>181825<span class="o">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="nv">$crust</span>     <span class="o">=</span> rgb<span class="o">(</span>11111b<span class="o">)</span></span></span></code></pre></div><p>Hyprland 的 <code>source</code> 可以引用這些變數。Waybar 和 Wofi 的 CSS 無法直接引用 Hyprland 變數，但可以用 build script 或 template 工具（如 pywal、flavours）從一份主設定產生各工具的配色。</p>
<p><strong>換配色方案</strong>時需要改的檔案清單：</p>
<ul>
<li>Hyprland appearance.conf（邊框、陰影顏色）</li>
<li>Waybar style.css</li>
<li>Wofi/Rofi style.css</li>
<li>Mako config</li>
<li>Hyprlock config</li>
<li>Terminal emulator 配色</li>
<li>Neovim colorscheme</li>
<li>GTK theme（影響 GUI 應用程式的外觀）</li>
</ul>
<p>把這個清單記在 dotfile repo 的 README 裡，換主題時有對照不會漏改。</p>
<h2 id="gtk--qt-主題">GTK / Qt 主題</h2>
<p>Linux 的 GUI 應用程式分兩大陣營：GTK（GNOME 系）和 Qt（KDE 系）。平鋪式 WM 不自帶主題引擎，需要手動設定：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># GTK 設定</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># ~/.config/gtk-3.0/settings.ini</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="o">[</span>Settings<span class="o">]</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">gtk-theme-name<span class="o">=</span>Catppuccin-Mocha-Standard-Blue-Dark
</span></span><span class="line"><span class="ln">5</span><span class="cl">gtk-icon-theme-name<span class="o">=</span>Papirus-Dark
</span></span><span class="line"><span class="ln">6</span><span class="cl">gtk-font-name<span class="o">=</span>Noto Sans CJK TC <span class="m">11</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">gtk-cursor-theme-name<span class="o">=</span>Bibata-Modern-Classic</span></span></code></pre></div><p>Qt 應用程式用 <code>qt5ct</code> / <code>qt6ct</code> 設定，或用 Kvantum 主題引擎統一風格。</p>
<h2 id="dotfile-結構對應">Dotfile 結構對應</h2>
<p>Rice 涉及的工具最多——waybar、wofi、mako、hyprlock、GTK 各自是獨立的 stow package。邊界切在「一個工具一個 package」，換主題時才能按需更新而不影響其他工具。<code>themes/</code> 放共用的配色變數，不屬於任何單一工具：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">~/dotfiles/
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├── waybar/
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">│   └── .config/
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">│       └── waybar/
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│           ├── config.jsonc
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">│           └── style.css
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">├── wofi/
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">│   └── .config/
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">│       └── wofi/
</span></span><span class="line"><span class="ln">10</span><span class="cl">│           ├── config
</span></span><span class="line"><span class="ln">11</span><span class="cl">│           └── style.css
</span></span><span class="line"><span class="ln">12</span><span class="cl">├── mako/
</span></span><span class="line"><span class="ln">13</span><span class="cl">│   └── .config/
</span></span><span class="line"><span class="ln">14</span><span class="cl">│       └── mako/
</span></span><span class="line"><span class="ln">15</span><span class="cl">│           └── config
</span></span><span class="line"><span class="ln">16</span><span class="cl">├── hyprlock/
</span></span><span class="line"><span class="ln">17</span><span class="cl">│   └── .config/
</span></span><span class="line"><span class="ln">18</span><span class="cl">│       └── hypr/
</span></span><span class="line"><span class="ln">19</span><span class="cl">│           └── hyprlock.conf
</span></span><span class="line"><span class="ln">20</span><span class="cl">├── gtk/
</span></span><span class="line"><span class="ln">21</span><span class="cl">│   └── .config/
</span></span><span class="line"><span class="ln">22</span><span class="cl">│       └── gtk-3.0/
</span></span><span class="line"><span class="ln">23</span><span class="cl">│           └── settings.ini
</span></span><span class="line"><span class="ln">24</span><span class="cl">└── themes/
</span></span><span class="line"><span class="ln">25</span><span class="cl">    └── colors.conf          # 集中配色定義</span></span></code></pre></div><h2 id="rice-的投資報酬判讀">Rice 的投資報酬判讀</h2>
<p>Rice 可以投入的時間沒有上限。務實的分界線：</p>
<ul>
<li><strong>功能性配置</strong>（waybar 顯示正確資訊、wofi 能搜到 app、通知會跳出來）：投入一到兩小時，這是桌面可用的前提</li>
<li><strong>視覺統一</strong>（全域配色一致、字型統一、圓角/間距協調）：投入半天到一天，這是「好看」跟「雜亂」的分界</li>
<li><strong>精雕細節</strong>（自定義動畫曲線、pixel-perfect 對齊、自製 widget）：時間無底洞，看個人興趣</li>
</ul>
<p>前兩層是值得做的——它們改善每天使用的體驗。第三層是嗜好領域，跟「把車改到完美」是同一種動力，不需要理性上的正當性。</p>
]]></content:encoded></item><item><title>Linux 工具選單</title><link>https://tarrragon.github.io/blog/linux/tools/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/tools/</guid><description>&lt;p>Linux 靈活的代價是選擇太多：同一件事往往有一堆工具，同一類桌面也有多種版本與各自的擴充。知道「有哪些選擇、在什麼情境該用哪個」本身就是一種能力。這個系列把常見情境的工具選項整理出來——不只列出名字，而是講清楚每個工具解什麼問題、跟預設工具（如 &lt;code>grep&lt;/code>、&lt;code>find&lt;/code>）差在哪、什麼情境值得換。&lt;/p>
&lt;p>工具依使用環境分三組，因為同一個需求在不同環境下的可用工具與取捨不同：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>環境&lt;/th>
 &lt;th>涵蓋&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="cli/">CLI 環境工具&lt;/a>&lt;/td>
 &lt;td>純終端機下的工具：搜尋、檔案、監控、Git、資料庫、日誌等的現代替代品&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="gui/">圖形桌面工具&lt;/a>&lt;/td>
 &lt;td>有圖形環境時的桌面應用：檔案管理員、桌面環境與擴充，以及它們的依賴取捨&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="remote/">遠端工具&lt;/a>&lt;/td>
 &lt;td>遠端操作專用：終端機多工器、連線與同步、把長任務留在遠端跑&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="為什麼把工具當成選項來教">為什麼把工具當成「選項」來教&lt;/h2>
&lt;p>以搜尋為例：多數教材教 &lt;code>grep&lt;/code> 跟 &lt;code>find&lt;/code>，但實務上很多開發者改用 &lt;code>ripgrep&lt;/code>（&lt;code>rg&lt;/code>）跟 &lt;code>fd&lt;/code>——更快、預設尊重 &lt;code>.gitignore&lt;/code>、輸出更好讀；互動式搜尋再加 &lt;code>fzf&lt;/code>。不同情境有更省力的選擇，&lt;code>grep&lt;/code> 本身並沒有錯。把工具當成「有取捨的選項」來認識，你才能在對的情境挑對工具，而不是永遠只會預設的那個。&lt;/p>
&lt;p>同理，圖形桌面下要一個檔案管理員，Thunar、PCManFM-Qt、Nemo 各有相依與桌面耦合的取捨；桌面環境本身也有 GNOME/KDE/Hyprland 等多種版本，各自還有可擴充的元件。這些「選項」的判讀，正是這個系列要補的。&lt;/p>
&lt;h2 id="在其他情境的落點">在其他情境的落點&lt;/h2>
&lt;p>工具選擇不是孤立的，它接在具體任務上：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="../install/">安裝與機器初始化&lt;/a>——新機器要裝哪些工具、進 dotfile 套件清單一次裝齊。&lt;/li>
&lt;li>&lt;a href="../debug/">除錯與診斷&lt;/a>——除錯用的診斷工具（&lt;code>busctl&lt;/code> / &lt;code>journalctl&lt;/code> / &lt;code>ip neigh&lt;/code> 等）在各情境怎麼用。&lt;/li>
&lt;li>&lt;a href="../dotfile/">Dotfile 管理&lt;/a>——選好的工具怎麼把配置檔版控、跨機器同步。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Linux 靈活的代價是選擇太多：同一件事往往有一堆工具，同一類桌面也有多種版本與各自的擴充。知道「有哪些選擇、在什麼情境該用哪個」本身就是一種能力。這個系列把常見情境的工具選項整理出來——不只列出名字，而是講清楚每個工具解什麼問題、跟預設工具（如 <code>grep</code>、<code>find</code>）差在哪、什麼情境值得換。</p>
<p>工具依使用環境分三組，因為同一個需求在不同環境下的可用工具與取捨不同：</p>
<table>
  <thead>
      <tr>
          <th>環境</th>
          <th>涵蓋</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="cli/">CLI 環境工具</a></td>
          <td>純終端機下的工具：搜尋、檔案、監控、Git、資料庫、日誌等的現代替代品</td>
      </tr>
      <tr>
          <td><a href="gui/">圖形桌面工具</a></td>
          <td>有圖形環境時的桌面應用：檔案管理員、桌面環境與擴充，以及它們的依賴取捨</td>
      </tr>
      <tr>
          <td><a href="remote/">遠端工具</a></td>
          <td>遠端操作專用：終端機多工器、連線與同步、把長任務留在遠端跑</td>
      </tr>
  </tbody>
</table>
<h2 id="為什麼把工具當成選項來教">為什麼把工具當成「選項」來教</h2>
<p>以搜尋為例：多數教材教 <code>grep</code> 跟 <code>find</code>，但實務上很多開發者改用 <code>ripgrep</code>（<code>rg</code>）跟 <code>fd</code>——更快、預設尊重 <code>.gitignore</code>、輸出更好讀；互動式搜尋再加 <code>fzf</code>。不同情境有更省力的選擇，<code>grep</code> 本身並沒有錯。把工具當成「有取捨的選項」來認識，你才能在對的情境挑對工具，而不是永遠只會預設的那個。</p>
<p>同理，圖形桌面下要一個檔案管理員，Thunar、PCManFM-Qt、Nemo 各有相依與桌面耦合的取捨；桌面環境本身也有 GNOME/KDE/Hyprland 等多種版本，各自還有可擴充的元件。這些「選項」的判讀，正是這個系列要補的。</p>
<h2 id="在其他情境的落點">在其他情境的落點</h2>
<p>工具選擇不是孤立的，它接在具體任務上：</p>
<ul>
<li><a href="../install/">安裝與機器初始化</a>——新機器要裝哪些工具、進 dotfile 套件清單一次裝齊。</li>
<li><a href="../debug/">除錯與診斷</a>——除錯用的診斷工具（<code>busctl</code> / <code>journalctl</code> / <code>ip neigh</code> 等）在各情境怎麼用。</li>
<li><a href="../dotfile/">Dotfile 管理</a>——選好的工具怎麼把配置檔版控、跨機器同步。</li>
</ul>
]]></content:encoded></item><item><title>遠端工具</title><link>https://tarrragon.github.io/blog/linux/tools/remote/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/tools/remote/</guid><description>&lt;p>遠端操作的工具選擇，圍繞一個核心需求：&lt;strong>把工作留在遠端、連線斷了也不掉。&lt;/strong> SSH 連線本身是脆弱的（網路一抖就斷），所以遠端工作的關鍵是「就算斷了，遠端的 session 與長任務還在、重連就接回去」，而非追求連線本身多穩。這一組工具都在解這件事的不同面向。&lt;/p>
&lt;h2 id="核心終端機多工器">核心：終端機多工器&lt;/h2>
&lt;p>遠端工作的地基是終端機多工器（&lt;code>tmux&lt;/code> / &lt;code>zellij&lt;/code>）——它把你的 session 常駐在遠端，SSH 斷了 session 不受影響，重連 &lt;code>attach&lt;/code> 就回到原狀。這也是把長任務交給遠端機器無人值守跑的前提。深入配置與比較見 &lt;a href="../cli/">CLI 環境工具&lt;/a> 裡的多工器篇：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="../cli/tmux-persistence-and-basics/">tmux 持久化與基礎&lt;/a>——最通用的多工器，session 持久化的核心概念。&lt;/li>
&lt;li>&lt;a href="../cli/zellij-pane/">zellij 分頁與 pane&lt;/a>——較新、開箱即用、內建佈局的多工器。&lt;/li>
&lt;li>&lt;a href="../cli/zellij-remote-web-client/">zellij 遠端 web 客戶端&lt;/a>——從瀏覽器連遠端 session 的路徑。&lt;/li>
&lt;/ul>
&lt;h2 id="連線與同步工具">連線與同步工具&lt;/h2>
&lt;p>多工器保住 session 之後，還有兩塊獨立的能力：連線層（怎麼接上遠端、斷了怎麼辦）與同步層（本地與遠端的檔案怎麼一致）。這兩塊各有多個工具、解不同弱點，挑錯會很痛——連線存活、可達性、檔案一致是三層不同的問題。&lt;/p>
&lt;ul>
&lt;li>&lt;a href="connection-and-sync-tools/">遠端連線與同步工具選型&lt;/a>——&lt;code>ssh&lt;/code> / &lt;code>mosh&lt;/code>（漫遊不斷線）/ &lt;code>autossh&lt;/code>（自動重連）、&lt;code>tailscale&lt;/code> / &lt;code>wireguard&lt;/code>（NAT 後可達性）、&lt;code>rsync&lt;/code> / &lt;code>sshfs&lt;/code> / &lt;code>mutagen&lt;/code>（三種同步語義）、IDE remote 模式的定位與取捨。&lt;/li>
&lt;/ul>
&lt;h2 id="低頻寬--手機連線下的介面">低頻寬 / 手機連線下的介面&lt;/h2>
&lt;p>頻寬低或從手機 / 平板連線時，只傳純文字的 TUI 介面（ASCII / Unicode 製圖，不傳影像）最穩。監控、圖表、檔案瀏覽、資料庫操作都有這類工具，整理在 &lt;a href="../cli/">CLI 環境工具&lt;/a>。&lt;/p>
&lt;h2 id="遠端連線與-session-的除錯">遠端連線與 session 的除錯&lt;/h2>
&lt;p>遠端連線本身出問題時（連不上、終端機噴亂碼、要從 SSH 操控圖形桌面），是診斷問題而非工具選擇問題，見 &lt;a href="../../debug/ssh-and-terminal-troubleshooting/">除錯與診斷：遠端連線與終端機問題&lt;/a> 與 &lt;a href="../../debug/machine-unreachable/">機器連不到或起不來&lt;/a>。&lt;/p>
&lt;h2 id="把機器交給遠端無人值守">把機器交給遠端無人值守&lt;/h2>
&lt;p>設好讓遠端機器在你離開後自己跑完長任務、把成果送回來，見 &lt;a href="../../install/unattended-remote-work/">Linux 安裝與機器初始化：讓機器跑無人值守的長任務&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>遠端操作的工具選擇，圍繞一個核心需求：<strong>把工作留在遠端、連線斷了也不掉。</strong> SSH 連線本身是脆弱的（網路一抖就斷），所以遠端工作的關鍵是「就算斷了，遠端的 session 與長任務還在、重連就接回去」，而非追求連線本身多穩。這一組工具都在解這件事的不同面向。</p>
<h2 id="核心終端機多工器">核心：終端機多工器</h2>
<p>遠端工作的地基是終端機多工器（<code>tmux</code> / <code>zellij</code>）——它把你的 session 常駐在遠端，SSH 斷了 session 不受影響，重連 <code>attach</code> 就回到原狀。這也是把長任務交給遠端機器無人值守跑的前提。深入配置與比較見 <a href="../cli/">CLI 環境工具</a> 裡的多工器篇：</p>
<ul>
<li><a href="../cli/tmux-persistence-and-basics/">tmux 持久化與基礎</a>——最通用的多工器，session 持久化的核心概念。</li>
<li><a href="../cli/zellij-pane/">zellij 分頁與 pane</a>——較新、開箱即用、內建佈局的多工器。</li>
<li><a href="../cli/zellij-remote-web-client/">zellij 遠端 web 客戶端</a>——從瀏覽器連遠端 session 的路徑。</li>
</ul>
<h2 id="連線與同步工具">連線與同步工具</h2>
<p>多工器保住 session 之後，還有兩塊獨立的能力：連線層（怎麼接上遠端、斷了怎麼辦）與同步層（本地與遠端的檔案怎麼一致）。這兩塊各有多個工具、解不同弱點，挑錯會很痛——連線存活、可達性、檔案一致是三層不同的問題。</p>
<ul>
<li><a href="connection-and-sync-tools/">遠端連線與同步工具選型</a>——<code>ssh</code> / <code>mosh</code>（漫遊不斷線）/ <code>autossh</code>（自動重連）、<code>tailscale</code> / <code>wireguard</code>（NAT 後可達性）、<code>rsync</code> / <code>sshfs</code> / <code>mutagen</code>（三種同步語義）、IDE remote 模式的定位與取捨。</li>
</ul>
<h2 id="低頻寬--手機連線下的介面">低頻寬 / 手機連線下的介面</h2>
<p>頻寬低或從手機 / 平板連線時，只傳純文字的 TUI 介面（ASCII / Unicode 製圖，不傳影像）最穩。監控、圖表、檔案瀏覽、資料庫操作都有這類工具，整理在 <a href="../cli/">CLI 環境工具</a>。</p>
<h2 id="遠端連線與-session-的除錯">遠端連線與 session 的除錯</h2>
<p>遠端連線本身出問題時（連不上、終端機噴亂碼、要從 SSH 操控圖形桌面），是診斷問題而非工具選擇問題，見 <a href="../../debug/ssh-and-terminal-troubleshooting/">除錯與診斷：遠端連線與終端機問題</a> 與 <a href="../../debug/machine-unreachable/">機器連不到或起不來</a>。</p>
<h2 id="把機器交給遠端無人值守">把機器交給遠端無人值守</h2>
<p>設好讓遠端機器在你離開後自己跑完長任務、把成果送回來，見 <a href="../../install/unattended-remote-work/">Linux 安裝與機器初始化：讓機器跑無人值守的長任務</a>。</p>
]]></content:encoded></item><item><title>機器連不到或起不來</title><link>https://tarrragon.github.io/blog/linux/debug/machine-unreachable/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/debug/machine-unreachable/</guid><description>&lt;p>一台原本能連的機器突然連不上，或一台虛擬機根本開不起來，判讀的方向是「從你這端往那台機器，一層一層確認哪裡斷了」，而不是反覆重試同一個連線動作。連線失敗是最終症狀，真正斷掉的可能是網路、可能是那台機器的某個服務沒起來、可能是虛擬機的宿主側出問題、也可能是一個把上面全部拖下水的共同根因：磁碟滿。這篇從網路層與宿主側的權威狀態切入，把「連不上」拆成可定位的環節。&lt;/p>
&lt;h2 id="遠端機器突然連不上先分清是哪一段斷">遠端機器突然連不上：先分清是哪一段斷&lt;/h2>
&lt;p>一台昨天還能 SSH 的機器今天連不上，第一步是確認「網路層通不通」，跟「SSH 服務在不在」分開。連線在 TCP 就 timeout（連 port 22 卡住沒回應），多半是網路層或機器沒在跑；連線有回應但被拒（&lt;code>Connection refused&lt;/code>），是網路通、但那台機器上沒有服務在聽 port 22。&lt;/p>
&lt;p>對虛擬機或同網段的機器，一個很有用的權威來源是&lt;strong>鄰居表&lt;/strong>（IP 對 MAC 的對應）。要填起來需要對方在鏈路層有回應，所以它直接反映「對方在不在」。用 &lt;code>ip neigh&lt;/code> 看目標 IP 的條目——優先用 &lt;code>ip neigh&lt;/code> 而不是 &lt;code>arp -a&lt;/code>，因為 &lt;code>ip&lt;/code>（iproute2）在現代最小系統一定有，&lt;code>arp&lt;/code>（net-tools）常常沒裝、跑了會 command not found 反而誤導。如果狀態是 &lt;code>INCOMPLETE&lt;/code>（&lt;code>arp -a&lt;/code> 顯示的是 &lt;code>incomplete&lt;/code>），代表這個 IP 在鏈路層上根本沒有機器回應——不是 SSH 的問題，是那台機器的網路沒起來、或根本沒在跑。一個實際案例：一台虛擬機 SSH timeout，鄰居表顯示整個網段的 guest 位址全是 incomplete、只有閘道（宿主那側的橋接介面）是好的——這就定位到「宿主的橋沒問題，但橋的另一頭沒有 VM 在講話」，方向立刻從「調 SSH」轉到「去看 VM 的網路或開機狀態」。&lt;/p>
&lt;p>定位到「機器在跑但網路沒起來」後，去那台機器的主控台（不是 SSH，SSH 正是連不上的東西）確認：&lt;code>ip -brief a&lt;/code> 看有沒有拿到 IP、&lt;code>systemctl status &amp;lt;網路服務&amp;gt;&lt;/code>（&lt;code>dhcpcd&lt;/code> / &lt;code>systemd-networkd&lt;/code>）看網路服務起了沒，需要時 &lt;code>sudo systemctl restart &amp;lt;網路服務&amp;gt;&lt;/code> 重拉。IP 回來、鄰居表的條目從 incomplete 變成有 MAC，就通了。&lt;/p>
&lt;p>還有一個常見誤區是 IP 變了。SSH 的別名、金鑰、&lt;code>known_hosts&lt;/code> 都綁在特定機器身分上；換機器 / 重裝 / DHCP 重配後 IP 或 host key 變了，用舊別名會連錯或被 host key 檢查擋。這條的判讀與修法（&lt;code>ssh user@新IP&lt;/code> 直連、&lt;code>ssh-keygen -R&lt;/code>）見 &lt;a href="../../install/ssh-keyless-bootstrap/">外部連入與無 key 的 bootstrap 路徑&lt;/a>。&lt;/p>
&lt;h2 id="網路通但域名解析不了">網路通、但域名解析不了&lt;/h2>
&lt;p>有一種故障看起來像「網路壞了」，其實是 DNS 解析斷了：能連 IP、卻連不上任何用域名的東西——&lt;code>ping 8.8.8.8&lt;/code> 通、但 &lt;code>ping google.com&lt;/code>、&lt;code>pacman -Sy&lt;/code>、&lt;code>curl https://...&lt;/code> 全失敗。判讀要跟前面「網路沒起來」分開，因為網路層是通的，斷的是「域名 → IP」這一步。權威檢查：&lt;code>ping &amp;lt;IP&amp;gt;&lt;/code> 通而 &lt;code>ping &amp;lt;域名&amp;gt;&lt;/code> 不通、或 &lt;code>getent hosts &amp;lt;域名&amp;gt;&lt;/code>（&lt;code>resolvectl query &amp;lt;域名&amp;gt;&lt;/code> 若有 systemd-resolved）解不出位址，就定位到 DNS。常見成因是 &lt;code>/etc/resolv.conf&lt;/code> 沒有可用的 nameserver（新裝或網路重設後沒填），或負責 DNS 的服務沒起來。修：確認 &lt;code>/etc/resolv.conf&lt;/code> 有一行 &lt;code>nameserver&lt;/code>（如 &lt;code>nameserver 1.1.1.1&lt;/code>）、&lt;code>systemctl status systemd-resolved&lt;/code>（若用它）。這一層在剛裝好的最小系統特別常撞到——&lt;code>ip -brief a&lt;/code> 明明有 IP，&lt;code>pacman&lt;/code> 或 bootstrap 卻抓不到套件，看起來像「網路好好的卻裝不了東西」，根因是 DNS 沒設。&lt;/p>
&lt;h2 id="虛擬機開不起來分清-guest-內部還是宿主側">虛擬機開不起來：分清 guest 內部還是宿主側&lt;/h2>
&lt;p>虛擬機開機失敗時，關鍵判斷是「錯誤來自 guest 內部（作業系統層）還是宿主側（虛擬化軟體 / QEMU 層）」。宿主側的錯誤訊息通常來自虛擬機軟體本身、在 guest 還沒開始開機前就跳出來，跟 guest 裡裝了什麼無關。&lt;/p>
&lt;p>一個實例是 QEMU 報「找不到某個 ROM 檔」（例如 &lt;code>efi-virtio.rom&lt;/code>）而拒絕啟動。第一反應可能是「檔案不見了要重裝」，但正確的第一步是&lt;strong>去確認那個檔在不在&lt;/strong>——實際去虛擬機軟體的安裝目錄裡找（&lt;code>find &amp;lt;安裝目錄&amp;gt; -name '&amp;lt;rom名&amp;gt;'&lt;/code>），會發現 ROM 檔明明就在。既然檔案在，「找不到」就不是缺檔，是 QEMU 執行時&lt;strong>在它預期的路徑下找不到&lt;/strong>——成因隨宿主 OS 不同。&lt;strong>在 macOS + UTM 宿主上&lt;/strong>，最常見的是 Gatekeeper app translocation：帶隔離屬性的 app 被搬到一個隨機唯讀路徑跑，讓 QEMU 解析資源的相對路徑失效，明明存在的檔案在那個執行路徑下就找不到。&lt;strong>在 Linux 宿主上&lt;/strong>（沒有 translocation 這回事），同樣的「找不到 ROM」通常是缺對應套件（&lt;code>ovmf&lt;/code> / &lt;code>ipxe-roms&lt;/code> / &lt;code>edk2-ovmf&lt;/code>）、libvirt XML 指的 ROM 路徑錯、或檔案權限不對——一樣先確認檔在哪、QEMU 是用哪個路徑去找。&lt;/p>
&lt;p>另外兩個常見的「VM 起不來」故障也順手一起排除，它們不會特定產生「找不到 ROM」但常伴隨出現：上一次崩潰殘留的 helper 行程卡著（&lt;code>pgrep -af 'qemu|&amp;lt;虛擬機軟體名&amp;gt;'&lt;/code> 找，沒清乾淨會佔住資源），以及宿主磁碟滿（&lt;code>df -h&lt;/code>，啟動要寫暫存 / 狀態檔）。多數情況下，完全退出虛擬機軟體（連殘留 helper 一起清）+ 清出宿主磁碟空間 + 重新啟動，就恢復了。&lt;/p></description><content:encoded><![CDATA[<p>一台原本能連的機器突然連不上，或一台虛擬機根本開不起來，判讀的方向是「從你這端往那台機器，一層一層確認哪裡斷了」，而不是反覆重試同一個連線動作。連線失敗是最終症狀，真正斷掉的可能是網路、可能是那台機器的某個服務沒起來、可能是虛擬機的宿主側出問題、也可能是一個把上面全部拖下水的共同根因：磁碟滿。這篇從網路層與宿主側的權威狀態切入，把「連不上」拆成可定位的環節。</p>
<h2 id="遠端機器突然連不上先分清是哪一段斷">遠端機器突然連不上：先分清是哪一段斷</h2>
<p>一台昨天還能 SSH 的機器今天連不上，第一步是確認「網路層通不通」，跟「SSH 服務在不在」分開。連線在 TCP 就 timeout（連 port 22 卡住沒回應），多半是網路層或機器沒在跑；連線有回應但被拒（<code>Connection refused</code>），是網路通、但那台機器上沒有服務在聽 port 22。</p>
<p>對虛擬機或同網段的機器，一個很有用的權威來源是<strong>鄰居表</strong>（IP 對 MAC 的對應）。要填起來需要對方在鏈路層有回應，所以它直接反映「對方在不在」。用 <code>ip neigh</code> 看目標 IP 的條目——優先用 <code>ip neigh</code> 而不是 <code>arp -a</code>，因為 <code>ip</code>（iproute2）在現代最小系統一定有，<code>arp</code>（net-tools）常常沒裝、跑了會 command not found 反而誤導。如果狀態是 <code>INCOMPLETE</code>（<code>arp -a</code> 顯示的是 <code>incomplete</code>），代表這個 IP 在鏈路層上根本沒有機器回應——不是 SSH 的問題，是那台機器的網路沒起來、或根本沒在跑。一個實際案例：一台虛擬機 SSH timeout，鄰居表顯示整個網段的 guest 位址全是 incomplete、只有閘道（宿主那側的橋接介面）是好的——這就定位到「宿主的橋沒問題，但橋的另一頭沒有 VM 在講話」，方向立刻從「調 SSH」轉到「去看 VM 的網路或開機狀態」。</p>
<p>定位到「機器在跑但網路沒起來」後，去那台機器的主控台（不是 SSH，SSH 正是連不上的東西）確認：<code>ip -brief a</code> 看有沒有拿到 IP、<code>systemctl status &lt;網路服務&gt;</code>（<code>dhcpcd</code> / <code>systemd-networkd</code>）看網路服務起了沒，需要時 <code>sudo systemctl restart &lt;網路服務&gt;</code> 重拉。IP 回來、鄰居表的條目從 incomplete 變成有 MAC，就通了。</p>
<p>還有一個常見誤區是 IP 變了。SSH 的別名、金鑰、<code>known_hosts</code> 都綁在特定機器身分上；換機器 / 重裝 / DHCP 重配後 IP 或 host key 變了，用舊別名會連錯或被 host key 檢查擋。這條的判讀與修法（<code>ssh user@新IP</code> 直連、<code>ssh-keygen -R</code>）見 <a href="../../install/ssh-keyless-bootstrap/">外部連入與無 key 的 bootstrap 路徑</a>。</p>
<h2 id="網路通但域名解析不了">網路通、但域名解析不了</h2>
<p>有一種故障看起來像「網路壞了」，其實是 DNS 解析斷了：能連 IP、卻連不上任何用域名的東西——<code>ping 8.8.8.8</code> 通、但 <code>ping google.com</code>、<code>pacman -Sy</code>、<code>curl https://...</code> 全失敗。判讀要跟前面「網路沒起來」分開，因為網路層是通的，斷的是「域名 → IP」這一步。權威檢查：<code>ping &lt;IP&gt;</code> 通而 <code>ping &lt;域名&gt;</code> 不通、或 <code>getent hosts &lt;域名&gt;</code>（<code>resolvectl query &lt;域名&gt;</code> 若有 systemd-resolved）解不出位址，就定位到 DNS。常見成因是 <code>/etc/resolv.conf</code> 沒有可用的 nameserver（新裝或網路重設後沒填），或負責 DNS 的服務沒起來。修：確認 <code>/etc/resolv.conf</code> 有一行 <code>nameserver</code>（如 <code>nameserver 1.1.1.1</code>）、<code>systemctl status systemd-resolved</code>（若用它）。這一層在剛裝好的最小系統特別常撞到——<code>ip -brief a</code> 明明有 IP，<code>pacman</code> 或 bootstrap 卻抓不到套件，看起來像「網路好好的卻裝不了東西」，根因是 DNS 沒設。</p>
<h2 id="虛擬機開不起來分清-guest-內部還是宿主側">虛擬機開不起來：分清 guest 內部還是宿主側</h2>
<p>虛擬機開機失敗時，關鍵判斷是「錯誤來自 guest 內部（作業系統層）還是宿主側（虛擬化軟體 / QEMU 層）」。宿主側的錯誤訊息通常來自虛擬機軟體本身、在 guest 還沒開始開機前就跳出來，跟 guest 裡裝了什麼無關。</p>
<p>一個實例是 QEMU 報「找不到某個 ROM 檔」（例如 <code>efi-virtio.rom</code>）而拒絕啟動。第一反應可能是「檔案不見了要重裝」，但正確的第一步是<strong>去確認那個檔在不在</strong>——實際去虛擬機軟體的安裝目錄裡找（<code>find &lt;安裝目錄&gt; -name '&lt;rom名&gt;'</code>），會發現 ROM 檔明明就在。既然檔案在，「找不到」就不是缺檔，是 QEMU 執行時<strong>在它預期的路徑下找不到</strong>——成因隨宿主 OS 不同。<strong>在 macOS + UTM 宿主上</strong>，最常見的是 Gatekeeper app translocation：帶隔離屬性的 app 被搬到一個隨機唯讀路徑跑，讓 QEMU 解析資源的相對路徑失效，明明存在的檔案在那個執行路徑下就找不到。<strong>在 Linux 宿主上</strong>（沒有 translocation 這回事），同樣的「找不到 ROM」通常是缺對應套件（<code>ovmf</code> / <code>ipxe-roms</code> / <code>edk2-ovmf</code>）、libvirt XML 指的 ROM 路徑錯、或檔案權限不對——一樣先確認檔在哪、QEMU 是用哪個路徑去找。</p>
<p>另外兩個常見的「VM 起不來」故障也順手一起排除，它們不會特定產生「找不到 ROM」但常伴隨出現：上一次崩潰殘留的 helper 行程卡著（<code>pgrep -af 'qemu|&lt;虛擬機軟體名&gt;'</code> 找，沒清乾淨會佔住資源），以及宿主磁碟滿（<code>df -h</code>，啟動要寫暫存 / 狀態檔）。多數情況下，完全退出虛擬機軟體（連殘留 helper 一起清）+ 清出宿主磁碟空間 + 重新啟動，就恢復了。</p>
<p>判讀通則：<strong>虛擬機開不起來，先讀錯誤訊息判斷是 guest 還是宿主側；宿主側報「找不到某資源」而資源其實存在時，往「QEMU 是用哪個路徑找、那條路徑對不對」查（macOS 是 translocation、Linux 是缺套件 / 路徑 / 權限），再順手排除殘留行程與磁碟滿，而不是急著重裝。</strong></p>
<h2 id="磁碟滿是連鎖故障的共同根因">磁碟滿是連鎖故障的共同根因</h2>
<p>很多看起來各自獨立的故障，共同根因是磁碟滿。磁碟一滿，寫入就會失敗，而系統裡太多東西依賴寫入：SSH session 可能因為寫不了而被斷、正在跑的編譯 / 安裝會中途失敗、log 寫不進去、虛擬機狀態檔存不下導致連不上或開不起來。所以當你在短時間內撞到「連線斷了 + 某個任務失敗 + 服務怪怪的」這種一串症狀時，<code>df -h</code> 應該是很早就要做的檢查——一個廉價的檢查就可能一次解釋掉全部。</p>
<p>這裡有一個容易搞錯的點：<strong>清錯了地方</strong>。宿主跟 guest 是兩個獨立的檔案系統；虛擬機的宿主磁碟滿，跟 guest 內部磁碟滿，是兩件事。如果你 SSH 進 guest 裡 <code>df</code> 看到還有空間就以為沒事，但真正滿的是宿主的磁碟，那問題不會解決。判讀時要分清這串故障是「哪一台機器的哪個檔案系統」滿了——在宿主上 <code>df -h</code> 看宿主、在 guest 裡 <code>df -h</code> 看 guest，兩邊都要確認。清空間也要清在對的那一側。</p>
<h2 id="判讀路由">判讀路由</h2>
<ul>
<li>SSH timeout（TCP 卡住）→ 網路層或機器沒跑，查 <code>ip neigh</code>（<code>INCOMPLETE</code> = 對方沒回應）→ 去主控台看 <code>ip -brief a</code> / 網路服務。</li>
<li><code>Connection refused</code> → 網路通、但沒有服務在聽 → 去機器上確認 sshd 起了沒。</li>
<li>能 ping IP、不能用域名（<code>pacman</code> / <code>curl</code> 失敗）→ DNS 解析問題，查 <code>/etc/resolv.conf</code> 有沒有 nameserver、<code>systemd-resolved</code> 起了沒，不是網路層斷。</li>
<li>連錯 / host key 被擋 → IP 或身分變了，見 <a href="../../install/ssh-keyless-bootstrap/">外部連入與無 key 的 bootstrap 路徑</a>。</li>
<li>虛擬機開不起來、宿主側報「找不到資源」但資源在 → 主因查路徑隔離，再排除殘留行程（<code>pgrep -af 'qemu\|...'</code>）/ 磁碟。</li>
<li>一串症狀同時發生 → 早點 <code>df -h</code>，宿主與 guest 兩側都查，磁碟滿常是共同根因。</li>
</ul>
<p>連不上只是最終症狀，真正的定位靠網路表、服務狀態、資源用量這些權威來源一層層往回推——完整的判讀紀律見 <a href="../diagnosis-read-authoritative-state/">診斷心法</a>。</p>
]]></content:encoded></item><item><title>最小安裝後的工具驗證與補足</title><link>https://tarrragon.github.io/blog/linux/install/minimal-install-verify/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/install/minimal-install-verify/</guid><description>&lt;p>最小化安裝給你的是一台能開機的系統，但「能開機」跟「能用」之間隔著一組「大家都假設存在」但其實沒被裝進去的工具。最小安裝（多數發行版的 &lt;code>base&lt;/code> 之類的套件組）刻意只裝開機與基本運作所需的東西，把工具的選擇權留給你。代價是許多你以為理所當然會在的指令——&lt;code>sudo&lt;/code>、&lt;code>which&lt;/code>、&lt;code>rsync&lt;/code>——一個都沒有。驗證它們在不在，比假設它們在安全。&lt;/p>
&lt;p>這層落差最常在你跑自動化腳本時引爆。一支 bootstrap script 的第一行可能就是 &lt;code>sudo pacman -S ...&lt;/code>，在一台連 &lt;code>sudo&lt;/code> 都沒有的機器上，它連第一步都跨不過去。所以裝好系統後、跑任何自動化之前，先過一輪工具驗證，把缺的補上。&lt;/p>
&lt;h2 id="sudo先有雞還是先有蛋">sudo：先有雞還是先有蛋&lt;/h2>
&lt;p>&lt;code>sudo&lt;/code> 是最容易被假設存在、卻最常缺席的工具，而且它的缺席有一個結構性的麻煩：補它的動作本身需要 root 權限。最小安裝通常不含 sudo。某些安裝程式（如本例的 archboot）即使你勾了「把這個使用者設為管理員」，那個動作也往往只是把使用者加進 &lt;code>wheel&lt;/code> 群組，並沒有真的裝上 sudo、也沒有啟用 sudoers 裡 wheel 群組的授權行。結果就是使用者「名義上是管理員」，但系統裡並沒有 sudo 這支指令。&lt;/p>
&lt;p>這形成一個先有雞還是先有蛋的關卡：bootstrap script 要靠 sudo 來裝套件，但 sudo 自己得先存在。它的解法不能是「把 sudo 寫進套件清單」——那份清單正是靠 sudo 來安裝的。sudo 只能是「跑 bootstrap 之前的前置」，用 root 身分手動補上（&lt;code>su -&lt;/code> 成為 root、&lt;code>echo &amp;gt; 檔案&lt;/code> 重導向、&lt;code>chmod&lt;/code> 設權限這些基礎操作不熟的話，見 &lt;a href="../basic-operations/">安裝過程用到的基礎操作&lt;/a>）：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">su - &lt;span class="c1"># 切到 root（輸入 root 密碼）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">pacman -S --needed sudo &lt;span class="c1"># root 身分裝 sudo，不需要 sudo&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s1">&amp;#39;%wheel ALL=(ALL:ALL) ALL&amp;#39;&lt;/span> &amp;gt; /etc/sudoers.d/10-wheel &lt;span class="c1"># 啟用 wheel 群組授權&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">chmod &lt;span class="m">440&lt;/span> /etc/sudoers.d/10-wheel
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">visudo -c &lt;span class="c1"># 驗證 sudoers 語法，印 parsed OK 才安全&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">exit&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>切回一般使用者後用 &lt;code>sudo -v&lt;/code> 確認——能輸入密碼、沒報「不在 sudoers 檔」就成。這一步揭示一條通則：凡是 bootstrap 自身要依賴的工具，都不能由 bootstrap 來裝，必須當成前置先備好。&lt;code>sudo&lt;/code> 是這類前置最典型的一個。&lt;/p>
&lt;p>上面的指令以 Arch 的 &lt;code>pacman&lt;/code> 為例。Fedora 用 &lt;code>dnf&lt;/code>、Debian/Ubuntu 用 &lt;code>apt&lt;/code>；而 Debian 系的桌面與伺服器映像多半預設就裝了 sudo、也設好了授權，這個缺口主要出現在刻意精簡的 minimal 安裝。換句話說「sudo 是前置」這條判讀軸跨發行版成立，但「你這台到底缺不缺」要靠驗證、不是假設。&lt;/p>
&lt;h2 id="which腳本裡的隱形地雷">which：腳本裡的隱形地雷&lt;/h2>
&lt;p>&lt;code>which&lt;/code> 是另一個最小系統常缺、卻被腳本大量引用的指令，它的缺席會以一種隱晦的方式讓腳本出錯。很多腳本用 &lt;code>$(which zsh)&lt;/code> 之類的寫法取一支程式的完整路徑；在沒有 &lt;code>which&lt;/code> 的系統上，這個命令替換會吐出空字串，而下游拿到空字串的指令可能不會立刻報「找不到 which」，而是報一個看似無關的錯。實測中就遇過 &lt;code>chsh -s &amp;quot;$(which zsh)&amp;quot;&lt;/code> 因為 &lt;code>which&lt;/code> 不存在而變成 &lt;code>chsh -s &amp;quot;&amp;quot;&lt;/code>，最後報的是 &lt;code>chsh: shell must be a full path name&lt;/code>——錯誤訊息完全沒提到真正的元兇。&lt;/p>
&lt;p>正確的做法是用 &lt;code>command -v&lt;/code> 取代 &lt;code>which&lt;/code>。&lt;code>command -v&lt;/code> 是 POSIX 規範的 shell 內建，不依賴任何外部套件，在最小系統上一定存在。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nb">command&lt;/span> -v zsh &lt;span class="c1"># 印出 /usr/bin/zsh；找不到則回傳非零、不印東西&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這條判讀對你自己寫的腳本是「把 &lt;code>which&lt;/code> 全換成 &lt;code>command -v&lt;/code>」，對別人的腳本是「在缺 &lt;code>which&lt;/code> 的系統上，先補 &lt;code>which&lt;/code> 套件或改腳本」。它跟 sudo 的差別在於：&lt;code>which&lt;/code> 的缺席會悄悄製造一個誤導性的下游錯誤，而不是當場大聲報錯，所以更值得在驗證階段主動排掉。&lt;/p>
&lt;h2 id="其他常缺的工具">其他常缺的工具&lt;/h2>
&lt;p>除了 sudo 與 which，最小系統還常缺幾類在自動化裡會用到的工具，各有各的補法。它們不像 sudo 是硬前置，但缺了會在特定步驟卡住。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>工具&lt;/th>
 &lt;th>缺了會怎樣&lt;/th>
 &lt;th>補法&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>rsync&lt;/code>&lt;/td>
 &lt;td>從本機同步 dotfile 進機器時 &lt;code>rsync: command not found&lt;/code>&lt;/td>
 &lt;td>進套件清單；急用時改用 &lt;code>tar&lt;/code> over SSH 過渡&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>ca-certificates&lt;/code>&lt;/td>
 &lt;td>HTTPS / 任何 TLS 連線在憑證驗證直接失敗（沒有信任根）&lt;/td>
 &lt;td>進套件清單；它是下一篇 HTTPS clone 的隱性前置&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>hostname&lt;/code>&lt;/td>
 &lt;td>某些腳本呼叫 &lt;code>hostname&lt;/code> 取主機名時失敗&lt;/td>
 &lt;td>補 &lt;code>inetutils&lt;/code>，或改用 &lt;code>hostnamectl&lt;/code> / 讀 &lt;code>/etc/hostname&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>編譯工具鏈&lt;/td>
 &lt;td>從原始碼或社群套件庫編譯時缺 &lt;code>gcc&lt;/code> / &lt;code>make&lt;/code>&lt;/td>
 &lt;td>補發行版的開發工具組（如 &lt;code>base-devel&lt;/code>）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;code>rsync&lt;/code> 的缺席要特別點出，因為它常被當成理所當然的傳輸工具。最小系統沒有它時，第一次把檔案弄進機器可以用兩邊都有的 &lt;code>tar&lt;/code> 搭配 SSH 過渡：&lt;/p></description><content:encoded><![CDATA[<p>最小化安裝給你的是一台能開機的系統，但「能開機」跟「能用」之間隔著一組「大家都假設存在」但其實沒被裝進去的工具。最小安裝（多數發行版的 <code>base</code> 之類的套件組）刻意只裝開機與基本運作所需的東西，把工具的選擇權留給你。代價是許多你以為理所當然會在的指令——<code>sudo</code>、<code>which</code>、<code>rsync</code>——一個都沒有。驗證它們在不在，比假設它們在安全。</p>
<p>這層落差最常在你跑自動化腳本時引爆。一支 bootstrap script 的第一行可能就是 <code>sudo pacman -S ...</code>，在一台連 <code>sudo</code> 都沒有的機器上，它連第一步都跨不過去。所以裝好系統後、跑任何自動化之前，先過一輪工具驗證，把缺的補上。</p>
<h2 id="sudo先有雞還是先有蛋">sudo：先有雞還是先有蛋</h2>
<p><code>sudo</code> 是最容易被假設存在、卻最常缺席的工具，而且它的缺席有一個結構性的麻煩：補它的動作本身需要 root 權限。最小安裝通常不含 sudo。某些安裝程式（如本例的 archboot）即使你勾了「把這個使用者設為管理員」，那個動作也往往只是把使用者加進 <code>wheel</code> 群組，並沒有真的裝上 sudo、也沒有啟用 sudoers 裡 wheel 群組的授權行。結果就是使用者「名義上是管理員」，但系統裡並沒有 sudo 這支指令。</p>
<p>這形成一個先有雞還是先有蛋的關卡：bootstrap script 要靠 sudo 來裝套件，但 sudo 自己得先存在。它的解法不能是「把 sudo 寫進套件清單」——那份清單正是靠 sudo 來安裝的。sudo 只能是「跑 bootstrap 之前的前置」，用 root 身分手動補上（<code>su -</code> 成為 root、<code>echo &gt; 檔案</code> 重導向、<code>chmod</code> 設權限這些基礎操作不熟的話，見 <a href="../basic-operations/">安裝過程用到的基礎操作</a>）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">su -                                          <span class="c1"># 切到 root（輸入 root 密碼）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">pacman -S --needed sudo                        <span class="c1"># root 身分裝 sudo，不需要 sudo</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nb">echo</span> <span class="s1">&#39;%wheel ALL=(ALL:ALL) ALL&#39;</span> &gt; /etc/sudoers.d/10-wheel   <span class="c1"># 啟用 wheel 群組授權</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">chmod <span class="m">440</span> /etc/sudoers.d/10-wheel
</span></span><span class="line"><span class="ln">5</span><span class="cl">visudo -c                                      <span class="c1"># 驗證 sudoers 語法，印 parsed OK 才安全</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">exit</span></span></code></pre></div><p>切回一般使用者後用 <code>sudo -v</code> 確認——能輸入密碼、沒報「不在 sudoers 檔」就成。這一步揭示一條通則：凡是 bootstrap 自身要依賴的工具，都不能由 bootstrap 來裝，必須當成前置先備好。<code>sudo</code> 是這類前置最典型的一個。</p>
<p>上面的指令以 Arch 的 <code>pacman</code> 為例。Fedora 用 <code>dnf</code>、Debian/Ubuntu 用 <code>apt</code>；而 Debian 系的桌面與伺服器映像多半預設就裝了 sudo、也設好了授權，這個缺口主要出現在刻意精簡的 minimal 安裝。換句話說「sudo 是前置」這條判讀軸跨發行版成立，但「你這台到底缺不缺」要靠驗證、不是假設。</p>
<h2 id="which腳本裡的隱形地雷">which：腳本裡的隱形地雷</h2>
<p><code>which</code> 是另一個最小系統常缺、卻被腳本大量引用的指令，它的缺席會以一種隱晦的方式讓腳本出錯。很多腳本用 <code>$(which zsh)</code> 之類的寫法取一支程式的完整路徑；在沒有 <code>which</code> 的系統上，這個命令替換會吐出空字串，而下游拿到空字串的指令可能不會立刻報「找不到 which」，而是報一個看似無關的錯。實測中就遇過 <code>chsh -s &quot;$(which zsh)&quot;</code> 因為 <code>which</code> 不存在而變成 <code>chsh -s &quot;&quot;</code>，最後報的是 <code>chsh: shell must be a full path name</code>——錯誤訊息完全沒提到真正的元兇。</p>
<p>正確的做法是用 <code>command -v</code> 取代 <code>which</code>。<code>command -v</code> 是 POSIX 規範的 shell 內建，不依賴任何外部套件，在最小系統上一定存在。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">command</span> -v zsh        <span class="c1"># 印出 /usr/bin/zsh；找不到則回傳非零、不印東西</span></span></span></code></pre></div><p>這條判讀對你自己寫的腳本是「把 <code>which</code> 全換成 <code>command -v</code>」，對別人的腳本是「在缺 <code>which</code> 的系統上，先補 <code>which</code> 套件或改腳本」。它跟 sudo 的差別在於：<code>which</code> 的缺席會悄悄製造一個誤導性的下游錯誤，而不是當場大聲報錯，所以更值得在驗證階段主動排掉。</p>
<h2 id="其他常缺的工具">其他常缺的工具</h2>
<p>除了 sudo 與 which，最小系統還常缺幾類在自動化裡會用到的工具，各有各的補法。它們不像 sudo 是硬前置，但缺了會在特定步驟卡住。</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>缺了會怎樣</th>
          <th>補法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>rsync</code></td>
          <td>從本機同步 dotfile 進機器時 <code>rsync: command not found</code></td>
          <td>進套件清單；急用時改用 <code>tar</code> over SSH 過渡</td>
      </tr>
      <tr>
          <td><code>ca-certificates</code></td>
          <td>HTTPS / 任何 TLS 連線在憑證驗證直接失敗（沒有信任根）</td>
          <td>進套件清單；它是下一篇 HTTPS clone 的隱性前置</td>
      </tr>
      <tr>
          <td><code>hostname</code></td>
          <td>某些腳本呼叫 <code>hostname</code> 取主機名時失敗</td>
          <td>補 <code>inetutils</code>，或改用 <code>hostnamectl</code> / 讀 <code>/etc/hostname</code></td>
      </tr>
      <tr>
          <td>編譯工具鏈</td>
          <td>從原始碼或社群套件庫編譯時缺 <code>gcc</code> / <code>make</code></td>
          <td>補發行版的開發工具組（如 <code>base-devel</code>）</td>
      </tr>
  </tbody>
</table>
<p><code>rsync</code> 的缺席要特別點出，因為它常被當成理所當然的傳輸工具。最小系統沒有它時，第一次把檔案弄進機器可以用兩邊都有的 <code>tar</code> 搭配 SSH 過渡：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">tar czf - --exclude <span class="s1">&#39;.git&#39;</span> . <span class="p">|</span> ssh user@host <span class="s1">&#39;mkdir -p ~/dest &amp;&amp; tar xzf - -C ~/dest&#39;</span></span></span></code></pre></div><p>這條的好處是不依賴目標機有 rsync；缺點是它每次都傳全部、沒有 rsync 的增量。在反覆同步的工作流裡，值得早點把 rsync 補進套件清單換取增量傳輸。</p>
<p><code>ca-certificates</code> 最容易在下一步咬人。最小系統可能沒有 CA 信任根，這時任何 HTTPS 連線——包括下一篇主推的「公開 repo 用 HTTPS clone」——會在 TLS 憑證驗證直接失敗，而錯誤訊息常指向 SSL handshake 而非「缺信任根」，容易誤判成網路問題。打算走 HTTPS 取得 dotfile 的機器，先確認 <code>ca-certificates</code> 在。<code>git</code> 與 <code>curl</code> 同理：它們是 bootstrap 取得程式碼的基本工具，下面的驗證迴圈也會檢查，最小系統若沒有要一併補。</p>
<p>剩下兩項的缺席各有觸發時機。<code>hostname</code> 只在腳本明確呼叫它取主機名時才會浮現缺失，而用 <code>hostnamectl</code> 或直接讀 <code>/etc/hostname</code> 可以繞過，所以它常被當成「補了省事、不補也有替代」的軟缺口。編譯工具鏈則是在你要從原始碼或社群套件庫編譯時才需要——純跑預編譯套件的機器可以不裝，但只要你的 dotfile 流程會編譯任何東西（例如從社群套件庫裝桌面元件），它就得進清單。</p>
<h2 id="系統性的驗證">系統性的驗證</h2>
<p>裝好系統後先跑一輪集中驗證、把缺口一次盤出來，比等腳本跑到一半才逐一踩雷省事。驗證的對象是「你接下來的流程會用到、但最小系統可能沒有」的工具。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">for</span> cmd in sudo git curl rsync tar zsh<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="k">if</span> <span class="nb">command</span> -v <span class="s2">&#34;</span><span class="nv">$cmd</span><span class="s2">&#34;</span> &gt;/dev/null 2&gt;<span class="p">&amp;</span>1<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;OK   </span><span class="nv">$cmd</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="k">else</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;缺   </span><span class="nv">$cmd</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">done</span></span></span></code></pre></div><p>這段刻意用 <code>command -v</code> 來檢查（而不是 <code>which</code>），因為要檢查的對象之一正是「外部工具在不在」，用一個一定存在的內建來檢查才不會自己先掛掉。盤出來的缺口分兩類處理：bootstrap 自身依賴的（如 sudo）當前置手動補；其餘的（如 rsync、編譯工具）進套件清單，交給 bootstrap 一起裝。</p>
<h2 id="跟-bootstrap-套件清單的界線">跟 Bootstrap 套件清單的界線</h2>
<p>這篇的驗證跟 <a href="/blog/linux/dotfile/08-sync-bootstrap/bootstrap-script-packages/" data-link-title="Bootstrap Script 與套件清單管理" data-link-desc="寫 dotfile 的 install script、或整理「這台機器裝了什麼」的套件清單時回來讀">模組八的 bootstrap script 設計</a> 是兩件互補的事，界線在「假設」上。bootstrap script 的套件清單假設一個前提：機器已經有能力執行安裝（有 sudo、有 package manager、清單裡的東西都能被裝上）。這篇處理的正是那個前提成立之前的階段——最小系統到底有沒有滿足那些假設，缺的補上，讓 bootstrap 的假設變成事實。</p>
<p>換句話說，套件清單回答「這台機器最終要有哪些套件」，工具驗證回答「這台機器現在夠不夠資格開始跑那份清單」。把兩者分清楚，才不會把 sudo 這種前置誤塞進靠 sudo 安裝的清單裡。</p>
<h2 id="下一步">下一步</h2>
<p>工具補齊、機器有能力執行安裝之後，你還困在一個地方：擠在機器的主控台手打。怎麼從舒適的本機終端機操作它，以及還沒有 SSH key 時怎麼把 dotfile 弄進去，<a href="../ssh-keyless-bootstrap/">外部連入、SSH key 與無 key 的 bootstrap 路徑</a> 處理這兩件事。</p>
]]></content:encoded></item><item><title>日誌判讀與診斷工具</title><link>https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/log-reading-diagnostic-tools/</link><pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/log-reading-diagnostic-tools/</guid><description>&lt;p>恢復操作解決的是「怎麼讓桌面回來」，日誌判讀解決的是「為什麼會壞掉」。前者是急救，後者是找病因。如果同一個問題反覆出現，只做急救不找根因會一直繞圈。&lt;/p>
&lt;h2 id="journalctl系統日誌的主要入口">journalctl：系統日誌的主要入口&lt;/h2>
&lt;p>systemd 的日誌系統（journal）集中收錄所有 service、kernel、user session 的 log。&lt;code>journalctl&lt;/code> 是查詢這些日誌的指令。&lt;/p>
&lt;h3 id="基本用法">基本用法&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 本次開機的所有日誌&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">journalctl -b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 本次開機的錯誤以上等級（err + crit + alert + emerg）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">journalctl -b -p err
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1"># 本次開機，只看最後 50 行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">journalctl -b -n &lt;span class="m">50&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># 上一次開機的日誌（如果問題發生在上次開機、這次重開後想查）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">journalctl -b -1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1"># 即時跟蹤新 log（類似 tail -f）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">journalctl -f&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="過濾特定來源">過濾特定來源&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 只看 Hyprland 相關&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">journalctl -b &lt;span class="p">|&lt;/span> grep -i hypr
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 只看特定 systemd user unit&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">journalctl --user -u waybar -b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1"># 只看 kernel 訊息（等同 dmesg）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">journalctl -b -k
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># 只看某個 process 的 log（用 PID）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">journalctl &lt;span class="nv">_PID&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">12345&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="時間範圍過濾">時間範圍過濾&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 最近 10 分鐘的 log&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">journalctl --since &lt;span class="s2">&amp;#34;10 min ago&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 指定時間區間&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">journalctl --since &lt;span class="s2">&amp;#34;2026-06-30 14:00&amp;#34;&lt;/span> --until &lt;span class="s2">&amp;#34;2026-06-30 14:30&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="dmesgkernel-層訊息">dmesg：Kernel 層訊息&lt;/h2>
&lt;p>&lt;code>dmesg&lt;/code> 顯示 kernel ring buffer 的內容——硬體偵測、driver 載入、硬體錯誤這些 kernel 層面的事件。排查 GPU driver 問題、USB 裝置問題、磁碟錯誤時需要看這裡。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 所有 kernel 訊息（帶時間戳記）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">dmesg -T
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 只看錯誤和警告&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">dmesg -T --level&lt;span class="o">=&lt;/span>err,warn
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1"># GPU 相關（NVIDIA）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">dmesg -T &lt;span class="p">|&lt;/span> grep -i nvidia
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># GPU 相關（AMD）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">dmesg -T &lt;span class="p">|&lt;/span> grep -i amdgpu
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1"># USB 相關（鍵盤滑鼠突然不回應時看這裡）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">dmesg -T &lt;span class="p">|&lt;/span> grep -i usb&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>GPU driver 問題在 dmesg 裡的嚴重度差異很大：&lt;/p>
&lt;p>一般 GPU hang（driver 嘗試自動恢復）：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">[ 123.456] nvidia-modeset: ERROR: ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">[ 123.789] NVRM: Xid (PCI:0000:01:00): 79, pid=1234, ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">[ 124.000] amdgpu: GPU reset begin!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">[ 124.500] amdgpu: GPU reset succeeded&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>NVIDIA 的 &lt;code>Xid&lt;/code> 錯誤代碼表示不同類型的 GPU 錯誤。常見的 &lt;code>Xid 79&lt;/code> 是 GPU fallback，&lt;code>Xid 31&lt;/code> 是 GPU setup failure。完整代碼表可在 NVIDIA 官方文件搜尋「Xid Errors」。&lt;/p></description><content:encoded><![CDATA[<p>恢復操作解決的是「怎麼讓桌面回來」，日誌判讀解決的是「為什麼會壞掉」。前者是急救，後者是找病因。如果同一個問題反覆出現，只做急救不找根因會一直繞圈。</p>
<h2 id="journalctl系統日誌的主要入口">journalctl：系統日誌的主要入口</h2>
<p>systemd 的日誌系統（journal）集中收錄所有 service、kernel、user session 的 log。<code>journalctl</code> 是查詢這些日誌的指令。</p>
<h3 id="基本用法">基本用法</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 本次開機的所有日誌</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">journalctl -b
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 本次開機的錯誤以上等級（err + crit + alert + emerg）</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">journalctl -b -p err
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 本次開機，只看最後 50 行</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">journalctl -b -n <span class="m">50</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 上一次開機的日誌（如果問題發生在上次開機、這次重開後想查）</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">journalctl -b -1
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 即時跟蹤新 log（類似 tail -f）</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">journalctl -f</span></span></code></pre></div><h3 id="過濾特定來源">過濾特定來源</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 只看 Hyprland 相關</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">journalctl -b <span class="p">|</span> grep -i hypr
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 只看特定 systemd user unit</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">journalctl --user -u waybar -b
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 只看 kernel 訊息（等同 dmesg）</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">journalctl -b -k
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 只看某個 process 的 log（用 PID）</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">journalctl <span class="nv">_PID</span><span class="o">=</span><span class="m">12345</span></span></span></code></pre></div><h3 id="時間範圍過濾">時間範圍過濾</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 最近 10 分鐘的 log</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">journalctl --since <span class="s2">&#34;10 min ago&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 指定時間區間</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">journalctl --since <span class="s2">&#34;2026-06-30 14:00&#34;</span> --until <span class="s2">&#34;2026-06-30 14:30&#34;</span></span></span></code></pre></div><h2 id="dmesgkernel-層訊息">dmesg：Kernel 層訊息</h2>
<p><code>dmesg</code> 顯示 kernel ring buffer 的內容——硬體偵測、driver 載入、硬體錯誤這些 kernel 層面的事件。排查 GPU driver 問題、USB 裝置問題、磁碟錯誤時需要看這裡。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 所有 kernel 訊息（帶時間戳記）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">dmesg -T
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 只看錯誤和警告</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">dmesg -T --level<span class="o">=</span>err,warn
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># GPU 相關（NVIDIA）</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">dmesg -T <span class="p">|</span> grep -i nvidia
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># GPU 相關（AMD）</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">dmesg -T <span class="p">|</span> grep -i amdgpu
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># USB 相關（鍵盤滑鼠突然不回應時看這裡）</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">dmesg -T <span class="p">|</span> grep -i usb</span></span></code></pre></div><p>GPU driver 問題在 dmesg 裡的嚴重度差異很大：</p>
<p>一般 GPU hang（driver 嘗試自動恢復）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">[  123.456] nvidia-modeset: ERROR: ...
</span></span><span class="line"><span class="ln">2</span><span class="cl">[  123.789] NVRM: Xid (PCI:0000:01:00): 79, pid=1234, ...
</span></span><span class="line"><span class="ln">3</span><span class="cl">[  124.000] amdgpu: GPU reset begin!
</span></span><span class="line"><span class="ln">4</span><span class="cl">[  124.500] amdgpu: GPU reset succeeded</span></span></code></pre></div><p>NVIDIA 的 <code>Xid</code> 錯誤代碼表示不同類型的 GPU 錯誤。常見的 <code>Xid 79</code> 是 GPU fallback，<code>Xid 31</code> 是 GPU setup failure。完整代碼表可在 NVIDIA 官方文件搜尋「Xid Errors」。</p>
<p>硬體層級故障（嚴重，可能需要檢查硬體）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">[  123.789] NVRM: Xid (PCI:0000:01:00): 79, pid=1234, GPU has fallen off the bus</span></span></code></pre></div><p><code>GPU has fallen off the bus</code> 表示 GPU 跟主機板的 PCIe 連線完全中斷。偶發一次可能是 driver 問題，反覆出現通常是硬體故障（PCIe 供電不足、顯卡接觸不良、過熱）。</p>
<h2 id="hyprctlhyprland-的-runtime-狀態查詢">hyprctl：Hyprland 的 Runtime 狀態查詢</h2>
<p><code>hyprctl</code> 是 Hyprland 提供的命令列控制工具，可以在 compositor 運行中查詢狀態和執行操作。只有在 Hyprland 正在跑的時候才能使用。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 目前所有視窗的資訊</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">hyprctl clients
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 目前的 monitor 設定</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">hyprctl monitors
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 目前的 workspace 資訊</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">hyprctl workspaces
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># Hyprland 版本和 build 資訊</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">hyprctl version
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 重新載入 config（不重啟 compositor）</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">hyprctl reload
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># 查看上一次 config reload 是否有錯誤</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">hyprctl systeminfo</span></span></code></pre></div><p><code>hyprctl reload</code> 是測試 config 變更的安全方式。如果 config 有語法錯誤，reload 會報錯但 compositor 繼續用舊 config 跑，不會崩潰。</p>
<h2 id="systemctlservice-狀態管理">systemctl：Service 狀態管理</h2>
<p>桌面環境的工具（waybar、mako 等）如果用 systemd user unit 管理，可以用 <code>systemctl --user</code> 查看狀態和重啟。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 查看某個 user service 的狀態</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">systemctl --user status waybar
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 輸出範例：</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># waybar.service - Highly customizable Wayland bar</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">#    Loaded: loaded (/usr/lib/systemd/user/waybar.service; enabled)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">#    Active: active (running) since Mon 2026-06-30 10:00:00 CST</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">#    Main PID: 1234 (waybar)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 重啟</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">systemctl --user restart waybar
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 看最近的 log</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">systemctl --user status waybar -n <span class="m">20</span></span></span></code></pre></div><p>如果這些工具不是 systemd unit（在 Hyprland config 裡用 <code>exec-once</code> 啟動的），就不能用 <code>systemctl</code> 管理。改用 <code>pgrep</code> 和 <code>kill</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">pgrep waybar      <span class="c1"># 查看是否在跑</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">killall waybar    <span class="c1"># 停止</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">waybar <span class="p">&amp;</span>          <span class="c1"># 背景啟動</span></span></span></code></pre></div><h2 id="即時資源監控">即時資源監控</h2>
<p>排查效能問題和記憶體耗盡時，需要看即時的系統資源使用情況。</p>
<p><strong>htop</strong>：互動式 process 監控。按 <code>M</code> 可以按記憶體用量排序，按 <code>P</code> 按 CPU 排序。找到佔用異常的 process 後按 <code>F9</code> 可以直接 kill。</p>
<p><strong>btop</strong>：功能更豐富的替代品，顯示 CPU、記憶體、磁碟、網路的即時使用情況，圖形化介面比 htop 直觀。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 安裝</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">sudo pacman -S btop    <span class="c1"># Arch</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">sudo apt install btop  <span class="c1"># Debian/Ubuntu</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 執行</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">btop</span></span></code></pre></div><p><strong>nvidia-smi</strong>：NVIDIA GPU 的專屬監控工具。顯示 GPU 使用率、記憶體、溫度、跑在上面的 process。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 一次性查看</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">nvidia-smi
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 持續監控（每 2 秒更新）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">nvidia-smi -l <span class="m">2</span></span></span></code></pre></div><h2 id="常見-log-pattern-速查">常見 Log Pattern 速查</h2>
<table>
  <thead>
      <tr>
          <th>Pattern</th>
          <th>出處</th>
          <th>代表什麼</th>
          <th>下一步</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>Out of memory: Killed process</code></td>
          <td>journalctl / dmesg</td>
          <td>OOM Killer 殺了某個 process</td>
          <td>檢查被殺的 process 名稱、設定 swap 或 systemd-oomd</td>
      </tr>
      <tr>
          <td><code>GPU has fallen off the bus</code></td>
          <td>dmesg</td>
          <td>NVIDIA GPU 完全失聯</td>
          <td>檢查 PCIe 供電、更新 driver、檢查硬體</td>
      </tr>
      <tr>
          <td><code>Xid ... pid=</code></td>
          <td>dmesg</td>
          <td>NVIDIA GPU 錯誤（Xid 編號對應不同類型的錯誤）</td>
          <td>查 NVIDIA 的 Xid 錯誤代碼表</td>
      </tr>
      <tr>
          <td><code>GPU reset begin</code></td>
          <td>dmesg</td>
          <td>AMD GPU driver 嘗試 reset GPU</td>
          <td>通常會自動恢復，頻繁出現代表 driver 或硬體問題</td>
      </tr>
      <tr>
          <td><code>segfault at</code></td>
          <td>journalctl</td>
          <td>某個 process segfault（記憶體存取違規）</td>
          <td>記下 process 名稱，搜尋該軟體的已知 bug</td>
      </tr>
      <tr>
          <td><code>Failed to start</code></td>
          <td>systemctl status</td>
          <td>systemd unit 啟動失敗</td>
          <td>看完整的 status 輸出和 journalctl log 找原因</td>
      </tr>
      <tr>
          <td><code>config error</code> / <code>parse error</code></td>
          <td>各工具自身的 log</td>
          <td>Config 檔語法錯誤</td>
          <td>檢查最近改過的 config 檔</td>
      </tr>
  </tbody>
</table>
<h2 id="排查流程">排查流程</h2>
<p>這篇是 Hyprland 桌面的具體日誌工具；背後「先讀權威狀態、不靠肉眼猜」的通用診斷心法（每種問題的權威來源、四步流程），見 <a href="/blog/linux/debug/diagnosis-read-authoritative-state/" data-link-title="診斷心法：讀權威狀態，不靠肉眼猜表象" data-link-desc="Linux 上一個現象看起來像 A 卻可能是 B、想建立一套先讀權威狀態再下判斷的除錯紀律、避免看畫面就猜而猜錯時回來讀">Linux 除錯與診斷：診斷心法</a>。遇到桌面環境問題時的判讀順序：</p>
<ol>
<li>
<p><strong>判斷影響範圍</strong>：只有一個視窗壞了、某個工具壞了、整個桌面壞了、還是系統完全不回應？影響範圍決定要看哪一層的 log。</p>
</li>
<li>
<p><strong>看 journalctl</strong>：<code>journalctl -b -p err</code> 先看本次開機有沒有錯誤等級的訊息。大部分 userspace 的問題（compositor crash、工具 crash）會出現在這裡。</p>
</li>
<li>
<p><strong>看 dmesg</strong>：如果 journalctl 沒有明顯線索、或症狀跟硬體有關（畫面凍結、USB 不回應），<code>dmesg -T --level=err,warn</code> 看 kernel 層有沒有硬體或 driver 錯誤。</p>
</li>
<li>
<p><strong>查特定工具的狀態</strong>：<code>systemctl --user status &lt;tool&gt;</code> 或 <code>pgrep &lt;tool&gt;</code> 確認工具是否還活著。如果死了，看它最後的 log 訊息。</p>
</li>
<li>
<p><strong>即時監控</strong>：如果問題是漸進式的（越來越慢、偶爾卡頓），開 <code>btop</code> 或 <code>htop</code> 觀察 CPU 和記憶體的即時趨勢，找出佔用異常的 process。</p>
</li>
</ol>
<h2 id="找到問題後的下一步">找到問題後的下一步</h2>
<p>判讀完 log 確認問題類型後，行動路徑依問題性質分流：</p>
<ul>
<li><strong>Config 錯誤</strong>：直接修 config，用 <code>hyprctl reload</code> 或重啟工具驗證。操作步驟見<a href="/blog/linux/dotfile/07-desktop-maintenance/common-failures-recovery/" data-link-title="常見故障場景與恢復操作" data-link-desc="Hyprland 黑屏、waybar 消失、畫面凍結、記憶體爆掉或 config 寫錯導致進不了桌面時，按症狀查恢復操作">常見故障場景與恢復操作</a>。</li>
<li><strong>軟體 bug</strong>（segfault、特定操作觸發 crash）：到該軟體的 issue tracker（通常在 GitHub）搜尋錯誤訊息。Hyprland 的 issue tracker 在 <code>github.com/hyprwm/Hyprland</code>。回報 bug 時附上 <code>hyprctl systeminfo</code> 的輸出和相關的 journalctl log。</li>
<li><strong>GPU driver 問題</strong>：NVIDIA 用戶檢查是否有更新的 driver 版本（<code>pacman -Syu nvidia</code>）。AMD 用戶的 driver 跟 kernel 綁定，更新 kernel 就更新 driver（<code>pacman -Syu linux</code>）。</li>
<li><strong>硬體故障</strong>（<code>GPU has fallen off the bus</code> 反覆出現）：軟體層面無法解決，需要檢查硬體（PCIe 插槽接觸、供電、溫度）。</li>
</ul>
]]></content:encoded></item><item><title>Linux Tiling WM 生態</title><link>https://tarrragon.github.io/blog/linux/dotfile/04-window-management/linux-tiling-wm/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/04-window-management/linux-tiling-wm/</guid><description>&lt;p>Linux 桌面的視窗管理跑在**顯示協議（display protocol）**上，目前有兩套。&lt;/p>
&lt;p>&lt;strong>X11&lt;/strong>（X Window System）是用了三十多年的傳統協議。成熟、穩定、所有工具都支援。設計上的根本問題是安全性——任何 X11 應用程式都能讀取其他視窗的內容和鍵盤輸入，這是協議層的限制，不是 bug。&lt;/p>
&lt;p>&lt;strong>Wayland&lt;/strong> 是設計來取代 X11 的新協議。每個應用程式只看得到自己的視窗、效能更好、對現代硬體的支援更完整。多數主流發行版已經把 Wayland 設為預設，但部分老舊應用程式和特殊需求（某些螢幕錄製、遠端桌面工具）的支援還在追趕中，這些情境會透過 XWayland（相容層）跑 X11 應用程式。&lt;/p>
&lt;p>新的 tiling WM 主要基於 Wayland 開發，X11 上的老牌（i3, bspwm, dwm）仍然活躍但不再是未來方向。&lt;/p>
&lt;h2 id="主流-tiling-wm">主流 Tiling WM&lt;/h2>
&lt;p>&lt;strong>i3（X11）/ sway（Wayland）&lt;/strong> 是社群最大、文件最齊全、行為最可預測的選擇。i3 跑在 X11 上，sway 是它的 Wayland 移植，配置格式幾乎相同。配置檔用自己的語法，直覺的 &lt;code>key = action&lt;/code> 對應。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl"># sway/config 片段
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">set $mod Mod4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">bindsym $mod+h focus left
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">bindsym $mod+j focus down
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">bindsym $mod+k focus up
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">bindsym $mod+l focus right
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">bindsym $mod+Shift+h move left
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">bindsym $mod+1 workspace number 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">bindsym $mod+2 workspace number 2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>穩定性是 i3/sway 最大的賣點——配置寫好之後很少因為更新而壞掉。適合想要可靠的平鋪工作流、不需要華麗視覺效果的人。&lt;/p>
&lt;p>&lt;strong>Hyprland（Wayland）&lt;/strong> 吸引的是在意桌面視覺品質的平鋪使用者——看 r/unixporn 的 Hyprland 截圖就知道，流暢的視窗切換動畫、圓角、視窗模糊、漸層邊框，這些傳統 tiling WM 社群視為不必要的裝飾，Hyprland 做成內建功能並且認真打磨。配置檔改完即時生效（hot reload），迭代調教的回饋循環很短。&lt;/p>
&lt;p>這份視覺投入換來的 trade-off 是穩定性。開發節奏快意味著偶爾會有 breaking changes——某次更新後配置語法改了（2026 年 4 月的 Lua 遷移就是一例）、某個選項改名或移除。把它當主力桌面，要有「更新後可能要回來調配置」的心理準備。&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">Hyprland 配置&lt;/a>會詳細講它的設定。&lt;/p>
&lt;p>&lt;strong>bspwm（X11）&lt;/strong> 是純粹的 BSP 樹狀分割。它只做一件事：管理視窗的樹狀結構。所有操作透過 &lt;code>bspc&lt;/code> 命令列工具驅動，快捷鍵綁定交給 sxhkd（一個獨立的快捷鍵 daemon）。UNIX 哲學——每個工具只做一件事，組合起來用。&lt;/p>
&lt;p>&lt;strong>dwm（X11）&lt;/strong> 極簡到配置要改 C 原始碼然後重新編譯。不是給「想要方便配置」的人用的，而是給「想要完全理解自己桌面每一行程式碼」的人用的。&lt;/p>
&lt;h2 id="拼裝式桌面的代價">拼裝式桌面的代價&lt;/h2>
&lt;p>Linux tiling WM 的桌面是你自己拼出來的：compositor（Hyprland/sway）負責視窗管理、狀態列（waybar）負責頂部資訊條、啟動器（rofi/wofi）負責 app 搜尋啟動、通知（mako/dunst）負責通知彈窗、鎖屏另外裝。每一塊是不同專案、不同作者。&lt;/p>
&lt;p>好處是每一塊都可以換。壞處是當某件事壞了，你要自己判斷是哪一層的問題。藍牙選單不能點？可能是狀態列的 module 設定錯、可能是 blueman 沒跑、可能是 D-Bus session 有問題。完整桌面環境（KDE/GNOME）幫你整合測試過了，拼裝式桌面沒有這層保障。&lt;/p>
&lt;h2 id="多螢幕的處理">多螢幕的處理&lt;/h2>
&lt;p>多螢幕是自動平鋪比較能展現價值的場景。每個螢幕是獨立的平鋪區域，視窗在哪個螢幕就在那個螢幕的範圍內平鋪。&lt;/p>
&lt;p>快捷鍵跨螢幕操作是標配功能：把焦點跳到另一個螢幕、把當前視窗丟到另一個螢幕（丟過去後自動融入那邊的平鋪佈局）。三螢幕時這個便利性比單螢幕更明顯——純滑鼠拖視窗跨螢幕需要的移動距離很長。&lt;/p>
&lt;p>工作區跟螢幕的綁定方式是各工具差異最大的地方。&lt;/p>
&lt;p>macOS 的 Spaces 是綁定螢幕的——每個螢幕有自己獨立的一組 Spaces。yabai 沿用這個行為，切換工作區時只影響當前螢幕。AeroSpace 用自己的虛擬工作區繞過 macOS 原生 Spaces 的限制，多螢幕操作被普遍認為更穩定、更直覺。&lt;/p>
&lt;p>Hyprland 的 workspace 可以動態指派到不同螢幕——workspace 3 現在在螢幕 A，你可以把它移到螢幕 B。這種模型彈性最大，但也需要清楚的心智模型來管理「哪個 workspace 在哪」。&lt;/p>
&lt;p>熱插拔螢幕（接上、拔掉外接螢幕）是各工具的常見痛點。拔掉螢幕時，該螢幕上的視窗要搬到剩餘螢幕重新平鋪；插上螢幕時，佈局要擴展。多數工具有對應設定（記住螢幕配置、自動還原佈局），但體驗不見得完美，偶爾需要手動整理。&lt;/p>
&lt;h2 id="dotfile-中的視窗管理配置">Dotfile 中的視窗管理配置&lt;/h2>
&lt;p>視窗管理是 dotfile 管理裡「桌面層」的核心。WM 配置檔決定了整個桌面的操作邏輯——快捷鍵怎麼綁、視窗間距多大、哪些 app 要浮動、工作區怎麼分配。&lt;/p>
&lt;p>macOS 工具的配置檔通常是一個檔案：AeroSpace 的 &lt;code>~/.aerospace.toml&lt;/code>、yabai 的 &lt;code>~/.yabairc&lt;/code> + &lt;code>~/.skhdrc&lt;/code>、Amethyst 的 &lt;code>~/.amethyst.yml&lt;/code>。把這些檔案放進 dotfile repo，換 Mac 時就能還原整套視窗管理行為。&lt;/p>
&lt;p>Linux tiling WM 的配置在 &lt;code>~/.config/&lt;/code> 下，通常是一個資料夾：Hyprland 的 &lt;code>~/.config/hypr/&lt;/code>、sway 的 &lt;code>~/.config/sway/&lt;/code>、i3 的 &lt;code>~/.config/i3/&lt;/code>。除了 WM 本身，狀態列（waybar 的 &lt;code>~/.config/waybar/&lt;/code>）、啟動器（rofi 的 &lt;code>~/.config/rofi/&lt;/code>）、通知（mako 的 &lt;code>~/.config/mako/&lt;/code>）等周邊元件的配置也各自有檔案。一套完整的 Linux 平鋪桌面，dotfile repo 裡可能會有十幾個配置目錄——這也是為什麼 Linux 桌面客製化社群那麼依賴 dotfile 管理工具（見&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/" data-link-title="模組一：管理工具與目錄結構" data-link-desc="要把散落在家目錄的配置檔集中版控時，選 bare repo、stow 還是 chezmoi、目錄該怎麼組織">管理工具與目錄結構&lt;/a>）。&lt;/p></description><content:encoded><![CDATA[<p>Linux 桌面的視窗管理跑在**顯示協議（display protocol）**上，目前有兩套。</p>
<p><strong>X11</strong>（X Window System）是用了三十多年的傳統協議。成熟、穩定、所有工具都支援。設計上的根本問題是安全性——任何 X11 應用程式都能讀取其他視窗的內容和鍵盤輸入，這是協議層的限制，不是 bug。</p>
<p><strong>Wayland</strong> 是設計來取代 X11 的新協議。每個應用程式只看得到自己的視窗、效能更好、對現代硬體的支援更完整。多數主流發行版已經把 Wayland 設為預設，但部分老舊應用程式和特殊需求（某些螢幕錄製、遠端桌面工具）的支援還在追趕中，這些情境會透過 XWayland（相容層）跑 X11 應用程式。</p>
<p>新的 tiling WM 主要基於 Wayland 開發，X11 上的老牌（i3, bspwm, dwm）仍然活躍但不再是未來方向。</p>
<h2 id="主流-tiling-wm">主流 Tiling WM</h2>
<p><strong>i3（X11）/ sway（Wayland）</strong> 是社群最大、文件最齊全、行為最可預測的選擇。i3 跑在 X11 上，sway 是它的 Wayland 移植，配置格式幾乎相同。配置檔用自己的語法，直覺的 <code>key = action</code> 對應。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl"># sway/config 片段
</span></span><span class="line"><span class="ln">2</span><span class="cl">set $mod Mod4
</span></span><span class="line"><span class="ln">3</span><span class="cl">bindsym $mod+h focus left
</span></span><span class="line"><span class="ln">4</span><span class="cl">bindsym $mod+j focus down
</span></span><span class="line"><span class="ln">5</span><span class="cl">bindsym $mod+k focus up
</span></span><span class="line"><span class="ln">6</span><span class="cl">bindsym $mod+l focus right
</span></span><span class="line"><span class="ln">7</span><span class="cl">bindsym $mod+Shift+h move left
</span></span><span class="line"><span class="ln">8</span><span class="cl">bindsym $mod+1 workspace number 1
</span></span><span class="line"><span class="ln">9</span><span class="cl">bindsym $mod+2 workspace number 2</span></span></code></pre></div><p>穩定性是 i3/sway 最大的賣點——配置寫好之後很少因為更新而壞掉。適合想要可靠的平鋪工作流、不需要華麗視覺效果的人。</p>
<p><strong>Hyprland（Wayland）</strong> 吸引的是在意桌面視覺品質的平鋪使用者——看 r/unixporn 的 Hyprland 截圖就知道，流暢的視窗切換動畫、圓角、視窗模糊、漸層邊框，這些傳統 tiling WM 社群視為不必要的裝飾，Hyprland 做成內建功能並且認真打磨。配置檔改完即時生效（hot reload），迭代調教的回饋循環很短。</p>
<p>這份視覺投入換來的 trade-off 是穩定性。開發節奏快意味著偶爾會有 breaking changes——某次更新後配置語法改了（2026 年 4 月的 Lua 遷移就是一例）、某個選項改名或移除。把它當主力桌面，要有「更新後可能要回來調配置」的心理準備。<a href="/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">Hyprland 配置</a>會詳細講它的設定。</p>
<p><strong>bspwm（X11）</strong> 是純粹的 BSP 樹狀分割。它只做一件事：管理視窗的樹狀結構。所有操作透過 <code>bspc</code> 命令列工具驅動，快捷鍵綁定交給 sxhkd（一個獨立的快捷鍵 daemon）。UNIX 哲學——每個工具只做一件事，組合起來用。</p>
<p><strong>dwm（X11）</strong> 極簡到配置要改 C 原始碼然後重新編譯。不是給「想要方便配置」的人用的，而是給「想要完全理解自己桌面每一行程式碼」的人用的。</p>
<h2 id="拼裝式桌面的代價">拼裝式桌面的代價</h2>
<p>Linux tiling WM 的桌面是你自己拼出來的：compositor（Hyprland/sway）負責視窗管理、狀態列（waybar）負責頂部資訊條、啟動器（rofi/wofi）負責 app 搜尋啟動、通知（mako/dunst）負責通知彈窗、鎖屏另外裝。每一塊是不同專案、不同作者。</p>
<p>好處是每一塊都可以換。壞處是當某件事壞了，你要自己判斷是哪一層的問題。藍牙選單不能點？可能是狀態列的 module 設定錯、可能是 blueman 沒跑、可能是 D-Bus session 有問題。完整桌面環境（KDE/GNOME）幫你整合測試過了，拼裝式桌面沒有這層保障。</p>
<h2 id="多螢幕的處理">多螢幕的處理</h2>
<p>多螢幕是自動平鋪比較能展現價值的場景。每個螢幕是獨立的平鋪區域，視窗在哪個螢幕就在那個螢幕的範圍內平鋪。</p>
<p>快捷鍵跨螢幕操作是標配功能：把焦點跳到另一個螢幕、把當前視窗丟到另一個螢幕（丟過去後自動融入那邊的平鋪佈局）。三螢幕時這個便利性比單螢幕更明顯——純滑鼠拖視窗跨螢幕需要的移動距離很長。</p>
<p>工作區跟螢幕的綁定方式是各工具差異最大的地方。</p>
<p>macOS 的 Spaces 是綁定螢幕的——每個螢幕有自己獨立的一組 Spaces。yabai 沿用這個行為，切換工作區時只影響當前螢幕。AeroSpace 用自己的虛擬工作區繞過 macOS 原生 Spaces 的限制，多螢幕操作被普遍認為更穩定、更直覺。</p>
<p>Hyprland 的 workspace 可以動態指派到不同螢幕——workspace 3 現在在螢幕 A，你可以把它移到螢幕 B。這種模型彈性最大，但也需要清楚的心智模型來管理「哪個 workspace 在哪」。</p>
<p>熱插拔螢幕（接上、拔掉外接螢幕）是各工具的常見痛點。拔掉螢幕時，該螢幕上的視窗要搬到剩餘螢幕重新平鋪；插上螢幕時，佈局要擴展。多數工具有對應設定（記住螢幕配置、自動還原佈局），但體驗不見得完美，偶爾需要手動整理。</p>
<h2 id="dotfile-中的視窗管理配置">Dotfile 中的視窗管理配置</h2>
<p>視窗管理是 dotfile 管理裡「桌面層」的核心。WM 配置檔決定了整個桌面的操作邏輯——快捷鍵怎麼綁、視窗間距多大、哪些 app 要浮動、工作區怎麼分配。</p>
<p>macOS 工具的配置檔通常是一個檔案：AeroSpace 的 <code>~/.aerospace.toml</code>、yabai 的 <code>~/.yabairc</code> + <code>~/.skhdrc</code>、Amethyst 的 <code>~/.amethyst.yml</code>。把這些檔案放進 dotfile repo，換 Mac 時就能還原整套視窗管理行為。</p>
<p>Linux tiling WM 的配置在 <code>~/.config/</code> 下，通常是一個資料夾：Hyprland 的 <code>~/.config/hypr/</code>、sway 的 <code>~/.config/sway/</code>、i3 的 <code>~/.config/i3/</code>。除了 WM 本身，狀態列（waybar 的 <code>~/.config/waybar/</code>）、啟動器（rofi 的 <code>~/.config/rofi/</code>）、通知（mako 的 <code>~/.config/mako/</code>）等周邊元件的配置也各自有檔案。一套完整的 Linux 平鋪桌面，dotfile repo 裡可能會有十幾個配置目錄——這也是為什麼 Linux 桌面客製化社群那麼依賴 dotfile 管理工具（見<a href="/blog/linux/dotfile/01-dotfile-management/" data-link-title="模組一：管理工具與目錄結構" data-link-desc="要把散落在家目錄的配置檔集中版控時，選 bare repo、stow 還是 chezmoi、目錄該怎麼組織">管理工具與目錄結構</a>）。</p>
<p>跟螢幕硬體綁定的設定（螢幕解析度、縮放比、螢幕排列順序）通常也寫在 WM 配置裡。這部分在跨機器搬移 dotfile 時需要調整——同一份 Hyprland 配置裡的 <code>monitor</code> 設定，在筆電上是一個螢幕、在桌機上可能是三個。常見做法是把硬體相關設定拆到單獨檔案（如 <code>monitors.lua</code>），主配置用 <code>require</code> 引入，這樣跨機器時只需要替換這一個檔案。<a href="/blog/linux/dotfile/08-sync-bootstrap/" data-link-title="模組八：同步、Bootstrap 與環境重建" data-link-desc="換機器或重灌時怎麼還原工作環境 — bootstrap script 設計、套件清單管理、跨機器同步策略、secret 排除，以及 VM 快照和 dotfile 重建兩種思路的場景判讀">同步與環境重建</a>會講跨機器搬移時怎麼處理這類硬體差異。</p>
]]></content:encoded></item><item><title>Rice（桌面視覺客製化）</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/rice/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/rice/</guid><description>&lt;p>Rice 在 Linux 桌面社群指的是&lt;strong>桌面視覺客製化&lt;/strong>——把系統外觀調教成個人化的美學呈現。動詞 ricing 是「正在美化桌面」，名詞 a rice 是「一套美化成果/配置」，做這件事的人叫 ricer。&lt;/p>
&lt;h2 id="詞源">詞源&lt;/h2>
&lt;p>最被廣泛接受的說法是源自汽車改裝文化的 &amp;ldquo;rice burner&amp;rdquo; / &amp;ldquo;ricer&amp;rdquo;——原本指對（通常是日系的）平價車裝上浮誇外觀套件（大尾翼、炫光、貼紙），看起來拉風但實際性能沒提升。後來 Linux 社群借用這個概念：把桌面打扮得花俏好看，本質也是「外觀的炫技」。&lt;/p>
&lt;p>也有人提出 &amp;ldquo;Race Inspired Cosmetic Enhancements&amp;rdquo; 的逆向縮寫，但普遍被認為是事後湊的解釋。&lt;/p>
&lt;p>在 Linux 圈裡，rice 的原始貶意和種族色彩已經淡化，變成中性甚至帶自豪的自稱——r/unixporn 社群就是圍繞 ricing 成果的分享運轉的。&lt;/p>
&lt;h2 id="rice-涵蓋的範圍">Rice 涵蓋的範圍&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>配色方案&lt;/strong>：Catppuccin、Tokyo Night、Gruvbox、Nord 等跨工具統一的色彩定義&lt;/li>
&lt;li>&lt;strong>狀態列&lt;/strong>：Waybar、Eww 的模組設計和 CSS 外觀&lt;/li>
&lt;li>&lt;strong>啟動器&lt;/strong>：Wofi、Rofi 的搜尋框外觀&lt;/li>
&lt;li>&lt;strong>通知&lt;/strong>：Mako、Dunst 的通知氣泡樣式&lt;/li>
&lt;li>&lt;strong>鎖屏&lt;/strong>：Hyprlock 的登入畫面設計&lt;/li>
&lt;li>&lt;strong>桌布&lt;/strong>：靜態桌布或動態桌布（Swww）&lt;/li>
&lt;li>&lt;strong>終端機配色&lt;/strong>：Alacritty / Kitty / Foot 的 ANSI 色碼&lt;/li>
&lt;li>&lt;strong>字型&lt;/strong>：Nerd Font 的 icon glyph&lt;/li>
&lt;/ul>
&lt;p>Caelestia 這類「desktop shell」專案把上述元件統一設計出貨，是「打包好的 rice」。手動逐一挑選和調教各元件是「DIY rice」。兩者的目標相同——視覺上協調、好看、符合個人美學。&lt;/p>
&lt;p>完整的 rice 配置實務見&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">桌面 Rice 設計&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Rice 在 Linux 桌面社群指的是<strong>桌面視覺客製化</strong>——把系統外觀調教成個人化的美學呈現。動詞 ricing 是「正在美化桌面」，名詞 a rice 是「一套美化成果/配置」，做這件事的人叫 ricer。</p>
<h2 id="詞源">詞源</h2>
<p>最被廣泛接受的說法是源自汽車改裝文化的 &ldquo;rice burner&rdquo; / &ldquo;ricer&rdquo;——原本指對（通常是日系的）平價車裝上浮誇外觀套件（大尾翼、炫光、貼紙），看起來拉風但實際性能沒提升。後來 Linux 社群借用這個概念：把桌面打扮得花俏好看，本質也是「外觀的炫技」。</p>
<p>也有人提出 &ldquo;Race Inspired Cosmetic Enhancements&rdquo; 的逆向縮寫，但普遍被認為是事後湊的解釋。</p>
<p>在 Linux 圈裡，rice 的原始貶意和種族色彩已經淡化，變成中性甚至帶自豪的自稱——r/unixporn 社群就是圍繞 ricing 成果的分享運轉的。</p>
<h2 id="rice-涵蓋的範圍">Rice 涵蓋的範圍</h2>
<ul>
<li><strong>配色方案</strong>：Catppuccin、Tokyo Night、Gruvbox、Nord 等跨工具統一的色彩定義</li>
<li><strong>狀態列</strong>：Waybar、Eww 的模組設計和 CSS 外觀</li>
<li><strong>啟動器</strong>：Wofi、Rofi 的搜尋框外觀</li>
<li><strong>通知</strong>：Mako、Dunst 的通知氣泡樣式</li>
<li><strong>鎖屏</strong>：Hyprlock 的登入畫面設計</li>
<li><strong>桌布</strong>：靜態桌布或動態桌布（Swww）</li>
<li><strong>終端機配色</strong>：Alacritty / Kitty / Foot 的 ANSI 色碼</li>
<li><strong>字型</strong>：Nerd Font 的 icon glyph</li>
</ul>
<p>Caelestia 這類「desktop shell」專案把上述元件統一設計出貨，是「打包好的 rice」。手動逐一挑選和調教各元件是「DIY rice」。兩者的目標相同——視覺上協調、好看、符合個人美學。</p>
<p>完整的 rice 配置實務見<a href="/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">桌面 Rice 設計</a>。</p>
]]></content:encoded></item><item><title>Workspace、Window Rules 與外觀</title><link>https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/workspace-rules-appearance/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/workspace-rules-appearance/</guid><description>&lt;p>本篇的配置範例使用 Lua 語法（Hyprland v0.55+）。Lua 基礎見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/lua-scripting-language/" data-link-title="Lua 腳本語言" data-link-desc="在 Hyprland 或 Neovim 配置檔遇到 Lua 語法看不懂時回來讀 — 配置檔需要的最小 Lua 知識">Lua 腳本語言&lt;/a>。&lt;/p>
&lt;h2 id="workspace-設定">Workspace 設定&lt;/h2>
&lt;p>Workspace 是平鋪式桌面的空間管理單位。Hyprland 的 workspace 是動態的——不需要預先定義多少個，按了不存在的 workspace 編號就會自動建立。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-lua" data-lang="lua">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1">-- 把特定 workspace 綁定到特定螢幕&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="n">hl.config&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="n">workspace&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;1, monitor:DP-1, default:true&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;2, monitor:DP-1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;3, monitor:DP-1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;7, monitor:HDMI-A-1, default:true&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;8, monitor:HDMI-A-1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;9, monitor:HDMI-A-1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="p">})&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>常見的使用模式：&lt;/p>
&lt;ul>
&lt;li>Workspace 1-3 放在主螢幕（寫程式用）&lt;/li>
&lt;li>Workspace 7-9 放在副螢幕（瀏覽器、通訊軟體、監控）&lt;/li>
&lt;li>用鍵盤瞬間切換（SUPER + 數字鍵），比 alt-tab 在一堆視窗裡找快&lt;/li>
&lt;/ul>
&lt;h3 id="per-workspace-layout-覆寫">Per-workspace layout 覆寫&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-lua" data-lang="lua">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="n">hl.config&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="n">workspace&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;1, layoutopt:orientation:left&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">-- workspace 1 用左側 master&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;2, layoutopt:orientation:top&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">-- workspace 2 用頂部 master&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="p">})&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;strong>[VM 可測試]&lt;/strong> Workspace 切換、綁定邏輯、per-workspace layout。&lt;br>
&lt;strong>[需實機測試]&lt;/strong> Workspace 綁定到特定實體螢幕的行為。&lt;/p>&lt;/blockquote>
&lt;h2 id="window-rules">Window Rules&lt;/h2>
&lt;p>Window rules 讓特定應用程式在開啟時自動套用設定——指定 workspace、強制浮動、設定大小等。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-lua" data-lang="lua">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="n">hl.config&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="n">windowrule&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="c1">-- 特定 app 自動送到指定 workspace&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;workspace 8, class:^(firefox)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;workspace 9, class:^(Slack)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="c1">-- 設定面板、對話框等維持浮動（不進平鋪）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;float, class:^(pavucontrol)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;float, class:^(nm-connection-editor)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;float, title:^(Open File)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;float, title:^(Save As)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="c1">-- Picture-in-picture 浮動 + 置頂 + 固定大小&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;float, title:^(Picture-in-Picture)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;pin, title:^(Picture-in-Picture)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;size 480 270, title:^(Picture-in-Picture)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;move 100%-490 50, title:^(Picture-in-Picture)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="c1">-- 視窗透明度（active inactive）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;opacity 0.9, class:^(kitty)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;opacity 0.85 0.75, class:^(Code)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="c1">-- 防止 Electron app 自動最大化&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;suppressevent maximize, class:.*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="c1">-- XWayland video bridge（螢幕分享用）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;opacity 0.0 override, class:^(xwaylandvideobridge)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;noanim, class:^(xwaylandvideobridge)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;noinitialfocus, class:^(xwaylandvideobridge)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;maxsize 1 1, class:^(xwaylandvideobridge)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;noblur, class:^(xwaylandvideobridge)$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="p">})&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="查詢應用程式的-class-和-title">查詢應用程式的 class 和 title&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">hyprctl clients &lt;span class="c1"># 列出所有開啟的視窗（含 class、title、PID 等）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">hyprctl activewindow &lt;span class="c1"># 當前焦點視窗的資訊&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Window rules 用 regex 匹配 &lt;code>class&lt;/code> 和 &lt;code>title&lt;/code>。靜態 rules 匹配的是視窗開啟時的初始值（&lt;code>initialTitle&lt;/code> / &lt;code>initialClass&lt;/code>）。&lt;/p></description><content:encoded><![CDATA[<p>本篇的配置範例使用 Lua 語法（Hyprland v0.55+）。Lua 基礎見 <a href="/blog/linux/dotfile/knowledge-cards/lua-scripting-language/" data-link-title="Lua 腳本語言" data-link-desc="在 Hyprland 或 Neovim 配置檔遇到 Lua 語法看不懂時回來讀 — 配置檔需要的最小 Lua 知識">Lua 腳本語言</a>。</p>
<h2 id="workspace-設定">Workspace 設定</h2>
<p>Workspace 是平鋪式桌面的空間管理單位。Hyprland 的 workspace 是動態的——不需要預先定義多少個，按了不存在的 workspace 編號就會自動建立。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- 把特定 workspace 綁定到特定螢幕</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">hl.config</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">workspace</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="s2">&#34;1, monitor:DP-1, default:true&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="s2">&#34;2, monitor:DP-1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="s2">&#34;3, monitor:DP-1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="s2">&#34;7, monitor:HDMI-A-1, default:true&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="s2">&#34;8, monitor:HDMI-A-1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="s2">&#34;9, monitor:HDMI-A-1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><p>常見的使用模式：</p>
<ul>
<li>Workspace 1-3 放在主螢幕（寫程式用）</li>
<li>Workspace 7-9 放在副螢幕（瀏覽器、通訊軟體、監控）</li>
<li>用鍵盤瞬間切換（SUPER + 數字鍵），比 alt-tab 在一堆視窗裡找快</li>
</ul>
<h3 id="per-workspace-layout-覆寫">Per-workspace layout 覆寫</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">hl.config</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">workspace</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="s2">&#34;1, layoutopt:orientation:left&#34;</span><span class="p">,</span>     <span class="c1">-- workspace 1 用左側 master</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="s2">&#34;2, layoutopt:orientation:top&#34;</span><span class="p">,</span>      <span class="c1">-- workspace 2 用頂部 master</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><blockquote>
<p><strong>[VM 可測試]</strong> Workspace 切換、綁定邏輯、per-workspace layout。<br>
<strong>[需實機測試]</strong> Workspace 綁定到特定實體螢幕的行為。</p></blockquote>
<h2 id="window-rules">Window Rules</h2>
<p>Window rules 讓特定應用程式在開啟時自動套用設定——指定 workspace、強制浮動、設定大小等。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">hl.config</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">windowrule</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="c1">-- 特定 app 自動送到指定 workspace</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="s2">&#34;workspace 8, class:^(firefox)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="s2">&#34;workspace 9, class:^(Slack)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="c1">-- 設定面板、對話框等維持浮動（不進平鋪）</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="s2">&#34;float, class:^(pavucontrol)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="s2">&#34;float, class:^(nm-connection-editor)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="s2">&#34;float, title:^(Open File)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s2">&#34;float, title:^(Save As)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="c1">-- Picture-in-picture 浮動 + 置頂 + 固定大小</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="s2">&#34;float, title:^(Picture-in-Picture)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="s2">&#34;pin, title:^(Picture-in-Picture)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="s2">&#34;size 480 270, title:^(Picture-in-Picture)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="s2">&#34;move 100%-490 50, title:^(Picture-in-Picture)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="c1">-- 視窗透明度（active inactive）</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="s2">&#34;opacity 0.9, class:^(kitty)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="s2">&#34;opacity 0.85 0.75, class:^(Code)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="c1">-- 防止 Electron app 自動最大化</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="s2">&#34;suppressevent maximize, class:.*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="c1">-- XWayland video bridge（螢幕分享用）</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="s2">&#34;opacity 0.0 override, class:^(xwaylandvideobridge)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="s2">&#34;noanim, class:^(xwaylandvideobridge)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="s2">&#34;noinitialfocus, class:^(xwaylandvideobridge)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="s2">&#34;maxsize 1 1, class:^(xwaylandvideobridge)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="s2">&#34;noblur, class:^(xwaylandvideobridge)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><h3 id="查詢應用程式的-class-和-title">查詢應用程式的 class 和 title</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">hyprctl clients          <span class="c1"># 列出所有開啟的視窗（含 class、title、PID 等）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">hyprctl activewindow     <span class="c1"># 當前焦點視窗的資訊</span></span></span></code></pre></div><p>Window rules 用 regex 匹配 <code>class</code> 和 <code>title</code>。靜態 rules 匹配的是視窗開啟時的初始值（<code>initialTitle</code> / <code>initialClass</code>）。</p>
<p>Window rules 是讓平鋪式桌面「不只是把所有東西硬塞格子」的關鍵——識別哪些 app 適合浮動、哪些適合指定位置，需要累積使用經驗。</p>
<blockquote>
<p><strong>[VM 可測試]</strong> Window rules 邏輯、workspace 指派、float 規則。查 class/title 也在 VM 裡能做。</p></blockquote>
<h2 id="layout-配置">Layout 配置</h2>
<h3 id="dwindle">Dwindle</h3>
<p>每個新視窗把當前區域一分為二（螺旋式切分），適合不固定視窗數量的工作流：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">hl.config</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">dwindle</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="n">pseudotile</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>       <span class="c1">-- 允許 app 請求 pseudo-tiling（保留原始大小）</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="n">preserve_split</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>   <span class="c1">-- 不自動改變分割方向</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="n">force_split</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span>         <span class="c1">-- 0=跟隨滑鼠, 1=永遠左/上, 2=永遠右/下</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="n">smart_split</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="n">smart_resizing</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><h3 id="master">Master</h3>
<p>一個主區域 + 其餘視窗堆疊在側邊，適合「一個主力視窗 + 多個參考視窗」的模式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">hl.config</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">master</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="n">new_on_top</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>       <span class="c1">-- 新視窗放 master 還是 stack</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="n">orientation</span> <span class="o">=</span> <span class="s2">&#34;left&#34;</span><span class="p">,</span>     <span class="c1">-- master 區域位置：left / right / top / bottom / center</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="n">mfact</span> <span class="o">=</span> <span class="mf">0.55</span><span class="p">,</span>             <span class="c1">-- master 區域佔螢幕的比例</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><p>在 <code>general</code> 裡設定預設 layout：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">hl.config</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">general</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="n">layout</span> <span class="o">=</span> <span class="s2">&#34;dwindle&#34;</span><span class="p">,</span>       <span class="c1">-- 或 &#34;master&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><blockquote>
<p><strong>[VM 可測試]</strong> Layout 切換和參數調整的行為邏輯。</p></blockquote>
<h2 id="外觀設定">外觀設定</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">hl.config</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">general</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="n">gaps_in</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="n">gaps_out</span> <span class="o">=</span> <span class="mi">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="n">border_size</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="p">[</span><span class="s2">&#34;col.active_border&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;rgba(89b4faee) rgba(cba6f7ee) 45deg&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="p">[</span><span class="s2">&#34;col.inactive_border&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;rgba(313244aa)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">decoration</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">rounding</span> <span class="o">=</span> <span class="mi">8</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">blur</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="n">enabled</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="n">size</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span>             <span class="c1">-- 模糊半徑（越高越模糊、越吃 GPU）</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="n">passes</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span>           <span class="c1">-- 模糊次數（越多越平滑、越重）</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">vibrancy</span> <span class="o">=</span> <span class="mf">0.17</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="n">noise</span> <span class="o">=</span> <span class="mf">0.02</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="n">new_optimizations</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">shadow</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            <span class="n">enabled</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="n">range</span> <span class="o">=</span> <span class="mi">15</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">render_power</span> <span class="o">=</span> <span class="mi">3</span><span class="p">,</span>     <span class="c1">-- 1-4，衰減曲線</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="n">color</span> <span class="o">=</span> <span class="s2">&#34;0xee1a1a2e&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="n">dim_inactive</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="n">dim_strength</span> <span class="o">=</span> <span class="mf">0.2</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="n">active_opacity</span> <span class="o">=</span> <span class="mf">1.0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">inactive_opacity</span> <span class="o">=</span> <span class="mf">0.95</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><blockquote>
<p><strong>[需實機測試]</strong> blur 的效能影響和視覺品質——這是 Hyprland 最吃 GPU 的功能。VM 裡建議 <code>blur = { enabled = false }</code> 避免卡頓。shadow 和 rounding 在軟體渲染下可以動但會很慢。</p></blockquote>
<h2 id="動畫設定">動畫設定</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- 定義 bezier 曲線</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">hl.animation</span><span class="p">(</span><span class="s2">&#34;bezier&#34;</span><span class="p">,</span> <span class="s2">&#34;ease&#34;</span><span class="p">,</span> <span class="mf">0.25</span><span class="p">,</span> <span class="mf">0.1</span><span class="p">,</span> <span class="mf">0.25</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">hl.animation</span><span class="p">(</span><span class="s2">&#34;bezier&#34;</span><span class="p">,</span> <span class="s2">&#34;overshot&#34;</span><span class="p">,</span> <span class="mf">0.05</span><span class="p">,</span> <span class="mf">0.9</span><span class="p">,</span> <span class="mf">0.1</span><span class="p">,</span> <span class="mf">1.05</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">-- Spring 物理動畫（較新的替代方案）</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">hl.animation</span><span class="p">(</span><span class="s2">&#34;spring&#34;</span><span class="p">,</span> <span class="s2">&#34;bouncy&#34;</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>   <span class="c1">-- damping, frequency, speed</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">-- 套用動畫</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">hl.animation</span><span class="p">(</span><span class="s2">&#34;animation&#34;</span><span class="p">,</span> <span class="s2">&#34;windows&#34;</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="s2">&#34;ease&#34;</span><span class="p">,</span> <span class="s2">&#34;slide&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">hl.animation</span><span class="p">(</span><span class="s2">&#34;animation&#34;</span><span class="p">,</span> <span class="s2">&#34;windowsOut&#34;</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="s2">&#34;ease&#34;</span><span class="p">,</span> <span class="s2">&#34;popin 80%&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">hl.animation</span><span class="p">(</span><span class="s2">&#34;animation&#34;</span><span class="p">,</span> <span class="s2">&#34;fade&#34;</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="s2">&#34;ease&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">hl.animation</span><span class="p">(</span><span class="s2">&#34;animation&#34;</span><span class="p">,</span> <span class="s2">&#34;workspaces&#34;</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="s2">&#34;ease&#34;</span><span class="p">,</span> <span class="s2">&#34;slide&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1">-- 關閉特定動畫</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">hl.animation</span><span class="p">(</span><span class="s2">&#34;animation&#34;</span><span class="p">,</span> <span class="s2">&#34;windowsMove&#34;</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span></span></span></code></pre></div><p>VM 裡跑 Hyprland 時，建議完全關閉動畫以維持可用的操作速度：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">hl.config</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">animations</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="n">enabled</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><blockquote>
<p><strong>[需實機測試]</strong> 動畫流暢度是 Hyprland 的核心賣點，只有實機能評估。VM 裡的卡頓是 GPU 加速不足造成的，不代表 Hyprland 本身的效能。</p></blockquote>
<h2 id="autostart">Autostart</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">hl.config</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">exec_once</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="c1">-- 桌面元件</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="s2">&#34;waybar&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="s2">&#34;mako&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="s2">&#34;hyprpaper&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="c1">-- 系統服務</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="s2">&#34;/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="s2">&#34;wl-paste --type text --watch cliphist store&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="c1">-- Idle daemon（閒置鎖屏，鎖屏安全模型見 session-lock 卡）</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="s2">&#34;hypridle&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><p><code>exec_once</code> 只在 Hyprland 啟動時跑一次（config reload 不會重複執行）。如果需要每次 reload 都重跑某個指令，用 <code>exec</code>（但多數情況不需要）。</p>
<h2 id="plugin-系統">Plugin 系統</h2>
<p>Hyprland 用 <code>hyprpm</code>（Hyprland Plugin Manager）管理 plugin：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">hyprpm update                                              <span class="c1"># 更新 plugin index</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">hyprpm add https://github.com/hyprwm/hyprland-plugins     <span class="c1"># 加入官方 plugin repo</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">hyprpm <span class="nb">enable</span> hyprexpo                                     <span class="c1"># 啟用 plugin</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">hyprpm disable hyprexpo                                    <span class="c1"># 停用 plugin</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">hyprpm list                                                <span class="c1"># 列出可用 plugin</span></span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>Plugin</th>
          <th>功能</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>hyprexpo</strong></td>
          <td>workspace 鳥瞰（所有 workspace 縮圖一覽）</td>
      </tr>
      <tr>
          <td><strong>hy3</strong></td>
          <td>i3/sway 風格的手動 tiling layout（替代 dwindle）</td>
      </tr>
      <tr>
          <td><strong>hyprspace</strong></td>
          <td>類似 macOS Mission Control 的 workspace 切換動畫</td>
      </tr>
      <tr>
          <td><strong>hyprbars</strong></td>
          <td>視窗標題列（可自訂按鈕）</td>
      </tr>
      <tr>
          <td><strong>hyprtrails</strong></td>
          <td>游標拖尾特效</td>
      </tr>
  </tbody>
</table>
<p>Plugin 的配置寫在 hyprland.lua 裡，是 dotfile 的一部分。</p>
<blockquote>
<p><strong>[VM 可測試]</strong> 不依賴 GPU 的 plugin（hy3、hyprbars）。<br>
<strong>[需實機測試]</strong> 視覺特效類 plugin（hyprexpo、hyprspace、hyprtrails）。</p></blockquote>
<h2 id="dotfile-結構對應">Dotfile 結構對應</h2>
<p>Hyprland 的配置拆成多個 <code>.lua</code> 檔，全部放在同一個 stow package 裡。<code>monitors.lua</code> 是硬體相關的——跨機器搬移時可能要排除或用 template/local 機制處理（見<a href="/blog/linux/dotfile/01-dotfile-management/cross-platform-one-repo/" data-link-title="跨平台共用一個 Repo" data-link-desc="macOS 跟 Linux 要共用同一個 dotfile repo、不想維護兩份時回來讀">跨平台共用一個 Repo</a>）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">~/dotfiles/
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">└── hyprland/
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    └── .config/
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        └── hypr/
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">            ├── hyprland.lua       # 主入口（只有 require 行）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            ├── monitors.lua       # 硬體相關、可能排除或 template
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            ├── keybinds.lua
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            ├── rules.lua
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            ├── autostart.lua
</span></span><span class="line"><span class="ln">10</span><span class="cl">            ├── appearance.lua
</span></span><span class="line"><span class="ln">11</span><span class="cl">            └── env.lua</span></span></code></pre></div><h2 id="穩定性與維護的務實面">穩定性與維護的務實面</h2>
<p>Hyprland 的開發節奏快、功能更新激進。v0.55 的 Lua 遷移就是一個典型案例——配置格式整個換掉，舊的 <code>.conf</code> 只會再支援一到兩個版本。配置檔的語法和可用選項會隨版本變動。</p>
<p>應對策略：</p>
<ul>
<li>更新前先看 Hyprland wiki 的 Configuring 頁面和 changelog</li>
<li>dotfile repo 的 commit message 記錄「因應 Hyprland vX.Y 改了什麼設定」</li>
<li>如果用 Arch 的 rolling release，<code>pacman -Syu</code> 前先確認 Hyprland 是否有 breaking change（Arch 社群通常會在論壇預警）</li>
<li>官方提供遷移工具（如 <code>hyprlang2lua</code>），格式變更時優先使用</li>
</ul>
<p>這是<a href="/blog/linux/dotfile/04-window-management/" data-link-title="模組四：視窗管理與平鋪式工作流" data-link-desc="同時開多個視窗時的排列策略 — 手動貼齊跟自動平鋪的差距在哪、macOS 和 Linux 各有哪些工具、多螢幕怎麼處理、什麼情境值得從浮動切換到平鋪">視窗管理與平鋪式工作流</a>提過的代價——把日常桌面建立在高速移動的專案上，持續的配置維護是實際成本。</p>
<h2 id="vm-與實機測試對照">VM 與實機測試對照</h2>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>VM 可測試</th>
          <th>需實機測試</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>配置語法 / 結構</td>
          <td>可</td>
          <td></td>
      </tr>
      <tr>
          <td>Keybind 設計</td>
          <td>可</td>
          <td></td>
      </tr>
      <tr>
          <td>Window rules 邏輯</td>
          <td>可</td>
          <td></td>
      </tr>
      <tr>
          <td>Workspace 切換</td>
          <td>可</td>
          <td></td>
      </tr>
      <tr>
          <td>Layout 參數</td>
          <td>可</td>
          <td></td>
      </tr>
      <tr>
          <td>Autostart 順序</td>
          <td>可</td>
          <td></td>
      </tr>
      <tr>
          <td>Plugin 配置</td>
          <td>部分</td>
          <td>視覺類</td>
      </tr>
      <tr>
          <td>動畫流暢度</td>
          <td></td>
          <td>必要</td>
      </tr>
      <tr>
          <td>Blur 效能 / 品質</td>
          <td></td>
          <td>必要</td>
      </tr>
      <tr>
          <td>多螢幕排列</td>
          <td></td>
          <td>必要</td>
      </tr>
      <tr>
          <td>HiDPI 縮放</td>
          <td></td>
          <td>必要</td>
      </tr>
      <tr>
          <td>觸控板 / 手勢</td>
          <td></td>
          <td>必要</td>
      </tr>
      <tr>
          <td>媒體鍵 / 亮度鍵</td>
          <td></td>
          <td>必要</td>
      </tr>
      <tr>
          <td>NVIDIA 驅動設定</td>
          <td></td>
          <td>必要</td>
      </tr>
      <tr>
          <td>螢幕分享</td>
          <td></td>
          <td>必要</td>
      </tr>
      <tr>
          <td>休眠 / 喚醒</td>
          <td></td>
          <td>必要</td>
      </tr>
      <tr>
          <td>合蓋行為</td>
          <td></td>
          <td>必要</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>安裝期套件與網路故障排除：pacman / DNS / mirror / keyring</title><link>https://tarrragon.github.io/blog/linux/install/package-and-network-troubleshooting/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/install/package-and-network-troubleshooting/</guid><description>&lt;p>裝好 OS、第一次跑套件管理器抓 bootstrap 要的東西時，最常撞的一類故障是「套件裝不下來」。這類故障的第一步判讀，是把它拆成兩層完全不同的問題：&lt;strong>連不到（網路 / DNS / mirror）&lt;/strong>，還是&lt;strong>連得到但被拒（套件管理器自己的狀態）&lt;/strong>。這兩層的檢查工具、根因、修法都不一樣，先分對層再往下查，才不會拿修 DNS 的方法去治簽章過期。這篇以 Arch 的 &lt;code>pacman&lt;/code> 為主要案例（本系列 VM 實測踩過的坑），其他發行版的套件管理器概念對應相同。&lt;/p>
&lt;h2 id="第一步分連不到還是連得到但被拒">第一步：分「連不到」還是「連得到但被拒」&lt;/h2>
&lt;p>錯誤訊息本身就能分層，不用猜：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>訊息提到主機名解不出、連線逾時、retrieving file 失敗&lt;/strong> → 連不到，往網路 / DNS / mirror 查。&lt;/li>
&lt;li>&lt;strong>訊息提到 database lock、signature、trust、conflicting、partial&lt;/strong> → 連得到、封包也拿到了，是套件管理器的狀態問題。&lt;/li>
&lt;/ul>
&lt;p>判準是問一句：「它到底有沒有成功連上 mirror？」有連上才談得到簽章、相依、db 狀態；連都沒連上，那些都還輪不到。剛裝好的最小系統最常見的是前者——網路設定還沒到位。&lt;/p>
&lt;h2 id="連不到那層從實體介面往上查到域名">連不到那層：從實體介面往上查到域名&lt;/h2>
&lt;p>網路不通有好幾層，從最底層往上逐層確認，哪一層斷了一目了然。這條鏈跟&lt;a href="../minimal-install-verify/">最小安裝後的驗證&lt;/a>裡的網路檢查同源，這裡聚焦在「抓套件失敗」這個症狀上：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">ip -brief a &lt;span class="c1"># 1. 有沒有拿到 IP？介面 UP 且有位址&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">ping -c1 8.8.8.8 &lt;span class="c1"># 2. IP 層對外通不通？（直接打 IP、跳過 DNS）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">getent hosts archlinux.org &lt;span class="c1"># 3. 域名解得出來嗎？&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">timedatectl &lt;span class="c1"># 4. 時間對嗎？（影響下一層的簽章驗證）&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>第 2 步通、第 3 步不通 = DNS 問題&lt;/strong>，這是最小安裝最典型的落點：IP 層明明通（&lt;code>ping 8.8.8.8&lt;/code> 有回應），但域名解不出來，因為 &lt;code>/etc/resolv.conf&lt;/code> 還沒設 nameserver。這時 pacman 會卡在解析 mirror 主機名。修法是給系統一個 resolver——臨時可直接寫 &lt;code>/etc/resolv.conf&lt;/code>（&lt;code>nameserver 1.1.1.1&lt;/code>）。先看它是什麼（&lt;code>ls -l /etc/resolv.conf&lt;/code>）：啟用了 &lt;code>systemd-resolved&lt;/code> 或 NetworkManager 的系統上它是那些服務管理的 symlink，手寫會被覆蓋，治本要透過該網路管理服務設定 DNS；裸 Arch 最小安裝若沒啟用這些服務，它通常就是一個普通檔案，手寫即持久生效。&lt;/p>
&lt;p>&lt;strong>mirror 逾時 / 抓不到&lt;/strong>：DNS 通了、但某個 mirror 慢或掛了。換 &lt;code>/etc/pacman.d/mirrorlist&lt;/code> 到地理近且快的鏡像（實測不同 mirror 速度可差數倍）。這也接回&lt;a href="../install-option-decisions/">安裝選項判讀&lt;/a>裡選 mirror 的決策——裝機當下選錯 mirror，這裡就會慢。&lt;/p>
&lt;h2 id="連得到但被拒那層pacman-自己的狀態">連得到但被拒那層：pacman 自己的狀態&lt;/h2>
&lt;p>連上 mirror、封包也拿到了卻失敗，問題在 pacman 的本地狀態或簽章驗證。這幾種各有明確徵兆與修法：&lt;/p>
&lt;h3 id="database-lock上次沒清乾淨的殘留">database lock：上次沒清乾淨的殘留&lt;/h3>
&lt;p>&lt;code>error: failed to init transaction (unable to lock database)&lt;/code>。pacman 用 &lt;code>/var/lib/pacman/db.lck&lt;/code> 這個鎖檔保證同時只有一個 pacman 在動資料庫；上次 pacman 被中斷（斷電、Ctrl+C、當掉）沒清掉鎖檔就會殘留。&lt;strong>先確認真的沒有 pacman 在跑&lt;/strong>（&lt;code>pgrep -x pacman&lt;/code>），確認沒有再刪鎖檔：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">pgrep -x pacman &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;有 pacman 在跑、別刪&amp;#34;&lt;/span> &lt;span class="o">||&lt;/span> sudo rm /var/lib/pacman/db.lck&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>先查再刪這個順序重要——盲刪鎖檔時如果真的有另一個 pacman 在跑，兩個同時寫資料庫會弄壞它。&lt;/p>
&lt;h3 id="簽章--keyring-過期十之八九是時間不對">簽章 / keyring 過期：十之八九是時間不對&lt;/h3>
&lt;p>&lt;code>invalid or corrupted package (PGP signature)&lt;/code> 或 &lt;code>signature is unknown trust&lt;/code>。pacman 驗證每個套件的 GPG 簽章，驗證失敗最常見的根因是&lt;strong>系統時間不對&lt;/strong>——這正是第一步要 &lt;code>timedatectl&lt;/code> 的原因。時間差太多（新裝的 VM、主機板電池沒電的老機器）會讓「簽章的有效期」判斷錯誤，明明有效的簽章被判過期。先校時：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">sudo timedatectl set-ntp &lt;span class="nb">true&lt;/span> &lt;span class="c1"># 開 NTP 自動校時（SSH 進最小系統無 polkit 互動代理、裸跑會被拒，要 sudo）&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>時間對了還失敗，才是 keyring 本身的問題（archlinux-keyring 太舊）：&lt;code>sudo pacman -Sy archlinux-keyring&lt;/code> 更新 keyring，必要時 &lt;code>sudo pacman-key --refresh-keys&lt;/code>。順序是先校時再動 keyring，因為時間不對時連 keyring 都更新不了。&lt;/p>
&lt;h3 id="partial-upgrade只同步不升級造成的相依斷裂">partial upgrade：只同步不升級造成的相依斷裂&lt;/h3>
&lt;p>&lt;code>conflicting dependencies&lt;/code> 或裝完某個套件後系統行為異常。根因是在 rolling 發行版上只做了 &lt;code>pacman -Sy&lt;/code>（同步資料庫）就裝新套件，卻沒 &lt;code>-u&lt;/code>（升級既有套件）——新套件依賴新版函式庫，但系統還是舊的，相依對不上。Arch 只支援 full upgrade：&lt;strong>一律 &lt;code>pacman -Syu&lt;/code>，永遠不要單獨 &lt;code>-Sy&lt;/code> 之後裝東西&lt;/strong>。這條規則救掉這一整類故障。&lt;/p></description><content:encoded><![CDATA[<p>裝好 OS、第一次跑套件管理器抓 bootstrap 要的東西時，最常撞的一類故障是「套件裝不下來」。這類故障的第一步判讀，是把它拆成兩層完全不同的問題：<strong>連不到（網路 / DNS / mirror）</strong>，還是<strong>連得到但被拒（套件管理器自己的狀態）</strong>。這兩層的檢查工具、根因、修法都不一樣，先分對層再往下查，才不會拿修 DNS 的方法去治簽章過期。這篇以 Arch 的 <code>pacman</code> 為主要案例（本系列 VM 實測踩過的坑），其他發行版的套件管理器概念對應相同。</p>
<h2 id="第一步分連不到還是連得到但被拒">第一步：分「連不到」還是「連得到但被拒」</h2>
<p>錯誤訊息本身就能分層，不用猜：</p>
<ul>
<li><strong>訊息提到主機名解不出、連線逾時、retrieving file 失敗</strong> → 連不到，往網路 / DNS / mirror 查。</li>
<li><strong>訊息提到 database lock、signature、trust、conflicting、partial</strong> → 連得到、封包也拿到了，是套件管理器的狀態問題。</li>
</ul>
<p>判準是問一句：「它到底有沒有成功連上 mirror？」有連上才談得到簽章、相依、db 狀態；連都沒連上，那些都還輪不到。剛裝好的最小系統最常見的是前者——網路設定還沒到位。</p>
<h2 id="連不到那層從實體介面往上查到域名">連不到那層：從實體介面往上查到域名</h2>
<p>網路不通有好幾層，從最底層往上逐層確認，哪一層斷了一目了然。這條鏈跟<a href="../minimal-install-verify/">最小安裝後的驗證</a>裡的網路檢查同源，這裡聚焦在「抓套件失敗」這個症狀上：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">ip -brief a              <span class="c1"># 1. 有沒有拿到 IP？介面 UP 且有位址</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">ping -c1 8.8.8.8         <span class="c1"># 2. IP 層對外通不通？（直接打 IP、跳過 DNS）</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">getent hosts archlinux.org   <span class="c1"># 3. 域名解得出來嗎？</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">timedatectl              <span class="c1"># 4. 時間對嗎？（影響下一層的簽章驗證）</span></span></span></code></pre></div><p><strong>第 2 步通、第 3 步不通 = DNS 問題</strong>，這是最小安裝最典型的落點：IP 層明明通（<code>ping 8.8.8.8</code> 有回應），但域名解不出來，因為 <code>/etc/resolv.conf</code> 還沒設 nameserver。這時 pacman 會卡在解析 mirror 主機名。修法是給系統一個 resolver——臨時可直接寫 <code>/etc/resolv.conf</code>（<code>nameserver 1.1.1.1</code>）。先看它是什麼（<code>ls -l /etc/resolv.conf</code>）：啟用了 <code>systemd-resolved</code> 或 NetworkManager 的系統上它是那些服務管理的 symlink，手寫會被覆蓋，治本要透過該網路管理服務設定 DNS；裸 Arch 最小安裝若沒啟用這些服務，它通常就是一個普通檔案，手寫即持久生效。</p>
<p><strong>mirror 逾時 / 抓不到</strong>：DNS 通了、但某個 mirror 慢或掛了。換 <code>/etc/pacman.d/mirrorlist</code> 到地理近且快的鏡像（實測不同 mirror 速度可差數倍）。這也接回<a href="../install-option-decisions/">安裝選項判讀</a>裡選 mirror 的決策——裝機當下選錯 mirror，這裡就會慢。</p>
<h2 id="連得到但被拒那層pacman-自己的狀態">連得到但被拒那層：pacman 自己的狀態</h2>
<p>連上 mirror、封包也拿到了卻失敗，問題在 pacman 的本地狀態或簽章驗證。這幾種各有明確徵兆與修法：</p>
<h3 id="database-lock上次沒清乾淨的殘留">database lock：上次沒清乾淨的殘留</h3>
<p><code>error: failed to init transaction (unable to lock database)</code>。pacman 用 <code>/var/lib/pacman/db.lck</code> 這個鎖檔保證同時只有一個 pacman 在動資料庫；上次 pacman 被中斷（斷電、Ctrl+C、當掉）沒清掉鎖檔就會殘留。<strong>先確認真的沒有 pacman 在跑</strong>（<code>pgrep -x pacman</code>），確認沒有再刪鎖檔：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">pgrep -x pacman <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">&#34;有 pacman 在跑、別刪&#34;</span> <span class="o">||</span> sudo rm /var/lib/pacman/db.lck</span></span></code></pre></div><p>先查再刪這個順序重要——盲刪鎖檔時如果真的有另一個 pacman 在跑，兩個同時寫資料庫會弄壞它。</p>
<h3 id="簽章--keyring-過期十之八九是時間不對">簽章 / keyring 過期：十之八九是時間不對</h3>
<p><code>invalid or corrupted package (PGP signature)</code> 或 <code>signature is unknown trust</code>。pacman 驗證每個套件的 GPG 簽章，驗證失敗最常見的根因是<strong>系統時間不對</strong>——這正是第一步要 <code>timedatectl</code> 的原因。時間差太多（新裝的 VM、主機板電池沒電的老機器）會讓「簽章的有效期」判斷錯誤，明明有效的簽章被判過期。先校時：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">sudo timedatectl set-ntp <span class="nb">true</span>     <span class="c1"># 開 NTP 自動校時（SSH 進最小系統無 polkit 互動代理、裸跑會被拒，要 sudo）</span></span></span></code></pre></div><p>時間對了還失敗，才是 keyring 本身的問題（archlinux-keyring 太舊）：<code>sudo pacman -Sy archlinux-keyring</code> 更新 keyring，必要時 <code>sudo pacman-key --refresh-keys</code>。順序是先校時再動 keyring，因為時間不對時連 keyring 都更新不了。</p>
<h3 id="partial-upgrade只同步不升級造成的相依斷裂">partial upgrade：只同步不升級造成的相依斷裂</h3>
<p><code>conflicting dependencies</code> 或裝完某個套件後系統行為異常。根因是在 rolling 發行版上只做了 <code>pacman -Sy</code>（同步資料庫）就裝新套件，卻沒 <code>-u</code>（升級既有套件）——新套件依賴新版函式庫，但系統還是舊的，相依對不上。Arch 只支援 full upgrade：<strong>一律 <code>pacman -Syu</code>，永遠不要單獨 <code>-Sy</code> 之後裝東西</strong>。這條規則救掉這一整類故障。</p>
<h3 id="stale-db-404裝機當下的資料庫已經過期">stale db 404：裝機當下的資料庫已經過期</h3>
<p><code>error: failed retrieving file '...' 404</code>，而且換好幾個 mirror 都一樣。這是 rolling 發行版特有的時序陷阱：Arch 的 mirror 不保留舊版檔案，你裝機時 ISO 內建的套件資料庫指向的檔名，可能幾天內就被輪替掉了——資料庫說有這個檔、mirror 上已經沒有。修法跟上一條同源：<code>pacman -Syu</code> 先把資料庫同步到最新，檔名對上了就抓得到。這也是為什麼「一律 <code>-Syu</code>」是 Arch 的鐵律，而不只是建議。</p>
<h2 id="判讀總表">判讀總表</h2>
<table>
  <thead>
      <tr>
          <th>症狀</th>
          <th>層</th>
          <th>權威檢查</th>
          <th>修法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>主機名解不出</td>
          <td>網路</td>
          <td><code>getent hosts &lt;域名&gt;</code></td>
          <td>設 resolver（注意 symlink）</td>
      </tr>
      <tr>
          <td>ping IP 通、域名不通</td>
          <td>DNS</td>
          <td><code>ping 8.8.8.8</code> vs <code>getent</code></td>
          <td>設 <code>/etc/resolv.conf</code> 或網管服務</td>
      </tr>
      <tr>
          <td>mirror 慢 / 逾時</td>
          <td>網路</td>
          <td>換 mirror 測速</td>
          <td>改 mirrorlist</td>
      </tr>
      <tr>
          <td>unable to lock database</td>
          <td>pacman</td>
          <td><code>pgrep -x pacman</code></td>
          <td>確認無後刪 db.lck</td>
      </tr>
      <tr>
          <td>PGP signature / unknown trust</td>
          <td>pacman</td>
          <td><code>timedatectl</code>（先校時）</td>
          <td>校時 →（仍失敗）更新 keyring</td>
      </tr>
      <tr>
          <td>conflicting / partial</td>
          <td>pacman</td>
          <td>是否只跑了 <code>-Sy</code></td>
          <td><code>pacman -Syu</code>（永遠 full）</td>
      </tr>
      <tr>
          <td>retrieving file 404（多 mirror）</td>
          <td>pacman</td>
          <td>rolling stale db</td>
          <td><code>pacman -Syu</code> 同步再裝</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步">下一步</h2>
<ul>
<li>這幾步用到的網路驗證，完整版在<a href="../minimal-install-verify/">最小安裝後的工具驗證與補足</a>。</li>
<li>裝機時選 mirror / locale / 時區的決策，見<a href="../install-option-decisions/">Linux 安裝選項判讀</a>。</li>
<li>跨發行版時「這個套件名 / 這個旗標在別的發行版叫什麼」的差異判讀，見<a href="../platform-divergence-map/">平台與發行版差異的判讀地圖</a>。</li>
<li>套件抓下來了、但 bootstrap 腳本本身失敗要 debug，見<a href="../observable-bootstrap/">可除錯的 bootstrap</a>。</li>
<li>系統跑起來後才出的套件問題（AUR 建置失敗、<code>-bin</code> 包 soname 斷裂等），屬除錯範疇，見<a href="../../debug/">Linux 除錯與診斷</a>。</li>
</ul>
]]></content:encoded></item><item><title>程序、服務與狀態怎麼判</title><link>https://tarrragon.github.io/blog/linux/debug/process-service-state-diagnosis/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/debug/process-service-state-diagnosis/</guid><description>&lt;p>判斷「某個東西現在是什麼狀態」——程式活著沒、服務由誰提供、螢幕鎖了沒、session 還在不在——是除錯裡最常做、也最常判錯的一步。判錯多半不是工具不對，是問錯了來源：用一個猜的名字去掃行程、用畫面有沒有反應去推服務狀態、用畫面上有沒有某個元素去斷定 session 狀態。這篇把幾個常見的狀態判斷，對到它們各自的權威來源與正確工具。&lt;/p>
&lt;p>底層的心法（讀權威狀態、不靠肉眼）見 &lt;a href="../diagnosis-read-authoritative-state/">診斷心法&lt;/a>，這篇是它在「程序 / 服務 / 狀態」這一類的具體招式。&lt;/p>
&lt;h2 id="程式活著沒比對正確的行程名">程式活著沒：比對正確的行程名&lt;/h2>
&lt;p>判斷一個程式在不在，行程表是權威來源，&lt;code>pgrep&lt;/code> / &lt;code>ps&lt;/code> 是對的工具，但成敗在於&lt;strong>比對正確的行程名&lt;/strong>（comm，行程表裡記的執行檔短名，可從 &lt;code>/proc/&amp;lt;pid&amp;gt;/comm&lt;/code> 看）。一個實際的坑：某個桌面 shell（畫桌面 UI 的圖形程式，不是 bash/zsh 那種命令列 shell）的可執行檔叫 &lt;code>quickshell&lt;/code>，但透過名為 &lt;code>qs&lt;/code> 的 symlink 啟動時，它在行程表裡的 comm 是 &lt;code>qs&lt;/code>。這時 &lt;code>pgrep quickshell&lt;/code> 找不到，很容易誤判成程式掛了、甚至誤觸「重啟」而引發更大的問題，實際上它以 &lt;code>qs&lt;/code> 這個名字好好跑著。&lt;/p>
&lt;p>可靠的做法：&lt;/p>
&lt;ul>
&lt;li>先確認實際的 comm 名：&lt;code>ps -eo pid,comm | grep -i &amp;lt;關鍵字&amp;gt;&lt;/code>，或看你啟動它的實際指令。&lt;/li>
&lt;li>用精確比對：&lt;code>pgrep -x &amp;lt;comm&amp;gt;&lt;/code>（&lt;code>-x&lt;/code> 要求完全相符），或 &lt;code>pgrep -af &amp;lt;pattern&amp;gt;&lt;/code> 連完整命令列一起比對，避免被 symlink 名 / 縮寫名騙。&lt;/li>
&lt;li>另一個 comm 的坑：kernel 把 comm 截在 15 字元（&lt;code>TASK_COMM_LEN&lt;/code>），名字超過 15 字的程式用 &lt;code>pgrep -x &amp;lt;完整長名&amp;gt;&lt;/code> 反而 miss——這時改用 &lt;code>pgrep -af &amp;lt;pattern&amp;gt;&lt;/code> 比對完整命令列。&lt;/li>
&lt;li>別用一個「你以為的名字」掃過去就下生死結論——行程表沒騙你，是查詢條件寫錯。&lt;/li>
&lt;/ul>
&lt;h3 id="進程活著--內部子系統活著">進程活著 ≠ 內部子系統活著&lt;/h3>
&lt;p>比對到了正確的 comm、&lt;code>pgrep&lt;/code> 也有輸出，只證明「這個進程存在」，不證明「它內部在正常運作」。有一類故障是進程好端端活著（&lt;code>pgrep&lt;/code> 找得到、STAT 是正常的 &lt;code>S&lt;/code>、在 &lt;code>poll&lt;/code> 等事件、CPU 不高），但它內部某個子系統已經 wedged——例如一個圖形 shell 的 QML scene 因為上游錯誤（渲染 pipeline 建失敗之類）某個物件沒建起來變 null，於是負責互動的模組全部失效。表現是 bar 還畫得出來、卻點不動，keybind 叫不出東西，但焦點視窗打字正常。這時 &lt;code>pgrep&lt;/code> 會騙你說「在跑」。&lt;/p>
&lt;p>這種情況權威來源不是行程表，是&lt;strong>程式自己的 log&lt;/strong>，而且這種 log 常常不在 &lt;code>journalctl&lt;/code>、也不在你猜的路徑，要用該程式專屬的 log 指令（例如某桌面 shell 的 &lt;code>&amp;lt;shell&amp;gt; -l&lt;/code>）。log 裡的 &lt;code>TypeError: Cannot read property 'X' of null&lt;/code> 這類訊息，才是「進程活著但子系統死了」的定案證據。另一個更精準的活性探針是程式的 &lt;strong>IPC 回不回真實狀態&lt;/strong>：正常時查詢會回傳資料、子系統死掉時回空——這比「進程在不在」可靠得多。判「進程活著到底有沒有在運作」時，讀它自己的 log 與 IPC，不是看 &lt;code>pgrep&lt;/code> 有沒有輸出。桌面 shell 的具體案例與恢復（讀 &lt;code>caelestia shell -l&lt;/code> 抓到 null 根因、重啟重建 scene）見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/common-failures-recovery/" data-link-title="常見故障場景與恢復操作" data-link-desc="Hyprland 黑屏、waybar 消失、畫面凍結、記憶體爆掉或 config 寫錯導致進不了桌面時，按症狀查恢復操作">常見故障場景與恢復操作&lt;/a> 的「畫得出來但互動死掉」場景。&lt;/p>
&lt;h2 id="服務由誰提供問註冊表">服務由誰提供：問註冊表&lt;/h2>
&lt;p>「某個系統服務現在由哪個程式在提供」，權威來源是服務註冊，不是畫面。桌面服務多半註冊在 &lt;strong>D-Bus&lt;/strong>（Linux 桌面的行程間訊息匯流排）上：一個服務用一個名字掛在上面，而&lt;strong>同一個名字同一時間只能被一個行程擁有&lt;/strong>。以桌面通知為例，&lt;code>org.freedesktop.Notifications&lt;/code> 這個 D-Bus 名同一時間只有一個擁有者——兩個通知 daemon（例如 mako 跟某個桌面 shell 內建的通知服務）不能共存，誰先註冊誰佔著，後者只能等前者退出。&lt;/p>
&lt;p>想知道現在是誰接管，查註冊表而不是送一則通知看畫面：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 查 org.freedesktop.Notifications 目前被哪個連線擁有&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="nv">owner&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>busctl --user call org.freedesktop.DBus /org/freedesktop/DBus &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> org.freedesktop.DBus GetNameOwner s org.freedesktop.Notifications &lt;span class="p">|&lt;/span> awk &lt;span class="s1">&amp;#39;{print $2}&amp;#39;&lt;/span> &lt;span class="p">|&lt;/span> tr -d &lt;span class="s1">&amp;#39;&amp;#34;&amp;#39;&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 把那個連線換算成 PID，再看行程名&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="nv">pid&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>busctl --user call org.freedesktop.DBus /org/freedesktop/DBus &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> org.freedesktop.DBus GetConnectionUnixProcessID s &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$owner&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span> awk &lt;span class="s1">&amp;#39;{print $2}&amp;#39;&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">ps -o &lt;span class="nv">comm&lt;/span>&lt;span class="o">=&lt;/span> -p &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$pid&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>停掉舊 daemon 前擁有者是舊的、停掉後換成新的，就確認接管成功。這比「送通知看畫面有沒有跳」可靠——畫面沒跳可能是勿擾模式吃掉、可能根本沒送出，畫面反應不等於服務歸屬。切換兩個搶同一服務名的 daemon 時，這也解釋了為什麼「新的裝了卻沒作用」：舊的還佔著名字，新的靜默註冊失敗（通常只在它的 log 留一行 warning），得先停掉舊的。&lt;/p></description><content:encoded><![CDATA[<p>判斷「某個東西現在是什麼狀態」——程式活著沒、服務由誰提供、螢幕鎖了沒、session 還在不在——是除錯裡最常做、也最常判錯的一步。判錯多半不是工具不對，是問錯了來源：用一個猜的名字去掃行程、用畫面有沒有反應去推服務狀態、用畫面上有沒有某個元素去斷定 session 狀態。這篇把幾個常見的狀態判斷，對到它們各自的權威來源與正確工具。</p>
<p>底層的心法（讀權威狀態、不靠肉眼）見 <a href="../diagnosis-read-authoritative-state/">診斷心法</a>，這篇是它在「程序 / 服務 / 狀態」這一類的具體招式。</p>
<h2 id="程式活著沒比對正確的行程名">程式活著沒：比對正確的行程名</h2>
<p>判斷一個程式在不在，行程表是權威來源，<code>pgrep</code> / <code>ps</code> 是對的工具，但成敗在於<strong>比對正確的行程名</strong>（comm，行程表裡記的執行檔短名，可從 <code>/proc/&lt;pid&gt;/comm</code> 看）。一個實際的坑：某個桌面 shell（畫桌面 UI 的圖形程式，不是 bash/zsh 那種命令列 shell）的可執行檔叫 <code>quickshell</code>，但透過名為 <code>qs</code> 的 symlink 啟動時，它在行程表裡的 comm 是 <code>qs</code>。這時 <code>pgrep quickshell</code> 找不到，很容易誤判成程式掛了、甚至誤觸「重啟」而引發更大的問題，實際上它以 <code>qs</code> 這個名字好好跑著。</p>
<p>可靠的做法：</p>
<ul>
<li>先確認實際的 comm 名：<code>ps -eo pid,comm | grep -i &lt;關鍵字&gt;</code>，或看你啟動它的實際指令。</li>
<li>用精確比對：<code>pgrep -x &lt;comm&gt;</code>（<code>-x</code> 要求完全相符），或 <code>pgrep -af &lt;pattern&gt;</code> 連完整命令列一起比對，避免被 symlink 名 / 縮寫名騙。</li>
<li>另一個 comm 的坑：kernel 把 comm 截在 15 字元（<code>TASK_COMM_LEN</code>），名字超過 15 字的程式用 <code>pgrep -x &lt;完整長名&gt;</code> 反而 miss——這時改用 <code>pgrep -af &lt;pattern&gt;</code> 比對完整命令列。</li>
<li>別用一個「你以為的名字」掃過去就下生死結論——行程表沒騙你，是查詢條件寫錯。</li>
</ul>
<h3 id="進程活著--內部子系統活著">進程活著 ≠ 內部子系統活著</h3>
<p>比對到了正確的 comm、<code>pgrep</code> 也有輸出，只證明「這個進程存在」，不證明「它內部在正常運作」。有一類故障是進程好端端活著（<code>pgrep</code> 找得到、STAT 是正常的 <code>S</code>、在 <code>poll</code> 等事件、CPU 不高），但它內部某個子系統已經 wedged——例如一個圖形 shell 的 QML scene 因為上游錯誤（渲染 pipeline 建失敗之類）某個物件沒建起來變 null，於是負責互動的模組全部失效。表現是 bar 還畫得出來、卻點不動，keybind 叫不出東西，但焦點視窗打字正常。這時 <code>pgrep</code> 會騙你說「在跑」。</p>
<p>這種情況權威來源不是行程表，是<strong>程式自己的 log</strong>，而且這種 log 常常不在 <code>journalctl</code>、也不在你猜的路徑，要用該程式專屬的 log 指令（例如某桌面 shell 的 <code>&lt;shell&gt; -l</code>）。log 裡的 <code>TypeError: Cannot read property 'X' of null</code> 這類訊息，才是「進程活著但子系統死了」的定案證據。另一個更精準的活性探針是程式的 <strong>IPC 回不回真實狀態</strong>：正常時查詢會回傳資料、子系統死掉時回空——這比「進程在不在」可靠得多。判「進程活著到底有沒有在運作」時，讀它自己的 log 與 IPC，不是看 <code>pgrep</code> 有沒有輸出。桌面 shell 的具體案例與恢復（讀 <code>caelestia shell -l</code> 抓到 null 根因、重啟重建 scene）見 <a href="/blog/linux/dotfile/07-desktop-maintenance/common-failures-recovery/" data-link-title="常見故障場景與恢復操作" data-link-desc="Hyprland 黑屏、waybar 消失、畫面凍結、記憶體爆掉或 config 寫錯導致進不了桌面時，按症狀查恢復操作">常見故障場景與恢復操作</a> 的「畫得出來但互動死掉」場景。</p>
<h2 id="服務由誰提供問註冊表">服務由誰提供：問註冊表</h2>
<p>「某個系統服務現在由哪個程式在提供」，權威來源是服務註冊，不是畫面。桌面服務多半註冊在 <strong>D-Bus</strong>（Linux 桌面的行程間訊息匯流排）上：一個服務用一個名字掛在上面，而<strong>同一個名字同一時間只能被一個行程擁有</strong>。以桌面通知為例，<code>org.freedesktop.Notifications</code> 這個 D-Bus 名同一時間只有一個擁有者——兩個通知 daemon（例如 mako 跟某個桌面 shell 內建的通知服務）不能共存，誰先註冊誰佔著，後者只能等前者退出。</p>
<p>想知道現在是誰接管，查註冊表而不是送一則通知看畫面：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 查 org.freedesktop.Notifications 目前被哪個連線擁有</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nv">owner</span><span class="o">=</span><span class="k">$(</span>busctl --user call org.freedesktop.DBus /org/freedesktop/DBus <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  org.freedesktop.DBus GetNameOwner s org.freedesktop.Notifications <span class="p">|</span> awk <span class="s1">&#39;{print $2}&#39;</span> <span class="p">|</span> tr -d <span class="s1">&#39;&#34;&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 把那個連線換算成 PID，再看行程名</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nv">pid</span><span class="o">=</span><span class="k">$(</span>busctl --user call org.freedesktop.DBus /org/freedesktop/DBus <span class="se">\
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="se"></span>  org.freedesktop.DBus GetConnectionUnixProcessID s <span class="s2">&#34;</span><span class="nv">$owner</span><span class="s2">&#34;</span> <span class="p">|</span> awk <span class="s1">&#39;{print $2}&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">ps -o <span class="nv">comm</span><span class="o">=</span> -p <span class="s2">&#34;</span><span class="nv">$pid</span><span class="s2">&#34;</span></span></span></code></pre></div><p>停掉舊 daemon 前擁有者是舊的、停掉後換成新的，就確認接管成功。這比「送通知看畫面有沒有跳」可靠——畫面沒跳可能是勿擾模式吃掉、可能根本沒送出，畫面反應不等於服務歸屬。切換兩個搶同一服務名的 daemon 時，這也解釋了為什麼「新的裝了卻沒作用」：舊的還佔著名字，新的靜默註冊失敗（通常只在它的 log 留一行 warning），得先停掉舊的。</p>
<h2 id="桌面-session-有沒有被鎖認清是哪一層的鎖">桌面 session 有沒有被鎖：認清是哪一層的鎖</h2>
<p>判斷一個圖形 session 有沒有被鎖，最容易被畫面帶偏，因為「畫面上有密碼框」很有說服力、卻不等於 session 真的被鎖（現代桌面 shell 的儀表板常內嵌鎖屏樣式的 widget）。而且鎖有不同層，查錯層會得到誤導的答案。</p>
<p>關鍵是分清兩種鎖：</p>
<ul>
<li><strong>logind 層的鎖</strong>：systemd 登入管理的 session 鎖，權威狀態是 <code>loginctl show-session &lt;id&gt; -p LockedHint</code>。</li>
<li><strong>Wayland 合成器層的鎖</strong>：走 <code>ext-session-lock</code> 協議、由<strong>合成器</strong>（compositor，Wayland 下負責把各視窗合成到螢幕、管輸入輸出的核心程式，約當 X11 時代的視窗管理器加顯示伺服器；Hyprland、Sway 等都是）管的鎖，跟 logind 是獨立機制。這種鎖 <code>loginctl</code> 的 <code>LockedHint</code> <strong>查不到</strong>——不是沒鎖，是查錯層。（用 GNOME / KDE 的鎖屏走的機制不同，以下的 <code>ext-session-lock</code> 判法與復原針對 wlroots 系的 Wayland 合成器。）</li>
</ul>
<p>所以「<code>loginctl</code> 沒有 <code>LockedHint</code>、<code>pgrep</code> 找不到獨立鎖屏程式」不足以斷定「沒鎖」：合成器層的鎖不歸 logind、而鎖屏畫面可能由 shell 主程式在自己行程內畫（沒有獨立可執行檔可抓）。這種情況真正的權威來源是那個 shell 自己的 log（有沒有載入鎖屏模組、idle 計時器有沒有觸發鎖定），或直接看 compositor 的 session-lock 狀態。判鎖看合成器 / shell 的 log，不是 <code>loginctl</code>、更不是畫面有沒有密碼框。</p>
<h3 id="鎖屏程式死掉造成的死局與復原">鎖屏程式死掉造成的死局與復原</h3>
<p><code>ext-session-lock</code> 有一個安全設計：持鎖的鎖屏程式若在鎖定狀態下崩潰 / 被中止，compositor <strong>會保持鎖定</strong>、不會因為鎖屏程式沒了就解鎖（否則殺掉鎖屏程式就成了繞過鎖的漏洞）。表現是畫面卡在「鎖屏程式已死」的安全提示。復原要從另一個 VT 或 SSH 用 <code>hyprctl keyword misc:allow_session_lock_restore 1</code> 允許新鎖屏 client 接管、再 <code>hyprctl dispatch exec hyprlock</code> 起一個接管後輸密碼解鎖。完整機制、兩層鎖的關係、各 compositor 的差異，見 <a href="/blog/linux/dotfile/knowledge-cards/session-lock/" data-link-title="Wayland Session Lock（鎖屏安全狀態）" data-link-desc="hyprlock / swaylock 畫面卡住、pkill 後進不了桌面、或要在 VM / 自動化環境測試鎖屏時回來讀">Wayland Session Lock 卡</a>。</p>
<p>診斷紀律：<strong>測鎖屏、或 <code>pkill</code> 一個持鎖的鎖屏程式時，要預期它把 session 卡在鎖定——這是協議的安全設計，不是 bug。</strong> 自動化 / 無人值守流程尤其要避免在持鎖狀態下殺鎖屏程式。</p>
<h2 id="終端機多工器的-session-還在不在">終端機多工器的 session 還在不在</h2>
<p>用 zellij / tmux 這類多工器跑遠端長任務時，判斷「重連後那個 session 還在不在」的權威來源是多工器自己的 session 列表，不是「我 SSH 斷了所以應該還在吧」的假設。<code>zellij ls</code>（或 <code>tmux ls</code>）會列出 session 與狀態：多工器是常駐在遠端的程序，SSH 斷不影響它，所以只要那台機器沒重開，<code>attach</code> 就能接回去；但如果機器重開過、或那個 session 因為資源不足（例如磁碟滿觸發的連鎖）被殺，列表會顯示它已 <code>EXITED</code> / 不存在，這種接不回去。</p>
<p>這裡有個順序上的紀律：<strong>當一個 session 可能已經死掉、而它裡面跑的任務有你在意的產出時，先確認產出有沒有被安全保存，再處理 session。</strong> 例如任務是在改 git repo，先 <code>git -C &lt;repo&gt; status</code> 跟 <code>git log @{u}..</code>（本地有、遠端沒有的 commit）確認有沒有沒推送的東西、把該推的推掉，再去 <code>zellij delete</code> 清死 session。搞反順序、先清了 session，可能連帶失去唯一還記得那些改動的地方。權威狀態（git 的推送狀態、多工器的 session 列表）先讀清楚，再動手。</p>
<h2 id="判讀路由">判讀路由</h2>
<ul>
<li>判程式活著 → <code>pgrep -x &lt;正確 comm&gt;</code> / <code>pgrep -af &lt;pattern&gt;</code>，先確認實際 comm 名，別用猜的名字。</li>
<li>判進程活著但「有沒有在運作」→ 讀程式自己的 log（可能要用它專屬的 log 指令、不在 journalctl）+ 它的 IPC 回不回真實狀態，不是看 <code>pgrep</code> 有輸出就當正常。</li>
<li>判服務歸誰 → <code>busctl</code> 查 D-Bus name 擁有者 → 換算 PID → comm，不看畫面反應。</li>
<li>判 session 鎖沒鎖 → 分清 logind 層（<code>loginctl LockedHint</code>）vs 合成器層（<code>ext-session-lock</code>，看 compositor / shell log），不看畫面有沒有密碼框。</li>
<li>鎖屏程式死掉卡住 → <code>allow_session_lock_restore</code> + 重起鎖屏程式接管解鎖。</li>
<li>判多工器 session 存活 → <code>zellij ls</code> / <code>tmux ls</code>；可能已死且有在意的產出時，先確認產出已保存 / 已推送再清 session。</li>
</ul>
<p>判不準時，<a href="../diagnosis-read-authoritative-state/">診斷心法</a> 的四步（描述症狀、定位權威來源、用對工具讀、矛盾時信權威）是通用的回退。</p>
]]></content:encoded></item><item><title>Dotfile 工作環境配置指南</title><link>https://tarrragon.github.io/blog/linux/dotfile/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/</guid><description>&lt;p>開發者的工作環境是一組配置檔的集合：shell 怎麼設定、編輯器用什麼快捷鍵、終端機長什麼樣、視窗怎麼排列。這些配置檔在 Unix 系統上以 &lt;code>.&lt;/code> 開頭（隱藏檔），統稱 dotfile。&lt;/p>
&lt;p>dotfile 管理解決的核心問題是&lt;strong>環境可重現性&lt;/strong>。一台新機器、一次重灌、一個 VM，都應該能用一份 Git repo 還原成你熟悉的工作環境。不做管理的代價是：每次換機器都在重新手動設定，每次都少記一兩個東西，每次都花一兩天才回到順手的狀態。&lt;/p>
&lt;h2 id="和其他系列的關係">和其他系列的關係&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>系列&lt;/th>
 &lt;th>聚焦&lt;/th>
 &lt;th>和 Dotfile 的交集&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/infra/" data-link-title="Infra 基礎設施建置指南" data-link-desc="從零循序漸進把雲端基礎設施做起來 — IaC、身分憑證、網路地基、環境分離、核心服務、可觀測性、自動化 review 與治理習慣，含怎麼在組織內推動">Infra&lt;/a>&lt;/td>
 &lt;td>雲端基礎設施地基（IaC、網路、身分、環境分離）&lt;/td>
 &lt;td>Infra 是組織的環境 as code，Dotfile 是個人的環境 as code，思想平行&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/" data-link-title="DevOps 實務指南" data-link-desc="負載平衡、水平擴展、流量管控、服務探活、容量規劃、高可用、突發流量、成本管理 — 服務營運的工程基礎">DevOps&lt;/a>&lt;/td>
 &lt;td>服務營運（負載、擴展、容量、成本）&lt;/td>
 &lt;td>DevOps 工程師的日常工具鏈（多終端機、SSH、log tail）正是 dotfile 高度客製的場景&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/tools/cli/" data-link-title="CLI 環境工具" data-link-desc="在純文字終端機下做事時要挑工具——日常指令的現代替代品（rg/fd/fzf），以及用文字字元做出圖形化操作介面的 TUI（監控、圖表、多工器、資料庫）——想知道各情境有哪些選擇時回來讀">CLI&lt;/a>&lt;/td>
 &lt;td>TUI 工具、多工器、檔案管理器&lt;/td>
 &lt;td>CLI 工具的配置檔是 dotfile 管理的核心對象&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/monitoring/" data-link-title="監控實務指南" data-link-desc="整理非伺服器端運行時的監控體系 — 行為蒐集、錯誤回報、效能指標、生命週期追蹤，從自架方案到商業方案的完整知識路線">Monitoring&lt;/a>&lt;/td>
 &lt;td>客戶端監控體系&lt;/td>
 &lt;td>Monitoring 有獨立的 hands-on 專案做實測，Dotfile 也會有 VM 實測專案&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Infra 教「組織的地基怎麼用 code 管理」，Dotfile 教「個人的工作桌面怎麼用 code 管理」。模組九把兩者接起來——當個人 dotfile 的思想擴展到團隊，就是 devcontainer 和標準化開發環境。&lt;/p>
&lt;h2 id="地基機器初始化">地基：機器初始化&lt;/h2>
&lt;p>編號模組都假設你已經有一台裝好 Linux、工具齊全、能從外部連入的機器。&lt;a href="https://tarrragon.github.io/blog/linux/install/" data-link-title="Linux 安裝與機器初始化" data-link-desc="在 VM 或新機器從零裝好 Linux、判讀安裝程式選項、驗證最小系統、或要從外部連入跑 bootstrap 時回來讀">Linux 安裝與機器初始化&lt;/a> 補的是那段被預設跳過的地基——OS 安裝選項怎麼判讀、最小系統缺哪些必要工具、怎麼連進去跑 bootstrap（含還沒有 SSH key 的情況），以及怎麼讓安裝腳本在失敗時可診斷。只要你換到全新環境（開 VM、租雲端主機、拿到空機器），就會先撞上這層。內容來自一次完整的 VM 實測，蒸餾成不綁特定發行版的判讀與決策。&lt;/p>
&lt;h2 id="教學模組">教學模組&lt;/h2>
&lt;p>模組編號反映學習路徑：先理解為什麼、再學怎麼管理、然後逐層處理 shell/終端機/視窗管理/視覺客製化，最後談維護排錯、同步可攜性和團隊化。模組四到七是 Linux 桌面環境的深度實作，之後會搭配 VM 專案做 hands-on 實測。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>模組&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;th>回答什麼問題&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/00-dotfile-mindset/" data-link-title="模組零：Dotfile 心智模型" data-link-desc="換機器、開 VM、重灌系統時需要快速還原開發環境，或想釐清哪些配置該版控、哪些該排除時回來讀">模組零：Dotfile 心智模型&lt;/a>&lt;/td>
 &lt;td>什麼是 dotfile、為什麼要管理、環境可重現性&lt;/td>
 &lt;td>為什麼不能每次手動設定就好&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/" data-link-title="模組一：管理工具與目錄結構" data-link-desc="要把散落在家目錄的配置檔集中版控時，選 bare repo、stow 還是 chezmoi、目錄該怎麼組織">模組一：管理工具與目錄結構&lt;/a>&lt;/td>
 &lt;td>bare repo / stow / chezmoi、目錄設計、版控工作流&lt;/td>
 &lt;td>dotfile 怎麼用 Git 管、目錄該怎麼組織&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/02-shell-config/" data-link-title="模組二：Shell 配置" data-link-desc="shell 配置檔長成一坨不敢動時回來讀 — .zshrc/.bashrc 的結構化拆分、alias/function/PATH 的模組化設計">模組二：Shell 配置&lt;/a>&lt;/td>
 &lt;td>zsh/bash 結構化配置、模組化拆分、alias/function/PATH&lt;/td>
 &lt;td>.zshrc 該怎麼寫才不會長成一坨&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/03-terminal-ecosystem/" data-link-title="模組三：終端機與編輯器" data-link-desc="終端機相關工具的配置檔散落在不同位置、不確定哪些該進 dotfile repo 時回來讀">模組三：終端機與編輯器&lt;/a>&lt;/td>
 &lt;td>terminal emulator 選型、tmux/zellij、neovim 基礎&lt;/td>
 &lt;td>終端機生態的配置檔有哪些、怎麼管理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/04-window-management/" data-link-title="模組四：視窗管理與平鋪式工作流" data-link-desc="同時開多個視窗時的排列策略 — 手動貼齊跟自動平鋪的差距在哪、macOS 和 Linux 各有哪些工具、多螢幕怎麼處理、什麼情境值得從浮動切換到平鋪">模組四：視窗管理與平鋪式工作流&lt;/a>&lt;/td>
 &lt;td>手動 vs 自動平鋪、macOS 工具鏈、Linux tiling WM&lt;/td>
 &lt;td>Rectangle 不夠用的時候該換什麼&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">模組五：Hyprland 配置&lt;/a>&lt;/td>
 &lt;td>Hyprland 安裝、核心設定、keybind、monitor、workspace&lt;/td>
 &lt;td>Hyprland 的配置檔怎麼寫、核心概念是什麼&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">模組六：桌面 Rice 設計&lt;/a>&lt;/td>
 &lt;td>狀態列、啟動器、通知、配色系統、desktop shell&lt;/td>
 &lt;td>桌面怎麼從「能用」變成「好看又好用」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/" data-link-title="模組七：桌面環境維護與故障排除" data-link-desc="桌面凍結、compositor 掛了、或某個工具不回應時回來讀 — Linux 桌面的故障隔離模型、常見故障場景的恢復操作、日誌判讀與診斷工具">模組七：桌面環境維護與故障排除&lt;/a>&lt;/td>
 &lt;td>故障隔離模型、常見故障恢復、日誌判讀&lt;/td>
 &lt;td>compositor 掛了或桌面凍結時怎麼診斷和恢復&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/08-sync-bootstrap/" data-link-title="模組八：同步、Bootstrap 與環境重建" data-link-desc="換機器或重灌時怎麼還原工作環境 — bootstrap script 設計、套件清單管理、跨機器同步策略、secret 排除，以及 VM 快照和 dotfile 重建兩種思路的場景判讀">模組八：同步、Bootstrap 與環境重建&lt;/a>&lt;/td>
 &lt;td>跨機器同步、install script、secret 排除、VM 對比&lt;/td>
 &lt;td>換機器時怎麼一鍵還原、哪些東西不該進 repo&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/09-team-environment/" data-link-title="模組九：從個人到團隊" data-link-desc="個人 dotfile 管理的思想要延伸到團隊開發環境標準化時回來讀 — devcontainer、nix、商業環境配置管理">模組九：從個人到團隊&lt;/a>&lt;/td>
 &lt;td>devcontainer、nix、商業開發環境標準化&lt;/td>
 &lt;td>個人 dotfile 的思想怎麼延伸到團隊環境管理&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="vm-實測專案">VM 實測專案&lt;/h2>
&lt;p>模組四到七的 Linux 桌面配置會搭配一個獨立的 VM 專案做 hands-on 實測（類似 &lt;a href="https://tarrragon.github.io/blog/monitoring/" data-link-title="監控實務指南" data-link-desc="整理非伺服器端運行時的監控體系 — 行為蒐集、錯誤回報、效能指標、生命週期追蹤，從自架方案到商業方案的完整知識路線">Monitoring&lt;/a> 系列搭配 monitor 專案的關係）。教材負責概念與配置邏輯的說明，VM 專案負責實際安裝、調教、截圖驗證。VM 專案會在教材完成後另外建立。&lt;/p></description><content:encoded><![CDATA[<p>開發者的工作環境是一組配置檔的集合：shell 怎麼設定、編輯器用什麼快捷鍵、終端機長什麼樣、視窗怎麼排列。這些配置檔在 Unix 系統上以 <code>.</code> 開頭（隱藏檔），統稱 dotfile。</p>
<p>dotfile 管理解決的核心問題是<strong>環境可重現性</strong>。一台新機器、一次重灌、一個 VM，都應該能用一份 Git repo 還原成你熟悉的工作環境。不做管理的代價是：每次換機器都在重新手動設定，每次都少記一兩個東西，每次都花一兩天才回到順手的狀態。</p>
<h2 id="和其他系列的關係">和其他系列的關係</h2>
<table>
  <thead>
      <tr>
          <th>系列</th>
          <th>聚焦</th>
          <th>和 Dotfile 的交集</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/infra/" data-link-title="Infra 基礎設施建置指南" data-link-desc="從零循序漸進把雲端基礎設施做起來 — IaC、身分憑證、網路地基、環境分離、核心服務、可觀測性、自動化 review 與治理習慣，含怎麼在組織內推動">Infra</a></td>
          <td>雲端基礎設施地基（IaC、網路、身分、環境分離）</td>
          <td>Infra 是組織的環境 as code，Dotfile 是個人的環境 as code，思想平行</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/" data-link-title="DevOps 實務指南" data-link-desc="負載平衡、水平擴展、流量管控、服務探活、容量規劃、高可用、突發流量、成本管理 — 服務營運的工程基礎">DevOps</a></td>
          <td>服務營運（負載、擴展、容量、成本）</td>
          <td>DevOps 工程師的日常工具鏈（多終端機、SSH、log tail）正是 dotfile 高度客製的場景</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/tools/cli/" data-link-title="CLI 環境工具" data-link-desc="在純文字終端機下做事時要挑工具——日常指令的現代替代品（rg/fd/fzf），以及用文字字元做出圖形化操作介面的 TUI（監控、圖表、多工器、資料庫）——想知道各情境有哪些選擇時回來讀">CLI</a></td>
          <td>TUI 工具、多工器、檔案管理器</td>
          <td>CLI 工具的配置檔是 dotfile 管理的核心對象</td>
      </tr>
      <tr>
          <td><a href="/blog/monitoring/" data-link-title="監控實務指南" data-link-desc="整理非伺服器端運行時的監控體系 — 行為蒐集、錯誤回報、效能指標、生命週期追蹤，從自架方案到商業方案的完整知識路線">Monitoring</a></td>
          <td>客戶端監控體系</td>
          <td>Monitoring 有獨立的 hands-on 專案做實測，Dotfile 也會有 VM 實測專案</td>
      </tr>
  </tbody>
</table>
<p>Infra 教「組織的地基怎麼用 code 管理」，Dotfile 教「個人的工作桌面怎麼用 code 管理」。模組九把兩者接起來——當個人 dotfile 的思想擴展到團隊，就是 devcontainer 和標準化開發環境。</p>
<h2 id="地基機器初始化">地基：機器初始化</h2>
<p>編號模組都假設你已經有一台裝好 Linux、工具齊全、能從外部連入的機器。<a href="/blog/linux/install/" data-link-title="Linux 安裝與機器初始化" data-link-desc="在 VM 或新機器從零裝好 Linux、判讀安裝程式選項、驗證最小系統、或要從外部連入跑 bootstrap 時回來讀">Linux 安裝與機器初始化</a> 補的是那段被預設跳過的地基——OS 安裝選項怎麼判讀、最小系統缺哪些必要工具、怎麼連進去跑 bootstrap（含還沒有 SSH key 的情況），以及怎麼讓安裝腳本在失敗時可診斷。只要你換到全新環境（開 VM、租雲端主機、拿到空機器），就會先撞上這層。內容來自一次完整的 VM 實測，蒸餾成不綁特定發行版的判讀與決策。</p>
<h2 id="教學模組">教學模組</h2>
<p>模組編號反映學習路徑：先理解為什麼、再學怎麼管理、然後逐層處理 shell/終端機/視窗管理/視覺客製化，最後談維護排錯、同步可攜性和團隊化。模組四到七是 Linux 桌面環境的深度實作，之後會搭配 VM 專案做 hands-on 實測。</p>
<table>
  <thead>
      <tr>
          <th>模組</th>
          <th>主題</th>
          <th>回答什麼問題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/linux/dotfile/00-dotfile-mindset/" data-link-title="模組零：Dotfile 心智模型" data-link-desc="換機器、開 VM、重灌系統時需要快速還原開發環境，或想釐清哪些配置該版控、哪些該排除時回來讀">模組零：Dotfile 心智模型</a></td>
          <td>什麼是 dotfile、為什麼要管理、環境可重現性</td>
          <td>為什麼不能每次手動設定就好</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/01-dotfile-management/" data-link-title="模組一：管理工具與目錄結構" data-link-desc="要把散落在家目錄的配置檔集中版控時，選 bare repo、stow 還是 chezmoi、目錄該怎麼組織">模組一：管理工具與目錄結構</a></td>
          <td>bare repo / stow / chezmoi、目錄設計、版控工作流</td>
          <td>dotfile 怎麼用 Git 管、目錄該怎麼組織</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/02-shell-config/" data-link-title="模組二：Shell 配置" data-link-desc="shell 配置檔長成一坨不敢動時回來讀 — .zshrc/.bashrc 的結構化拆分、alias/function/PATH 的模組化設計">模組二：Shell 配置</a></td>
          <td>zsh/bash 結構化配置、模組化拆分、alias/function/PATH</td>
          <td>.zshrc 該怎麼寫才不會長成一坨</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/03-terminal-ecosystem/" data-link-title="模組三：終端機與編輯器" data-link-desc="終端機相關工具的配置檔散落在不同位置、不確定哪些該進 dotfile repo 時回來讀">模組三：終端機與編輯器</a></td>
          <td>terminal emulator 選型、tmux/zellij、neovim 基礎</td>
          <td>終端機生態的配置檔有哪些、怎麼管理</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/04-window-management/" data-link-title="模組四：視窗管理與平鋪式工作流" data-link-desc="同時開多個視窗時的排列策略 — 手動貼齊跟自動平鋪的差距在哪、macOS 和 Linux 各有哪些工具、多螢幕怎麼處理、什麼情境值得從浮動切換到平鋪">模組四：視窗管理與平鋪式工作流</a></td>
          <td>手動 vs 自動平鋪、macOS 工具鏈、Linux tiling WM</td>
          <td>Rectangle 不夠用的時候該換什麼</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">模組五：Hyprland 配置</a></td>
          <td>Hyprland 安裝、核心設定、keybind、monitor、workspace</td>
          <td>Hyprland 的配置檔怎麼寫、核心概念是什麼</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">模組六：桌面 Rice 設計</a></td>
          <td>狀態列、啟動器、通知、配色系統、desktop shell</td>
          <td>桌面怎麼從「能用」變成「好看又好用」</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/07-desktop-maintenance/" data-link-title="模組七：桌面環境維護與故障排除" data-link-desc="桌面凍結、compositor 掛了、或某個工具不回應時回來讀 — Linux 桌面的故障隔離模型、常見故障場景的恢復操作、日誌判讀與診斷工具">模組七：桌面環境維護與故障排除</a></td>
          <td>故障隔離模型、常見故障恢復、日誌判讀</td>
          <td>compositor 掛了或桌面凍結時怎麼診斷和恢復</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/08-sync-bootstrap/" data-link-title="模組八：同步、Bootstrap 與環境重建" data-link-desc="換機器或重灌時怎麼還原工作環境 — bootstrap script 設計、套件清單管理、跨機器同步策略、secret 排除，以及 VM 快照和 dotfile 重建兩種思路的場景判讀">模組八：同步、Bootstrap 與環境重建</a></td>
          <td>跨機器同步、install script、secret 排除、VM 對比</td>
          <td>換機器時怎麼一鍵還原、哪些東西不該進 repo</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/09-team-environment/" data-link-title="模組九：從個人到團隊" data-link-desc="個人 dotfile 管理的思想要延伸到團隊開發環境標準化時回來讀 — devcontainer、nix、商業環境配置管理">模組九：從個人到團隊</a></td>
          <td>devcontainer、nix、商業開發環境標準化</td>
          <td>個人 dotfile 的思想怎麼延伸到團隊環境管理</td>
      </tr>
  </tbody>
</table>
<h2 id="vm-實測專案">VM 實測專案</h2>
<p>模組四到七的 Linux 桌面配置會搭配一個獨立的 VM 專案做 hands-on 實測（類似 <a href="/blog/monitoring/" data-link-title="監控實務指南" data-link-desc="整理非伺服器端運行時的監控體系 — 行為蒐集、錯誤回報、效能指標、生命週期追蹤，從自架方案到商業方案的完整知識路線">Monitoring</a> 系列搭配 monitor 專案的關係）。教材負責概念與配置邏輯的說明，VM 專案負責實際安裝、調教、截圖驗證。VM 專案會在教材完成後另外建立。</p>
]]></content:encoded></item><item><title>Wayland 顯示協議：為什麼 Hyprland 不跑在 X11 上</title><link>https://tarrragon.github.io/blog/linux/dotfile/04-window-management/wayland-explainer/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/04-window-management/wayland-explainer/</guid><description>&lt;p>Wayland 是 Linux 圖形系統的顯示協議，定義了應用程式怎麼跟負責畫面合成的 compositor 溝通。Hyprland、Sway 這些 tiling WM 都是 Wayland compositor——理解 Wayland 的架構，才能理解這些工具為什麼存在、能做什麼、不能做什麼。&lt;/p>
&lt;h2 id="wayland-是協議不是軟體">Wayland 是協議，不是軟體&lt;/h2>
&lt;p>一個常見的誤解是把 Wayland 當成「一個程式」。Wayland 是一份協議規格（protocol specification），描述了 client（應用程式）和 compositor（負責合成畫面的東西）之間怎麼傳遞 buffer、怎麼處理輸入事件。&lt;/p>
&lt;p>每個 Wayland compositor 同時扮演三個角色：display server（管理顯示輸出）、window manager（管理視窗排列）、compositor（合成最終畫面）。X11 的世界裡這三個角色是分開的，Wayland 把它們統一成一個程式。這個「三合一」定位的濃縮版與它在故障排除中的責任邊界，見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/compositor/" data-link-title="Compositor（合成器）" data-link-desc="教材反覆出現 compositor / 合成器、想確認它到底負責什麼、跟 window manager 和桌面環境差在哪時讀 — Wayland 下把畫面合成與視窗管理合一的核心程式">Compositor 術語卡&lt;/a>。&lt;/p>
&lt;p>「Hyprland 是 Wayland compositor」這個說法的意思是：它不只是一個視窗管理器，它同時是整個圖形系統的核心。&lt;/p>
&lt;h2 id="x11-vs-wayland-架構差異">X11 vs Wayland 架構差異&lt;/h2>
&lt;h3 id="x11-的三角架構1984-年設計">X11 的三角架構（1984 年設計）&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">應用程式 ──→ X Server（中央仲介）──→ Compositor ──→ 螢幕
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↑ ↑
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> └── 輸入事件 ──────────────┘&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>X Server 是一個龐大的中央仲介，負責接收所有應用程式的繪圖指令、管理輸入事件、再把畫面交給 compositor 做最終合成。Compositing 是後來才加上去的（Compiz、picom 這些都是外掛的 compositor），不是 X11 原始設計的一部分。&lt;/p>
&lt;h3 id="wayland-的直接模型2008-年設計">Wayland 的直接模型（2008 年設計）&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">應用程式 ──→ Compositor（= display server + window manager）──→ 螢幕
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↑
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> └── 輸入事件&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>應用程式用 OpenGL/Vulkan 自己渲染畫面到 buffer，然後把 buffer 直接交給 compositor。沒有中間人。compositor 收到所有 client 的 buffer 後合成最終畫面輸出到螢幕。&lt;/p>
&lt;p>這個架構上的差異帶來三個實際影響：效能、安全、screen tearing。&lt;/p>
&lt;h2 id="安全性">安全性&lt;/h2>
&lt;p>X11 的安全模型有一個根本性的設計缺陷：任何連到同一個 X Server 的 client，都能監聽其他 client 的鍵盤輸入、擷取其他視窗的畫面、甚至注入假的輸入事件。這代表一個惡意程式可以輕易做到 keylogging——記錄你在其他視窗打的所有字，包括密碼。&lt;/p>
&lt;p>Wayland 從協議層就隔離了 client 之間的存取。每個應用程式只看得到自己的 buffer，無法存取其他視窗的內容或輸入。要做 screen capture 或 screen sharing，必須透過 xdg-desktop-portal 這個受控的中介機制，使用者會看到明確的授權提示。&lt;/p>
&lt;p>對 tiling WM 的使用者來說，這個安全差異特別有意義：平鋪式桌面通常同時開大量終端機視窗（有些跑 SSH、有些在 sudo 操作），X11 下任何一個視窗裡的惡意程式都能讀到其他視窗的鍵盤輸入。Wayland 阻斷了這條攻擊路徑。&lt;/p>
&lt;h2 id="效能與-screen-tearing">效能與 Screen Tearing&lt;/h2>
&lt;p>X11 的渲染路徑有多餘的記憶體複製——應用程式把繪圖指令送給 X Server，X Server 渲染後再交給 compositor，compositor 再合成。Wayland 省掉了中間那一步：應用程式直接渲染到 buffer、直接交給 compositor，記憶體複製更少。&lt;/p>
&lt;p>Screen tearing（畫面撕裂）在 X11 上是長年的老問題——需要 compositor + vsync 的各種 workaround。Wayland 在協議層就處理了 frame presentation 的時機，compositor 控制什麼時候把合成好的畫面送到螢幕，原生消除 tearing。這也是為什麼 Hyprland 的動畫能跑得流暢——直接 rendering + 原生 vsync 的組合，在 X11 上需要大量額外配置才能接近的效果。&lt;/p>
&lt;h2 id="xwaylandx11-相容層">XWayland：X11 相容層&lt;/h2>
&lt;p>很多應用程式仍然只支援 X11 協議。XWayland 是一個 X11 server，但它本身作為一個 Wayland client 運行——X11 應用程式連到 XWayland，以為自己在跟正常的 X Server 溝通；XWayland 把 X11 協議翻譯成 Wayland 協議，把 buffer 交給 compositor。&lt;/p></description><content:encoded><![CDATA[<p>Wayland 是 Linux 圖形系統的顯示協議，定義了應用程式怎麼跟負責畫面合成的 compositor 溝通。Hyprland、Sway 這些 tiling WM 都是 Wayland compositor——理解 Wayland 的架構，才能理解這些工具為什麼存在、能做什麼、不能做什麼。</p>
<h2 id="wayland-是協議不是軟體">Wayland 是協議，不是軟體</h2>
<p>一個常見的誤解是把 Wayland 當成「一個程式」。Wayland 是一份協議規格（protocol specification），描述了 client（應用程式）和 compositor（負責合成畫面的東西）之間怎麼傳遞 buffer、怎麼處理輸入事件。</p>
<p>每個 Wayland compositor 同時扮演三個角色：display server（管理顯示輸出）、window manager（管理視窗排列）、compositor（合成最終畫面）。X11 的世界裡這三個角色是分開的，Wayland 把它們統一成一個程式。這個「三合一」定位的濃縮版與它在故障排除中的責任邊界，見 <a href="/blog/linux/dotfile/knowledge-cards/compositor/" data-link-title="Compositor（合成器）" data-link-desc="教材反覆出現 compositor / 合成器、想確認它到底負責什麼、跟 window manager 和桌面環境差在哪時讀 — Wayland 下把畫面合成與視窗管理合一的核心程式">Compositor 術語卡</a>。</p>
<p>「Hyprland 是 Wayland compositor」這個說法的意思是：它不只是一個視窗管理器，它同時是整個圖形系統的核心。</p>
<h2 id="x11-vs-wayland-架構差異">X11 vs Wayland 架構差異</h2>
<h3 id="x11-的三角架構1984-年設計">X11 的三角架構（1984 年設計）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">應用程式 ──→ X Server（中央仲介）──→ Compositor ──→ 螢幕
</span></span><span class="line"><span class="ln">2</span><span class="cl">             ↑                         ↑
</span></span><span class="line"><span class="ln">3</span><span class="cl">             └── 輸入事件 ──────────────┘</span></span></code></pre></div><p>X Server 是一個龐大的中央仲介，負責接收所有應用程式的繪圖指令、管理輸入事件、再把畫面交給 compositor 做最終合成。Compositing 是後來才加上去的（Compiz、picom 這些都是外掛的 compositor），不是 X11 原始設計的一部分。</p>
<h3 id="wayland-的直接模型2008-年設計">Wayland 的直接模型（2008 年設計）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">應用程式 ──→ Compositor（= display server + window manager）──→ 螢幕
</span></span><span class="line"><span class="ln">2</span><span class="cl">             ↑
</span></span><span class="line"><span class="ln">3</span><span class="cl">             └── 輸入事件</span></span></code></pre></div><p>應用程式用 OpenGL/Vulkan 自己渲染畫面到 buffer，然後把 buffer 直接交給 compositor。沒有中間人。compositor 收到所有 client 的 buffer 後合成最終畫面輸出到螢幕。</p>
<p>這個架構上的差異帶來三個實際影響：效能、安全、screen tearing。</p>
<h2 id="安全性">安全性</h2>
<p>X11 的安全模型有一個根本性的設計缺陷：任何連到同一個 X Server 的 client，都能監聽其他 client 的鍵盤輸入、擷取其他視窗的畫面、甚至注入假的輸入事件。這代表一個惡意程式可以輕易做到 keylogging——記錄你在其他視窗打的所有字，包括密碼。</p>
<p>Wayland 從協議層就隔離了 client 之間的存取。每個應用程式只看得到自己的 buffer，無法存取其他視窗的內容或輸入。要做 screen capture 或 screen sharing，必須透過 xdg-desktop-portal 這個受控的中介機制，使用者會看到明確的授權提示。</p>
<p>對 tiling WM 的使用者來說，這個安全差異特別有意義：平鋪式桌面通常同時開大量終端機視窗（有些跑 SSH、有些在 sudo 操作），X11 下任何一個視窗裡的惡意程式都能讀到其他視窗的鍵盤輸入。Wayland 阻斷了這條攻擊路徑。</p>
<h2 id="效能與-screen-tearing">效能與 Screen Tearing</h2>
<p>X11 的渲染路徑有多餘的記憶體複製——應用程式把繪圖指令送給 X Server，X Server 渲染後再交給 compositor，compositor 再合成。Wayland 省掉了中間那一步：應用程式直接渲染到 buffer、直接交給 compositor，記憶體複製更少。</p>
<p>Screen tearing（畫面撕裂）在 X11 上是長年的老問題——需要 compositor + vsync 的各種 workaround。Wayland 在協議層就處理了 frame presentation 的時機，compositor 控制什麼時候把合成好的畫面送到螢幕，原生消除 tearing。這也是為什麼 Hyprland 的動畫能跑得流暢——直接 rendering + 原生 vsync 的組合，在 X11 上需要大量額外配置才能接近的效果。</p>
<h2 id="xwaylandx11-相容層">XWayland：X11 相容層</h2>
<p>很多應用程式仍然只支援 X11 協議。XWayland 是一個 X11 server，但它本身作為一個 Wayland client 運行——X11 應用程式連到 XWayland，以為自己在跟正常的 X Server 溝通；XWayland 把 X11 協議翻譯成 Wayland 協議，把 buffer 交給 compositor。</p>
<p>多數 Wayland compositor（包括 Hyprland）預設啟用 XWayland，X11 應用程式不需要任何設定就能運行。</p>
<h3 id="需要-xwayland-的常見應用">需要 XWayland 的常見應用</h3>
<ul>
<li>舊版 Electron 應用（新版可用 <code>--ozone-platform=wayland</code> 旗標切換到 Wayland 原生）</li>
<li>Java 應用程式（NetBeans 等）</li>
<li>舊的 GTK2 應用程式</li>
<li>部分 Wine/Proton 遊戲（但 XWayland 下的遊戲效能已經相當好）</li>
<li>未啟用 PGTK 的舊版 Emacs build（Emacs 29+ 的 PGTK 後端已原生支援 Wayland）</li>
</ul>
<h3 id="xwayland-的已知問題">XWayland 的已知問題</h3>
<p>最大的實務痛點是 <strong>HiDPI 非整數縮放</strong>。在 150%、125% 等非整數 scale 下，XWayland 應用程式可能出現模糊渲染，因為 XWayland 不支援 fractional scaling 協議。整數縮放（100%、200%）則沒有問題。</p>
<h2 id="關鍵-wayland-協議">關鍵 Wayland 協議</h2>
<h3 id="wlr-layer-shell">wlr-layer-shell</h3>
<p>Layer-shell 定義了 client 可以在螢幕的哪個「層」上建立畫面——background（最底）、bottom、top、overlay（最頂）。Status bar（waybar）、notification daemon（mako）、launcher（wofi）、wallpaper（hyprpaper）、lock screen（hyprlock）都透過 layer-shell 來佔據螢幕空間。</p>
<p>沒有 layer-shell 支援的 compositor，這些桌面元件就無法運作。這也是為什麼<a href="/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">桌面 Rice 設計</a>的所有工具都只能在支援 layer-shell 的 Wayland compositor 上跑——它是可組裝式桌面的基礎設施。</p>
<h3 id="xdg-desktop-portal">xdg-desktop-portal</h3>
<p>Portal 是一個 D-Bus 介面，讓被隔離的 Wayland 應用程式可以請求特權操作：screen sharing、檔案對話框、螢幕截圖。每個 compositor 提供自己的 portal backend：</p>
<ul>
<li>Hyprland：<code>xdg-desktop-portal-hyprland</code></li>
<li>GNOME：<code>xdg-desktop-portal-gnome</code></li>
<li>KDE：<code>xdg-desktop-portal-kde</code></li>
<li>Sway/wlroots 系：<code>xdg-desktop-portal-wlr</code></li>
</ul>
<p>Screen sharing 的完整鏈路是：<code>PipeWire</code> + <code>xdg-desktop-portal-hyprland</code>。OBS 的 PipeWire source、瀏覽器的畫面分享，都走這條路。</p>
<h2 id="wlroots-與-hyprland-的關係">wlroots 與 Hyprland 的關係</h2>
<p>wlroots 是一個模組化的 C library，提供 Wayland compositor 的共用基礎建設——輸入處理、輸出管理、協議實作。Sway、river、wayfire 這些 compositor 都基於 wlroots 開發，共享同一套底層程式碼。</p>
<p>Hyprland 最初也基於 wlroots，但後來 fork 出去自己維護底層。這讓 Hyprland 可以更快地加入新功能（動畫、模糊、漸層邊框），不需要等 wlroots 上游審核。代價是 Hyprland 的更新不再跟 wlroots 生態同步，偶爾會有相容性差異。</p>
<h2 id="2026-年的-wayland-採用現況">2026 年的 Wayland 採用現況</h2>
<p>Wayland 在 2026 年已經從「取代 X11 的方向」變成「Linux 桌面的預設」：</p>
<table>
  <thead>
      <tr>
          <th>桌面環境 / 發行版</th>
          <th>狀態</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>GNOME</td>
          <td>GNOME 49（2025）放棄 X11 session，計劃在 GNOME 50 永久移除</td>
      </tr>
      <tr>
          <td>KDE Plasma</td>
          <td>Plasma 6.0 起預設 Wayland，6.8（2026 末）將 Wayland-only</td>
      </tr>
      <tr>
          <td>Fedora</td>
          <td>Fedora 43 完全移除 X11</td>
      </tr>
      <tr>
          <td>Ubuntu</td>
          <td>Ubuntu 25.10 不再提供 X11 session</td>
      </tr>
      <tr>
          <td>整體趨勢</td>
          <td>主流發行版的預設 session 已切換到 Wayland，X11 session 逐步移除中</td>
      </tr>
      <tr>
          <td>NVIDIA 驅動</td>
          <td>driver 555+ 支援 GBM，590 系列（2025）修復了多數 HDR 和多螢幕問題</td>
      </tr>
  </tbody>
</table>
<p>X11 上的老牌 tiling WM（i3、bspwm、dwm）仍然活躍，但不再是新專案的主流方向。新的 tiling WM 幾乎都選擇 Wayland 作為基礎。</p>
<h2 id="x11-仍有優勢的場景">X11 仍有優勢的場景</h2>
<p>Wayland 並非在所有面向都超越 X11：</p>
<ul>
<li><strong>SSH X forwarding</strong>：X11 原生支援透過網路轉送 GUI 視窗（<code>ssh -X</code>）。Wayland 沒有對等機制，替代方案是 <code>waypipe</code>，但不是 drop-in replacement</li>
<li><strong>跨 compositor 的 GUI 自動化</strong>：<code>xdotool</code>、<code>wmctrl</code> 在 X11 上可以操控任何 WM 的視窗。Wayland 沒有跨 compositor 的通用對應方案——每個 compositor 提供自己的 IPC 工具。不過 Hyprland 的 <code>hyprctl</code> 在 Hyprland 環境內提供了比 xdotool 更豐富的能力：socket-based IPC、JSON 格式查詢所有視窗和工作區狀態、即時 dispatch 動作、event 監控（<code>hyprctl socket2</code>）。瓶頸在「跨 compositor 通用」，不在單一 compositor 內的功能深度</li>
<li><strong>VNC / 遠端桌面</strong>：X11 的網路透明性讓遠端桌面相對簡單。Wayland 需要 PipeWire + portal，設定複雜度更高</li>
<li><strong>Color management</strong>：專業色彩工作流（ICC profile 管理）在部分 Wayland compositor 上仍需手動配置</li>
</ul>
<p>這些場景如果是你的核心需求，目前 X11（或 X11-based 的 i3/sway 的 X11 版本）可能仍是更務實的選擇。</p>
<h2 id="為什麼-tiling-wm-使用者該關心-wayland">為什麼 Tiling WM 使用者該關心 Wayland</h2>
<p>平鋪式桌面是「自己從元件組裝桌面」的工作流。Wayland 的幾個特性讓這件事變得更好：</p>
<p><strong>layer-shell</strong> 給了 status bar、launcher、notification daemon 一個標準化的方式來佔據螢幕空間，不再需要各種 hack 讓這些元件「浮在視窗上面又不被平鋪規則管到」。</p>
<p><strong>安全隔離</strong>在多終端機場景特別有價值——平鋪式桌面通常同時開著 SSH session、密碼管理器、sudo 操作，Wayland 確保任何一個視窗裡的程式都無法偷看其他視窗的鍵盤輸入。</p>
<p><strong>直接 rendering</strong> 讓 Hyprland 能做到流暢的視窗動畫和即時模糊，這在 X11 上需要大量額外的 compositor 配置（picom + 各種 backend 選擇 + vsync 調校）才能接近。</p>
<p>從實務角度看：2026 年開始接觸 Linux tiling WM，選 Wayland-based 的工具（Hyprland、Sway）是順流而行。X11 不會立刻消失，但新功能、新工具、社群活力都在 Wayland 這一邊。</p>
]]></content:encoded></item><item><title>服務掛了怎麼自動知道：從肉眼盯到主動告警</title><link>https://tarrragon.github.io/blog/linux/debug/service-failure-monitoring/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/debug/service-failure-monitoring/</guid><description>&lt;p>服務掛了不需要用肉眼盯——systemd 本來就在追蹤每個 unit 的狀態，你要做的是把「讀權威狀態」這件事自動化，並在狀態變成失敗時主動推播給自己。這篇跟本系列其他篇的差別在時機：診斷是出事後回頭找根因，監控是讓系統在出事的當下就告訴你。兩者共用同一個地基——權威狀態。診斷是手動讀一次權威狀態，監控是訂閱權威狀態的變化、變壞就推播。&lt;/p>
&lt;p>理解這個框架後，監控就不是「裝一套很重的東西」，而是分層選擇：從 systemd 內建的失敗鉤子（不裝任何額外服務），到推播管道，到「整台機器死掉」的體外心跳，到完整的指標儀表板。多數人只需要前一兩層。&lt;/p>
&lt;h2 id="你現在手動在做的事要被取代的基線">你現在手動在做的事（要被取代的基線）&lt;/h2>
&lt;p>在自動化之前，先認清手動版本——這也是所有告警底層讀的同一個權威來源：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">systemctl --failed &lt;span class="c1"># 現在有哪些 unit 處於 failed（開機後系統怪怪的先掃這個）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">systemctl is-failed &amp;lt;unit&amp;gt; &lt;span class="c1"># 單一 unit 明確判失敗（比 is-active 直接）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">journalctl -u &amp;lt;unit&amp;gt; -f &lt;span class="c1"># 即時跟一個 unit 的 log&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>systemctl --failed&lt;/code> 就是「服務死活」的權威清單。手動版的問題不是不準，是你得記得去看。下面每一層都是把「記得去看」換成「壞了它來找你」。&lt;/p>
&lt;h2 id="第一層systemd-原生-onfailure-鉤子不裝額外服務">第一層：systemd 原生 &lt;code>OnFailure&lt;/code> 鉤子（不裝額外服務）&lt;/h2>
&lt;p>systemd 每個 unit 進入 failed 狀態時，可以自動觸發另一個 unit。這是最正統、零額外依賴的做法——告警邏輯就寫成一個普通的 systemd service。它由三塊組成：一個負責送通知的處理器 unit、一個實際送出的腳本、以及在你要監控的 unit 上掛一行 &lt;code>OnFailure=&lt;/code>。&lt;/p>
&lt;p>&lt;strong>通知處理器&lt;/strong>是一個 template unit（&lt;code>@&lt;/code> 表示可帶參數），參數 &lt;code>%i&lt;/code> 會是失敗的那個 unit 名：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># /etc/systemd/system/alert@.service&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="k">[Unit]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="na">Description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">Alert on failure of %i&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="k">[Service]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="na">Type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">oneshot&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="na">ExecStart&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">/usr/local/bin/notify-failure %i&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>送出腳本&lt;/strong>負責把「哪個 unit、在哪台機、什麼時候」推出去。這裡有個實測踩到的坑：在 systemd service 的執行環境下，&lt;code>hostname&lt;/code> 指令可能回傳空字串，要改用 &lt;code>uname -n&lt;/code> 或讀 &lt;code>/etc/hostname&lt;/code> 才穩：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="cp">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="cp">&lt;/span>&lt;span class="c1"># /usr/local/bin/notify-failure （記得 chmod +x）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="nv">unit&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$1&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 只在「真正放棄」時告警：OnFailure 每次失敗都觸發（含 auto-restart 中途，見下節實測），&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c1"># auto-restart 中途 ActiveState 是 activating、撞重試上限才進 failed。gate 掉中途避免洗告警。&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="nv">state&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>systemctl show &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$unit&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> -p ActiveState --value&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="o">[&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$state&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">=&lt;/span> failed &lt;span class="o">]&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nb">exit&lt;/span> &lt;span class="m">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="nv">host&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>uname -n&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="c1"># 不要用 hostname，systemd 環境下可能回空&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="nv">ts&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>date -Is&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="nv">topic&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;你的私密topic&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">curl -fsS &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> -H &lt;span class="s2">&amp;#34;Title: &lt;/span>&lt;span class="nv">$host&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="nv">$unit&lt;/span>&lt;span class="s2"> failed&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> -d &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$unit&lt;/span>&lt;span class="s2"> 於 &lt;/span>&lt;span class="nv">$ts&lt;/span>&lt;span class="s2"> 進入 failed&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="s2">&amp;#34;https://ntfy.sh/&lt;/span>&lt;span class="nv">$topic&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>在要監控的 unit 掛上鉤子&lt;/strong>。針對單一 unit，加一行：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">[Unit]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="na">OnFailure&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">alert@%n.service # %n 是本 unit 的全名，會展開成 alert@&amp;lt;本unit&amp;gt;.service&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>要&lt;strong>一次套用到所有 service&lt;/strong>，用 top-level drop-in（放在 &lt;code>service.d/&lt;/code> 這個型別目錄下的設定會套用到每個 &lt;code>.service&lt;/code>）：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># /etc/systemd/system/service.d/onfailure.conf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="k">[Unit]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="na">OnFailure&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">alert@%n.service&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>改完 &lt;code>sudo systemctl daemon-reload&lt;/code>。&lt;strong>一個必須注意的遞迴陷阱&lt;/strong>：全域 drop-in 也會套到 &lt;code>alert@&lt;/code> 自己，它若失敗會觸發自己。給 &lt;code>alert@.service&lt;/code> 一個清空 &lt;code>OnFailure=&lt;/code> 的 override（&lt;code>[Unit]&lt;/code> 段寫 &lt;code>OnFailure=&lt;/code>）擋掉。&lt;/p>
&lt;p>這條鏈是實測驗證過的：故意讓一個 &lt;code>ExecStart=/bin/false&lt;/code> 的測試 service 失敗，systemd log 出現 &lt;code>Triggering OnFailure= dependencies&lt;/code>、&lt;code>alert@&lt;/code> 處理器被觸發跑完、&lt;code>curl&lt;/code> 推到 ntfy 回 HTTP 200——通知確實送出，全程沒有肉眼介入。&lt;/p>
&lt;h3 id="先自動重啟放棄了才吵你">先自動重啟、放棄了才吵你&lt;/h3>
&lt;p>多數暫時性失敗（一次連線抖動、一個 race）自己重試就好，不值得半夜叫醒你。把「自動復原」跟「告警」分兩段：讓 systemd 先重啟幾次，撐過重試上限才真的算放棄。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">[Service]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="na">Restart&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">on-failure&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="na">RestartSec&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="k">[Unit]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="na">StartLimitBurst&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">3 # 重試 3 次&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="na">StartLimitIntervalSec&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">60 # 60 秒內都失敗才進 failed（start-limit-hit）&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>這裡有個實測踩到、跟直覺相反的坑&lt;/strong>：&lt;code>OnFailure&lt;/code> 不是「放棄才觸發」，而是&lt;strong>每一次失敗都觸發&lt;/strong>——包含 &lt;code>Restart=on-failure&lt;/code> 的每次 auto-restart 中途。實測一個反覆 crash 的服務（重試 3 次後放棄）觸發了 &lt;strong>4 次&lt;/strong> &lt;code>OnFailure&lt;/code>（3 次 auto-restart + 1 次最終 &lt;code>start-limit-hit&lt;/code>）。所以只靠 &lt;code>Restart=&lt;/code> + &lt;code>StartLimit=&lt;/code> 這段 config，你會被每次瞬斷洗告警。&lt;/p></description><content:encoded><![CDATA[<p>服務掛了不需要用肉眼盯——systemd 本來就在追蹤每個 unit 的狀態，你要做的是把「讀權威狀態」這件事自動化，並在狀態變成失敗時主動推播給自己。這篇跟本系列其他篇的差別在時機：診斷是出事後回頭找根因，監控是讓系統在出事的當下就告訴你。兩者共用同一個地基——權威狀態。診斷是手動讀一次權威狀態，監控是訂閱權威狀態的變化、變壞就推播。</p>
<p>理解這個框架後，監控就不是「裝一套很重的東西」，而是分層選擇：從 systemd 內建的失敗鉤子（不裝任何額外服務），到推播管道，到「整台機器死掉」的體外心跳，到完整的指標儀表板。多數人只需要前一兩層。</p>
<h2 id="你現在手動在做的事要被取代的基線">你現在手動在做的事（要被取代的基線）</h2>
<p>在自動化之前，先認清手動版本——這也是所有告警底層讀的同一個權威來源：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">systemctl --failed          <span class="c1"># 現在有哪些 unit 處於 failed（開機後系統怪怪的先掃這個）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">systemctl is-failed &lt;unit&gt;  <span class="c1"># 單一 unit 明確判失敗（比 is-active 直接）</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">journalctl -u &lt;unit&gt; -f     <span class="c1"># 即時跟一個 unit 的 log</span></span></span></code></pre></div><p><code>systemctl --failed</code> 就是「服務死活」的權威清單。手動版的問題不是不準，是你得記得去看。下面每一層都是把「記得去看」換成「壞了它來找你」。</p>
<h2 id="第一層systemd-原生-onfailure-鉤子不裝額外服務">第一層：systemd 原生 <code>OnFailure</code> 鉤子（不裝額外服務）</h2>
<p>systemd 每個 unit 進入 failed 狀態時，可以自動觸發另一個 unit。這是最正統、零額外依賴的做法——告警邏輯就寫成一個普通的 systemd service。它由三塊組成：一個負責送通知的處理器 unit、一個實際送出的腳本、以及在你要監控的 unit 上掛一行 <code>OnFailure=</code>。</p>
<p><strong>通知處理器</strong>是一個 template unit（<code>@</code> 表示可帶參數），參數 <code>%i</code> 會是失敗的那個 unit 名：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># /etc/systemd/system/alert@.service</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Alert on failure of %i</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/local/bin/notify-failure %i</span></span></span></code></pre></div><p><strong>送出腳本</strong>負責把「哪個 unit、在哪台機、什麼時候」推出去。這裡有個實測踩到的坑：在 systemd service 的執行環境下，<code>hostname</code> 指令可能回傳空字串，要改用 <code>uname -n</code> 或讀 <code>/etc/hostname</code> 才穩：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="cp"></span><span class="c1"># /usr/local/bin/notify-failure   （記得 chmod +x）</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nv">unit</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 只在「真正放棄」時告警：OnFailure 每次失敗都觸發（含 auto-restart 中途，見下節實測），</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># auto-restart 中途 ActiveState 是 activating、撞重試上限才進 failed。gate 掉中途避免洗告警。</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nv">state</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>systemctl show <span class="s2">&#34;</span><span class="nv">$unit</span><span class="s2">&#34;</span> -p ActiveState --value<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="o">[</span> <span class="s2">&#34;</span><span class="nv">$state</span><span class="s2">&#34;</span> <span class="o">=</span> failed <span class="o">]</span> <span class="o">||</span> <span class="nb">exit</span> <span class="m">0</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nv">host</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>uname -n<span class="k">)</span><span class="s2">&#34;</span>                     <span class="c1"># 不要用 hostname，systemd 環境下可能回空</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nv">ts</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>date -Is<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nv">topic</span><span class="o">=</span><span class="s2">&#34;你的私密topic&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">curl -fsS <span class="se">\
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="se"></span>  -H <span class="s2">&#34;Title: </span><span class="nv">$host</span><span class="s2">: </span><span class="nv">$unit</span><span class="s2"> failed&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="se"></span>  -d <span class="s2">&#34;</span><span class="nv">$unit</span><span class="s2"> 於 </span><span class="nv">$ts</span><span class="s2"> 進入 failed&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="se"></span>  <span class="s2">&#34;https://ntfy.sh/</span><span class="nv">$topic</span><span class="s2">&#34;</span></span></span></code></pre></div><p><strong>在要監控的 unit 掛上鉤子</strong>。針對單一 unit，加一行：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="na">OnFailure</span><span class="o">=</span><span class="s">alert@%n.service    # %n 是本 unit 的全名，會展開成 alert@&lt;本unit&gt;.service</span></span></span></code></pre></div><p>要<strong>一次套用到所有 service</strong>，用 top-level drop-in（放在 <code>service.d/</code> 這個型別目錄下的設定會套用到每個 <code>.service</code>）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># /etc/systemd/system/service.d/onfailure.conf</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="na">OnFailure</span><span class="o">=</span><span class="s">alert@%n.service</span></span></span></code></pre></div><p>改完 <code>sudo systemctl daemon-reload</code>。<strong>一個必須注意的遞迴陷阱</strong>：全域 drop-in 也會套到 <code>alert@</code> 自己，它若失敗會觸發自己。給 <code>alert@.service</code> 一個清空 <code>OnFailure=</code> 的 override（<code>[Unit]</code> 段寫 <code>OnFailure=</code>）擋掉。</p>
<p>這條鏈是實測驗證過的：故意讓一個 <code>ExecStart=/bin/false</code> 的測試 service 失敗，systemd log 出現 <code>Triggering OnFailure= dependencies</code>、<code>alert@</code> 處理器被觸發跑完、<code>curl</code> 推到 ntfy 回 HTTP 200——通知確實送出，全程沒有肉眼介入。</p>
<h3 id="先自動重啟放棄了才吵你">先自動重啟、放棄了才吵你</h3>
<p>多數暫時性失敗（一次連線抖動、一個 race）自己重試就好，不值得半夜叫醒你。把「自動復原」跟「告警」分兩段：讓 systemd 先重啟幾次，撐過重試上限才真的算放棄。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="na">Restart</span><span class="o">=</span><span class="s">on-failure</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="na">RestartSec</span><span class="o">=</span><span class="s">5</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="na">StartLimitBurst</span><span class="o">=</span><span class="s">3          # 重試 3 次</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="na">StartLimitIntervalSec</span><span class="o">=</span><span class="s">60   # 60 秒內都失敗才進 failed（start-limit-hit）</span></span></span></code></pre></div><p><strong>這裡有個實測踩到、跟直覺相反的坑</strong>：<code>OnFailure</code> 不是「放棄才觸發」，而是<strong>每一次失敗都觸發</strong>——包含 <code>Restart=on-failure</code> 的每次 auto-restart 中途。實測一個反覆 crash 的服務（重試 3 次後放棄）觸發了 <strong>4 次</strong> <code>OnFailure</code>（3 次 auto-restart + 1 次最終 <code>start-limit-hit</code>）。所以只靠 <code>Restart=</code> + <code>StartLimit=</code> 這段 config，你會被每次瞬斷洗告警。</p>
<p>真正做到「只在放棄才吵」，靠的是上面送出腳本開頭那道 gate：<code>systemctl show &lt;unit&gt; -p ActiveState</code> 在 auto-restart 中途是 <code>activating</code>、撞上限進 failed 才是 <code>failed</code>，腳本只在 <code>failed</code> 才送。加上 gate 後同一個 crash 測試從 4 次告警降到 1 次（只剩最終放棄那次）。config 負責「重試幾次」，handler 的 gate 負責「只在終局告警」——兩段合起來才是完整的「先重啟、放棄才吵」。</p>
<h3 id="抓進程活著但沒在做事外部健康探針">抓「進程活著但沒在做事」：外部健康探針</h3>
<p><code>OnFailure</code> 抓的是「進程狀態變了」——crash、exit、被 kill。但服務可能<strong>進程還在、卻沒在做事</strong>：hung、deadlock、內部子系統壞掉。這種 systemd 看它還 <code>active</code>、不會觸發任何告警——正是<a href="../process-service-state-diagnosis/">「進程活著 ≠ 在運作」</a>那條，搬到監控場景。</p>
<p>要抓這種，得從外面<strong>主動戳它、看它回不回應</strong>：一個 timer 定時對服務發一個健康請求（HTTP 服務就 curl 它的 <code>/health</code>）並設逾時；戳不動、逾時失敗，就讓「那個檢查」自己 failed，一樣走 <code>OnFailure</code> 告警。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># health-check.service（oneshot）+ 一個每 2 分鐘跑的 .timer</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/curl -fsS --max-time 5 http://127.0.0.1:8899/health</span></span></span></code></pre></div><p>實測對照最清楚：讓一個健康服務卡在 <code>sleep</code>（進程還在、單執行緒不再回應），<code>systemctl is-active</code> 仍顯示 <code>active</code>——systemd 沒察覺；但這個外部探針 curl <code>/health</code> 5 秒逾時、check 失敗、告警發出。<strong>systemd 抓進程死、外部探針抓進程活著但 hung，兩層互補、缺一漏一種。</strong></p>
<h3 id="canary先證明告警管線本身是好的">canary：先證明告警管線本身是好的</h3>
<p>監控最怕的失效模式是「出事時才發現它早就不會叫了」。防這個的辦法是養一隻 <strong>canary</strong>——一個你可控的假服務，專門用來確認整條管線是活的。它一物兩用：</p>
<ul>
<li><strong>驗證管線</strong>：故意弄掛它，看「失敗 → OnFailure → 推送」真的一路通到你手機，不必拿 sshd 這種真服務去冒險。</li>
<li><strong>當活性訊號</strong>：它自己若無故失敗告警，等於告訴你告警系統本身還在運作。</li>
</ul>
<p>做法是一個極簡 HTTP 服務（stdlib 就夠、不必框架），留幾個測試入口：<code>/health</code> 正常回、<code>/crash</code> 故意退出（測 <code>OnFailure</code>）、<code>/hang</code> 進程活著但不回應（測外部探針）。這樣任何時候都能一鍵重驗監控沒有默默失效。</p>
<h2 id="第二層推去哪裡關鍵是能離開這台機器">第二層：推去哪裡（關鍵是能離開這台機器）</h2>
<p>處理器腳本裡那一段 <code>curl</code> 可以換成任何管道：</p>
<ul>
<li><strong>ntfy</strong>（<code>ntfy.sh</code> 或自架）：一行 <code>curl</code> 推到手機，最省事，上面的例子就是。它怎麼運作、公共站 vs 自架、以及「topic 名稱就是唯一的密碼」這個安全模型，見 <a href="../ntfy-push-notification-service/">ntfy：推送通知服務</a>。</li>
<li><strong>email</strong>：要先設好一個 MTA（如 <code>msmtp</code>），腳本改成 <code>mail</code> / <code>sendmail</code>。</li>
<li><strong>Telegram bot、Apprise</strong>（一個工具打多個目標）等。</li>
</ul>
<p>判準只有一條：<strong>告警要送到機器外</strong>。送桌面 <code>notify-send</code> 只有你正盯著螢幕時才有用；送手機或 email，離開座位、人在外面也收得到。一台跑正事的機器，告警管道應該落在它之外。</p>
<h2 id="第三層整台機器死掉怎麼辦監控自己的盲點">第三層：整台機器死掉怎麼辦（監控自己的盲點）</h2>
<p><code>OnFailure</code> 有個根本限制：<strong>它靠 systemd 觸發，機器整台掛了（當機、斷電、kernel panic），systemd 自己都沒了，發不出任何告警。</strong> 這是所有「機器自己監控自己」方案的共同盲點——它報得了服務的死，報不了自己這台的死。</p>
<p>覆蓋這一層要反過來做：讓機器定時對一個<strong>體外</strong>的服務「報平安」，平安訊號一停，由那個體外服務替你告警。這叫 dead-man&rsquo;s switch（心跳監控）。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># /etc/systemd/system/heartbeat.service</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">curl -fsS https://hc-ping.com/&lt;你的-uuid&gt;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 搭配一個 heartbeat.timer，OnUnitActiveSec=5min 定時打</span></span></span></code></pre></div><p>心跳超過設定時間沒到，healthchecks.io（或自架的 Uptime Kuma）就通知你。<strong>體內的監控管不了自己這台的死亡，一定要有體外的一隻眼睛</strong>——這跟本系列 <a href="../machine-unreachable/">機器連不到或起不來</a> 是同一個問題的兩面：那篇是機器已經不回應時從外面怎麼查，心跳是讓「不回應」這件事本身自動觸發告警。</p>
<h2 id="第四層要指標趨勢門檻不只是-updown">第四層：要指標、趨勢、門檻（不只是 up/down）</h2>
<p>當你要的不只是「掛了沒」，而是 CPU、記憶體、磁碟、延遲的趨勢與門檻告警（例如磁碟用量超過 80% 就先警告，接上本系列反覆出現的「磁碟滿連鎖」），就進到完整監控堆疊：</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>定位</th>
          <th>什麼時候選它</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Netdata</td>
          <td>開箱即用、自帶大量預設告警</td>
          <td>單機、想要圖表 + 門檻告警、最不想設定</td>
      </tr>
      <tr>
          <td>Monit</td>
          <td>輕量、每服務健康檢查 + 自動動作</td>
          <td>要「掛了自動跑一段修復腳本」、超出 systemd <code>Restart=</code> 能表達的邏輯</td>
      </tr>
      <tr>
          <td>Prometheus + Alertmanager</td>
          <td>指標抓取 + 告警規則引擎</td>
          <td>多台機器、要歷史數據與可擴展的告警規則</td>
      </tr>
      <tr>
          <td>Uptime Kuma</td>
          <td>自架的 up/down + 心跳面板</td>
          <td>想要一個面板統一看多台/多服務、也能當第三層的心跳接收端</td>
      </tr>
  </tbody>
</table>
<p>這一層不是每個人都需要。單機、只想知道某個服務死活，第一層就夠；要看趨勢、跨機、設門檻，才值得付這層的設定與維運成本。</p>
<h2 id="先確認有沒有沒有就從最簡單開始">先確認有沒有，沒有就從最簡單開始</h2>
<p>監控最好在出事之前就建好，不是等第一次沒人發現的當機才想到。有兩個時機該主動確認這台機器有沒有在監控自己：<strong>裝好一台新機器時</strong>，跟<strong>發現自己反覆在除同一個服務的失敗時</strong>。確認的方式就是讀權威狀態：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">systemctl --failed                      <span class="c1"># 現在有沒有 failed 的</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">systemctl show sshd -p OnFailure        <span class="c1"># 關鍵服務有沒有掛告警鉤子</span></span></span></code></pre></div><p>沒有任何監控的話，<strong>從最簡單那層開始建，別一開始就上重的</strong>：第一層的 <code>OnFailure</code> + ntfy 就能讓「服務掛了」主動找上你，零額外 daemon、幾個檔案就設好。遠端機器至少把 sshd 掛上——它掛了你就失聯，是最該先監控的一個。等你真的需要趨勢圖、跨機、或告警內容不能經過第三方時，再往自架 ntfy（帳號 + ACL）跟完整監控堆疊爬。多數單機、個人用的情境，停在第一層就夠。</p>
<h2 id="依情境選">依情境選</h2>
<p>把上面四層對回你實際要監控的東西：</p>
<ul>
<li><strong>某個 service 掛了想被通知</strong> → 第一層 <code>OnFailure</code> drop-in + ntfy。不裝額外 daemon，最貼近 systemd。</li>
<li><strong>希望先自動重啟、救不回來才告警</strong> → 第一層再加 <code>Restart=on-failure</code> + <code>StartLimit*</code>。</li>
<li><strong>怕整台機器當掉沒人知道</strong> → 第三層心跳 / dead-man switch。這層體內方案覆蓋不到，必須體外。</li>
<li><strong>要看資源趨勢、跨多台、設門檻告警</strong> → 第四層，單機用 Netdata、多機用 Prometheus 堆疊。</li>
</ul>
<p>判準是先分清你要監控的層級：<strong>單一 service 的死活、整台機器的死活、還是資源的趨勢</strong>——三種對應不同層，別拿其中一種去蓋另一種。最常見的誤區是以為體內的 <code>OnFailure</code> 能報自己這台的當機，那正是它的盲點。</p>
<h2 id="下一步">下一步</h2>
<ul>
<li>告警把你叫來之後，怎麼判那個服務到底是什麼狀態（failed、restart loop、還是活著但子系統 wedged）→ <a href="../process-service-state-diagnosis/">程序、服務與狀態怎麼判</a>。</li>
<li>機器完全不回應、心跳斷掉之後從外面怎麼查 → <a href="../machine-unreachable/">機器連不到或起不來</a>。</li>
<li>底層那套「讀權威狀態、不靠肉眼猜」的判讀紀律 → <a href="../diagnosis-read-authoritative-state/">診斷心法</a>。</li>
</ul>
]]></content:encoded></item><item><title>外部連入、SSH key 與無 key 的 bootstrap 路徑</title><link>https://tarrragon.github.io/blog/linux/install/ssh-keyless-bootstrap/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/install/ssh-keyless-bootstrap/</guid><description>&lt;p>操作一台新機器，從你本機的終端機透過 SSH 連進去是阻力最小的位置。直接在主控台操作有兩個實際的痛點：純文字的主控台（TTY 或虛擬機的序列 console）往往不能貼上，長指令只能手打、還容易掉字；畫面也通常擠、不能捲。把機器的 sshd 跑起來、從本機 SSH 進去之後，貼上、捲動、補全全部回到你熟悉的環境，而且這條路本身就貼近真實的遠端維運。&lt;/p>
&lt;p>這篇處理三件事：把 sshd 跑起來並從本機連入、設 SSH key 達到免密碼、以及一個容易被卡住的情境——你還沒有 SSH key 時，怎麼把 dotfile 弄進機器、跑完基礎安裝。&lt;/p>
&lt;h2 id="啟用-sshd-並從本機連入">啟用 sshd 並從本機連入&lt;/h2>
&lt;p>讓機器能被 SSH 連入只需要兩步：裝 SSH 伺服器、啟動它的服務。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">pacman -S openssh &lt;span class="c1"># 剛裝好的系統套件資料庫是新的，-S 不必先 -Sy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">systemctl &lt;span class="nb">enable&lt;/span> --now sshd &lt;span class="c1"># enable 開機自啟、--now 立刻啟動&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>指令以 Arch 為例。換發行版時套件管理器不同（Fedora &lt;code>dnf&lt;/code>、Debian/Ubuntu &lt;code>apt&lt;/code>），服務名也可能不同——Debian 系的 OpenSSH 服務叫 &lt;code>ssh&lt;/code> 不是 &lt;code>sshd&lt;/code>，那邊要 &lt;code>systemctl enable --now ssh&lt;/code>。&lt;/p>
&lt;p>從本機連的時候用一般使用者、不要用 root：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">ssh user@&amp;lt;機器 IP&amp;gt; &lt;span class="c1"># IP 來自機器上的 ip -brief a&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>用一般使用者是因為多數發行版的 sshd 預設擋 root 密碼登入（&lt;code>PermitRootLogin prohibit-password&lt;/code>）——root 只能用 key、不能用密碼。這個預設是好的安全姿態，順著它走、用你裝系統時建的一般使用者連即可。連進去之後，後續所有需要長指令、需要貼上的操作都在這個 session 裡做，不再回主控台手打。&lt;/p>
&lt;p>這裡啟用 sshd 是為了 bootstrap 期間從本機連入操作，跟 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/00-dotfile-mindset/setup-order-guide/" data-link-title="環境建置的操作順序" data-link-desc="第一次從零建立 Linux 或 macOS 開發環境、不確定先做什麼後做什麼時讀 — 依賴順序路線圖，每一步附對應模組連結">操作順序指引&lt;/a> 後段把 sshd 當「桌面就緒後的常駐遠端救援通道」是兩個不同的時間點與目的——同一個 &lt;code>systemctl enable sshd&lt;/code> 動作，這裡是為了「現在好操作」，那裡是為了「之後好救援」。&lt;/p>
&lt;h2 id="ssh-key-免密碼">SSH key 免密碼&lt;/h2>
&lt;p>每次連線都打密碼很快會變成阻力，尤其當你要反覆同步檔案或跑自動化時。SSH key 讓本機免密碼連入，做法是生一把金鑰、把公鑰放進機器、本機用私鑰認證。&lt;/p>
&lt;p>生 key 時建議生一把專用的、不要佔用本機的預設金鑰槽，並在 SSH 設定裡給它一個好記的別名：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">ssh-keygen -t ed25519 -f ~/.ssh/vm_arch -N &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span> -C &lt;span class="s2">&amp;#34;vm_arch host-&amp;gt;target&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1"># 在 ~/.ssh/config 加一段別名：&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="c1"># Host vm&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># HostName &amp;lt;機器 IP&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1"># User &amp;lt;你的使用者&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="c1"># IdentityFile ~/.ssh/vm_arch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="c1"># IdentitiesOnly yes&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>專用 key 的好處是它的權限範圍清楚——這把只給這台機器用，跟你其他身分的金鑰互不牽連。設好別名後，&lt;code>ssh vm&lt;/code> 就免密碼連入，後面的 &lt;code>rsync&lt;/code>、&lt;code>scp&lt;/code> 也跟著免密碼。&lt;/p>
&lt;p>把公鑰放進機器有兩條路。標準工具是 &lt;code>ssh-copy-id&lt;/code>，它會在本機跑、要你輸入一次目標機的密碼。另一條省一次切換的路是：當你已經用密碼連進機器、且這個 session 在真終端機裡（貼上可用），直接把公鑰內容貼進機器的 &lt;code>authorized_keys&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">mkdir -p ~/.ssh &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> chmod &lt;span class="m">700&lt;/span> ~/.ssh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;ssh-ed25519 AAAA... 你的公鑰內容&amp;#34;&lt;/span> &amp;gt;&amp;gt; ~/.ssh/authorized_keys
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">chmod &lt;span class="m">600&lt;/span> ~/.ssh/authorized_keys&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>兩條路等價，選哪條看你當下在哪——還沒連上就用 &lt;code>ssh-copy-id&lt;/code>，已經連上就直接貼，少一次切換。&lt;/p>
&lt;h2 id="還沒有-ssh-key-時怎麼把-dotfile-弄進去">還沒有 SSH key 時，怎麼把 dotfile 弄進去&lt;/h2>
&lt;p>設 SSH key 是讓「之後」連線變方便，但 bootstrap 的第一步——把 dotfile repo 弄進機器——並不一定需要 key。常見的卡點是把「clone repo」跟「有 SSH key」綁在一起，但 clone 有不需要 key 的路徑。怎麼把 dotfile 弄進去，取決於這份 dotfile 放在哪。&lt;/p>
&lt;p>&lt;strong>repo 是公開的（在 GitHub 之類）&lt;/strong>：用 HTTPS clone，公開 repo 的唯讀 clone 不需要任何認證。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">git clone https://github.com/&amp;lt;帳號&amp;gt;/dotfiles ~/dotfiles
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ~/dotfiles &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> ./scripts/install.sh&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這是最直接的路——機器只要能上網就能拉到 dotfile，完全繞過 key 的問題。clone URL 裡的帳號要對；用錯帳號（例如把 email handle 當成 GitHub 帳號）會 clone 失敗或抓到別的 repo，這類筆誤在只看 README 範例時很容易漏掉。SSH key 在這個情境只有「之後要從機器 push 回去」才需要，純粹跑部署用不到。&lt;/p></description><content:encoded><![CDATA[<p>操作一台新機器，從你本機的終端機透過 SSH 連進去是阻力最小的位置。直接在主控台操作有兩個實際的痛點：純文字的主控台（TTY 或虛擬機的序列 console）往往不能貼上，長指令只能手打、還容易掉字；畫面也通常擠、不能捲。把機器的 sshd 跑起來、從本機 SSH 進去之後，貼上、捲動、補全全部回到你熟悉的環境，而且這條路本身就貼近真實的遠端維運。</p>
<p>這篇處理三件事：把 sshd 跑起來並從本機連入、設 SSH key 達到免密碼、以及一個容易被卡住的情境——你還沒有 SSH key 時，怎麼把 dotfile 弄進機器、跑完基礎安裝。</p>
<h2 id="啟用-sshd-並從本機連入">啟用 sshd 並從本機連入</h2>
<p>讓機器能被 SSH 連入只需要兩步：裝 SSH 伺服器、啟動它的服務。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">pacman -S openssh             <span class="c1"># 剛裝好的系統套件資料庫是新的，-S 不必先 -Sy</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">systemctl <span class="nb">enable</span> --now sshd   <span class="c1"># enable 開機自啟、--now 立刻啟動</span></span></span></code></pre></div><p>指令以 Arch 為例。換發行版時套件管理器不同（Fedora <code>dnf</code>、Debian/Ubuntu <code>apt</code>），服務名也可能不同——Debian 系的 OpenSSH 服務叫 <code>ssh</code> 不是 <code>sshd</code>，那邊要 <code>systemctl enable --now ssh</code>。</p>
<p>從本機連的時候用一般使用者、不要用 root：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">ssh user@&lt;機器 IP&gt;            <span class="c1"># IP 來自機器上的 ip -brief a</span></span></span></code></pre></div><p>用一般使用者是因為多數發行版的 sshd 預設擋 root 密碼登入（<code>PermitRootLogin prohibit-password</code>）——root 只能用 key、不能用密碼。這個預設是好的安全姿態，順著它走、用你裝系統時建的一般使用者連即可。連進去之後，後續所有需要長指令、需要貼上的操作都在這個 session 裡做，不再回主控台手打。</p>
<p>這裡啟用 sshd 是為了 bootstrap 期間從本機連入操作，跟 <a href="/blog/linux/dotfile/00-dotfile-mindset/setup-order-guide/" data-link-title="環境建置的操作順序" data-link-desc="第一次從零建立 Linux 或 macOS 開發環境、不確定先做什麼後做什麼時讀 — 依賴順序路線圖，每一步附對應模組連結">操作順序指引</a> 後段把 sshd 當「桌面就緒後的常駐遠端救援通道」是兩個不同的時間點與目的——同一個 <code>systemctl enable sshd</code> 動作，這裡是為了「現在好操作」，那裡是為了「之後好救援」。</p>
<h2 id="ssh-key-免密碼">SSH key 免密碼</h2>
<p>每次連線都打密碼很快會變成阻力，尤其當你要反覆同步檔案或跑自動化時。SSH key 讓本機免密碼連入，做法是生一把金鑰、把公鑰放進機器、本機用私鑰認證。</p>
<p>生 key 時建議生一把專用的、不要佔用本機的預設金鑰槽，並在 SSH 設定裡給它一個好記的別名：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">ssh-keygen -t ed25519 -f ~/.ssh/vm_arch -N <span class="s2">&#34;&#34;</span> -C <span class="s2">&#34;vm_arch host-&gt;target&#34;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 在 ~/.ssh/config 加一段別名：</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">#   Host vm</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">#       HostName &lt;機器 IP&gt;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">#       User &lt;你的使用者&gt;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1">#       IdentityFile ~/.ssh/vm_arch</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">#       IdentitiesOnly yes</span></span></span></code></pre></div><p>專用 key 的好處是它的權限範圍清楚——這把只給這台機器用，跟你其他身分的金鑰互不牽連。設好別名後，<code>ssh vm</code> 就免密碼連入，後面的 <code>rsync</code>、<code>scp</code> 也跟著免密碼。</p>
<p>把公鑰放進機器有兩條路。標準工具是 <code>ssh-copy-id</code>，它會在本機跑、要你輸入一次目標機的密碼。另一條省一次切換的路是：當你已經用密碼連進機器、且這個 session 在真終端機裡（貼上可用），直接把公鑰內容貼進機器的 <code>authorized_keys</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">mkdir -p ~/.ssh <span class="o">&amp;&amp;</span> chmod <span class="m">700</span> ~/.ssh
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;ssh-ed25519 AAAA... 你的公鑰內容&#34;</span> &gt;&gt; ~/.ssh/authorized_keys
</span></span><span class="line"><span class="ln">3</span><span class="cl">chmod <span class="m">600</span> ~/.ssh/authorized_keys</span></span></code></pre></div><p>兩條路等價，選哪條看你當下在哪——還沒連上就用 <code>ssh-copy-id</code>，已經連上就直接貼，少一次切換。</p>
<h2 id="還沒有-ssh-key-時怎麼把-dotfile-弄進去">還沒有 SSH key 時，怎麼把 dotfile 弄進去</h2>
<p>設 SSH key 是讓「之後」連線變方便，但 bootstrap 的第一步——把 dotfile repo 弄進機器——並不一定需要 key。常見的卡點是把「clone repo」跟「有 SSH key」綁在一起，但 clone 有不需要 key 的路徑。怎麼把 dotfile 弄進去，取決於這份 dotfile 放在哪。</p>
<p><strong>repo 是公開的（在 GitHub 之類）</strong>：用 HTTPS clone，公開 repo 的唯讀 clone 不需要任何認證。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">git clone https://github.com/&lt;帳號&gt;/dotfiles ~/dotfiles
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">cd</span> ~/dotfiles <span class="o">&amp;&amp;</span> ./scripts/install.sh</span></span></code></pre></div><p>這是最直接的路——機器只要能上網就能拉到 dotfile，完全繞過 key 的問題。clone URL 裡的帳號要對；用錯帳號（例如把 email handle 當成 GitHub 帳號）會 clone 失敗或抓到別的 repo，這類筆誤在只看 README 範例時很容易漏掉。SSH key 在這個情境只有「之後要從機器 push 回去」才需要，純粹跑部署用不到。</p>
<p><strong>repo 是私有的、但機器能上網</strong>：機器可以直接 clone，用 GitHub Personal Access Token（PAT）走 HTTPS——這是私有 repo 免 SSH key 的標準解。clone 時把 PAT 當密碼填進認證，機器就拉得到，一樣不必在它上面設 SSH key。</p>
<p><strong>repo 還沒推到任何遠端、或機器離線</strong>：從本機把檔案傳進去。如果本機到機器的 SSH 已經能用（即使只是密碼登入），用 <code>tar</code> over SSH 一次傳進去（跟 <code>scp -r</code> 等價，差別只在 tar 能一次打包、又好控制要不要帶 <code>.git</code>）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">tar czf - --exclude <span class="s1">&#39;.git&#39;</span> . <span class="p">|</span> ssh user@host <span class="s1">&#39;mkdir -p ~/dotfiles &amp;&amp; tar xzf - -C ~/dotfiles&#39;</span></span></span></code></pre></div><p>這條只需要兩邊都有的 <code>ssh</code> 跟 <code>tar</code>，不依賴目標機有 rsync。從 macOS 傳的時候要關掉 AppleDouble 中繼檔，否則會夾帶一堆 <code>._</code> 開頭的中繼檔到 Linux 上：在指令前加 <code>COPYFILE_DISABLE=1</code>。完全離線、連 SSH 都還沒通時，最後手段是把 repo 放進 USB、掛載到機器上複製出來。</p>
<p>把 dotfile 弄進去之後，跑它的 <code>install.sh</code> 完成基礎安裝。如果安裝腳本一開始就要用 sudo，記得 sudo 必須在工具驗證階段就備好——它是 <a href="../minimal-install-verify/">最小安裝後的工具驗證與補足</a> 的前置，bootstrap 自身補不了。</p>
<h2 id="換一台新機器或重裝時ssh-為什麼突然連不上">換一台新機器（或重裝）時，SSH 為什麼突然連不上</h2>
<p>SSH 的別名、金鑰、<code>known_hosts</code> 都是綁在「某一台特定機器」上的，所以當你重裝、或換一台新 VM，先前設好的 <code>ssh &lt;別名&gt;</code> 往往會以看似無關的錯誤失敗——那套設定是為舊機器建的，而重裝後是另一台機器：不同的 IP、不同的 SSH host key、還沒裝 sshd、<code>authorized_keys</code> 也是空的。判讀的起點是把重裝後的機器當成全新的一台，重做第一次連線的設定，而不是沿用舊別名。</p>
<p>失敗會以三種形式出現，各對應不同層、各有各的修法：</p>
<p><code>Permission denied (publickey)</code> 是認證被拒，代表 sshd 有在跑、連線有到（這是進度），卡在金鑰這關。常見於你用的別名設了 <code>IdentitiesOnly yes</code> 只送某一把 key，而新機器的 <code>authorized_keys</code> 還沒有它。修法是改用帳號加 IP 直連、走密碼，繞過那個鎖死金鑰的別名：<code>ssh user@&lt;新 IP&gt;</code>，密碼是「這次安裝」為該使用者設的（每次重裝各自獨立，不是舊機器那個）。連進去後再把公鑰貼回新機器的 <code>authorized_keys</code>、把別名的 <code>HostName</code> 更新成新 IP，免密碼才會恢復。</p>
<p><code>Host key verification failed</code>（或 <code>REMOTE HOST IDENTIFICATION HAS CHANGED</code>）發生在新機器剛好拿到跟舊機器一樣的 IP 時：你本機 <code>known_hosts</code> 存的是舊機器的 host key，SSH 偵測到同一個 IP 換了 key、當成可能的中間人攻擊而拒連。修法是刪掉那筆舊紀錄，再重連時接受新 key：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">ssh-keygen -R &lt;IP&gt;       <span class="c1"># 刪掉該 IP 的舊 host key</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">ssh-keygen -R &lt;別名&gt;     <span class="c1"># 有用別名的話一併刪</span></span></span></code></pre></div><p><code>Connection refused</code> 代表沒有 sshd 在監聽，也就是新機器還沒把 SSH server 起來。修法回到最開始——在新機器的 console 裝 openssh、啟動服務（見本篇開頭「啟用 sshd」），這一步在每台全新機器上都要重做。</p>
<p>三個症狀的共同根因是同一件事：SSH 的便利設定（別名、金鑰、host key 快取）綁的是機器身分、不會跟著「重裝」自動轉移。把它們當成「為某一台機器設好的」，換機器就重做第一次連線，能省下對著看似無關的錯誤瞎猜的時間。</p>
<h2 id="連入後可能遇到的兩個終端機問題">連入後可能遇到的兩個終端機問題</h2>
<p>SSH 連線本身通了之後，互動 shell 還可能因為終端機環境不對而出現「打字變亂碼、prompt 重繪錯位」。這類問題在你用現代終端機（如 Ghostty、Kitty）連進一台剛裝好的最小 Linux、又跑了 unicode 較重的 prompt（如 Powerlevel10k）時最容易出現，根源是兩個跟字元處理有關的終端機設定，跟你的 shell 配置無關。</p>
<p>第一個是 locale。macOS 的終端機 SSH 連線時常把 <code>LC_CTYPE=UTF-8</code> 送到遠端，但 <code>UTF-8</code> 不是合法的 Linux locale 名稱，Linux 收到後 fallback 成 <code>POSIX</code>/C locale——於是 shell 的行編輯器把輸入當單位元組處理，配上 unicode 字元的 prompt 就重繪成一個字母重複好幾次的累加亂碼。判讀方式是在遠端跑 <code>locale</code>，看 <code>LANG</code> 是不是空的、<code>LC_CTYPE</code> 是不是 <code>POSIX</code>。修法是在 shell 設定裡強制一個合法的 UTF-8 locale（前提是該 locale 已生成，見 <a href="../install-option-decisions/">安裝選項判讀</a> 的 locale 段）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">export</span> <span class="nv">LANG</span><span class="o">=</span>en_US.UTF-8
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">export</span> <span class="nv">LC_CTYPE</span><span class="o">=</span>en_US.UTF-8</span></span></code></pre></div><p>第二個是 terminfo。現代終端機會把 <code>TERM</code> 設成自己的值（Ghostty 是 <code>xterm-ghostty</code>、Kitty 是 <code>xterm-kitty</code>），而一台剛裝好的 Linux 的 terminfo 資料庫沒有這些條目，shell 的行編輯器做「清行重繪」時找不到對應的控制序列、就把畫面畫壞。判讀方式是在遠端 <code>echo $TERM</code> 看是哪個值、<code>toe | grep &lt;值&gt;</code> 看遠端認不認得。修法有兩條：把你終端機的 terminfo 灌進遠端（保留完整功能），或退而求其次強制一個遠端一定有的 <code>TERM</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 把本機終端機的 terminfo 灌進遠端的 ~/.terminfo（推薦）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">infocmp -x <span class="s2">&#34;</span><span class="nv">$TERM</span><span class="s2">&#34;</span> <span class="p">|</span> ssh remote <span class="s1">&#39;tic -x -&#39;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 或：連線時強制遠端一定有的 TERM（功能略降，但保證能用）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">ssh -t remote <span class="s1">&#39;TERM=xterm-256color exec zsh -l&#39;</span></span></span></code></pre></div><p>這兩個問題的共同點是：它們在你裝了 unicode 較重的互動 shell 之後才浮現，而陽春的 shell（ASCII prompt）即使 locale 跟 terminfo 都不對也照樣能用。所以排查時，先確認是不是這層、而不是去懷疑剛裝的 shell 配置壞了。</p>
<h2 id="連入傳輸安裝的順序">連入、傳輸、安裝的順序</h2>
<p>這三件事有一個固定的先後，順序錯了會在中間卡住。先把 sshd 跑起來、從本機連入，取得一個能貼上、可捲動的 session；再把 dotfile 弄進機器（公開 repo 走 HTTPS clone、私有或本地走傳輸）；最後在機器上跑 install.sh 完成安裝。SSH key 是讓「連入」從每次打密碼變成免密碼的優化，可以在任何時候補，不是這條鏈的必要環節、也不是 bootstrap 的前置。</p>
<p><a href="/blog/linux/dotfile/00-dotfile-mindset/setup-order-guide/" data-link-title="環境建置的操作順序" data-link-desc="第一次從零建立 Linux 或 macOS 開發環境、不確定先做什麼後做什麼時讀 — 依賴順序路線圖，每一步附對應模組連結">模組零的操作順序指引</a> 把「生成 SSH key、部署公鑰」列為標準流程的一環，那是預設你會建 key 的主路徑。這篇補的是它沒展開的另一面：當你手上還沒有 key、或這台機器的 dotfile 根本不需要 key 就能取得時，怎麼一樣把 bootstrap 跑完。</p>
<h2 id="下一步">下一步</h2>
<p>連入、傳輸、安裝都跑通之後，真正的考驗是當 install.sh 中途失敗時——而它遲早會撞到失敗——你能不能快速看出哪裡錯了。這取決於安裝腳本有沒有把可觀測性內建進去，<a href="../observable-bootstrap/">可除錯的 bootstrap</a> 談的就是怎麼內建。</p>
]]></content:encoded></item><item><title>模組五：Hyprland 配置</title><link>https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/</guid><description>&lt;p>Hyprland 是 Wayland 上的動態平鋪式 compositor，在 tiling WM 裡少見地重視視覺效果——流暢的視窗動畫、圓角、模糊、漸層邊框。這個模組教它的配置檔怎麼寫、核心概念是什麼、以及常見的設定場景。&lt;/p>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/04-window-management/" data-link-title="模組四：視窗管理與平鋪式工作流" data-link-desc="同時開多個視窗時的排列策略 — 手動貼齊跟自動平鋪的差距在哪、macOS 和 Linux 各有哪些工具、多螢幕怎麼處理、什麼情境值得從浮動切換到平鋪">視窗管理與平鋪式工作流&lt;/a>講了平鋪概念和選型，這裡直接進入 Hyprland 的配置實務。之後的 VM 實測專案會用這些配置做實際安裝和驗證。&lt;/p>
&lt;p>macOS 讀者如果不打算在 Linux 或 VM 上跑 Hyprland，可以跳到&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/08-sync-bootstrap/" data-link-title="模組八：同步、Bootstrap 與環境重建" data-link-desc="換機器或重灌時怎麼還原工作環境 — bootstrap script 設計、套件清單管理、跨機器同步策略、secret 排除，以及 VM 快照和 dotfile 重建兩種思路的場景判讀">同步、Bootstrap 與環境重建&lt;/a>——bootstrap script 和 secret 管理的概念對 macOS 同樣適用。&lt;/p>
&lt;h2 id="章節文章">章節文章&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>文章&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/hyprland-installation/" data-link-title="Hyprland 安裝與環境建置" data-link-desc="要在 Arch Linux 上從零安裝 Hyprland 桌面環境時回來讀">安裝與環境建置&lt;/a>&lt;/td>
 &lt;td>Arch Linux 套件、GPU 驅動（AMD/Intel/NVIDIA 含完整設定）、配套工具、登入管理器、首次啟動除錯&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/hyprland-core-config/" data-link-title="Hyprland 核心配置" data-link-desc="Hyprland 的配置檔該怎麼組織、monitor 怎麼設定、keybind 怎麼設計、輸入裝置和環境變數怎麼配時回來讀">Hyprland 核心配置&lt;/a>&lt;/td>
 &lt;td>配置檔位置與模組化拆分、monitor 設定、keybind 設計（含完整配置範例）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/workspace-rules-appearance/" data-link-title="Workspace、Window Rules 與外觀" data-link-desc="Hyprland 的 workspace 綁定螢幕、window rules 設定浮動例外、外觀動畫調教、layout 選型、autostart 和 plugin 管理時回來讀">Workspace、Window Rules 與外觀&lt;/a>&lt;/td>
 &lt;td>workspace 設定、window rules、外觀設定、兩種平鋪分割演算法（dwindle / master）、autostart、plugin、穩定性維護&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/hyprland-vm-setup/" data-link-title="Hyprland VM 環境設定與測試矩陣" data-link-desc="要在 VM 裡測試 Hyprland 配置、或判斷某個設定該在 VM 還是實機驗證時回來讀">VM 環境設定與測試矩陣&lt;/a>&lt;/td>
 &lt;td>UTM/QEMU 設定、VM 環境變數、效能預期、VM 可測試 vs 需實機測試的完整矩陣&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="跨分類引用">跨分類引用&lt;/h2>
&lt;ul>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/04-window-management/" data-link-title="模組四：視窗管理與平鋪式工作流" data-link-desc="同時開多個視窗時的排列策略 — 手動貼齊跟自動平鋪的差距在哪、macOS 和 Linux 各有哪些工具、多螢幕怎麼處理、什麼情境值得從浮動切換到平鋪">模組四：視窗管理與自動平鋪&lt;/a>：平鋪式 WM 的概念和選型&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">模組六：桌面 Rice 設計&lt;/a>：compositor 之上的狀態列、啟動器、通知、配色系統&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/" data-link-title="模組七：桌面環境維護與故障排除" data-link-desc="桌面凍結、compositor 掛了、或某個工具不回應時回來讀 — Linux 桌面的故障隔離模型、常見故障場景的恢復操作、日誌判讀與診斷工具">模組七：桌面環境維護與故障排除&lt;/a>：Hyprland crash、GPU hang、config 寫錯時的診斷和恢復&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/08-sync-bootstrap/" data-link-title="模組八：同步、Bootstrap 與環境重建" data-link-desc="換機器或重灌時怎麼還原工作環境 — bootstrap script 設計、套件清單管理、跨機器同步策略、secret 排除，以及 VM 快照和 dotfile 重建兩種思路的場景判讀">模組八：同步、Bootstrap 與環境重建&lt;/a>：跨機器搬移時硬體相關設定怎麼處理&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/linux/tools/gui/" data-link-title="圖形桌面工具" data-link-desc="有圖形環境時要挑桌面應用（檔案管理員、桌面環境與擴充）、需要判斷不同實作的相依足跡與桌面環境耦合、以及在最小化 WM 上加一個 GUI app 的取捨時回來讀">Linux 工具選單：圖形桌面工具&lt;/a>：在 Hyprland 下加圖形檔案管理員（Thunar / Nemo / PCManFM-Qt）的相依取捨&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Hyprland 是 Wayland 上的動態平鋪式 compositor，在 tiling WM 裡少見地重視視覺效果——流暢的視窗動畫、圓角、模糊、漸層邊框。這個模組教它的配置檔怎麼寫、核心概念是什麼、以及常見的設定場景。</p>
<p><a href="/blog/linux/dotfile/04-window-management/" data-link-title="模組四：視窗管理與平鋪式工作流" data-link-desc="同時開多個視窗時的排列策略 — 手動貼齊跟自動平鋪的差距在哪、macOS 和 Linux 各有哪些工具、多螢幕怎麼處理、什麼情境值得從浮動切換到平鋪">視窗管理與平鋪式工作流</a>講了平鋪概念和選型，這裡直接進入 Hyprland 的配置實務。之後的 VM 實測專案會用這些配置做實際安裝和驗證。</p>
<p>macOS 讀者如果不打算在 Linux 或 VM 上跑 Hyprland，可以跳到<a href="/blog/linux/dotfile/08-sync-bootstrap/" data-link-title="模組八：同步、Bootstrap 與環境重建" data-link-desc="換機器或重灌時怎麼還原工作環境 — bootstrap script 設計、套件清單管理、跨機器同步策略、secret 排除，以及 VM 快照和 dotfile 重建兩種思路的場景判讀">同步、Bootstrap 與環境重建</a>——bootstrap script 和 secret 管理的概念對 macOS 同樣適用。</p>
<h2 id="章節文章">章節文章</h2>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>主題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/linux/dotfile/05-hyprland-config/hyprland-installation/" data-link-title="Hyprland 安裝與環境建置" data-link-desc="要在 Arch Linux 上從零安裝 Hyprland 桌面環境時回來讀">安裝與環境建置</a></td>
          <td>Arch Linux 套件、GPU 驅動（AMD/Intel/NVIDIA 含完整設定）、配套工具、登入管理器、首次啟動除錯</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/05-hyprland-config/hyprland-core-config/" data-link-title="Hyprland 核心配置" data-link-desc="Hyprland 的配置檔該怎麼組織、monitor 怎麼設定、keybind 怎麼設計、輸入裝置和環境變數怎麼配時回來讀">Hyprland 核心配置</a></td>
          <td>配置檔位置與模組化拆分、monitor 設定、keybind 設計（含完整配置範例）</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/05-hyprland-config/workspace-rules-appearance/" data-link-title="Workspace、Window Rules 與外觀" data-link-desc="Hyprland 的 workspace 綁定螢幕、window rules 設定浮動例外、外觀動畫調教、layout 選型、autostart 和 plugin 管理時回來讀">Workspace、Window Rules 與外觀</a></td>
          <td>workspace 設定、window rules、外觀設定、兩種平鋪分割演算法（dwindle / master）、autostart、plugin、穩定性維護</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/05-hyprland-config/hyprland-vm-setup/" data-link-title="Hyprland VM 環境設定與測試矩陣" data-link-desc="要在 VM 裡測試 Hyprland 配置、或判斷某個設定該在 VM 還是實機驗證時回來讀">VM 環境設定與測試矩陣</a></td>
          <td>UTM/QEMU 設定、VM 環境變數、效能預期、VM 可測試 vs 需實機測試的完整矩陣</td>
      </tr>
  </tbody>
</table>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/linux/dotfile/04-window-management/" data-link-title="模組四：視窗管理與平鋪式工作流" data-link-desc="同時開多個視窗時的排列策略 — 手動貼齊跟自動平鋪的差距在哪、macOS 和 Linux 各有哪些工具、多螢幕怎麼處理、什麼情境值得從浮動切換到平鋪">模組四：視窗管理與自動平鋪</a>：平鋪式 WM 的概念和選型</li>
<li>→ <a href="/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">模組六：桌面 Rice 設計</a>：compositor 之上的狀態列、啟動器、通知、配色系統</li>
<li>→ <a href="/blog/linux/dotfile/07-desktop-maintenance/" data-link-title="模組七：桌面環境維護與故障排除" data-link-desc="桌面凍結、compositor 掛了、或某個工具不回應時回來讀 — Linux 桌面的故障隔離模型、常見故障場景的恢復操作、日誌判讀與診斷工具">模組七：桌面環境維護與故障排除</a>：Hyprland crash、GPU hang、config 寫錯時的診斷和恢復</li>
<li>→ <a href="/blog/linux/dotfile/08-sync-bootstrap/" data-link-title="模組八：同步、Bootstrap 與環境重建" data-link-desc="換機器或重灌時怎麼還原工作環境 — bootstrap script 設計、套件清單管理、跨機器同步策略、secret 排除，以及 VM 快照和 dotfile 重建兩種思路的場景判讀">模組八：同步、Bootstrap 與環境重建</a>：跨機器搬移時硬體相關設定怎麼處理</li>
<li>→ <a href="/blog/linux/tools/gui/" data-link-title="圖形桌面工具" data-link-desc="有圖形環境時要挑桌面應用（檔案管理員、桌面環境與擴充）、需要判斷不同實作的相依足跡與桌面環境耦合、以及在最小化 WM 上加一個 GUI app 的取捨時回來讀">Linux 工具選單：圖形桌面工具</a>：在 Hyprland 下加圖形檔案管理員（Thunar / Nemo / PCManFM-Qt）的相依取捨</li>
</ul>
]]></content:encoded></item><item><title>模組五：Windows / Linux + 獨立 GPU</title><link>https://tarrragon.github.io/blog/llm/05-discrete-gpu/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/05-discrete-gpu/</guid><description>&lt;p>本模組的核心目標是把 &lt;a href="https://tarrragon.github.io/blog/llm/00-foundations/" data-link-title="模組零：基礎知識與心智模型" data-link-desc="建立本地 LLM 的心智模型、釐清 MLX / MTP / oMLX 等常被混淆的術語、Apple Silicon 記憶體現實">模組零&lt;/a> 的心智模型落地到「Windows / Linux + 獨立 GPU」這條硬體路線。跟 &lt;a href="https://tarrragon.github.io/blog/llm/01-local-llm-services/" data-link-title="模組一：本地 LLM 服務的安裝與應用" data-link-desc="Ollama、LM Studio、llama.cpp 的安裝與差異、VS Code &amp;#43; Continue.dev 整合、模型選型與期望管理">模組一&lt;/a>（Apple Silicon Mac）平行、共用模組零的詞彙跟 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/" data-link-title="Knowledge Cards" data-link-desc="用原子化卡片整理本地 LLM 寫 code 場景所需的概念詞彙">knowledge-cards&lt;/a>、但硬體判讀模型本質不同：Mac 是統一記憶體一塊預算、PC 是 VRAM + 系統 RAM 兩塊分層預算、要分開判讀。&lt;/p>
&lt;p>讀完本模組後、你應該能對自己這台 PC 直接回答：能跑哪些模型、要不要卸載 MoE 專家層到 RAM、KV cache 該量化到哪一級、context 能開多大、併發數能拉到多少。&lt;/p>
&lt;h2 id="為什麼-pc-路線值得獨立模組">為什麼 PC 路線值得獨立模組&lt;/h2>
&lt;p>Mac 統一記憶體的判讀模型把「能載入多大模型」這個問題收斂到一塊預算。PC 場景被獨立 VRAM 拆成兩個記憶體區域、判讀軸增加：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>VRAM&lt;/strong>：高頻寬區。常見消費級 NVIDIA 卡的廠商標稱頻寬大致落在數百 GB/s 到 1 TB/s 級的區間（例如 RTX 5060 Ti 16GB 標稱約 448 GB/s、RTX 5070 Ti 標稱約 896 GB/s、以廠商規格表為準）、生字速度上限主要受 VRAM 頻寬影響。&lt;/li>
&lt;li>&lt;strong>系統 RAM&lt;/strong>：高容量區。DDR5 6000 雙通道的標稱頻寬約 96 GB/s（依主機板與時序變化）、相對 VRAM 慢約一個量級、但 64GB / 128GB 在 PC 平台的擴充成本相對低、適合放容量需求大但存取頻率較低的權重。&lt;/li>
&lt;li>&lt;strong>PCIe&lt;/strong>：兩個區域之間的連線。PCIe 5.0 x16 廠商標稱單向約 64 GB/s（PCIe 4.0 x16 約一半）；實際傳輸吞吐受驅動、檔案系統與工作流影響。&lt;/li>
&lt;/ol>
&lt;p>這三層差異產生兩個 Mac 場景上較少出現的工程選項：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/moe-cpu-offload/" data-link-title="MoE CPU 卸載" data-link-desc="把 Mixture-of-Experts 模型不活躍的專家層權重放在系統 RAM、用到再走 PCIe 拉回 GPU、讓有限 VRAM 跑得了更大模型">MoE 模型 + 專家層 CPU 卸載&lt;/a>&lt;/strong>：MoE 模型每個 token 只啟用少數專家、把不活躍的專家權重放在系統 RAM、用到再走 PCIe 拉回 GPU。讓 16GB VRAM 卡能載入 30B / 70B 等級的 MoE 模型。&lt;/li>
&lt;li>&lt;strong>KV cache 量化開大 context&lt;/strong>：把 K cache 量化到 Q8、V cache 量化到 Q4、KV cache 體積大幅壓縮、騰出的 VRAM 可用於開大 context window 或提高併發數。&lt;/li>
&lt;/ol>
&lt;p>這兩個選項在 Mac 統一記憶體場景下較少使用（VRAM 跟 RAM 共用、不需在兩個區域之間搬資料）、在 PC 場景則是常用的調參工具。&lt;/p>
&lt;h2 id="章節列表">章節列表&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>章節&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;th>關鍵收穫&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/05-discrete-gpu/vram-ram-budget/" data-link-title="5.0 VRAM &amp;#43; RAM 分層預算" data-link-desc="PC 獨立 GPU 場景的記憶體預算判讀：VRAM 是快的世界、RAM 是大的世界、PCIe 把兩個世界連起來">5.0&lt;/a>&lt;/td>
 &lt;td>VRAM + RAM 分層預算&lt;/td>
 &lt;td>16GB VRAM × 64GB RAM 等情境的模型對照、跟 Mac 統一記憶體的對比&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/05-discrete-gpu/moe-cpu-offload-strategy/" data-link-title="5.1 MoE 模型與 CPU 卸載策略" data-link-desc="PC 場景把 MoE 不活躍專家層留在系統 RAM 的判讀：何時值得卸載、卸幾層、對 prefill 跟生成的影響各自不同">5.1&lt;/a>&lt;/td>
 &lt;td>MoE 模型與 CPU 卸載策略&lt;/td>
 &lt;td>何時把專家層卸到 RAM、卸幾層、prefill / generation 影響各自不同&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/05-discrete-gpu/kv-cache-quantization-strategy/" data-link-title="5.2 KV cache 量化策略" data-link-desc="PC 場景用 K=Q8 / V=Q4 等量化把 KV cache 壓縮、騰出 VRAM 開大 context window 或加併發數的判讀">5.2&lt;/a>&lt;/td>
 &lt;td>KV cache 量化策略&lt;/td>
 &lt;td>K=Q8 / V=Q4 跟 context window / 併發數的權衡、flash attention 的關係&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/05-discrete-gpu/llama-cpp-on-pc/" data-link-title="5.3 llama.cpp 在 PC 上" data-link-desc="CUDA / ROCm build 取得、核心旗標地圖、llama-bench 校準、多卡 tensor split 的入門設定">5.3&lt;/a>&lt;/td>
 &lt;td>llama.cpp 在 PC 上&lt;/td>
 &lt;td>CUDA / ROCm build、核心旗標地圖、&lt;code>llama-bench&lt;/code> 校準工作流&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/05-discrete-gpu/lm-studio-on-windows/" data-link-title="5.4 LM Studio 在 Windows" data-link-desc="Windows &amp;#43; 獨立 GPU 場景用 LM Studio：CUDA / ROCm backend 選擇、GUI 內對應 -ngl / cache-type / cpu-moe 的設定位置">5.4&lt;/a>&lt;/td>
 &lt;td>LM Studio 在 Windows&lt;/td>
 &lt;td>Windows 安裝、CUDA backend 選擇、GUI 欄位對應到 llama.cpp 旗標&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/05-discrete-gpu/model-selection-priority-pc/" data-link-title="5.5 PC 場景的模型選型優先順序" data-link-desc="PC 獨立 GPU 場景下、MoE 卸載讓「全載小模型 vs 卸載大 MoE」變成主要的選型軸；對應不同 VRAM 容量的模型推薦">5.5&lt;/a>&lt;/td>
 &lt;td>PC 場景的模型選型優先順序&lt;/td>
 &lt;td>全載 14B Dense vs 卸載 30B MoE 等的選型決策&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/05-discrete-gpu/gpu-vendor-differences/" data-link-title="5.6 GPU 廠商差異" data-link-desc="NVIDIA CUDA、AMD ROCm、Intel ARC 在 llama.cpp 生態的相對位置、選卡時的判讀軸">5.6&lt;/a>&lt;/td>
 &lt;td>GPU 廠商差異&lt;/td>
 &lt;td>NVIDIA / AMD / Intel 的工具鏈支援度、選卡判讀框架&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="跟模組一的對應關係">跟模組一的對應關係&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>模組一（Mac）&lt;/th>
 &lt;th>模組五（PC）&lt;/th>
 &lt;th>關係&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>0.5 Apple Silicon 記憶體預算&lt;/td>
 &lt;td>5.0 VRAM + RAM 分層預算&lt;/td>
 &lt;td>平行、不同硬體模型；都在 &lt;a href="https://tarrragon.github.io/blog/llm/00-foundations/" data-link-title="模組零：基礎知識與心智模型" data-link-desc="建立本地 LLM 的心智模型、釐清 MLX / MTP / oMLX 等常被混淆的術語、Apple Silicon 記憶體現實">模組零&lt;/a> 之下&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1.0 Ollama&lt;/td>
 &lt;td>（Ollama Windows 同樣可用、不獨立成章）&lt;/td>
 &lt;td>跨平台、不重複&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1.1 LM Studio&lt;/td>
 &lt;td>5.4 LM Studio 在 Windows&lt;/td>
 &lt;td>Windows 多了 CUDA backend 選擇與 driver 議題&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1.2 llama.cpp&lt;/td>
 &lt;td>5.3 llama.cpp 在 PC 上&lt;/td>
 &lt;td>PC 多了 CUDA build、tensor split、&lt;code>--n-cpu-moe&lt;/code> 等參數&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1.3 VS Code + Continue.dev&lt;/td>
 &lt;td>（共用、不獨立成章）&lt;/td>
 &lt;td>介面層跨平台、設定檔幾乎相同&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1.4 模型選型優先順序&lt;/td>
 &lt;td>5.5 PC 場景的模型選型優先順序&lt;/td>
 &lt;td>選型邏輯類似、但 PC 多了 MoE 卸載這個變數&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1.5 期望管理&lt;/td>
 &lt;td>（共用、不獨立成章）&lt;/td>
 &lt;td>本地 vs 雲端分工跟硬體無關&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="最短路徑16gb-vram--64gb-ram-跑-qwen3-30b-moe">最短路徑：16GB VRAM + 64GB RAM 跑 Qwen3 30B MoE&lt;/h2>
&lt;blockquote>
&lt;p>&lt;strong>事實查核註&lt;/strong>：本模組引用的硬體規格、模型體積、社群實測數量級、廠商工具鏈成熟度、皆以 2026 年 5 月的公開資訊與社群常見回報為基準。GPU 規格、driver 版本、llama.cpp release、模型釋出與量化版本快速演進、引用前請以 &lt;a href="https://github.com/ggml-org/llama.cpp/releases">llama.cpp release notes&lt;/a>、各廠商官方規格表、各模型 Hugging Face model card 為準、並用 &lt;code>llama-bench&lt;/code> 或實際工作流校準。&lt;/p></description><content:encoded><![CDATA[<p>本模組的核心目標是把 <a href="/blog/llm/00-foundations/" data-link-title="模組零：基礎知識與心智模型" data-link-desc="建立本地 LLM 的心智模型、釐清 MLX / MTP / oMLX 等常被混淆的術語、Apple Silicon 記憶體現實">模組零</a> 的心智模型落地到「Windows / Linux + 獨立 GPU」這條硬體路線。跟 <a href="/blog/llm/01-local-llm-services/" data-link-title="模組一：本地 LLM 服務的安裝與應用" data-link-desc="Ollama、LM Studio、llama.cpp 的安裝與差異、VS Code &#43; Continue.dev 整合、模型選型與期望管理">模組一</a>（Apple Silicon Mac）平行、共用模組零的詞彙跟 <a href="/blog/llm/knowledge-cards/" data-link-title="Knowledge Cards" data-link-desc="用原子化卡片整理本地 LLM 寫 code 場景所需的概念詞彙">knowledge-cards</a>、但硬體判讀模型本質不同：Mac 是統一記憶體一塊預算、PC 是 VRAM + 系統 RAM 兩塊分層預算、要分開判讀。</p>
<p>讀完本模組後、你應該能對自己這台 PC 直接回答：能跑哪些模型、要不要卸載 MoE 專家層到 RAM、KV cache 該量化到哪一級、context 能開多大、併發數能拉到多少。</p>
<h2 id="為什麼-pc-路線值得獨立模組">為什麼 PC 路線值得獨立模組</h2>
<p>Mac 統一記憶體的判讀模型把「能載入多大模型」這個問題收斂到一塊預算。PC 場景被獨立 VRAM 拆成兩個記憶體區域、判讀軸增加：</p>
<ol>
<li><strong>VRAM</strong>：高頻寬區。常見消費級 NVIDIA 卡的廠商標稱頻寬大致落在數百 GB/s 到 1 TB/s 級的區間（例如 RTX 5060 Ti 16GB 標稱約 448 GB/s、RTX 5070 Ti 標稱約 896 GB/s、以廠商規格表為準）、生字速度上限主要受 VRAM 頻寬影響。</li>
<li><strong>系統 RAM</strong>：高容量區。DDR5 6000 雙通道的標稱頻寬約 96 GB/s（依主機板與時序變化）、相對 VRAM 慢約一個量級、但 64GB / 128GB 在 PC 平台的擴充成本相對低、適合放容量需求大但存取頻率較低的權重。</li>
<li><strong>PCIe</strong>：兩個區域之間的連線。PCIe 5.0 x16 廠商標稱單向約 64 GB/s（PCIe 4.0 x16 約一半）；實際傳輸吞吐受驅動、檔案系統與工作流影響。</li>
</ol>
<p>這三層差異產生兩個 Mac 場景上較少出現的工程選項：</p>
<ol>
<li><strong><a href="/blog/llm/knowledge-cards/moe-cpu-offload/" data-link-title="MoE CPU 卸載" data-link-desc="把 Mixture-of-Experts 模型不活躍的專家層權重放在系統 RAM、用到再走 PCIe 拉回 GPU、讓有限 VRAM 跑得了更大模型">MoE 模型 + 專家層 CPU 卸載</a></strong>：MoE 模型每個 token 只啟用少數專家、把不活躍的專家權重放在系統 RAM、用到再走 PCIe 拉回 GPU。讓 16GB VRAM 卡能載入 30B / 70B 等級的 MoE 模型。</li>
<li><strong>KV cache 量化開大 context</strong>：把 K cache 量化到 Q8、V cache 量化到 Q4、KV cache 體積大幅壓縮、騰出的 VRAM 可用於開大 context window 或提高併發數。</li>
</ol>
<p>這兩個選項在 Mac 統一記憶體場景下較少使用（VRAM 跟 RAM 共用、不需在兩個區域之間搬資料）、在 PC 場景則是常用的調參工具。</p>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>關鍵收穫</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/llm/05-discrete-gpu/vram-ram-budget/" data-link-title="5.0 VRAM &#43; RAM 分層預算" data-link-desc="PC 獨立 GPU 場景的記憶體預算判讀：VRAM 是快的世界、RAM 是大的世界、PCIe 把兩個世界連起來">5.0</a></td>
          <td>VRAM + RAM 分層預算</td>
          <td>16GB VRAM × 64GB RAM 等情境的模型對照、跟 Mac 統一記憶體的對比</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/05-discrete-gpu/moe-cpu-offload-strategy/" data-link-title="5.1 MoE 模型與 CPU 卸載策略" data-link-desc="PC 場景把 MoE 不活躍專家層留在系統 RAM 的判讀：何時值得卸載、卸幾層、對 prefill 跟生成的影響各自不同">5.1</a></td>
          <td>MoE 模型與 CPU 卸載策略</td>
          <td>何時把專家層卸到 RAM、卸幾層、prefill / generation 影響各自不同</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/05-discrete-gpu/kv-cache-quantization-strategy/" data-link-title="5.2 KV cache 量化策略" data-link-desc="PC 場景用 K=Q8 / V=Q4 等量化把 KV cache 壓縮、騰出 VRAM 開大 context window 或加併發數的判讀">5.2</a></td>
          <td>KV cache 量化策略</td>
          <td>K=Q8 / V=Q4 跟 context window / 併發數的權衡、flash attention 的關係</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/05-discrete-gpu/llama-cpp-on-pc/" data-link-title="5.3 llama.cpp 在 PC 上" data-link-desc="CUDA / ROCm build 取得、核心旗標地圖、llama-bench 校準、多卡 tensor split 的入門設定">5.3</a></td>
          <td>llama.cpp 在 PC 上</td>
          <td>CUDA / ROCm build、核心旗標地圖、<code>llama-bench</code> 校準工作流</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/05-discrete-gpu/lm-studio-on-windows/" data-link-title="5.4 LM Studio 在 Windows" data-link-desc="Windows &#43; 獨立 GPU 場景用 LM Studio：CUDA / ROCm backend 選擇、GUI 內對應 -ngl / cache-type / cpu-moe 的設定位置">5.4</a></td>
          <td>LM Studio 在 Windows</td>
          <td>Windows 安裝、CUDA backend 選擇、GUI 欄位對應到 llama.cpp 旗標</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/05-discrete-gpu/model-selection-priority-pc/" data-link-title="5.5 PC 場景的模型選型優先順序" data-link-desc="PC 獨立 GPU 場景下、MoE 卸載讓「全載小模型 vs 卸載大 MoE」變成主要的選型軸；對應不同 VRAM 容量的模型推薦">5.5</a></td>
          <td>PC 場景的模型選型優先順序</td>
          <td>全載 14B Dense vs 卸載 30B MoE 等的選型決策</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/05-discrete-gpu/gpu-vendor-differences/" data-link-title="5.6 GPU 廠商差異" data-link-desc="NVIDIA CUDA、AMD ROCm、Intel ARC 在 llama.cpp 生態的相對位置、選卡時的判讀軸">5.6</a></td>
          <td>GPU 廠商差異</td>
          <td>NVIDIA / AMD / Intel 的工具鏈支援度、選卡判讀框架</td>
      </tr>
  </tbody>
</table>
<h2 id="跟模組一的對應關係">跟模組一的對應關係</h2>
<table>
  <thead>
      <tr>
          <th>模組一（Mac）</th>
          <th>模組五（PC）</th>
          <th>關係</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0.5 Apple Silicon 記憶體預算</td>
          <td>5.0 VRAM + RAM 分層預算</td>
          <td>平行、不同硬體模型；都在 <a href="/blog/llm/00-foundations/" data-link-title="模組零：基礎知識與心智模型" data-link-desc="建立本地 LLM 的心智模型、釐清 MLX / MTP / oMLX 等常被混淆的術語、Apple Silicon 記憶體現實">模組零</a> 之下</td>
      </tr>
      <tr>
          <td>1.0 Ollama</td>
          <td>（Ollama Windows 同樣可用、不獨立成章）</td>
          <td>跨平台、不重複</td>
      </tr>
      <tr>
          <td>1.1 LM Studio</td>
          <td>5.4 LM Studio 在 Windows</td>
          <td>Windows 多了 CUDA backend 選擇與 driver 議題</td>
      </tr>
      <tr>
          <td>1.2 llama.cpp</td>
          <td>5.3 llama.cpp 在 PC 上</td>
          <td>PC 多了 CUDA build、tensor split、<code>--n-cpu-moe</code> 等參數</td>
      </tr>
      <tr>
          <td>1.3 VS Code + Continue.dev</td>
          <td>（共用、不獨立成章）</td>
          <td>介面層跨平台、設定檔幾乎相同</td>
      </tr>
      <tr>
          <td>1.4 模型選型優先順序</td>
          <td>5.5 PC 場景的模型選型優先順序</td>
          <td>選型邏輯類似、但 PC 多了 MoE 卸載這個變數</td>
      </tr>
      <tr>
          <td>1.5 期望管理</td>
          <td>（共用、不獨立成章）</td>
          <td>本地 vs 雲端分工跟硬體無關</td>
      </tr>
  </tbody>
</table>
<h2 id="最短路徑16gb-vram--64gb-ram-跑-qwen3-30b-moe">最短路徑：16GB VRAM + 64GB RAM 跑 Qwen3 30B MoE</h2>
<blockquote>
<p><strong>事實查核註</strong>：本模組引用的硬體規格、模型體積、社群實測數量級、廠商工具鏈成熟度、皆以 2026 年 5 月的公開資訊與社群常見回報為基準。GPU 規格、driver 版本、llama.cpp release、模型釋出與量化版本快速演進、引用前請以 <a href="https://github.com/ggml-org/llama.cpp/releases">llama.cpp release notes</a>、各廠商官方規格表、各模型 Hugging Face model card 為準、並用 <code>llama-bench</code> 或實際工作流校準。</p></blockquote>
<p>如果你有類似 RTX 5060 Ti 16GB / 5070 Ti 16GB + 64GB DDR5 的配置、想用一小時搞定 PC 本地 LLM 寫 code、下面是最短路徑：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 1. 裝 llama.cpp 的 CUDA build（Windows / Linux 各有預編好的 release）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># 從 ggml-org/llama.cpp GitHub release 抓 CUDA 12.x 版</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 2. 抓一個 MoE 模型（如 Qwen3-30B-A3B 的 GGUF Q4_K_M 版本）</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 從 Hugging Face 下載到 ~/models/</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 3. 啟動 server、把 30 層 MoE 專家層卸載到 CPU</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">./llama-server <span class="se">\
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="se"></span>  -m ~/models/Qwen3-30B-A3B-Q4_K_M.gguf <span class="se">\
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="se"></span>  -ngl <span class="m">99</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="se"></span>  --n-cpu-moe <span class="m">30</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="se"></span>  --cache-type-k q8_0 <span class="se">\
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="se"></span>  --cache-type-v q4_0 <span class="se">\
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="se"></span>  -c <span class="m">32768</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="se"></span>  --port <span class="m">8080</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># 4. 在 VS Code 裝 Continue 擴充套件、config 指向 http://localhost:8080</span></span></span></code></pre></div><p>關鍵參數的意義先濃縮成一句、詳細推導留給 <a href="/blog/llm/05-discrete-gpu/llama-cpp-on-pc/" data-link-title="5.3 llama.cpp 在 PC 上" data-link-desc="CUDA / ROCm build 取得、核心旗標地圖、llama-bench 校準、多卡 tensor split 的入門設定">5.3 llama.cpp 在 PC 上</a>：</p>
<ul>
<li><code>-ngl 99</code>：把所有可放的層丟到 GPU。</li>
<li><code>--n-cpu-moe 30</code>：把 30 層的 MoE 專家權重留在系統 RAM、不上 VRAM。實際層數視模型結構與 VRAM 餘量微調。</li>
<li><code>--cache-type-k q8_0</code> / <code>--cache-type-v q4_0</code>：KV cache 量化、騰出 VRAM 開大 context。</li>
<li><code>-c 32768</code>：context window。配上 KV cache 量化、單卡 16GB 通常能開到 128K ~ 256K（看模型）。</li>
</ul>
<h2 id="為什麼這個順序">為什麼這個順序</h2>
<p>本模組章節順序的設計脈絡：</p>
<ol>
<li><strong>先 5.0 VRAM + RAM 分層預算</strong>：建立 PC 硬體判讀模型、是後面所有章節的前提。</li>
<li><strong>再 5.1 MoE 卸載</strong>：MoE + CPU 卸載是 PC 場景相對 Mac 的核心優勢、先把這個工程選項說清楚。</li>
<li><strong>接 5.2 KV cache 量化</strong>：跟 5.1 一起決定 VRAM 怎麼切、是 PC 場景的第二個獨有選項。</li>
<li><strong>再 5.3 llama.cpp 在 PC 上</strong>：把前三章的理論落地到實際參數。</li>
<li><strong>再 5.4 LM Studio 在 Windows</strong>：給「不想直接面對 CLI」的讀者另一條路、補上 GUI 內對應 5.1 / 5.2 設定的位置。</li>
<li><strong>然後 5.5 模型選型</strong>：所有工程選項都建立後、回答「具體裝哪個模型」。</li>
<li><strong>最後 5.6 GPU 廠商差異</strong>：選好模型跟參數後、再處理 NVIDIA / AMD / Intel 的工具鏈差異。</li>
</ol>
<h2 id="不在本模組內的主題">不在本模組內的主題</h2>
<p>本模組不討論：</p>
<ol>
<li><strong>多卡 NVLink、tensor parallelism</strong>：消費級 PC 場景通常單卡、多卡分散式推論屬於資料中心級教材。</li>
<li><strong>資料中心級 GPU（H100 / H200 / B200）部署</strong>：本模組聚焦消費級 PC、不涵蓋 vLLM / TGI / Triton 等資料中心 inference server。</li>
<li><strong>Linux 系統管理 / CUDA 驅動安裝細節</strong>：假設讀者已會基本系統管理；具體驅動安裝步驟交給 NVIDIA / AMD 官方文件。</li>
<li><strong>訓練 / fine-tuning</strong>：跟「跑現成模型」是不同工程問題、見 <a href="/blog/llm/03-theoretical-foundations/" data-link-title="模組三：LLM 的理論基礎" data-link-desc="從神經網路、embedding、attention、Transformer 架構、訓練到 sampling：LLM 內部運作的完整理論圖像">模組三</a> 與其推薦課程。</li>
<li><strong>產圖模型</strong>：<a href="/blog/llm/knowledge-cards/diffusion/" data-link-title="Diffusion" data-link-desc="產圖用的生成式 AI 架構：跟寫 code 用的 Transformer 是不同路線">Diffusion</a> 跟 Transformer 是不同架構、見 ComfyUI / Stable Diffusion 專門教材。</li>
</ol>
]]></content:encoded></item><item><title>可除錯的 bootstrap：把可觀測性內建進安裝腳本</title><link>https://tarrragon.github.io/blog/linux/install/observable-bootstrap/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/install/observable-bootstrap/</guid><description>&lt;p>Bootstrap 腳本失敗是常態，所以它的設計目標之一應該是「失敗時可診斷」：把失敗當成會發生的事來設計，預先留好定位問題的痕跡。一支自動化安裝腳本要跨越的環境差異很多——機器缺某個工具、套件清單有筆誤、某個指令在這個發行版的行為跟預期不同——任何一處都可能讓它中斷。決定你是「三分鐘看出哪裡錯」還是「對著終端機捲半天瞎猜」的，是這支腳本有沒有在設計時就把可觀測性內建進去，跟運氣無關。&lt;/p>
&lt;p>可觀測性要事先設計，是因為失敗發生的當下，你能拿到的資訊就已經定型了。如果腳本只把輸出丟到終端機、失敗時只留下一句通用的錯誤，那當下你就只有那句話可看；如果它一路把帶時間戳的紀錄寫進檔案、失敗時主動印出出錯的位置，那同一個失敗就變得可定位。差別不在失敗本身，在失敗前你準備了什麼。如果你寫的是自己的 bootstrap（例如部署 dotfile 的那支 &lt;code>install.sh&lt;/code>），這層要在你第一次跑它之前就設計進去，而不是等它出事才回頭加；就算腳本不是你寫的、你只是來 debug 一次失敗，下一段「找程式自己的 log」一樣適用。&lt;/p>
&lt;h2 id="為什麼會瞎找">為什麼會瞎找&lt;/h2>
&lt;p>不可觀測的腳本失敗時，你手上只有終端機捲動過的那些輸出，而那往往不足以定位真正的原因。終端機的輸出是易逝的、會被後續輸出沖掉、多個來源的訊息交錯在一起；更麻煩的是，很多失敗的「表面錯誤」離「真正原因」隔了好幾層。一個指令因為前面某個變數是空的而失敗，但它報出來的錯可能完全沒提到那個空變數——你看著一個誤導性的症狀，往上游找不到源頭。&lt;/p>
&lt;p>破解這種瞎找的，常常是一份你一開始沒看的 log。很多程式在終端機只印一段摘要，卻同時把詳細的執行紀錄寫進一個 log 檔；當終端機的訊息不足以定位時，那份程式自己寫的 log 裡往往就有答案。除錯時養成「找程式自己的 log，而不是只盯著終端機捲動」的習慣，是把瞎找變成定位的關鍵一步——這也是 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/log-reading-diagnostic-tools/" data-link-title="日誌判讀與診斷工具" data-link-desc="知道桌面出了問題但不確定原因時回來讀 — journalctl、dmesg、hyprctl、systemctl 的使用方式和常見 log pattern">模組七日誌判讀&lt;/a> 的核心。而對你自己寫的 bootstrap，你可以更進一步：在設計時就讓它產生這樣一份 log。&lt;/p>
&lt;h2 id="三個內建可觀測性的手法">三個內建可觀測性的手法&lt;/h2>
&lt;p>讓一支 bootstrap 腳本可診斷，有三個低成本、效果明顯的手法，它們合起來把「失敗了」變成「失敗在第幾行、哪個指令、什麼狀態」。&lt;/p>
&lt;h3 id="log-落地把全部輸出-tee-進帶時間戳的檔案">log 落地：把全部輸出 tee 進帶時間戳的檔案&lt;/h3>
&lt;p>第一個手法是讓腳本的全部輸出同時進終端機跟一個 log 檔，而不是只進終端機。終端機的捲動是易逝的，log 檔是持久的——可以事後 &lt;code>grep&lt;/code>、可以貼給別人看、可以比對前後兩次跑的差異。在 bash 裡，一行 &lt;code>exec&lt;/code> 就能把後續所有 stdout 與 stderr 都導去 &lt;code>tee&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nv">LOG_DIR&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">XDG_STATE_HOME&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="nv">$HOME&lt;/span>&lt;span class="p">/.local/state&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/dotfiles&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">mkdir -p &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$LOG_DIR&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="nv">LOG_FILE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$LOG_DIR&lt;/span>&lt;span class="s2">/install-&lt;/span>&lt;span class="k">$(&lt;/span>date +%Y%m%d-%H%M%S&lt;span class="k">)&lt;/span>&lt;span class="s2">.log&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="nb">exec&lt;/span> &amp;gt; &amp;gt;&lt;span class="o">(&lt;/span>tee -a &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$LOG_FILE&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="o">)&lt;/span> 2&amp;gt;&lt;span class="p">&amp;amp;&lt;/span>&lt;span class="m">1&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>帶時間戳的檔名讓每次跑各留一份、不互相覆蓋，事後可以回溯「上一次成功跟這次失敗差在哪」。log 檔放在 &lt;code>XDG_STATE_HOME&lt;/code>（狀態資料的標準位置）底下，符合慣例、也不污染家目錄。&lt;/p>
&lt;h3 id="錯誤定位用-err-trap-印出出錯的行與指令">錯誤定位：用 ERR trap 印出出錯的行與指令&lt;/h3>
&lt;p>第二個手法是讓腳本在中斷的瞬間，主動報出「是哪一行、哪個指令、什麼結束碼」失敗的。配合 &lt;code>set -e&lt;/code>（出錯即停）的腳本，預設只會默默地停，不告訴你停在哪。加一個 &lt;code>ERR&lt;/code> trap，就能在 &lt;code>set -e&lt;/code> 中斷之前先印出定位資訊：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nb">set&lt;/span> -Eeuo pipefail &lt;span class="c1"># -E 讓 ERR trap 在函式/子 shell 也生效&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="nb">trap&lt;/span> &lt;span class="s1">&amp;#39;log &amp;#34;ERROR line $LINENO: [$BASH_COMMAND] exit=$?&amp;#34;&amp;#39;&lt;/span> ERR&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>$LINENO&lt;/code> 是出錯的行號、&lt;code>$BASH_COMMAND&lt;/code> 是當下正在執行的那條指令、&lt;code>$?&lt;/code> 是它的結束碼。三者合起來，輸出會長這樣：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">[00:06:51] ERROR line 40: [sudo pacman -S --needed stow git zsh] exit=1&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>範例裡的 &lt;code>pacman&lt;/code> 換發行版會不同，這裡只是示意 trap 輸出的格式——手法本身（行號 + 指令 + 結束碼）跟發行版無關。這一行直接點名元兇。前面提過的那類「表面錯誤離真正原因隔好幾層」的情況——例如某個指令因為 &lt;code>which&lt;/code> 不存在而拿到空字串、最後報一個看似無關的錯——有了這行，你會直接看到是哪一行的哪條指令掛了，不必從誤導性的症狀往回猜。&lt;code>set -E&lt;/code>（&lt;code>-E&lt;/code> 旗標）是為了讓 trap 在函式跟子 shell 裡也照樣觸發，少了它，包在函式裡的錯誤會漏掉。&lt;/p>
&lt;h3 id="步驟標記用帶時間戳的-log-函式標出進度">步驟標記：用帶時間戳的 log 函式標出進度&lt;/h3>
&lt;p>第三個手法是在關鍵步驟前印一行帶時間戳的標記，讓你能看出腳本跑到哪、哪一步慢。一個極簡的 log 函式就夠：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">log&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span> &lt;span class="nb">printf&lt;/span> &lt;span class="s1">&amp;#39;[%s] %s\n&amp;#39;&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>date +%H:%M:%S&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$*&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">log &lt;span class="s2">&amp;#34;install.sh start | OS=&lt;/span>&lt;span class="nv">$OS&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">log &lt;span class="s2">&amp;#34;Installing base packages...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">log &lt;span class="s2">&amp;#34;Stowing configs...&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>時間戳的價值在於它同時給你「進度」跟「效能」兩種資訊：失敗時，最後一行成功的 log 告訴你它跨過了哪些步驟、卡在哪一步之後；正常時，相鄰兩行的時間差告訴你哪一步耗時最久。這比沒有標記、只能從一堆套件下載輸出裡猜「現在到底在幹嘛」清楚得多。&lt;/p>
&lt;h2 id="失敗可診斷是設計目標">失敗可診斷是設計目標&lt;/h2>
&lt;p>把這三個手法合起來，一支原本「失敗時只留一句通用錯誤」的腳本，會變成「每次跑都留一份完整 log、失敗時直接點名第幾行哪個指令、過程中每步都有時間戳」。成本是腳本開頭多幾行，回報是把未來每一次除錯從瞎找變成定位。這層可觀測性是 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/08-sync-bootstrap/bootstrap-script-packages/" data-link-title="Bootstrap Script 與套件清單管理" data-link-desc="寫 dotfile 的 install script、或整理「這台機器裝了什麼」的套件清單時回來讀">模組八 bootstrap script 設計&lt;/a> 的延伸——那篇給安裝腳本的骨架與套件清單，這篇給它加上失敗時的診斷能力，兩篇處理的是同一支腳本的兩個層面。&lt;/p>
&lt;p>這是設計階段的決定，不是事後能補的。當一支沒有可觀測性的腳本在一台陌生機器上失敗，你沒辦法回到過去讓它記錄當時的狀態——資訊在失敗的瞬間就已經流失了。所以「失敗可診斷」要跟功能一起設計進去，把它當成 bootstrap 的基本屬性，而不是出事之後才想加的補丁。&lt;/p>
&lt;h2 id="回到系列">回到系列&lt;/h2>
&lt;p>這幾篇合起來，是把一台機器從「空的」帶到「能接收 dotfile、且部署過程可診斷」的完整地基：&lt;a href="../install-option-decisions/">安裝選項判讀&lt;/a> 處理 OS 怎麼裝、&lt;a href="../minimal-install-verify/">工具驗證與補足&lt;/a> 處理裝完缺什麼、&lt;a href="../ssh-keyless-bootstrap/">外部連入與無 key bootstrap&lt;/a> 處理怎麼連進去把 dotfile 弄進來，這一篇處理當部署失敗時怎麼快速看出原因。再往前一步，把這套地基用在無人值守的長任務上、讓機器在你離開後自己跑完工作，見 &lt;a href="../unattended-remote-work/">讓機器跑無人值守的長任務&lt;/a>——無人盯著的任務尤其依賴這篇談的可觀測性。地基打好，後面 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/" data-link-title="模組一：管理工具與目錄結構" data-link-desc="要把散落在家目錄的配置檔集中版控時，選 bare repo、stow 還是 chezmoi、目錄該怎麼組織">模組一到八&lt;/a> 的 dotfile 管理才有立足點。&lt;/p></description><content:encoded><![CDATA[<p>Bootstrap 腳本失敗是常態，所以它的設計目標之一應該是「失敗時可診斷」：把失敗當成會發生的事來設計，預先留好定位問題的痕跡。一支自動化安裝腳本要跨越的環境差異很多——機器缺某個工具、套件清單有筆誤、某個指令在這個發行版的行為跟預期不同——任何一處都可能讓它中斷。決定你是「三分鐘看出哪裡錯」還是「對著終端機捲半天瞎猜」的，是這支腳本有沒有在設計時就把可觀測性內建進去，跟運氣無關。</p>
<p>可觀測性要事先設計，是因為失敗發生的當下，你能拿到的資訊就已經定型了。如果腳本只把輸出丟到終端機、失敗時只留下一句通用的錯誤，那當下你就只有那句話可看；如果它一路把帶時間戳的紀錄寫進檔案、失敗時主動印出出錯的位置，那同一個失敗就變得可定位。差別不在失敗本身，在失敗前你準備了什麼。如果你寫的是自己的 bootstrap（例如部署 dotfile 的那支 <code>install.sh</code>），這層要在你第一次跑它之前就設計進去，而不是等它出事才回頭加；就算腳本不是你寫的、你只是來 debug 一次失敗，下一段「找程式自己的 log」一樣適用。</p>
<h2 id="為什麼會瞎找">為什麼會瞎找</h2>
<p>不可觀測的腳本失敗時，你手上只有終端機捲動過的那些輸出，而那往往不足以定位真正的原因。終端機的輸出是易逝的、會被後續輸出沖掉、多個來源的訊息交錯在一起；更麻煩的是，很多失敗的「表面錯誤」離「真正原因」隔了好幾層。一個指令因為前面某個變數是空的而失敗，但它報出來的錯可能完全沒提到那個空變數——你看著一個誤導性的症狀，往上游找不到源頭。</p>
<p>破解這種瞎找的，常常是一份你一開始沒看的 log。很多程式在終端機只印一段摘要，卻同時把詳細的執行紀錄寫進一個 log 檔；當終端機的訊息不足以定位時，那份程式自己寫的 log 裡往往就有答案。除錯時養成「找程式自己的 log，而不是只盯著終端機捲動」的習慣，是把瞎找變成定位的關鍵一步——這也是 <a href="/blog/linux/dotfile/07-desktop-maintenance/log-reading-diagnostic-tools/" data-link-title="日誌判讀與診斷工具" data-link-desc="知道桌面出了問題但不確定原因時回來讀 — journalctl、dmesg、hyprctl、systemctl 的使用方式和常見 log pattern">模組七日誌判讀</a> 的核心。而對你自己寫的 bootstrap，你可以更進一步：在設計時就讓它產生這樣一份 log。</p>
<h2 id="三個內建可觀測性的手法">三個內建可觀測性的手法</h2>
<p>讓一支 bootstrap 腳本可診斷，有三個低成本、效果明顯的手法，它們合起來把「失敗了」變成「失敗在第幾行、哪個指令、什麼狀態」。</p>
<h3 id="log-落地把全部輸出-tee-進帶時間戳的檔案">log 落地：把全部輸出 tee 進帶時間戳的檔案</h3>
<p>第一個手法是讓腳本的全部輸出同時進終端機跟一個 log 檔，而不是只進終端機。終端機的捲動是易逝的，log 檔是持久的——可以事後 <code>grep</code>、可以貼給別人看、可以比對前後兩次跑的差異。在 bash 裡，一行 <code>exec</code> 就能把後續所有 stdout 與 stderr 都導去 <code>tee</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nv">LOG_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">XDG_STATE_HOME</span><span class="k">:-</span><span class="nv">$HOME</span><span class="p">/.local/state</span><span class="si">}</span><span class="s2">/dotfiles&#34;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">mkdir -p <span class="s2">&#34;</span><span class="nv">$LOG_DIR</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nv">LOG_FILE</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$LOG_DIR</span><span class="s2">/install-</span><span class="k">$(</span>date +%Y%m%d-%H%M%S<span class="k">)</span><span class="s2">.log&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nb">exec</span> &gt; &gt;<span class="o">(</span>tee -a <span class="s2">&#34;</span><span class="nv">$LOG_FILE</span><span class="s2">&#34;</span><span class="o">)</span> 2&gt;<span class="p">&amp;</span><span class="m">1</span></span></span></code></pre></div><p>帶時間戳的檔名讓每次跑各留一份、不互相覆蓋，事後可以回溯「上一次成功跟這次失敗差在哪」。log 檔放在 <code>XDG_STATE_HOME</code>（狀態資料的標準位置）底下，符合慣例、也不污染家目錄。</p>
<h3 id="錯誤定位用-err-trap-印出出錯的行與指令">錯誤定位：用 ERR trap 印出出錯的行與指令</h3>
<p>第二個手法是讓腳本在中斷的瞬間，主動報出「是哪一行、哪個指令、什麼結束碼」失敗的。配合 <code>set -e</code>（出錯即停）的腳本，預設只會默默地停，不告訴你停在哪。加一個 <code>ERR</code> trap，就能在 <code>set -e</code> 中斷之前先印出定位資訊：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">set</span> -Eeuo pipefail   <span class="c1"># -E 讓 ERR trap 在函式/子 shell 也生效</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">trap</span> <span class="s1">&#39;log &#34;ERROR line $LINENO: [$BASH_COMMAND] exit=$?&#34;&#39;</span> ERR</span></span></code></pre></div><p><code>$LINENO</code> 是出錯的行號、<code>$BASH_COMMAND</code> 是當下正在執行的那條指令、<code>$?</code> 是它的結束碼。三者合起來，輸出會長這樣：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">[00:06:51] ERROR line 40: [sudo pacman -S --needed stow git zsh] exit=1</span></span></code></pre></div><p>範例裡的 <code>pacman</code> 換發行版會不同，這裡只是示意 trap 輸出的格式——手法本身（行號 + 指令 + 結束碼）跟發行版無關。這一行直接點名元兇。前面提過的那類「表面錯誤離真正原因隔好幾層」的情況——例如某個指令因為 <code>which</code> 不存在而拿到空字串、最後報一個看似無關的錯——有了這行，你會直接看到是哪一行的哪條指令掛了，不必從誤導性的症狀往回猜。<code>set -E</code>（<code>-E</code> 旗標）是為了讓 trap 在函式跟子 shell 裡也照樣觸發，少了它，包在函式裡的錯誤會漏掉。</p>
<h3 id="步驟標記用帶時間戳的-log-函式標出進度">步驟標記：用帶時間戳的 log 函式標出進度</h3>
<p>第三個手法是在關鍵步驟前印一行帶時間戳的標記，讓你能看出腳本跑到哪、哪一步慢。一個極簡的 log 函式就夠：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">log<span class="o">()</span> <span class="o">{</span> <span class="nb">printf</span> <span class="s1">&#39;[%s] %s\n&#39;</span> <span class="s2">&#34;</span><span class="k">$(</span>date +%H:%M:%S<span class="k">)</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$*</span><span class="s2">&#34;</span><span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">log <span class="s2">&#34;install.sh start | OS=</span><span class="nv">$OS</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">log <span class="s2">&#34;Installing base packages...&#34;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">log <span class="s2">&#34;Stowing configs...&#34;</span></span></span></code></pre></div><p>時間戳的價值在於它同時給你「進度」跟「效能」兩種資訊：失敗時，最後一行成功的 log 告訴你它跨過了哪些步驟、卡在哪一步之後；正常時，相鄰兩行的時間差告訴你哪一步耗時最久。這比沒有標記、只能從一堆套件下載輸出裡猜「現在到底在幹嘛」清楚得多。</p>
<h2 id="失敗可診斷是設計目標">失敗可診斷是設計目標</h2>
<p>把這三個手法合起來，一支原本「失敗時只留一句通用錯誤」的腳本，會變成「每次跑都留一份完整 log、失敗時直接點名第幾行哪個指令、過程中每步都有時間戳」。成本是腳本開頭多幾行，回報是把未來每一次除錯從瞎找變成定位。這層可觀測性是 <a href="/blog/linux/dotfile/08-sync-bootstrap/bootstrap-script-packages/" data-link-title="Bootstrap Script 與套件清單管理" data-link-desc="寫 dotfile 的 install script、或整理「這台機器裝了什麼」的套件清單時回來讀">模組八 bootstrap script 設計</a> 的延伸——那篇給安裝腳本的骨架與套件清單，這篇給它加上失敗時的診斷能力，兩篇處理的是同一支腳本的兩個層面。</p>
<p>這是設計階段的決定，不是事後能補的。當一支沒有可觀測性的腳本在一台陌生機器上失敗，你沒辦法回到過去讓它記錄當時的狀態——資訊在失敗的瞬間就已經流失了。所以「失敗可診斷」要跟功能一起設計進去，把它當成 bootstrap 的基本屬性，而不是出事之後才想加的補丁。</p>
<h2 id="回到系列">回到系列</h2>
<p>這幾篇合起來，是把一台機器從「空的」帶到「能接收 dotfile、且部署過程可診斷」的完整地基：<a href="../install-option-decisions/">安裝選項判讀</a> 處理 OS 怎麼裝、<a href="../minimal-install-verify/">工具驗證與補足</a> 處理裝完缺什麼、<a href="../ssh-keyless-bootstrap/">外部連入與無 key bootstrap</a> 處理怎麼連進去把 dotfile 弄進來，這一篇處理當部署失敗時怎麼快速看出原因。再往前一步，把這套地基用在無人值守的長任務上、讓機器在你離開後自己跑完工作，見 <a href="../unattended-remote-work/">讓機器跑無人值守的長任務</a>——無人盯著的任務尤其依賴這篇談的可觀測性。地基打好，後面 <a href="/blog/linux/dotfile/01-dotfile-management/" data-link-title="模組一：管理工具與目錄結構" data-link-desc="要把散落在家目錄的配置檔集中版控時，選 bare repo、stow 還是 chezmoi、目錄該怎麼組織">模組一到八</a> 的 dotfile 管理才有立足點。</p>
]]></content:encoded></item><item><title>模組六：桌面 Rice 設計</title><link>https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/</guid><description>&lt;p>Rice 在 Linux 桌面社群指的是桌面視覺客製化——把系統外觀調教成個人化的美學呈現。這個詞源自汽車改裝文化（Race Inspired Cosmetic Enhancements 的逆向縮寫），在 Linux 圈已轉為中性的圈內用語，r/unixporn 社群就是圍繞這件事運轉的。&lt;/p>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">Hyprland 配置&lt;/a>教了 compositor（Wayland 下負責視窗排列和畫面合成的程式）本身的設定。這個模組處理的是 compositor 之上的「桌面 shell」層——狀態列、啟動器、通知、鎖屏、桌布、配色系統。做法有兩條路：自己從 Waybar + Wofi + Mako 等獨立工具一個個拼裝，或用 Caelestia 這類預組裝的桌面 shell 一次部署。&lt;/p>
&lt;h2 id="章節文章">章節文章&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>文章&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/desktop-shell-components/" data-link-title="桌面 Shell 元件：狀態列、啟動器與通知" data-link-desc="Hyprland 桌面要拼哪些元件、各元件的配置檔怎麼寫時回來讀">桌面 Shell 元件：狀態列、啟動器與通知&lt;/a>&lt;/td>
 &lt;td>拼裝式桌面的元件組成、Waybar 狀態列配置、Wofi/Rofi 啟動器、Mako 通知&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/color-system-theming/" data-link-title="配色系統、鎖屏與 GTK 主題" data-link-desc="桌面配色散亂看起來雜、或要換主題不知道該改哪些檔案時回來讀">配色系統、鎖屏與 GTK 主題&lt;/a>&lt;/td>
 &lt;td>Hyprlock 鎖屏配置、配色變數集中管理、GTK/Qt 主題統一、投資報酬判讀&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/caelestia-overview/" data-link-title="Caelestia 總覽：預組裝的 Hyprland 桌面 Shell" data-link-desc="考慮用 Caelestia 取代手動拼裝 waybar&amp;#43;wofi&amp;#43;mako、或評估預組裝桌面 shell 的 trade-off 時回來讀">Caelestia 總覽&lt;/a>&lt;/td>
 &lt;td>Quickshell 框架、提供的元件、跟手動拼裝和 AGS/Eww 的 trade-off、定位認知&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/caelestia-installation/" data-link-title="Caelestia 安裝" data-link-desc="要在 Arch Linux 上安裝 Caelestia 桌面 shell 時回來讀">Caelestia 安裝&lt;/a>&lt;/td>
 &lt;td>AUR 套件、CLI 安裝流程、依賴清單、登入管理器、CLI 指令、VM 測試範圍&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/caelestia-configuration/" data-link-title="Caelestia 配置" data-link-desc="安裝完 Caelestia 後要客製化設定時回來讀">Caelestia 配置&lt;/a>&lt;/td>
 &lt;td>shell.json 結構、token 系統、hypr-user.lua、動態取色、已知問題、dotfile 結構&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/integrated-shell-vs-manual-assembly/" data-link-title="整合式 Shell vs 手動拼裝：實測足跡、失敗半徑與選型判準" data-link-desc="在整合式桌面 shell（如 Caelestia）與手動拼裝 waybar&amp;#43;wofi&amp;#43;mako 之間選型、需要實測的資源足跡、失敗半徑與配色一致性數據來判斷時回來讀">整合式 Shell vs 手動拼裝：實測取捨&lt;/a>&lt;/td>
 &lt;td>兩種桌面棧的實測足跡（安裝/記憶體）、失敗半徑、配色一致性機制與選型判準&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="跨分類引用">跨分類引用&lt;/h2>
&lt;ul>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/03-terminal-ecosystem/" data-link-title="模組三：終端機與編輯器" data-link-desc="終端機相關工具的配置檔散落在不同位置、不確定哪些該進 dotfile repo 時回來讀">模組三：終端機與編輯器&lt;/a>：Nerd Font 安裝是 Waybar icon 正常顯示的前提&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">模組五：Hyprland 配置&lt;/a>：compositor 的 appearance 設定（圓角、動畫）跟 rice 的視覺層互補&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/" data-link-title="模組一：管理工具與目錄結構" data-link-desc="要把散落在家目錄的配置檔集中版控時，選 bare repo、stow 還是 chezmoi、目錄該怎麼組織">模組一：管理工具與目錄結構&lt;/a>：各 rice 元件的配置檔怎麼放進 stow 結構&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/session-lock/" data-link-title="Wayland Session Lock（鎖屏安全狀態）" data-link-desc="hyprlock / swaylock 畫面卡住、pkill 後進不了桌面、或要在 VM / 自動化環境測試鎖屏時回來讀">Session Lock&lt;/a>：鎖屏的安全模型——殺 process 不等於解鎖&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/font-availability-at-startup/" data-link-title="字型的可用集合在 process 啟動時決定" data-link-desc="裝了字型但應用程式 / 狀態列 / 通知還是看不到、還是豆腐時回來讀">字型的可用集合在 process 啟動時決定&lt;/a>：裝了字型但狀態列 / 通知還是豆腐時的判讀&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/fontconfig/" data-link-title="fontconfig — 字型搜尋、匹配與 fallback 服務" data-link-desc="不確定 fc-list / fc-match / fc-cache 各做什麼、或 fontconfig fallback 機制怎麼運作時回來讀">fontconfig&lt;/a>：字型搜尋、匹配與 fallback 機制&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Rice 在 Linux 桌面社群指的是桌面視覺客製化——把系統外觀調教成個人化的美學呈現。這個詞源自汽車改裝文化（Race Inspired Cosmetic Enhancements 的逆向縮寫），在 Linux 圈已轉為中性的圈內用語，r/unixporn 社群就是圍繞這件事運轉的。</p>
<p><a href="/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">Hyprland 配置</a>教了 compositor（Wayland 下負責視窗排列和畫面合成的程式）本身的設定。這個模組處理的是 compositor 之上的「桌面 shell」層——狀態列、啟動器、通知、鎖屏、桌布、配色系統。做法有兩條路：自己從 Waybar + Wofi + Mako 等獨立工具一個個拼裝，或用 Caelestia 這類預組裝的桌面 shell 一次部署。</p>
<h2 id="章節文章">章節文章</h2>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>主題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/linux/dotfile/06-rice-design/desktop-shell-components/" data-link-title="桌面 Shell 元件：狀態列、啟動器與通知" data-link-desc="Hyprland 桌面要拼哪些元件、各元件的配置檔怎麼寫時回來讀">桌面 Shell 元件：狀態列、啟動器與通知</a></td>
          <td>拼裝式桌面的元件組成、Waybar 狀態列配置、Wofi/Rofi 啟動器、Mako 通知</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/06-rice-design/color-system-theming/" data-link-title="配色系統、鎖屏與 GTK 主題" data-link-desc="桌面配色散亂看起來雜、或要換主題不知道該改哪些檔案時回來讀">配色系統、鎖屏與 GTK 主題</a></td>
          <td>Hyprlock 鎖屏配置、配色變數集中管理、GTK/Qt 主題統一、投資報酬判讀</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/06-rice-design/caelestia-overview/" data-link-title="Caelestia 總覽：預組裝的 Hyprland 桌面 Shell" data-link-desc="考慮用 Caelestia 取代手動拼裝 waybar&#43;wofi&#43;mako、或評估預組裝桌面 shell 的 trade-off 時回來讀">Caelestia 總覽</a></td>
          <td>Quickshell 框架、提供的元件、跟手動拼裝和 AGS/Eww 的 trade-off、定位認知</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/06-rice-design/caelestia-installation/" data-link-title="Caelestia 安裝" data-link-desc="要在 Arch Linux 上安裝 Caelestia 桌面 shell 時回來讀">Caelestia 安裝</a></td>
          <td>AUR 套件、CLI 安裝流程、依賴清單、登入管理器、CLI 指令、VM 測試範圍</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/06-rice-design/caelestia-configuration/" data-link-title="Caelestia 配置" data-link-desc="安裝完 Caelestia 後要客製化設定時回來讀">Caelestia 配置</a></td>
          <td>shell.json 結構、token 系統、hypr-user.lua、動態取色、已知問題、dotfile 結構</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/06-rice-design/integrated-shell-vs-manual-assembly/" data-link-title="整合式 Shell vs 手動拼裝：實測足跡、失敗半徑與選型判準" data-link-desc="在整合式桌面 shell（如 Caelestia）與手動拼裝 waybar&#43;wofi&#43;mako 之間選型、需要實測的資源足跡、失敗半徑與配色一致性數據來判斷時回來讀">整合式 Shell vs 手動拼裝：實測取捨</a></td>
          <td>兩種桌面棧的實測足跡（安裝/記憶體）、失敗半徑、配色一致性機制與選型判準</td>
      </tr>
  </tbody>
</table>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/linux/dotfile/03-terminal-ecosystem/" data-link-title="模組三：終端機與編輯器" data-link-desc="終端機相關工具的配置檔散落在不同位置、不確定哪些該進 dotfile repo 時回來讀">模組三：終端機與編輯器</a>：Nerd Font 安裝是 Waybar icon 正常顯示的前提</li>
<li>→ <a href="/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">模組五：Hyprland 配置</a>：compositor 的 appearance 設定（圓角、動畫）跟 rice 的視覺層互補</li>
<li>→ <a href="/blog/linux/dotfile/01-dotfile-management/" data-link-title="模組一：管理工具與目錄結構" data-link-desc="要把散落在家目錄的配置檔集中版控時，選 bare repo、stow 還是 chezmoi、目錄該怎麼組織">模組一：管理工具與目錄結構</a>：各 rice 元件的配置檔怎麼放進 stow 結構</li>
<li>→ <a href="/blog/linux/dotfile/knowledge-cards/session-lock/" data-link-title="Wayland Session Lock（鎖屏安全狀態）" data-link-desc="hyprlock / swaylock 畫面卡住、pkill 後進不了桌面、或要在 VM / 自動化環境測試鎖屏時回來讀">Session Lock</a>：鎖屏的安全模型——殺 process 不等於解鎖</li>
<li>→ <a href="/blog/linux/dotfile/knowledge-cards/font-availability-at-startup/" data-link-title="字型的可用集合在 process 啟動時決定" data-link-desc="裝了字型但應用程式 / 狀態列 / 通知還是看不到、還是豆腐時回來讀">字型的可用集合在 process 啟動時決定</a>：裝了字型但狀態列 / 通知還是豆腐時的判讀</li>
<li>→ <a href="/blog/linux/dotfile/knowledge-cards/fontconfig/" data-link-title="fontconfig — 字型搜尋、匹配與 fallback 服務" data-link-desc="不確定 fc-list / fc-match / fc-cache 各做什麼、或 fontconfig fallback 機制怎麼運作時回來讀">fontconfig</a>：字型搜尋、匹配與 fallback 機制</li>
</ul>
]]></content:encoded></item><item><title>讓機器跑無人值守的長任務</title><link>https://tarrragon.github.io/blog/linux/install/unattended-remote-work/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/install/unattended-remote-work/</guid><description>&lt;p>一台機器能被連入、能跑 bootstrap（把它從空機器設定成可用環境的安裝流程）之後，下一個層次是讓它在你不盯著的時候自己跑完一個長任務——一次耗時的編譯、一個批次作業、一個無人值守的 agent。能不能放著走人，取決於有沒有把三件會中斷無人值守執行的事先解決掉：互動提示、斷線即死、結果出不去。這三件是「讓任務能在無人時順利啟動並交付」的障礙；任務跑起來之後的資源耗盡、OOM、額度或憑證到期是另一條軸（執行期的持久性），最後一段會接到那裡。這篇逐一拆解這三個障礙與對應的解法，並說明它們共同的代價判讀——這些便利大多拿安全性換自主性，該不該開要看這台機器的爆炸半徑。&lt;/p>
&lt;p>底下用一個具體情境當例子：在一台用完即丟的測試 VM 上，讓 Claude Code 這類 agent 自己跑完一段工作、把成果推回 GitHub 給你早上 review。同一組障礙換成 overnight 編譯或 cron 批次也成立。&lt;/p>
&lt;h2 id="障礙一互動提示擋住自動執行">障礙一：互動提示擋住自動執行&lt;/h2>
&lt;p>無人值守的程序沒有人在鍵盤前，所以任何「停下來等你輸入」的提示都會讓它卡死，其中最常見的是 sudo 密碼。一個要裝套件、改系統設定的任務，跑到 &lt;code>sudo&lt;/code> 那行就停在密碼提示、永遠等不到輸入，整個任務卡在那裡直到你回來。&lt;/p>
&lt;p>解法是讓這台機器的 sudo 免密碼（NOPASSWD），但這是一個明確的安全取捨、不是預設該開的東西。設定方式是給 sudoers 加一條 NOPASSWD 規則：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>whoami&lt;span class="k">)&lt;/span>&lt;span class="s2"> ALL=(ALL:ALL) NOPASSWD: ALL&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span> sudo tee /etc/sudoers.d/20-nopasswd &lt;span class="c1"># $(whoami) 會填入你的登入帳號&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">sudo chmod &lt;span class="m">440&lt;/span> /etc/sudoers.d/20-nopasswd&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>開了 NOPASSWD，等於放棄「sudo 密碼」這道在你被入侵或程序失控時的最後防線。判讀軸是這台機器的爆炸半徑——它持有哪些憑證、能觸及哪些系統，也就是最壞情況下會波及多大範圍。一台範圍受限、沒有任何真實憑證、出事就重建的測試 VM，放棄這道防線換取自動執行是划算的；一台共享主機、生產伺服器、或裝著真實憑證與資料的機器，不該為了方便開 NOPASSWD。關鍵是「可不可丟」不等於「爆炸半徑小」：一台用完即丟的 VM，一旦塞進能碰到生產系統或你帳號的憑證，爆炸半徑就不小了——看的不是機器本身，是它最壞情況能波及什麼。&lt;/p>
&lt;h2 id="障礙二ssh-斷線就把任務一起殺掉">障礙二：SSH 斷線就把任務一起殺掉&lt;/h2>
&lt;p>直接在 SSH session 裡跑的程序，會隨著 SSH 連線中斷而一起死掉——你闔上筆電、網路斷一下、或單純關掉終端機，正在跑的任務就沒了。對一個要跑好幾小時的無人值守任務，這條等於「你不能離開」，跟無人值守的目的矛盾。&lt;/p>
&lt;p>把任務搬進終端機多工器（zellij、tmux 這類，配置見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/03-terminal-ecosystem/multiplexer-tmux-zellij/" data-link-title="Multiplexer：tmux vs zellij" data-link-desc="在終端機裡切分 pane、管理多個 session、SSH 斷線後保持工作時回來讀 — tmux 和 zellij 的配置與選型">模組三&lt;/a>）就解決了。多工器的 session 活在那台機器上、獨立於你的 SSH 連線：你在多工器裡啟動任務、然後 detach（卸離），任務繼續在機器上跑，你這頭關掉 SSH 都不影響；之後再連回來 attach（接回）就能看它跑到哪。典型流程是連入機器、起多工器、在裡面啟動任務、detach、走人：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">ssh user@host
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">zellij &lt;span class="c1"># 起多工器（tmux 同理）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">./run-my-long-task.sh &lt;span class="c1"># 在裡面啟動你的長任務（換成你的實際指令）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 然後 detach：zellij 預設 Ctrl+o 再按 d（tmux 是 Ctrl+b 再按 d）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 此時關掉 SSH 不影響任務，它在 host 上繼續跑&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="c1"># 之後連回來看進度：再 ssh 進去，然後&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">zellij attach &lt;span class="c1"># tmux 是 tmux attach&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>判讀訊號是「這個任務跑完前，我會不會斷線」。只要會（過夜、跨小時、不穩的網路），就把它放進多工器；幾秒鐘就結束的指令不需要這層。&lt;/p>
&lt;h2 id="障礙三成果推不出去等於沒做">障礙三：成果推不出去，等於沒做&lt;/h2>
&lt;p>無人值守任務的產出留在那台機器上，你看不到——除非它能把結果送出去。最常見的形式是把改動 commit 後 push 回 git 遠端，你在別處 pull 來看。但 push 需要認證，而一台剛連入的機器通常還沒設好推送的憑證，於是任務做完了、commit 也建了，卻卡在 push 那步推不出去，你隔天連回來才發現結果根本沒送出去。&lt;/p>
&lt;p>先在這台機器上設好推送認證，這個障礙就消失。用 GitHub CLI 是直接的一條路，它認證後會一併把 git 的 credential helper（git 用來自動帶出認證、不必每次手打的機制）設好，後續 &lt;code>git push&lt;/code> 就能用——但 &lt;code>gh auth login&lt;/code> 本身是互動式的、要你在場完成一次，屬於離開前的人工前置：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">gh auth login &lt;span class="c1"># 選 HTTPS、完成認證、同意設定 git 認證&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>判讀軸是「這個任務的價值要怎麼回到你手上」。如果你打算從遠端（GitHub）看結果，那 push 認證就是必要前置——沒設好，整段工作就被困在機器裡。連帶的紀律是讓任務頻繁 commit 當檢查點、做完務必確認 push 成功：對一個你不在場的任務，「沒推出去」跟「沒做」對你是一樣的。機器若沒裝 &lt;code>gh&lt;/code>，也可以用 PAT 走 HTTPS，見 &lt;a href="../ssh-keyless-bootstrap/">外部連入篇&lt;/a> 的私有 repo 段。&lt;/p>
&lt;p>把 push 憑證設進這台機器，等於提高了它的爆炸半徑——它現在能動你的 repo 了。這會回頭讓障礙一的 NOPASSWD、以及下面 agent 段的權限放行更該謹慎：最壞情況從「弄壞這台機器」升級成「污染你的 repo」，而後者不是重建一台 VM 就能還原的。所以設了 push 憑證之後，要連帶重估前面那些「因為機器可丟所以放心」的取捨。&lt;/p>
&lt;h2 id="額外一層宿主暫停會連帶停掉任務">額外一層：宿主暫停會連帶停掉任務&lt;/h2>
&lt;p>當這台機器是跑在某個宿主上的虛擬機，還有一個容易忽略的中斷源：宿主睡著，VM 跟著暫停，裡面的無人值守任務也一起停。你以為它整夜在跑，回來發現它從你離開那刻就凍在那裡。判讀方式是想一下「這台機器的存在依賴什麼」——VM 依賴宿主醒著、雲端主機依賴帳單沒欠費。對 VM 的情況，離開前確保宿主不會自動睡眠（macOS 用 &lt;code>caffeinate&lt;/code>、Linux 宿主用 &lt;code>systemd-inhibit&lt;/code> 或停用 suspend、Windows 調電源設定，或直接關掉節能的自動睡眠）。&lt;/p>
&lt;h2 id="如果無人值守的工作者是-ai-agent">如果無人值守的工作者是 AI agent&lt;/h2>
&lt;p>當你放著跑的是一個 AI agent，除了上面三個障礙，還多一個它自己的互動提示要處理：agent 預設會在每個有風險的動作前停下來問你確認，而無人值守時沒人回答，它就卡住。對應的是 agent 的「跳過確認」模式（如 Claude Code 的權限放行旗標），讓它不停下來問。這跟 NOPASSWD 是同一類取捨、判讀軸也一樣：放給一個無人盯著的 agent 在一台範圍受限、用完即丟的機器上自主動作是可接受的；在一台有真實資料或共享的機器上不該這樣。降低風險的兩個做法是把 agent 的工作範圍用清楚的指引限定（只動哪些目錄、別碰系統其他地方），以及讓它在分支上做、產出交給你 review，而不是直接動到你會依賴的東西。&lt;/p></description><content:encoded><![CDATA[<p>一台機器能被連入、能跑 bootstrap（把它從空機器設定成可用環境的安裝流程）之後，下一個層次是讓它在你不盯著的時候自己跑完一個長任務——一次耗時的編譯、一個批次作業、一個無人值守的 agent。能不能放著走人，取決於有沒有把三件會中斷無人值守執行的事先解決掉：互動提示、斷線即死、結果出不去。這三件是「讓任務能在無人時順利啟動並交付」的障礙；任務跑起來之後的資源耗盡、OOM、額度或憑證到期是另一條軸（執行期的持久性），最後一段會接到那裡。這篇逐一拆解這三個障礙與對應的解法，並說明它們共同的代價判讀——這些便利大多拿安全性換自主性，該不該開要看這台機器的爆炸半徑。</p>
<p>底下用一個具體情境當例子：在一台用完即丟的測試 VM 上，讓 Claude Code 這類 agent 自己跑完一段工作、把成果推回 GitHub 給你早上 review。同一組障礙換成 overnight 編譯或 cron 批次也成立。</p>
<h2 id="障礙一互動提示擋住自動執行">障礙一：互動提示擋住自動執行</h2>
<p>無人值守的程序沒有人在鍵盤前，所以任何「停下來等你輸入」的提示都會讓它卡死，其中最常見的是 sudo 密碼。一個要裝套件、改系統設定的任務，跑到 <code>sudo</code> 那行就停在密碼提示、永遠等不到輸入，整個任務卡在那裡直到你回來。</p>
<p>解法是讓這台機器的 sudo 免密碼（NOPASSWD），但這是一個明確的安全取捨、不是預設該開的東西。設定方式是給 sudoers 加一條 NOPASSWD 規則：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;</span><span class="k">$(</span>whoami<span class="k">)</span><span class="s2"> ALL=(ALL:ALL) NOPASSWD: ALL&#34;</span> <span class="p">|</span> sudo tee /etc/sudoers.d/20-nopasswd  <span class="c1"># $(whoami) 會填入你的登入帳號</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">sudo chmod <span class="m">440</span> /etc/sudoers.d/20-nopasswd</span></span></code></pre></div><p>開了 NOPASSWD，等於放棄「sudo 密碼」這道在你被入侵或程序失控時的最後防線。判讀軸是這台機器的爆炸半徑——它持有哪些憑證、能觸及哪些系統，也就是最壞情況下會波及多大範圍。一台範圍受限、沒有任何真實憑證、出事就重建的測試 VM，放棄這道防線換取自動執行是划算的；一台共享主機、生產伺服器、或裝著真實憑證與資料的機器，不該為了方便開 NOPASSWD。關鍵是「可不可丟」不等於「爆炸半徑小」：一台用完即丟的 VM，一旦塞進能碰到生產系統或你帳號的憑證，爆炸半徑就不小了——看的不是機器本身，是它最壞情況能波及什麼。</p>
<h2 id="障礙二ssh-斷線就把任務一起殺掉">障礙二：SSH 斷線就把任務一起殺掉</h2>
<p>直接在 SSH session 裡跑的程序，會隨著 SSH 連線中斷而一起死掉——你闔上筆電、網路斷一下、或單純關掉終端機，正在跑的任務就沒了。對一個要跑好幾小時的無人值守任務，這條等於「你不能離開」，跟無人值守的目的矛盾。</p>
<p>把任務搬進終端機多工器（zellij、tmux 這類，配置見 <a href="/blog/linux/dotfile/03-terminal-ecosystem/multiplexer-tmux-zellij/" data-link-title="Multiplexer：tmux vs zellij" data-link-desc="在終端機裡切分 pane、管理多個 session、SSH 斷線後保持工作時回來讀 — tmux 和 zellij 的配置與選型">模組三</a>）就解決了。多工器的 session 活在那台機器上、獨立於你的 SSH 連線：你在多工器裡啟動任務、然後 detach（卸離），任務繼續在機器上跑，你這頭關掉 SSH 都不影響；之後再連回來 attach（接回）就能看它跑到哪。典型流程是連入機器、起多工器、在裡面啟動任務、detach、走人：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">ssh user@host
</span></span><span class="line"><span class="ln">2</span><span class="cl">zellij                       <span class="c1"># 起多工器（tmux 同理）</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">./run-my-long-task.sh        <span class="c1"># 在裡面啟動你的長任務（換成你的實際指令）</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 然後 detach：zellij 預設 Ctrl+o 再按 d（tmux 是 Ctrl+b 再按 d）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 此時關掉 SSH 不影響任務，它在 host 上繼續跑</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 之後連回來看進度：再 ssh 進去，然後</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">zellij attach                <span class="c1"># tmux 是 tmux attach</span></span></span></code></pre></div><p>判讀訊號是「這個任務跑完前，我會不會斷線」。只要會（過夜、跨小時、不穩的網路），就把它放進多工器；幾秒鐘就結束的指令不需要這層。</p>
<h2 id="障礙三成果推不出去等於沒做">障礙三：成果推不出去，等於沒做</h2>
<p>無人值守任務的產出留在那台機器上，你看不到——除非它能把結果送出去。最常見的形式是把改動 commit 後 push 回 git 遠端，你在別處 pull 來看。但 push 需要認證，而一台剛連入的機器通常還沒設好推送的憑證，於是任務做完了、commit 也建了，卻卡在 push 那步推不出去，你隔天連回來才發現結果根本沒送出去。</p>
<p>先在這台機器上設好推送認證，這個障礙就消失。用 GitHub CLI 是直接的一條路，它認證後會一併把 git 的 credential helper（git 用來自動帶出認證、不必每次手打的機制）設好，後續 <code>git push</code> 就能用——但 <code>gh auth login</code> 本身是互動式的、要你在場完成一次，屬於離開前的人工前置：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">gh auth login    <span class="c1"># 選 HTTPS、完成認證、同意設定 git 認證</span></span></span></code></pre></div><p>判讀軸是「這個任務的價值要怎麼回到你手上」。如果你打算從遠端（GitHub）看結果，那 push 認證就是必要前置——沒設好，整段工作就被困在機器裡。連帶的紀律是讓任務頻繁 commit 當檢查點、做完務必確認 push 成功：對一個你不在場的任務，「沒推出去」跟「沒做」對你是一樣的。機器若沒裝 <code>gh</code>，也可以用 PAT 走 HTTPS，見 <a href="../ssh-keyless-bootstrap/">外部連入篇</a> 的私有 repo 段。</p>
<p>把 push 憑證設進這台機器，等於提高了它的爆炸半徑——它現在能動你的 repo 了。這會回頭讓障礙一的 NOPASSWD、以及下面 agent 段的權限放行更該謹慎：最壞情況從「弄壞這台機器」升級成「污染你的 repo」，而後者不是重建一台 VM 就能還原的。所以設了 push 憑證之後，要連帶重估前面那些「因為機器可丟所以放心」的取捨。</p>
<h2 id="額外一層宿主暫停會連帶停掉任務">額外一層：宿主暫停會連帶停掉任務</h2>
<p>當這台機器是跑在某個宿主上的虛擬機，還有一個容易忽略的中斷源：宿主睡著，VM 跟著暫停，裡面的無人值守任務也一起停。你以為它整夜在跑，回來發現它從你離開那刻就凍在那裡。判讀方式是想一下「這台機器的存在依賴什麼」——VM 依賴宿主醒著、雲端主機依賴帳單沒欠費。對 VM 的情況，離開前確保宿主不會自動睡眠（macOS 用 <code>caffeinate</code>、Linux 宿主用 <code>systemd-inhibit</code> 或停用 suspend、Windows 調電源設定，或直接關掉節能的自動睡眠）。</p>
<h2 id="如果無人值守的工作者是-ai-agent">如果無人值守的工作者是 AI agent</h2>
<p>當你放著跑的是一個 AI agent，除了上面三個障礙，還多一個它自己的互動提示要處理：agent 預設會在每個有風險的動作前停下來問你確認，而無人值守時沒人回答，它就卡住。對應的是 agent 的「跳過確認」模式（如 Claude Code 的權限放行旗標），讓它不停下來問。這跟 NOPASSWD 是同一類取捨、判讀軸也一樣：放給一個無人盯著的 agent 在一台範圍受限、用完即丟的機器上自主動作是可接受的；在一台有真實資料或共享的機器上不該這樣。降低風險的兩個做法是把 agent 的工作範圍用清楚的指引限定（只動哪些目錄、別碰系統其他地方），以及讓它在分支上做、產出交給你 review，而不是直接動到你會依賴的東西。</p>
<h2 id="下一步">下一步</h2>
<p>把這三到四個障礙解決掉，一台機器就能在你離開後自己跑完工作、把成果送回你手上。這篇是 <a href="../ssh-keyless-bootstrap/">外部連入</a>（怎麼連進去）的延伸——從「我連進去手動操作」進到「我設好讓它自己跑」。而要讓那個無人值守的任務在失敗時還留得下可診斷的痕跡，回到 <a href="../observable-bootstrap/">可除錯的 bootstrap</a> 的原則：無人盯著的任務尤其需要把可觀測性內建進去，因為你不在場、只能事後從 log 重建發生了什麼。</p>
]]></content:encoded></item><item><title>模組七：桌面環境維護與故障排除</title><link>https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/</link><pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/</guid><description>&lt;p>模組零到六教的是怎麼建立桌面環境，這個模組教的是壞了怎麼修。&lt;/p>
&lt;p>Linux 桌面環境跟 Windows 在故障模型上有根本的結構差異。Windows 的藍屏（BSOD）是核心層崩潰，整台機器停擺；Linux 桌面環境的大部分故障只影響 userspace，系統核心不受波及。理解這個隔離邊界，是判斷「當下該做什麼」的前提。&lt;/p>
&lt;p>這個模組的閱讀方式跟其他模組不同。其他模組是線性學習——從頭讀到尾建立知識。這個模組是 reference——出問題時根據症狀查對應的恢復操作。第一篇建立概念模型，第二篇按場景查操作，第三篇教怎麼看日誌找根因。&lt;/p>
&lt;h2 id="章節文章">章節文章&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>文章&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/fault-isolation-model/" data-link-title="Linux 桌面的故障隔離模型" data-link-desc="從 Windows 轉過來想知道 Linux 桌面掛了會不會整台崩潰時讀 — kernel vs userspace 隔離、compositor 是 userspace process、TTY 救生通道與其限制">Linux 桌面的故障隔離模型&lt;/a>&lt;/td>
 &lt;td>kernel vs userspace 隔離、compositor 掛了不等於系統崩潰、TTY&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/common-failures-recovery/" data-link-title="常見故障場景與恢復操作" data-link-desc="Hyprland 黑屏、waybar 消失、畫面凍結、記憶體爆掉或 config 寫錯導致進不了桌面時，按症狀查恢復操作">常見故障場景與恢復操作&lt;/a>&lt;/td>
 &lt;td>compositor crash、工具掛了、GPU hang、OOM、config 錯誤、suspend/resume 異常的處理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/log-reading-diagnostic-tools/" data-link-title="日誌判讀與診斷工具" data-link-desc="知道桌面出了問題但不確定原因時回來讀 — journalctl、dmesg、hyprctl、systemctl 的使用方式和常見 log pattern">日誌判讀與診斷工具&lt;/a>&lt;/td>
 &lt;td>journalctl、dmesg、hyprctl、systemctl 的使用與常見 pattern&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="跨分類引用">跨分類引用&lt;/h2>
&lt;ul>
&lt;li>-&amp;gt; &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">模組五：Hyprland 配置&lt;/a>：Hyprland 的配置結構和 hyprctl 指令&lt;/li>
&lt;li>-&amp;gt; &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">模組六：桌面 Rice 設計&lt;/a>：waybar / wofi / mako 等工具的配置位置&lt;/li>
&lt;li>-&amp;gt; &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/08-sync-bootstrap/" data-link-title="模組八：同步、Bootstrap 與環境重建" data-link-desc="換機器或重灌時怎麼還原工作環境 — bootstrap script 設計、套件清單管理、跨機器同步策略、secret 排除，以及 VM 快照和 dotfile 重建兩種思路的場景判讀">模組八：同步、Bootstrap 與環境重建&lt;/a>：環境損壞到無法修復時的重建策略&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>模組零到六教的是怎麼建立桌面環境，這個模組教的是壞了怎麼修。</p>
<p>Linux 桌面環境跟 Windows 在故障模型上有根本的結構差異。Windows 的藍屏（BSOD）是核心層崩潰，整台機器停擺；Linux 桌面環境的大部分故障只影響 userspace，系統核心不受波及。理解這個隔離邊界，是判斷「當下該做什麼」的前提。</p>
<p>這個模組的閱讀方式跟其他模組不同。其他模組是線性學習——從頭讀到尾建立知識。這個模組是 reference——出問題時根據症狀查對應的恢復操作。第一篇建立概念模型，第二篇按場景查操作，第三篇教怎麼看日誌找根因。</p>
<h2 id="章節文章">章節文章</h2>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>主題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/linux/dotfile/07-desktop-maintenance/fault-isolation-model/" data-link-title="Linux 桌面的故障隔離模型" data-link-desc="從 Windows 轉過來想知道 Linux 桌面掛了會不會整台崩潰時讀 — kernel vs userspace 隔離、compositor 是 userspace process、TTY 救生通道與其限制">Linux 桌面的故障隔離模型</a></td>
          <td>kernel vs userspace 隔離、compositor 掛了不等於系統崩潰、TTY</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/07-desktop-maintenance/common-failures-recovery/" data-link-title="常見故障場景與恢復操作" data-link-desc="Hyprland 黑屏、waybar 消失、畫面凍結、記憶體爆掉或 config 寫錯導致進不了桌面時，按症狀查恢復操作">常見故障場景與恢復操作</a></td>
          <td>compositor crash、工具掛了、GPU hang、OOM、config 錯誤、suspend/resume 異常的處理</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/07-desktop-maintenance/log-reading-diagnostic-tools/" data-link-title="日誌判讀與診斷工具" data-link-desc="知道桌面出了問題但不確定原因時回來讀 — journalctl、dmesg、hyprctl、systemctl 的使用方式和常見 log pattern">日誌判讀與診斷工具</a></td>
          <td>journalctl、dmesg、hyprctl、systemctl 的使用與常見 pattern</td>
      </tr>
  </tbody>
</table>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>-&gt; <a href="/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">模組五：Hyprland 配置</a>：Hyprland 的配置結構和 hyprctl 指令</li>
<li>-&gt; <a href="/blog/linux/dotfile/06-rice-design/" data-link-title="模組六：桌面 Rice 設計" data-link-desc="Hyprland 桌面從能用到好看好用 — 狀態列、啟動器、通知、鎖屏、配色系統的設計與配置">模組六：桌面 Rice 設計</a>：waybar / wofi / mako 等工具的配置位置</li>
<li>-&gt; <a href="/blog/linux/dotfile/08-sync-bootstrap/" data-link-title="模組八：同步、Bootstrap 與環境重建" data-link-desc="換機器或重灌時怎麼還原工作環境 — bootstrap script 設計、套件清單管理、跨機器同步策略、secret 排除，以及 VM 快照和 dotfile 重建兩種思路的場景判讀">模組八：同步、Bootstrap 與環境重建</a>：環境損壞到無法修復時的重建策略</li>
</ul>
]]></content:encoded></item><item><title>Compositor（合成器）</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/compositor/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/compositor/</guid><description>&lt;p>Compositor（合成器）是 Wayland 下負責把各個應用視窗的畫面合成到螢幕、同時管理視窗位置與輸入的核心程式。它一個角色承擔了舊 X11 世界裡分給多個程式的責任——畫面合成、視窗管理、輸入處理，在 Wayland 架構裡合在同一個程式。&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">Hyprland&lt;/a> 就是一個 Wayland compositor。&lt;/p>
&lt;p>跟 X11 的分工對照能看清它的定位。X11 時代，X server 負責畫面、一個獨立的 window manager 負責視窗排列，兩者透過協定溝通；Wayland 取消這個分家，compositor 直接兼任兩者。所以在 Wayland 語境裡，「compositor」和「window manager」常指同一個東西——Hyprland 既是 compositor 也是 tiling window manager。&lt;/p>
&lt;p>它跟桌面環境（desktop environment）是不同層次。桌面環境（GNOME、KDE）是一整套元件（面板、設定、通知、檔案管理），其中內含一個 compositor；而 Hyprland 這類「只有 compositor」的方案不含那圈桌面服務，面板、啟動器、鎖屏都要另外自己接。這條「整合度 vs 自己組裝」的軸線，是&lt;a href="https://tarrragon.github.io/blog/linux/tools/gui/desktop-environment-selection/" data-link-title="桌面環境選型：整合度與組裝自由度的取捨" data-link-desc="從 Windows/macOS 轉來或要挑第一個 Linux 桌面、在 GNOME / KDE / Hyprland / XFCE / Cinnamon 之間拿不定、想知道各自的定位與代價（資源、客製自由、穩定性、Wayland 支援）時回來讀">桌面環境選型&lt;/a>的主題。&lt;/p>
&lt;p>compositor 在故障排除中是一個關鍵的責任邊界，因為多個系統狀態由它持有、而非別的層：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>它握著 DRM master&lt;/strong>：直接畫到顯示裝置的獨佔權由 compositor 持有，這是為什麼 compositor 必須從實體圖形 VT 起、不能從 SSH pty 起（SSH 連線裡沒有那個 seat 與 DRM 資源），也是為什麼它跟同樣要畫到 DRM 的 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/tty/" data-link-title="TTY" data-link-desc="恢復操作提到切 TTY 但不知道 TTY 是什麼時讀 — Linux 核心直接提供的純文字終端機介面">kmscon 之類 userspace console&lt;/a> 會相衝。&lt;/li>
&lt;li>&lt;strong>它持有 session lock 狀態&lt;/strong>：Wayland 的 &lt;code>ext-session-lock&lt;/code> 鎖是 compositor 層的狀態、跟 logind 獨立，殺掉鎖屏程式 compositor 仍保持鎖定——詳見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/session-lock/" data-link-title="Wayland Session Lock（鎖屏安全狀態）" data-link-desc="hyprlock / swaylock 畫面卡住、pkill 後進不了桌面、或要在 VM / 自動化環境測試鎖屏時回來讀">Session Lock&lt;/a>。&lt;/li>
&lt;li>&lt;strong>它掛了、桌面才真的黑&lt;/strong>：反過來說，只要 kernel 還活著、compositor 以外的東西壞了，&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/tty/" data-link-title="TTY" data-link-desc="恢復操作提到切 TTY 但不知道 TTY 是什麼時讀 — Linux 核心直接提供的純文字終端機介面">TTY&lt;/a> 仍能登入操作。判斷「畫面黑」是 compositor 掛了、還是沒 getty、還是顯示輸出沒接，是桌面故障排除的第一個分岔。&lt;/li>
&lt;/ul>
&lt;p>相關概念：&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/tty/" data-link-title="TTY" data-link-desc="恢復操作提到切 TTY 但不知道 TTY 是什麼時讀 — Linux 核心直接提供的純文字終端機介面">TTY&lt;/a>（不依賴 compositor 的救生通道）、&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/session-lock/" data-link-title="Wayland Session Lock（鎖屏安全狀態）" data-link-desc="hyprlock / swaylock 畫面卡住、pkill 後進不了桌面、或要在 VM / 自動化環境測試鎖屏時回來讀">Session Lock&lt;/a>（compositor 持有的鎖定狀態）、&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/rice/" data-link-title="Rice（桌面視覺客製化）" data-link-desc="Linux 桌面文章裡看到 rice / ricing / ricer 不確定意思時回來讀">Rice&lt;/a>（在 compositor 上做視覺客製）。&lt;/p></description><content:encoded><![CDATA[<p>Compositor（合成器）是 Wayland 下負責把各個應用視窗的畫面合成到螢幕、同時管理視窗位置與輸入的核心程式。它一個角色承擔了舊 X11 世界裡分給多個程式的責任——畫面合成、視窗管理、輸入處理，在 Wayland 架構裡合在同一個程式。<a href="/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">Hyprland</a> 就是一個 Wayland compositor。</p>
<p>跟 X11 的分工對照能看清它的定位。X11 時代，X server 負責畫面、一個獨立的 window manager 負責視窗排列，兩者透過協定溝通；Wayland 取消這個分家，compositor 直接兼任兩者。所以在 Wayland 語境裡，「compositor」和「window manager」常指同一個東西——Hyprland 既是 compositor 也是 tiling window manager。</p>
<p>它跟桌面環境（desktop environment）是不同層次。桌面環境（GNOME、KDE）是一整套元件（面板、設定、通知、檔案管理），其中內含一個 compositor；而 Hyprland 這類「只有 compositor」的方案不含那圈桌面服務，面板、啟動器、鎖屏都要另外自己接。這條「整合度 vs 自己組裝」的軸線，是<a href="/blog/linux/tools/gui/desktop-environment-selection/" data-link-title="桌面環境選型：整合度與組裝自由度的取捨" data-link-desc="從 Windows/macOS 轉來或要挑第一個 Linux 桌面、在 GNOME / KDE / Hyprland / XFCE / Cinnamon 之間拿不定、想知道各自的定位與代價（資源、客製自由、穩定性、Wayland 支援）時回來讀">桌面環境選型</a>的主題。</p>
<p>compositor 在故障排除中是一個關鍵的責任邊界，因為多個系統狀態由它持有、而非別的層：</p>
<ul>
<li><strong>它握著 DRM master</strong>：直接畫到顯示裝置的獨佔權由 compositor 持有，這是為什麼 compositor 必須從實體圖形 VT 起、不能從 SSH pty 起（SSH 連線裡沒有那個 seat 與 DRM 資源），也是為什麼它跟同樣要畫到 DRM 的 <a href="/blog/linux/dotfile/knowledge-cards/tty/" data-link-title="TTY" data-link-desc="恢復操作提到切 TTY 但不知道 TTY 是什麼時讀 — Linux 核心直接提供的純文字終端機介面">kmscon 之類 userspace console</a> 會相衝。</li>
<li><strong>它持有 session lock 狀態</strong>：Wayland 的 <code>ext-session-lock</code> 鎖是 compositor 層的狀態、跟 logind 獨立，殺掉鎖屏程式 compositor 仍保持鎖定——詳見 <a href="/blog/linux/dotfile/knowledge-cards/session-lock/" data-link-title="Wayland Session Lock（鎖屏安全狀態）" data-link-desc="hyprlock / swaylock 畫面卡住、pkill 後進不了桌面、或要在 VM / 自動化環境測試鎖屏時回來讀">Session Lock</a>。</li>
<li><strong>它掛了、桌面才真的黑</strong>：反過來說，只要 kernel 還活著、compositor 以外的東西壞了，<a href="/blog/linux/dotfile/knowledge-cards/tty/" data-link-title="TTY" data-link-desc="恢復操作提到切 TTY 但不知道 TTY 是什麼時讀 — Linux 核心直接提供的純文字終端機介面">TTY</a> 仍能登入操作。判斷「畫面黑」是 compositor 掛了、還是沒 getty、還是顯示輸出沒接，是桌面故障排除的第一個分岔。</li>
</ul>
<p>相關概念：<a href="/blog/linux/dotfile/knowledge-cards/tty/" data-link-title="TTY" data-link-desc="恢復操作提到切 TTY 但不知道 TTY 是什麼時讀 — Linux 核心直接提供的純文字終端機介面">TTY</a>（不依賴 compositor 的救生通道）、<a href="/blog/linux/dotfile/knowledge-cards/session-lock/" data-link-title="Wayland Session Lock（鎖屏安全狀態）" data-link-desc="hyprlock / swaylock 畫面卡住、pkill 後進不了桌面、或要在 VM / 自動化環境測試鎖屏時回來讀">Session Lock</a>（compositor 持有的鎖定狀態）、<a href="/blog/linux/dotfile/knowledge-cards/rice/" data-link-title="Rice（桌面視覺客製化）" data-link-desc="Linux 桌面文章裡看到 rice / ricing / ricer 不確定意思時回來讀">Rice</a>（在 compositor 上做視覺客製）。</p>
]]></content:encoded></item><item><title>TTY</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/tty/</link><pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/tty/</guid><description>&lt;p>TTY（TeleTYpewriter）是 Linux 核心直接提供的純文字終端機介面，獨立於任何桌面環境或圖形介面。&lt;/p>
&lt;p>名稱來自早期電腦透過電報打字機（teletypewriter）做輸入輸出的歷史。現代 Linux 的 TTY 是 virtual console——核心在記憶體中模擬的文字終端機，不需要實體硬體。&lt;/p>
&lt;p>systemd 預設配置下有 6 個 virtual console（TTY1-TTY6）。Wayland compositor（如 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">Hyprland&lt;/a>）通常佔用 TTY1 顯示圖形桌面，TTY2-TTY6 保持為純文字介面可用。&lt;/p>
&lt;p>切換方式：&lt;code>Ctrl+Alt+F2&lt;/code>（切到 TTY2）到 &lt;code>Ctrl+Alt+F6&lt;/code>（切到 TTY6）。&lt;code>Ctrl+Alt+F1&lt;/code> 切回圖形桌面（TTY1）。&lt;/p>
&lt;p>TTY 在桌面故障排除中的價值在於它&lt;strong>不依賴 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/compositor/" data-link-title="Compositor（合成器）" data-link-desc="教材反覆出現 compositor / 合成器、想確認它到底負責什麼、跟 window manager 和桌面環境差在哪時讀 — Wayland 下把畫面合成與視窗管理合一的核心程式">compositor&lt;/a>&lt;/strong>（Wayland 下負責畫面與視窗的核心程式）。Compositor 掛了、GPU driver 出問題導致畫面凍結——只要 kernel 還活著，TTY 就能登入操作。這是 Linux 桌面環境「掛了不等於崩潰」的關鍵機制，詳見&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/07-desktop-maintenance/" data-link-title="模組七：桌面環境維護與故障排除" data-link-desc="桌面凍結、compositor 掛了、或某個工具不回應時回來讀 — Linux 桌面的故障隔離模型、常見故障場景的恢復操作、日誌判讀與診斷工具">桌面環境維護與故障排除&lt;/a>。&lt;/p>
&lt;p>相關概念：&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/rice/" data-link-title="Rice（桌面視覺客製化）" data-link-desc="Linux 桌面文章裡看到 rice / ricing / ricer 不確定意思時回來讀">Rice&lt;/a>（桌面客製化）、&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/gnu-stow/" data-link-title="GNU Stow" data-link-desc="dotfile 管理文章裡提到 stow、symlink、package 看不懂時回來讀 — stow 的核心概念和常用指令">GNU Stow&lt;/a>（dotfile 管理）。&lt;/p></description><content:encoded><![CDATA[<p>TTY（TeleTYpewriter）是 Linux 核心直接提供的純文字終端機介面，獨立於任何桌面環境或圖形介面。</p>
<p>名稱來自早期電腦透過電報打字機（teletypewriter）做輸入輸出的歷史。現代 Linux 的 TTY 是 virtual console——核心在記憶體中模擬的文字終端機，不需要實體硬體。</p>
<p>systemd 預設配置下有 6 個 virtual console（TTY1-TTY6）。Wayland compositor（如 <a href="/blog/linux/dotfile/05-hyprland-config/" data-link-title="模組五：Hyprland 配置" data-link-desc="要在 Linux 上設定 Hyprland 平鋪式桌面時回來讀">Hyprland</a>）通常佔用 TTY1 顯示圖形桌面，TTY2-TTY6 保持為純文字介面可用。</p>
<p>切換方式：<code>Ctrl+Alt+F2</code>（切到 TTY2）到 <code>Ctrl+Alt+F6</code>（切到 TTY6）。<code>Ctrl+Alt+F1</code> 切回圖形桌面（TTY1）。</p>
<p>TTY 在桌面故障排除中的價值在於它<strong>不依賴 <a href="/blog/linux/dotfile/knowledge-cards/compositor/" data-link-title="Compositor（合成器）" data-link-desc="教材反覆出現 compositor / 合成器、想確認它到底負責什麼、跟 window manager 和桌面環境差在哪時讀 — Wayland 下把畫面合成與視窗管理合一的核心程式">compositor</a></strong>（Wayland 下負責畫面與視窗的核心程式）。Compositor 掛了、GPU driver 出問題導致畫面凍結——只要 kernel 還活著，TTY 就能登入操作。這是 Linux 桌面環境「掛了不等於崩潰」的關鍵機制，詳見<a href="/blog/linux/dotfile/07-desktop-maintenance/" data-link-title="模組七：桌面環境維護與故障排除" data-link-desc="桌面凍結、compositor 掛了、或某個工具不回應時回來讀 — Linux 桌面的故障隔離模型、常見故障場景的恢復操作、日誌判讀與診斷工具">桌面環境維護與故障排除</a>。</p>
<p>相關概念：<a href="/blog/linux/dotfile/knowledge-cards/rice/" data-link-title="Rice（桌面視覺客製化）" data-link-desc="Linux 桌面文章裡看到 rice / ricing / ricer 不確定意思時回來讀">Rice</a>（桌面客製化）、<a href="/blog/linux/dotfile/knowledge-cards/gnu-stow/" data-link-title="GNU Stow" data-link-desc="dotfile 管理文章裡提到 stow、symlink、package 看不懂時回來讀 — stow 的核心概念和常用指令">GNU Stow</a>（dotfile 管理）。</p>
]]></content:encoded></item><item><title>initramfs</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/initramfs/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/initramfs/</guid><description>&lt;p>initramfs（initial RAM filesystem）是 kernel 開機初期、在真正的 root 檔案系統被掛起來之前，載入記憶體的一個小型臨時根檔系統。&lt;/p>
&lt;p>它的責任是「把掛載真 root 所需的東西先備齊」。kernel 本身不內建所有硬體與檔案系統的驅動，當 root 位在一個需要額外驅動才讀得到的裝置上——LVM 邏輯卷、LUKS 加密卷、特殊磁碟控制器——kernel 沒辦法直接掛它。initramfs 提供一個臨時環境，把這些驅動與工具載進來、把真 root 掛起來，然後把控制權交給真 root 上的 init。&lt;/p>
&lt;p>因為它要跟 kernel 一起被 bootloader 載入，所以它跟 kernel 放在同一個地方——&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/" data-link-title="UEFI 開機鏈" data-link-desc="在 bootloader 選型（GRUB / EFISTUB / systemd-boot）卡住、或機器重開後找不到 kernel、需要理解韌體怎麼找到並載入系統時讀 — 韌體到 kernel 的交棒過程">UEFI 開機鏈&lt;/a>裡的 ESP，或獨立的 &lt;code>/boot&lt;/code>。這也是為什麼估 ESP 大小時要把它算進去：一個 kernel 加上它的 initramfs（含 fallback 版本）大約一兩百 MB。&lt;/p>
&lt;p>生成工具依發行版而異：Arch 用 &lt;code>mkinitcpio&lt;/code>、Fedora 用 &lt;code>dracut&lt;/code>。換了 kernel 或改了開機需要的驅動，要重新生成 initramfs，否則新 kernel 可能掛不起 root。&lt;/p>
&lt;p>相關概念：&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/" data-link-title="UEFI 開機鏈" data-link-desc="在 bootloader 選型（GRUB / EFISTUB / systemd-boot）卡住、或機器重開後找不到 kernel、需要理解韌體怎麼找到並載入系統時讀 — 韌體到 kernel 的交棒過程">UEFI 開機鏈&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/partition-identification/" data-link-title="分區識別（PARTUUID / FSUUID）" data-link-desc="在 fstab 或 bootloader 設定要指定一個分區、不確定該用 PARTUUID、UUID 還是 /dev/sda1、或重格式化後系統開不了機時讀 — 分區的穩定識別方式">分區識別&lt;/a>。安裝時 ESP 大小怎麼估，見 &lt;a href="https://tarrragon.github.io/blog/linux/install/install-option-decisions/" data-link-title="Linux 安裝選項判讀" data-link-desc="在 Linux 安裝程式面對 locale、網路、磁碟分割、檔案系統、bootloader 等選項、需要判斷依據而非靠預設值硬選時回來讀">Linux 安裝選項判讀&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>initramfs（initial RAM filesystem）是 kernel 開機初期、在真正的 root 檔案系統被掛起來之前，載入記憶體的一個小型臨時根檔系統。</p>
<p>它的責任是「把掛載真 root 所需的東西先備齊」。kernel 本身不內建所有硬體與檔案系統的驅動，當 root 位在一個需要額外驅動才讀得到的裝置上——LVM 邏輯卷、LUKS 加密卷、特殊磁碟控制器——kernel 沒辦法直接掛它。initramfs 提供一個臨時環境，把這些驅動與工具載進來、把真 root 掛起來，然後把控制權交給真 root 上的 init。</p>
<p>因為它要跟 kernel 一起被 bootloader 載入，所以它跟 kernel 放在同一個地方——<a href="/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/" data-link-title="UEFI 開機鏈" data-link-desc="在 bootloader 選型（GRUB / EFISTUB / systemd-boot）卡住、或機器重開後找不到 kernel、需要理解韌體怎麼找到並載入系統時讀 — 韌體到 kernel 的交棒過程">UEFI 開機鏈</a>裡的 ESP，或獨立的 <code>/boot</code>。這也是為什麼估 ESP 大小時要把它算進去：一個 kernel 加上它的 initramfs（含 fallback 版本）大約一兩百 MB。</p>
<p>生成工具依發行版而異：Arch 用 <code>mkinitcpio</code>、Fedora 用 <code>dracut</code>。換了 kernel 或改了開機需要的驅動，要重新生成 initramfs，否則新 kernel 可能掛不起 root。</p>
<p>相關概念：<a href="/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/" data-link-title="UEFI 開機鏈" data-link-desc="在 bootloader 選型（GRUB / EFISTUB / systemd-boot）卡住、或機器重開後找不到 kernel、需要理解韌體怎麼找到並載入系統時讀 — 韌體到 kernel 的交棒過程">UEFI 開機鏈</a>、<a href="/blog/linux/dotfile/knowledge-cards/partition-identification/" data-link-title="分區識別（PARTUUID / FSUUID）" data-link-desc="在 fstab 或 bootloader 設定要指定一個分區、不確定該用 PARTUUID、UUID 還是 /dev/sda1、或重格式化後系統開不了機時讀 — 分區的穩定識別方式">分區識別</a>。安裝時 ESP 大小怎麼估，見 <a href="/blog/linux/install/install-option-decisions/" data-link-title="Linux 安裝選項判讀" data-link-desc="在 Linux 安裝程式面對 locale、網路、磁碟分割、檔案系統、bootloader 等選項、需要判斷依據而非靠預設值硬選時回來讀">Linux 安裝選項判讀</a>。</p>
]]></content:encoded></item><item><title>UEFI 開機鏈</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/</guid><description>&lt;p>UEFI 開機鏈是現代機器從通電到 kernel 跑起來的一段交棒過程：韌體 → 開機項 → bootloader → kernel。理解這條鏈，bootloader 選型與「重開機找不到系統」的故障才有判讀依據。&lt;/p>
&lt;p>UEFI 韌體開機時，要找到一個 EFI 執行檔來載入。它從兩個來源找：NVRAM（韌體用來存開機項的非揮發記憶體）裡登記的開機項，或 ESP（EFI System Partition，一個 FAT32 格式的分區）裡的標準路徑。找到的 EFI 執行檔可能是一個獨立的 bootloader，也可能直接是 kernel。&lt;/p>
&lt;p>這對應兩種風格。EFISTUB 讓韌體直接載入 kernel、不經過獨立 bootloader，最精簡，但典型上依賴 NVRAM 裡的開機項。獨立 bootloader（GRUB、systemd-boot）則多一層：它有開機選單、能救援、還能裝到 ESP 的 fallback 路徑（&lt;code>\EFI\BOOT\BOOT&amp;lt;ARCH&amp;gt;.EFI&lt;/code>，aarch64 是 &lt;code>BOOTAA64.EFI&lt;/code>、x86_64 是 &lt;code>BOOTX64.EFI&lt;/code>）。&lt;/p>
&lt;p>fallback 路徑是這條鏈的保命機制。NVRAM 的開機項可能丟失——QEMU 系的虛擬機尤其容易——這時靠 NVRAM 開機項的 EFISTUB 會開不了機，而 fallback 路徑上有 bootloader 的機器，韌體仍找得到。這就是「VM 上偏好獨立 bootloader」的根據。&lt;/p>
&lt;p>這條鏈預設 Secure Boot 關閉。Secure Boot 開啟時，韌體會拒載沒簽章的 EFI 執行檔（kernel 或 bootloader），這也是「重開後找不到 kernel」的一類成因——最小 VM 安裝通常把它關掉，但實體機若開著就要處理簽章。&lt;/p>
&lt;p>相關概念：&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/initramfs/" data-link-title="initramfs" data-link-desc="看到 ESP 大小要算進 initramfs、或開機卡在掛載 root 之前、不知道 initramfs 是什麼時讀 — 開機初期掛真 root 之前的臨時根檔系統">initramfs&lt;/a>（bootloader 載入的對象之一）、&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/partition-identification/" data-link-title="分區識別（PARTUUID / FSUUID）" data-link-desc="在 fstab 或 bootloader 設定要指定一個分區、不確定該用 PARTUUID、UUID 還是 /dev/sda1、或重格式化後系統開不了機時讀 — 分區的穩定識別方式">分區識別&lt;/a>。bootloader 選型的判讀，見 &lt;a href="https://tarrragon.github.io/blog/linux/install/install-option-decisions/" data-link-title="Linux 安裝選項判讀" data-link-desc="在 Linux 安裝程式面對 locale、網路、磁碟分割、檔案系統、bootloader 等選項、需要判斷依據而非靠預設值硬選時回來讀">Linux 安裝選項判讀&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>UEFI 開機鏈是現代機器從通電到 kernel 跑起來的一段交棒過程：韌體 → 開機項 → bootloader → kernel。理解這條鏈，bootloader 選型與「重開機找不到系統」的故障才有判讀依據。</p>
<p>UEFI 韌體開機時，要找到一個 EFI 執行檔來載入。它從兩個來源找：NVRAM（韌體用來存開機項的非揮發記憶體）裡登記的開機項，或 ESP（EFI System Partition，一個 FAT32 格式的分區）裡的標準路徑。找到的 EFI 執行檔可能是一個獨立的 bootloader，也可能直接是 kernel。</p>
<p>這對應兩種風格。EFISTUB 讓韌體直接載入 kernel、不經過獨立 bootloader，最精簡，但典型上依賴 NVRAM 裡的開機項。獨立 bootloader（GRUB、systemd-boot）則多一層：它有開機選單、能救援、還能裝到 ESP 的 fallback 路徑（<code>\EFI\BOOT\BOOT&lt;ARCH&gt;.EFI</code>，aarch64 是 <code>BOOTAA64.EFI</code>、x86_64 是 <code>BOOTX64.EFI</code>）。</p>
<p>fallback 路徑是這條鏈的保命機制。NVRAM 的開機項可能丟失——QEMU 系的虛擬機尤其容易——這時靠 NVRAM 開機項的 EFISTUB 會開不了機，而 fallback 路徑上有 bootloader 的機器，韌體仍找得到。這就是「VM 上偏好獨立 bootloader」的根據。</p>
<p>這條鏈預設 Secure Boot 關閉。Secure Boot 開啟時，韌體會拒載沒簽章的 EFI 執行檔（kernel 或 bootloader），這也是「重開後找不到 kernel」的一類成因——最小 VM 安裝通常把它關掉，但實體機若開著就要處理簽章。</p>
<p>相關概念：<a href="/blog/linux/dotfile/knowledge-cards/initramfs/" data-link-title="initramfs" data-link-desc="看到 ESP 大小要算進 initramfs、或開機卡在掛載 root 之前、不知道 initramfs 是什麼時讀 — 開機初期掛真 root 之前的臨時根檔系統">initramfs</a>（bootloader 載入的對象之一）、<a href="/blog/linux/dotfile/knowledge-cards/partition-identification/" data-link-title="分區識別（PARTUUID / FSUUID）" data-link-desc="在 fstab 或 bootloader 設定要指定一個分區、不確定該用 PARTUUID、UUID 還是 /dev/sda1、或重格式化後系統開不了機時讀 — 分區的穩定識別方式">分區識別</a>。bootloader 選型的判讀，見 <a href="/blog/linux/install/install-option-decisions/" data-link-title="Linux 安裝選項判讀" data-link-desc="在 Linux 安裝程式面對 locale、網路、磁碟分割、檔案系統、bootloader 等選項、需要判斷依據而非靠預設值硬選時回來讀">Linux 安裝選項判讀</a>。</p>
]]></content:encoded></item><item><title>分區識別（PARTUUID / FSUUID）</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/partition-identification/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/partition-identification/</guid><description>&lt;p>分區識別是 &lt;code>fstab&lt;/code>（開機時決定哪個分區掛到哪的設定檔）與 bootloader 指涉某個分區時用的名字，它的選擇決定一件事：重開機或重格式化後，系統還找不找得到自己的分區。&lt;/p>
&lt;p>有三種識別方式，穩定性不同。PARTUUID 是寫在 GPT 分區表裡的 ID，綁在分區本身、跨重開機穩定，而且重新格式化檔案系統也不會變。FSUUID 是檔案系統 superblock（檔案系統開頭記錄自身中繼資料的區塊）裡的 UUID，綁在檔案系統上，所以一重新格式化就變，會讓引用它的 &lt;code>fstab&lt;/code> 失效。kernel 名稱（&lt;code>/dev/sda1&lt;/code>、&lt;code>/dev/vda1&lt;/code>）則隨偵測順序浮動，多接一顆磁碟就可能對調，最不穩。&lt;code>fstab&lt;/code> 還吃 &lt;code>LABEL=&lt;/code> / &lt;code>PARTLABEL=&lt;/code>（你自己給的標籤），穩定性看你維不維護那個標籤，跟前三種「系統生成」的識別不同層級，這裡不展開。&lt;/p>
&lt;p>穩定性排序是 PARTUUID 優於 FSUUID 優於 kernel 名稱。在 GPT 磁碟上用 PARTUUID，得到「綁分區、重格不變」的最穩識別。這也是為什麼安裝程式問「device name scheme」時，GPT 磁碟選 PARTUUID。&lt;/p>
&lt;p>理解這個差異，能解釋一類典型故障：重新格式化某個分區後機器開不了機，往往是因為 &lt;code>fstab&lt;/code> 或 bootloader 用了 FSUUID，而格式化讓那個 UUID 變了。&lt;/p>
&lt;p>相關概念：&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/" data-link-title="UEFI 開機鏈" data-link-desc="在 bootloader 選型（GRUB / EFISTUB / systemd-boot）卡住、或機器重開後找不到 kernel、需要理解韌體怎麼找到並載入系統時讀 — 韌體到 kernel 的交棒過程">UEFI 開機鏈&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/initramfs/" data-link-title="initramfs" data-link-desc="看到 ESP 大小要算進 initramfs、或開機卡在掛載 root 之前、不知道 initramfs 是什麼時讀 — 開機初期掛真 root 之前的臨時根檔系統">initramfs&lt;/a>。安裝時的識別方式選擇，見 &lt;a href="https://tarrragon.github.io/blog/linux/install/install-option-decisions/" data-link-title="Linux 安裝選項判讀" data-link-desc="在 Linux 安裝程式面對 locale、網路、磁碟分割、檔案系統、bootloader 等選項、需要判斷依據而非靠預設值硬選時回來讀">Linux 安裝選項判讀&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>分區識別是 <code>fstab</code>（開機時決定哪個分區掛到哪的設定檔）與 bootloader 指涉某個分區時用的名字，它的選擇決定一件事：重開機或重格式化後，系統還找不找得到自己的分區。</p>
<p>有三種識別方式，穩定性不同。PARTUUID 是寫在 GPT 分區表裡的 ID，綁在分區本身、跨重開機穩定，而且重新格式化檔案系統也不會變。FSUUID 是檔案系統 superblock（檔案系統開頭記錄自身中繼資料的區塊）裡的 UUID，綁在檔案系統上，所以一重新格式化就變，會讓引用它的 <code>fstab</code> 失效。kernel 名稱（<code>/dev/sda1</code>、<code>/dev/vda1</code>）則隨偵測順序浮動，多接一顆磁碟就可能對調，最不穩。<code>fstab</code> 還吃 <code>LABEL=</code> / <code>PARTLABEL=</code>（你自己給的標籤），穩定性看你維不維護那個標籤，跟前三種「系統生成」的識別不同層級，這裡不展開。</p>
<p>穩定性排序是 PARTUUID 優於 FSUUID 優於 kernel 名稱。在 GPT 磁碟上用 PARTUUID，得到「綁分區、重格不變」的最穩識別。這也是為什麼安裝程式問「device name scheme」時，GPT 磁碟選 PARTUUID。</p>
<p>理解這個差異，能解釋一類典型故障：重新格式化某個分區後機器開不了機，往往是因為 <code>fstab</code> 或 bootloader 用了 FSUUID，而格式化讓那個 UUID 變了。</p>
<p>相關概念：<a href="/blog/linux/dotfile/knowledge-cards/uefi-boot-chain/" data-link-title="UEFI 開機鏈" data-link-desc="在 bootloader 選型（GRUB / EFISTUB / systemd-boot）卡住、或機器重開後找不到 kernel、需要理解韌體怎麼找到並載入系統時讀 — 韌體到 kernel 的交棒過程">UEFI 開機鏈</a>、<a href="/blog/linux/dotfile/knowledge-cards/initramfs/" data-link-title="initramfs" data-link-desc="看到 ESP 大小要算進 initramfs、或開機卡在掛載 root 之前、不知道 initramfs 是什麼時讀 — 開機初期掛真 root 之前的臨時根檔系統">initramfs</a>。安裝時的識別方式選擇，見 <a href="/blog/linux/install/install-option-decisions/" data-link-title="Linux 安裝選項判讀" data-link-desc="在 Linux 安裝程式面對 locale、網路、磁碟分割、檔案系統、bootloader 等選項、需要判斷依據而非靠預設值硬選時回來讀">Linux 安裝選項判讀</a>。</p>
]]></content:encoded></item><item><title>LLM 寫 code 工程實務指南：從心智模型到應用架構</title><link>https://tarrragon.github.io/blog/llm/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/</guid><description>&lt;p>本指南的核心目標是把「LLM 在寫 code 工作流的完整工程地圖」拆成可決策、可實作、可期望管理的工程問題。範圍覆蓋四條讀者旅程：(1) 在自己機器跑本地 LLM 寫 code 的最短可行路徑（Mac 或 PC）、(2) 想懂 LLM 內部運作機制（數學 + 理論基礎）、(3) 想做 LLM 應用開發（RAG / agent / tool use / VLM / benchmarking / 靜態 deployment）、(4) 關心 LLM 工作流的安全議題（本地 dev 視角 + 靜態網站視角）。網路上的 LLM 文章常把推論框架、加速技巧、應用模式、安全議題混為一談；本指南先把這些名詞放回正確的層級、再回答各層的具體取捨。&lt;/p>
&lt;p>本指南預設讀者已經會用過雲端 LLM（ChatGPT、Claude）、熟悉終端機操作、想以工程視角理解 LLM。&lt;strong>寫 code 場景是主要使用例、但模組二 / 三 / 四 / 六多數章節跨場景通用&lt;/strong>：想懂 reasoning model / RAG / embedding model 內部、即使不裝本地 LLM 也能讀。硬體前提分兩條路線：Apple Silicon Mac（M1 ~ M4、統一記憶體）走模組一；Windows / Linux + 獨立 GPU（NVIDIA / AMD、獨立 VRAM + 系統 RAM）走模組五。文章不販賣 LLM 焦慮、也不誇大本地能取代雲端的程度；它的責任是給每條讀者旅程的最短可行路徑、並標出每個階段的取捨。&lt;/p>
&lt;p>模組零（心智模型）是所有讀者旅程的共同前置。模組一跟模組五是「裝本地 LLM」的兩條硬體路線、依平台選一條；想懂底層走模組二跟模組三（跟硬體無關、含 reasoning model / speculative decoding 等推論細節）；想看 LLM 作為系統元件走模組四（12 章涵蓋 RAG、tool use、agent、應用層協議、workflow、production resource、long context、embedding model、benchmarking、vision、靜態 deployment）；本地工作流跑穩想看安全議題走模組六（個人 dev 視角的供應鏈、伺服器綁定、tool use 權限、prompt injection、跨雲端邊界、production routing）。&lt;/p>
&lt;h2 id="教材邊界">教材邊界&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>類型&lt;/th>
 &lt;th>放在本指南&lt;/th>
 &lt;th>不放在本指南&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>心智模型&lt;/td>
 &lt;td>本地 vs 雲端的差異、為何 LLM 生字慢、三層架構（介面 / 伺服器 / 模型）、&lt;a href="https://tarrragon.github.io/blog/llm/00-foundations/openai-compatible-api/" data-link-title="0.3 OpenAI 相容 API" data-link-desc="為什麼幾乎所有本地 LLM 工具不用改就能切到本地：背後是同一套 API 形狀">OpenAI 相容 API&lt;/a>&lt;/td>
 &lt;td>雲端 GPU 租用、AGI 預測&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>術語澄清&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/00-foundations/mlx-mtp-omlx/" data-link-title="0.4 MLX / MTP / oMLX 的區別" data-link-desc="三個常被混為一談的術語：framework、加速技巧、特化 server，疊加而非互斥">MLX&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/00-foundations/mlx-mtp-omlx/" data-link-title="0.4 MLX / MTP / oMLX 的區別" data-link-desc="三個常被混為一談的術語：framework、加速技巧、特化 server，疊加而非互斥">MTP&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/00-foundations/mlx-mtp-omlx/" data-link-title="0.4 MLX / MTP / oMLX 的區別" data-link-desc="三個常被混為一談的術語：framework、加速技巧、特化 server，疊加而非互斥">oMLX&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/speculative-decoding/" data-link-title="Speculative Decoding" data-link-desc="用小模型猜未來 token、大模型並行驗證的加速技巧">speculative decoding&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/quantization/" data-link-title="Quantization" data-link-desc="用較少 bits 表示模型權重：壓縮記憶體佔用、加快生字速度，代價是少量品質衰減">量化&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/kv-cache/" data-link-title="KV Cache" data-link-desc="已處理 token 的 attention 中間結果暫存：避免重算、加速後續生成">KV cache&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/ttft/" data-link-title="TTFT" data-link-desc="Time To First Token：送出 prompt 到第一個 token 出現的等待時間">TTFT&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/moe-cpu-offload/" data-link-title="MoE CPU 卸載" data-link-desc="把 Mixture-of-Experts 模型不活躍的專家層權重放在系統 RAM、用到再走 PCIe 拉回 GPU、讓有限 VRAM 跑得了更大模型">MoE CPU 卸載&lt;/a>&lt;/td>
 &lt;td>post-training fine-tuning 細節&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Mac 硬體現實&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/00-foundations/hardware-memory-budget/" data-link-title="0.5 Apple Silicon 記憶體預算" data-link-desc="記憶體決定能跑什麼，Q4 量化下的可運作模型對照與系統保留">記憶體預算與模型大小&lt;/a>、量化選擇、首字延遲、風扇與功耗&lt;/td>
 &lt;td>雲端 GPU 租用、資料中心訓練&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>PC 硬體現實&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/05-discrete-gpu/vram-ram-budget/" data-link-title="5.0 VRAM &amp;#43; RAM 分層預算" data-link-desc="PC 獨立 GPU 場景的記憶體預算判讀：VRAM 是快的世界、RAM 是大的世界、PCIe 把兩個世界連起來">VRAM + RAM 分層預算&lt;/a>、MoE 專家層 CPU 卸載、KV cache 量化、PCIe 頻寬限制&lt;/td>
 &lt;td>多卡 NVLink、資料中心級分散式推論&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>本地推論伺服器&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/01-local-llm-services/ollama/" data-link-title="1.0 Ollama：主流推論伺服器" data-link-desc="一行 brew 裝完、ollama run 一鍵跑 Gemma 4 MTP、OpenAI 相容 API on localhost:11434">Ollama&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/01-local-llm-services/lm-studio/" data-link-title="1.1 LM Studio：GUI 探索模型" data-link-desc="GUI 取向的本地推論伺服器：內建模型瀏覽器、speculative decoding 設定面板、適合探索新模型">LM Studio&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/01-local-llm-services/llama-cpp/" data-link-title="1.2 llama.cpp：底層推論引擎" data-link-desc="GGUF 格式、量化、MTP 仍 beta；多數讀者不需要直接接觸，Ollama 已經包好">llama.cpp&lt;/a>（Mac + PC 通用）&lt;/td>
 &lt;td>vLLM、TGI、Triton 等資料中心級 inference server&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>編輯器整合&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/01-local-llm-services/vscode-continue-integration/" data-link-title="1.3 VS Code &amp;#43; Continue.dev 整合" data-link-desc="安裝 Continue 擴充套件、config.json 設定、Cmd&amp;#43;L 對話 / Cmd&amp;#43;I 行內編輯快捷鍵">Continue.dev + VS Code&lt;/a>、Cursor 對應關係&lt;/td>
 &lt;td>JetBrains 全套整合、Vim / Emacs 進階 plugin&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>模型挑選&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/01-local-llm-services/model-selection-priority/" data-link-title="1.4 寫 code 場景的模型選型優先順序" data-link-desc="Gemma 4 31B MTP → Qwen3-Coder 30B → Qwen3 14B → gpt-oss 20B 的取捨與適用情境">coding 場景的模型優先順序&lt;/a>、量化等級對體感影響&lt;/td>
 &lt;td>benchmark 跑分方法論的完整推導&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>期望管理&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/01-local-llm-services/expectation-management/" data-link-title="1.5 期望管理：本地 LLM 的擅長領域與分工" data-link-desc="本地 LLM 是免費的初階 pair programmer：辨識它的擅長領域、跟雲端旗艦做結構性分工">本地 LLM 的擅長領域與分工&lt;/a>、混用雲端的時機&lt;/td>
 &lt;td>LLM 通用能力評估、AGI 預測&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>數學基礎&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/02-math-foundations/linear-algebra-for-llm/" data-link-title="2.0 線性代數：向量、矩陣、空間" data-link-desc="LLM 內部運算的基底：向量、矩陣、向量空間、內積、norm、矩陣乘法的角色">線性代數&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/02-math-foundations/probability-and-information/" data-link-title="2.1 機率與資訊論" data-link-desc="LLM 輸出的本質是機率分佈：softmax、cross-entropy、KL divergence、perplexity 在訓練與推論中的角色">機率與資訊論&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/02-math-foundations/calculus-and-optimization/" data-link-title="2.2 微積分與最佳化" data-link-desc="從 gradient、chain rule 到 SGD / Adam：LLM 訓練如何更新數十億參數">最佳化&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/02-math-foundations/numerical-precision/" data-link-title="2.3 數值精度與量化的數學依據" data-link-desc="fp32 / bf16 / fp16 / int8 / int4 的差別、量化能省哪些 bits、品質衰減從哪裡來">數值精度&lt;/a> 在 LLM 中的角色&lt;/td>
 &lt;td>完整數學證明、測度論等屬於數學系範圍的主題&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>理論基礎&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/neural-network-basics/" data-link-title="3.0 神經網路基礎" data-link-desc="從單一 neuron 到 multi-layer：weights、activation function、forward / backward pass 的角色">神經網路&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/embedding-spaces/" data-link-title="3.1 Embedding 空間" data-link-desc="token 怎麼變成向量、為什麼相似 token 在向量空間中靠近、embedding 是怎麼學出來的">embedding&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/attention-mechanism/" data-link-title="3.2 Attention 機制" data-link-desc="Query / Key / Value、scaled dot-product attention、multi-head attention：Transformer 的核心運算">attention&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/transformer-architecture/" data-link-title="3.3 Transformer 架構細節" data-link-desc="Decoder-only 結構、Transformer block、positional encoding、layer norm、residual stream">Transformer&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/training-pipeline/" data-link-title="3.4 訓練流程：pre-train → SFT → RLHF" data-link-desc="LLM 的三階段訓練：預訓練、指令微調、人類反饋強化學習；各階段目標與最新替代方案">訓練流程&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/sampling-and-decoding/" data-link-title="3.5 Sampling 與 Decoding 策略" data-link-desc="Greedy、beam search、top-k、top-p、temperature、min-p：模型輸出後怎麼挑下一個 token">sampling&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/tokenization-algorithms/" data-link-title="3.6 Tokenization：BPE、SentencePiece、Tiktoken" data-link-desc="把文字切成 token 的算法：為什麼不同模型切出不同 token 數、tokenizer 選擇對能力的影響">tokenization&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/cross-language-tokenization/" data-link-title="3.7 跨語言場景的 tokenizer 與訓練分佈原理" data-link-desc="為什麼模型對不同語言表現不一致：tokenizer &amp;#43; 訓練資料分佈雙因素、語言選擇取捨">跨語言原理&lt;/a>&lt;/td>
 &lt;td>多模態擴展、最新研究細節交給 Stanford CS25&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>應用層原理&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &amp;#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">RAG&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">Tool use&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">Agent 架構&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">應用層協議&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">Workflow 編排&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/production-resource-planning/" data-link-title="4.9 Production 部署的資源評估原理" data-link-desc="從本地單 user 到 production multi-tenant：concurrent users、cost model、observability、SLA、capacity planning 的設計取捨">Production resource&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/artifact-management/" data-link-title="4.10 衍生產物管理原理：什麼進 git、什麼不該" data-link-desc="LLM 應用的 source / derived / external 三類產物對應 git / build cache / registry、與 production 部署的 reproducibility / cost / share 取捨">Artifact 管理&lt;/a>&lt;/td>
 &lt;td>具體 framework 教學（LangChain / LlamaIndex）、prompt engineering&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>進階理論&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/reasoning-models/" data-link-title="3.8 Reasoning models：test-time compute paradigm" data-link-desc="Chain-of-thought 從 prompting 技巧演化成訓練 paradigm、reasoning model 的內部運作、本地可跑的選項與適用任務">Reasoning models&lt;/a>（o1 / R1 / QwQ 風格）、&lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/speculative-decoding-internals/" data-link-title="3.9 Speculative decoding 內部：drafter / 驗證 / 加速上限" data-link-desc="speculative decoding 的演算法細節、drafter 跟 target 怎麼配對、acceptance rate 怎麼決定實際加速、MTP 跟 EAGLE 等變體">Speculative decoding 內部&lt;/a>（drafter / MTP / EAGLE）&lt;/td>
 &lt;td>完整 paper 推導、最新研究 frontier&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>進階應用&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/long-context-engineering/" data-link-title="4.11 Long context engineering" data-link-desc="128K / 1M context 模型怎麼用：claimed vs effective context、lost-in-the-middle、context 設計策略、Long context vs RAG 取捨">Long context engineering&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &amp;#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">Embedding model 內部&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">Benchmarking&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/vision-in-coding-workflow/" data-link-title="4.15 Vision in coding workflow：本地 VLM 怎麼接寫 code" data-link-desc="VLM 在 coding 工作流的 use cases、本地 VLM 選型、跟雲端 VLM 的分工、Continue.dev / Ollama 整合現狀">Vision in coding&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/static-and-serverless-rag-deployment/" data-link-title="4.16 靜態 / serverless RAG deployment：架構選擇與資安取捨" data-link-desc="沒 backend 的場景怎麼做 RAG：四種 deployment 方案、API key 暴露問題、CORS / abuse / 第三方信任、跟模組六的 routing">靜態 / serverless RAG deployment&lt;/a>&lt;/td>
 &lt;td>完整 LangChain / LlamaIndex 教學&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Fine-tuning&lt;/td>
 &lt;td>原理（&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/lora/" data-link-title="LoRA" data-link-desc="Low-Rank Adaptation：凍住原模型權重、只訓兩個小矩陣的 parameter-efficient fine-tuning">LoRA&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/qlora/" data-link-title="QLoRA" data-link-desc="把 base model 量化到 4-bit &amp;#43; LoRA fine-tune 的組合、消費級 GPU 也能 fine-tune 大模型">QLoRA&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/catastrophic-forgetting/" data-link-title="Catastrophic Forgetting" data-link-desc="Fine-tune 模型時、新訓練資料覆蓋掉原本學到的能力的現象、LoRA / 資料 mixing 是主要緩解">catastrophic forgetting&lt;/a>）+ &lt;a href="https://tarrragon.github.io/blog/llm/01-local-llm-services/hands-on/local-fine-tuning/" data-link-title="Hands-on：用 QLoRA 在本機 fine-tune coding 模型" data-link-desc="Apple Silicon Mac / PC 獨立 GPU 上跑 QLoRA fine-tune 的完整流程：環境、資料、訓練、evaluation、合併、部署到 Ollama">本機 hands-on&lt;/a>&lt;/td>
 &lt;td>完整資料工程、large-scale distributed fine-tune&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>隱私 / 安全&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/00-foundations/privacy-data-flow/" data-link-title="0.7 隱私 / 資安的資料流原理" data-link-desc="從「位置」到「資料流」的思考升級：信任邊界、合約模型、零信任原則套用到 LLM 工作流">隱私資料流&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">本地 dev 安全模組&lt;/a>（供應鏈 / 伺服器綁定 / tool use / prompt injection / 跨雲端邊界 / production routing）、&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/static-and-serverless-rag-deployment/" data-link-title="4.16 靜態 / serverless RAG deployment：架構選擇與資安取捨" data-link-desc="沒 backend 的場景怎麼做 RAG：四種 deployment 方案、API key 暴露問題、CORS / abuse / 第三方信任、跟模組六的 routing">靜態網站 RAG 資安&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/01-local-llm-services/troubleshooting/" data-link-title="1.7 排錯方法論：用三層架構做故障定位" data-link-desc="故障定位的分層思考、症狀到層級的對應反射、log 在三層的角色差異、最小可重現的縮減策略">排錯方法論&lt;/a>&lt;/td>
 &lt;td>企業合規逐條檢核、SOC 2 / HIPAA 流程&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>進一步學習&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/02-math-foundations/going-deeper-math/" data-link-title="2.4 想學更深：推薦公開課程" data-link-desc="MIT、Stanford、Harvard 等公開課程：數學基礎跟 LLM 預備知識的完整學習路線">數學公開課推薦&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/going-deeper-theory/" data-link-title="3.11 想學更深：推薦公開課程" data-link-desc="Karpathy、Stanford CS224N / CS25 / CS336、DeepLearning.AI、Hugging Face：LLM 理論深入學習的完整路線">LLM 理論公開課推薦&lt;/a>&lt;/td>
 &lt;td>（交給推薦的課程跟書籍）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="學習路線">學習路線&lt;/h2>
&lt;p>本指南分成七個模組加一組前置卡片（111 張）。讀者依目的選讀、不需要從頭到尾全讀：&lt;/p></description><content:encoded><![CDATA[<p>本指南的核心目標是把「LLM 在寫 code 工作流的完整工程地圖」拆成可決策、可實作、可期望管理的工程問題。範圍覆蓋四條讀者旅程：(1) 在自己機器跑本地 LLM 寫 code 的最短可行路徑（Mac 或 PC）、(2) 想懂 LLM 內部運作機制（數學 + 理論基礎）、(3) 想做 LLM 應用開發（RAG / agent / tool use / VLM / benchmarking / 靜態 deployment）、(4) 關心 LLM 工作流的安全議題（本地 dev 視角 + 靜態網站視角）。網路上的 LLM 文章常把推論框架、加速技巧、應用模式、安全議題混為一談；本指南先把這些名詞放回正確的層級、再回答各層的具體取捨。</p>
<p>本指南預設讀者已經會用過雲端 LLM（ChatGPT、Claude）、熟悉終端機操作、想以工程視角理解 LLM。<strong>寫 code 場景是主要使用例、但模組二 / 三 / 四 / 六多數章節跨場景通用</strong>：想懂 reasoning model / RAG / embedding model 內部、即使不裝本地 LLM 也能讀。硬體前提分兩條路線：Apple Silicon Mac（M1 ~ M4、統一記憶體）走模組一；Windows / Linux + 獨立 GPU（NVIDIA / AMD、獨立 VRAM + 系統 RAM）走模組五。文章不販賣 LLM 焦慮、也不誇大本地能取代雲端的程度；它的責任是給每條讀者旅程的最短可行路徑、並標出每個階段的取捨。</p>
<p>模組零（心智模型）是所有讀者旅程的共同前置。模組一跟模組五是「裝本地 LLM」的兩條硬體路線、依平台選一條；想懂底層走模組二跟模組三（跟硬體無關、含 reasoning model / speculative decoding 等推論細節）；想看 LLM 作為系統元件走模組四（12 章涵蓋 RAG、tool use、agent、應用層協議、workflow、production resource、long context、embedding model、benchmarking、vision、靜態 deployment）；本地工作流跑穩想看安全議題走模組六（個人 dev 視角的供應鏈、伺服器綁定、tool use 權限、prompt injection、跨雲端邊界、production routing）。</p>
<h2 id="教材邊界">教材邊界</h2>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>放在本指南</th>
          <th>不放在本指南</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>心智模型</td>
          <td>本地 vs 雲端的差異、為何 LLM 生字慢、三層架構（介面 / 伺服器 / 模型）、<a href="/blog/llm/00-foundations/openai-compatible-api/" data-link-title="0.3 OpenAI 相容 API" data-link-desc="為什麼幾乎所有本地 LLM 工具不用改就能切到本地：背後是同一套 API 形狀">OpenAI 相容 API</a></td>
          <td>雲端 GPU 租用、AGI 預測</td>
      </tr>
      <tr>
          <td>術語澄清</td>
          <td><a href="/blog/llm/00-foundations/mlx-mtp-omlx/" data-link-title="0.4 MLX / MTP / oMLX 的區別" data-link-desc="三個常被混為一談的術語：framework、加速技巧、特化 server，疊加而非互斥">MLX</a>、<a href="/blog/llm/00-foundations/mlx-mtp-omlx/" data-link-title="0.4 MLX / MTP / oMLX 的區別" data-link-desc="三個常被混為一談的術語：framework、加速技巧、特化 server，疊加而非互斥">MTP</a>、<a href="/blog/llm/00-foundations/mlx-mtp-omlx/" data-link-title="0.4 MLX / MTP / oMLX 的區別" data-link-desc="三個常被混為一談的術語：framework、加速技巧、特化 server，疊加而非互斥">oMLX</a>、<a href="/blog/llm/knowledge-cards/speculative-decoding/" data-link-title="Speculative Decoding" data-link-desc="用小模型猜未來 token、大模型並行驗證的加速技巧">speculative decoding</a>、<a href="/blog/llm/knowledge-cards/quantization/" data-link-title="Quantization" data-link-desc="用較少 bits 表示模型權重：壓縮記憶體佔用、加快生字速度，代價是少量品質衰減">量化</a>、<a href="/blog/llm/knowledge-cards/kv-cache/" data-link-title="KV Cache" data-link-desc="已處理 token 的 attention 中間結果暫存：避免重算、加速後續生成">KV cache</a>、<a href="/blog/llm/knowledge-cards/ttft/" data-link-title="TTFT" data-link-desc="Time To First Token：送出 prompt 到第一個 token 出現的等待時間">TTFT</a>、<a href="/blog/llm/knowledge-cards/moe-cpu-offload/" data-link-title="MoE CPU 卸載" data-link-desc="把 Mixture-of-Experts 模型不活躍的專家層權重放在系統 RAM、用到再走 PCIe 拉回 GPU、讓有限 VRAM 跑得了更大模型">MoE CPU 卸載</a></td>
          <td>post-training fine-tuning 細節</td>
      </tr>
      <tr>
          <td>Mac 硬體現實</td>
          <td><a href="/blog/llm/00-foundations/hardware-memory-budget/" data-link-title="0.5 Apple Silicon 記憶體預算" data-link-desc="記憶體決定能跑什麼，Q4 量化下的可運作模型對照與系統保留">記憶體預算與模型大小</a>、量化選擇、首字延遲、風扇與功耗</td>
          <td>雲端 GPU 租用、資料中心訓練</td>
      </tr>
      <tr>
          <td>PC 硬體現實</td>
          <td><a href="/blog/llm/05-discrete-gpu/vram-ram-budget/" data-link-title="5.0 VRAM &#43; RAM 分層預算" data-link-desc="PC 獨立 GPU 場景的記憶體預算判讀：VRAM 是快的世界、RAM 是大的世界、PCIe 把兩個世界連起來">VRAM + RAM 分層預算</a>、MoE 專家層 CPU 卸載、KV cache 量化、PCIe 頻寬限制</td>
          <td>多卡 NVLink、資料中心級分散式推論</td>
      </tr>
      <tr>
          <td>本地推論伺服器</td>
          <td><a href="/blog/llm/01-local-llm-services/ollama/" data-link-title="1.0 Ollama：主流推論伺服器" data-link-desc="一行 brew 裝完、ollama run 一鍵跑 Gemma 4 MTP、OpenAI 相容 API on localhost:11434">Ollama</a>、<a href="/blog/llm/01-local-llm-services/lm-studio/" data-link-title="1.1 LM Studio：GUI 探索模型" data-link-desc="GUI 取向的本地推論伺服器：內建模型瀏覽器、speculative decoding 設定面板、適合探索新模型">LM Studio</a>、<a href="/blog/llm/01-local-llm-services/llama-cpp/" data-link-title="1.2 llama.cpp：底層推論引擎" data-link-desc="GGUF 格式、量化、MTP 仍 beta；多數讀者不需要直接接觸，Ollama 已經包好">llama.cpp</a>（Mac + PC 通用）</td>
          <td>vLLM、TGI、Triton 等資料中心級 inference server</td>
      </tr>
      <tr>
          <td>編輯器整合</td>
          <td><a href="/blog/llm/01-local-llm-services/vscode-continue-integration/" data-link-title="1.3 VS Code &#43; Continue.dev 整合" data-link-desc="安裝 Continue 擴充套件、config.json 設定、Cmd&#43;L 對話 / Cmd&#43;I 行內編輯快捷鍵">Continue.dev + VS Code</a>、Cursor 對應關係</td>
          <td>JetBrains 全套整合、Vim / Emacs 進階 plugin</td>
      </tr>
      <tr>
          <td>模型挑選</td>
          <td><a href="/blog/llm/01-local-llm-services/model-selection-priority/" data-link-title="1.4 寫 code 場景的模型選型優先順序" data-link-desc="Gemma 4 31B MTP → Qwen3-Coder 30B → Qwen3 14B → gpt-oss 20B 的取捨與適用情境">coding 場景的模型優先順序</a>、量化等級對體感影響</td>
          <td>benchmark 跑分方法論的完整推導</td>
      </tr>
      <tr>
          <td>期望管理</td>
          <td><a href="/blog/llm/01-local-llm-services/expectation-management/" data-link-title="1.5 期望管理：本地 LLM 的擅長領域與分工" data-link-desc="本地 LLM 是免費的初階 pair programmer：辨識它的擅長領域、跟雲端旗艦做結構性分工">本地 LLM 的擅長領域與分工</a>、混用雲端的時機</td>
          <td>LLM 通用能力評估、AGI 預測</td>
      </tr>
      <tr>
          <td>數學基礎</td>
          <td><a href="/blog/llm/02-math-foundations/linear-algebra-for-llm/" data-link-title="2.0 線性代數：向量、矩陣、空間" data-link-desc="LLM 內部運算的基底：向量、矩陣、向量空間、內積、norm、矩陣乘法的角色">線性代數</a>、<a href="/blog/llm/02-math-foundations/probability-and-information/" data-link-title="2.1 機率與資訊論" data-link-desc="LLM 輸出的本質是機率分佈：softmax、cross-entropy、KL divergence、perplexity 在訓練與推論中的角色">機率與資訊論</a>、<a href="/blog/llm/02-math-foundations/calculus-and-optimization/" data-link-title="2.2 微積分與最佳化" data-link-desc="從 gradient、chain rule 到 SGD / Adam：LLM 訓練如何更新數十億參數">最佳化</a>、<a href="/blog/llm/02-math-foundations/numerical-precision/" data-link-title="2.3 數值精度與量化的數學依據" data-link-desc="fp32 / bf16 / fp16 / int8 / int4 的差別、量化能省哪些 bits、品質衰減從哪裡來">數值精度</a> 在 LLM 中的角色</td>
          <td>完整數學證明、測度論等屬於數學系範圍的主題</td>
      </tr>
      <tr>
          <td>理論基礎</td>
          <td><a href="/blog/llm/03-theoretical-foundations/neural-network-basics/" data-link-title="3.0 神經網路基礎" data-link-desc="從單一 neuron 到 multi-layer：weights、activation function、forward / backward pass 的角色">神經網路</a>、<a href="/blog/llm/03-theoretical-foundations/embedding-spaces/" data-link-title="3.1 Embedding 空間" data-link-desc="token 怎麼變成向量、為什麼相似 token 在向量空間中靠近、embedding 是怎麼學出來的">embedding</a>、<a href="/blog/llm/03-theoretical-foundations/attention-mechanism/" data-link-title="3.2 Attention 機制" data-link-desc="Query / Key / Value、scaled dot-product attention、multi-head attention：Transformer 的核心運算">attention</a>、<a href="/blog/llm/03-theoretical-foundations/transformer-architecture/" data-link-title="3.3 Transformer 架構細節" data-link-desc="Decoder-only 結構、Transformer block、positional encoding、layer norm、residual stream">Transformer</a>、<a href="/blog/llm/03-theoretical-foundations/training-pipeline/" data-link-title="3.4 訓練流程：pre-train → SFT → RLHF" data-link-desc="LLM 的三階段訓練：預訓練、指令微調、人類反饋強化學習；各階段目標與最新替代方案">訓練流程</a>、<a href="/blog/llm/03-theoretical-foundations/sampling-and-decoding/" data-link-title="3.5 Sampling 與 Decoding 策略" data-link-desc="Greedy、beam search、top-k、top-p、temperature、min-p：模型輸出後怎麼挑下一個 token">sampling</a>、<a href="/blog/llm/03-theoretical-foundations/tokenization-algorithms/" data-link-title="3.6 Tokenization：BPE、SentencePiece、Tiktoken" data-link-desc="把文字切成 token 的算法：為什麼不同模型切出不同 token 數、tokenizer 選擇對能力的影響">tokenization</a>、<a href="/blog/llm/03-theoretical-foundations/cross-language-tokenization/" data-link-title="3.7 跨語言場景的 tokenizer 與訓練分佈原理" data-link-desc="為什麼模型對不同語言表現不一致：tokenizer &#43; 訓練資料分佈雙因素、語言選擇取捨">跨語言原理</a></td>
          <td>多模態擴展、最新研究細節交給 Stanford CS25</td>
      </tr>
      <tr>
          <td>應用層原理</td>
          <td><a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">RAG</a>、<a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">Tool use</a>、<a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">Agent 架構</a>、<a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">應用層協議</a>、<a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">Workflow 編排</a>、<a href="/blog/llm/04-applications/production-resource-planning/" data-link-title="4.9 Production 部署的資源評估原理" data-link-desc="從本地單 user 到 production multi-tenant：concurrent users、cost model、observability、SLA、capacity planning 的設計取捨">Production resource</a>、<a href="/blog/llm/04-applications/artifact-management/" data-link-title="4.10 衍生產物管理原理：什麼進 git、什麼不該" data-link-desc="LLM 應用的 source / derived / external 三類產物對應 git / build cache / registry、與 production 部署的 reproducibility / cost / share 取捨">Artifact 管理</a></td>
          <td>具體 framework 教學（LangChain / LlamaIndex）、prompt engineering</td>
      </tr>
      <tr>
          <td>進階理論</td>
          <td><a href="/blog/llm/03-theoretical-foundations/reasoning-models/" data-link-title="3.8 Reasoning models：test-time compute paradigm" data-link-desc="Chain-of-thought 從 prompting 技巧演化成訓練 paradigm、reasoning model 的內部運作、本地可跑的選項與適用任務">Reasoning models</a>（o1 / R1 / QwQ 風格）、<a href="/blog/llm/03-theoretical-foundations/speculative-decoding-internals/" data-link-title="3.9 Speculative decoding 內部：drafter / 驗證 / 加速上限" data-link-desc="speculative decoding 的演算法細節、drafter 跟 target 怎麼配對、acceptance rate 怎麼決定實際加速、MTP 跟 EAGLE 等變體">Speculative decoding 內部</a>（drafter / MTP / EAGLE）</td>
          <td>完整 paper 推導、最新研究 frontier</td>
      </tr>
      <tr>
          <td>進階應用</td>
          <td><a href="/blog/llm/04-applications/long-context-engineering/" data-link-title="4.11 Long context engineering" data-link-desc="128K / 1M context 模型怎麼用：claimed vs effective context、lost-in-the-middle、context 設計策略、Long context vs RAG 取捨">Long context engineering</a>、<a href="/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">Embedding model 內部</a>、<a href="/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">Benchmarking</a>、<a href="/blog/llm/04-applications/vision-in-coding-workflow/" data-link-title="4.15 Vision in coding workflow：本地 VLM 怎麼接寫 code" data-link-desc="VLM 在 coding 工作流的 use cases、本地 VLM 選型、跟雲端 VLM 的分工、Continue.dev / Ollama 整合現狀">Vision in coding</a>、<a href="/blog/llm/04-applications/static-and-serverless-rag-deployment/" data-link-title="4.16 靜態 / serverless RAG deployment：架構選擇與資安取捨" data-link-desc="沒 backend 的場景怎麼做 RAG：四種 deployment 方案、API key 暴露問題、CORS / abuse / 第三方信任、跟模組六的 routing">靜態 / serverless RAG deployment</a></td>
          <td>完整 LangChain / LlamaIndex 教學</td>
      </tr>
      <tr>
          <td>Fine-tuning</td>
          <td>原理（<a href="/blog/llm/knowledge-cards/lora/" data-link-title="LoRA" data-link-desc="Low-Rank Adaptation：凍住原模型權重、只訓兩個小矩陣的 parameter-efficient fine-tuning">LoRA</a> / <a href="/blog/llm/knowledge-cards/qlora/" data-link-title="QLoRA" data-link-desc="把 base model 量化到 4-bit &#43; LoRA fine-tune 的組合、消費級 GPU 也能 fine-tune 大模型">QLoRA</a> / <a href="/blog/llm/knowledge-cards/catastrophic-forgetting/" data-link-title="Catastrophic Forgetting" data-link-desc="Fine-tune 模型時、新訓練資料覆蓋掉原本學到的能力的現象、LoRA / 資料 mixing 是主要緩解">catastrophic forgetting</a>）+ <a href="/blog/llm/01-local-llm-services/hands-on/local-fine-tuning/" data-link-title="Hands-on：用 QLoRA 在本機 fine-tune coding 模型" data-link-desc="Apple Silicon Mac / PC 獨立 GPU 上跑 QLoRA fine-tune 的完整流程：環境、資料、訓練、evaluation、合併、部署到 Ollama">本機 hands-on</a></td>
          <td>完整資料工程、large-scale distributed fine-tune</td>
      </tr>
      <tr>
          <td>隱私 / 安全</td>
          <td><a href="/blog/llm/00-foundations/privacy-data-flow/" data-link-title="0.7 隱私 / 資安的資料流原理" data-link-desc="從「位置」到「資料流」的思考升級：信任邊界、合約模型、零信任原則套用到 LLM 工作流">隱私資料流</a>、<a href="/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">本地 dev 安全模組</a>（供應鏈 / 伺服器綁定 / tool use / prompt injection / 跨雲端邊界 / production routing）、<a href="/blog/llm/04-applications/static-and-serverless-rag-deployment/" data-link-title="4.16 靜態 / serverless RAG deployment：架構選擇與資安取捨" data-link-desc="沒 backend 的場景怎麼做 RAG：四種 deployment 方案、API key 暴露問題、CORS / abuse / 第三方信任、跟模組六的 routing">靜態網站 RAG 資安</a>、<a href="/blog/llm/01-local-llm-services/troubleshooting/" data-link-title="1.7 排錯方法論：用三層架構做故障定位" data-link-desc="故障定位的分層思考、症狀到層級的對應反射、log 在三層的角色差異、最小可重現的縮減策略">排錯方法論</a></td>
          <td>企業合規逐條檢核、SOC 2 / HIPAA 流程</td>
      </tr>
      <tr>
          <td>進一步學習</td>
          <td><a href="/blog/llm/02-math-foundations/going-deeper-math/" data-link-title="2.4 想學更深：推薦公開課程" data-link-desc="MIT、Stanford、Harvard 等公開課程：數學基礎跟 LLM 預備知識的完整學習路線">數學公開課推薦</a>、<a href="/blog/llm/03-theoretical-foundations/going-deeper-theory/" data-link-title="3.11 想學更深：推薦公開課程" data-link-desc="Karpathy、Stanford CS224N / CS25 / CS336、DeepLearning.AI、Hugging Face：LLM 理論深入學習的完整路線">LLM 理論公開課推薦</a></td>
          <td>（交給推薦的課程跟書籍）</td>
      </tr>
  </tbody>
</table>
<h2 id="學習路線">學習路線</h2>
<p>本指南分成七個模組加一組前置卡片（111 張）。讀者依目的選讀、不需要從頭到尾全讀：</p>
<ul>
<li><strong>想用 Apple Silicon Mac 裝本地 LLM 寫 code</strong>：讀模組零 + 模組一（最短路徑）</li>
<li><strong>想用 Windows / Linux + 獨立 GPU 裝</strong>：讀模組零 + 模組五</li>
<li><strong>想懂 LLM 內部原理</strong>：模組二（數學） + 模組三（理論、含 reasoning models / speculative decoding）— 跟硬體無關</li>
<li><strong>想做 LLM 應用開發（含 RAG / agent / VLM / 靜態 deployment）</strong>：模組四（12 章、跨工具世代不變的原理）— 跟硬體無關</li>
<li><strong>想懂本地工作流的安全議題</strong>：模組一 / 五跑穩後接模組六（個人 dev 視角）</li>
<li><strong>想選 RAG 的 storage 方案（pickle / vector DB / hosted SaaS）</strong>：直接看 <a href="/blog/llm/04-applications/vector-storage-engineering/" data-link-title="4.22 RAG storage 工程：從 pickle 到 vector database 的選型判讀" data-link-desc="RAG storage backend 選型：規模到哪個階段該從 in-memory 升級到 vector DB、dependency chain 如何收窄選項">4.22 RAG storage 工程</a></li>
<li><strong>想在靜態網站加 RAG / 智能搜尋</strong>：直接看 <a href="/blog/llm/04-applications/static-and-serverless-rag-deployment/" data-link-title="4.16 靜態 / serverless RAG deployment：架構選擇與資安取捨" data-link-desc="沒 backend 的場景怎麼做 RAG：四種 deployment 方案、API key 暴露問題、CORS / abuse / 第三方信任、跟模組六的 routing">4.16 靜態 / serverless RAG deployment</a></li>
<li><strong>想在本機 fine-tune 模型</strong>：模組三 3.4 訓練流程原理 → <a href="/blog/llm/01-local-llm-services/hands-on/local-fine-tuning/" data-link-title="Hands-on：用 QLoRA 在本機 fine-tune coding 模型" data-link-desc="Apple Silicon Mac / PC 獨立 GPU 上跑 QLoRA fine-tune 的完整流程：環境、資料、訓練、evaluation、合併、部署到 Ollama">本機 QLoRA hands-on</a></li>
<li><strong>想跟最新進展接軌</strong>：讀完模組後進推薦的公開課程跟 paper（模組二 2.4 + 模組三 3.10）</li>
</ul>
<h3 id="前置知識卡片"><a href="/blog/llm/knowledge-cards/" data-link-title="Knowledge Cards" data-link-desc="用原子化卡片整理本地 LLM 寫 code 場景所需的概念詞彙">前置知識卡片</a></h3>
<p>用原子化卡片整理 <a href="/blog/llm/knowledge-cards/token/" data-link-title="Token" data-link-desc="LLM 處理文字時的最小單位：介於字元與單字之間">token</a>、<a href="/blog/llm/knowledge-cards/autoregressive/" data-link-title="Autoregressive" data-link-desc="LLM 一次生成一個 token、把已生成內容作為下一次輸入的架構">自回歸</a>、<a href="/blog/llm/knowledge-cards/kv-cache/" data-link-title="KV Cache" data-link-desc="已處理 token 的 attention 中間結果暫存：避免重算、加速後續生成">KV cache</a>、<a href="/blog/llm/knowledge-cards/quantization/" data-link-title="Quantization" data-link-desc="用較少 bits 表示模型權重：壓縮記憶體佔用、加快生字速度，代價是少量品質衰減">量化</a>、<a href="/blog/llm/knowledge-cards/speculative-decoding/" data-link-title="Speculative Decoding" data-link-desc="用小模型猜未來 token、大模型並行驗證的加速技巧">speculative decoding</a>、<a href="/blog/llm/knowledge-cards/mtp/" data-link-title="Multi-Token Prediction (MTP)" data-link-desc="Google 為 Gemma 系列釋出的 speculative decoding 工程化實作">MTP</a>、<a href="/blog/llm/knowledge-cards/mlx/" data-link-title="MLX" data-link-desc="Apple 釋出的 Apple Silicon 數值運算 framework：類似 PyTorch / JAX 的 Mac 對應物">MLX</a>、<a href="/blog/llm/knowledge-cards/inference-server/" data-link-title="Inference Server" data-link-desc="載入模型權重、處理 prompt、產生 token 的常駐 process">推論伺服器</a>、<a href="/blog/llm/knowledge-cards/openai-compatible-api/" data-link-title="OpenAI 相容 API" data-link-desc="本地推論伺服器跟雲端 OpenAI 共用的 API 形狀標準">OpenAI 相容 API</a>、<a href="/blog/llm/knowledge-cards/memory-bandwidth/" data-link-title="Memory Bandwidth" data-link-desc="記憶體每秒能讀寫多少 bytes：決定本地 LLM 生字速度的真正瓶頸">memory bandwidth</a>、<a href="/blog/llm/knowledge-cards/unified-memory/" data-link-title="Unified Memory Architecture" data-link-desc="Apple Silicon 讓 CPU / GPU / NE 共用同一塊記憶體：跑大模型的優勢來源">統一記憶體</a>、<a href="/blog/llm/knowledge-cards/ttft/" data-link-title="TTFT" data-link-desc="Time To First Token：送出 prompt 到第一個 token 出現的等待時間">TTFT</a>、<a href="/blog/llm/knowledge-cards/prefill/" data-link-title="Prefill" data-link-desc="Prompt 首次處理時的計算階段：把整段輸入跑過模型、產生 KV cache">prefill</a>、<a href="/blog/llm/knowledge-cards/context-window/" data-link-title="Context Window" data-link-desc="模型一次能處理的最大 token 數量：prompt 加生成的總和上限">context window</a>、<a href="/blog/llm/knowledge-cards/transformer/" data-link-title="Transformer" data-link-desc="寫 code 用的 LLM 神經網路架構：基於 attention 機制、自回歸生成 token">Transformer</a>、<a href="/blog/llm/knowledge-cards/diffusion/" data-link-title="Diffusion" data-link-desc="產圖用的生成式 AI 架構：跟寫 code 用的 Transformer 是不同路線">Diffusion</a> 等核心概念。章節文章專注情境推導、術語背景交由卡片維持一致。</p>
<h3 id="模組零基礎知識與心智模型"><a href="/blog/llm/00-foundations/" data-link-title="模組零：基礎知識與心智模型" data-link-desc="建立本地 LLM 的心智模型、釐清 MLX / MTP / oMLX 等常被混淆的術語、Apple Silicon 記憶體現實">模組零：基礎知識與心智模型</a></h3>
<p>整理本地 vs 雲端 LLM 的差異、自回歸架構與記憶體頻寬瓶頸、介面 / 伺服器 / 模型三層心智模型、OpenAI 相容 API 為何重要、MLX / MTP / oMLX 三個容易搞混的術語、Apple Silicon Mac 記憶體與模型大小的對應關係、判讀本地 LLM 資訊的五個框架。</p>
<h3 id="模組一本地-llm-服務的安裝與應用"><a href="/blog/llm/01-local-llm-services/" data-link-title="模組一：本地 LLM 服務的安裝與應用" data-link-desc="Ollama、LM Studio、llama.cpp 的安裝與差異、VS Code &#43; Continue.dev 整合、模型選型與期望管理">模組一：本地 LLM 服務的安裝與應用</a></h3>
<p>整理 Ollama、LM Studio、llama.cpp 三個主流推論伺服器的現況差異與安裝路徑、用 Continue.dev 把本地 LLM 接到 VS Code 的完整步驟、寫 code 場景下模型選型的優先順序、本地模型的期望管理、想進一步玩 coding agent、Web UI、產圖時的延伸方向。</p>
<h3 id="模組二llm-的數學基礎"><a href="/blog/llm/02-math-foundations/" data-link-title="模組二：LLM 的數學基礎" data-link-desc="整理 LLM 推論背後需要理解的線性代數、機率與資訊論、最佳化、數值精度等數學概念">模組二：LLM 的數學基礎</a></h3>
<p>整理 LLM 推論背後的數學工具：<a href="/blog/llm/02-math-foundations/linear-algebra-for-llm/" data-link-title="2.0 線性代數：向量、矩陣、空間" data-link-desc="LLM 內部運算的基底：向量、矩陣、向量空間、內積、norm、矩陣乘法的角色">線性代數</a>（向量、矩陣、空間）、<a href="/blog/llm/02-math-foundations/probability-and-information/" data-link-title="2.1 機率與資訊論" data-link-desc="LLM 輸出的本質是機率分佈：softmax、cross-entropy、KL divergence、perplexity 在訓練與推論中的角色">機率與資訊論</a>（softmax、cross-entropy、KL、perplexity）、<a href="/blog/llm/02-math-foundations/calculus-and-optimization/" data-link-title="2.2 微積分與最佳化" data-link-desc="從 gradient、chain rule 到 SGD / Adam：LLM 訓練如何更新數十億參數">微積分與最佳化</a>（gradient、SGD / Adam）、<a href="/blog/llm/02-math-foundations/numerical-precision/" data-link-title="2.3 數值精度與量化的數學依據" data-link-desc="fp32 / bf16 / fp16 / int8 / int4 的差別、量化能省哪些 bits、品質衰減從哪裡來">數值精度</a>（fp32 / bf16 / Q4 / Q8 的取捨）。每章末尾接到<a href="/blog/llm/02-math-foundations/going-deeper-math/" data-link-title="2.4 想學更深：推薦公開課程" data-link-desc="MIT、Stanford、Harvard 等公開課程：數學基礎跟 LLM 預備知識的完整學習路線">公開課推薦</a>。</p>
<h3 id="模組三llm-的理論基礎"><a href="/blog/llm/03-theoretical-foundations/" data-link-title="模組三：LLM 的理論基礎" data-link-desc="從神經網路、embedding、attention、Transformer 架構、訓練到 sampling：LLM 內部運作的完整理論圖像">模組三：LLM 的理論基礎</a></h3>
<p>整理 LLM 內部運作機制、共 11 章：<a href="/blog/llm/03-theoretical-foundations/neural-network-basics/" data-link-title="3.0 神經網路基礎" data-link-desc="從單一 neuron 到 multi-layer：weights、activation function、forward / backward pass 的角色">神經網路基礎</a>、<a href="/blog/llm/03-theoretical-foundations/embedding-spaces/" data-link-title="3.1 Embedding 空間" data-link-desc="token 怎麼變成向量、為什麼相似 token 在向量空間中靠近、embedding 是怎麼學出來的">embedding 空間</a>、<a href="/blog/llm/03-theoretical-foundations/attention-mechanism/" data-link-title="3.2 Attention 機制" data-link-desc="Query / Key / Value、scaled dot-product attention、multi-head attention：Transformer 的核心運算">attention 機制</a>、<a href="/blog/llm/03-theoretical-foundations/transformer-architecture/" data-link-title="3.3 Transformer 架構細節" data-link-desc="Decoder-only 結構、Transformer block、positional encoding、layer norm、residual stream">Transformer 架構</a>、<a href="/blog/llm/03-theoretical-foundations/training-pipeline/" data-link-title="3.4 訓練流程：pre-train → SFT → RLHF" data-link-desc="LLM 的三階段訓練：預訓練、指令微調、人類反饋強化學習；各階段目標與最新替代方案">訓練流程</a>（pre-train → SFT → RLHF / DPO）、<a href="/blog/llm/03-theoretical-foundations/sampling-and-decoding/" data-link-title="3.5 Sampling 與 Decoding 策略" data-link-desc="Greedy、beam search、top-k、top-p、temperature、min-p：模型輸出後怎麼挑下一個 token">sampling 策略</a>、<a href="/blog/llm/03-theoretical-foundations/tokenization-algorithms/" data-link-title="3.6 Tokenization：BPE、SentencePiece、Tiktoken" data-link-desc="把文字切成 token 的算法：為什麼不同模型切出不同 token 數、tokenizer 選擇對能力的影響">tokenization 算法</a>、<a href="/blog/llm/03-theoretical-foundations/cross-language-tokenization/" data-link-title="3.7 跨語言場景的 tokenizer 與訓練分佈原理" data-link-desc="為什麼模型對不同語言表現不一致：tokenizer &#43; 訓練資料分佈雙因素、語言選擇取捨">跨語言場景原理</a>、<a href="/blog/llm/03-theoretical-foundations/reasoning-models/" data-link-title="3.8 Reasoning models：test-time compute paradigm" data-link-desc="Chain-of-thought 從 prompting 技巧演化成訓練 paradigm、reasoning model 的內部運作、本地可跑的選項與適用任務">Reasoning models</a>（o1 / R1 / QwQ 等 test-time compute paradigm）、<a href="/blog/llm/03-theoretical-foundations/speculative-decoding-internals/" data-link-title="3.9 Speculative decoding 內部：drafter / 驗證 / 加速上限" data-link-desc="speculative decoding 的演算法細節、drafter 跟 target 怎麼配對、acceptance rate 怎麼決定實際加速、MTP 跟 EAGLE 等變體">Speculative decoding 內部</a>（drafter / MTP / EAGLE）。每章末尾接到<a href="/blog/llm/03-theoretical-foundations/going-deeper-theory/" data-link-title="3.11 想學更深：推薦公開課程" data-link-desc="Karpathy、Stanford CS224N / CS25 / CS336、DeepLearning.AI、Hugging Face：LLM 理論深入學習的完整路線">公開課推薦</a>（Karpathy、Stanford CS224N / CS25 / CS336、DeepLearning.AI）。</p>
<h3 id="模組四llm-應用層原理"><a href="/blog/llm/04-applications/" data-link-title="模組四：LLM 應用層原理" data-link-desc="Prompt 技術光譜、RAG、tool use、agent、應用層協議、人機協作、multi-agent、workflow 編排、eval 設計：跨工具不變的概念地圖">模組四：LLM 應用層原理</a></h3>
<p>整理 LLM 作為系統元件的設計原理、共 12 章：<a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">RAG</a>、<a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">tool use</a>、<a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">agent 架構</a>、<a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">應用層協議</a>、<a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">workflow 編排模式</a>、<a href="/blog/llm/04-applications/production-resource-planning/" data-link-title="4.9 Production 部署的資源評估原理" data-link-desc="從本地單 user 到 production multi-tenant：concurrent users、cost model、observability、SLA、capacity planning 的設計取捨">Production resource planning</a>、<a href="/blog/llm/04-applications/artifact-management/" data-link-title="4.10 衍生產物管理原理：什麼進 git、什麼不該" data-link-desc="LLM 應用的 source / derived / external 三類產物對應 git / build cache / registry、與 production 部署的 reproducibility / cost / share 取捨">衍生產物管理</a>、<a href="/blog/llm/04-applications/long-context-engineering/" data-link-title="4.11 Long context engineering" data-link-desc="128K / 1M context 模型怎麼用：claimed vs effective context、lost-in-the-middle、context 設計策略、Long context vs RAG 取捨">Long context engineering</a>、<a href="/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">Embedding model 內部</a>、<a href="/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">Benchmarking 方法論</a>、<a href="/blog/llm/04-applications/vision-in-coding-workflow/" data-link-title="4.15 Vision in coding workflow：本地 VLM 怎麼接寫 code" data-link-desc="VLM 在 coding 工作流的 use cases、本地 VLM 選型、跟雲端 VLM 的分工、Continue.dev / Ollama 整合現狀">Vision in coding workflow</a>（本地 VLM 接 IDE）、<a href="/blog/llm/04-applications/static-and-serverless-rag-deployment/" data-link-title="4.16 靜態 / serverless RAG deployment：架構選擇與資安取捨" data-link-desc="沒 backend 的場景怎麼做 RAG：四種 deployment 方案、API key 暴露問題、CORS / abuse / 第三方信任、跟模組六的 routing">靜態 / serverless RAG deployment</a>（沒 backend 場景）。本模組刻意只寫跨工具世代不變的原理、避開 LangChain / LlamaIndex 等具體 framework 教學。</p>
<h3 id="模組五windows--linux--獨立-gpu"><a href="/blog/llm/05-discrete-gpu/" data-link-title="模組五：Windows / Linux &#43; 獨立 GPU" data-link-desc="消費級 PC（Windows / Linux &#43; NVIDIA / AMD 獨立 GPU）跑本地 LLM 的硬體判讀、MoE CPU 卸載、KV cache 量化與 llama.cpp 調參">模組五：Windows / Linux + 獨立 GPU</a></h3>
<p>整理消費級 PC（Windows / Linux + NVIDIA / AMD 獨立 GPU）跑本地 LLM 的硬體判讀模型與工程選項：<a href="/blog/llm/05-discrete-gpu/vram-ram-budget/" data-link-title="5.0 VRAM &#43; RAM 分層預算" data-link-desc="PC 獨立 GPU 場景的記憶體預算判讀：VRAM 是快的世界、RAM 是大的世界、PCIe 把兩個世界連起來">VRAM + RAM 分層預算</a>、MoE 模型的 <a href="/blog/llm/knowledge-cards/moe-cpu-offload/" data-link-title="MoE CPU 卸載" data-link-desc="把 Mixture-of-Experts 模型不活躍的專家層權重放在系統 RAM、用到再走 PCIe 拉回 GPU、讓有限 VRAM 跑得了更大模型">CPU 卸載策略</a>（<code>--n-cpu-moe</code>）、KV cache 量化（K=Q8 / V=Q4）跟 context 長度的權衡、llama.cpp 在 PC 上的調參空間。本模組跟模組一是平行的硬體路線、共用模組零的心智模型跟卡片。</p>
<h3 id="模組六本地-llm-的安全與權限"><a href="/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">模組六：本地 LLM 的安全與權限</a></h3>
<p>整理個人 dev 在自己機器上跑本地 LLM 的安全議題：<a href="/blog/llm/06-security/model-supply-chain-trust/" data-link-title="6.0 模型供應鏈與信任邊界" data-link-desc="個人 dev 用本地 LLM 時的模型權重來源信任：GGUF 完整性、Hugging Face / Ollama registry 信任、量化版本污染、檔案完整性檢查">模型供應鏈與信任邊界</a>、<a href="/blog/llm/06-security/inference-server-binding/" data-link-title="6.1 推論伺服器的綁定與暴露範圍" data-link-desc="個人 dev 場景下 llama-server / Ollama / LM Studio 的 bind address 判讀：127.0.0.1 vs LAN vs 反代、預設安全、誤開放給內網的後果">推論伺服器的綁定與暴露範圍</a>、<a href="/blog/llm/06-security/tool-use-permission-model/" data-link-title="6.2 tool use 與 MCP server 的權限模型" data-link-desc="個人 dev 場景下 tool use / MCP server 的副作用權限：檔案系統 / shell / 網路存取邊界、第三方 MCP 信任、副作用的可逆性">tool use 與 MCP server 的權限模型</a>、<a href="/blog/llm/06-security/prompt-injection-in-ide/" data-link-title="6.3 IDE 場景的 prompt injection" data-link-desc="個人 dev 場景下 IDE 寫 code 工作流的 prompt injection：codebase 內容、外部文件、剪貼簿作為攻擊面、跟雲端 LLM 場景的差異">IDE 場景的 prompt injection</a>、<a href="/blog/llm/06-security/cross-cloud-local-data-boundary/" data-link-title="6.4 跨雲端 / 本地的資料邊界" data-link-desc="個人 dev 場景下混用雲端 LLM 跟本地 LLM 時的 prompt 洩漏點：Continue.dev 多 provider 設定、隱私資料流、按敏感度分流的判讀">跨雲端 / 本地的資料邊界</a>、<a href="/blog/llm/06-security/routing-to-production-security/" data-link-title="6.5 跨進 production 的 routing 中樞" data-link-desc="個人 dev → 團隊 → production LLM 服務的三層演化、跟 backend/07 對應卡片的 routing 清單">跨進 production 的 routing 中樞</a>。framing 是個人 dev 視角、不是 enterprise 資安管理；production / 多租戶 LLM 服務的特殊資安議題見 <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">Backend 模組七 資安與資料保護</a> 的 LLM 相關章節。</p>
<h2 id="模組之間怎麼配合">模組之間怎麼配合</h2>
<table>
  <thead>
      <tr>
          <th>模組</th>
          <th>角度</th>
          <th>跟其他模組的關係</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>模組零</td>
          <td>操作層心智模型</td>
          <td>是模組一跟模組五的共同前置</td>
      </tr>
      <tr>
          <td>模組一</td>
          <td>工具層、Mac 實際安裝</td>
          <td>用模組零的詞彙、跟模組三的理論互補</td>
      </tr>
      <tr>
          <td>模組二</td>
          <td>數學工具</td>
          <td>提供模組三需要的數學詞彙、跟硬體平台無關</td>
      </tr>
      <tr>
          <td>模組三</td>
          <td>理論機制</td>
          <td>用模組二的工具拼出完整 LLM、跟硬體平台無關</td>
      </tr>
      <tr>
          <td>模組四</td>
          <td>應用層原理</td>
          <td>用前面模組建的詞彙、看 LLM 作為系統元件</td>
      </tr>
      <tr>
          <td>模組五</td>
          <td>工具層、PC 獨立 GPU</td>
          <td>跟模組一平行、用模組零的詞彙、處理 VRAM 場景</td>
      </tr>
      <tr>
          <td>模組六</td>
          <td>安全層、個人 dev 視角</td>
          <td>在模組一 / 五的工作流上加安全判讀、cross-link backend/07 通用資安卡片</td>
      </tr>
  </tbody>
</table>
<p>模組二跟模組三可並讀。閱讀模組三遇到陌生數學詞時跳回模組二補完、再回模組三繼續。模組四在前面模組之上、但讀者熟悉 LLM 應用詞彙也可直接從這裡讀起。模組一跟模組五依硬體選一條主路線、共用模組零的心智模型與 <a href="/blog/llm/knowledge-cards/" data-link-title="Knowledge Cards" data-link-desc="用原子化卡片整理本地 LLM 寫 code 場景所需的概念詞彙">knowledge-cards</a>。模組六在模組一 / 五跑穩後接、處理「跑起來後該注意什麼」。</p>
<h2 id="適合的讀者">適合的讀者</h2>
<table>
  <thead>
      <tr>
          <th>背景</th>
          <th>適合程度</th>
          <th>建議起點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>用過 ChatGPT / Claude、沒碰過本地模型</td>
          <td>直接適合</td>
          <td><a href="/blog/llm/00-foundations/" data-link-title="模組零：基礎知識與心智模型" data-link-desc="建立本地 LLM 的心智模型、釐清 MLX / MTP / oMLX 等常被混淆的術語、Apple Silicon 記憶體現實">模組零</a> 從頭讀</td>
      </tr>
      <tr>
          <td>裝過 Ollama 但被網路上的術語混淆</td>
          <td>直接適合</td>
          <td><a href="/blog/llm/00-foundations/mlx-mtp-omlx/" data-link-title="0.4 MLX / MTP / oMLX 的區別" data-link-desc="三個常被混為一談的術語：framework、加速技巧、特化 server，疊加而非互斥">MLX / MTP / oMLX 區分</a> + <a href="/blog/llm/00-foundations/info-judgment-frames/" data-link-title="0.6 判讀本地 LLM 資訊的五個框架" data-link-desc="本地 LLM 資訊更新快，學會用版本、層級、變數、能力、資料流五個框架評估文章與宣稱">判讀框架</a></td>
      </tr>
      <tr>
          <td>想知道 24GB / 32GB Mac 該選哪個模型</td>
          <td>直接適合</td>
          <td><a href="/blog/llm/00-foundations/hardware-memory-budget/" data-link-title="0.5 Apple Silicon 記憶體預算" data-link-desc="記憶體決定能跑什麼，Q4 量化下的可運作模型對照與系統保留">硬體記憶體預算</a> + <a href="/blog/llm/01-local-llm-services/model-selection-priority/" data-link-title="1.4 寫 code 場景的模型選型優先順序" data-link-desc="Gemma 4 31B MTP → Qwen3-Coder 30B → Qwen3 14B → gpt-oss 20B 的取捨與適用情境">模型選型</a></td>
      </tr>
      <tr>
          <td>想用本地 LLM 完全取代 Claude / GPT-5</td>
          <td>部分適合</td>
          <td><a href="/blog/llm/01-local-llm-services/expectation-management/" data-link-title="1.5 期望管理：本地 LLM 的擅長領域與分工" data-link-desc="本地 LLM 是免費的初階 pair programmer：辨識它的擅長領域、跟雲端旗艦做結構性分工">期望管理</a> 先看完再決定</td>
      </tr>
      <tr>
          <td>想懂 LLM 內部運作機制</td>
          <td>直接適合</td>
          <td><a href="/blog/llm/03-theoretical-foundations/" data-link-title="模組三：LLM 的理論基礎" data-link-desc="從神經網路、embedding、attention、Transformer 架構、訓練到 sampling：LLM 內部運作的完整理論圖像">模組三 理論基礎</a> 從頭讀（含 reasoning models / speculative decoding）</td>
      </tr>
      <tr>
          <td>想懂背後的數學</td>
          <td>直接適合</td>
          <td><a href="/blog/llm/02-math-foundations/" data-link-title="模組二：LLM 的數學基礎" data-link-desc="整理 LLM 推論背後需要理解的線性代數、機率與資訊論、最佳化、數值精度等數學概念">模組二 數學基礎</a> 從頭讀</td>
      </tr>
      <tr>
          <td>想懂 o1 / DeepSeek-R1 等 reasoning model 怎麼運作</td>
          <td>直接適合</td>
          <td><a href="/blog/llm/03-theoretical-foundations/reasoning-models/" data-link-title="3.8 Reasoning models：test-time compute paradigm" data-link-desc="Chain-of-thought 從 prompting 技巧演化成訓練 paradigm、reasoning model 的內部運作、本地可跑的選項與適用任務">3.8 Reasoning models</a> 從頭讀</td>
      </tr>
      <tr>
          <td>想做 LLM 應用開發（RAG / agent / tool use）</td>
          <td>直接適合</td>
          <td><a href="/blog/llm/04-applications/" data-link-title="模組四：LLM 應用層原理" data-link-desc="Prompt 技術光譜、RAG、tool use、agent、應用層協議、人機協作、multi-agent、workflow 編排、eval 設計：跨工具不變的概念地圖">模組四</a> 從 4.0 RAG 依序讀</td>
      </tr>
      <tr>
          <td>想在自家 Hugo / Astro 等靜態網站加 RAG</td>
          <td>直接適合</td>
          <td><a href="/blog/llm/04-applications/static-and-serverless-rag-deployment/" data-link-title="4.16 靜態 / serverless RAG deployment：架構選擇與資安取捨" data-link-desc="沒 backend 的場景怎麼做 RAG：四種 deployment 方案、API key 暴露問題、CORS / abuse / 第三方信任、跟模組六的 routing">4.16 靜態 / serverless RAG deployment</a>（含資安取捨）</td>
      </tr>
      <tr>
          <td>想用 VLM 看截圖 / 設計稿輔助寫 code</td>
          <td>直接適合</td>
          <td><a href="/blog/llm/04-applications/vision-in-coding-workflow/" data-link-title="4.15 Vision in coding workflow：本地 VLM 怎麼接寫 code" data-link-desc="VLM 在 coding 工作流的 use cases、本地 VLM 選型、跟雲端 VLM 的分工、Continue.dev / Ollama 整合現狀">4.15 Vision in coding workflow</a></td>
      </tr>
      <tr>
          <td>想評估 LLM benchmark 數字、做 in-house eval</td>
          <td>直接適合</td>
          <td><a href="/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14 Benchmarking 方法論</a></td>
      </tr>
      <tr>
          <td>想在本機 fine-tune 模型懂自家 codebase 慣例</td>
          <td>直接適合</td>
          <td><a href="/blog/llm/03-theoretical-foundations/training-pipeline/" data-link-title="3.4 訓練流程：pre-train → SFT → RLHF" data-link-desc="LLM 的三階段訓練：預訓練、指令微調、人類反饋強化學習；各階段目標與最新替代方案">3.4 訓練流程</a> 原理 + <a href="/blog/llm/01-local-llm-services/hands-on/local-fine-tuning/" data-link-title="Hands-on：用 QLoRA 在本機 fine-tune coding 模型" data-link-desc="Apple Silicon Mac / PC 獨立 GPU 上跑 QLoRA fine-tune 的完整流程：環境、資料、訓練、evaluation、合併、部署到 Ollama">QLoRA hands-on</a></td>
      </tr>
      <tr>
          <td>想做 large-scale fine-tune / 從頭訓練</td>
          <td>部分適合</td>
          <td>讀完模組三後進入 <a href="/blog/llm/03-theoretical-foundations/going-deeper-theory/" data-link-title="3.11 想學更深：推薦公開課程" data-link-desc="Karpathy、Stanford CS224N / CS25 / CS336、DeepLearning.AI、Hugging Face：LLM 理論深入學習的完整路線">推薦的公開課程</a> 跟 Stanford CS336</td>
      </tr>
      <tr>
          <td>用 Windows / Linux + NVIDIA / AMD 獨立 GPU 跑本地 LLM</td>
          <td>直接適合</td>
          <td><a href="/blog/llm/00-foundations/" data-link-title="模組零：基礎知識與心智模型" data-link-desc="建立本地 LLM 的心智模型、釐清 MLX / MTP / oMLX 等常被混淆的術語、Apple Silicon 記憶體現實">模組零</a> 建心智模型 + <a href="/blog/llm/05-discrete-gpu/" data-link-title="模組五：Windows / Linux &#43; 獨立 GPU" data-link-desc="消費級 PC（Windows / Linux &#43; NVIDIA / AMD 獨立 GPU）跑本地 LLM 的硬體判讀、MoE CPU 卸載、KV cache 量化與 llama.cpp 調參">模組五</a> 處理 VRAM 預算、MoE 卸載、KV cache 量化</td>
      </tr>
      <tr>
          <td>想知道本地 LLM 跑起來後的安全議題</td>
          <td>直接適合</td>
          <td><a href="/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">模組六</a> 個人 dev 視角的安全與權限</td>
      </tr>
      <tr>
          <td>想把 LLM 部署成 production 服務、處理服務化資安</td>
          <td>部分適合</td>
          <td>個人視角見 <a href="/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">模組六</a>；production 場景見 <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">Backend 模組七 資安</a> 的 LLM 相關章節</td>
      </tr>
      <tr>
          <td>想在資料中心級 GPU（H100 / H200 / B200）部署</td>
          <td>部分適合</td>
          <td>心智模型跟 <a href="/blog/llm/knowledge-cards/" data-link-title="Knowledge Cards" data-link-desc="用原子化卡片整理本地 LLM 寫 code 場景所需的概念詞彙">knowledge-cards</a> 通用；vLLM / TGI / Triton 等資料中心 inference server 另尋專門教材</td>
      </tr>
      <tr>
          <td>想跑 Stable Diffusion / Midjourney 等產圖</td>
          <td>跟主題不同</td>
          <td>產圖是 Diffusion 架構、見 <a href="/blog/llm/knowledge-cards/diffusion/" data-link-title="Diffusion" data-link-desc="產圖用的生成式 AI 架構：跟寫 code 用的 Transformer 是不同路線">Diffusion 卡片</a>、另尋 ComfyUI / Draw Things 教材</td>
      </tr>
  </tbody>
</table>
<h2 id="用語約定">用語約定</h2>
<p>本指南使用的關鍵術語在第一次出現時都附原文。為避免歧義，下列詞彙在本指南內固定指涉：</p>
<ol>
<li><strong>本地 LLM</strong>：跑在使用者自己機器（Mac 或 PC）上的大型語言模型推論、prompt 留在本機。</li>
<li><strong>推論伺服器</strong>（inference server）：負責載入模型權重、處理 prompt、產生 token 的常駐程式、例如 Ollama、LM Studio 內建 server、llama.cpp <code>server</code>。</li>
<li><strong>介面層</strong>：使用者實際打字互動的工具、例如 VS Code + Continue.dev、CLI、Web UI。介面層透過 API 跟推論伺服器溝通。</li>
<li><strong>模型</strong>（model）：權重檔本身、例如 <code>gemma4:31b</code>、<code>qwen3-coder:30b</code>。模型可以在不同推論伺服器之間共用、前提是格式相容。</li>
<li><strong>量化</strong>（quantization）：把模型權重從高精度（如 bf16）壓成低精度（如 Q4）以減少記憶體佔用、代價是少許品質下降。</li>
</ol>
<h2 id="不在本指南內的主題">不在本指南內的主題</h2>
<p>本指南不討論：</p>
<ul>
<li><strong>Speech / audio LLM</strong>：跟核心文字 LLM 是不同方向、本指南不涵蓋。Vision（VLM）原本不放、但因 coding 工作流的 vision use case 進入主流、補上 <a href="/blog/llm/04-applications/vision-in-coding-workflow/" data-link-title="4.15 Vision in coding workflow：本地 VLM 怎麼接寫 code" data-link-desc="VLM 在 coding 工作流的 use cases、本地 VLM 選型、跟雲端 VLM 的分工、Continue.dev / Ollama 整合現狀">4.15 Vision in coding workflow</a>；video LLM 仍不放。</li>
<li><strong>資料中心訓練的工程細節</strong>：data parallelism、ZeRO、tensor parallelism 等屬於專門課程的範圍。</li>
<li><strong>向量資料庫的 vendor 比較</strong>（Pinecone vs Weaviate vs Chroma 等）：vendor 格局半年一變、不適合寫入教材。RAG 的 storage 工程原理（升級判讀、index 生命週期、dependency 約束）見 <a href="/blog/llm/04-applications/vector-storage-engineering/" data-link-title="4.22 RAG storage 工程：從 pickle 到 vector database 的選型判讀" data-link-desc="RAG storage backend 選型：規模到哪個階段該從 in-memory 升級到 vector DB、dependency chain 如何收窄選項">4.22 RAG storage 工程</a>。</li>
<li><strong>Kubernetes / 資料中心級分散式推論</strong>：跟個人機器本地 LLM 方向不同、需另尋專門教材。</li>
<li><strong>多卡 NVLink、tensor parallelism</strong>：消費級 PC 場景通常單卡、本指南不涵蓋多卡分散式推論。</li>
</ul>
<p>若讀完本指南後想往這些方向走：</p>
<ol>
<li><strong>想做 <a href="/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG</a> 應用</strong>：先把 Ollama + Continue.dev 跑穩、再讀 <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">模組四 4.1 RAG 原理</a> 建立設計取捨判讀、或 <a href="/blog/llm/03-theoretical-foundations/going-deeper-theory/" data-link-title="3.11 想學更深：推薦公開課程" data-link-desc="Karpathy、Stanford CS224N / CS25 / CS336、DeepLearning.AI、Hugging Face：LLM 理論深入學習的完整路線">模組三 3.8 推薦</a> 的 DeepLearning.AI short courses。</li>
<li><strong>想跑 coding <a href="/blog/llm/knowledge-cards/agent/" data-link-title="LLM Agent" data-link-desc="把控制流交給 LLM 的應用模式：自主決策、跨多步呼叫工具、人類角色從主導變監督">agent</a></strong>：先讀 <a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 Agent 架構原理</a> 建立判讀、再看 <a href="/blog/llm/01-local-llm-services/extension-paths/" data-link-title="1.6 延伸方向：Web UI、coding agent、產圖" data-link-desc="日常路徑跑穩後可以玩的延伸：Open WebUI、aider、ComfyUI；先把基底跑穩再進階">1.6 延伸方向</a> 了解 aider、Cline 等工具的定位差異。</li>
<li><strong>想跑產圖模型</strong>：<a href="/blog/llm/knowledge-cards/diffusion/" data-link-title="Diffusion" data-link-desc="產圖用的生成式 AI 架構：跟寫 code 用的 Transformer 是不同路線">Diffusion</a> 跟 Transformer 是不同架構、請另尋 ComfyUI / Draw Things / Diffusers 教材。</li>
<li><strong>想自己訓練 / fine-tune</strong>：讀完模組三、進入 Karpathy zero-to-hero、Stanford CS336、Hugging Face NLP Course 等<a href="/blog/llm/03-theoretical-foundations/going-deeper-theory/" data-link-title="3.11 想學更深：推薦公開課程" data-link-desc="Karpathy、Stanford CS224N / CS25 / CS336、DeepLearning.AI、Hugging Face：LLM 理論深入學習的完整路線">推薦資源</a>。</li>
</ol>
<hr>
<p><em>文件版本：v0.7.0</em>
<em>最後更新：2026-05-12</em>
<em>系列狀態：七個模組 + 125 張知識卡片。模組零（9 章）/ 一（10 章 + hands-on、含 QLoRA + judge harness）/ 二（5 章）/ 三（12 章、含 reasoning / speculative / constrained decoding）/ 四（17 章、含 long context / embedding / benchmarking / VLM / 靜態 deployment / coding agent harness / prompt caching / agent memory / tracing / LLM-as-judge）/ 五（7 章）/ 六（7 章、含 OWASP 對照）。</em></p>
]]></content:encoded></item><item><title>GUI 應用的安裝驗證：拆包、首跑對話框與播放判讀</title><link>https://tarrragon.github.io/blog/linux/install/gui-apps-install-verify/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/install/gui-apps-install-verify/</guid><description>&lt;p>GUI 應用的安裝驗證跟 CLI 工具走不同的判讀鏈：CLI 工具裝完 &lt;code>command -v&lt;/code> 加一次試跑就能定案，GUI 應用則有三個 CLI 沒有的失敗層——依賴鏈拆包（裝了本體、缺功能模組）、首跑同意對話框（程式要求使用者決策才繼續）、播放輸出鏈（視窗有了、聲音或畫面沒有）。這三層都有各自的權威判讀位置，本篇以一輪 VM 實測（檔案管理器、瀏覽器、媒體播放器、音樂串流）把它們走一遍。&lt;/p>
&lt;h2 id="拆包生態裝了本體不等於裝了功能">拆包生態：裝了本體不等於裝了功能&lt;/h2>
&lt;p>發行版為了控制依賴體積，會把一個應用的核心跟功能模組拆成多個套件，預設只裝核心。這個設計讓「安裝成功」跟「功能可用」變成兩件事，而缺件的症狀往往是靜默的：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>VLC 的解碼器是獨立 plugin&lt;/strong>：Arch 的 &lt;code>vlc&lt;/code> 本體開得起來、UI 完整，播 H.264 影片卻回報 &lt;code>Codec 'h264' is not supported&lt;/code>——解碼能力在 &lt;code>vlc-plugin-ffmpeg&lt;/code>（或整組 &lt;code>vlc-plugins-all&lt;/code>）。judgment 訊號是「應用正常啟動、特定格式失敗」，權威來源是應用自己的 log（&lt;code>vlc --verbose=2&lt;/code>）。&lt;/li>
&lt;li>&lt;strong>pipewire 的 session manager 是獨立套件&lt;/strong>：&lt;code>pipewire&lt;/code> 常被依賴鏈拉進來，但沒有 &lt;code>wireplumber&lt;/code> 就沒有人建立音訊 graph——daemon 在跑、&lt;code>wpctl status&lt;/code> 的 Sinks 段是空的、所有應用無聲且不報錯。補 &lt;code>wireplumber&lt;/code> + &lt;code>pipewire-pulse&lt;/code>（多數 GUI 應用走 PulseAudio API）後輸出裝置立即出現。&lt;/li>
&lt;li>&lt;strong>optional dependency 不會自動安裝&lt;/strong>：套件宣告的 optdepends 是「裝了會多什麼功能」的提示、不是安裝動作。影片縮圖、壓縮格式支援、硬體加速常落在這層，&lt;code>pacman -Qi &amp;lt;pkg&amp;gt;&lt;/code> 的 Optional Deps 段列出哪些沒裝。&lt;/li>
&lt;/ul>
&lt;p>判讀原則：GUI 應用「開得起來但某個功能不動」時，先查發行版有沒有把那個功能拆成獨立套件，再懷疑設定或相容性。&lt;/p>
&lt;h2 id="首跑同意對話框程式在等使用者決策">首跑同意對話框：程式在等使用者決策&lt;/h2>
&lt;p>不少 GUI 應用第一次啟動會彈出需要使用者決策的對話框，最典型的是 VLC 的「Privacy and Network Access Policy」：&lt;/p>
&lt;p>VLC 聲明自己不蒐集、不傳輸任何個人資料，但它能自動向第三方網路服務抓取播放清單裡媒體的中繼資料（封面圖、曲名、演出者）——這個行為等於把「你在播哪些檔案」暴露給第三方服務，所以 VLC 開發者要求使用者明示同意（Allow metadata network access 勾選框、預設勾選）後才允許自動連網。&lt;/p>
&lt;p>這個對話框的判讀是用途導向：拿 VLC 播本機影片、看下載的影片檔，中繼資料抓取沒有用處、取消勾選讓播放器完全離線工作；拿它管理音樂庫、想要自動補封面跟曲目資訊，才需要同意。同意與否都能在偏好設定（Privacy / Network Interaction）事後改。&lt;/p>
&lt;p>首跑對話框對自動化流程有一層額外影響：無人值守安裝驗證時，應用會停在對話框等輸入、腳本側只看到「程式起了但沒繼續」。VLC 把這兩個決策記在 &lt;code>~/.config/vlc/vlcrc&lt;/code> 的 &lt;code>qt-privacy-ask&lt;/code> 與 &lt;code>metadata-network-access&lt;/code> 兩個鍵——首跑後檔案才生成，而且 VLC 退出時會整檔重寫（幾千行的完整設定 dump），把它納入 dotfile 版控會持續產生無意義的 diff，比較合理的處理是讓首跑對話框留給人、或在自動化腳本裡預先寫入只含這兩鍵的最小 vlcrc。&lt;/p>
&lt;p>同型的首跑決策也出現在瀏覽器（預設瀏覽器詢問、錯誤回報同意）跟大型 GUI 應用（遙測同意）。它們的共通判讀：對話框問的是「要不要讓程式自動連外 / 回傳資料」，答案取決於這台機器的用途與隱私要求，安裝驗證流程要把「首跑會有互動」納入預期、不是當成故障。&lt;/p>
&lt;h2 id="播放驗證鏈三個權威位置">播放驗證鏈：三個權威位置&lt;/h2>
&lt;p>「有沒有真的在播」的驗證不靠肉眼跟喇叭，三個權威位置各管一段：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>驗證對象&lt;/th>
 &lt;th>權威來源&lt;/th>
 &lt;th>工具與判準&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>視窗存在&lt;/td>
 &lt;td>compositor 的視窗表&lt;/td>
 &lt;td>&lt;code>hyprctl clients&lt;/code> 有該應用的 class 條目&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>音訊真的在出&lt;/td>
 &lt;td>音訊伺服器 graph&lt;/td>
 &lt;td>&lt;code>wpctl status&lt;/code> Streams 段有該應用的 stream 且 &lt;code>[active]&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>失敗的原因&lt;/td>
 &lt;td>程式自己的 log&lt;/td>
 &lt;td>&lt;code>vlc --verbose=2&lt;/code>、瀏覽器 &lt;code>--enable-logging=stderr&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>把「管線通不通」跟「應用會不會播」拆開驗證能大幅縮短歸因：先用本機音檔 &lt;code>pw-play &amp;lt;file&amp;gt;&lt;/code> 打通音訊路徑（stream 出現 &lt;code>[active]&lt;/code> 代表 guest 側無誤），再測應用層；應用層失敗就跟管線無關，往解碼器、DRM、應用 log 查。串流再多拆一層：先用無 DRM 的串流（一般影音網站）確立網路串流基線，DRM 內容（Spotify、Netflix 類）的失敗才能歸因到 DRM 層——DRM 在非 x86_64 架構的可用性判讀見 &lt;a href="../platform-divergence-map/">平台與發行版差異的判讀地圖&lt;/a> 的套件存在性段。&lt;/p>
&lt;h2 id="vm-特有硬體解碼回退">VM 特有：硬體解碼回退&lt;/h2>
&lt;p>在 VM 裡播放影片，第一次開檔常會閃一個錯誤對話框（&lt;code>failed to create video output&lt;/code>）然後正常播放——這是硬體解碼回退的痕跡：播放器預設先嘗試硬體加速解碼（VDPAU / VAAPI），虛擬 GPU（如 virtio-gpu）沒有視訊解碼能力，嘗試失敗後回退軟體解碼重建輸出。log 上的特徵是一次性的 decoder error 加上之後穩定的 &lt;code>avcodec decoder&lt;/code> 軟體解碼行；實體機器有 GPU 解碼時不會出現。VM 裡想要乾淨啟動，在播放器偏好設定停用 hardware-accelerated decoding 即可——這是機器特性設定，適合留在該機器本機、不進共用 dotfile。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>套件在這個平台 / 架構存不存在、名字叫什麼：&lt;a href="../platform-divergence-map/">平台與發行版差異的判讀地圖&lt;/a>&lt;/li>
&lt;li>音訊、行程、服務狀態的權威判讀：&lt;a href="../../debug/">Linux 除錯與診斷&lt;/a>&lt;/li>
&lt;li>GUI 應用清單怎麼進 bootstrap：&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/08-sync-bootstrap/bootstrap-script-packages/" data-link-title="Bootstrap Script 與套件清單管理" data-link-desc="寫 dotfile 的 install script、或整理「這台機器裝了什麼」的套件清單時回來讀">模組八：Bootstrap script 設計&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>GUI 應用的安裝驗證跟 CLI 工具走不同的判讀鏈：CLI 工具裝完 <code>command -v</code> 加一次試跑就能定案，GUI 應用則有三個 CLI 沒有的失敗層——依賴鏈拆包（裝了本體、缺功能模組）、首跑同意對話框（程式要求使用者決策才繼續）、播放輸出鏈（視窗有了、聲音或畫面沒有）。這三層都有各自的權威判讀位置，本篇以一輪 VM 實測（檔案管理器、瀏覽器、媒體播放器、音樂串流）把它們走一遍。</p>
<h2 id="拆包生態裝了本體不等於裝了功能">拆包生態：裝了本體不等於裝了功能</h2>
<p>發行版為了控制依賴體積，會把一個應用的核心跟功能模組拆成多個套件，預設只裝核心。這個設計讓「安裝成功」跟「功能可用」變成兩件事，而缺件的症狀往往是靜默的：</p>
<ul>
<li><strong>VLC 的解碼器是獨立 plugin</strong>：Arch 的 <code>vlc</code> 本體開得起來、UI 完整，播 H.264 影片卻回報 <code>Codec 'h264' is not supported</code>——解碼能力在 <code>vlc-plugin-ffmpeg</code>（或整組 <code>vlc-plugins-all</code>）。judgment 訊號是「應用正常啟動、特定格式失敗」，權威來源是應用自己的 log（<code>vlc --verbose=2</code>）。</li>
<li><strong>pipewire 的 session manager 是獨立套件</strong>：<code>pipewire</code> 常被依賴鏈拉進來，但沒有 <code>wireplumber</code> 就沒有人建立音訊 graph——daemon 在跑、<code>wpctl status</code> 的 Sinks 段是空的、所有應用無聲且不報錯。補 <code>wireplumber</code> + <code>pipewire-pulse</code>（多數 GUI 應用走 PulseAudio API）後輸出裝置立即出現。</li>
<li><strong>optional dependency 不會自動安裝</strong>：套件宣告的 optdepends 是「裝了會多什麼功能」的提示、不是安裝動作。影片縮圖、壓縮格式支援、硬體加速常落在這層，<code>pacman -Qi &lt;pkg&gt;</code> 的 Optional Deps 段列出哪些沒裝。</li>
</ul>
<p>判讀原則：GUI 應用「開得起來但某個功能不動」時，先查發行版有沒有把那個功能拆成獨立套件，再懷疑設定或相容性。</p>
<h2 id="首跑同意對話框程式在等使用者決策">首跑同意對話框：程式在等使用者決策</h2>
<p>不少 GUI 應用第一次啟動會彈出需要使用者決策的對話框，最典型的是 VLC 的「Privacy and Network Access Policy」：</p>
<p>VLC 聲明自己不蒐集、不傳輸任何個人資料，但它能自動向第三方網路服務抓取播放清單裡媒體的中繼資料（封面圖、曲名、演出者）——這個行為等於把「你在播哪些檔案」暴露給第三方服務，所以 VLC 開發者要求使用者明示同意（Allow metadata network access 勾選框、預設勾選）後才允許自動連網。</p>
<p>這個對話框的判讀是用途導向：拿 VLC 播本機影片、看下載的影片檔，中繼資料抓取沒有用處、取消勾選讓播放器完全離線工作；拿它管理音樂庫、想要自動補封面跟曲目資訊，才需要同意。同意與否都能在偏好設定（Privacy / Network Interaction）事後改。</p>
<p>首跑對話框對自動化流程有一層額外影響：無人值守安裝驗證時，應用會停在對話框等輸入、腳本側只看到「程式起了但沒繼續」。VLC 把這兩個決策記在 <code>~/.config/vlc/vlcrc</code> 的 <code>qt-privacy-ask</code> 與 <code>metadata-network-access</code> 兩個鍵——首跑後檔案才生成，而且 VLC 退出時會整檔重寫（幾千行的完整設定 dump），把它納入 dotfile 版控會持續產生無意義的 diff，比較合理的處理是讓首跑對話框留給人、或在自動化腳本裡預先寫入只含這兩鍵的最小 vlcrc。</p>
<p>同型的首跑決策也出現在瀏覽器（預設瀏覽器詢問、錯誤回報同意）跟大型 GUI 應用（遙測同意）。它們的共通判讀：對話框問的是「要不要讓程式自動連外 / 回傳資料」，答案取決於這台機器的用途與隱私要求，安裝驗證流程要把「首跑會有互動」納入預期、不是當成故障。</p>
<h2 id="播放驗證鏈三個權威位置">播放驗證鏈：三個權威位置</h2>
<p>「有沒有真的在播」的驗證不靠肉眼跟喇叭，三個權威位置各管一段：</p>
<table>
  <thead>
      <tr>
          <th>驗證對象</th>
          <th>權威來源</th>
          <th>工具與判準</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>視窗存在</td>
          <td>compositor 的視窗表</td>
          <td><code>hyprctl clients</code> 有該應用的 class 條目</td>
      </tr>
      <tr>
          <td>音訊真的在出</td>
          <td>音訊伺服器 graph</td>
          <td><code>wpctl status</code> Streams 段有該應用的 stream 且 <code>[active]</code></td>
      </tr>
      <tr>
          <td>失敗的原因</td>
          <td>程式自己的 log</td>
          <td><code>vlc --verbose=2</code>、瀏覽器 <code>--enable-logging=stderr</code></td>
      </tr>
  </tbody>
</table>
<p>把「管線通不通」跟「應用會不會播」拆開驗證能大幅縮短歸因：先用本機音檔 <code>pw-play &lt;file&gt;</code> 打通音訊路徑（stream 出現 <code>[active]</code> 代表 guest 側無誤），再測應用層；應用層失敗就跟管線無關，往解碼器、DRM、應用 log 查。串流再多拆一層：先用無 DRM 的串流（一般影音網站）確立網路串流基線，DRM 內容（Spotify、Netflix 類）的失敗才能歸因到 DRM 層——DRM 在非 x86_64 架構的可用性判讀見 <a href="../platform-divergence-map/">平台與發行版差異的判讀地圖</a> 的套件存在性段。</p>
<h2 id="vm-特有硬體解碼回退">VM 特有：硬體解碼回退</h2>
<p>在 VM 裡播放影片，第一次開檔常會閃一個錯誤對話框（<code>failed to create video output</code>）然後正常播放——這是硬體解碼回退的痕跡：播放器預設先嘗試硬體加速解碼（VDPAU / VAAPI），虛擬 GPU（如 virtio-gpu）沒有視訊解碼能力，嘗試失敗後回退軟體解碼重建輸出。log 上的特徵是一次性的 decoder error 加上之後穩定的 <code>avcodec decoder</code> 軟體解碼行；實體機器有 GPU 解碼時不會出現。VM 裡想要乾淨啟動，在播放器偏好設定停用 hardware-accelerated decoding 即可——這是機器特性設定，適合留在該機器本機、不進共用 dotfile。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>套件在這個平台 / 架構存不存在、名字叫什麼：<a href="../platform-divergence-map/">平台與發行版差異的判讀地圖</a></li>
<li>音訊、行程、服務狀態的權威判讀：<a href="../../debug/">Linux 除錯與診斷</a></li>
<li>GUI 應用清單怎麼進 bootstrap：<a href="/blog/linux/dotfile/08-sync-bootstrap/bootstrap-script-packages/" data-link-title="Bootstrap Script 與套件清單管理" data-link-desc="寫 dotfile 的 install script、或整理「這台機器裝了什麼」的套件清單時回來讀">模組八：Bootstrap script 設計</a></li>
</ul>
]]></content:encoded></item><item><title>平台與發行版差異的判讀地圖</title><link>https://tarrragon.github.io/blog/linux/install/platform-divergence-map/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/install/platform-divergence-map/</guid><description>&lt;p>同一個工作環境要在多台機器上復現時，差異集中在四個層次：套件管理器、套件名稱、套件存在性、版本節奏。這四層決定了 bootstrap 腳本哪些部分能共用、哪些必須按平台獨立維護，也決定了除錯時要先確認自己站在哪個平台上——很多「工具行為不對」的問題，根因是把 A 平台的經驗直接套到 B 平台。&lt;/p>
&lt;h2 id="差異的四個層次">差異的四個層次&lt;/h2>
&lt;h3 id="套件管理器每個平台各有原生解">套件管理器：每個平台各有原生解&lt;/h3>
&lt;p>macOS 用 Homebrew、Arch 用 pacman、Debian/Ubuntu 用 apt、Fedora 用 dnf。安裝指令、確認旗標、資料庫同步模型都不同，其中兩個差異會直接咬到自動化腳本：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>非互動旗標不對稱&lt;/strong>：apt 的慣例是 &lt;code>-y&lt;/code>，pacman 是 &lt;code>--noconfirm&lt;/code>。腳本只寫了其中一邊，換平台就會卡在確認提示——非 TTY 環境下（SSH 一行式、CI、無人值守）沒人回答 &lt;code>[Y/n]&lt;/code>，pacman 直接以錯誤結束。&lt;/li>
&lt;li>&lt;strong>資料庫同步模型不同&lt;/strong>：Arch 是 rolling release 且鏡像不保留舊版檔案，裝機當下的套件資料庫幾天內就會指向已被輪替掉的檔名，安裝時收到 404（&lt;code>failed retrieving file&lt;/code>）。修法是安裝前先 &lt;code>pacman -Syu&lt;/code> 同步資料庫並全系統升級——只 &lt;code>-Sy&lt;/code> 不 &lt;code>-u&lt;/code> 會造成 partial upgrade（新資料庫裝新套件、舊系統缺新依賴）。Debian stable 的套件庫凍結、沒有這個時序問題，但代價是版本舊。&lt;/li>
&lt;/ul>
&lt;h3 id="套件名稱同一個工具各發行版各叫各的">套件名稱：同一個工具、各發行版各叫各的&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>工具&lt;/th>
 &lt;th>Arch&lt;/th>
 &lt;th>Debian/Ubuntu&lt;/th>
 &lt;th>Fedora&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>fd&lt;/td>
 &lt;td>&lt;code>fd&lt;/code>&lt;/td>
 &lt;td>&lt;code>fd-find&lt;/code>（執行檔叫 &lt;code>fdfind&lt;/code>）&lt;/td>
 &lt;td>&lt;code>fd-find&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>bat&lt;/td>
 &lt;td>&lt;code>bat&lt;/code>&lt;/td>
 &lt;td>&lt;code>bat&lt;/code>（執行檔叫 &lt;code>batcat&lt;/code>）&lt;/td>
 &lt;td>&lt;code>bat&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>gh&lt;/td>
 &lt;td>&lt;code>github-cli&lt;/code>&lt;/td>
 &lt;td>&lt;code>gh&lt;/code>&lt;/td>
 &lt;td>&lt;code>gh&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CJK 字型&lt;/td>
 &lt;td>&lt;code>noto-fonts-cjk&lt;/code>&lt;/td>
 &lt;td>&lt;code>fonts-noto-cjk&lt;/code>&lt;/td>
 &lt;td>&lt;code>google-noto-sans-cjk-fonts&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Meslo Nerd Font&lt;/td>
 &lt;td>&lt;code>ttf-meslo-nerd&lt;/code>&lt;/td>
 &lt;td>未打包（手動裝）&lt;/td>
 &lt;td>未打包&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Debian 的重命名還會連執行檔一起改（&lt;code>fdfind&lt;/code>、&lt;code>batcat&lt;/code>），所以連 shell alias 與腳本內的指令呼叫都要跟著分歧。維護跨發行版清單的可靠做法是逐台實測建立——憑印象抄一份對照表，漂移只是時間問題。&lt;/p>
&lt;h3 id="套件存在性有些概念只存在於特定平台">套件存在性：有些概念只存在於特定平台&lt;/h3>
&lt;p>Hyprland 在 Arch 官方 repo、Fedora 要 COPR、Debian stable 沒有；Quickshell 只有 Arch 打包。反過來，macOS 的 cask app（GUI 應用程式）概念在 Linux 對應的是各桌面環境自己的生態。這層差異沒有翻譯的空間——桌面層的清單是平台專屬的維護對象。&lt;/p>
&lt;p>存在性差異還有一個容易漏看的軸：&lt;strong>CPU 架構&lt;/strong>。發行版 repo 有這個工具、不代表它在你的架構上存在——尤其是專有軟體的二進位發行。實測案例：Arch aarch64（ALARM）的 repo 有 &lt;code>spotify-launcher&lt;/code>（工具本身有 aarch64 建置），但它要下載的 Spotify 官方 client 只發 x86_64/i386 deb，實跑直接回報 &lt;code>There are no packages for your cpu's architecture (cpu=&amp;quot;aarch64&amp;quot;, supported=[&amp;quot;amd64&amp;quot;, &amp;quot;i386&amp;quot;])&lt;/code>。這類失敗的判讀重點是分清「工具沒打包」跟「工具打包了、它依賴的專有 blob 沒有這個架構」——前者可能有 AUR / 第三方 repo 補、後者只能找替代路徑（Spotify 的替代是 Web Player + 從 ChromeOS 鏡像抽出的 arm64 Widevine CDM）。DRM、GPU driver、印表機 driver 這類含專有二進位的軟體，在非 x86_64 架構上都要先查架構支援再排進安裝清單。&lt;/p>
&lt;h3 id="版本節奏rolling-與-stable-的行為差">版本節奏：rolling 與 stable 的行為差&lt;/h3>
&lt;p>Arch rolling 永遠最新，Debian stable 的同名工具可能舊兩年。版本差會讓 config 語法對不上（新版工具的設定選項在舊版不存在）、也會讓「照著文件做卻失敗」——文件寫的是新版行為。除錯時看到「同一份 config 在 A 機器能跑、B 機器報錯」，先比對兩邊的工具版本再懷疑 config 本身。&lt;/p>
&lt;h2 id="除錯前先定平台">除錯前先定平台&lt;/h2>
&lt;p>跨平台差異對除錯的意義：&lt;strong>判讀工具與修法都是平台相依的，先確認自己站在哪，再開始查。&lt;/strong> 三條指令建立座標：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">cat /etc/os-release &lt;span class="c1"># 發行版與版本（Linux）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">uname -m &lt;span class="c1"># CPU 架構：x86_64 / aarch64（套件生態差很多）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="nb">command&lt;/span> -v pacman apt-get dnf brew &lt;span class="c1"># 哪個套件管理器在場&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>架構那條容易被忽略：aarch64（ARM）的套件生態比 x86_64 小——Homebrew on Linux 對 aarch64 沒有預編譯 bottle、AUR 部分套件不支援 ARM。在 ARM 機器上照 x86 的教學走，會在意想不到的地方碰壁。&lt;/p></description><content:encoded><![CDATA[<p>同一個工作環境要在多台機器上復現時，差異集中在四個層次：套件管理器、套件名稱、套件存在性、版本節奏。這四層決定了 bootstrap 腳本哪些部分能共用、哪些必須按平台獨立維護，也決定了除錯時要先確認自己站在哪個平台上——很多「工具行為不對」的問題，根因是把 A 平台的經驗直接套到 B 平台。</p>
<h2 id="差異的四個層次">差異的四個層次</h2>
<h3 id="套件管理器每個平台各有原生解">套件管理器：每個平台各有原生解</h3>
<p>macOS 用 Homebrew、Arch 用 pacman、Debian/Ubuntu 用 apt、Fedora 用 dnf。安裝指令、確認旗標、資料庫同步模型都不同，其中兩個差異會直接咬到自動化腳本：</p>
<ul>
<li><strong>非互動旗標不對稱</strong>：apt 的慣例是 <code>-y</code>，pacman 是 <code>--noconfirm</code>。腳本只寫了其中一邊，換平台就會卡在確認提示——非 TTY 環境下（SSH 一行式、CI、無人值守）沒人回答 <code>[Y/n]</code>，pacman 直接以錯誤結束。</li>
<li><strong>資料庫同步模型不同</strong>：Arch 是 rolling release 且鏡像不保留舊版檔案，裝機當下的套件資料庫幾天內就會指向已被輪替掉的檔名，安裝時收到 404（<code>failed retrieving file</code>）。修法是安裝前先 <code>pacman -Syu</code> 同步資料庫並全系統升級——只 <code>-Sy</code> 不 <code>-u</code> 會造成 partial upgrade（新資料庫裝新套件、舊系統缺新依賴）。Debian stable 的套件庫凍結、沒有這個時序問題，但代價是版本舊。</li>
</ul>
<h3 id="套件名稱同一個工具各發行版各叫各的">套件名稱：同一個工具、各發行版各叫各的</h3>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>Arch</th>
          <th>Debian/Ubuntu</th>
          <th>Fedora</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>fd</td>
          <td><code>fd</code></td>
          <td><code>fd-find</code>（執行檔叫 <code>fdfind</code>）</td>
          <td><code>fd-find</code></td>
      </tr>
      <tr>
          <td>bat</td>
          <td><code>bat</code></td>
          <td><code>bat</code>（執行檔叫 <code>batcat</code>）</td>
          <td><code>bat</code></td>
      </tr>
      <tr>
          <td>gh</td>
          <td><code>github-cli</code></td>
          <td><code>gh</code></td>
          <td><code>gh</code></td>
      </tr>
      <tr>
          <td>CJK 字型</td>
          <td><code>noto-fonts-cjk</code></td>
          <td><code>fonts-noto-cjk</code></td>
          <td><code>google-noto-sans-cjk-fonts</code></td>
      </tr>
      <tr>
          <td>Meslo Nerd Font</td>
          <td><code>ttf-meslo-nerd</code></td>
          <td>未打包（手動裝）</td>
          <td>未打包</td>
      </tr>
  </tbody>
</table>
<p>Debian 的重命名還會連執行檔一起改（<code>fdfind</code>、<code>batcat</code>），所以連 shell alias 與腳本內的指令呼叫都要跟著分歧。維護跨發行版清單的可靠做法是逐台實測建立——憑印象抄一份對照表，漂移只是時間問題。</p>
<h3 id="套件存在性有些概念只存在於特定平台">套件存在性：有些概念只存在於特定平台</h3>
<p>Hyprland 在 Arch 官方 repo、Fedora 要 COPR、Debian stable 沒有；Quickshell 只有 Arch 打包。反過來，macOS 的 cask app（GUI 應用程式）概念在 Linux 對應的是各桌面環境自己的生態。這層差異沒有翻譯的空間——桌面層的清單是平台專屬的維護對象。</p>
<p>存在性差異還有一個容易漏看的軸：<strong>CPU 架構</strong>。發行版 repo 有這個工具、不代表它在你的架構上存在——尤其是專有軟體的二進位發行。實測案例：Arch aarch64（ALARM）的 repo 有 <code>spotify-launcher</code>（工具本身有 aarch64 建置），但它要下載的 Spotify 官方 client 只發 x86_64/i386 deb，實跑直接回報 <code>There are no packages for your cpu's architecture (cpu=&quot;aarch64&quot;, supported=[&quot;amd64&quot;, &quot;i386&quot;])</code>。這類失敗的判讀重點是分清「工具沒打包」跟「工具打包了、它依賴的專有 blob 沒有這個架構」——前者可能有 AUR / 第三方 repo 補、後者只能找替代路徑（Spotify 的替代是 Web Player + 從 ChromeOS 鏡像抽出的 arm64 Widevine CDM）。DRM、GPU driver、印表機 driver 這類含專有二進位的軟體，在非 x86_64 架構上都要先查架構支援再排進安裝清單。</p>
<h3 id="版本節奏rolling-與-stable-的行為差">版本節奏：rolling 與 stable 的行為差</h3>
<p>Arch rolling 永遠最新，Debian stable 的同名工具可能舊兩年。版本差會讓 config 語法對不上（新版工具的設定選項在舊版不存在）、也會讓「照著文件做卻失敗」——文件寫的是新版行為。除錯時看到「同一份 config 在 A 機器能跑、B 機器報錯」，先比對兩邊的工具版本再懷疑 config 本身。</p>
<h2 id="除錯前先定平台">除錯前先定平台</h2>
<p>跨平台差異對除錯的意義：<strong>判讀工具與修法都是平台相依的，先確認自己站在哪，再開始查。</strong> 三條指令建立座標：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">cat /etc/os-release        <span class="c1"># 發行版與版本（Linux）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">uname -m                   <span class="c1"># CPU 架構：x86_64 / aarch64（套件生態差很多）</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nb">command</span> -v pacman apt-get dnf brew   <span class="c1"># 哪個套件管理器在場</span></span></span></code></pre></div><p>架構那條容易被忽略：aarch64（ARM）的套件生態比 x86_64 小——Homebrew on Linux 對 aarch64 沒有預編譯 bottle、AUR 部分套件不支援 ARM。在 ARM 機器上照 x86 的教學走，會在意想不到的地方碰壁。</p>
<h2 id="bootstrap-的分歧設計判準">Bootstrap 的分歧設計判準</h2>
<p>把差異收進腳本架構的三條判準，決定每段邏輯住在哪：</p>
<ol>
<li><strong>安裝手段跨平台一致</strong>（git clone、curl installer、stow 部署）→ 進共通層，一份邏輯全平台用</li>
<li><strong>只是套件名或套件管理器不同</strong> → 各平台一份安裝腳本 + 一份套件清單，獨立維護、分歧不寫進共通層的 if/else</li>
<li><strong>概念只存在於某平台</strong>（Hyprland、cask）→ 只出現在該平台清單的桌面層</li>
</ol>
<p>這個切法的維護成本結構：共通層改一次全平台生效；平台層只在你真的用那個平台時才付維護成本。沒有實測機器的發行版不預先建清單——那種清單沒有實測支撐、注定漂移。</p>
<h2 id="統一層的誘惑與代價">統一層的誘惑與代價</h2>
<p>「用一個跨平台套件管理器統一所有機器」聽起來能消掉整個分歧層，實際的適用邊界很窄。Homebrew 支援 Linux，但它在 Arch 上會建一套與 pacman 平行的套件世界（獨立 prefix、重複的函式庫、PATH 互搶），而且對 aarch64 Linux 沒有 bottle、全部從原始碼編譯。它真正的適用場景是「發行版套件太舊」（如 Ubuntu LTS 上要新版工具）或「沒有 root 權限」。Nix 能做到真正的跨平台一致，代價是整套心智模型重學。判準是：分歧層的維護成本（每個發行版一份清單）低於統一層的引入成本時，保持原生套件管理器 + 分平台清單。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Bootstrap 腳本本身的設計（log 落地、錯誤定位）見<a href="/blog/linux/install/observable-bootstrap/" data-link-title="可除錯的 bootstrap：把可觀測性內建進安裝腳本" data-link-desc="安裝腳本中途失敗卻只能對著終端機捲動瞎找原因、想在 bootstrap 設計階段就讓失敗可定位時回來讀">可除錯的 bootstrap</a></li>
<li>最小系統缺什麼、怎麼驗證見<a href="/blog/linux/install/minimal-install-verify/" data-link-title="最小安裝後的工具驗證與補足" data-link-desc="最小化安裝的 Linux 裝完發現連 sudo 或 which 都沒有、bootstrap 腳本第一行就炸、需要先確認系統缺哪些必要工具再補時回來讀">最小安裝後的工具驗證與補足</a></li>
<li>出問題時的判讀紀律見 <a href="/blog/linux/debug/" data-link-title="Linux 除錯與診斷" data-link-desc="遠端或本地除錯 Linux 時，一個現象看起來像 A 卻可能是 B，想建立一套先讀權威狀態再下判斷的紀律、按症狀分流到對的檢查與工具時回來讀">Linux 除錯與診斷</a></li>
<li>dotfile repo 怎麼同時服務 macOS 與 Linux 見<a href="/blog/linux/dotfile/01-dotfile-management/cross-platform-one-repo/" data-link-title="跨平台共用一個 Repo" data-link-desc="macOS 跟 Linux 要共用同一個 dotfile repo、不想維護兩份時回來讀">一個 repo 管理跨平台環境</a></li>
</ul>
]]></content:encoded></item><item><title>SSH Key 設定筆記（macOS / Linux / Windows）</title><link>https://tarrragon.github.io/blog/work-log/ssh-key-%E8%A8%AD%E5%AE%9A%E7%AD%86%E8%A8%98macos-/-linux-/-windows/</link><pubDate>Thu, 05 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/work-log/ssh-key-%E8%A8%AD%E5%AE%9A%E7%AD%86%E8%A8%98macos-/-linux-/-windows/</guid><description>&lt;h2 id="0-產生金鑰如果還沒有的話">0. 產生金鑰（如果還沒有的話）&lt;/h2>
&lt;p>目前推薦使用 &lt;strong>Ed25519&lt;/strong> 演算法，相比 RSA 更安全、金鑰更短、驗證速度更快：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">ssh-keygen -t ed25519 -C &lt;span class="s2">&amp;#34;your_email@example.com&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>若需相容較舊的系統（不支援 Ed25519），可退而使用 RSA-4096：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">ssh-keygen -t rsa -b &lt;span class="m">4096&lt;/span> -C &lt;span class="s2">&amp;#34;your_email@example.com&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/blockquote>
&lt;p>產生時會提示設定 &lt;strong>passphrase（密碼短語）&lt;/strong>，強烈建議設定。即使私鑰外洩，攻擊者仍需要密碼才能使用。&lt;/p>
&lt;hr>
&lt;h2 id="1-寫入金鑰檔案">1. 寫入金鑰檔案&lt;/h2>
&lt;h3 id="macos--linux">macOS / Linux&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">cat &amp;gt; ~/.ssh/&amp;lt;key_name&amp;gt; &lt;span class="s">&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="s">（貼上金鑰內容）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="s">EOF&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;code>'EOF'&lt;/code> 加單引號 → 防止 shell 解析內容中的特殊字元（如 &lt;code>$&lt;/code>、&lt;code>`&lt;/code>）&lt;/p>&lt;/blockquote>
&lt;h3 id="windowspowershell">Windows（PowerShell）&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nb">New-Item&lt;/span> &lt;span class="n">-Path&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$env:USERPROFILE&lt;/span>&lt;span class="s2">\.ssh&amp;#34;&lt;/span> &lt;span class="n">-ItemType&lt;/span> &lt;span class="n">Directory&lt;/span> &lt;span class="n">-Force&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="nb">Set-Content&lt;/span> &lt;span class="n">-Path&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$env:USERPROFILE&lt;/span>&lt;span class="s2">\.ssh\&amp;lt;key_name&amp;gt;&amp;#34;&lt;/span> &lt;span class="n">-Value&lt;/span> &lt;span class="sh">@&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="sh">（貼上金鑰內容）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="sh">&amp;#34;@&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>Windows 的 SSH 金鑰預設路徑為 &lt;code>C:\Users\&amp;lt;使用者&amp;gt;\.ssh\&lt;/code>&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="2-設定權限">2. 設定權限&lt;/h2>
&lt;h3 id="macos--linux-1">macOS / Linux&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">chmod &lt;span class="m">600&lt;/span> ~/.ssh/&amp;lt;key_name&amp;gt;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;code>chmod 600&lt;/code> → 僅擁有者可讀寫，SSH 要求私鑰權限不可過於開放，否則會拒絕使用。&lt;/p>&lt;/blockquote>
&lt;h3 id="windowspowershell以系統管理員執行">Windows（PowerShell，以系統管理員執行）&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="n">icacls&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$env:USERPROFILE&lt;/span>&lt;span class="s2">\.ssh\&amp;lt;key_name&amp;gt;&amp;#34;&lt;/span> &lt;span class="p">/&lt;/span>&lt;span class="n">inheritance&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="nb">r &lt;/span>&lt;span class="p">/&lt;/span>&lt;span class="n">grant&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="nb">r &lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">$(&lt;/span>&lt;span class="nv">$env:USERNAME&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="s2">:(R)&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>Windows 需透過 &lt;code>icacls&lt;/code> 移除繼承權限，並限制為只有當前使用者可讀取。
若權限過於開放，OpenSSH 同樣會拒絕載入金鑰。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="3-加入-ssh-agent">3. 加入 SSH Agent&lt;/h2>
&lt;h3 id="macos">macOS&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 啟動 agent（通常 macOS 已自動啟動）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="nb">eval&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>ssh-agent -s&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 加入金鑰，並存入 Keychain 避免重開機後失效&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">ssh-add --apple-use-keychain ~/.ssh/&amp;lt;key_name&amp;gt;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>若要讓金鑰在每次登入時自動載入，可在 &lt;code>~/.ssh/config&lt;/code> 中加入：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">Host *
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> AddKeysToAgent yes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> UseKeychain yes
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> IdentityFile ~/.ssh/&amp;lt;key_name&amp;gt;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="linux">Linux&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 啟動 agent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="nb">eval&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>ssh-agent -s&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 加入金鑰&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">ssh-add ~/.ssh/&amp;lt;key_name&amp;gt;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>Linux 重開機後 agent 會重置。可將 &lt;code>eval &amp;quot;$(ssh-agent -s)&amp;quot;&lt;/code> 加入 &lt;code>~/.bashrc&lt;/code> 或 &lt;code>~/.zshrc&lt;/code> 讓它自動啟動。&lt;/p>&lt;/blockquote>
&lt;h3 id="windowspowershell以系統管理員執行-1">Windows（PowerShell，以系統管理員執行）&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c"># 啟用 ssh-agent 服務（預設為停用）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="nb">Get-Service&lt;/span> &lt;span class="nb">ssh-agent&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="nb">Set-Service&lt;/span> &lt;span class="n">-StartupType&lt;/span> &lt;span class="n">Automatic&lt;/span> &lt;span class="n">-PassThru&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="nb">Start-Service&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c"># 加入金鑰&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="nb">ssh-add&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$env:USERPROFILE&lt;/span>&lt;span class="s2">\.ssh\&amp;lt;key_name&amp;gt;&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>Windows 的 &lt;code>ssh-agent&lt;/code> 是系統服務，啟用後重開機也會自動執行，不需額外設定。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="4-測試連線">4. 測試連線&lt;/h2>
&lt;p>三個平台指令相同：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">ssh -i ~/.ssh/&amp;lt;key_name&amp;gt; -T git@&amp;lt;host&amp;gt;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>Windows 請在 PowerShell 或 Git Bash 中執行，路徑會自動對應到 &lt;code>$env:USERPROFILE\.ssh\&lt;/code>。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="備註">備註&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>項目&lt;/th>
 &lt;th>macOS&lt;/th>
 &lt;th>Linux&lt;/th>
 &lt;th>Windows&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>金鑰路徑&lt;/td>
 &lt;td>&lt;code>~/.ssh/&lt;/code>&lt;/td>
 &lt;td>&lt;code>~/.ssh/&lt;/code>&lt;/td>
 &lt;td>&lt;code>C:\Users\&amp;lt;使用者&amp;gt;\.ssh\&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>權限設定&lt;/td>
 &lt;td>&lt;code>chmod 600&lt;/code>&lt;/td>
 &lt;td>&lt;code>chmod 600&lt;/code>&lt;/td>
 &lt;td>&lt;code>icacls&lt;/code> 移除繼承&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Agent 持久化&lt;/td>
 &lt;td>Keychain&lt;/td>
 &lt;td>需加入 shell rc&lt;/td>
 &lt;td>系統服務，自動持久&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>預裝 SSH&lt;/td>
 &lt;td>是&lt;/td>
 &lt;td>大多數發行版已預裝&lt;/td>
 &lt;td>Windows 10 1809+ 內建 OpenSSH&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="安全性建議">安全性建議&lt;/h2>
&lt;h3 id="優先使用-ed25519">優先使用 Ed25519&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>演算法&lt;/th>
 &lt;th>金鑰長度&lt;/th>
 &lt;th>安全性&lt;/th>
 &lt;th>效能&lt;/th>
 &lt;th>相容性&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>Ed25519&lt;/strong>&lt;/td>
 &lt;td>256 bit&lt;/td>
 &lt;td>高&lt;/td>
 &lt;td>最快&lt;/td>
 &lt;td>2014 年後的 OpenSSH 6.5+&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>RSA-4096&lt;/td>
 &lt;td>4096 bit&lt;/td>
 &lt;td>高&lt;/td>
 &lt;td>較慢&lt;/td>
 &lt;td>最廣泛，適合舊系統&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ECDSA&lt;/td>
 &lt;td>256/384/521 bit&lt;/td>
 &lt;td>中高&lt;/td>
 &lt;td>快&lt;/td>
 &lt;td>已被 Ed25519 取代&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>DSA&lt;/td>
 &lt;td>1024 bit&lt;/td>
 &lt;td>低&lt;/td>
 &lt;td>-&lt;/td>
 &lt;td>已棄用，OpenSSH 7.0+ 預設停用&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="為私鑰設定-passphrase">為私鑰設定 Passphrase&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 為已存在的金鑰補設或更換 passphrase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">ssh-keygen -p -f ~/.ssh/&amp;lt;key_name&amp;gt;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>即使私鑰檔案被他人取得，沒有 passphrase 就無法使用。搭配 SSH Agent 後只需輸入一次，不影響日常使用體驗。&lt;/p></description><content:encoded><![CDATA[<h2 id="0-產生金鑰如果還沒有的話">0. 產生金鑰（如果還沒有的話）</h2>
<p>目前推薦使用 <strong>Ed25519</strong> 演算法，相比 RSA 更安全、金鑰更短、驗證速度更快：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">ssh-keygen -t ed25519 -C <span class="s2">&#34;your_email@example.com&#34;</span></span></span></code></pre></div><blockquote>
<p>若需相容較舊的系統（不支援 Ed25519），可退而使用 RSA-4096：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">ssh-keygen -t rsa -b <span class="m">4096</span> -C <span class="s2">&#34;your_email@example.com&#34;</span></span></span></code></pre></div></blockquote>
<p>產生時會提示設定 <strong>passphrase（密碼短語）</strong>，強烈建議設定。即使私鑰外洩，攻擊者仍需要密碼才能使用。</p>
<hr>
<h2 id="1-寫入金鑰檔案">1. 寫入金鑰檔案</h2>
<h3 id="macos--linux">macOS / Linux</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">cat &gt; ~/.ssh/&lt;key_name&gt; <span class="s">&lt;&lt; &#39;EOF&#39;
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="s">（貼上金鑰內容）
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="s">EOF</span></span></span></code></pre></div><blockquote>
<p><code>'EOF'</code> 加單引號 → 防止 shell 解析內容中的特殊字元（如 <code>$</code>、<code>`</code>）</p></blockquote>
<h3 id="windowspowershell">Windows（PowerShell）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">New-Item</span> <span class="n">-Path</span> <span class="s2">&#34;</span><span class="nv">$env:USERPROFILE</span><span class="s2">\.ssh&#34;</span> <span class="n">-ItemType</span> <span class="n">Directory</span> <span class="n">-Force</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">Set-Content</span> <span class="n">-Path</span> <span class="s2">&#34;</span><span class="nv">$env:USERPROFILE</span><span class="s2">\.ssh\&lt;key_name&gt;&#34;</span> <span class="n">-Value</span> <span class="sh">@&#34;
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="sh">（貼上金鑰內容）
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="sh">&#34;@</span></span></span></code></pre></div><blockquote>
<p>Windows 的 SSH 金鑰預設路徑為 <code>C:\Users\&lt;使用者&gt;\.ssh\</code></p></blockquote>
<hr>
<h2 id="2-設定權限">2. 設定權限</h2>
<h3 id="macos--linux-1">macOS / Linux</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">chmod <span class="m">600</span> ~/.ssh/&lt;key_name&gt;</span></span></code></pre></div><blockquote>
<p><code>chmod 600</code> → 僅擁有者可讀寫，SSH 要求私鑰權限不可過於開放，否則會拒絕使用。</p></blockquote>
<h3 id="windowspowershell以系統管理員執行">Windows（PowerShell，以系統管理員執行）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">icacls</span> <span class="s2">&#34;</span><span class="nv">$env:USERPROFILE</span><span class="s2">\.ssh\&lt;key_name&gt;&#34;</span> <span class="p">/</span><span class="n">inheritance</span><span class="err">:</span><span class="nb">r </span><span class="p">/</span><span class="n">grant</span><span class="err">:</span><span class="nb">r </span><span class="s2">&#34;</span><span class="p">$(</span><span class="nv">$env:USERNAME</span><span class="p">)</span><span class="s2">:(R)&#34;</span></span></span></code></pre></div><blockquote>
<p>Windows 需透過 <code>icacls</code> 移除繼承權限，並限制為只有當前使用者可讀取。
若權限過於開放，OpenSSH 同樣會拒絕載入金鑰。</p></blockquote>
<hr>
<h2 id="3-加入-ssh-agent">3. 加入 SSH Agent</h2>
<h3 id="macos">macOS</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 啟動 agent（通常 macOS 已自動啟動）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">eval</span> <span class="s2">&#34;</span><span class="k">$(</span>ssh-agent -s<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 加入金鑰，並存入 Keychain 避免重開機後失效</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">ssh-add --apple-use-keychain ~/.ssh/&lt;key_name&gt;</span></span></code></pre></div><p>若要讓金鑰在每次登入時自動載入，可在 <code>~/.ssh/config</code> 中加入：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Host *
</span></span><span class="line"><span class="ln">2</span><span class="cl">  AddKeysToAgent yes
</span></span><span class="line"><span class="ln">3</span><span class="cl">  UseKeychain yes
</span></span><span class="line"><span class="ln">4</span><span class="cl">  IdentityFile ~/.ssh/&lt;key_name&gt;</span></span></code></pre></div><h3 id="linux">Linux</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 啟動 agent</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">eval</span> <span class="s2">&#34;</span><span class="k">$(</span>ssh-agent -s<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 加入金鑰</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">ssh-add ~/.ssh/&lt;key_name&gt;</span></span></code></pre></div><blockquote>
<p>Linux 重開機後 agent 會重置。可將 <code>eval &quot;$(ssh-agent -s)&quot;</code> 加入 <code>~/.bashrc</code> 或 <code>~/.zshrc</code> 讓它自動啟動。</p></blockquote>
<h3 id="windowspowershell以系統管理員執行-1">Windows（PowerShell，以系統管理員執行）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># 啟用 ssh-agent 服務（預設為停用）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">Get-Service</span> <span class="nb">ssh-agent</span> <span class="p">|</span> <span class="nb">Set-Service</span> <span class="n">-StartupType</span> <span class="n">Automatic</span> <span class="n">-PassThru</span> <span class="p">|</span> <span class="nb">Start-Service</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c"># 加入金鑰</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nb">ssh-add</span> <span class="s2">&#34;</span><span class="nv">$env:USERPROFILE</span><span class="s2">\.ssh\&lt;key_name&gt;&#34;</span></span></span></code></pre></div><blockquote>
<p>Windows 的 <code>ssh-agent</code> 是系統服務，啟用後重開機也會自動執行，不需額外設定。</p></blockquote>
<hr>
<h2 id="4-測試連線">4. 測試連線</h2>
<p>三個平台指令相同：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">ssh -i ~/.ssh/&lt;key_name&gt; -T git@&lt;host&gt;</span></span></code></pre></div><blockquote>
<p>Windows 請在 PowerShell 或 Git Bash 中執行，路徑會自動對應到 <code>$env:USERPROFILE\.ssh\</code>。</p></blockquote>
<hr>
<h2 id="備註">備註</h2>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>macOS</th>
          <th>Linux</th>
          <th>Windows</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>金鑰路徑</td>
          <td><code>~/.ssh/</code></td>
          <td><code>~/.ssh/</code></td>
          <td><code>C:\Users\&lt;使用者&gt;\.ssh\</code></td>
      </tr>
      <tr>
          <td>權限設定</td>
          <td><code>chmod 600</code></td>
          <td><code>chmod 600</code></td>
          <td><code>icacls</code> 移除繼承</td>
      </tr>
      <tr>
          <td>Agent 持久化</td>
          <td>Keychain</td>
          <td>需加入 shell rc</td>
          <td>系統服務，自動持久</td>
      </tr>
      <tr>
          <td>預裝 SSH</td>
          <td>是</td>
          <td>大多數發行版已預裝</td>
          <td>Windows 10 1809+ 內建 OpenSSH</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="安全性建議">安全性建議</h2>
<h3 id="優先使用-ed25519">優先使用 Ed25519</h3>
<table>
  <thead>
      <tr>
          <th>演算法</th>
          <th>金鑰長度</th>
          <th>安全性</th>
          <th>效能</th>
          <th>相容性</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Ed25519</strong></td>
          <td>256 bit</td>
          <td>高</td>
          <td>最快</td>
          <td>2014 年後的 OpenSSH 6.5+</td>
      </tr>
      <tr>
          <td>RSA-4096</td>
          <td>4096 bit</td>
          <td>高</td>
          <td>較慢</td>
          <td>最廣泛，適合舊系統</td>
      </tr>
      <tr>
          <td>ECDSA</td>
          <td>256/384/521 bit</td>
          <td>中高</td>
          <td>快</td>
          <td>已被 Ed25519 取代</td>
      </tr>
      <tr>
          <td>DSA</td>
          <td>1024 bit</td>
          <td>低</td>
          <td>-</td>
          <td>已棄用，OpenSSH 7.0+ 預設停用</td>
      </tr>
  </tbody>
</table>
<h3 id="為私鑰設定-passphrase">為私鑰設定 Passphrase</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 為已存在的金鑰補設或更換 passphrase</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">ssh-keygen -p -f ~/.ssh/&lt;key_name&gt;</span></span></code></pre></div><p>即使私鑰檔案被他人取得，沒有 passphrase 就無法使用。搭配 SSH Agent 後只需輸入一次，不影響日常使用體驗。</p>
<h3 id="定期輪換金鑰">定期輪換金鑰</h3>
<p>建議每 1-2 年輪換一次 SSH 金鑰。可在金鑰名稱中加入年份作為提醒，例如 <code>id_ed25519_2026</code>。</p>
<h3 id="其他注意事項">其他注意事項</h3>
<ul>
<li><strong>不要將私鑰上傳到雲端同步服務</strong>（如 iCloud、Google Drive、OneDrive），除非經過加密</li>
<li><strong>不要在多台機器之間複製同一把私鑰</strong>，應為每台機器各自產生獨立金鑰</li>
<li><strong>避免使用已棄用的 DSA 金鑰</strong>，部分新版 OpenSSH 已預設拒絕 DSA</li>
<li><strong><code>~/.ssh/</code> 目錄本身權限應為 <code>700</code></strong>，<code>authorized_keys</code> 應為 <code>600</code></li>
</ul>
<hr>
<h2 id="常見問題排查">常見問題排查</h2>
<h3 id="permission-denied-publickey">Permission denied (publickey)</h3>
<ol>
<li>確認私鑰權限：<code>chmod 600 ~/.ssh/&lt;key_name&gt;</code></li>
<li>確認 <code>~/.ssh/</code> 目錄權限：<code>chmod 700 ~/.ssh</code></li>
<li>確認上傳的是 <strong>公鑰</strong>（<code>.pub</code>）而非私鑰</li>
<li>使用 verbose 模式查看詳細錯誤：<code>ssh -vvv user@host</code></li>
</ol>
<h3 id="agent-中沒有金鑰">Agent 中沒有金鑰</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 確認 agent 是否正在運行</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">ssh-add -l
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 如果顯示 &#34;Could not open a connection to your authentication agent&#34;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nb">eval</span> <span class="s2">&#34;</span><span class="k">$(</span>ssh-agent -s<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">ssh-add ~/.ssh/&lt;key_name&gt;</span></span></code></pre></div><h3 id="金鑰類型被伺服器拒絕">金鑰類型被伺服器拒絕</h3>
<p>部分較新的伺服器已停用 DSA 和較短的 RSA 金鑰。檢查方式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">ssh -vvv user@host 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">|</span> grep <span class="s2">&#34;Offering&#34;</span></span></span></code></pre></div><p>如果你的金鑰類型不在伺服器接受的清單中，需要重新產生 Ed25519 金鑰。</p>
]]></content:encoded></item></channel></rss>