<?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>Brewfile on Tarragon</title><link>https://tarrragon.github.io/blog/tags/brewfile/</link><description>Recent content in Brewfile 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/brewfile/index.xml" rel="self" type="application/rss+xml"/><item><title>Bootstrap Script 與套件清單管理</title><link>https://tarrragon.github.io/blog/linux/dotfile/08-sync-bootstrap/bootstrap-script-packages/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/08-sync-bootstrap/bootstrap-script-packages/</guid><description>&lt;p>一份 bootstrap script 是重建指令的入口。它做三件事：安裝套件、部署配置檔、執行初始化設定。&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="cp">#!/usr/bin/env bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="cp">&lt;/span>&lt;span class="nb">set&lt;/span> -euo pipefail
&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="nv">DOTFILES_DIR&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>&lt;span class="nb">cd&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>dirname &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$0&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">)&lt;/span>&lt;span class="s2">/..&amp;#34;&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">pwd&lt;/span>&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 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"># --- 偵測 OS ---&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="nv">OS&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">install_packages&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="o">[[&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$OS&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">11&lt;/span>&lt;span class="cl"> &lt;span class="nb">command&lt;/span> -v brew &amp;gt;/dev/null &lt;span class="o">||&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;Installing Homebrew...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> /bin/bash -c &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> brew bundle --file&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$DOTFILES_DIR&lt;/span>&lt;span class="s2">/Brewfile&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="o">[[&lt;/span> -f /etc/arch-release &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">18&lt;/span>&lt;span class="cl"> sudo pacman -Syu --noconfirm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> sudo pacman -S --needed - &amp;lt; &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$DOTFILES_DIR&lt;/span>&lt;span class="s2">/packages.txt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="c1"># AUR 套件需要 AUR helper（假設已安裝 yay）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">command&lt;/span> -v yay &amp;gt;/dev/null &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="o">[[&lt;/span> -f &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$DOTFILES_DIR&lt;/span>&lt;span class="s2">/aur-packages.txt&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">22&lt;/span>&lt;span class="cl"> yay -S --needed - &amp;lt; &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$DOTFILES_DIR&lt;/span>&lt;span class="s2">/aur-packages.txt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&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">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="o">[[&lt;/span> -f /etc/debian_version &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">26&lt;/span>&lt;span class="cl"> sudo apt update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> xargs -a &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$DOTFILES_DIR&lt;/span>&lt;span class="s2">/apt-packages.txt&amp;#34;&lt;/span> sudo apt install -y
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&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">29&lt;/span>&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">deploy_configs&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> ! &lt;span class="nb">command&lt;/span> -v stow &amp;gt;/dev/null&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">33&lt;/span>&lt;span class="cl"> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;stow not found, skipping config deployment&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&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">36&lt;/span>&lt;span class="cl"> &lt;span class="nb">cd&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$DOTFILES_DIR&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> dir in zsh git nvim tmux hypr waybar&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">38&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">$dir&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> stow -v --target&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">&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$dir&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="k">done&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">post_setup&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 切換預設 shell&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&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="nv">$SHELL&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> !&lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>&lt;span class="nb">command&lt;/span> -v zsh&lt;span class="k">)&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="nb">command&lt;/span> -v zsh &amp;gt;/dev/null&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">45&lt;/span>&lt;span class="cl"> chsh -s &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>&lt;span class="nb">command&lt;/span> -v zsh&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&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">47&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="c1"># neovim plugin 安裝（headless 模式）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">command&lt;/span> -v nvim &amp;gt;/dev/null&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">50&lt;/span>&lt;span class="cl"> nvim --headless &lt;span class="s2">&amp;#34;+Lazy! sync&amp;#34;&lt;/span> +qa 2&amp;gt;/dev/null &lt;span class="o">||&lt;/span> &lt;span class="nb">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&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">52&lt;/span>&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;=== Installing packages ===&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl">install_packages
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;=== Deploying configs ===&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl">deploy_configs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;=== Post-setup ===&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl">post_setup
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;Done. Log out and back in for shell changes to take effect.&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="設計原則">設計原則&lt;/h2>
&lt;p>&lt;strong>冪等性&lt;/strong>是最重要的性質。跑一次和跑十次結果相同。&lt;code>pacman -S --needed&lt;/code> 只安裝缺少的套件、&lt;code>stow&lt;/code> 只建立不存在的 symlink、&lt;code>command -v&lt;/code> 在工具已存在時跳過安裝（用 &lt;code>command -v&lt;/code> 不用 &lt;code>which&lt;/code>——後者在最小系統可能不存在）。冪等的 script 可以放心重跑——改了一個配置後重新 deploy，不會弄壞其他已經正確的部分。&lt;/p></description><content:encoded><![CDATA[<p>一份 bootstrap script 是重建指令的入口。它做三件事：安裝套件、部署配置檔、執行初始化設定。</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="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="cp"></span><span class="nb">set</span> -euo pipefail
</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="nv">DOTFILES_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span><span class="nb">cd</span> <span class="s2">&#34;</span><span class="k">$(</span>dirname <span class="s2">&#34;</span><span class="nv">$0</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">/..&#34;</span> <span class="o">&amp;&amp;</span> <span class="nb">pwd</span><span class="k">)</span><span class="s2">&#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="c1"># --- 偵測 OS ---</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nv">OS</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></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">install_packages<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$OS</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">11</span><span class="cl">        <span class="nb">command</span> -v brew &gt;/dev/null <span class="o">||</span> <span class="o">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="nb">echo</span> <span class="s2">&#34;Installing Homebrew...&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            /bin/bash -c <span class="s2">&#34;</span><span class="k">$(</span>curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="o">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        brew bundle --file<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$DOTFILES_DIR</span><span class="s2">/Brewfile&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">elif</span> <span class="o">[[</span> -f /etc/arch-release <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        sudo pacman -Syu --noconfirm
</span></span><span class="line"><span class="ln">19</span><span class="cl">        sudo pacman -S --needed - &lt; <span class="s2">&#34;</span><span class="nv">$DOTFILES_DIR</span><span class="s2">/packages.txt&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="c1"># AUR 套件需要 AUR helper（假設已安裝 yay）</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">if</span> <span class="nb">command</span> -v yay &gt;/dev/null <span class="o">&amp;&amp;</span> <span class="o">[[</span> -f <span class="s2">&#34;</span><span class="nv">$DOTFILES_DIR</span><span class="s2">/aur-packages.txt&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            yay -S --needed - &lt; <span class="s2">&#34;</span><span class="nv">$DOTFILES_DIR</span><span class="s2">/aur-packages.txt&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">fi</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">elif</span> <span class="o">[[</span> -f /etc/debian_version <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        sudo apt update
</span></span><span class="line"><span class="ln">27</span><span class="cl">        xargs -a <span class="s2">&#34;</span><span class="nv">$DOTFILES_DIR</span><span class="s2">/apt-packages.txt&#34;</span> sudo apt install -y
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">deploy_configs<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">if</span> ! <span class="nb">command</span> -v stow &gt;/dev/null<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;stow not found, skipping config deployment&#34;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="k">return</span> <span class="m">1</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="nb">cd</span> <span class="s2">&#34;</span><span class="nv">$DOTFILES_DIR</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">for</span> dir in zsh git nvim tmux hypr waybar<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="o">[[</span> -d <span class="s2">&#34;</span><span class="nv">$dir</span><span class="s2">&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> stow -v --target<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$dir</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="k">done</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl">post_setup<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="c1"># 切換預設 shell</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$SHELL</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;</span><span class="k">$(</span><span class="nb">command</span> -v zsh<span class="k">)</span><span class="s2">&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nb">command</span> -v zsh &gt;/dev/null<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        chsh -s <span class="s2">&#34;</span><span class="k">$(</span><span class="nb">command</span> -v zsh<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="c1"># neovim plugin 安裝（headless 模式）</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="k">if</span> <span class="nb">command</span> -v nvim &gt;/dev/null<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        nvim --headless <span class="s2">&#34;+Lazy! sync&#34;</span> +qa 2&gt;/dev/null <span class="o">||</span> <span class="nb">true</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;=== Installing packages ===&#34;</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">install_packages
</span></span><span class="line"><span class="ln">56</span><span class="cl">
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;=== Deploying configs ===&#34;</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">deploy_configs
</span></span><span class="line"><span class="ln">59</span><span class="cl">
</span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;=== Post-setup ===&#34;</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">post_setup
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Done. Log out and back in for shell changes to take effect.&#34;</span></span></span></code></pre></div><h2 id="設計原則">設計原則</h2>
<p><strong>冪等性</strong>是最重要的性質。跑一次和跑十次結果相同。<code>pacman -S --needed</code> 只安裝缺少的套件、<code>stow</code> 只建立不存在的 symlink、<code>command -v</code> 在工具已存在時跳過安裝（用 <code>command -v</code> 不用 <code>which</code>——後者在最小系統可能不存在）。冪等的 script 可以放心重跑——改了一個配置後重新 deploy，不會弄壞其他已經正確的部分。</p>
<p><strong>失敗可診斷</strong>是這支範例為了聚焦結構而省略、但實際該有的性質。bootstrap 在陌生機器上失敗是常態，怎麼讓它在某一步掛掉時留下可定位的痕跡（全輸出 tee 落地 + ERR trap 點名出錯的行與指令），見 <a href="/blog/linux/install/observable-bootstrap/" data-link-title="可除錯的 bootstrap：把可觀測性內建進安裝腳本" data-link-desc="安裝腳本中途失敗卻只能對著終端機捲動瞎找原因、想在 bootstrap 設計階段就讓失敗可定位時回來讀">可除錯的 bootstrap</a>——那篇是這支腳本的「失敗時看得見」那一層。</p>
<p><strong>偵測 OS 分流</strong>處理的是跨平台差異。macOS 用 Homebrew、Arch 用 pacman、Debian 系用 apt——套件管理器不同、套件名稱有時也不同（macOS 的 <code>coreutils</code> 在 Linux 是預裝的）。分流邏輯集中在 bootstrap script 裡，配置檔本身盡量保持跨平台一致。</p>
<p><strong>最少依賴</strong>原則：script 本身只依賴 bash 和 curl（幾乎所有系統預裝），其他工具由 script 自己安裝。這確保你可以在一台只有 base system 的機器上直接跑 script。不過「base system 直接跑」有個前提——最小安裝可能連 <code>sudo</code> 都沒有，而 script 裝套件正要靠它。跑這支 script 之前該驗證並補齊的前置工具，見 <a href="/blog/linux/install/minimal-install-verify/" data-link-title="最小安裝後的工具驗證與補足" data-link-desc="最小化安裝的 Linux 裝完發現連 sudo 或 which 都沒有、bootstrap 腳本第一行就炸、需要先確認系統缺哪些必要工具再補時回來讀">最小安裝後的工具驗證與補足</a>。</p>
<p><strong>交付完整可用的環境</strong>：script 的職責是讓部署完的配置「能直接用」，所以它必須裝齊配置實際引用的每一樣東西，而不是假設它們已經在。一個常見的破綻是把依賴寫進 README 的「dependencies」清單、卻沒在 script 裡實作安裝——例如 <code>.zshrc</code> 引用了 oh-my-zsh、主題、外掛，但 install script 只裝了 zsh 本身，結果 stow 部署完、第一次開 shell 就因為找不到那些東西而報錯。README 列依賴是給人看的、不會被執行；要讓配置真的能用，那些依賴得由 script 自己裝（例如把外掛 git clone 進對應位置）。檢查方式是反過來從配置出發：把每個 config 會 source 或引用的外部東西列出來，逐一確認 script 有沒有負責把它裝上。</p>
<p><strong>可部分執行</strong>的結構：拆成 function，允許只跑某一段。除錯時只想重新 deploy 配置、不想重裝套件，直接呼叫 <code>deploy_configs</code> 就好。進一步可以把每段拆成獨立 script（<code>scripts/install-packages.sh</code>、<code>scripts/deploy-configs.sh</code>），bootstrap 入口只是依序呼叫它們。</p>
<h2 id="套件清單管理">套件清單管理</h2>
<p>dotfile repo 管的是「配置」，但配置的前提是軟體已安裝。沒有附帶套件清單的 dotfile repo 是不完整的重建指令——你 clone 下來卻不知道該先裝什麼。</p>
<h3 id="macosbrewfile">macOS：Brewfile</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Brewfile</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">tap</span> <span class="s2">&#34;homebrew/cask-fonts&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># CLI 工具</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">brew</span> <span class="s2">&#34;git&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">brew</span> <span class="s2">&#34;neovim&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">brew</span> <span class="s2">&#34;tmux&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">brew</span> <span class="s2">&#34;stow&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">brew</span> <span class="s2">&#34;ripgrep&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">brew</span> <span class="s2">&#34;fd&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">brew</span> <span class="s2">&#34;fzf&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">brew</span> <span class="s2">&#34;zsh&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># GUI app</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">cask</span> <span class="s2">&#34;wezterm&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">cask</span> <span class="s2">&#34;rectangle&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">cask</span> <span class="s2">&#34;font-jetbrains-mono-nerd-font&#34;</span></span></span></code></pre></div><p><code>brew bundle dump</code> 從當前系統產生 Brewfile、<code>brew bundle</code> 照 Brewfile 安裝。Brewfile 區分三種來源：<code>brew</code>（CLI formula）、<code>cask</code>（GUI app）、<code>tap</code>（第三方 repo）。把 Brewfile 放在 dotfile repo 根目錄，bootstrap script 用 <code>brew bundle --file=./Brewfile</code> 安裝。</p>
<h3 id="arch-linuxpackagestxt">Arch Linux：packages.txt</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 匯出已安裝的 explicitly installed 套件</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">pacman -Qqe &gt; packages.txt
</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"># AUR 套件另外記</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">pacman -Qqem &gt; aur-packages.txt</span></span></code></pre></div><p><code>-Qqe</code> 只列出使用者主動安裝的套件（不含被依賴自動拉進來的），這是你實際需要管理的範圍。<code>-Qqem</code> 進一步篩出外部來源（AUR）。還原時用 <code>pacman -S --needed - &lt; packages.txt</code>，<code>--needed</code> 跳過已安裝的。</p>
<h3 id="ubuntudebian">Ubuntu/Debian</h3>
<p>apt 的匯出格式比較雜。務實做法是手動維護一份清單檔（<code>apt-packages.txt</code>），每行一個套件名，用 <code>xargs -a apt-packages.txt sudo apt install -y</code> 安裝。比起 <code>apt list --installed</code> 的完整匯出（包含大量系統依賴），手動維護的清單更乾淨、更容易讀懂。</p>
<h3 id="為什麼套件清單要進-repo">為什麼套件清單要進 repo</h3>
<p>一個常見的失敗模式：dotfile repo 裡有完整的 neovim 配置，clone 到新機器後發現 neovim 沒裝、ripgrep 沒裝、字型沒裝，配置跑起來全是 error。套件清單跟配置檔放在同一個 repo，bootstrap script 才能先裝套件再 deploy 配置，形成完整的重建鏈路。</p>
]]></content:encoded></item></channel></rss>