Skip to content
Go back
Table of Contents

點文件管理二三事

前段時間為了把包括 Niri 在內的所有配置文件統一管理, 設計了一個 Dotfile 管理工具. 這個腳本作為框架存在, 是一個命令執行器, 本身不具備安裝命令的功能. 這個腳本在設計之初還有跨設備和跨系統 (包括 Linux 和 MacOS)需求. 因此動用了不少手段來實現能夠同時管理不同版本的 dotfiles , 並復用共有的部分.

我曾經在 neovim 的配置過程中嘗試過每個設備一個分支進行管理, 但是效果並不好. 這次並沒有選擇git多分支管理, 因為多分支需要定期通過 patch 或者定期 rebase 或 merge 來同步, 共有復用代碼處理的並不好.

Bootstrap 脚本

./bootstrap 負責驅動 dotfiles 的检测, 安裝和卸載功能.

./bootstrap init --item=test 會在 items 目錄下初始化 test 的項目, 初始化結構如下.

.
├── bootstrap
├── bootstrap.conf
├── items
   └── test
       ├── item.conf
       └── src
├── README.md
└── utils
    └── replace.awk

Item

這個腳本管理的對象是item, 除了普通的配置文件, 還包括字體, 應用程序啟動補丁等. 我現在的一些配置如下

$ ./bootstrap -ls
[+] alacritty
[+] anyrun
[+] bash
[+] btop
[+] discord
[+] fastfetch
[+] fcitx5-theme
[+] fish
[+] fonts
[+] ghostty
[ ] gimp-patch
[+] gitignore
[+] gnome-setting
[+] gtk-theme
[ ] hyprland
[+] hyprlock
[+] imv
[ ] karabiner
[+] kitty
[+] linuxqq
[+] lsd
[+] matrix
[+] mpv
[+] neovide
[+] niri
[+] nvim
[ ] nvim.vscode
[ ] quickshell
[ ] rofi
[+] starship
[+] swaync
[+] tmux
[+] waybar
[+] wezterm
[+] wlogout
[+] xmake
[+] yazi
[+] yt-dlp
[+] zathura
[+] zsh

每個 item 都有自己的 item.conf, 用來管理這個項目的安裝和卸載過程.

#!/usr/bin/env bash

# Check the dependency return false if installed dependency
function check_dep() {
    return 0
}

# Things to do to install the dependency
function install_dep() {
    :;
}

# Things to do to install the item
function install() {
    :;
}

# Check if the item is installed, return false if installed
function check() {
    return 0
}

# Things to do to uninstall the item
function uninstall() {
    :;
}

這個空的模板定義了相關的函數。這裏的依賴可以是配置文件對應的程序,例如 nvim 的依賴是 nvim,在 install_dep 函數中使用 系统包管理器安裝或拉取源碼編譯。

