Opinionated defaults. This repo is not meant to be used by everyone, just a personal configuration to take ideas out of. However, if you are brave enough you can install it with the instructions below. You are advised to read the installation scripts beforehand.
You might wonder why the repository isn't just cloned directly into ~/dotfiles. The nested ~/dotfiles/dotfiles structure is required by how GNU Stow manages packages. Stow expects a "stow directory" (the parent, ~/dotfiles) containing one or more "packages" (the child, dotfiles, which is this repo).
By cloning into ~/dotfiles/dotfiles, stow correctly treats the inner dotfiles folder as the package name, allowing it to safely symlink the contents (like .config/, .vim/) directly into your $HOME directory without confusing the repository root with the target deployment.
So, the structure looks like this:
~/dotfiles/
└── dotfiles/ (this repo)
├── .config/ ──┐
├── .local/ │ GNU Stow symlinks
├── .vim/ ├──────────────────► $HOME/
├── .xmonad/ │ ├── .config/ → ~/dotfiles/dotfiles/.config/
├── .zshenv ──┘ ├── .local/ → ~/dotfiles/dotfiles/.local/
├── Makefile (make targets) └── ...
└── .local/scripts/
└── bootstrap/ (OS-specific setup)
GNU Stow creates symlinks from $HOME
into this repo so that every config file stays version-controlled in one place.
The Makefile wraps stow and exposes common tasks. Bootstrap scripts install
packages and perform one-time setup for each supported OS.
The $DOTFILES variable (exported by .zshenv) points to the repo root,
auto-detected by resolving the .zshenv symlink. Scripts and configs that
need to reference the repo should use $DOTFILES.
Clone the repo with its submodules and use GNU Stow to symlink everything into $HOME:
cd ~
mkdir -p dotfiles
git clone --recurse-submodules https://github.com/cuberhaus/dotfiles dotfiles/dotfiles
cd dotfiles/dotfiles
make installIf you don't have
stowinstalled, grab it first:
- Arch/Manjaro:
sudo pacman -S stow- Ubuntu/Debian:
sudo apt install stow- macOS:
brew install stow
Common tasks are available via make:
make help # Show all targets
make install # Symlink dotfiles into $HOME
make uninstall # Remove symlinks from $HOME
make restow # Re-stow (cleans stale links)
make lint # Run shellcheck on all scripts
make check # Run all linters (shellcheck + markdownlint + vint)
make submodules # Init and update submodules
make update # Pull latest for every submodule
make skip-worktree # Ignore runtime changes to volatile config files (run once after cloning)
make bootstrap-<os> # Run bootstrap (arch, manjaro, ubuntu, mac, work)
Some tracked files (e.g. user_preferences.json for Warp, javasettings_Linux_X86_64.xml for LibreOffice) are rewritten by their apps on every launch. They are kept in the repo so the settings you care about are versioned, but the constant runtime changes make git status noisy and block git pull.
After cloning, run once:
make skip-worktreeThis applies git update-index --skip-worktree to those files — git keeps the committed version but stops noticing local changes.
When you intentionally want to update one of them in the repo:
git update-index --no-skip-worktree .config/warp-terminal/user_preferences.json
# edit / copy the new settings you want to keep
git add .config/warp-terminal/user_preferences.json
git commit -m "update warp settings"
git update-index --skip-worktree .config/warp-terminal/user_preferences.json # re-applyOS-specific bootstrap scripts live in .local/scripts/bootstrap/.
Read the script for your OS before running it — they install hundreds
of packages and change system settings.
make bootstrap-arch # Arch
make bootstrap-manjaro # Manjaro
make bootstrap-ubuntu # Ubuntu
make bootstrap-mac # macOS
make bootstrap-work # Work machine (Ubuntu + NVIDIA, minimal)For a machine that boots both Windows and physical Linux, opt in to a UTC hardware clock during bootstrap:
make bootstrap-ubuntu BOOTSTRAP_ARGS=--dual-boot-utc
# Also supported: bootstrap-arch, bootstrap-manjaro, and bootstrap-workFor an existing physical Linux installation, run:
make dual-boot-utcThe helper rejects WSL and non-systemd systems. It enables network time, waits
for NTPSynchronized=yes, then runs timedatectl set-local-rtc 0 and verifies
LocalRTC=no. It refuses to change the RTC if synchronization cannot be
established. Configure Windows with WinDotfiles' -DualBootUtcRtc option as
well; changing only one operating system leaves the original disagreement in
place.
Verify with timedatectl: it should report System clock synchronized: yes,
NTP service: active, and RTC in local TZ: no. Do not use
timedatectl set-local-rtc 1 as a permanent workaround because local RTC is
fragile across DST and timezone changes. The option is intentionally not
forwarded to the WSL or macOS bootstrap targets.
Each bootstrap entrypoint follows the same pattern:
- Sources
base_functions(shared logging helpers and$DOTFILESauto-detection). - Sources the OS-specific
*_functionsfile (package lists and installer functions). - Asks whether this is a first-time install (enables services, sets up hardware, etc.).
- Runs a system update.
- Calls installer functions in dependency order (base packages, WM-specific, optional apps).
- Switches the default shell to zsh.
- Installs the native maintenance schedules shown below.
The bootstrap installs these automations without requiring a separate command:
| Automation | Linux | macOS |
|---|---|---|
| System packages | Sunday 10:00 via system systemd (apt-get or pacman) |
Managed by macOS Software Update |
| User packages | Sunday 10:30 via user systemd (Homebrew and/or yay) |
Sunday 10:30 via launchd (Homebrew) |
~/cuberhaus pull |
Daily 11:00 via user systemd |
Daily 11:00 via launchd |
The workspace pull matches the shell helper's depth-3 discovery but uses
git pull --ff-only --no-edit with credential prompts disabled. Repositories
without upstreams are skipped; diverged or unauthenticated repositories are
left unchanged and reported. None of the maintenance jobs requests a reboot.
On Linux, Persistent=true runs a missed timer when the machine becomes
available. On macOS, the agents run at login as well as on schedule, with state
guards preventing duplicate work: 20 hours for workspace pulls and seven days
for user package updates. WSL deliberately installs no duplicate timers because
Windows Task Scheduler owns the same automations.
For an existing checkout, restow the new files and install the schedules:
make restow
make install-automationsPreview or remove scheduler registrations without deleting logs or state:
make uninstall-automations-dry-run
make uninstall-automationsmake uninstall removes scheduler registrations before unstowing the files, so
no active job is left pointing at a removed script.
Inspect the schedules and logs:
# Linux
systemctl list-timers 'cuberhaus-*'
systemctl --user list-timers 'cuberhaus-*'
journalctl -u cuberhaus-system-maintenance.service
journalctl --user -u cuberhaus-user-package-maintenance.service
journalctl --user -u cuberhaus-workspace-pull.service
# macOS
launchctl print "gui/$UID/com.cuberhaus.user-package-maintenance"
launchctl print "gui/$UID/com.cuberhaus.workspace-pull"
tail -f "$HOME/Library/Logs/Cuberhaus/user-package-maintenance.log"
tail -f "$HOME/Library/Logs/Cuberhaus/workspace-pull.log"See .local/README.md for a detailed breakdown of the
scripts directory.
| Category | Tool / Config | Notes |
|---|---|---|
| Shells | zsh (antigen, p10k), bash | XDG-compliant $ZDOTDIR in .config/zsh/ |
| Editors | Vim, Neovim, Doom Emacs, personal Emacs (chemacs) | Vim config at .vim/vimrc; Emacs literate config in .config/emacs.org |
| Terminals | kitty, Alacritty, termite | |
| Window Managers | XMonad (+xmobar), i3 (+i3blocks +polybar), qtile, sway | XMonad is the primary config; i3 is the secondary |
| Desktop Environments | Cinnamon, GNOME | |
| Utilities | tmux, ranger, dunst, picom, rofi, fzf, bat, eza | |
| Themes | Arc, Dracula, OneDark, base16 | Managed via toggle_theme script |
- Xmonad (Main config)
- i3 (Second best)
- Cinnamon desktop
- Gnome



