<?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>模組八：同步、Bootstrap 與環境重建 on Tarragon</title><link>https://tarrragon.github.io/blog/linux/dotfile/08-sync-bootstrap/</link><description>Recent content in 模組八：同步、Bootstrap 與環境重建 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/linux/dotfile/08-sync-bootstrap/index.xml" rel="self" type="application/rss+xml"/><item><title>拍照 vs 重建指令：環境重建的兩種思路</title><link>https://tarrragon.github.io/blog/linux/dotfile/08-sync-bootstrap/snapshot-vs-rebuild/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/08-sync-bootstrap/snapshot-vs-rebuild/</guid><description>&lt;p>環境重建是 dotfile 管理的最終目的：拿到一台空白機器，能在可預期的時間內還原成你熟悉的工作環境。這件事有兩條根本不同的路線，選哪條決定了你之後所有的管理策略。&lt;/p>
&lt;h2 id="拍照vm-快照與磁碟映像">拍照：VM 快照與磁碟映像&lt;/h2>
&lt;p>第一種是&lt;strong>拍照&lt;/strong>。VM 快照和磁碟映像（Clonezilla、&lt;code>dd&lt;/code>）做的事是把整台機器某一刻的完整狀態凍結存檔——整個虛擬硬碟的 block-level 複製，有時連記憶體狀態都包含。還原就是把映像寫回去，系統回到那一刻，像時光倒流。Docker 的 &lt;code>docker commit&lt;/code> 也屬於這個方向：把正在跑的 container 的檔案系統快照成一個 image。&lt;/p>
&lt;p>拍照產出的是&lt;strong>黑盒子&lt;/strong>。一個磁碟映像是二進制檔案，沒人能看出裡面到底做了什麼設定、裝了什麼、改過什麼。它大（動輒 GB 級）、跟硬體耦合（換不同架構或不同顯卡可能開不起來）、無法做 diff 或 code review。&lt;/p>
&lt;h2 id="重建指令dotfile-repo--install-script">重建指令：Dotfile repo + install script&lt;/h2>
&lt;p>第二種是&lt;strong>重建指令&lt;/strong>。Dotfile repo + install script 描述的是「怎麼從一台空白機器組出這個環境」，每次都從零開始執行。Dockerfile 也是重建指令——一份「照著做就能重現」的食譜，描述每一步該安裝什麼、複製什麼、怎麼啟動。&lt;/p>
&lt;p>重建指令產出的是&lt;strong>白盒子&lt;/strong>。每一步都是可讀的文字——這行裝 zsh、那行設定 Hyprland 的 keybind——可以被 review、被 diff、被另一個人讀懂。它小（通常幾十 KB）、跨硬體（同一份 script 加 OS 判斷就能跑在不同機器）、可以進版控走 PR 流程。&lt;/p>
&lt;p>dotfile 管理選的是重建指令這條路。代價是你必須把環境建構的過程記錄清楚——每裝一個新工具、每改一個配置都要同步更新 repo。回報是任何一台機器、任何時間點，都能用一份 Git repo 重現你的工作環境。&lt;/p>
&lt;h2 id="場景判讀">場景判讀&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>需求&lt;/th>
 &lt;th>VM 快照&lt;/th>
 &lt;th>Dotfile 重建&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>保留某一刻的完整系統狀態&lt;/td>
 &lt;td>適合（block-level 完整備份）&lt;/td>
 &lt;td>不適合（只管配置層）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>在新機器還原工作環境&lt;/td>
 &lt;td>不適合（硬體耦合、映像大）&lt;/td>
 &lt;td>適合（跨硬體、輕量）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>讓環境可被他人重現（onboarding）&lt;/td>
 &lt;td>勉強（黑盒子、難維護）&lt;/td>
 &lt;td>適合（白盒子、可 review）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>在多台機器維持一致&lt;/td>
 &lt;td>不適合（每台都要拍照）&lt;/td>
 &lt;td>適合（一份 repo、多台 apply）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>做實驗後回滾（改壞了想恢復）&lt;/td>
 &lt;td>適合（秒級回滾）&lt;/td>
 &lt;td>要靠 git revert + 重新 apply&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>讓桌面配置進 review 流程&lt;/td>
 &lt;td>不適合（二進制映像無法 diff）&lt;/td>
 &lt;td>適合（純文字、可 diff、可 PR）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>兩者不互斥——常見的組合是：用 dotfile 管理配置（長期可維護的基線），VM 快照用於短期實驗保護（改爛了可以秒回）。&lt;/p></description><content:encoded><![CDATA[<p>環境重建是 dotfile 管理的最終目的：拿到一台空白機器，能在可預期的時間內還原成你熟悉的工作環境。這件事有兩條根本不同的路線，選哪條決定了你之後所有的管理策略。</p>
<h2 id="拍照vm-快照與磁碟映像">拍照：VM 快照與磁碟映像</h2>
<p>第一種是<strong>拍照</strong>。VM 快照和磁碟映像（Clonezilla、<code>dd</code>）做的事是把整台機器某一刻的完整狀態凍結存檔——整個虛擬硬碟的 block-level 複製，有時連記憶體狀態都包含。還原就是把映像寫回去，系統回到那一刻，像時光倒流。Docker 的 <code>docker commit</code> 也屬於這個方向：把正在跑的 container 的檔案系統快照成一個 image。</p>
<p>拍照產出的是<strong>黑盒子</strong>。一個磁碟映像是二進制檔案，沒人能看出裡面到底做了什麼設定、裝了什麼、改過什麼。它大（動輒 GB 級）、跟硬體耦合（換不同架構或不同顯卡可能開不起來）、無法做 diff 或 code review。</p>
<h2 id="重建指令dotfile-repo--install-script">重建指令：Dotfile repo + install script</h2>
<p>第二種是<strong>重建指令</strong>。Dotfile repo + install script 描述的是「怎麼從一台空白機器組出這個環境」，每次都從零開始執行。Dockerfile 也是重建指令——一份「照著做就能重現」的食譜，描述每一步該安裝什麼、複製什麼、怎麼啟動。</p>
<p>重建指令產出的是<strong>白盒子</strong>。每一步都是可讀的文字——這行裝 zsh、那行設定 Hyprland 的 keybind——可以被 review、被 diff、被另一個人讀懂。它小（通常幾十 KB）、跨硬體（同一份 script 加 OS 判斷就能跑在不同機器）、可以進版控走 PR 流程。</p>
<p>dotfile 管理選的是重建指令這條路。代價是你必須把環境建構的過程記錄清楚——每裝一個新工具、每改一個配置都要同步更新 repo。回報是任何一台機器、任何時間點，都能用一份 Git repo 重現你的工作環境。</p>
<h2 id="場景判讀">場景判讀</h2>
<table>
  <thead>
      <tr>
          <th>需求</th>
          <th>VM 快照</th>
          <th>Dotfile 重建</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>保留某一刻的完整系統狀態</td>
          <td>適合（block-level 完整備份）</td>
          <td>不適合（只管配置層）</td>
      </tr>
      <tr>
          <td>在新機器還原工作環境</td>
          <td>不適合（硬體耦合、映像大）</td>
          <td>適合（跨硬體、輕量）</td>
      </tr>
      <tr>
          <td>讓環境可被他人重現（onboarding）</td>
          <td>勉強（黑盒子、難維護）</td>
          <td>適合（白盒子、可 review）</td>
      </tr>
      <tr>
          <td>在多台機器維持一致</td>
          <td>不適合（每台都要拍照）</td>
          <td>適合（一份 repo、多台 apply）</td>
      </tr>
      <tr>
          <td>做實驗後回滾（改壞了想恢復）</td>
          <td>適合（秒級回滾）</td>
          <td>要靠 git revert + 重新 apply</td>
      </tr>
      <tr>
          <td>讓桌面配置進 review 流程</td>
          <td>不適合（二進制映像無法 diff）</td>
          <td>適合（純文字、可 diff、可 PR）</td>
      </tr>
  </tbody>
</table>
<p>兩者不互斥——常見的組合是：用 dotfile 管理配置（長期可維護的基線），VM 快照用於短期實驗保護（改爛了可以秒回）。</p>
]]></content:encoded></item><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><item><title>跨機器同步、Secret 管理與環境重建流程</title><link>https://tarrragon.github.io/blog/linux/dotfile/08-sync-bootstrap/sync-strategy-secret/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/linux/dotfile/08-sync-bootstrap/sync-strategy-secret/</guid><description>&lt;h2 id="跨機器同步策略">跨機器同步策略&lt;/h2>
&lt;p>多台機器共用 dotfile repo 時，需要一套同步策略來處理「改了配置後怎麼讓其他機器也更新」。&lt;/p>
&lt;h3 id="git-pushpull手動">Git push/pull（手動）&lt;/h3>
&lt;p>最基本的做法：改了就 commit + push，另一台機器 pull + 重新 apply。優點是簡單、沒有額外依賴。缺點是容易忘記——在公司機器上改了一個 alias，回家忘記 push，隔天公司又改了一版，兩邊 diverge。&lt;/p>
&lt;p>適合只有一兩台機器、改動不頻繁的人。&lt;/p>
&lt;h3 id="自動同步">自動同步&lt;/h3>
&lt;p>chezmoi 內建 &lt;code>chezmoi update&lt;/code> 指令（pull + apply 一步完成），搭配 cron 或 systemd timer 定期執行：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># ~/.config/systemd/user/chezmoi-update.timer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="k">[Unit]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="na">Description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">Update dotfiles daily&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">[Timer]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="na">OnCalendar&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">daily&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="na">Persistent&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="k">[Install]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="na">WantedBy&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">timers.target&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-ini" data-lang="ini">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># ~/.config/systemd/user/chezmoi-update.service&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="k">[Unit]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="na">Description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">chezmoi update&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">[Service]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="na">Type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">oneshot&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="na">ExecStart&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">/usr/bin/chezmoi update --no-tty&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>自動同步減少手動操作，但要注意衝突處理——如果兩台機器同時改了同一個檔案且都 push，後面那台的自動 pull 會遇到 merge conflict。實務上 dotfile 很少有真正的衝突（兩台機器同時改同一行的機率低），但偶爾發生時需要手動介入。&lt;/p>
&lt;h3 id="機器差異的處理">機器差異的處理&lt;/h3>
&lt;p>推薦的模式是 main branch 放所有共用配置，機器差異用條件判斷處理。&lt;/p>
&lt;p>用 shell 的 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"># ~/.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="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;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="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/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">4&lt;/span>&lt;span class="cl"> &lt;span class="nb">alias&lt;/span> &lt;span class="nv">ls&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;ls -G&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 class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="nb">alias&lt;/span> &lt;span class="nv">ls&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;ls --color=auto&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="k">fi&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>用 chezmoi template（Go template 語法）：&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"># chezmoi 管理的 .zshrc.tmpl&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> &lt;span class="k">if&lt;/span> eq .chezmoi.os &lt;span class="s2">&amp;#34;darwin&amp;#34;&lt;/span> -&lt;span class="o">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="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/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">4&lt;/span>&lt;span class="cl">&lt;span class="o">{{&lt;/span> end -&lt;span class="o">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="o">{{&lt;/span> &lt;span class="k">if&lt;/span> eq .chezmoi.hostname &lt;span class="s2">&amp;#34;work-laptop&amp;#34;&lt;/span> -&lt;span class="o">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">HTTP_PROXY&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;http://proxy.corp:8080&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="o">{{&lt;/span> end -&lt;span class="o">}}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>chezmoi template 的優勢是條件判斷發生在 apply 階段，產出的檔案裡看不到 template 語法，乾淨且不依賴 shell 的 runtime 判斷。&lt;/p>
&lt;p>不推薦每台機器一個 branch 的做法。短期可行，長期一定 diverge——main 加了新配置，各 branch 要 rebase 或 merge，忘了就漂移。一份 main + template 條件判斷是長期可維護的結構。&lt;/p>
&lt;h2 id="secret-排除與管理">Secret 排除與管理&lt;/h2>
&lt;p>dotfile repo 通常是 public 或至少多人可見的。以下東西進了 repo 等於把鑰匙掛在門口：&lt;/p>
&lt;ul>
&lt;li>SSH 私鑰（&lt;code>~/.ssh/id_*&lt;/code>、&lt;code>*.pem&lt;/code>）&lt;/li>
&lt;li>API token、password、.env 檔案&lt;/li>
&lt;li>GPG 私鑰&lt;/li>
&lt;li>cloud provider 的 credential 檔案（&lt;code>~/.aws/credentials&lt;/code>、&lt;code>~/.config/gcloud/application_default_credentials.json&lt;/code>）&lt;/li>
&lt;li>browser profile 裡的 cookie / session&lt;/li>
&lt;/ul>
&lt;h3 id="gitignore-是第一道防線">.gitignore 是第一道防線&lt;/h3>