注意,在編寫安裝步驟的時候實際產生影響的安裝命令都需要在前面使用run來包裝。這保證了在dry-run模式下命令不會被執行, 並能被監控。有重新導向或者管道的命令需要使用引號包裹。這是實現上的缺陷導致的。我沒有找到完美的命令執行器(也許需要一個含有 控制流的類似makefile的dsl出現才行吧(笑

相关变量

  • $rootdir

代表整个 dotfiles 仓库的根目录绝对路径, 在编写涉及链接或者移动文件时, 最好使用$rootdir为头生成绝对目录.

  • $sys_id

你可以给自己的安装对象起名字. 例如有两台安装 linux 系统的计算机需要安装不同的配置, 就可以起不同的sys_is 加以区分.

bootstrap.conf

同在项目根目录下的bootstrap.conf负责配置脚本相关的路径和基本信息, 这个配置文件是第一次运行脚本自动生成的, 其中rootdir是 由你的 dotfiles 项目根目录位置决定的, 不应该修改. 你需要修改 sys_id 字段, 并给当前系统起名字(这一步是可选的)

presets.conf

presets.conf 主要定義預設組合,很多配置項目經常是組合安裝的,比如裝機的時候需要恢復桌面環境和常用軟體的配置。也可以在 其中配置系統套件管理器的選項。

#!/usr/bin/env bash
presets["linux_basic"]="alacritty bash zsh fastfetch tmux yazi nvim nvim.vscode"
presets["macmini"]="bash btop fastfetch ghostty karabiner kitty lsd nvim nvim.vscode tmux wezterm yazi zsh"
presets["hypr"]="hyprland kitty rofi waybar"
presets["niri"]="alacritty anyrun bash btop fish fastfetch fcitx5-theme gnome-setting \
gtk-theme git imv hyprlock lsd mpv nvim niri waybar swaync tmux wlogout yazi zathura zsh"

paru_flags="--noconfirm --skipreview"

pacman_opt="--noconfirm"

brew_flags=""

function paru() {
    command paru $paru_flags "$@"
}

function pacman() {
    command pacman $pacman_opt "$@"
}

function brew() {
    command brew $brew_flags "$@"
}

工具脚本

utils/replace.awk是用來輔助產生配置文件的文字處理腳本。有些配置文件只有在特定的地方才有區別,可以使用模板來進行替換。

awk -f replace.awk <variables file> <template file>

在 MacOS 下需要使用gawk

變量被按字段儲存:

BEGIN {
    shell = "/bin/zsh"
    opacity = 0.95
    font_family_normal       = "ComicCode Nerd Font Medium"
    font_family_bold         = "ComicCode Nerd Font Medium"
    font_family_italic       = "ComicCode Nerd Font Medium"
    font_family_bold_italic  = "ComicCode Nerd Font Medium"
    font_size = 9.5
}

模板文件中, 變量被${{name}}标记, 並被替換成前面設置的值

# WARN: THIS IS A FILE GENERATED BY A TEMPLATE, DONNOT EDIT IT 

[env]
TERM = "xterm-256color"
[terminal.shell]
program = "${{shell}}"
[window]
opacity = ${{opacity}}
padding.x = 2
padding.y = 2
decorations = "Full"
decorations_theme_variant = "Dark" # "Dark"
dimensions.columns = 200
dimensions.lines = 50
dynamic_title = false
[font]
normal.family       = "${{font_family_normal}}"
bold.family         = "${{font_family_bold}}"
italic.family       = "${{font_family_italic}}"
bold_italic.family  = "${{font_family_bold_italic}}"
size = ${{font_size}}
[general]
import = [
    "~/.config/alacritty/color-scheme/catppuccin-mocha.toml"
]
[terminal]
osc52 = 'CopyPaste'

具體使用方法都可以在 我的 dotfiles 倉庫看到.

Misc && FAQ

  • 這個腳本安裝的配置會不會覆蓋原本的配置文件?

這個腳本只負責執行安裝的一系列命令, 你應該在check函數裡檢查install函數要安裝的位置,如果check函數傳回非0,會被視為配置已經安裝。再 install 的時候會詢問是否覆蓋。

  • 對比常見的dotfiles管理方案?

    • GNU Stow

    這直接連結到對應位置,我不是很喜歡在 git 倉庫下還出現 .config 這樣的點檔案。而且 GNU Stow 不會在安裝某個軟體配置的時候 附帶安裝它,你可能需要一個 list 來記錄有哪些軟體套件被安裝。而且它只是簡單的連結,沒有辦法做到同時維護多個系統的配置。到 時候又得開新的分支。

    • 裸儲存庫

    我對裸儲存庫的印象主要來自這裡。把整個 $HOME 做 git 的工作樹還是 風險比較大,我覺得這不適合新手,也不適合有跨系統、跨設備移植需求。

    • chezmoi

    之前被朋友推薦過,這裡有一篇文章介紹如何使用。雖然能進行跨平台的管理,但是 似乎不是很方便。*chezmoi自己的配置文件不能被管理*

    我寫的這個腳本完全剝離了具體的安裝管理 dotfiles 的步驟,只是提供了工具。可以自由的配置在什麼系統下執行什麼命令,安裝什麼 軟體的配置,可以完全適應跨系統、跨設備、快速重裝和部署 dotfiles 的任務,並配合 git 方便的進行管理。