<?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>Debian on Tarragon</title><link>https://tarrragon.github.io/blog/tags/debian/</link><description>Recent content in Debian on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Mon, 06 Jul 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/debian/index.xml" rel="self" type="application/rss+xml"/><item><title>工作站 dotfile 跨發行版落地</title><link>https://tarrragon.github.io/blog/linux/dotfile/10-prod-parity/workstation-cross-distro/</link><pubDate>Mon, 06 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/10-prod-parity/workstation-cross-distro/</guid><description>&lt;p>這篇的目的是讓一份照 Arch 寫的工作站 dotfile，能在 client 常見的 Debian/Ubuntu 機器上還原出同樣的環境。要改的部分比想像少：綁發行版的只有「用哪個 package manager 裝套件」這一層，config 檔內容、symlink 部署、shell 框架安裝全部可攜。原理見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/package-manager-abstraction/" data-link-title="Package Manager 抽象層" data-link-desc="同一份 dotfile 要從 Arch 跨到 Debian/macOS、不確定哪些要改哪些能共用時回來讀 — 綁 distro 的只有 package manager 這一層">Package Manager 抽象層&lt;/a>，這裡只講怎麼判斷跟怎麼落地。&lt;/p>
&lt;h2 id="哪些綁-distro哪些共用">哪些綁 distro、哪些共用&lt;/h2>
&lt;p>拿到一份 dotfile 要跨 distro 時，先把內容分成兩堆，判準是「換 distro 會不會變」：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>會變、要下沉到平台層&lt;/strong>：套件安裝指令（&lt;code>pacman&lt;/code> vs &lt;code>apt-get&lt;/code>）、套件清單、以及套件名分歧的工具。&lt;/li>
&lt;li>&lt;strong>不會變、留共通層&lt;/strong>：&lt;code>.zshrc&lt;/code> 內容、&lt;code>stow&lt;/code> 建 symlink 的邏輯、oh-my-zsh 的 &lt;code>git clone&lt;/code>、所有 config 檔本身。&lt;/li>
&lt;/ul>
&lt;p>判斷錯的代價是把套件名寫死在共通層——換 distro 就壞。正確形態是共通層只呼叫「裝套件」這個抽象動作，具體套件名交給各平台清單。&lt;/p>
&lt;h2 id="落地入口-detection--平台清單">落地：入口 detection + 平台清單&lt;/h2>
&lt;p>工作站的套件集在不同 distro 差異大，適合用入口 detection：安裝腳本開頭偵測 package manager，dispatch 到對應的平台腳本。dotfiles repo 的形態是 &lt;code>install.sh&lt;/code> 判斷 OS 後委派給 &lt;code>install-arch.sh&lt;/code> / &lt;code>install-macos.sh&lt;/code> / &lt;code>install-debian.sh&lt;/code>，套件清單各一份（&lt;code>packages/arch-*.txt&lt;/code>、&lt;code>packages/debian-*.txt&lt;/code>），共通的環境組裝（stow、框架 clone）留在 &lt;code>install.sh&lt;/code>。&lt;/p>
&lt;p>跨到 Debian 只需要補一支 &lt;code>install-debian.sh&lt;/code> 跟對應清單，共通層一行都不用動——這正是抽象層有效的證據。&lt;/p>
&lt;h2 id="套件名分歧裝好不等於叫得動">套件名分歧：裝好不等於叫得動&lt;/h2>
&lt;p>同一個工具在不同 distro 的名字有好幾種分歧（有哪些形態、為什麼，見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/package-manager-abstraction/" data-link-title="Package Manager 抽象層" data-link-desc="同一份 dotfile 要從 Arch 跨到 Debian/macOS、不確定哪些要改哪些能共用時回來讀 — 綁 distro 的只有 package manager 這一層">Package Manager 抽象層&lt;/a>）。落地時按形態各自處理：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>套件名不同&lt;/strong>（&lt;code>fd&lt;/code> → &lt;code>fd-find&lt;/code>、&lt;code>github-cli&lt;/code> → &lt;code>gh&lt;/code>）：靠各平台清單逐項對照，不假設同名。&lt;/li>
&lt;li>&lt;strong>binary 被改名&lt;/strong>（&lt;code>fd-find&lt;/code> 的 binary 是 &lt;code>fdfind&lt;/code>、&lt;code>bat&lt;/code> 是 &lt;code>batcat&lt;/code>）：&lt;code>.zshrc&lt;/code> 按平台補 alias（&lt;code>alias fd=fdfind&lt;/code>）——「套件裝好了」不等於「指令叫得動」。&lt;/li>
&lt;li>&lt;strong>名字撞到別的工具&lt;/strong>（&lt;code>apt install delta&lt;/code> 裝到的不是 &lt;code>.gitconfig&lt;/code> 要的 git-delta）：別照抄，確認同名同物。&lt;/li>
&lt;/ul>
&lt;p>&lt;code>ripgrep&lt;/code> 兩邊同名（binary 都是 &lt;code>rg&lt;/code>）不用處理，&lt;code>fd&lt;/code> / &lt;code>bat&lt;/code> 就要按上面吸收。&lt;/p>
&lt;h2 id="實作會遇到的狀況與排除">實作會遇到的狀況與排除&lt;/h2>
&lt;p>跨到 Debian 實跑時，除了名字分歧，還有幾個操作面的狀況會擋住 bootstrap。先知道怎麼排除：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>全新映像連 sudo/git 都沒有&lt;/strong>。乾淨的 Debian 映像預裝極簡，&lt;code>install.sh&lt;/code> 要用到的 &lt;code>sudo&lt;/code>、&lt;code>git&lt;/code> 本身都缺。排除：bootstrap 第一步先 &lt;code>apt-get install -y sudo git&lt;/code>，才有辦法 clone repo、跑後續。這是最小映像的預期形態，不是壞掉。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>清單裡一個沒打包的名字讓整批全滅&lt;/strong>。&lt;code>apt-get install&lt;/code> 是一筆全有或全無的交易，清單塞一個 Debian 沒有的名字（實測 broot、zellij 在 bookworm 沒打包），apt 直接中止，同批存在的工具也一個都沒裝。排除：清單逐項對齊該 distro 實際有的套件，動手前用 &lt;code>apt-get install -s &amp;lt;pkg&amp;gt;&lt;/code> 逐一 dry-run 篩掉解不開的。原理見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/apt-transaction-atomicity/" data-link-title="apt 安裝的交易原子性" data-link-desc="apt-get install 一次帶多個套件、其中一個名字沒打包卻導致整批都沒裝時回來讀 — apt 為什麼全有或全無">apt 安裝的交易原子性&lt;/a>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>某些工具 Debian 根本沒打包&lt;/strong>。broot、zellij、git-delta、lazygit、yazi 在 bookworm 預設 repo 都不存在（Arch 都有）。排除：把它們移出 apt 清單，改從 GitHub releases 裝——抓對應架構的預編譯檔（&lt;code>curl -L -O &amp;lt;release-url&amp;gt;&lt;/code>）、解壓、&lt;code>chmod +x&lt;/code>、&lt;code>mv&lt;/code> 到 &lt;code>~/.local/bin&lt;/code>（確認它在 &lt;code>PATH&lt;/code>）；若走 &lt;code>cargo install&lt;/code>，cargo 本身要先用 rustup 裝（裸 Debian 沒有）。原理見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/distro-package-granularity/" data-link-title="發行版打包粒度" data-link-desc="同一個 install 指令在不同發行版拉的套件數差一個量級、或某工具在某發行版根本沒打包時回來讀 — 發行版怎麼切套件顆粒度">發行版打包粒度&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>這篇的目的是讓一份照 Arch 寫的工作站 dotfile，能在 client 常見的 Debian/Ubuntu 機器上還原出同樣的環境。要改的部分比想像少：綁發行版的只有「用哪個 package manager 裝套件」這一層，config 檔內容、symlink 部署、shell 框架安裝全部可攜。原理見 <a href="/blog/linux/dotfile/knowledge-cards/package-manager-abstraction/" data-link-title="Package Manager 抽象層" data-link-desc="同一份 dotfile 要從 Arch 跨到 Debian/macOS、不確定哪些要改哪些能共用時回來讀 — 綁 distro 的只有 package manager 這一層">Package Manager 抽象層</a>，這裡只講怎麼判斷跟怎麼落地。</p>
<h2 id="哪些綁-distro哪些共用">哪些綁 distro、哪些共用</h2>
<p>拿到一份 dotfile 要跨 distro 時，先把內容分成兩堆，判準是「換 distro 會不會變」：</p>
<ul>
<li><strong>會變、要下沉到平台層</strong>：套件安裝指令（<code>pacman</code> vs <code>apt-get</code>）、套件清單、以及套件名分歧的工具。</li>
<li><strong>不會變、留共通層</strong>：<code>.zshrc</code> 內容、<code>stow</code> 建 symlink 的邏輯、oh-my-zsh 的 <code>git clone</code>、所有 config 檔本身。</li>
</ul>
<p>判斷錯的代價是把套件名寫死在共通層——換 distro 就壞。正確形態是共通層只呼叫「裝套件」這個抽象動作，具體套件名交給各平台清單。</p>
<h2 id="落地入口-detection--平台清單">落地：入口 detection + 平台清單</h2>
<p>工作站的套件集在不同 distro 差異大，適合用入口 detection：安裝腳本開頭偵測 package manager，dispatch 到對應的平台腳本。dotfiles repo 的形態是 <code>install.sh</code> 判斷 OS 後委派給 <code>install-arch.sh</code> / <code>install-macos.sh</code> / <code>install-debian.sh</code>，套件清單各一份（<code>packages/arch-*.txt</code>、<code>packages/debian-*.txt</code>），共通的環境組裝（stow、框架 clone）留在 <code>install.sh</code>。</p>
<p>跨到 Debian 只需要補一支 <code>install-debian.sh</code> 跟對應清單，共通層一行都不用動——這正是抽象層有效的證據。</p>
<h2 id="套件名分歧裝好不等於叫得動">套件名分歧：裝好不等於叫得動</h2>
<p>同一個工具在不同 distro 的名字有好幾種分歧（有哪些形態、為什麼，見 <a href="/blog/linux/dotfile/knowledge-cards/package-manager-abstraction/" data-link-title="Package Manager 抽象層" data-link-desc="同一份 dotfile 要從 Arch 跨到 Debian/macOS、不確定哪些要改哪些能共用時回來讀 — 綁 distro 的只有 package manager 這一層">Package Manager 抽象層</a>）。落地時按形態各自處理：</p>
<ul>
<li><strong>套件名不同</strong>（<code>fd</code> → <code>fd-find</code>、<code>github-cli</code> → <code>gh</code>）：靠各平台清單逐項對照，不假設同名。</li>
<li><strong>binary 被改名</strong>（<code>fd-find</code> 的 binary 是 <code>fdfind</code>、<code>bat</code> 是 <code>batcat</code>）：<code>.zshrc</code> 按平台補 alias（<code>alias fd=fdfind</code>）——「套件裝好了」不等於「指令叫得動」。</li>
<li><strong>名字撞到別的工具</strong>（<code>apt install delta</code> 裝到的不是 <code>.gitconfig</code> 要的 git-delta）：別照抄，確認同名同物。</li>
</ul>
<p><code>ripgrep</code> 兩邊同名（binary 都是 <code>rg</code>）不用處理，<code>fd</code> / <code>bat</code> 就要按上面吸收。</p>
<h2 id="實作會遇到的狀況與排除">實作會遇到的狀況與排除</h2>
<p>跨到 Debian 實跑時，除了名字分歧，還有幾個操作面的狀況會擋住 bootstrap。先知道怎麼排除：</p>
<ul>
<li>
<p><strong>全新映像連 sudo/git 都沒有</strong>。乾淨的 Debian 映像預裝極簡，<code>install.sh</code> 要用到的 <code>sudo</code>、<code>git</code> 本身都缺。排除：bootstrap 第一步先 <code>apt-get install -y sudo git</code>，才有辦法 clone repo、跑後續。這是最小映像的預期形態，不是壞掉。</p>
</li>
<li>
<p><strong>清單裡一個沒打包的名字讓整批全滅</strong>。<code>apt-get install</code> 是一筆全有或全無的交易，清單塞一個 Debian 沒有的名字（實測 broot、zellij 在 bookworm 沒打包），apt 直接中止，同批存在的工具也一個都沒裝。排除：清單逐項對齊該 distro 實際有的套件，動手前用 <code>apt-get install -s &lt;pkg&gt;</code> 逐一 dry-run 篩掉解不開的。原理見 <a href="/blog/linux/dotfile/knowledge-cards/apt-transaction-atomicity/" data-link-title="apt 安裝的交易原子性" data-link-desc="apt-get install 一次帶多個套件、其中一個名字沒打包卻導致整批都沒裝時回來讀 — apt 為什麼全有或全無">apt 安裝的交易原子性</a>。</p>
</li>
<li>
<p><strong>某些工具 Debian 根本沒打包</strong>。broot、zellij、git-delta、lazygit、yazi 在 bookworm 預設 repo 都不存在（Arch 都有）。排除：把它們移出 apt 清單，改從 GitHub releases 裝——抓對應架構的預編譯檔（<code>curl -L -O &lt;release-url&gt;</code>）、解壓、<code>chmod +x</code>、<code>mv</code> 到 <code>~/.local/bin</code>（確認它在 <code>PATH</code>）；若走 <code>cargo install</code>，cargo 本身要先用 rustup 裝（裸 Debian 沒有）。原理見 <a href="/blog/linux/dotfile/knowledge-cards/distro-package-granularity/" data-link-title="發行版打包粒度" data-link-desc="同一個 install 指令在不同發行版拉的套件數差一個量級、或某工具在某發行版根本沒打包時回來讀 — 發行版怎麼切套件顆粒度">發行版打包粒度</a>。</p>
</li>
<li>
<p><strong>裝個 node 拉進幾百個系統套件</strong>。<code>apt install nodejs npm</code> 在 Debian 會連帶 300 多個 <code>node-*</code> 套件（實測 bookworm 一次裝進 402 個新套件、其中 336 個是 node-*）。要避免的是拿系統套件管理器裝語言執行環境。排除：node / python / ruby 這類語言生態走 version manager（fnm / pyenv）在家目錄管，別放進 apt 清單。原理見 <a href="/blog/linux/dotfile/knowledge-cards/distro-package-granularity/" data-link-title="發行版打包粒度" data-link-desc="同一個 install 指令在不同發行版拉的套件數差一個量級、或某工具在某發行版根本沒打包時回來讀 — 發行版怎麼切套件顆粒度">發行版打包粒度</a>。</p>
</li>
</ul>
<h2 id="邊界與下一步">邊界與下一步</h2>
<p>桌面層（Hyprland 的 <a href="/blog/linux/dotfile/knowledge-cards/rice/" data-link-title="Rice（桌面視覺客製化）" data-link-desc="Linux 桌面文章裡看到 rice / ricing / ricer 不確定意思時回來讀">rice</a>，即桌面視覺客製化）的跨 distro 成本遠高於 CLI 層：Hyprland 在 Debian 要較新版才有打包，套件名與可用性都要重驗，所以工作站跨 distro 通常只做到 terminal 層，桌面留在實測過的 Arch。</p>
<p>工作站跨好之後，開發還缺一個對齊 client 線上的 runtime——那是下一篇 <a href="/blog/linux/dotfile/10-prod-parity/prod-parity-runtime/" data-link-title="對齊 prod 的 runtime container" data-link-desc="要開發一個線上跑 PHP 7.2 / MySQL 5.7 舊環境的專案、或要在本機重現線上事故時回來讀 — 對齊哪些維度、怎麼從線上抄設定、什麼時候值得">對齊 prod 的 runtime container</a>。工作站是你的機器、可以最新；runtime 要退回線上那個凍結的舊形狀，兩者對齊的目標相反。</p>
]]></content:encoded></item><item><title>發行版打包粒度</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/distro-package-granularity/</link><pubDate>Mon, 06 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/distro-package-granularity/</guid><description>&lt;p>發行版打包粒度是「一個發行版把軟體切成多細的系統套件」的策略。它決定同一個安裝指令的後果量級：有的發行版把每個 library 都包成獨立系統套件，有的把 library 留給語言自己的套件管理器管。同一個工具在前者可能拉進幾百個系統套件、在後者只拉幾個。這條粒度差異坐落在 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/package-manager-abstraction/" data-link-title="Package Manager 抽象層" data-link-desc="同一份 dotfile 要從 Arch 跨到 Debian/macOS、不確定哪些要改哪些能共用時回來讀 — 綁 distro 的只有 package manager 這一層">Package Manager 抽象層&lt;/a> 底下、是各發行版打包策略的具體展開。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>這條跟 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/package-manager-abstraction/" data-link-title="Package Manager 抽象層" data-link-desc="同一份 dotfile 要從 Arch 跨到 Debian/macOS、不確定哪些要改哪些能共用時回來讀 — 綁 distro 的只有 package manager 這一層">Package Manager 抽象層&lt;/a> 互補：抽象層講「同一件事換 backend 怎麼寫」，這張講「同一個指令換 backend 後果量級差多少」。一個爛名字為何拖垮整批安裝，見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/apt-transaction-atomicity/" data-link-title="apt 安裝的交易原子性" data-link-desc="apt-get install 一次帶多個套件、其中一個名字沒打包卻導致整批都沒裝時回來讀 — apt 為什麼全有或全無">apt 安裝的交易原子性&lt;/a>。實作上怎麼判斷該不該進 apt 清單，見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/10-prod-parity/workstation-cross-distro/" data-link-title="工作站 dotfile 跨發行版落地" data-link-desc="手上 dotfile 是照 Arch 寫的、要在 client 常見的 Debian/Ubuntu 機器上還原工作環境時回來讀 — 哪些綁 pacman、哪些能直接共用">工作站 dotfile 跨發行版落地&lt;/a>。&lt;/p>
&lt;h2 id="兩種粒度">兩種粒度&lt;/h2>
&lt;p>以 nodejs + npm 為例，同樣一個安裝動作在兩種發行版的展開差一個量級（Debian bookworm 實測）：&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">Debian apt install nodejs npm → 連帶 402 個新套件（其中 336 個 node-*，eslint、node-glob…每個 JS lib 一個 .deb）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">Arch pacman -S npm → 只拉少數幾個；JS library 交給 npm 在 node_modules 管&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Debian 的哲學是「所有東西都是系統套件」，所以它把 nodejs / npm 工具鏈本身的相依也拆成上百個 &lt;code>node-*&lt;/code> deb，一次 &lt;code>apt install npm&lt;/code> 就全灌進系統。要分清楚：這些 &lt;code>node-*&lt;/code> 是 Debian 為了打包 node 工具鏈而拉進的&lt;strong>系統層相依&lt;/strong>，跟你專案 &lt;code>npm install&lt;/code> 進 &lt;code>node_modules&lt;/code> 的依賴無關——後者不論哪個 distro 都走 npm、從不碰 apt。所以「402 套件」講的是系統工具鏈膨脹，不是應用依賴。Arch 不把這層 library 納入系統套件，同一個 npm 只在系統層多幾個檔案。&lt;/p>
&lt;h2 id="為什麼有的工具乾脆沒打包">為什麼有的工具乾脆沒打包&lt;/h2>
&lt;p>粒度策略也決定「哪些工具進得了官方 repo」。移動快速的新工具（多為 Rust 寫的 CLI）在保守、定版凍結的發行版常常還沒被打包：broot、zellij、git-delta、lazygit、yazi 在 Debian bookworm 的預設 repo 都不存在，但在 Arch 都有。發行版愈保守（stable 凍結愈久），這類缺口愈多。&lt;/p>
&lt;h2 id="repo-成員資格會隨時間漂移">repo 成員資格會隨時間漂移&lt;/h2>
&lt;p>「沒打包」不只是「保守發行版從沒收」的靜態狀態，也可能是「曾經收、後來被移出」的時間變化。官方 repo 會把不再維護的套件汰除：autojump（目錄跳轉工具）一度在 Arch 官方 repo，後來被移到 AUR，現在 &lt;code>pacman -S autojump&lt;/code> 回 &lt;code>target not found&lt;/code>——同一個發行版、同一個套件名，只是時間軸上的成員資格變了（維護版替代是 zoxide，仍在官方 repo）。這讓一份釘死的套件清單即使不換發行版也會腐化：清單寫的當下該套件在官方 repo、幾年後被移出，清單就在新機器上裝不起來。判讀是：&lt;code>target not found&lt;/code> 且你確定名字沒打錯、以前明明裝得起來，就往「它被移出官方 repo 了」查（多半移到 AUR，或改由上游自行發佈 binary）。若整批一次裝，一個被移出的名字還會拖垮整批（見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/apt-transaction-atomicity/" data-link-title="apt 安裝的交易原子性" data-link-desc="apt-get install 一次帶多個套件、其中一個名字沒打包卻導致整批都沒裝時回來讀 — apt 為什麼全有或全無">apt 安裝的交易原子性&lt;/a>）。&lt;/p>
&lt;h2 id="判讀訊號">判讀訊號&lt;/h2>
&lt;p>安裝一個工具卻拉進幾百個套件時，先問「這些是不是某個語言生態的 library」。是的話，通常代表這東西不該走系統套件管理器——語言生態的執行環境（node、python、ruby）交給 version manager（fnm / pyenv / rbenv）在家目錄管更乾淨：可切版本、不污染系統、不被發行版凍在舊版。系統套件管理器留給「系統層工具」本身。&lt;/p></description><content:encoded><![CDATA[<p>發行版打包粒度是「一個發行版把軟體切成多細的系統套件」的策略。它決定同一個安裝指令的後果量級：有的發行版把每個 library 都包成獨立系統套件，有的把 library 留給語言自己的套件管理器管。同一個工具在前者可能拉進幾百個系統套件、在後者只拉幾個。這條粒度差異坐落在 <a href="/blog/linux/dotfile/knowledge-cards/package-manager-abstraction/" data-link-title="Package Manager 抽象層" data-link-desc="同一份 dotfile 要從 Arch 跨到 Debian/macOS、不確定哪些要改哪些能共用時回來讀 — 綁 distro 的只有 package manager 這一層">Package Manager 抽象層</a> 底下、是各發行版打包策略的具體展開。</p>
<h2 id="概念位置">概念位置</h2>
<p>這條跟 <a href="/blog/linux/dotfile/knowledge-cards/package-manager-abstraction/" data-link-title="Package Manager 抽象層" data-link-desc="同一份 dotfile 要從 Arch 跨到 Debian/macOS、不確定哪些要改哪些能共用時回來讀 — 綁 distro 的只有 package manager 這一層">Package Manager 抽象層</a> 互補：抽象層講「同一件事換 backend 怎麼寫」，這張講「同一個指令換 backend 後果量級差多少」。一個爛名字為何拖垮整批安裝，見 <a href="/blog/linux/dotfile/knowledge-cards/apt-transaction-atomicity/" data-link-title="apt 安裝的交易原子性" data-link-desc="apt-get install 一次帶多個套件、其中一個名字沒打包卻導致整批都沒裝時回來讀 — apt 為什麼全有或全無">apt 安裝的交易原子性</a>。實作上怎麼判斷該不該進 apt 清單，見 <a href="/blog/linux/dotfile/10-prod-parity/workstation-cross-distro/" data-link-title="工作站 dotfile 跨發行版落地" data-link-desc="手上 dotfile 是照 Arch 寫的、要在 client 常見的 Debian/Ubuntu 機器上還原工作環境時回來讀 — 哪些綁 pacman、哪些能直接共用">工作站 dotfile 跨發行版落地</a>。</p>
<h2 id="兩種粒度">兩種粒度</h2>
<p>以 nodejs + npm 為例，同樣一個安裝動作在兩種發行版的展開差一個量級（Debian bookworm 實測）：</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">Debian   apt install nodejs npm   → 連帶 402 個新套件（其中 336 個 node-*，eslint、node-glob…每個 JS lib 一個 .deb）
</span></span><span class="line"><span class="ln">2</span><span class="cl">Arch     pacman -S npm            → 只拉少數幾個；JS library 交給 npm 在 node_modules 管</span></span></code></pre></div><p>Debian 的哲學是「所有東西都是系統套件」，所以它把 nodejs / npm 工具鏈本身的相依也拆成上百個 <code>node-*</code> deb，一次 <code>apt install npm</code> 就全灌進系統。要分清楚：這些 <code>node-*</code> 是 Debian 為了打包 node 工具鏈而拉進的<strong>系統層相依</strong>，跟你專案 <code>npm install</code> 進 <code>node_modules</code> 的依賴無關——後者不論哪個 distro 都走 npm、從不碰 apt。所以「402 套件」講的是系統工具鏈膨脹，不是應用依賴。Arch 不把這層 library 納入系統套件，同一個 npm 只在系統層多幾個檔案。</p>
<h2 id="為什麼有的工具乾脆沒打包">為什麼有的工具乾脆沒打包</h2>
<p>粒度策略也決定「哪些工具進得了官方 repo」。移動快速的新工具（多為 Rust 寫的 CLI）在保守、定版凍結的發行版常常還沒被打包：broot、zellij、git-delta、lazygit、yazi 在 Debian bookworm 的預設 repo 都不存在，但在 Arch 都有。發行版愈保守（stable 凍結愈久），這類缺口愈多。</p>
<h2 id="repo-成員資格會隨時間漂移">repo 成員資格會隨時間漂移</h2>
<p>「沒打包」不只是「保守發行版從沒收」的靜態狀態，也可能是「曾經收、後來被移出」的時間變化。官方 repo 會把不再維護的套件汰除：autojump（目錄跳轉工具）一度在 Arch 官方 repo，後來被移到 AUR，現在 <code>pacman -S autojump</code> 回 <code>target not found</code>——同一個發行版、同一個套件名，只是時間軸上的成員資格變了（維護版替代是 zoxide，仍在官方 repo）。這讓一份釘死的套件清單即使不換發行版也會腐化：清單寫的當下該套件在官方 repo、幾年後被移出，清單就在新機器上裝不起來。判讀是：<code>target not found</code> 且你確定名字沒打錯、以前明明裝得起來，就往「它被移出官方 repo 了」查（多半移到 AUR，或改由上游自行發佈 binary）。若整批一次裝，一個被移出的名字還會拖垮整批（見 <a href="/blog/linux/dotfile/knowledge-cards/apt-transaction-atomicity/" data-link-title="apt 安裝的交易原子性" data-link-desc="apt-get install 一次帶多個套件、其中一個名字沒打包卻導致整批都沒裝時回來讀 — apt 為什麼全有或全無">apt 安裝的交易原子性</a>）。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<p>安裝一個工具卻拉進幾百個套件時，先問「這些是不是某個語言生態的 library」。是的話，通常代表這東西不該走系統套件管理器——語言生態的執行環境（node、python、ruby）交給 version manager（fnm / pyenv / rbenv）在家目錄管更乾淨：可切版本、不污染系統、不被發行版凍在舊版。系統套件管理器留給「系統層工具」本身。</p>
<p>反過來，某工具 <code>apt install</code> 直接 unable to locate 時，多半是它還沒進這個發行版的 repo，不是名字打錯——退回 GitHub releases 的預編譯 binary 或 <code>cargo install</code>。</p>
<h2 id="邊界">邊界</h2>
<p>粒度細不是缺點：Debian 把 library 拆成系統套件，換來的是每個 library 都能收到發行版的安全更新與依賴一致性保證，這對伺服器場景有價值。問題只出在「拿它裝語言生態的開發依賴」——那是 version manager 的場域。判斷依用途，不是依發行版好壞。</p>
]]></content:encoded></item><item><title>apt 安裝的交易原子性</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/apt-transaction-atomicity/</link><pubDate>Mon, 06 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/apt-transaction-atomicity/</guid><description>&lt;p>apt 的安裝是一筆交易（transaction）：&lt;code>apt-get install a b c&lt;/code> 會先把所有指定套件連同它們的依賴一起解析成一份完整的安裝計畫，全部解析成功才開始下載安裝；只要其中一個套件解析不到（名字不存在、沒打包、依賴衝突），整筆交易放棄，一個都不裝。apt 是 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/package-manager-abstraction/" data-link-title="Package Manager 抽象層" data-link-desc="同一份 dotfile 要從 Arch 跨到 Debian/macOS、不確定哪些要改哪些能共用時回來讀 — 綁 distro 的只有 package manager 這一層">Package Manager 抽象層&lt;/a> 底下各發行版套件管理員之一，這個交易行為是 apt 特有的實作細節。&lt;/p>
&lt;h2 id="解析與安裝是兩階段">解析與安裝是兩階段&lt;/h2>
&lt;p>apt 把「算出要裝什麼」跟「實際裝」分成先後兩步：&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">階段一 解析：把 a b c + 全部依賴解成一份計畫；任一個解不到 → 整筆 abort，什麼都沒動
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">階段二 安裝：計畫完整才下載、解包、設定&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這是刻意的設計。系統套件之間有依賴關係，半裝的狀態（裝了 a、b 因為 c 失敗而中途停）會留下依賴不完整的系統，比「全部沒裝」更難收拾。全有或全無讓系統永遠停在一致的狀態。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>這解釋了為什麼跨發行版的套件清單要 curate、不能照抄，跟 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/package-manager-abstraction/" data-link-title="Package Manager 抽象層" data-link-desc="同一份 dotfile 要從 Arch 跨到 Debian/macOS、不確定哪些要改哪些能共用時回來讀 — 綁 distro 的只有 package manager 這一層">Package Manager 抽象層&lt;/a> 的「套件名分歧要逐項吸收」是同一件事的兩面。哪些工具在保守發行版沒打包，見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/distro-package-granularity/" data-link-title="發行版打包粒度" data-link-desc="同一個 install 指令在不同發行版拉的套件數差一個量級、或某工具在某發行版根本沒打包時回來讀 — 發行版怎麼切套件顆粒度">發行版打包粒度&lt;/a>。清單 curate 的實作見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/10-prod-parity/workstation-cross-distro/" data-link-title="工作站 dotfile 跨發行版落地" data-link-desc="手上 dotfile 是照 Arch 寫的、要在 client 常見的 Debian/Ubuntu 機器上還原工作環境時回來讀 — 哪些綁 pacman、哪些能直接共用">工作站 dotfile 跨發行版落地&lt;/a>。&lt;/p>
&lt;h2 id="一個爛名字全滅">一個爛名字全滅&lt;/h2>
&lt;p>實務上最容易撞到的後果：批次清單裡塞了一個該發行版沒有的套件名，整批就全部不裝。實測在 Debian bookworm 跑一份含 broot、zellij（兩者 bookworm 沒打包）的清單，apt 回 &lt;code>Unable to locate package broot / zellij&lt;/code> 後直接中止，同批的 autojump、gh、tig 明明存在也一個都沒裝。&lt;/p>
&lt;p>症狀是「我明明列了十個工具、跑完一個都沒有」，根因不是每個都失敗，是其中一兩個爛名字讓整筆交易 abort。&lt;/p>
&lt;h2 id="判讀訊號">判讀訊號&lt;/h2>
&lt;p>批次安裝清單必須逐項對齊該發行版實際有的套件，不能從別的發行版直接抄名字過來。動手前用 dry-run 先驗：&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">apt-get install -s a b c &lt;span class="c1"># -s 模擬，只解析不安裝，先看有沒有 Unable to locate&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>一個一個 dry-run 能精準指出哪個名字解不開，再把它移出批次清單、或換成該發行版的正確名字。&lt;/p>
&lt;h2 id="邊界">邊界&lt;/h2>
&lt;p>pacman 的批次安裝有類似的原子性——實測 &lt;code>pacman -S&lt;/code> 一份含 autojump 的清單，因 autojump 已從 Arch 官方 repo 移到 AUR、回 &lt;code>target not found&lt;/code>，整批就 abort，同清單裡本來裝得起來的 zoxide、ripgrep、fd 一個都沒裝（這種「曾在官方 repo、後來被移出」的漂移見 &lt;a href="https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/distro-package-granularity/" data-link-title="發行版打包粒度" data-link-desc="同一個 install 指令在不同發行版拉的套件數差一個量級、或某工具在某發行版根本沒打包時回來讀 — 發行版怎麼切套件顆粒度">發行版打包粒度&lt;/a>）。想繞過「一個失敗全滅」可以逐包安裝（迴圈裡一個一個裝、失敗的跳過），但那會失去交易的一致性保證、也讓「哪些沒裝成功」變得難追。多數情況正解是把清單修對，而不是放棄原子性。&lt;/p></description><content:encoded><![CDATA[<p>apt 的安裝是一筆交易（transaction）：<code>apt-get install a b c</code> 會先把所有指定套件連同它們的依賴一起解析成一份完整的安裝計畫，全部解析成功才開始下載安裝；只要其中一個套件解析不到（名字不存在、沒打包、依賴衝突），整筆交易放棄，一個都不裝。apt 是 <a href="/blog/linux/dotfile/knowledge-cards/package-manager-abstraction/" data-link-title="Package Manager 抽象層" data-link-desc="同一份 dotfile 要從 Arch 跨到 Debian/macOS、不確定哪些要改哪些能共用時回來讀 — 綁 distro 的只有 package manager 這一層">Package Manager 抽象層</a> 底下各發行版套件管理員之一，這個交易行為是 apt 特有的實作細節。</p>
<h2 id="解析與安裝是兩階段">解析與安裝是兩階段</h2>
<p>apt 把「算出要裝什麼」跟「實際裝」分成先後兩步：</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">階段一 解析：把 a b c + 全部依賴解成一份計畫；任一個解不到 → 整筆 abort，什麼都沒動
</span></span><span class="line"><span class="ln">2</span><span class="cl">階段二 安裝：計畫完整才下載、解包、設定</span></span></code></pre></div><p>這是刻意的設計。系統套件之間有依賴關係，半裝的狀態（裝了 a、b 因為 c 失敗而中途停）會留下依賴不完整的系統，比「全部沒裝」更難收拾。全有或全無讓系統永遠停在一致的狀態。</p>
<h2 id="概念位置">概念位置</h2>
<p>這解釋了為什麼跨發行版的套件清單要 curate、不能照抄，跟 <a href="/blog/linux/dotfile/knowledge-cards/package-manager-abstraction/" data-link-title="Package Manager 抽象層" data-link-desc="同一份 dotfile 要從 Arch 跨到 Debian/macOS、不確定哪些要改哪些能共用時回來讀 — 綁 distro 的只有 package manager 這一層">Package Manager 抽象層</a> 的「套件名分歧要逐項吸收」是同一件事的兩面。哪些工具在保守發行版沒打包，見 <a href="/blog/linux/dotfile/knowledge-cards/distro-package-granularity/" data-link-title="發行版打包粒度" data-link-desc="同一個 install 指令在不同發行版拉的套件數差一個量級、或某工具在某發行版根本沒打包時回來讀 — 發行版怎麼切套件顆粒度">發行版打包粒度</a>。清單 curate 的實作見 <a href="/blog/linux/dotfile/10-prod-parity/workstation-cross-distro/" data-link-title="工作站 dotfile 跨發行版落地" data-link-desc="手上 dotfile 是照 Arch 寫的、要在 client 常見的 Debian/Ubuntu 機器上還原工作環境時回來讀 — 哪些綁 pacman、哪些能直接共用">工作站 dotfile 跨發行版落地</a>。</p>
<h2 id="一個爛名字全滅">一個爛名字全滅</h2>
<p>實務上最容易撞到的後果：批次清單裡塞了一個該發行版沒有的套件名，整批就全部不裝。實測在 Debian bookworm 跑一份含 broot、zellij（兩者 bookworm 沒打包）的清單，apt 回 <code>Unable to locate package broot / zellij</code> 後直接中止，同批的 autojump、gh、tig 明明存在也一個都沒裝。</p>
<p>症狀是「我明明列了十個工具、跑完一個都沒有」，根因不是每個都失敗，是其中一兩個爛名字讓整筆交易 abort。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<p>批次安裝清單必須逐項對齊該發行版實際有的套件，不能從別的發行版直接抄名字過來。動手前用 dry-run 先驗：</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">apt-get install -s a b c    <span class="c1"># -s 模擬，只解析不安裝，先看有沒有 Unable to locate</span></span></span></code></pre></div><p>一個一個 dry-run 能精準指出哪個名字解不開，再把它移出批次清單、或換成該發行版的正確名字。</p>
<h2 id="邊界">邊界</h2>
<p>pacman 的批次安裝有類似的原子性——實測 <code>pacman -S</code> 一份含 autojump 的清單，因 autojump 已從 Arch 官方 repo 移到 AUR、回 <code>target not found</code>，整批就 abort，同清單裡本來裝得起來的 zoxide、ripgrep、fd 一個都沒裝（這種「曾在官方 repo、後來被移出」的漂移見 <a href="/blog/linux/dotfile/knowledge-cards/distro-package-granularity/" data-link-title="發行版打包粒度" data-link-desc="同一個 install 指令在不同發行版拉的套件數差一個量級、或某工具在某發行版根本沒打包時回來讀 — 發行版怎麼切套件顆粒度">發行版打包粒度</a>）。想繞過「一個失敗全滅」可以逐包安裝（迴圈裡一個一個裝、失敗的跳過），但那會失去交易的一致性保證、也讓「哪些沒裝成功」變得難追。多數情況正解是把清單修對，而不是放棄原子性。</p>
]]></content:encoded></item></channel></rss>