- Shell 44.7%
- Python 40.8%
- TypeScript 9.1%
- Nix 5.3%
- AppleScript 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .github/workflows | ||
| .ssh | ||
| atuin | ||
| home-manager | ||
| karabiner-ts | ||
| nix | ||
| scripts | ||
| skills | ||
| tests | ||
| tmux | ||
| zsh | ||
| .gitignore | ||
| LICENSE | ||
| README.md | ||
Dotfiles
Configuration files and a management script for a personalized development environment.
Usage
Clone
Clone the repository to any path you like — the dot script resolves its own location, so nothing depends on where it lives. The examples below use ${HOME}/.config/dotfiles.
Clone from GitHub, then add the private server as a second remote:
git clone https://github.com/kaygdotorg/dotfiles.git "${HOME}/.config/dotfiles"
cd "${HOME}/.config/dotfiles"
git remote add forgejo ssh://git@o2.vpn.kayg.org:2222/kayg/dotfiles.git
Alternatively, clone the private URL and add GitHub as a second remote. Both
remotes should be present. SSH authentication uses ~/.ssh/github.com and
~/.ssh/git.kayg.org; the private server requires VPN access. dot setup ssh
installs the corresponding host/key defaults without overwriting config.local.
Setup
The dot script handles all linking and installation. Start by symlinking it into your PATH:
"${HOME}/.config/dotfiles/scripts/dot" setup dot
export PATH="${HOME}/.local/bin:${PATH}"
Then set up whichever apps you need:
dot setup nix # reuse existing Nix; otherwise .pkg on macOS, script on Linux
export PATH="${HOME}/.nix-profile/bin:${PATH}"
dot setup zsh
dot setup tmux
dot setup atuin
dot setup agents
dot setup ssh
dot setup karabiner # macOS only; open Karabiner once and create "Default profile" first
On your Mac Studio, the existing package-installed Nix is reused. No installer
is downloaded or run. Apple Silicon Macs other than hostname mba select the
kayg Home Manager configuration; mba selects kayg-mba. Setup currently
requires user kayg, home /Users/kayg on macOS or /home/kayg on Linux, and
Nix 2.35 or newer. Git (Apple Command Line Tools on macOS) is needed to clone.
On macOS, Karabiner is a separate application installation. Only run
dot setup karabiner after installing Karabiner and creating Default profile.
After setup, open a fresh terminal. In tmux, Ctrl+T then Shift+I installs
its configured plugins. Atuin authentication/sync is a separate step:
atuin login -u kayg, then atuin sync (or register a new account if needed).
Reconcile an existing host
From the existing checkout (or using its installed dot command):
dot update dot # pull this repository
dot setup dot
dot setup agents # install native agents before removing old Nix copies
dot setup nix # apply the current lock; installs tmux, removes managed Zellij/Emacs
export PATH="${HOME}/.nix-profile/bin:${HOME}/.local/bin:${PATH}"
dot setup tmux
dot setup zsh
dot setup atuin
dot setup ssh
# macOS with Karabiner already installed:
dot setup karabiner
setup is intentional: it reconciles the tracked configuration without
refreshing package versions. Use dot update nix separately when you want to
refresh the flake inputs. dot update zsh updates shell plugins; dot update tmux explains the plugin update shortcut (Ctrl+T then Shift+U).
Close or detach from Zellij before opening the new shell. Old configuration
and Doom framework directories are preserved for manual archival. Existing
processes keep running; applying Home Manager removes its managed packages.
Inside an up-to-date tmux server, Ctrl+T then r reloads the config. A server
started with an older tmux binary must be restarted after you finish its
sessions to use the new binary; reload alone does not upgrade a running server.
Each dot setup <app> command creates the necessary directories and links or generates application configuration. Zsh plugins are cloned during their first setup; repeated setup reuses existing plugins.
Division of responsibility: Home Manager owns packages and the daily Nix updater on both macOS and Linux. dot deploys the remaining application configuration and bootstraps Home Manager. dot setup nix uses sudo only to install Nix when missing or change root-owned cache configuration. Daily package updates run as your user.
Pre-existing files are never destroyed. If a real file (rather than one of our symlinks) already sits at a destination — a distro-provided ~/.zshenv, an ~/.ssh/config you wrote by hand — it is moved to <file>.bak.<timestamp> before the symlink is created, and the backup is announced as it happens.
When something fails, dot prints the failing command and its output rather than a bare failed. Set DOT_LOG to also keep a transcript of every command:
DOT_LOG=/tmp/dot.log dot setup zsh
Command format and validation
The dot CLI takes an action and an application. Nix setup also accepts --yes to authorize installing Nix when it is missing; existing installations are reused:
dot <setup|update> <dot|zsh|tmux|atuin|agents|ssh|karabiner|nix>
If arguments are missing or invalid, dot prints usage and exits with a non-zero status.
Re-running setup commands
dot setup zshreuses valid plugin checkouts and rejects incomplete or unrelated plugin directories before changing shell configuration.dot update zshpulls plugin changes; Nix manages the Zsh support binaries.dot setup atuinlinks an existing Atuin install into~/.local/binand refreshes its config; the binary itself comes from Home Manager/Nix.dot setup dot,dot setup ssh, anddot setup karabinerare symlink- or generation-based and can be run repeatedly.dot setup sshseeds~/.ssh/config.localfrom.ssh/config.local.examplethe first time only — your machine-local hosts are never overwritten. A dangling link or directory at that path causes an error before setup changes anything.dot setup karabinerrequires npm and an existing Karabiner configuration containingDefault profile. It refuses to run anywhere but macOS. Changed profiles are backed up before an atomic write; identical reruns do not create another backup. Device settings and other profiles are preserved.dot setup nixapplies the repository's existing lock. It skips Nix installation when already present and only replaces cache settings when their contents change.dot update nixprepares updated flake inputs and an OmniWM release pin in a temporary candidate, validates packages, then activates and retains the new pins. A failed candidate does not replace the repository's working pins.- Cache-only package outputs and external dependencies are fetched in batches so Nix can download them concurrently. Missing cache entries still fail without compiling packages; reviewed configuration generation and activation remain ordered.
- Home Manager's
~/.nix-profile/bintakes precedence over system Nix and native CLI installs on macOS and Linux. Claude Code, Codex, and agy use the official native installers in~/.local/bin; Home Manager does not install agent binaries. dot update dotuses Git's autostash while pulling, so a generated OmniWM pin or another local edit is reapplied after the repository update. If reapplying local edits causes conflicts, the command reports failure and leaves Git's recovery state intact.
Apps
-
tmux — Restored Ctrl+T configuration with Catppuccin Mocha pills, vi copy mode, OSC 52 clipboard, nested-session passthrough (F12 or Alt+Shift+F), and mobile tuning.
dot setup tmuxlinks the configuration and installs TPM; prefix + I installs tmux-fingers. -
Zsh — Standalone configuration with Oh My Posh prompt, vi-mode, lazy-loaded nvm, OSC 133 semantic-prompt marks, and plugins (autosuggestions, syntax highlighting, history substring search, completions).
-
Atuin — Shell history replacement with sync to a self-hosted server, replacing the default zsh history search.
-
SSH — Managed SSH client configuration, tuned for mobile links (keepalives, connection multiplexing), with machine-local hosts kept out of the repository in
~/.ssh/config.local. -
yq — The Go implementation (
yq-go), managed by Home Manager. -
Rootshell compatibility — Hosts retain
xterm-ghosttyterminfo for Rootshell SSH sessions. No Ghostty app configuration is deployed. -
Karabiner — Advanced keyboard customization via karabiner.ts with Colemak-DH layout and hyper key layers.
-
Home Manager / Nix — User package management for every machine. The shared flake lives in
home-manager/;dot setup nixselects the macOS or Linux host configuration and enables its daily update.
Nix setup and updates
dot setup nix performs these steps in order:
- Detect the OS, architecture, and existing Nix executable. Install Determinate Nix only when Nix is missing. On macOS, download
Determinate.pkg, verify its Gatekeeper assessment and Determinate signing team, then run/usr/sbin/installerthrough sudo. On Linux, download and run the shell installer as separate commands. Installation requires a terminal confirmation or explicit--yes; a missing terminal is never treated as consent. - Install the tracked
nix/cache.confas a root-owned/etc/nix/dotfiles-cache.conf. Add its include to Determinate's/etc/nix/nix.custom.conf(or standard Nix's/etc/nix/nix.conf), preserving other settings. The managed snippet clears the former agent cache settings; Nix uses its existing default caches. Reload the installed daemon only when necessary. No global build settings are changed. - Link
~/.local/bin/dotfiles-nix-updateto this checkout's shared updater. Apply the already locked Home Manager configuration for the host. - Retire the previous updater units after successful activation. Home Manager owns the replacement launchd agent or systemd user timer.
Re-running setup converges the same configuration. It does not reinstall Nix, re-pin the registry, or update the lock. Refresh versions explicitly with dot update nix, or let the daily user service do it. The default schedule is 09:17 local on macOS and 03:47 UTC plus up to 15 minutes on Linux. A directory lock prevents overlapping setup/update runs.
The configurations are kayg and kayg-mba for Apple Silicon Macs, kayg-linux for x86_64 Linux, and kayg-linux-arm for ARM Linux. Other architectures fail with an explanation before setup changes anything. All hosts currently use the username kayg.
Agent CLIs
dot setup agents # install missing native Claude Code, Codex, and agy
dot update agents # update all three, installing any that are missing
These commands run as your normal user on macOS and Linux. They install to
~/.local/bin, download installer scripts before executing them, and stop on
a failed download, install, or update. Setup reuses existing user-local
executables and replaces local symlinks into the immutable Nix store without
changing their targets. Update explicitly runs the user-local binaries.
| Tool | Official installation | Explicit update |
|---|---|---|
| Claude Code | https://claude.ai/install.sh with Bash |
~/.local/bin/claude update |
| Codex | https://chatgpt.com/codex/install.sh with sh |
Rerun the same installer |
Antigravity CLI (agy) |
https://antigravity.google/cli/install.sh with Bash |
~/.local/bin/agy update |
Sources: Claude Code,
Codex CLI,
Antigravity CLI.
Sign in with each CLI after installation; dot does not manage credentials.
When migrating an existing host, run dot setup agents before dot setup nix so the native executables are installed before the old managed packages
are removed. Applying the new Home Manager configuration retires the old Nix
agent packages and OMP/CLIProxyAPI/code-review-graph. Native agents then resolve
from ~/.local/bin. The daily Nix updater updates Nix packages only; agent
updates use their own upstream behavior and dot update agents.
Precompiled packages
Ordinary tools come from the nixpkgs binary cache. Agent CLIs are installed
outside Nix using dot setup agents and dot update agents. OMP (the agent),
CLIProxyAPI, and code-review-graph are retired. Oh My Posh remains the shell
prompt. No agent flake dependency remains in the lockfile.
Packages installed separately by earlier versions are migrated only when their
profile names, Nix attributes, and original source URLs match the explicit
migration list. This includes standalone nixpkgs _1password-cli and yq-go,
and known legacy agent/tool entries from the old flake. Unrelated packages
are preserved.
The deployment helper evaluates home-manager/deployment.nix, including packages added by Home Manager modules. It requests external package outputs with local and remote builders disabled for that command. Missing cached packages stop the operation. This does not change the global max-jobs setting. It requires Nix 2.35 or newer for that command's scheduling behavior; an older installed Nix is reported rather than silently replaced.
Only exact, declared derivations may run locally: Home Manager's configuration generators, the OmniWM release ZIP downloader/unpacker, and Apple's unmodified font unpackers. Their tool dependencies must already exist or download from cache. Temporary Nix GC roots keep those outputs available until activation or rollback finishes. Additional uncached derivations stop the update instead of compiling. This policy is part of the reviewed repository; changing its approved derivations requires the same care as changing the package list.
Before any profile migration, the full candidate generation must be ready. Activation failure triggers an attempt to reactivate the prior Home Manager generation and restore the exact saved package-profile generation. The updater checks for concurrent edits before persisting pins and never uses Git reset, commits, or pulls automatically.
To inspect the configuration without activation:
~/.config/dotfiles/nix/nix-unstable-update --check
For rollback, inspect nix profile history and the generations under ~/.local/state/nix/profiles/home-manager. Run the chosen old generation's activate script to restore Home Manager files and packages. Restore the corresponding home-manager/flake.lock and OmniWM pin in Git before the next update if you want to retain the old versions. Profile rollback alone does not revert repository files.
OmniWM releases
OmniWM uses a versioned upstream ZIP and hash because its releases can arrive ahead of nixpkgs. The macOS updater runs the locked nix-update tool against a temporary candidate, then downloads and unpacks the approved binary bundle. Generic fixups and stripping are disabled; bundle structure and signature are checked before activation. home-manager/updates.nix declares both operating systems' schedules around the same updater.
Repository Structure
Configuration files are stored flat under each app's directory. The dot script is responsible for creating directories at the destination and symlinking files into place. No file in this repository needs to match its final system path.
.
├── .github/workflows/
│ └── shellcheck.yml # Shell, fixture, Python, and Karabiner checks
├── scripts/
│ ├── dot # Setup and management script (bash)
│ └── toggle-menu-bar-visibility.applescript
├── tmux/ # tmux config, mobile tuning, and keybinding cheatsheet
├── zsh/
│ ├── .zshenv # Sets ZDOTDIR so zsh finds its config
│ ├── .zshrc # Main shell configuration
│ └── omp.yaml # Oh My Posh prompt theme
├── atuin/
│ └── config.toml # Atuin shell history configuration
├── .ssh/
│ ├── config # SSH client configuration
│ └── config.local.example # Tracked template; its config.local copy is untracked
├── karabiner-ts/
│ └── index.ts # Generates Karabiner-Elements JSON profile
├── skills/ # Tracked local SKILL.md instructions and utilities
└── home-manager/ # Nix/Home Manager flake (user packages)
Linking map
The dot script symlinks each file from this repository into its expected system location. The diagram below shows what goes where:
graph LR
subgraph Repository
A["zsh/.zshenv"]
B["zsh/.zshrc"]
C["zsh/omp.yaml"]
T["tmux/.tmux.conf + helpers"]
E["atuin/config.toml"]
F[".ssh/config"]
F2[".ssh/config.local.example"]
G["karabiner-ts/index.ts"]
H["scripts/dot"]
end
subgraph System
A1["~/.zshenv"]
B1["~/.config/zsh/.zshrc"]
C1["~/.config/zsh/omp.yaml"]
T1["~/.config/tmux/"]
E1["~/.config/atuin/config.toml"]
F1["~/.ssh/config"]
F21["~/.ssh/config.local"]
G1["~/.config/karabiner/karabiner.json"]
H1["~/.local/bin/dot"]
end
A -->|symlink| A1
B -->|symlink| B1
C -->|symlink| C1
T -->|symlink| T1
E -->|symlink| E1
F -->|symlink| F1
F2 -->|copy once| F21
G -->|generates| G1
H -->|symlink| H1
Note: Karabiner is the exception — its local npm dependencies are installed and
index.tsis executed viatsx, which writes the profile JSON directly to~/.config/karabiner/karabiner.json. It is not symlinked.
Shell sessions
Interactive terminal shells automatically attach to tmux, or create the work
session when none exists. Set TMUX_CLIENT_NAME per device to use grouped
sessions sharing work's windows with separate session options. Nested tmux,
non-interactive shells, shells without a terminal, and forced SSH commands skip
automatic attachment. Set DOT_NO_AUTOMUX=1 in the remote shell environment to
opt out.
Run dot setup tmux and dot setup zsh to deploy the configuration. Apply your
Home Manager configuration with dot setup nix to install tmux and remove
Zellij and Emacs. Existing ~/.config/zellij, ~/.config/doom, and
~/.config/emacs directories and running sessions are left intact.
tmux navigation
The prefix is Ctrl+T. These features use built-in tmux commands and the existing fzf package; no session restoration plugin is installed.
| Keys | Action |
|---|---|
| Prefix, Enter | Enter scrollback/copy mode |
/ or ? in copy mode |
Incrementally search pane output forward/backward |
n / N in copy mode |
Repeat/reverse the last search |
[ / ] in copy mode |
Jump to the previous/next shell prompt (OSC 133) |
Prefix, p |
Fuzzy-pick an existing session or project directory |
Prefix, E |
Toggle extended keys (off by default; application opt-in, CSI u) |
Copy mode shows hybrid line numbers. Its search covers pane output, while Atuin continues to search shell command history.
The picker includes the current Git repository and immediate subdirectories of
~/src, ~/code, ~/projects, ~/Developer, and ~/repos. Set
DOT_PROJECT_ROOTS to a colon-separated list of alternative parent directories
in the environment that starts tmux. Project sessions reuse a stable name based
on the directory and its path hash. Escape cancels without creating a session.
Extended keys are server-wide and remain enabled across reloads when toggled, but default to off on a new server. Test modified keys in each terminal and application; toggle them off again if a client misbehaves.
Validation
Run the isolated checks without installing packages or changing your configuration:
bash scripts/tests/dot-regressions.sh
bash tests/nix-bootstrap.sh
bash tests/nix-update.sh
python3 -m unittest discover -s tests -p 'test_*.py'
(cd karabiner-ts && npm ci && npm run check)
With Nix installed, bash tests/nix-evaluation.sh evaluates all four hosts and checks that activation cannot restart its own updater. It does not build or activate any packages.
License
See LICENSE file for details.