使用 Git 管理 Linux 用户配置的新思路

2020年03月09日

在本系列的 上一篇文章 当中,我们已经把家目录的结构打理好了。接下来可以考虑使用强大的工具备份和同步配置。

本文介绍一种使用 git 的 bare 仓库 (裸仓库) 管理家目录的全新思路,以及我个人在该思路上延伸的方案。

GNU stow 管理用户配置文件的方案流行了很久,但我不喜欢它。stow 的理念就是在自定义的目录结构里集中化管理配置。

我所期待的方案要满足几个需求:

  • 对家目录的破坏最小

    stow 使用了软链接,这正是我所不希望看到的。

  • 轻松上手

    对我来说最熟悉的方式,最好无需引入新工具。

  • 工具没有过多的依赖

    关于这点上 stow 做得挺好。

stow?忘记它吧

因为 stow 的主要作用是聚合,通过在配置的原始路径上创建软链接,指向一个集中化的目录当中。 所以通常 stow 会配合 git 来使用,stow 用于从庞大的家目录中把配置隔离出去,git 则用于备份归档。

无论如何怎么看,我都更希望于选择用 git 直接管理所有在家目录下的用户配置,而不是先用 stow 聚合组织文件。 在上篇文章中已经介绍了有效组织家目录的方法,我不希望再借助额外的工具移动家目录下的文件。

git 是相当流行的工具,依赖较少。用 git 来管理 Linux 的配置文件是很稀松平常的想法。 但是,就此打住真的可以了吗?有这么简单的话,这篇文章就没存在的必要了。因为真的尝试一番 git 管理家目录, 很快便会捉襟见肘。

正确思路

使用 git 是最容易想到的方式,但是缺陷很明显,因为这样一来要在家目录下建立 git 仓库目录, 家目录下的任何子目录都将被纳入 git 管理。无论我们切换到任何子目录,git 都忠实地如影随形。 git 疯了,我也疯了。如果 zsh 和 vim 等软件集成了 git 插件之类的话,情况会更糟。 想一想 zsh 在家目录下每次执行完命令,就要调用 git 插件生成新的提示符……性能堪忧。

好消息是,从 Hacker News 得到的好消息是, 我们完全可以通过创建 git bare 仓库,然后分别指定 git 仓库目录和工作目录为不同的路径!

正如 StreakyCobra 所说的那样,可以使用以下方式:

1
2
git init --bare $HOME/.myconf
alias config='/usr/bin/git --git-dir=$HOME/.myconf/ --work-tree=$HOME'

如此一来,.myconf就是用来存放 git 的元数据和对象数据的目录了。.myconf$HOME做到了貌合神离。 git 根本意识不到.myconf$HOME的 git 仓库目录,尽管看上去.myconf确实位于$HOME下。(当然你也可以 让.myconf处于任何位置!)只有在我们使用config命令的时候,家目录才会被视为 git 工作目录, 接下来的操作就和正常使用 git 一样普通。

Arch Wiki 上也总结了这套方法,以及作者们的总体方案, 值得我们借鉴。

选项--git-dir--work-tree也可以用变量GIT_DIRGIT_WORK_TREE替代。使用变量的好处是 vim 这类集成 git 插件的软件也可以正常识别 git 目录了。

为了让 vim 的 git 插件可以正常使用,指定变量后启动 vim 就行了:

1
GIT_DIR=$HOME/.myconf GIT_WORK_TREE=$HOME vim

另一种思路(不推荐)

不生成 bare 仓库也是可以的,我们可以在创建普通仓库后,紧随着在 git 配置里面修改工作目录路径。

1
2
3
git init .myconf/
cd .myconf/
git config core.worktree ~

但是,一旦使用git status会发现文件路径都是相对路径,这对使用体验造成了负面影响。况且接下来我们 要用到 bare 仓库的一些特性,比如说,允许下游推送。所以,使用普通仓库还是很勉强的,不推荐使用。

消除干扰(可选步骤)

虽然 StreakyCobra 提到了可以用 config config status.showUntrackedFiles no 设置默认不显示未追踪的文件,但我喜欢用config status清楚地看到哪些文件是新添加的配置文件。 所以我并没有让未追踪的文件从状态信息中消失。

