ansible-playbook -l localhost[,otherhost,...] -K dotfiles-base.yamlansible-playbook -l localhost -t nvim -K dotfiles-base.yamlMost tracked files are symlinked straight into $HOME by the files role, so
editing the live file edits the repo. That does not work for configs the distro
also rewrites - Omarchy's update migrations mostly use mv, which replaces the
file and destroys the symlink without saying so.
For those, ownership is split: the distro keeps its own base config untracked,
personal settings live in a separate overlay file that is tracked and
symlinked, and the overlays role asserts the single line that connects them.
See roles/overlays/README.md for the mechanics and CHANGELOG.md for why.
Most tracked files do not need this. Direct symlinking is the default and
covers the large majority of files/ - nvim, zsh, gh, yamllint, systemd units,
gvim, alsa, pipewire and the rest. A file only becomes an overlay when something
other than this repo also writes to it.
Four do today:
| Where | Base config (not tracked) | Overlay (tracked here) | Wired by |
|---|---|---|---|
| everywhere | ~/.bashrc |
~/.bashrc_jack |
group_vars/all.yaml |
| Omarchy | ~/.config/tmux/tmux.conf |
~/.config/tmux/local.conf |
roles/omarchy |
| Omarchy | ~/.config/kitty/kitty.conf |
~/.config/kitty/local.conf |
roles/omarchy |
Hyprland is deliberately not in that table. Omarchy's hypr/input.lua,
bindings.lua and looknfeel.lua are all-comment templates carrying no settings
of their own, so they are symlinked directly like any other tracked file. An
overlay there would buy nothing and would break omarchy menu's "input
configuration" entry, which opens the base file. See
roles/overlays/README.md for the deciding test.
Bash is the oldest of these and predates the pattern having a name: ~/.bashrc
is seeded from /etc/skel and carries distro-specific setup, so the repo tracks
.bashrc_jack and asserts one source line instead of owning the file.
.bash_aliases_jack rides along, sourced by .bashrc_jack.
Only Omarchy needs distro-specific entries. On vanilla Arch, Debian and Ubuntu
neither pacman nor apt writes into $HOME - base configs are seeded once from
/etc/skel or by the app itself and never upgraded behind your back - so
roles/arch and roles/debian carry empty manifests and exist as the extension
point plus a symlink health check.
Day to day nothing changes: edit the overlay file at either path, git diff
shows it, commit.
A third case sits outside both mechanisms above: configs whose content varies by machine. A symlink cannot hold two values, so these are rendered from a template with per-machine variables.
roles/voxtype is the worked example. Two of its settings genuinely differ - the
audio source (default on the laptop, the PipeWire echocancel sink on the
desktop) and whether voxtype grabs the push-to-talk key itself or leaves it to
the compositor, since Omarchy binds recording in Hyprland. Everything else is
shared. Per-machine values live in voxtype_machines in group_vars/all.yaml,
keyed on the real hostname; a machine with no entry takes the defaults.
Templating also happens to be more robust here than symlinking would be, because
voxtype configure rewrites the config in place and would destroy a symlink.
The trade-off: a rendered file is generated, so editing it in ~/.config no
longer edits the repo. Reach for this only when content genuinely varies per
machine. Prefer a direct symlink, then an overlay, then files/hosts/, and only
then a template.
The playbook deliberately does not reset distro base configs to stock - that is destructive and only you can tell a real customization from stale drift. Do it by hand, in this order.
On a non-Arch host (oracle, homebox) there is nothing to migrate. The arch
and omarchy roles end immediately off their own distro. ~/.tmux.conf is still
the base config there and now sources the shared overlay, so just run the
playbook and confirm tmux still behaves:
ansible-playbook -l oracle dotfiles-base.yaml
tmux kill-server; tmux new -d \; show-options -g | grep history-limit # expect 100000On another Omarchy or Arch desktop:
-
Snapshot first. These are one-way steps.
omarchy snapshot create # Omarchy only; needs sudo cp -a ~/.config/{hypr,tmux,kitty} /tmp/preoverlay-backup/
-
Diff your base configs against stock and copy anything you actually want into the overlay files. Anything not in an overlay is about to be lost.
for f in tmux/tmux.conf kitty/kitty.conf hypr/bindings.lua hypr/input.lua hypr/looknfeel.lua; do echo "=== $f ===" diff -u "/usr/share/omarchy/config/$f" "$HOME/.config/$f" done
Split what you find three ways: real customizations (move to the overlay), drift where you never refreshed against a newer default (drop), and settings the current default already matches (drop - a phantom override silently fights future upstream changes).
-
Hand the base configs back to the distro. Each writes a
.bak.<epoch>beside the original.for f in tmux/tmux.conf kitty/kitty.conf \ hypr/bindings.lua hypr/input.lua hypr/looknfeel.lua hypr/hyprland.lua; do omarchy refresh config "$f" done
-
Run the playbook. This symlinks the overlay files in and asserts the include lines. It is idempotent - a second run reports
changed=0.ansible-playbook -l localhost dotfiles-base.yaml
-
Verify.
hyprctl reload && hyprctl configerrors # expect no output omarchy menu keybindings --print | grep -i qute tmux kill-server; tmux new -d \; show-options -g | grep history-limit ~/.config/omarchy/hooks/post-update.d/10-dotfiles-overlay # expect silence
The hook printing
re-asserted ...means an include line was missing; that is it doing its job, not an error. -
Delete the backups once you are satisfied, along with any orphaned pre-Quattro
~/.config/hypr/*.conf, which Hyprland no longer reads.
This repo now has a secrets role that restores these home-directory secrets from Bitwarden Secrets Manager:
~/.ssh/id*~/.ssh/config*~/.config/keys/*exceptBWS_ACCESS_TOKEN
Recommended:
mkdir -p ~/.config/bitwarden-sm
chmod 700 ~/.config/bitwarden-sm
printf '%s\n' 'YOUR_BWS_ACCESS_TOKEN' > ~/.config/bitwarden-sm/access-token
chmod 600 ~/.config/bitwarden-sm/access-tokenBackward-compatible fallback still supported:
~/.config/keys/BWS_ACCESS_TOKEN
Recommendation: use one Bitwarden Machine Account per machine and grant each machine account access to the shared dotfiles project. That gives you per-machine revocation without changing the repo.
From the machine that already has the correct files:
script/sync_secrets_to_bwsThat script will:
- create the Bitwarden project
dotfilesif needed - upsert matching files from
~/.sshand~/.config/keys - store each file as a Bitwarden secret keyed by its home-relative path
- Clone this repo.
- Put a Bitwarden Secrets Manager access token at
~/.config/bitwarden-sm/access-token. - Run the playbook:
ansible-playbook -l localhost[,otherhost,...] -K dotfiles-base.yamlIf you update secrets in the Bitwarden UI, just run the playbook again and the local files will be refreshed.
If you update secrets locally on a machine and want Bitwarden to become the source of truth again, run:
script/sync_secrets_to_bwsRecommendation: prefer editing in the Bitwarden UI for occasional changes, and use script/sync_secrets_to_bws when you intentionally want to publish the current machine state back into Bitwarden.
eval $(ssh-agent)
ssh-add ~/.ssh/id_rsa