<?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>Cross-Platform on Tarragon</title><link>https://tarrragon.github.io/blog/tags/cross-platform/</link><description>Recent content in Cross-Platform 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/cross-platform/index.xml" rel="self" type="application/rss+xml"/><item><title>跨平台共用一個 Repo</title><link>https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/cross-platform-one-repo/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/cross-platform-one-repo/</guid><description>&lt;p>macOS 跟 Linux 可以用同一個 dotfile repo。不需要 fork 成兩個 repo——兩個 repo 的同步成本會隨時間膨脹，改了 git config 或 neovim 設定要在兩邊各 commit 一次，忘了同步就漂移。&lt;/p>
&lt;p>本文的做法基於 GNU Stow——將 dotfile repo 的檔案透過 symlink 對應到家目錄的工具（完整說明見&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/management-strategies/" data-link-title="管理策略與選型" data-link-desc="要選 dotfile 管理工具時回來讀 — bare repo、stow、chezmoi 的適用場景與選型判讀">管理策略與選型&lt;/a>）。一個 repo 跨平台的做法是三層分離，每層處理不同粒度的差異：&lt;/p>
&lt;h2 id="第一層stow-選擇性安裝">第一層：stow 選擇性安裝&lt;/h2>
&lt;p>Stow 的 package 機制天然支持跨平台。repo 裡同時放 macOS 和 Linux 的配置，安裝時只 stow 該 OS 需要的 package：&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"># macOS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">stow zsh git zellij btop broot
&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"># Linux desktop&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">stow zsh git zellij btop broot hyprland waybar wofi mako&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>hyprland、waybar 這些目錄放在 repo 裡但 macOS 不 stow，不會有副作用。bootstrap script 裡用 &lt;code>uname&lt;/code> 自動決定要 stow 哪些 package：&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">PACKAGES&lt;/span>&lt;span class="o">=(&lt;/span>zsh git zellij btop broot&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">&lt;span class="k">if&lt;/span> &lt;span class="o">[[&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>uname -s&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;Linux&amp;#34;&lt;/span> &lt;span class="o">]]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&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">for&lt;/span> pkg in hyprland waybar wofi mako hyprlock&lt;span class="p">;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="o">[[&lt;/span> -d &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$pkg&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">]]&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nv">PACKAGES&lt;/span>&lt;span class="o">+=(&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$pkg&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&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="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="k">fi&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="第二層配置檔內的-os-分流">第二層：配置檔內的 OS 分流&lt;/h2>
&lt;p>同一份配置檔裡，用 &lt;code>uname&lt;/code> 區分 macOS 和 Linux 的差異。適合處理 PATH、工具初始化路徑這類「同一個用途、不同 OS 路徑」的狀況：&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"># path.zsh&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">typeset&lt;/span> -U PATH
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$HOME&lt;/span>&lt;span class="s2">/.local/bin:&lt;/span>&lt;span class="nv">$PATH&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="o">[[&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>uname&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;Darwin&amp;#34;&lt;/span> &lt;span class="o">]]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&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"># Homebrew Ruby、FVM、Android SDK（macOS 路徑）&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> -d &lt;span class="s2">&amp;#34;/opt/homebrew/opt/ruby/bin&amp;#34;&lt;/span> &lt;span class="o">]]&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">export&lt;/span> &lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/opt/homebrew/opt/ruby/bin:&lt;/span>&lt;span class="nv">$PATH&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="nb">export&lt;/span> &lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$HOME&lt;/span>&lt;span class="s2">/fvm/default/bin:&lt;/span>&lt;span class="nv">$PATH&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="nb">export&lt;/span> &lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$PATH&lt;/span>&lt;span class="s2">:&lt;/span>&lt;span class="nv">$HOME&lt;/span>&lt;span class="s2">/Library/Android/sdk/emulator&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="k">fi&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="k">if&lt;/span> &lt;span class="o">[[&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>uname&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;Linux&amp;#34;&lt;/span> &lt;span class="o">]]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Android SDK（Linux 路徑）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="o">[[&lt;/span> -d &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$HOME&lt;/span>&lt;span class="s2">/Android/Sdk/emulator&amp;#34;&lt;/span> &lt;span class="o">]]&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">export&lt;/span> &lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$PATH&lt;/span>&lt;span class="s2">:&lt;/span>&lt;span class="nv">$HOME&lt;/span>&lt;span class="s2">/Android/Sdk/emulator&amp;#34;&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">fi&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>




&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"># tools.zsh — autojump 的 source 路徑在兩個 OS 不同&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">if&lt;/span> &lt;span class="o">[[&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>uname&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;Darwin&amp;#34;&lt;/span> &lt;span class="o">]]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span>
&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> -f /opt/homebrew/etc/profile.d/autojump.sh &lt;span class="o">]&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> . /opt/homebrew/etc/profile.d/autojump.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="o">[&lt;/span> -f /usr/share/autojump/autojump.sh &lt;span class="o">]&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> . /usr/share/autojump/autojump.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="k">fi&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>每個路徑加 &lt;code>[[ -d ]]&lt;/code> 或 &lt;code>[ -f ]&lt;/code> 存在性檢查——即使在正確的 OS 上，如果該工具沒裝也不會報錯。&lt;/p>
&lt;h2 id="第三層localzsh-放機器專屬設定">第三層：local.zsh 放機器專屬設定&lt;/h2>
&lt;p>不是「macOS 的設定」而是「這台特定機器的設定」——工作專案 alias、公司 VPN proxy、只有這台機器需要的 PATH——放在 &lt;code>~/.config/zsh/local.zsh&lt;/code>，不進 Git。&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"># .zshrc 裡的載入方式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="o">[[&lt;/span> -f &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$HOME&lt;/span>&lt;span class="s2">/.config/zsh/local.zsh&amp;#34;&lt;/span> &lt;span class="o">]]&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">source&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$HOME&lt;/span>&lt;span class="s2">/.config/zsh/local.zsh&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>




&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/zsh/local.zsh（不在 repo 裡，每台機器自己建）&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">alias&lt;/span> unimall-dev&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;cd ~/project/unimall_shop &amp;amp;&amp;amp; ./scripts/run_commands.sh dev&amp;#39;&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">alias&lt;/span> unipos-dev&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;cd ~/project/unipos &amp;amp;&amp;amp; ./scripts/run_commands.sh dev&amp;#39;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Repo 裡放一份 &lt;code>local.zsh.example&lt;/code> 作為範本，&lt;code>.gitignore&lt;/code> 裡排除 &lt;code>local.zsh&lt;/code> 本身。&lt;/p></description><content:encoded><![CDATA[<p>macOS 跟 Linux 可以用同一個 dotfile repo。不需要 fork 成兩個 repo——兩個 repo 的同步成本會隨時間膨脹，改了 git config 或 neovim 設定要在兩邊各 commit 一次，忘了同步就漂移。</p>
<p>本文的做法基於 GNU Stow——將 dotfile repo 的檔案透過 symlink 對應到家目錄的工具（完整說明見<a href="/blog/linux/dotfile/01-dotfile-management/management-strategies/" data-link-title="管理策略與選型" data-link-desc="要選 dotfile 管理工具時回來讀 — bare repo、stow、chezmoi 的適用場景與選型判讀">管理策略與選型</a>）。一個 repo 跨平台的做法是三層分離，每層處理不同粒度的差異：</p>
<h2 id="第一層stow-選擇性安裝">第一層：stow 選擇性安裝</h2>
<p>Stow 的 package 機制天然支持跨平台。repo 裡同時放 macOS 和 Linux 的配置，安裝時只 stow 該 OS 需要的 package：</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"># macOS</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">stow zsh git zellij btop broot
</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"># Linux desktop</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">stow zsh git zellij btop broot hyprland waybar wofi mako</span></span></code></pre></div><p>hyprland、waybar 這些目錄放在 repo 裡但 macOS 不 stow，不會有副作用。bootstrap script 裡用 <code>uname</code> 自動決定要 stow 哪些 package：</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">PACKAGES</span><span class="o">=(</span>zsh git zellij btop broot<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"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="k">$(</span>uname -s<span class="k">)</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;Linux&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">for</span> pkg in hyprland waybar wofi mako hyprlock<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="o">[[</span> -d <span class="s2">&#34;</span><span class="nv">$pkg</span><span class="s2">&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nv">PACKAGES</span><span class="o">+=(</span><span class="s2">&#34;</span><span class="nv">$pkg</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">done</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">fi</span></span></span></code></pre></div><h2 id="第二層配置檔內的-os-分流">第二層：配置檔內的 OS 分流</h2>
<p>同一份配置檔裡，用 <code>uname</code> 區分 macOS 和 Linux 的差異。適合處理 PATH、工具初始化路徑這類「同一個用途、不同 OS 路徑」的狀況：</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"># path.zsh</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nb">typeset</span> -U PATH
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/bin:</span><span class="nv">$PATH</span><span class="s2">&#34;</span>    <span class="c1"># 共用</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="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="k">$(</span>uname<span class="k">)</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;Darwin&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># Homebrew Ruby、FVM、Android SDK（macOS 路徑）</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="o">[[</span> -d <span class="s2">&#34;/opt/homebrew/opt/ruby/bin&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;/opt/homebrew/opt/ruby/bin:</span><span class="nv">$PATH</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/fvm/default/bin:</span><span class="nv">$PATH</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$PATH</span><span class="s2">:</span><span class="nv">$HOME</span><span class="s2">/Library/Android/sdk/emulator&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">fi</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">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="k">$(</span>uname<span class="k">)</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;Linux&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Android SDK（Linux 路徑）</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="o">[[</span> -d <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/Android/Sdk/emulator&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$PATH</span><span class="s2">:</span><span class="nv">$HOME</span><span class="s2">/Android/Sdk/emulator&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">fi</span></span></span></code></pre></div>




<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"># tools.zsh — autojump 的 source 路徑在兩個 OS 不同</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="k">$(</span>uname<span class="k">)</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;Darwin&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="o">[</span> -f /opt/homebrew/etc/profile.d/autojump.sh <span class="o">]</span> <span class="o">&amp;&amp;</span> . /opt/homebrew/etc/profile.d/autojump.sh
</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="o">[</span> -f /usr/share/autojump/autojump.sh <span class="o">]</span> <span class="o">&amp;&amp;</span> . /usr/share/autojump/autojump.sh
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">fi</span></span></span></code></pre></div><p>每個路徑加 <code>[[ -d ]]</code> 或 <code>[ -f ]</code> 存在性檢查——即使在正確的 OS 上，如果該工具沒裝也不會報錯。</p>
<h2 id="第三層localzsh-放機器專屬設定">第三層：local.zsh 放機器專屬設定</h2>
<p>不是「macOS 的設定」而是「這台特定機器的設定」——工作專案 alias、公司 VPN proxy、只有這台機器需要的 PATH——放在 <code>~/.config/zsh/local.zsh</code>，不進 Git。</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"># .zshrc 裡的載入方式</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="o">[[</span> -f <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.config/zsh/local.zsh&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nb">source</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.config/zsh/local.zsh&#34;</span></span></span></code></pre></div>




<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/zsh/local.zsh（不在 repo 裡，每台機器自己建）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">alias</span> unimall-dev<span class="o">=</span><span class="s1">&#39;cd ~/project/unimall_shop &amp;&amp; ./scripts/run_commands.sh dev&#39;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nb">alias</span> unipos-dev<span class="o">=</span><span class="s1">&#39;cd ~/project/unipos &amp;&amp; ./scripts/run_commands.sh dev&#39;</span></span></span></code></pre></div><p>Repo 裡放一份 <code>local.zsh.example</code> 作為範本，<code>.gitignore</code> 裡排除 <code>local.zsh</code> 本身。</p>
<h2 id="套件清單也分-os">套件清單也分 OS</h2>





<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">├── Brewfile              # macOS: brew bundle dump 產生
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── packages-arch.txt     # Arch Linux: pacman -Qqe &gt; packages-arch.txt
</span></span><span class="line"><span class="ln">4</span><span class="cl">└── ...</span></span></code></pre></div><p>Bootstrap script 依 OS 讀對應的清單安裝套件。</p>
<h2 id="stow-跨平台的邊界">Stow 跨平台的邊界</h2>
<p>這個三層模型適合「配置檔本身大部分通用、差異只在 PATH 和少數工具路徑」的狀況——也就是多數開發者的實際情境。判斷是否需要遷移到 chezmoi 的訊號：</p>
<ul>
<li>同一份配置檔裡超過一半的行數是 OS 分流 → chezmoi template 更乾淨</li>
<li>需要在配置檔裡注入 secret（API key、token） → chezmoi 的 secret 管理是必要功能</li>
<li>管理的機器超過三種 OS/角色組合 → template 的條件判斷比 shell if-else 更可維護</li>
</ul>
<p>多數情況下 stow + uname + local.zsh 就足夠。</p>
]]></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></channel></rss>