จากบทความที่แล้ว My Agent Harness: The System Around the Model ผมเล่าเรื่อง Rule, Skill, Hook และ MCP ที่ห่อ AI Model อยู่ ซึ่ง Setup ค่อนข้างเยอะ ปกติอยู่บ้านผมทำงานบน WSL แต่บางครั้งต้องออกไปทำงานนอกบ้านด้วย MacBook ปัญหาที่เจอคือ AI ทำงานไม่ได้ดั่งใจ เพราะ Harness ติดตั้งไว้ไม่ครบ บางตัวมี บางตัวไม่มี นอกจากนี้ผมยังมี PC อีกเครื่องที่ลง Hermes ไว้ใช้งาน ก็เจอปัญหาเดียวกัน
Harness ทั้งชุดอยู่ใน Home Directory
ของใน Harness แทบไม่มีตัวไหนอยู่ใน Repo ของ Project เลย มันอยู่ใน ~/.cursor กับ ~/.agents จริง ๆ จะใส่ไว้ใน Project ก็ได้ แต่ต้องมานั่งใส่ทุก Repo ก็ไม่ไหว
เรื่องติดตั้ง Application ผมเคยเล่าวิธีแก้ไปแล้ว Mac ใช้ Homebrew, Cask และ mas-cli ฝั่ง Windows ใช้ Winget
แต่ Package Manager ไม่ได้เอา Config กลับมาให้ด้วย Agent บนเครื่องใหม่จึงเริ่มจากศูนย์ ไม่รู้ Rule ไม่มี Skill ไม่มี Hook แถมยังขาด .zshrc, .gitconfig, .vimrc และอื่น ๆ
แต่ละตัวเก็บสิ่งที่เคยตั้งไว้คนละเรื่อง ยกตัวอย่างเช่น
.zshrc-PATH, Homebrew, pnpm และโหลด Alias ของ Shell.eji4h_bash_aliases/- Custom Alias ของผมดั่งเดิม แยกตาม Tool เช่น Git, kubectl, gcloud, Terragrunt.gitconfig- Identity ของ Git แยก.gitconfig-personalกับ.gitconfig-workตาม Folder งาน ตามที่เล่าไว้ใน Manage multiple Git account in machine.vimrc- Config ของ Vim.tmux.conf- Config ของ tmux.cursor/และ.agents/- Rule, Skill และ Hook ของ AI Agent
บางไฟล์ติดมากับการ Setup บางไฟล์ผมเขียนเองและแก้เพิ่มอยู่เรื่อย ๆ ของแบบนี้ควรอยู่ใน Version Control เหมือน Source Code
Dotfiles คืออะไร
Dotfiles คือไฟล์ Config ที่ชื่อขึ้นต้นด้วยจุด อย่าง .zshrc หรือ .gitconfig ซึ่งอยู่ใน Home Directory ($HOME) จุดข้างหน้าเป็นธรรมเนียมของ Unix ว่าไฟล์นี้เป็นของโปรแกรม ไม่ใช่งานของเรา ls กับ File Manager เลยซ่อนไว้เป็นค่าเริ่มต้น
$ ls ~
Downloads Library works
$ ls -a ~ # a = all รวมไฟล์ที่ซ่อนอยู่
.agents .config .cursor .gitconfig .p10k.zsh .ssh .zshrc Downloads Library works
ทำไม git init ที่ $HOME ไม่พอ
ทางแรกที่ผมนึกออกคือ git init ที่ $HOME แล้ว git add เฉพาะไฟล์ที่ต้องการ ทางนี้ติด 2 เรื่อง
ไฟล์กระจาย — $HOME มีทั้งโฟลเดอร์งาน, Downloads และ Cache ของโปรแกรม git status จะขึ้น Untracked File เป็นพัน และ git add . พลาดครั้งเดียว ของที่ไม่ควรเข้าก็เข้า Repo
ต่างเครื่องต่าง Config — macOS เก็บ Home ที่ /Users/${USERNAME} ส่วน WSL อยู่ที่ /home/${USERNAME}
เทียบเครื่องมือ
Credit: chezmoi Comparison Table (Capture เมื่อ 2 พฤศจิกายน 2026)
รูปด้านบนอยู่ใน Website ของ chezmoi แต่จริง ๆ มันก็ยังมีตัวเลือกอื่นอีก ในรูปจะมี Link https://dotfiles.github.io/utilities/ ที่ดัง ๆ เลยก็จะมี GNU Stow กับ Nix home-manager และสำหรับคนทำ Infra อาจจะนึกถึง Ansible อีกตัวด้วย
ไล่ตัดทีละตัว
โจทย์ของผมคือ 3 เครื่อง 2 ตระกูล OS — WSL สองเครื่อง (eji4h-pc, eji4h-server) กับ MacBook Apple Silicon (eji4h-macbook)

