No description
  • Shell 44.7%
  • Python 40.8%
  • TypeScript 9.1%
  • Nix 5.3%
  • AppleScript 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-26 10:49:37 +00:00
.github/workflows Install agent CLIs through official upstream installers 2026-10-26 10:49:37 +00:00
.ssh Restore tmux workflow and use the macOS Nix package installer 2026-10-26 09:01:40 +00:00
atuin feat(atuin): qol pass from 18.19 option research 2026-09-08 10:59:07 +00:00
home-manager Install agent CLIs through official upstream installers 2026-10-26 10:49:37 +00:00
karabiner-ts Harden cross-platform dotfiles setup and updates 2026-09-12 17:30:28 +05:30
nix Install agent CLIs through official upstream installers 2026-10-26 10:49:37 +00:00
scripts Install agent CLIs through official upstream installers 2026-10-26 10:49:37 +00:00
skills Harden cross-platform dotfiles setup and updates 2026-09-12 17:30:28 +05:30
tests Install agent CLIs through official upstream installers 2026-10-26 10:49:37 +00:00
tmux Restore tmux workflow and use the macOS Nix package installer 2026-10-26 09:01:40 +00:00
zsh Restore tmux workflow and use the macOS Nix package installer 2026-10-26 09:01:40 +00:00
.gitignore Harden cross-platform dotfiles setup and updates 2026-09-12 17:30:28 +05:30
LICENSE chore(license): update copyright year to 2026 2026-01-30 17:25:16 +01:00
README.md Install agent CLIs through official upstream installers 2026-10-26 10:49:37 +00:00

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 zsh reuses valid plugin checkouts and rejects incomplete or unrelated plugin directories before changing shell configuration. dot update zsh pulls plugin changes; Nix manages the Zsh support binaries.
  • dot setup atuin links an existing Atuin install into ~/.local/bin and refreshes its config; the binary itself comes from Home Manager/Nix.
  • dot setup dot, dot setup ssh, and dot setup karabiner are symlink- or generation-based and can be run repeatedly.
  • dot setup ssh seeds ~/.ssh/config.local from .ssh/config.local.example the 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 karabiner requires npm and an existing Karabiner configuration containing Default 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 nix applies the repository's existing lock. It skips Nix installation when already present and only replaces cache settings when their contents change.
  • dot update nix prepares 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/bin takes 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 dot uses 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 tmux links 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-ghostty terminfo 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 nix selects the macOS or Linux host configuration and enables its daily update.

Nix setup and updates

dot setup nix performs these steps in order:

  1. 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/installer through 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.
  2. Install the tracked nix/cache.conf as 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.
  3. Link ~/.local/bin/dotfiles-nix-update to this checkout's shared updater. Apply the already locked Home Manager configuration for the host.
  4. 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.ts is executed via tsx, 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.