&lt;pre tabindex="0">&lt;code class="language-gitignore" data-lang="gitignore"># SSH 私鑰
*.pem
id_*
known_hosts
authorized_keys

# 環境變數
.env
.env.*

# Cloud credentials
credentials
application_default_credentials.json&lt;/code>&lt;/pre>&lt;p>但 .gitignore 只防「不小心 add」，不防「故意 add -f」。更重要的是建立習慣：repo 裡永遠只放「看到了也沒關係」的東西。&lt;/p>
&lt;h3 id="ssh-config-的特殊處理">SSH config 的特殊處理&lt;/h3>
&lt;p>&lt;code>~/.ssh/config&lt;/code>（host alias、ProxyJump 設定、port forwarding）本身不含 secret，可以進 repo——它記錄的是「連線要怎麼走」而不是「憑證是什麼」。但同一個 &lt;code>~/.ssh/&lt;/code> 目錄下的私鑰絕對排除。&lt;/p></description><content:encoded><![CDATA[<h2 id="跨機器同步策略">跨機器同步策略</h2>
<p>多台機器共用 dotfile repo 時，需要一套同步策略來處理「改了配置後怎麼讓其他機器也更新」。</p>
<h3 id="git-pushpull手動">Git push/pull（手動）</h3>
<p>最基本的做法：改了就 commit + push，另一台機器 pull + 重新 apply。優點是簡單、沒有額外依賴。缺點是容易忘記——在公司機器上改了一個 alias，回家忘記 push，隔天公司又改了一版，兩邊 diverge。</p>
<p>適合只有一兩台機器、改動不頻繁的人。</p>
<h3 id="自動同步">自動同步</h3>
<p>chezmoi 內建 <code>chezmoi update</code> 指令（pull + apply 一步完成），搭配 cron 或 systemd timer 定期執行：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># ~/.config/systemd/user/chezmoi-update.timer</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Update dotfiles daily</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">[Timer]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="na">OnCalendar</span><span class="o">=</span><span class="s">daily</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="na">Persistent</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">timers.target</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># ~/.config/systemd/user/chezmoi-update.service</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">chezmoi update</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">[Service]</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/chezmoi update --no-tty</span></span></span></code></pre></div><p>自動同步減少手動操作，但要注意衝突處理——如果兩台機器同時改了同一個檔案且都 push，後面那台的自動 pull 會遇到 merge conflict。實務上 dotfile 很少有真正的衝突（兩台機器同時改同一行的機率低），但偶爾發生時需要手動介入。</p>
<h3 id="機器差異的處理">機器差異的處理</h3>
<p>推薦的模式是 main branch 放所有共用配置，機器差異用條件判斷處理。</p>
<p>用 shell 的 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"># ~/.zshrc</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 -s<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="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;/opt/homebrew/bin:</span><span class="nv">$PATH</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nb">alias</span> <span class="nv">ls</span><span class="o">=</span><span class="s2">&#34;ls -G&#34;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nb">alias</span> <span class="nv">ls</span><span class="o">=</span><span class="s2">&#34;ls --color=auto&#34;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">fi</span></span></span></code></pre></div><p>用 chezmoi template（Go 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 管理的 .zshrc.tmpl</span>
</span></span><span class="line"><span class="ln">2</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">3</span><span class="cl"><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;/opt/homebrew/bin:</span><span class="nv">$PATH</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="o">{{</span> end -<span class="o">}}</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.hostname <span class="s2">&#34;work-laptop&#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">HTTP_PROXY</span><span class="o">=</span><span class="s2">&#34;http://proxy.corp:8080&#34;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="o">{{</span> end -<span class="o">}}</span></span></span></code></pre></div><p>chezmoi template 的優勢是條件判斷發生在 apply 階段，產出的檔案裡看不到 template 語法，乾淨且不依賴 shell 的 runtime 判斷。</p>
<p>不推薦每台機器一個 branch 的做法。短期可行，長期一定 diverge——main 加了新配置，各 branch 要 rebase 或 merge，忘了就漂移。一份 main + template 條件判斷是長期可維護的結構。</p>
<h2 id="secret-排除與管理">Secret 排除與管理</h2>
<p>dotfile repo 通常是 public 或至少多人可見的。以下東西進了 repo 等於把鑰匙掛在門口：</p>
<ul>
<li>SSH 私鑰（<code>~/.ssh/id_*</code>、<code>*.pem</code>）</li>
<li>API token、password、.env 檔案</li>
<li>GPG 私鑰</li>
<li>cloud provider 的 credential 檔案（<code>~/.aws/credentials</code>、<code>~/.config/gcloud/application_default_credentials.json</code>）</li>
<li>browser profile 裡的 cookie / session</li>
</ul>
<h3 id="gitignore-是第一道防線">.gitignore 是第一道防線</h3>





<pre tabindex="0"><code class="language-gitignore" data-lang="gitignore"># SSH 私鑰
*.pem
id_*
known_hosts
authorized_keys

# 環境變數
.env
.env.*

# Cloud credentials
credentials
application_default_credentials.json</code></pre><p>但 .gitignore 只防「不小心 add」，不防「故意 add -f」。更重要的是建立習慣：repo 裡永遠只放「看到了也沒關係」的東西。</p>
<h3 id="ssh-config-的特殊處理">SSH config 的特殊處理</h3>
<p><code>~/.ssh/config</code>（host alias、ProxyJump 設定、port forwarding）本身不含 secret，可以進 repo——它記錄的是「連線要怎麼走」而不是「憑證是什麼」。但同一個 <code>~/.ssh/</code> 目錄下的私鑰絕對排除。</p>
<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">└── ssh/
</span></span><span class="line"><span class="ln">3</span><span class="cl">    └── .ssh/
</span></span><span class="line"><span class="ln">4</span><span class="cl">        └── config        # 進 repo
</span></span><span class="line"><span class="ln">5</span><span class="cl">        # id_rsa 不放這裡
</span></span><span class="line"><span class="ln">6</span><span class="cl">        # known_hosts 不放這裡</span></span></code></pre></div><h3 id="三個層級的-secret-管理">三個層級的 secret 管理</h3>
<p><strong>層級一：手動</strong>。.gitignore 排除 secret 檔案，在 README 記錄「這些東西需要在新機器手動設定」。最低成本、對只有一兩台機器的人足夠。</p>
<p><strong>層級二：密碼管理器整合</strong>。chezmoi 支援從 1Password、Bitwarden、pass（Unix password manager）等拉取 secret：</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"># chezmoi template 語法
</span></span><span class="line"><span class="ln">2</span><span class="cl">{{ (onepasswordRead &#34;op://Personal/SSH Key/private key&#34;).value }}</span></span></code></pre></div><p>配置檔的 template 裡引用密碼管理器的條目，apply 時自動填入。secret 不在 repo 裡，但 repo 知道去哪拉。</p>
<p><strong>層級三：加密存放</strong>。用 age 或 sops 把 secret 加密後直接存在 repo 裡。解密需要對應的 key。chezmoi 原生支援 age 加密：</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">chezmoi add --encrypt ~/.ssh/id_rsa
</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"># repo 裡看到的是加密後的內容</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">cat ~/.local/share/chezmoi/private_dot_ssh/id_rsa.age</span></span></code></pre></div><p>加密存放的好處是 secret 跟著 repo 走、不用另外設密碼管理器。風險是加密 key 本身變成唯一的依賴——丟了 key，加密的 secret 就拿不回來。</p>
<p>層級選擇取決於安全需求和便利需求的平衡。多數情況從層級一開始，覺得手動處理太煩再往上升級。</p>
<h2 id="環境重建的實際流程">環境重建的實際流程</h2>
<p>假設拿到一台全新的 Arch Linux 機器，要從零重建完整的 Hyprland 桌面環境。以下是 end-to-end 的步驟，對應 <a href="/blog/linux/dotfile/08-sync-bootstrap/bootstrap-script-packages/" data-link-title="Bootstrap Script 與套件清單管理" data-link-desc="寫 dotfile 的 install script、或整理「這台機器裝了什麼」的套件清單時回來讀">bootstrap script</a> 的每個階段。</p>
<h3 id="階段一最小可用環境">階段一：最小可用環境</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Arch 安裝完成後，base system 只有 bash 和基本工具</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">sudo pacman -S git base-devel</span></span></code></pre></div><p>這是 bootstrap script 的唯一外部前提：有 Git 能 clone repo、有 base-devel 能編譯 AUR 套件。其他一切由 script 處理。</p>
<h3 id="階段二取得-dotfile-repo">階段二：取得 dotfile repo</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">git clone https://github.com/you/dotfiles ~/dotfiles
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">cd</span> ~/dotfiles</span></span></code></pre></div><p>如果 repo 是 private，這一步需要先設定 SSH key 或用 HTTPS + token。這是前面提到的 secret 雞生蛋問題——clone 含有 SSH config 的 repo 本身就需要 SSH key。解法通常是：第一次用 HTTPS clone，deploy 完 SSH config 後把 remote 改成 SSH。</p>
<h3 id="階段三執行-bootstrap">階段三：執行 bootstrap</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">./scripts/install.sh</span></span></code></pre></div><p>script 依序：安裝套件（Hyprland、waybar、rofi、wezterm、zsh、neovim、stow 等）、用 stow 部署配置到 <code>$HOME</code>、執行初始化（換 shell、安裝 neovim plugin）。</p>
<h3 id="階段四手動處理">階段四：手動處理</h3>
<p>bootstrap 處理不了（或不該處理）的部分：</p>
<ul>
<li><strong>SSH 私鑰</strong>：從備份或密碼管理器取回，放到 <code>~/.ssh/</code>，設定正確權限（<code>chmod 600</code>）</li>
<li><strong>Git 簽署用的 GPG key</strong>：如果有用 commit signing</li>
<li><strong>密碼管理器登入</strong>：如果 secret 管理用了層級二或三</li>
</ul>
<h3 id="階段五硬體相關調整">階段五：硬體相關調整</h3>
<p>Hyprland 的 monitor 設定（解析度、縮放、排列位置）跟實際接的螢幕有關，這部分配置每台機器都不同：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># ~/.config/hypr/hyprland.conf 的 monitor 段</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 這幾行在每台機器上都要調</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="na">monitor</span><span class="o">=</span><span class="s">DP-1, 2560x1440@144, 0x0, 1</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="na">monitor</span><span class="o">=</span><span class="s">HDMI-A-1, 1920x1080@60, 2560x0, 1</span></span></span></code></pre></div><p>處理方式有兩種：把 monitor 設定拆成獨立的 <code>monitor.conf</code>，主配置用 <code>source</code> 引入，<code>monitor.conf</code> 不進 repo（加進 .gitignore）、每台機器本地維護；或者用 chezmoi template 按 hostname 判斷。</p>
<p>顯卡驅動（Intel/AMD 通常自動、NVIDIA 需要額外安裝 <code>nvidia-dkms</code> 和設定環境變數）也是硬體相關的步驟，可以放在 bootstrap script 的 OS 判斷裡，但通常 Arch 安裝階段就已經處理。</p>
<h3 id="階段六驗證">階段六：驗證</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 登出 TTY，重新用 Hyprland 登入</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 或者直接在 TTY 執行</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">Hyprland</span></span></code></pre></div><p>登入後確認：視窗管理器正常運作、keybind 正確、狀態列出現、字型正確渲染、終端機配色正常。如果某個元件沒反應，通常是套件沒裝或配置路徑不對——回去檢查 bootstrap 的套件清單和 stow 的 symlink。</p>
<h3 id="時間預估">時間預估</h3>
<p>整個流程在網路順暢的情況下，大約 30 分鐘到 1 小時，取決於套件數量和下載速度。主要時間花在套件安裝（pacman 下載 + 編譯 AUR 套件）。配置 deploy 本身是秒級操作（stow 只建 symlink）。</p>
<p>對比沒有 dotfile 管理時的重建：邊想邊裝、裝了忘記某個工具的名稱、配置靠記憶手打、兩天後還在調某個快捷鍵為什麼不對。差距在「可預期 vs 碰運氣」。</p>
<h2 id="維護節奏">維護節奏</h2>
<p>環境重建能力需要持續維護，不是設定完就一勞永逸。</p>
<p>日常習慣：新裝一個工具時，順手更新套件清單（<code>brew bundle dump</code> 或手動加一行到 <code>packages.txt</code>）。改了一個配置後，commit + push。這個習慣的建立成本低，但需要刻意練幾週才會變成反射動作。</p>
<p>定期檢查：每隔幾個月在 VM 或 container 裡跑一次完整的 bootstrap，驗證 script 還能從零跑通。配置會演進、套件會改名或被取代、script 裡硬寫的路徑可能失效——定期驗證才能確保「這份重建指令真的能重建」，而不是一份過期的紀錄。</p>
]]></content:encoded></item></channel></rss>