Organizing a dotfiles repo
A small setup can live entirely in dots.lua. As the repo grows, split the config into normal Lua modules and load them with require().
lua
require("dots.common")
if dots.platform.family == "arch" then
require("dots.arch")
end
if dots.platform.family == "darwin" then
require("dots.macos")
endOne possible layout:
text
dotfiles/
dots.lua
dots/
common.lua
arch.lua
macos.luaThere is no dots.load() wrapper. dots adds the repo to Lua's module path, so plain Lua loading works.
Common config
Put declarations shared by every machine in dots/common.lua:
lua
dots.user.shell("zsh")
dots.symlink("~/.zshrc", ".zshrc")
dots.symlink("~/.gitconfig", ".gitconfig")
dots.fonts.install()This is a good place for shared files, fonts, shell settings, and commands.
Platform config
Put OS-specific declarations in platform modules:
lua
-- dots/arch.lua
dots.pacman.install({ "base-devel", "git" })
dots.yay.enable({ method = "aur" })
dots.yay.install({ "bat", "docker", "ripgrep" })
dots.group.create({ "docker" })
dots.user.add_to_groups({ "docker" })
dots.systemd.enable({ "docker.service" })
dots.systemd.start({ "docker.service" })lua
-- dots/macos.lua
dots.brew.enable()
dots.brew.install({ "bat", "ripgrep" })
dots.brew.cask({ "ghostty", "firefox" })The top-level dots.lua stays small: load common config first, then load the platform module for the current machine.
Profiles
Profiles are useful when the operating system is not enough. For example, the same machine might have a work profile and a personal profile.
lua
if dots.profile == "work" then
require("dots.profiles.work")
end
if dots.profile == "personal" then
require("dots.profiles.personal")
endThat maps to:
text
dotfiles/
dots.lua
dots/
common.lua
arch.lua
macos.lua
profiles/
work.lua
personal.luaA practical split is:
common.lua: shared everywherearch.lua,macos.lua: OS-specific setupprofiles/*.lua: host, role, or profile-specific setup