为了排除掉所有我没添加到 git 仓库,但实际上我并不关心的文件, 可以在~/.myconf/info/exclude文件里添加它们。顶级目录里,所有的除 dot 文件(点文件)以外的文件都是可以排除的, 而 dot 文件则是重点关注对象,所以要用到/*!/.*(斜杠/不能去掉,因为子目录里要追踪普通文件)。 ~/.myconf本身也要排除。剩下的无关的目录都统统排除:~/.cache~/.local……但是除了~/.config。 在 上一篇文章 里,杂乱的文件已经被管理好了,但还是剩下一些顽固分子, 只好也排除掉它们吧。

~/.myconf/info/exclude看上去大概是这样子的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/*
!/.*
/.myconf/

# XDG
/.cache/
/.data/
/.local/

# dbus
/.dbus/
# nss
/.pki/
# ...

因为我选择不排除掉~/.config,但是其下的目录也非常多,很多都是不需要追踪的,怎么办呢? 用最笨的办法,一个个去排除掉! 这是项耗时耗力的任务,需要熟悉~/.config目录下的每一个子目录是干什么的。但好处也是显而易见的: 因为想要找出所有需要管理的配置文件,就必须清楚地知道它们是什么,从而做到完美地整理配置。这 就是我们苦苦追寻的目标呀!

实际上也花不了多少时间。就是只有少部分含混不清的目录,最好在 exclude 文件里注释一下。 完成了之后任何新增软件的配置都无法逃脱 git 的管理, 使用git status查看那些在主机上新安家的软件们都创建了什么文件和目录。真可谓一劳永逸。 额外要提醒下的是,只要目录为空,git 就会无视,即使是多级子目录。

filter 处理敏感文件

如果考虑公开分享自己的配置,需要考虑过滤一些敏感信息。 ID,邮箱和 gpg 公钥对我来说是可以公开的,需要额外注意有没有把密码等私密信息包含进去。 Arch Wiki 上提到了通过 git 的filter功能 可以用来替换密码等敏感信息。

~/.myconf/config

1
2
[filter "remove-secrets"]
clean = "sed -e 's/name = .*/name = NAME/'  -e 's/password = .*/password = PASSWORD/'"

~/.myconf/info/attributes

* filter=remove-secrets

当然这只是个简单的尝试,不保证安全和可靠性,最好是针对不同文件分别设置 filter。

而且,千万不要一次性把整个.config目录提交了!最好分批次确认文件内容后再提交。

多分支管理多平台的配置

原文提到了可以用分支管理不同平台主机下的配置。在~/.myconf上新建分支上想法固然好,但却美中不足。 关键在于其他平台使用的配置会和当前操作的主机的配置不一致。直接使用~/.myconf仓库管理其它 平台的配置需要改变 HEAD 指针,同时修改过程中导致工作区被污染,进而影响了当前正在使用的主机的配置。

当然可以用 stow 等工具隔离不同主机的配置,但这样又回到老路子上了。

我实践的结果是采用本地克隆仓库的方案,并且另外指定一个工作区。

1
git clone --bare .myconf <newdir>

这个新仓库代表着另一个平台的配置,所以我们可以在其中任意增删和修改文件,同时不影响家目录下的配置文件。

同时该仓库也是 bare 仓库,因为我需要它自身被克隆后,也能接受来自其它平台的推送。所以使用上也需要指定 git-dirwork-tree。为此可以单独用额外的目录来管理,比如我为了管理 Android termux 的配置,我安排了 ~/Config(不同于~/.config),分别指定~/Config/termux.home/~/Config/termux.git,另外在 ~/Config/termux.sh里为 termux 指定命令:

1
alias config.termux='/usr/bin/git --git-dir=$HOME/Config/termux.git --work-tree=$HOME/Config/termux.home'

需要的时候zsh source就能使用config.termux管理我的 termux 配置了。

在新创建的本地克隆仓库中,存储的 git 数据对象使用了硬链接方式,意味着和~/.myconf共享相同文件,因此 git 目录不会占用很多磁盘空间;真正增加的文件在签出的工作目录中。所以不用过多担心占用了额外的存储空间。

使用 git clone 同步配置

克隆下来的仓库也采用 bare 仓库的方式(克隆时使用--bare选项),所以同样能分别指定工作目录和 git 仓库目录。

以 termux 为例,从本地局域网同步配置。需要上一节内容中提到的 bare 仓库。我已经在用$HOME/Config/termux.git管理了, 所以我需要在本地主机上开启 ssh 服务,然后在 termux 上执行:

1
git clone --bare --single-branch --branch termux user@host:[path/to/git] ~/.myconf

就能在 termux 上恢复配置了

1
git --git-dir=$HOME/.myconf/ --work-tree=$HOME checkout

配置好了之后 termux 也能使用命令config

只要上游仓库是 bare 仓库(上节也提到了),双向同步是可以的。

多个远程备份

既然是 git 仓库,自然也可以推送到 GitHub 等等地方去。目前我同时推送到 GitHub 和我自行搭建的 Gogs 服务上面。

最后

这不一定是当前最好的方案,毕竟人的需求永远是要进步的,同时还要考虑到最适合自己的。 至于我的目标是追求最清晰最简单的方案,所以我很喜欢 git 的方式。

欢迎前来围观我的 配置文件

CC BY-NC-SA 4.0

© 2020-2024 rydesun