Зачем управлять dotfiles
С тех пор как я начал использовать Git для управления моими dotfiles, прошло уже несколько лет. Но необходимость этого я осознал не сразу. Осознание пришло уже после моего первого опыта использования Linux. Первым дистрибутивом Linux, которым я пользовался достаточно долгое время, был Pop!_OS. Я использовал его, пока изучал программирование. Я выполнил несколько личных проектов и уже создал некоторое подобие окружения разработчика с установленными pyenv, neovim, VS Code. Соответственно, у меня в системе уже было определённое количество dotfiles, которые я редактировал для улучшения моего процесса работы в ОС.
Но при очередном обновлении система не смогла обновиться до конца. Её стало невозможно запустить. Вместе с ней были потеряны и мои dotfiles. Не то чтобы я следил за ними в тот момент, но я расстроился, когда не смог их восстановить.
С тех пор я стал управлять моими dotfiles при помощи системы контроля версий. И я сохраняю мои dotfiles в удалённом репозитории на GitHub.
Какие инструменты я использовал
На протяжении нескольких лет я использовал несколько разных способов и инструментов для управления dotfiles. Почему несколько? Потому что я стремлюсь к упрощению и автоматизации процессов внесения изменений в dotfiles и установки dotfiles и зависимостей в операционной системе.
Итак, какие этапы я прошёл.
Управление dotfiles при помощи git bare
Этот метод заключается в сохранении «голого» репозитория Git bare repository в отдельной папке (например, $HOME/.cfg или $HOME/.myconfig) и использовании специально подготовленного псевдонима (alias), чтобы команды выполнялись именно для этого репозитория, а не для обычной локальной папки .git, которая может конфликтовать с другими Git-репозиториями в системе.
git init --bare $HOME/.cfg
alias config='/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME'
config config --local status.showUntrackedFiles no
echo "alias config='/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME'" >> $HOME/.bashrc
- Первая строка создаёт папку
~/.cfg, которая будет являться «голым» Git-репозиторием для отслеживания ваших файлов. - Затем создаётся псевдоним
config, который будет использоваться вместо обычной командыgitпри работе с конфигурационным репозиторием. - Устанавливается локальный для этого репозитория флаг, скрывающий файлы, которые ещё не добавлены в отслеживание. Это позволяет команде
config statusи другим не отображать файлы, которые вы не хотите отслеживать, как «untracked» (неотслеживаемые). - Определение псевдонима можно добавить вручную в ваш
.bashrcлибо воспользоваться четвёртой строкой выше для удобства.
Преимущества этого подхода:
- Отсутствие зависимостей от дополнительных программ и утилит.
- Явное добавление файлов в репозиторий.
Недостатки этого подхода:
- Относительная сложность начальной настройки.
- Ручное разрешение конфликтов.
- Риск перезаписи существующих данных.
- Необходимость лишних шагов по добавлению файлов и папок в репозиторий.
- Дополнительные файлы и директории (
.cfg,.gitignore) в домашней директории. - Отсутствие шаблонов для работы в разных окружениях (рабочая машина, домашний компьютер).
Управление dotfiles при помощи Stow и symlinks
Такой подход уже использует дополнительную утилиту GNU Stow. Эта программа позволяет сохранить все dotfiles в одной директории, а затем создать символические ссылки на них в домашней директории.
- Создаём Git-репозиторий (обычно называемый, например,
dotfiles), внутри которого создаём подкаталоги по «пакетам» — например,.config/vim/,.config/zsh/,.config/tmux/. - Внутри каждого подкаталога размещаем файлы так, как они должны появиться в
$HOME.
Например, файлzsh/.zshrcв репозитории будет ссылаться на~/.zshrc. - Из корня репозитория выполняем:
stow .
Это создаст символические ссылки в $HOME, указывающие на соответствующие файлы в нашем репозитории.
Преимущества подхода с Stow:
- Оригинальные файлы хранятся только в репозитории; в
$HOME— только ссылки. - Простота переноса. Клонируем репозиторий и выполняем
stow— конфигурация готова. - Весь репозиторий — обычный Git, без специальных флагов или алиасов. Именно поэтому я перешёл на этот способ вместо git bare.
- Изменения в символических ссылках отражаются в оригинальных dotfiles.
Недостатки подхода с Stow:
- Stow не входит в базовую поставку большинства систем (хотя легко устанавливается через пакетные менеджеры).
- Не поддерживает шаблонизацию или подстановку переменных.
- Если файл уже существует в
$HOME, Stow откажется создавать ссылку (нужно вручную удалять или перемещать). - Изменение структуры пакетов требует ручного обновления ссылок (
stow -Rпомогает, но не всегда достаточно). - Некоторые программы плохо ведут себя с символическими ссылками.
- При создании символической ссылки на директорию в неё будут попадать автоматически создаваемые и временные файлы. Нужно использовать
.gitignore. - Риск попадания конфиденциальных данных в репозиторий.
Управление dotfiles при помощи Nix и Home Manager
Об этом способе у меня есть полноценная статья. Подробности можно посмотреть в ней. Здесь же перечислю только преимущества и недостатки использования.
Преимущества этого подхода:
- Все конфигурации описываются в коде: мы объявляем желаемое состояние, а система приводит систему к нему.
- Нет императивных скриптов вроде «скопируй этот файл» — только описание того, что должно быть.
- Каждая конфигурация — это уникальное поколение (generation). При сбое легко откатиться к предыдущей рабочей версии через
home-manager switch --rollback. - Изменения не «ломают» текущую систему — они применяются в новом профиле.
- Home Manager может не только управлять dotfiles, но и устанавливать пользовательские пакеты (без прав root) через Nix. Например: «установить
neovim,ripgrep,lazygitи настроить~/.config/nvim» — всё в одном месте. Именно это привлекло меня в первую очередь. Nix может заменить стандартные пакетные менеджеры в Linux-дистрибутивах. - Пользовательские пакеты и конфиги не влияют на систему глобально.
- Нет риска случайной перезаписи файлов — Home Manager управляет только тем, что описано.
Недостатки этого подхода:
- Сложность первоначальной настройки. Нужно установить Nix, Nix flakes, Home Manager.
- Требуется понимание языка Nix (функциональный, со своей спецификой), концепций Nix Store, derivations, flakes и т.д.
- Для новичков кривая обучения может быть очень крутой.
- Хотя Home Manager работает и на не-NixOS системах, он всё равно требует установки Nix — что не всегда возможно (ограниченные серверы, корпоративные политики).
- Не все пакеты доступны или актуальны в Nixpkgs. Некоторые пакеты несовместимы с отдельными Linux-дистрибутивами.
- Сложность работы с Nix в macOS.
- Сложность реализации шаблонов для использования разных dotfiles в разных окружениях.
- Сложность работы с конфиденциальными данными и опасность их попадания в репозиторий.
Управление dotfiles при помощи chezmoi и brew
Так как на работе я перешёл с использования WSL и Windows на macOS, мне потребовалась новая система управления dotfiles. Я уже не мог использовать Nix на macOS из соображений корпоративной безопасности и сложности работы с Nix на macOS. Кроме этого, мне нужна была поддержка шаблонов и возможность работы с конфиденциальными данными.
Решением для меня стала использование связки chezmoi и brew. О них и пойдёт речь далее.
Почему chezmoi и brew стали решением для меня
Для того, чтобы эффективно управлять dotfiles, мне нужны были следующие возможности:
- Возможность использования шаблонов и переменных для использования dotfiles как на корпоративных так и на личных ОС.
- Возможность управления конфиденциальными данными. Например возможность добавления токенов и секретных ключей в файлы конфигурации без опасности закоммитить их в git.
- Простой процесс переноса dotfiles на новую машину.
- Возможность быстрой автоматической установки программ и утилит при помощи скрипта или команды.
- Явное указание файлов которые необходимо добавить в git.
Как chezmoi и brew реализуют эти возможности:
- chezmoi имеет шаблоны и возможность использовать условные выражения и переменные (в том числе определенные пользователем) для форматирования dotfiles.
- chezmoi имеет возможность задавать значения кастомных переменных при форматировании dotfiles.
- chezmoi позволяет иницилизировать dotfiles локально напрямую через ссылку на удаленный репозиторий.
- Brew доступно как на MacOS так и на Linux. Программы можно установить путем передачи содержимого текстового файла на стандартный ввод команды
brew install. - dotfiles хранятся в виде шаблонов в изолированной директории и компилируются в финальные файлы в домашней директории.
Из недостатков подобной схемы могу выделить:
- Необходимость установки brew и chezmoi перед тем как установить dotfiles.
- chezmoi не умеет удалять файлы которые были переименованы или удалены. После переименования или удалени файла в репозитории, файл со старым названием нужно удалять вручную из домашней директории.
Теперь посмотрим как это работает.
Как работать с chezmoi и brew
chezmoi
Для начала нужно установить chezmoi и brew.
Если репозиторий chezmoi ещё не создан, можно использовать команду:
chezmoi init
chezmoi создаст директорию по пути ~/.local/share/chezmoi/. Нужно добавить её в git. После этого можно добавить свои dotfiles в chezmoi командой:
chezmoi add ~/.bashrc
Если удалённый git-репозиторий уже существует, можно скачать его командой:
chezmoi init git@github.com:$GITHUB_USERNAME/dotfiles.git
При инициализации, если мы задавали пользовательские переменные, будет предложено заполнить значения этих переменных. Эти значения не будут фигурировать в файлах, сохранённых в git. Они будут применяться к шаблонам в момент применения dotfiles из chezmoi в домашнюю директорию командой:
chezmoi apply
brew
При помощи brew можно установить список приложений, используя текстовый файл:
tap "charmbracelet/tap"
tap "sst/tap"
brew "atuin"
brew "bat"
...
Для установки подойдёт команда:
brew bundle --file $HOMEBREW_BUNDLE_FILE
Она выполняет установку (и, при необходимости, синхронизацию) всех зависимостей, описанных в файле, путь к которому указан в переменной окружения $HOMEBREW_BUNDLE_FILE.
brew bundle— подкоманда Homebrew, предназначенная для управления наборами пакетов, приложений (cask) и даже Mas-приложений (из Mac App Store) через декларативный файл (обычно называемыйBrewfile).--file $HOMEBREW_BUNDLE_FILE— явно указывает, какой именно файл использовать (вместо стандартногоBrewfileв текущей директории).
Также можно использовать просто перечень необходимых утилит:
atuin
bat
...
И воспользоваться командой:
xargs -I {} brew install {} < Brewfile
Она работает следующим образом:
< Brewfile— перенаправляет содержимое файлаBrewfile(каждая строка которого, как предполагается, содержит имя пакета для Homebrew) на стандартный ввод командыxargs.xargsчитает этот ввод построчно.-I {}указываетxargsзаменять{}на каждую прочитанную строку.- Для каждой строки (например,
neovim)xargsвыполняет команду:
brew install neovim
Таким образом, команда устанавливает все пакеты, перечисленные в Brewfile, по одному, вызывая brew install для каждого.
Установка dotfiles и приложений с нуля
Предположим, мы создали виртуальную машину Linux и хотим установить в неё dotfiles. Для этого нужно выполнить несколько простых действий.
- Создать SSH-ключи в директории
.sshи добавить публичный ключ на GitHub. - Установить brew через официальный установщик.
- Установить chezmoi через пакетный менеджер или brew:
/opt/homebrew/bin/brew install chezmoi # На macOS
/home/linuxbrew/.linuxbrew/bin/brew install chezmoi # На Linux
- Скачать и применить dotfiles из удалённого репозитория:
/opt/homebrew/bin/chezmoi init --apply git@github.com:GeorgeKuzora/dotfiles.git # На macOS
/home/linuxbrew/.linuxbrew/bin/chezmoi init --apply git@github.com:GeorgeKuzora/dotfiles.git # На Linux
- Ввести значения пользовательских переменных в вызванном диалоге.
- Перейти в директорию с Brewfile:
cd ~/.config/brewfile
- Установить приложения и утилиты из Brewfile:
/opt/homebrew/bin/brew bundle # На macOS
/home/linuxbrew/.linuxbrew/bin/brew bundle # На Linux
- Все указанные приложения и утилиты будут установлены.
- Теперь можно открыть новое окно терминала и проверить, что не возникает никаких ошибок при инициализации оболочки.
При необходимости эти шаги можно объединить в bash-скрипт.