Bare Git เบาที่สุด มี Git ก็พอ
git init --bare $HOME/.dotfiles
alias dot='git --git-dir=$HOME/.dotfiles --work-tree=$HOME'
dot config status.showUntrackedFiles no
แต่ไม่มีกลไกในตัวสำหรับไฟล์ที่ต่างกันต่อเครื่อง ต้องแยก Branch หรือแยกไฟล์เอง แล้วรวมส่วนที่ใช้ร่วมกันด้วยมือ
GNU Stow มี Mental Model ง่ายที่สุด แยกโฟลเดอร์ละ Package แล้ว Stow สร้าง Symlink กลับไปที่ $HOME ให้ แต่ก็ไม่มี Template เหมือน Bare Git
dotbot คือ Stow ที่มี Install Script ในตัว YAML ไฟล์เดียวบอกทั้ง Link และคำสั่งที่ต้องรัน ใกล้สิ่งที่ผมต้องการมาก แต่ก็ไม่มี Template ต่างเครื่องยังต้องแยกไฟล์เอง
Ansible และ Nix home-manager ผ่านเกณฑ์เกือบหมด แต่มาพร้อม Ecosystem ทั้งชุด Ansible คือ Playbook กับ Python ที่ออกแบบมาดูแล Server เป็นฝูง ส่วน Nix คือทั้งภาษาและ Package Manager ใหม่ทั้งชุด ผมแค่อยาก Sync Config ไม่ได้อยากทำให้มันซับซ้อนมาก
| เกณฑ์ | Bare Git | GNU Stow | dotbot | chezmoi | Ansible | Nix home-manager |
|---|---|---|---|---|---|---|
| ต้องมีก่อน Bootstrap | git | Perl | Python¹ | ไม่มี² | Python | Nix³ |
| Dotfiles เป็น | File | Symlink | Symlink | File | แล้วแต่ Module | Symlink ไป Nix Store |
| ดู Diff ก่อน Apply | ✅ git diff | ⚠️ stow -n⁴ | ⚠️ --dry-run⁴ | ✅ chezmoi diff | ✅ --check --diff | ⚠️ ไม่มี Diff ของ $HOME⁴ |
| ดึง Secret จาก Password Manager | ❌ | ❌ | ❌ | ✅ | ⚠️ Lookup Plugin | ❌⁵ |
| ต่างเครื่องต่าง Config | ⚠️ ไม่มีในตัว⁶ | แยก Package | แยกไฟล์ | Template | Template + Host Vars | Nix Module |
| Run Scripts | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ |
¹ Runtime ของ dotbot คือ Python. git ไม่ใช่ Dependency ของตัวโปรแกรม แต่ Script ./install ค่าเริ่มต้นเรียก git submodule update ถ้า Repo ใช้ Submodule และต้องมี git อยู่แล้วตอน Clone.
² chezmoi เป็น Binary ไฟล์เดียว ไม่ต้องมี Runtime แต่ข้ามเครื่องยังต้องมี git เพื่อ Clone Repo ไม่ได้ใช้ chezmoi init
³ Nix คือ Platform ทั้งชุด ไม่มีชื่อ Runtime สั้นกว่านั้นแบบ Perl หรือ Python
⁴ ⚠️ ในแถวนี้แปลว่าไม่ได้เทียบเนื้อไฟล์ใน $HOME ก่อน Apply. ตารางของ chezmoi ให้ dotbot เป็น ❌ ผมให้ ⚠️ เพราะ --dry-run บอกได้ว่าจะสร้าง Link อะไรบ้าง แต่ไม่ได้เทียบเนื้อไฟล์. stow -n ก็เป็นแบบเดียวกัน. Nix home-manager ไม่มีคำสั่ง Diff ของ $HOME. home-manager build แค่ Build Activation Package ให้เปิดดู.
⁵ home-manager ดึง Secret จาก Password Manager ไม่ได้. sops-nix ถอดรหัสไฟล์ตอน Activation แต่ไม่ได้ดึงค่าจาก Password Manager. พื้นฐานของ SOPS อยู่ใน SOPS: Secrets OPerationS
⁶ ตารางของ chezmoi ให้ Bare Git เป็น ⁉️ คือทำได้แต่ต้องลงแรงเอง ไม่มี Template ในตัว ทางที่ใช้บ่อยคือแยก Branch หรือแยกไฟล์ แล้วรวมส่วนที่ใช้ร่วมกันด้วยมือ
chezmoi
chezmoi เป็น Binary ตัวเดียว เก็บไฟล์ต้นทางไว้ใน Repo แล้วเขียนเป็นไฟล์จริงลง $HOME โดยค่าเริ่มต้นไม่ได้ทำ Symlink กลับไปที่ Repo ไฟล์ไหนต้องต่างกันต่อเครื่องก็ต่อท้ายชื่อด้วย .tmpl แล้วเขียน Go Template ในไฟล์นั้น
ตัวอย่างจาก dot_zshrc.tmpl ของผม (ตัดบรรทัดอื่นในแต่ละเงื่อนไขออก)
{{- if eq .chezmoi.os "darwin" }}
eval "$(/opt/homebrew/bin/brew shellenv)"
{{- else }}
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
{{- end }}
.zshrc ไฟล์เดียว Mac ได้ Path ของ Homebrew บน Apple Silicon ส่วน WSL ได้ Path ของ Linuxbrew ไม่ต้องแตก Branch
เครื่องที่ Setup แล้ว
ใช้ chezmoi update ซึ่งจะ Pull แล้ว Apply ให้ ถ้าอยากดูก่อนว่าจะเปลี่ยนอะไร ค่อยรัน chezmoi diff
Secret
chezmoi ดึง Secret ตอน Apply ผ่าน Template Function ของ Password Manager ค่าจริงไม่ได้อยู่ใน Repo ตัวอย่างจาก Docs คือ .zshrc.tmpl ที่ดึง Cloudflare API Token จาก 1Password
# set up $PATH
# …
# Cloudflare API Token retrieved from 1Password for use with flarectl
export CF_API_TOKEN='{{ onepasswordRead "op://Personal/cloudflare-api-token/password" }}'
# set up aliases and useful functions
Credit: Password Manager Integration
Package
Script ที่ชื่อขึ้นต้นด้วย run_ จะถูกรันตอน chezmoi apply เรียงตามชื่อไฟล์ run_onchange_ รันใหม่เฉพาะตอนเนื้อ Script เปลี่ยน ถ้าใส่ไว้ใน .chezmoiscripts/ chezmoi จะไม่สร้างโฟลเดอร์นั้นใน $HOME
ตัวอย่างจาก Docs คือ run_onchange_install-packages.sh.tmpl ติดตั้ง ripgrep คนละคำสั่งตาม OS
{{ if eq .chezmoi.os "linux" -}}
#!/bin/sh
sudo apt install ripgrep
{{ else if eq .chezmoi.os "darwin" -}}
#!/bin/sh
brew install ripgrep
{{ end -}}
Credit: Use scripts to perform actions
สิ่งนี้คือช่วยให้ผมสามารถ Install Package จบใน Repo นี้ได้เลย รายการ Package อยู่กับ Config ใน Repo เดียวกัน เครื่องใหม่รัน
chezmoi applyครั้งเดียว ได้ทั้งไฟล์ Config และคำสั่งติดตั้ง แก้ Script เพื่อเพิ่ม Package แล้วrun_onchange_จะรันใหม่ให้ ตัวที่ติดตั้งจริงยังเป็น Package Manager อย่างaptหรือbrewchezmoi เป็นตัวเรียกคำสั่งนั้น Script เดียวกันเรียกฝั่ง Windows ได้ด้วย เช่น สั่งwingetผ่านpowershell.exeจาก WSL รายการของ Linux, macOS และ Windows เลยจบใน Repo นี้ที่เดียว
สิ่งที่ต้องแลก
คือชื่อไฟล์ ใน Repo .zshrc ชื่อ dot_zshrc.tmpl และไฟล์ที่ต้อง Execute ได้ขึ้นต้นด้วย executable_ ช่วงแรกต้องแปลชื่อในหัวทุกครั้งที่เปิด Repo
Harness
.cursor/ กับ .agents/ คือ Harness ที่เล่าในบทความที่แล้ว มันอยู่ใน Home Directory เหมือน Config อื่น เครื่องใหม่ต้องได้ของชุดนี้ด้วย
Skill, Rule, Hook และ MCP ที่มีอยู่ใน Marketplace ไม่ได้ก็อปทั้งก้อนเข้า ผมใช้ Repo Script ของ chezmoi เป็นตัวติดตั้งให้ตอน Apply และสำหรับของที่ผมเขียนเอง รวมทั้ง Hook กับ MCP ของผม แยกเก็บใน Dotfiles Repo กับ Config อื่น ๆ
เลือกตามโจทย์
- มีเครื่องเดียว Bare Git ก็พอ
- ชอบ Symlink และใช้ OS เดียว Stow อ่านง่ายกว่า
- ดูแลเครื่องเป็นฝูงอยู่แล้ว Ansible คุ้มกว่า
- อยากให้ทั้ง Environment Reproducible ทุกเครื่อง Nix ตอบโจทย์ตรงกว่า
ของผมคือหลายเครื่อง หลาย OS และอยาก Sync แค่ Config เลยจบที่ chezmoi
เจอกันใหม่บทความหน้าครับ