<?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>Stow on Tarragon</title><link>https://tarrragon.github.io/blog/tags/stow/</link><description>Recent content in Stow on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Mon, 29 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/stow/index.xml" rel="self" type="application/rss+xml"/><item><title>管理策略與選型</title><link>https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/management-strategies/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/management-strategies/</guid><description>&lt;p>Dotfile 管理的核心動作是把散落在家目錄各處的配置檔集中到一個 Git repo 裡版控。工具只是幫你處理「repo 裡的檔案怎麼對應到家目錄正確位置」這一層映射，選型看的是你的機器數量、OS 組合和 secret 需求。&lt;/p>
&lt;h2 id="git-bare-repo直接把家目錄當-work-tree">Git bare repo：直接把家目錄當 work tree&lt;/h2>
&lt;p>bare repo 的概念是在家目錄建一個沒有工作目錄的 Git 倉庫，然後用 alias 指定 &lt;code>--work-tree=$HOME&lt;/code>，讓 Git 直接追蹤家目錄下的檔案，不需要 symlink、不需要額外工具。&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">git init --bare &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$HOME&lt;/span>&lt;span class="s2">/.dotfiles&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在 shell 配置裡加一行 alias：&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">alias&lt;/span> &lt;span class="nv">dotfiles&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;git --git-dir=&amp;#34;$HOME/.dotfiles&amp;#34; --work-tree=&amp;#34;$HOME&amp;#34;&amp;#39;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>之後所有 dotfile 操作都透過這個 alias：&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">dotfiles add ~/.zshrc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">dotfiles commit -m &lt;span class="s2">&amp;#34;add zshrc&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">dotfiles remote add origin git@github.com:you/dotfiles.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">dotfiles push -u origin main&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>第一件要做的事是隱藏未追蹤檔案。家目錄底下有成千上萬個檔案，如果不設定這一行，&lt;code>dotfiles status&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">dotfiles config --local status.showUntrackedFiles no&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&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">git clone --bare git@github.com:you/dotfiles.git &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$HOME&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">&lt;span class="nb">alias&lt;/span> &lt;span class="nv">dotfiles&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;git --git-dir=&amp;#34;$HOME/.dotfiles&amp;#34; --work-tree=&amp;#34;$HOME&amp;#34;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">dotfiles config --local status.showUntrackedFiles no
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">dotfiles checkout&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>checkout&lt;/code> 這步會把 repo 裡的檔案寫到家目錄。如果家目錄已經有同名檔案（例如系統預設的 &lt;code>.bashrc&lt;/code>），checkout 會失敗並列出衝突檔案，需要先手動備份或刪除。&lt;/p>
&lt;p>bare repo 適合配置量少、只管一台機器、不想安裝任何額外工具的人。它的限制是：概念對 Git 初學者不直覺（bare repo + work-tree 的組合不常見）、沒有模組化的概念（無法選擇性安裝某些配置）、多 profile 支援弱（不同機器要不同配置時只能靠 branch，長期維護困難）。&lt;/p>
&lt;h2 id="gnu-stowsymlink-農場管理器">GNU Stow：symlink 農場管理器&lt;/h2>
&lt;p>Stow 的概念是把 dotfile 集中放在一個普通目錄（如 &lt;code>~/dotfiles&lt;/code>），然後用 stow 指令在家目錄建立 symlink。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="c1"># 安裝 stow&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"># macOS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">brew install stow
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># Arch Linux&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">sudo pacman -S stow
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="c1"># Ubuntu/Debian&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">sudo apt install stow&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>以 zsh 配置為例，目錄結構長這樣：&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">~/dotfiles/zsh/.zshrc&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>執行 &lt;code>stow zsh&lt;/code> 後，stow 會在 &lt;code>$HOME&lt;/code> 建一個 symlink：&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">~/.zshrc -&amp;gt; ~/dotfiles/zsh/.zshrc&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>對於放在 &lt;code>~/.config/&lt;/code> 底下的工具（XDG 規範），目錄結構映射同樣的邏輯：&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">~/dotfiles/nvim/.config/nvim/init.lua
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">~/dotfiles/nvim/.config/nvim/lua/plugins.lua&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>執行 &lt;code>stow nvim&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">~/.config/nvim -&amp;gt; ~/dotfiles/nvim/.config/nvim&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>stow 會自動判斷該 symlink 整個目錄還是個別檔案——如果目標目錄不存在或目錄內所有檔案都由同一個 package 管理，stow 會 symlink 整個目錄（folding）；如果目標目錄已有其他檔案，stow 會展開（unfolding）成逐檔 symlink。&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"># 初始化&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">mkdir ~/dotfiles &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">cd&lt;/span> ~/dotfiles
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">git init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">git remote add origin git@github.com:you/dotfiles.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="c1"># 把現有 .zshrc 搬進 dotfiles&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">mkdir -p ~/dotfiles/zsh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">mv ~/.zshrc ~/dotfiles/zsh/.zshrc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ~/dotfiles &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> stow zsh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># 現在 ~/.zshrc 是一個 symlink，指向 ~/dotfiles/zsh/.zshrc&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"># 日常修改：直接編輯，symlink 透通&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">vim ~/.zshrc &lt;span class="c1"># 實際編輯的是 ~/dotfiles/zsh/.zshrc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ~/dotfiles &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> git add -A &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> git commit -m &lt;span class="s2">&amp;#34;update zshrc&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>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="c1"># 新機器還原&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">git clone git@github.com:you/dotfiles.git ~/dotfiles
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ~/dotfiles &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> stow zsh git nvim tmux&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>批次安裝所有 package：&lt;/p></description><content:encoded><![CDATA[<p>Dotfile 管理的核心動作是把散落在家目錄各處的配置檔集中到一個 Git repo 裡版控。工具只是幫你處理「repo 裡的檔案怎麼對應到家目錄正確位置」這一層映射，選型看的是你的機器數量、OS 組合和 secret 需求。</p>
<h2 id="git-bare-repo直接把家目錄當-work-tree">Git bare repo：直接把家目錄當 work tree</h2>
<p>bare repo 的概念是在家目錄建一個沒有工作目錄的 Git 倉庫，然後用 alias 指定 <code>--work-tree=$HOME</code>，讓 Git 直接追蹤家目錄下的檔案，不需要 symlink、不需要額外工具。</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">git init --bare <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.dotfiles&#34;</span></span></span></code></pre></div><p>在 shell 配置裡加一行 alias：</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">alias</span> <span class="nv">dotfiles</span><span class="o">=</span><span class="s1">&#39;git --git-dir=&#34;$HOME/.dotfiles&#34; --work-tree=&#34;$HOME&#34;&#39;</span></span></span></code></pre></div><p>之後所有 dotfile 操作都透過這個 alias：</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">dotfiles add ~/.zshrc
</span></span><span class="line"><span class="ln">2</span><span class="cl">dotfiles commit -m <span class="s2">&#34;add zshrc&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">dotfiles remote add origin git@github.com:you/dotfiles.git
</span></span><span class="line"><span class="ln">4</span><span class="cl">dotfiles push -u origin main</span></span></code></pre></div><p>第一件要做的事是隱藏未追蹤檔案。家目錄底下有成千上萬個檔案，如果不設定這一行，<code>dotfiles status</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">dotfiles config --local status.showUntrackedFiles no</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">git clone --bare git@github.com:you/dotfiles.git <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.dotfiles&#34;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">alias</span> <span class="nv">dotfiles</span><span class="o">=</span><span class="s1">&#39;git --git-dir=&#34;$HOME/.dotfiles&#34; --work-tree=&#34;$HOME&#34;&#39;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">dotfiles config --local status.showUntrackedFiles no
</span></span><span class="line"><span class="ln">4</span><span class="cl">dotfiles checkout</span></span></code></pre></div><p><code>checkout</code> 這步會把 repo 裡的檔案寫到家目錄。如果家目錄已經有同名檔案（例如系統預設的 <code>.bashrc</code>），checkout 會失敗並列出衝突檔案，需要先手動備份或刪除。</p>
<p>bare repo 適合配置量少、只管一台機器、不想安裝任何額外工具的人。它的限制是：概念對 Git 初學者不直覺（bare repo + work-tree 的組合不常見）、沒有模組化的概念（無法選擇性安裝某些配置）、多 profile 支援弱（不同機器要不同配置時只能靠 branch，長期維護困難）。</p>
<h2 id="gnu-stowsymlink-農場管理器">GNU Stow：symlink 農場管理器</h2>
<p>Stow 的概念是把 dotfile 集中放在一個普通目錄（如 <code>~/dotfiles</code>），然後用 stow 指令在家目錄建立 symlink。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="c1"># 安裝 stow</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># macOS</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">brew install stow
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Arch Linux</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">sudo pacman -S stow
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># Ubuntu/Debian</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">sudo apt install stow</span></span></code></pre></div><p>以 zsh 配置為例，目錄結構長這樣：</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/zsh/.zshrc</span></span></code></pre></div><p>執行 <code>stow zsh</code> 後，stow 會在 <code>$HOME</code> 建一個 symlink：</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">~/.zshrc -&gt; ~/dotfiles/zsh/.zshrc</span></span></code></pre></div><p>對於放在 <code>~/.config/</code> 底下的工具（XDG 規範），目錄結構映射同樣的邏輯：</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/nvim/.config/nvim/init.lua
</span></span><span class="line"><span class="ln">2</span><span class="cl">~/dotfiles/nvim/.config/nvim/lua/plugins.lua</span></span></code></pre></div><p>執行 <code>stow nvim</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">~/.config/nvim -&gt; ~/dotfiles/nvim/.config/nvim</span></span></code></pre></div><p>stow 會自動判斷該 symlink 整個目錄還是個別檔案——如果目標目錄不存在或目錄內所有檔案都由同一個 package 管理，stow 會 symlink 整個目錄（folding）；如果目標目錄已有其他檔案，stow 會展開（unfolding）成逐檔 symlink。</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">mkdir ~/dotfiles <span class="o">&amp;&amp;</span> <span class="nb">cd</span> ~/dotfiles
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">git init
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">git remote add origin git@github.com:you/dotfiles.git
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 把現有 .zshrc 搬進 dotfiles</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">mkdir -p ~/dotfiles/zsh
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">mv ~/.zshrc ~/dotfiles/zsh/.zshrc
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nb">cd</span> ~/dotfiles <span class="o">&amp;&amp;</span> stow zsh
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 現在 ~/.zshrc 是一個 symlink，指向 ~/dotfiles/zsh/.zshrc</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"># 日常修改：直接編輯，symlink 透通</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">vim ~/.zshrc  <span class="c1"># 實際編輯的是 ~/dotfiles/zsh/.zshrc</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nb">cd</span> ~/dotfiles <span class="o">&amp;&amp;</span> git add -A <span class="o">&amp;&amp;</span> git commit -m <span class="s2">&#34;update zshrc&#34;</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">git clone git@github.com:you/dotfiles.git ~/dotfiles
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="nb">cd</span> ~/dotfiles <span class="o">&amp;&amp;</span> stow zsh git nvim tmux</span></span></code></pre></div><p>批次安裝所有 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="nb">cd</span> ~/dotfiles
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># stow 會把每個頂層目錄當成一個 package</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">stow */</span></span></code></pre></div><p>移除某個 package 的 symlink：</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">cd</span> ~/dotfiles <span class="o">&amp;&amp;</span> stow -D nvim</span></span></code></pre></div><p>stow 適合中等複雜度的配置管理。它的優勢是模組化（每個工具獨立一個 package、可選擇性安裝）和概念直覺（目錄結構就是安裝後的樣子）。它的限制是只管 symlink 映射，不管套件安裝；跨 OS 的路徑差異（macOS 和 Linux 某些工具的配置路徑不同）需要自己處理；stow 也不管 file permission——需要 0600 權限的 secret 檔（SSH private key、API token config）靠 symlink 繼承來源檔案權限，不能在部署過程中自動設定。</p>
<h2 id="yadmbare-repo-的升級版">yadm：bare repo 的升級版</h2>
<p>yadm 包裝了 Git bare repo 的操作，加上三個 bare repo 缺少的能力：alternate files（依 OS、hostname、甚至 user 條件選擇安裝不同版本的配置檔）、encrypt（用 GPG 或 OpenSSL 加密敏感檔案、不依賴外部密碼管理器）、bootstrap script（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"><span class="c1"># 安裝</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">brew install yadm          <span class="c1"># macOS</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">sudo pacman -S yadm        <span class="c1"># Arch</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"># 初始化（等同 git init --bare + 自動設定 alias）</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">yadm init
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">yadm remote add origin git@github.com:you/dotfiles.git
</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"># 操作方式跟 Git 完全一樣</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">yadm add ~/.zshrc
</span></span><span class="line"><span class="ln">11</span><span class="cl">yadm commit -m <span class="s2">&#34;add zshrc&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">yadm push</span></span></code></pre></div><p>Alternate files 的概念是在同一個 repo 裡放多個版本的同一個檔案，yadm 依條件決定用哪一個：</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">~/.config/alacritty/alacritty.toml##os.Darwin
</span></span><span class="line"><span class="ln">2</span><span class="cl">~/.config/alacritty/alacritty.toml##os.Linux</span></span></code></pre></div><p>macOS 上 yadm 自動 checkout Darwin 版本、Linux 上 checkout Linux 版本。比 stow 的 shell if-else 判斷更乾淨，比 chezmoi 的 Go template 學習曲線低。</p>
<p>yadm 適合想要 bare repo 的簡單性、但需要條件安裝或 secret 加密的人。它的限制是沒有 stow 的模組化概念（無法選擇性只安裝某些工具的配置）、沒有 chezmoi 的 template 細粒度（alternate files 是整個檔案切換，不是檔案內的段落條件）。</p>
<h2 id="chezmoi多機器-dotfile-管理工具">Chezmoi：多機器 dotfile 管理工具</h2>
<p>Chezmoi 是專為 dotfile 管理設計的工具，原生處理 template、secret 管理和多機器差異。它把 dotfile 存在自己的 source directory（<code>~/.local/share/chezmoi</code>），用 <code>chezmoi apply</code> 把檔案實際寫入目標位置（不是 symlink，是複製）。</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">brew install chezmoi        <span class="c1"># macOS</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">pacman -S chezmoi           <span class="c1"># Arch</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">sh -c <span class="s2">&#34;</span><span class="k">$(</span>curl -fsLS get.chezmoi.io<span class="k">)</span><span class="s2">&#34;</span>  <span class="c1"># 通用</span></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"># 初始化（會建立 source directory 和 Git repo）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">chezmoi init
</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">chezmoi add ~/.zshrc
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">chezmoi add ~/.config/nvim
</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"># 編輯（在 source directory 裡編輯，不是直接改家目錄的檔案）</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">chezmoi edit ~/.zshrc
</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">chezmoi diff
</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">chezmoi apply
</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"># 推上遠端</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">chezmoi <span class="nb">cd</span>  <span class="c1"># 進入 source directory</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">git add -A <span class="o">&amp;&amp;</span> git commit -m <span class="s2">&#34;update&#34;</span> <span class="o">&amp;&amp;</span> git push</span></span></code></pre></div><p>chezmoi 的核心優勢是 template。同一份配置檔在不同機器可以產生不同內容：</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"># chezmoi 的 source directory 裡，檔案名稱加 .tmpl 後綴</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># dot_zshrc.tmpl</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="nb">export</span> <span class="nv">EDITOR</span><span class="o">=</span><span class="s2">&#34;nvim&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="o">{{</span>- <span class="k">if</span> eq .chezmoi.os <span class="s2">&#34;darwin&#34;</span> <span class="o">}}</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nb">export</span> <span class="nv">HOMEBREW_PREFIX</span><span class="o">=</span><span class="s2">&#34;/opt/homebrew&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nb">eval</span> <span class="s2">&#34;</span><span class="k">$(</span><span class="nv">$HOMEBREW_PREFIX</span>/bin/brew shellenv<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="o">{{</span>- end <span class="o">}}</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="o">{{</span>- <span class="k">if</span> eq .chezmoi.os <span class="s2">&#34;linux&#34;</span> <span class="o">}}</span>
</span></span><span class="line"><span class="ln">12</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></span><span class="line"><span class="ln">13</span><span class="cl"><span class="o">{{</span>- end <span class="o">}}</span></span></span></code></pre></div><p><code>chezmoi apply</code> 會根據當前機器的 OS 展開 template，macOS 上產生的 <code>.zshrc</code> 會包含 Homebrew 設定，Linux 上不會。</p>
<p>Secret 管理是另一個殺手功能。chezmoi 整合了 1Password、Bitwarden、pass、gopass、LastPass 等密碼管理器：</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"># dot_gitconfig.tmpl
</span></span><span class="line"><span class="ln">2</span><span class="cl">[user]
</span></span><span class="line"><span class="ln">3</span><span class="cl">    name = Your Name
</span></span><span class="line"><span class="ln">4</span><span class="cl">    email = {{ (onepasswordRead &#34;op://Personal/Git Config/email&#34;).value }}</span></span></code></pre></div><p><code>chezmoi apply</code> 時會即時從 1Password 拉值填入，secret 不會存在 Git repo 裡。</p>
<p>chezmoi 適合管理多台異質機器（macOS 工作機 + Linux 伺服器 + Linux 桌面 VM）且有 secret 需求的人。它的代價是學習曲線最陡——要理解 chezmoi 自己的目錄命名慣例（<code>dot_</code> 前綴代表 <code>.</code> 開頭、<code>private_</code> 前綴代表權限 0600）、template 語法（Go template）、以及「source directory 和目標位置是兩份獨立的檔案」這個心智模型。</p>
<h2 id="選型判讀">選型判讀</h2>
<p>選工具看三個維度：機器數量、OS 組合、secret 需求。</p>
<p>只有一台機器、配置簡單 — bare repo 或 stow 都夠用，差別在於你喜不喜歡 symlink 的管理方式。bare repo 最輕量，stow 多一層模組化。</p>
<p>多台同質機器（都是 macOS 或都是 Linux）— stow。配置檔在同 OS 間差異小，不需要 template，stow 的模組化讓你可以只在桌面機安裝 hyprland package、伺服器只裝 zsh + git + tmux。</p>
<p>多台異質機器（macOS + Linux）但 secret 需求不高 — stow 加上 OS 分流仍然可行，<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>
<p>多台異質機器、需要條件安裝但不想學 template 語法 — yadm。alternate files 讓你依 OS/hostname 切換整個配置檔，內建 encrypt 處理 secret，Git 操作方式跟 bare repo 相同。</p>
<p>多台異質機器（macOS + Linux）、有細粒度 template 或密碼管理器整合需求 — chezmoi。檔案內的段落條件、跟 1Password/Bitwarden 的整合、<code>private_</code> 前綴的 permission 管理是它存在的理由。</p>
<p>不確定 — 從 stow 開始。它的概念最直覺（目錄結構 = 安裝後位置）、遷移成本最低（要換到 yadm 是加一層 wrapper、要換到 chezmoi 時目錄結構的概念是相通的）。</p>
]]></content:encoded></item><item><title>模組一：管理工具與目錄結構</title><link>https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/</guid><description>&lt;p>Dotfile 管理的核心動作是把散落在家目錄各處的配置檔集中到一個 Git repo 裡版控。工具只是幫你處理「repo 裡的檔案怎麼對應到家目錄正確位置」這一層映射，選型看的是你的機器數量、OS 組合和 secret 需求。&lt;/p>
&lt;p>開始之前，確認 SSH key 和 Git 已經設好、dotfile repo 已經 clone 到本機——這些前置步驟見&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>的階段一。&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/01-dotfile-management/management-strategies/" data-link-title="管理策略與選型" data-link-desc="要選 dotfile 管理工具時回來讀 — bare repo、stow、chezmoi 的適用場景與選型判讀">管理策略與選型&lt;/a>&lt;/td>
 &lt;td>bare repo / stow / chezmoi 三種策略的操作方式、優劣與選型判讀&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/cross-platform-one-repo/" data-link-title="跨平台共用一個 Repo" data-link-desc="macOS 跟 Linux 要共用同一個 dotfile repo、不想維護兩份時回來讀">跨平台共用一個 Repo&lt;/a>&lt;/td>
 &lt;td>macOS + Linux 用同一個 repo 的三層模型：stow 選擇性安裝、OS 分流、local.zsh 機器專屬&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/directory-structure-workflow/" data-link-title="目錄結構、Git 工作流與常見陷阱" data-link-desc="設計 dotfile repo 的目錄結構、或遇到 symlink 衝突和私鑰外洩等問題時回來讀">目錄結構、Git 工作流與常見陷阱&lt;/a>&lt;/td>
 &lt;td>stow 的目錄結構設計原則、日常 Git 操作流程、私鑰外洩等常見陷阱&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/00-dotfile-mindset/" data-link-title="模組零：Dotfile 心智模型" data-link-desc="換機器、開 VM、重灌系統時需要快速還原開發環境，或想釐清哪些配置該版控、哪些該排除時回來讀">模組零：Dotfile 心智模型&lt;/a>：為什麼要管理、哪些東西該進 repo&lt;/li>
&lt;li>→ &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>：目錄結構裡 zsh package 的具體拆法&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>：跨機器同步策略與 secret 管理&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Dotfile 管理的核心動作是把散落在家目錄各處的配置檔集中到一個 Git repo 裡版控。工具只是幫你處理「repo 裡的檔案怎麼對應到家目錄正確位置」這一層映射，選型看的是你的機器數量、OS 組合和 secret 需求。</p>
<p>開始之前，確認 SSH key 和 Git 已經設好、dotfile repo 已經 clone 到本機——這些前置步驟見<a href="/blog/linux/dotfile/00-dotfile-mindset/setup-order-guide/" data-link-title="環境建置的操作順序" data-link-desc="第一次從零建立 Linux 或 macOS 開發環境、不確定先做什麼後做什麼時讀 — 依賴順序路線圖，每一步附對應模組連結">環境建置的操作順序</a>的階段一。</p>
<h2 id="章節文章">章節文章</h2>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>主題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/linux/dotfile/01-dotfile-management/management-strategies/" data-link-title="管理策略與選型" data-link-desc="要選 dotfile 管理工具時回來讀 — bare repo、stow、chezmoi 的適用場景與選型判讀">管理策略與選型</a></td>
          <td>bare repo / stow / chezmoi 三種策略的操作方式、優劣與選型判讀</td>
      </tr>
      <tr>
          <td><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></td>
          <td>macOS + Linux 用同一個 repo 的三層模型：stow 選擇性安裝、OS 分流、local.zsh 機器專屬</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/01-dotfile-management/directory-structure-workflow/" data-link-title="目錄結構、Git 工作流與常見陷阱" data-link-desc="設計 dotfile repo 的目錄結構、或遇到 symlink 衝突和私鑰外洩等問題時回來讀">目錄結構、Git 工作流與常見陷阱</a></td>
          <td>stow 的目錄結構設計原則、日常 Git 操作流程、私鑰外洩等常見陷阱</td>
      </tr>
  </tbody>
</table>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/linux/dotfile/00-dotfile-mindset/" data-link-title="模組零：Dotfile 心智模型" data-link-desc="換機器、開 VM、重灌系統時需要快速還原開發環境，或想釐清哪些配置該版控、哪些該排除時回來讀">模組零：Dotfile 心智模型</a>：為什麼要管理、哪些東西該進 repo</li>
<li>→ <a href="/blog/linux/dotfile/02-shell-config/" data-link-title="模組二：Shell 配置" data-link-desc="shell 配置檔長成一坨不敢動時回來讀 — .zshrc/.bashrc 的結構化拆分、alias/function/PATH 的模組化設計">模組二：Shell 配置</a>：目錄結構裡 zsh package 的具體拆法</li>
<li>→ <a href="/blog/linux/dotfile/08-sync-bootstrap/" data-link-title="模組八：同步、Bootstrap 與環境重建" data-link-desc="換機器或重灌時怎麼還原工作環境 — bootstrap script 設計、套件清單管理、跨機器同步策略、secret 排除，以及 VM 快照和 dotfile 重建兩種思路的場景判讀">模組八：同步、Bootstrap 與環境重建</a>：跨機器同步策略與 secret 管理</li>
</ul>
]]></content:encoded></item><item><title>GNU Stow</title><link>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/gnu-stow/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/knowledge-cards/gnu-stow/</guid><description>&lt;p>GNU Stow 是一個 symlink farm manager，原本設計給軟體安裝用（把 &lt;code>/usr/local/stow/program/&lt;/code> 下的檔案 symlink 到 &lt;code>/usr/local/&lt;/code>），在 dotfile 管理場景被借來做「把 repo 裡的配置檔 symlink 到家目錄」。&lt;/p>
&lt;h2 id="核心規則">核心規則&lt;/h2>
&lt;p>Stow 的核心規則只有一條：&lt;strong>package 目錄內的路徑結構，就是安裝後相對於目標目錄的路徑結構&lt;/strong>。&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">~/dotfiles/zsh/.zshrc → ~/.zshrc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">~/dotfiles/nvim/.config/nvim/ → ~/.config/nvim/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">~/dotfiles/git/.gitconfig → ~/.gitconfig&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>每個頂層目錄（&lt;code>zsh/&lt;/code>、&lt;code>nvim/&lt;/code>、&lt;code>git/&lt;/code>）是一個 stow package，可以獨立安裝或移除。&lt;/p>
&lt;h2 id="常用指令">常用指令&lt;/h2>





&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> ~/dotfiles
&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="c1"># 安裝：在目標目錄（預設 $HOME 的上一層，通常用 --target）建立 symlink&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">stow zsh &lt;span class="c1"># 安裝 zsh package&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">stow zsh git nvim tmux &lt;span class="c1"># 批次安裝多個 package&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">stow */ &lt;span class="c1"># 安裝所有 package&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"># 移除：刪除該 package 建立的 symlink&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">stow -D nvim
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="c1"># 重新安裝（移除 + 安裝）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">stow -R zsh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="c1"># 收養：如果目標位置已有檔案，--adopt 把它移進 repo 再建 symlink&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">stow --adopt zsh&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>--adopt&lt;/code> 是首次把現有配置納入 dotfile 管理時的關鍵操作——它把家目錄的既有檔案「收養」進 repo（移動過去），然後建 symlink。之後 &lt;code>git diff&lt;/code> 就能看到 repo 版本跟原版的差異。&lt;/p>
&lt;h2 id="folding-與-unfolding">Folding 與 Unfolding&lt;/h2>
&lt;p>Stow 會自動判斷要 symlink 整個目錄還是逐一 symlink 檔案：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Folding&lt;/strong>：目標目錄不存在、或目錄內所有檔案都由同一個 package 管理 → symlink 整個目錄&lt;/li>
&lt;li>&lt;strong>Unfolding&lt;/strong>：目標目錄已有其他來源的檔案 → 展開成逐檔 symlink，保留既有檔案不受影響&lt;/li>
&lt;/ul>
&lt;p>這個機制讓多個 package 可以共存於同一個目標目錄（如 &lt;code>~/.config/&lt;/code>）。&lt;/p>
&lt;h2 id="限制">限制&lt;/h2>
&lt;ul>
&lt;li>只管 symlink 映射，不管套件安裝（套件由 Brewfile 或 packages.txt 處理）&lt;/li>
&lt;li>不管 file permission——需要 0600 的 secret 檔靠 symlink 繼承來源權限，無法在部署時自動 chmod&lt;/li>
&lt;li>沒有 template 機制——同一份配置在不同機器要不同內容時，需要在配置檔內用 shell 的 OS 判斷處理&lt;/li>
&lt;/ul>
&lt;p>完整選型比較見&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>。&lt;/p></description><content:encoded><![CDATA[<p>GNU Stow 是一個 symlink farm manager，原本設計給軟體安裝用（把 <code>/usr/local/stow/program/</code> 下的檔案 symlink 到 <code>/usr/local/</code>），在 dotfile 管理場景被借來做「把 repo 裡的配置檔 symlink 到家目錄」。</p>
<h2 id="核心規則">核心規則</h2>
<p>Stow 的核心規則只有一條：<strong>package 目錄內的路徑結構，就是安裝後相對於目標目錄的路徑結構</strong>。</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/zsh/.zshrc          → ~/.zshrc
</span></span><span class="line"><span class="ln">2</span><span class="cl">~/dotfiles/nvim/.config/nvim/  → ~/.config/nvim/
</span></span><span class="line"><span class="ln">3</span><span class="cl">~/dotfiles/git/.gitconfig      → ~/.gitconfig</span></span></code></pre></div><p>每個頂層目錄（<code>zsh/</code>、<code>nvim/</code>、<code>git/</code>）是一個 stow package，可以獨立安裝或移除。</p>
<h2 id="常用指令">常用指令</h2>





<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> ~/dotfiles
</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"># 安裝：在目標目錄（預設 $HOME 的上一層，通常用 --target）建立 symlink</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">stow zsh                <span class="c1"># 安裝 zsh package</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">stow zsh git nvim tmux  <span class="c1"># 批次安裝多個 package</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">stow */                 <span class="c1"># 安裝所有 package</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"># 移除：刪除該 package 建立的 symlink</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">stow -D nvim
</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">stow -R zsh
</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"># 收養：如果目標位置已有檔案，--adopt 把它移進 repo 再建 symlink</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">stow --adopt zsh</span></span></code></pre></div><p><code>--adopt</code> 是首次把現有配置納入 dotfile 管理時的關鍵操作——它把家目錄的既有檔案「收養」進 repo（移動過去），然後建 symlink。之後 <code>git diff</code> 就能看到 repo 版本跟原版的差異。</p>
<h2 id="folding-與-unfolding">Folding 與 Unfolding</h2>
<p>Stow 會自動判斷要 symlink 整個目錄還是逐一 symlink 檔案：</p>
<ul>
<li><strong>Folding</strong>：目標目錄不存在、或目錄內所有檔案都由同一個 package 管理 → symlink 整個目錄</li>
<li><strong>Unfolding</strong>：目標目錄已有其他來源的檔案 → 展開成逐檔 symlink，保留既有檔案不受影響</li>
</ul>
<p>這個機制讓多個 package 可以共存於同一個目標目錄（如 <code>~/.config/</code>）。</p>
<h2 id="限制">限制</h2>
<ul>
<li>只管 symlink 映射，不管套件安裝（套件由 Brewfile 或 packages.txt 處理）</li>
<li>不管 file permission——需要 0600 的 secret 檔靠 symlink 繼承來源權限，無法在部署時自動 chmod</li>
<li>沒有 template 機制——同一份配置在不同機器要不同內容時，需要在配置檔內用 shell 的 OS 判斷處理</li>
</ul>
<p>完整選型比較見<a href="/blog/linux/dotfile/01-dotfile-management/management-strategies/" data-link-title="管理策略與選型" data-link-desc="要選 dotfile 管理工具時回來讀 — bare repo、stow、chezmoi 的適用場景與選型判讀">管理策略與選型</a>。</p>
]]></content:encoded></item><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>目錄結構、Git 工作流與常見陷阱</title><link>https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/directory-structure-workflow/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/01-dotfile-management/directory-structure-workflow/</guid><description>&lt;p>不管用哪個工具，dotfile repo 的目錄結構都遵循同一個原則：每個工具（或 package）是一個頂層目錄，內部路徑反映安裝後在家目錄的相對位置。&lt;/p>
&lt;h2 id="目錄結構設計">目錄結構設計&lt;/h2>
&lt;p>以 stow 為例的標準結構：&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">~/dotfiles/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">├── zsh/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">│ └── .zshrc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">├── git/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">│ ├── .gitconfig
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">│ └── .gitignore_global
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">├── ssh/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">│ └── .ssh/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">│ └── config
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">├── nvim/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">│ └── .config/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">│ └── nvim/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">│ ├── init.lua
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">│ └── lua/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">├── tmux/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">│ └── .config/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">│ └── tmux/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">│ └── tmux.conf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">├── hyprland/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">│ └── .config/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">│ └── hypr/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">│ └── hyprland.conf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">├── waybar/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">│ └── .config/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">│ └── waybar/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">│ ├── config.jsonc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">│ └── style.css
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">├── scripts/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">│ └── install.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">├── Brewfile
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">├── packages.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">├── .gitignore
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">└── README.md&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這些設計選擇的理由：&lt;/p>
&lt;p>&lt;strong>每個工具一個頂層目錄&lt;/strong>。stow 的 package 概念讓你可以選擇性安裝——伺服器不需要 hyprland 和 waybar，只 stow 需要的 package。即使不用 stow，這個分法也讓 repo 結構清晰：看頂層目錄就知道管了哪些工具。&lt;/p>
&lt;p>&lt;strong>目錄內路徑映射安裝位置&lt;/strong>。&lt;code>nvim/.config/nvim/init.lua&lt;/code> 安裝後變成 &lt;code>~/.config/nvim/init.lua&lt;/code>。這個映射是 stow 的核心假設，但即使用 chezmoi 或 bare repo，維持同樣的思維讓目錄結構自解釋。&lt;/p>
&lt;p>&lt;strong>scripts/ 不是 stow package&lt;/strong>。&lt;code>scripts/install.sh&lt;/code> 是 bootstrap 用的安裝腳本，不應該被 stow 到家目錄。它放在 repo 裡是為了讓新機器還原時有一個入口點可以跑。&lt;/p>
&lt;p>&lt;strong>Brewfile / packages.txt 記錄套件清單&lt;/strong>。配置檔只告訴工具「怎麼用」，但前提是工具已安裝。&lt;code>Brewfile&lt;/code>（macOS 用 &lt;code>brew bundle&lt;/code>）和 &lt;code>packages.txt&lt;/code>（Linux 用套件管理器批次安裝）把「裝了什麼」也納入版控，讓新機器還原時不用靠記憶。&lt;/p>
&lt;p>&lt;strong>ssh/ 只放 config，不放私鑰&lt;/strong>。&lt;code>~/.ssh/config&lt;/code> 記錄 SSH 連線設定（Host alias、ProxyJump 等），是有版控價值的配置。私鑰（&lt;code>id_ed25519&lt;/code>、&lt;code>id_rsa&lt;/code>）和公鑰不應進 dotfile repo，即使 repo 是 private。私鑰用密碼管理器或機器本地生成。&lt;/p>
&lt;h2 id="git-工作流">Git 工作流&lt;/h2>
&lt;p>dotfile repo 的 Git 工作流比一般程式碼專案簡單，因為通常只有一個人在用，branch 和 PR 的需求低。&lt;/p>
&lt;p>&lt;strong>日常修改&lt;/strong>。直接編輯配置（symlink 透通到 repo 裡的實體檔案），然後 commit：&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">cd&lt;/span> ~/dotfiles
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">git add zsh/.zshrc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">git commit -m &lt;span class="s2">&amp;#34;zsh: add fzf integration&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">git push&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>新增一個工具的配置&lt;/strong>。先在 dotfiles 建好目錄結構，把現有配置搬進去，建 symlink，然後 commit：&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 ~/dotfiles/alacritty/.config/alacritty
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">mv ~/.config/alacritty/alacritty.toml ~/dotfiles/alacritty/.config/alacritty/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ~/dotfiles &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> stow alacritty
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">git add alacritty/ &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> git commit -m &lt;span class="s2">&amp;#34;add alacritty config&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>新機器還原&lt;/strong>。整個流程應該能在幾分鐘內完成：&lt;/p></description><content:encoded><![CDATA[<p>不管用哪個工具，dotfile repo 的目錄結構都遵循同一個原則：每個工具（或 package）是一個頂層目錄，內部路徑反映安裝後在家目錄的相對位置。</p>
<h2 id="目錄結構設計">目錄結構設計</h2>
<p>以 stow 為例的標準結構：</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">├── zsh/
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">│   └── .zshrc
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">├── git/
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│   ├── .gitconfig
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">│   └── .gitignore_global
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">├── ssh/
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">│   └── .ssh/
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">│       └── config
</span></span><span class="line"><span class="ln">10</span><span class="cl">├── nvim/
</span></span><span class="line"><span class="ln">11</span><span class="cl">│   └── .config/
</span></span><span class="line"><span class="ln">12</span><span class="cl">│       └── nvim/
</span></span><span class="line"><span class="ln">13</span><span class="cl">│           ├── init.lua
</span></span><span class="line"><span class="ln">14</span><span class="cl">│           └── lua/
</span></span><span class="line"><span class="ln">15</span><span class="cl">├── tmux/
</span></span><span class="line"><span class="ln">16</span><span class="cl">│   └── .config/
</span></span><span class="line"><span class="ln">17</span><span class="cl">│       └── tmux/
</span></span><span class="line"><span class="ln">18</span><span class="cl">│           └── tmux.conf
</span></span><span class="line"><span class="ln">19</span><span class="cl">├── hyprland/
</span></span><span class="line"><span class="ln">20</span><span class="cl">│   └── .config/
</span></span><span class="line"><span class="ln">21</span><span class="cl">│       └── hypr/
</span></span><span class="line"><span class="ln">22</span><span class="cl">│           └── hyprland.conf
</span></span><span class="line"><span class="ln">23</span><span class="cl">├── waybar/
</span></span><span class="line"><span class="ln">24</span><span class="cl">│   └── .config/
</span></span><span class="line"><span class="ln">25</span><span class="cl">│       └── waybar/
</span></span><span class="line"><span class="ln">26</span><span class="cl">│           ├── config.jsonc
</span></span><span class="line"><span class="ln">27</span><span class="cl">│           └── style.css
</span></span><span class="line"><span class="ln">28</span><span class="cl">├── scripts/
</span></span><span class="line"><span class="ln">29</span><span class="cl">│   └── install.sh
</span></span><span class="line"><span class="ln">30</span><span class="cl">├── Brewfile
</span></span><span class="line"><span class="ln">31</span><span class="cl">├── packages.txt
</span></span><span class="line"><span class="ln">32</span><span class="cl">├── .gitignore
</span></span><span class="line"><span class="ln">33</span><span class="cl">└── README.md</span></span></code></pre></div><p>這些設計選擇的理由：</p>
<p><strong>每個工具一個頂層目錄</strong>。stow 的 package 概念讓你可以選擇性安裝——伺服器不需要 hyprland 和 waybar，只 stow 需要的 package。即使不用 stow，這個分法也讓 repo 結構清晰：看頂層目錄就知道管了哪些工具。</p>
<p><strong>目錄內路徑映射安裝位置</strong>。<code>nvim/.config/nvim/init.lua</code> 安裝後變成 <code>~/.config/nvim/init.lua</code>。這個映射是 stow 的核心假設，但即使用 chezmoi 或 bare repo，維持同樣的思維讓目錄結構自解釋。</p>
<p><strong>scripts/ 不是 stow package</strong>。<code>scripts/install.sh</code> 是 bootstrap 用的安裝腳本，不應該被 stow 到家目錄。它放在 repo 裡是為了讓新機器還原時有一個入口點可以跑。</p>
<p><strong>Brewfile / packages.txt 記錄套件清單</strong>。配置檔只告訴工具「怎麼用」，但前提是工具已安裝。<code>Brewfile</code>（macOS 用 <code>brew bundle</code>）和 <code>packages.txt</code>（Linux 用套件管理器批次安裝）把「裝了什麼」也納入版控，讓新機器還原時不用靠記憶。</p>
<p><strong>ssh/ 只放 config，不放私鑰</strong>。<code>~/.ssh/config</code> 記錄 SSH 連線設定（Host alias、ProxyJump 等），是有版控價值的配置。私鑰（<code>id_ed25519</code>、<code>id_rsa</code>）和公鑰不應進 dotfile repo，即使 repo 是 private。私鑰用密碼管理器或機器本地生成。</p>
<h2 id="git-工作流">Git 工作流</h2>
<p>dotfile repo 的 Git 工作流比一般程式碼專案簡單，因為通常只有一個人在用，branch 和 PR 的需求低。</p>
<p><strong>日常修改</strong>。直接編輯配置（symlink 透通到 repo 裡的實體檔案），然後 commit：</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">cd</span> ~/dotfiles
</span></span><span class="line"><span class="ln">2</span><span class="cl">git add zsh/.zshrc
</span></span><span class="line"><span class="ln">3</span><span class="cl">git commit -m <span class="s2">&#34;zsh: add fzf integration&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">git push</span></span></code></pre></div><p><strong>新增一個工具的配置</strong>。先在 dotfiles 建好目錄結構，把現有配置搬進去，建 symlink，然後 commit：</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 ~/dotfiles/alacritty/.config/alacritty
</span></span><span class="line"><span class="ln">2</span><span class="cl">mv ~/.config/alacritty/alacritty.toml ~/dotfiles/alacritty/.config/alacritty/
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nb">cd</span> ~/dotfiles <span class="o">&amp;&amp;</span> stow alacritty
</span></span><span class="line"><span class="ln">4</span><span class="cl">git add alacritty/ <span class="o">&amp;&amp;</span> git commit -m <span class="s2">&#34;add alacritty config&#34;</span></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"><span class="c1"># 1. 裝 Git 和 stow（通常是最先裝的兩個東西）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># 2. clone</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">git clone git@github.com:you/dotfiles.git ~/dotfiles
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 3. 安裝套件</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nb">cd</span> ~/dotfiles
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">brew bundle          <span class="c1"># macOS</span>
</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">xargs sudo pacman -S &lt; packages.txt  <span class="c1"># Arch</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 4. 建 symlink</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">stow zsh git nvim tmux ssh
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 5. 重開 shell，配置生效</span></span></span></code></pre></div><h2 id="常見陷阱">常見陷阱</h2>
<p><strong>私鑰進 repo</strong>。把 <code>~/.ssh/</code> 整個目錄（含 <code>id_ed25519</code>）推上 GitHub 是最危險的錯誤。即使事後刪除，Git 歷史裡仍然留有私鑰。做法是只追蹤 <code>~/.ssh/config</code>，在 <code>.gitignore</code> 明確排除 <code>*.pem</code>、<code>id_*</code>。</p>
<p><strong>缺少 .gitignore</strong>。很多工具會在配置目錄產生 cache、compiled 檔案、session 狀態。nvim 的 <code>plugin/packer_compiled.lua</code>、zsh 的 <code>.zcompdump</code>、tmux 的 <code>resurrect/</code> 都不該進 repo。建 repo 時第一件事就是寫 <code>.gitignore</code>。</p>
<p><strong>symlink 衝突</strong>。<code>stow zsh</code> 時如果 <code>~/.zshrc</code> 已經存在且不是 symlink，stow 會拒絕操作。解法是先備份再安裝：</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">mv ~/.zshrc ~/.zshrc.bak
</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> stow zsh</span></span></code></pre></div><p><strong>路徑寫死</strong>。<code>.zshrc</code> 裡寫 <code>source /Users/mac-eric/.nvm/nvm.sh</code> 搬到 Linux 就壞了。改用 <code>$HOME</code>：<code>source &quot;$HOME/.nvm/nvm.sh&quot;</code>。配置檔裡每一處絕對路徑都是可攜性的隱患。</p>
<p><strong>整包 .config 放一個 package</strong>。把 <code>~/.config</code> 整個目錄當成一個 stow package 會喪失模組化的好處，而且衝突風險大幅增加。正確做法是每個工具拆開：nvim 一個、tmux 一個、hyprland 一個。</p>
]]></content:encoded></item></channel></rss>