initd bootstraps a fresh development machine from one repo and one shell script. It supports both macOS and Fedora Workstation Linux from the same codebase.
It owns both:
- machine setup: Homebrew (mac) / dnf (linux), apps, runtimes, OS defaults, Hyprland desktop
- user config: Neovim, Ghostty, Fish, tmux, Git, Hyprland/waybar/rofi/dunst, and other dotfiles via managed symlinks
Three top-level buckets. The platform directories are decoupled — rm -rf macos/ or rm -rf linux/ and the other platform keeps working.
initd/
├── bootstrap.sh # dispatcher: detects uname -s and execs the platform bootstrap
├── shared/ # cross-platform
│ ├── lib/ # logging, fs, link, cleanup, git-profile
│ ├── managed-links.sh # MANAGED_LINKS for shared configs + git helpers
│ ├── configs/ # fish, git, ghostty, mise, nvim, starship, tmux
│ └── test.sh # behavior tests (auto-detects host OS)
├── macos/ # self-contained macOS bootstrap
│ ├── bootstrap.sh
│ ├── Brewfile
│ ├── brewinstall / brewinstall.sh
│ ├── defaults.sh # macOS defaults write …
│ ├── managed-links.sh
│ └── update.sh
└── linux/ # self-contained Linux bootstrap
├── bootstrap.sh # targets Fedora Workstation 44+ (dnf5)
├── packages.txt # dnf package list (some via COPR — see below)
├── setup.sh # fonts/swappiness/theme/Firefox profile glue
├── managed-links.sh
├── update.sh
├── scripts/ # session scripts invoked by hyprland.lua/waybar
└── configs/ # hypr, waybar, rofi, dunst, gtk-3.0, firefox
git clone <repo-url> ~/.config/initd
bash ~/.config/initd/bootstrap.shThe dispatcher runs macos/bootstrap.sh on Darwin and linux/bootstrap.sh on Linux. Both:
- install required packages (Homebrew or dnf, enabling COPRs on Linux where Fedora's own repos don't carry a package)
- install mise and gh (dnf repo on Linux, brew on macOS)
- link managed configs into
$HOME, backing up unmanaged files to~/.config/initd-backups/<timestamp>/ - set fish as the login shell (
dsclon macOS,chshon Linux), then sync fisher plugins - run
mise installfor shared runtimes and LSP tooling - prompt for
personal/workGit identity on first run (work stores a separate email inlocal.gitconfig)
Re-running is safe and idempotent.
| Action | Command |
|---|---|
| Full bootstrap | bash bootstrap.sh |
| Re-apply links only | shared/lib/link.sh <macos|linux> |
| Set Git identity | shared/lib/git-profile.sh personal / work |
| Remove managed symlinks | shared/lib/cleanup.sh <macos|linux> --dry-run |
| Add a brew formula/cask | macos/brewinstall <name> |
| Update tools | macos/update.sh or linux/update.sh |
| Run behavior tests | shared/test.sh |
Runtime paths in $HOME are symlinks back into this repo. Editing happens inside initd, not at the live paths.
| Runtime path | Source |
|---|---|
~/.config/fish |
shared/configs/fish/.config/fish |
~/.config/ghostty |
shared/configs/ghostty/.config/ghostty |
~/.config/mise |
shared/configs/mise/.config/mise |
~/.config/nvim |
shared/configs/nvim/.config/nvim |
~/.config/starship.toml |
shared/configs/starship/.config/starship.toml |
~/.config/tmux |
shared/configs/tmux/.config/tmux |
~/.gitconfig |
shared/configs/git/gitconfig (work email override via local.gitconfig) |
| Runtime path | Source |
|---|---|
~/.config/hypr |
linux/configs/hypr |
~/.config/waybar |
linux/configs/waybar |
~/.config/rofi |
linux/configs/rofi |
~/.config/dunst |
linux/configs/dunst |
~/.config/fontconfig |
linux/configs/fontconfig |
~/.config/gtk-3.0 |
linux/configs/gtk-3.0 |
~/.gtkrc-2.0, ~/.icons/default/index.theme, Firefox profile glue |
linked individually by linux/setup.sh (paths are dynamic or outside ~/.config/) |
Two gitignored files — sourced/included if present, silently skipped if absent.
| File | Purpose |
|---|---|
shared/configs/git/local.gitconfig |
Work (or other) Git email override — absent on personal machines |
shared/configs/fish/.config/fish/local.fish |
Machine-specific env vars and overrides |
If a managed runtime path already exists and isn't an initd symlink, it is moved to:
~/.config/initd-backups/<timestamp>/<original-path>
before initd takes ownership. Nothing is deleted.
.gitattributes enforces LF for *.sh, *.fish, *.conf, *.ini, etc. so commits from any host (including Windows) land as LF — the only line-ending bash and /etc/-style configs accept on macOS/Linux.
macos/brewinstall <name> detects whether the package is a formula or cask, appends it to macos/Brewfile, and runs brew bundle. brew bundle dump --force --file macos/Brewfile exports the current machine's state if you want a starting point — review carefully before committing.
Docker comes via Colima (no Docker Desktop): the colima, docker, docker-compose, and docker-credential-helper formulas in the Brewfile, a managed VM template at shared/configs/colima/, and an ensure_docker_config bootstrap step that points docker login at the macOS Keychain and wires the brew-installed compose plugin into the docker CLI. See docs/colima.md.
linux/packages.txt is one package per line, comments allowed. Add to it and re-run linux/bootstrap.sh. The packages list intentionally tracks the brew formulas where equivalents exist (fish, tmux, tig, git, gnupg2, neovim) plus Fedora -devel build deps for mise-managed Python/Node. Fedora's own repos don't carry the Hyprland ecosystem, nwg-displays, or ghostty — install_packages() enables the sdegler/hyprland, tofik/nwg-shell, and scottames/ghostty COPRs first. Docker Engine is installed separately from Docker's official dnf repository, together with Buildx and the Compose v2 plugin; bootstrap adds the login user to the privileged docker group, so log out and back in once after its first installation. gh CLI and 1Password also come from their own official dnf repos.
linux/setup.sh handles things that don't fit the symlink flow:
- system fixes requiring sudo (swappiness,
videogroup membership for backlight keys) - GTK theme + font/cursor gsettings (Fedora has no
fonts-ubuntuor DMZ-cursor package, so this usesAdwaita Sansand theAdwaitacursor theme instead) - session scripts symlinked to absolute
~/.config/paths that hyprland.lua/waybar invoke directly - special-case paths outside
~/.config/:~/.gtkrc-2.0,~/.icons/default/, Firefox profile files (profile path is dynamic; Firefox itself is unmanaged by bootstrap.sh — install it however you like, this glue configures whatever it finds)
- Cross-platform: drop the source under
shared/configs/<name>/, add an entry toMANAGED_LINKSinshared/managed-links.sh. - macOS-only: drop under
macos/configs/<name>/, append toMANAGED_LINKSinmacos/managed-links.sh. - Linux-only: drop under
linux/configs/<name>/, append inlinux/managed-links.sh.
Then re-run shared/test.sh.
docs/bash-primer.md— repo-specific Bash patternsdocs/fish.md— fish/bash/zsh syntax comparisondocs/nvim.md— Neovim setup with Lazy.nvimdocs/mise.md— mise tool managementdocs/git-branching-conflicts.md— Git branching and conflict resolutiondocs/tmux-nvim.md— tmux and Neovim workspace conceptsdocs/tmux-sessions.md— tmux session/window/pane workflow and keybindings