Configuration files managed with GNU Stow.
git clone <repo-url> ~/dotfiles
cd ~/dotfiles
./install.sh- Edit
config.jsonto toggle steps or change terminal - Use
./install.sh -ito confirm each step interactively - Use
./install.sh --just lazyvimfor a minimal install (only what lazyvim needs)
./install.sh [options]| Argument | Description |
|---|---|
-i, --interactive |
Confirm each step before running |
--check |
Validate config and show plan without making changes |
--just PKG [PKG ...] |
Only stow specified packages + dependencies + base. Skips unrelated pipeline steps. |
Examples:
# Full install (default)
./install.sh
# Confirm each step
./install.sh -i
# Minimal: only stow lazyvim + its deps (opencode, fonts, runtimes)
./install.sh --just lazyvim
# Single terminal package
./install.sh --just ghostty
# Dry-run check (validate config, no changes)
./install.sh --check./uninstall.sh [options]| Argument | Description |
|---|---|
-f, --force |
Skip confirmation prompt |
-i, --interactive |
Confirm each step before running |
--just PKG [PKG ...] |
Only unstow specified packages + restore their backups |
Example:
./uninstall.sh -fmacOS
| Component | Notes |
|---|---|
| macOS 14+ (Sonoma) | Recommended, but 13+ should work |
| Xcode Command Line Tools | Installed automatically if missing by install.sh |
| Homebrew | Installed automatically if missing by install.sh |
The script (1_setup.sh) handles everything: Xcode CLT → Homebrew → python@3 curl unzip stow.
No manual steps needed — just clone and run.
Linux (apt / pacman / dnf)
| Component | Notes |
|---|---|
git |
To clone the repo (usually pre-installed) |
curl or wget |
For downloading release assets |
sudo |
Required for apt/pacman/dnf during bootstrap |
Everything else (python3 curl unzip stow) is installed automatically by install.sh via your distro's package manager.
Distros tested: Ubuntu 24.04 (apt), Arch (pacman), Fedora (dnf). Others may work but are untested.
Windows (WSL2)
Since install.sh is a bash script, it needs a Linux environment. The supported path is WSL2 + Ubuntu 24.04 + Windows Terminal.
Copy-paste the entire block below into PowerShell as Administrator. It handles all Windows prerequisites and prints the final commands to clone and install inside WSL.
#Requires -RunAsAdministrator
$ErrorActionPreference = "Stop"
Write-Host "=== Setting up WSL2 + Windows Terminal for dotfiles ===" -ForegroundColor Cyan
Write-Host "(Safe to re-run — completed steps are skipped)" -ForegroundColor Gray
Write-Host ""
# --- 1. Enable WSL + VM features ---
Write-Host "[1/4] Enabling Windows Subsystem for Linux..." -ForegroundColor Yellow
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
# --- 2. Check if restart is needed (dism returns 3010) ---
Write-Host "[2/4] Checking if restart is required..." -ForegroundColor Yellow
if ($LASTEXITCODE -eq 3010 -or (Test-Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations")) {
Write-Host " -> Restart required. Reboot now, then re-run this script." -ForegroundColor Red
Write-Host " Already completed steps will be skipped automatically." -ForegroundColor Yellow
pause
exit
}
# --- 3. Install Ubuntu 24.04 ---
$ubuntuInstalled = wsl -l -v 2>$null | Select-String -Pattern "Ubuntu-24.04" -Quiet
if (-not $ubuntuInstalled) {
Write-Host "[3/4] Setting WSL2 as default and installing Ubuntu 24.04..." -ForegroundColor Yellow
wsl --set-default-version 2
wsl --install -d Ubuntu-24.04
Write-Host ""
Write-Host "=== Ubuntu was installed. It will launch automatically to finish setup. ===" -ForegroundColor Cyan
Write-Host "Create your Linux user/password when prompted, then close the Ubuntu window." -ForegroundColor Yellow
pause
}
else {
Write-Host "[3/4] Ubuntu 24.04 already installed — skipping." -ForegroundColor Green
}
# --- 4. Install Windows Terminal ---
if (-not (Get-Command wt.exe -ErrorAction SilentlyContinue)) {
Write-Host "[4/4] Installing Windows Terminal..." -ForegroundColor Yellow
if (Get-Command winget -ErrorAction SilentlyContinue) {
winget install --id Microsoft.WindowsTerminal --accept-source-agreements
} else {
Write-Host " -> winget not found. Install from: https://apps.microsoft.com/detail/9n0dx20hk701" -ForegroundColor Yellow
}
}
else {
Write-Host "[4/4] Windows Terminal already installed — skipping." -ForegroundColor Green
}
Write-Host ""
Write-Host "=== All done! ===" -ForegroundColor Green
Write-Host ""
Write-Host "Now open Windows Terminal, select 'Ubuntu-24.04' from the tab dropdown, and run:" -ForegroundColor Cyan
Write-Host ""
Write-Host ' git clone https://github.com/Sarctiann/dotfiles.git ~/dotfiles' -ForegroundColor White
Write-Host ' cd ~/dotfiles' -ForegroundColor White
Write-Host ' ./install.sh' -ForegroundColor White
Write-Host ""If you prefer to do it manually instead, each step is detailed below.
# Enable WSL feature (if `wsl` command is not found)
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
# Restart, then:
wsl --set-default-version 2
wsl --install -d Ubuntu-24.04winget install --id Microsoft.WindowsTerminal --accept-source-agreementswsl -d Ubuntu-24.04
git clone <repo-url> ~/dotfiles
cd ~/dotfiles
./install.shThe installer detects WSL, uses apt for bootstrap packages, and sets up Windows Terminal config via post_install.py.
GitHub API rate limit: The installer hits the GitHub API ~15 times for release assets. Set
GITHUB_PERSONAL_ACCESS_TOKENin your environment to raise the limit from 60/h to 5000/h:export GITHUB_PERSONAL_ACCESS_TOKEN=$(gh auth token) # if gh is authenticatedFalls back to
GITHUB_TOKENorGH_TOKENfor compatibility.
┌─────────────────────────────────────────┐
│ Terminal: Ghostty (macOS/Linux) │
│ Windows Terminal (WSL) │
├─────────────────────────────────────────┤
│ Multiplexer: tmux + TPM │
├─────────────────────────────────────────┤
│ Shell: zsh + custom plugins │
├─────────────────────────────────────────┤
│ Editor: Neovim + LazyVim │
│ (LSPs, formatters via Mason) │
├─────────────────────────────────────────┤
│ Tools: ripgrep, fd, bat, fzf, │
│ lazygit, lazydocker, lazysql, │
│ yazi, gh, uv, bun, rust, nvm, │
│ auggie, gemini-cli │
├─────────────────────────────────────────┤
│ AI: OpenCode + custom agents, │
│ Augment (auggie) user-level + │
│ work-profile (neovim-deployed), │
│ Gemini CLI (stow-managed), │
│ Neovim MCP integration across all │
│ three clients │
└─────────────────────────────────────────┘
| OS | Terminal | Config flag |
|---|---|---|
| macOS/Linux | Ghostty | stow.ghostty_or_windowsTerminal |
| WSL | Windows Terminal | stow.ghostty_or_windowsTerminal |
| macOS/Linux | Alacritty | stow.alacritty |
| macOS/Linux | Wezterm | stow.wezterm |
Multiple terminals can be enabled at once. The terminal font is set via terminal_font (default: CodeNewRoman), which also drives the Nerd Font installation. The full font name is assembled during install: {base} Nerd Font Propo for most terminals, {base} Nerd Font for Ghostty.
Shell (zsh), editor (lazyvim), multiplexer (tmux), and Git are shared across all three.
dotfiles/
├── install.sh ← entry point (delegates to tools_management/1_setup.sh)
├── uninstall.sh ← entry point (delegates to tools_management/1_uninstall.sh)
├── stow-packages/ ← stow packages (one per program)
│ ├── lazyvim/ ← LazyVim config + plugins + opencode-neovim integration
│ ├── zsh/ ← .zshrc, aliases, env
│ ├── tmux/ ← .tmux.conf + TPM
│ ├── ghostty/ ← Ghostty config + GLSL shaders
│ ├── alacritty/ ← Alacritty config
│ ├── wezterm/ ← Wezterm config
│ ├── windows-terminal/ ← Windows Terminal settings.json (WSL)
│ ├── opencode/ ← OpenCode config (agents, skills, themes, quota, notifier)
│ ├── augment/ ← Augment user-level config (AGENTS.md, skills)
│ ├── gemini/ ← Gemini CLI config (policies, custom-skills)
│ ├── bat/ ← bat theme (.config/bat/config)
│ ├── lazygit/ ← lazygit config with tokyonight theme
│ ├── lazydocker/ ← lazydocker config with tokyonight theme
│ ├── yazi/ ← Yazi config (preview, tasks)
│ ├── local-bin/ ← local scripts (includes sync_git_config.py)
│ ├── mojo/ ← Mojo + Pixi config
│ └── ...
├── testing/ ← container-based test suite
│ ├── test_pipeline.py ← CLI orchestrator (linux/wsl/mac)
│ ├── test_containers/ ← Dockerfiles + pipeline script
│ └── TESTING.md ← testing documentation
├── tools_management/ ← two-stage bootstrap + uninstall
│ ├── 1_setup.sh ← Stage 1: bash bootstrap (python3 + base pkgs)
│ ├── 1_uninstall.sh ← Stage 1: bash bootstrap (python3)
│ ├── 2_management.py ← Stage 2: Python orchestrator (install/uninstall)
│ ├── config.json ← declarative config (what to install)
│ ├── config.py ← config loader
│ ├── core.py ← OS/arch/WSL detection + utilities
│ ├── system_packages.py ← Homebrew/apt/pacman/dnf
│ ├── gh_releases.py ← GitHub release binary installer
│ ├── fonts.py ← Nerd Font installer
│ ├── stow.py ← symlink management
│ ├── manifest.py ← tracks installed vs pre-existing for safe uninstall
│ ├── cli_tools.py ← CLI tools aggregator
│ ├── npm_packages.py ← npm global packages (auggie, gemini-cli)
│ ├── runtimes.py ← nvm/bun/rust/opencode/uv
│ ├── post_install.py ← TPM, Windows Terminal, font overrides
│ └── verify.py ← post-install verification
└── README.md
The install runs in two stages:
Stage 1 (tools_management/1_setup.sh) — minimal bash bootstrap:
- macOS: Xcode Command Line Tools → Homebrew →
brew install python@3 curl unzip stow - Linux/WSL: ensures
python3 curl unzip stowvia apt/pacman/dnf
Stage 1 handles the absolute minimum to get Python running. stow, curl, and unzip are installed here and not duplicated in Stage 2. 3. Hands off to Stage 2
Stage 2 (tools_management/2_management.py) — Python orchestrator:
- System packages — brew/apt/pacman packages (tmux, git)
- CLI tools — neovim, ripgrep, fd, bat, lazygit, lazydocker, lazysql, gh, fzf, yazi
- Fonts — installs the Nerd Font specified by
terminal_fontinconfig.json - Stow — creates symlinks for all stow packages
- Git config — prompts for identity vars, generates
~/.gitconfigviasync_git_config.py - Runtimes — nvm + Node LTS, Bun, Rust (rustup), OpenCode, uv
- NPM packages — auggie, gemini-cli (via bun, fallback npm)
- Post-install — TPM, Windows Terminal sync (WSL), terminal font overrides
- Verify — checks essential commands are in PATH
All steps can be toggled on/off via config.json. Run with -i for interactive mode (confirms each step before proceeding). Use --just PKG to run only the steps needed by one or more stow packages (skips unrelated phases).
The core of this project. Each folder under stow-packages/ mirrors part of $HOME:
stow-packages/lazyvim/.config/nvim/init.lua → $HOME/.config/nvim/init.lua
stow-packages/zsh/.zshrc → $HOME/.zshrc
When stow runs, it creates symlinks from $HOME into the repo. Removing the symlink later is safe — the repo is the source of truth.
Packages can declare dependencies in config.json → stow.deps. When you use --just PKG, the system computes a transitive closure:
--just ghostty → ghostty + tmux + zsh + lazyvim + opencode + mojo + local-bin
The resolver handles circular deps (lazyvim ↔ opencode) gracefully.
Configure which terminals to stow via boolean flags in config.json → stow:
| Flag | true (default) → |
|---|---|
ghostty_or_windowsTerminal |
ghostty (Linux/macOS) or Windows Terminal (WSL) |
alacritty |
stow alacritty |
wezterm |
stow wezterm |
Multiple terminals can be active at once. The terminal font (terminal_font at the top level of config.json, e.g. CodeNewRoman) is written to local override files during post-install (ghostty: local_config → ~/.config/ghostty/local_config, alacritty: local.toml, wezterm: local.lua, Windows Terminal: patched during copy). The full font name is assembled as {base} Nerd Font Propo for most terminals, {base} Nerd Font for Ghostty.
Ghostty uses a two-file config split: config (tracked in git) holds all shared settings, while local_config (gitignored, created by installer) holds machine-specific overrides. The config-file directive in config loads local_config after all shared settings, so overrides take precedence.
stow.base (default: ["local-bin"]) always gets stowed regardless of --just.
Regardless of --just, these steps always execute:
- System packages — tmux, git
- Fonts — Nerd Font from
terminal_font - Stow — symlink creation
| Pipeline step | Needed by |
|---|---|
| CLI tools | lazyvim, bat |
| NPM packages | lazyvim, zsh |
| Runtimes | lazyvim, opencode, zsh |
| Post-install | tmux, ghostty, alacritty, wezterm, windows-terminal |
Before stow overwrites an existing file, stow.py copies it to ~/.local/share/dotfiles/backups/. On uninstall, backups are restored and symlinks removed.
.stowignore contains glob patterns for files stow should never touch:
**/.gitkeep
.DS_Store
**/.playwright-mcp
.config/ghostty/local_config # gitignored, created by installer
-
Create a directory:
stow-packages/my-app/.config/my-app/config -
Add files with paths relative to
$HOME -
If it needs other packages, add deps to
config.json → stow.deps:"my-app": ["lazyvim"]
-
If it needs specific pipeline steps (CLI tools, runtimes, etc.), update
should_skip_step()in2_management.py -
Run
./install.sh— the new package is auto-discovered
# Stow a single package (create symlinks)
stow -R -t $HOME lazyvim
# Remove symlinks for a package
stow -D -t $HOME lazyvim./uninstall.sh # removes symlinks, restores backups, removes tools
./uninstall.sh -f # skip confirmation prompt
./uninstall.sh -i # confirm before each stepWhat gets removed:
- Stow symlinks — deleted, original files restored from backup
- CLI tools — only those installed by the script (not pre-existing ones)
- NPM packages — auggie, gemini-cli (only if not pre-existing)
- Runtimes — nvm, Bun, Rust, OpenCode, uv (only if not pre-existing)
- Fonts — Nerd Font files (derived from
terminal_font) - Post-install — TPM, Windows Terminal symlink (WSL)
System packages (stow, tmux, git via brew/apt) are not removed.
The install/uninstall pipeline is tested inside Docker containers for Linux and WSL, plus a read-only check for macOS.
# Test all platforms (parallel Docker + local mac)
./testing/test_pipeline.py
# Test a single platform
./testing/test_pipeline.py linux
./testing/test_pipeline.py wsl
./testing/test_pipeline.py mac
# Dry-run check directly (no containers needed)
./install.sh --checkSee testing/TESTING.md for details.
- Distribution: LazyVim
- Completion: blink.cmp with emoji support
- Picker: snacks.nvim (dashboard, files, grep)
- AI integration: OpenCode CLI + custom Neovim utils (server management, tunnel, session browser)
- Plugin extras: copilot, formatting (black, prettier), language support (Python, Rust, TypeScript, Zig, Docker, SQL, TOML, YAML, Markdown, Tailwind, Ruby, Mojo, V)
- Custom plugins: cli-integration.nvim, cursor-agent.nvim, blamer.nvim
~/.gitconfig is generated from environment variables by
sync_git_config.py,
which is called automatically during install.sh (the "Git config" step).
Define these in ~/.config/zsh/.credentials (see Credentials docs):
| Variable | Purpose |
|---|---|
GIT_NAME |
Personal [user] name |
GIT_EMAIL |
Personal [user] email |
COMPANY_GIT_NAME |
Work identity (for includeIf) |
COMPANY_GIT_EMAIL |
Work email (for includeIf) |
COMPANY_DIR |
Path under which work identity applies |
To regenerate manually:
source ~/.config/zsh/.credentials
sync_git_config.pytmux-help— show keymaps in shell (runsbaton help file)prefix + H— show keymaps in a new tmux window- Keymaps auto-display on new session creation
| Keys | Action |
|---|---|
Ctrl+Alt+w |
Create window |
Alt+w |
Kill window / session |
Alt+, / Alt+. |
Previous / next window |
Alt+1..9 |
Select window 1-9 |
Ctrl+Alt+s |
Create session |
Alt+s |
Kill current session |
Ctrl+Alt+r |
Rename session |
Alt+Shift+←/→ |
Previous / next session |
Ctrl+b Ctrl+s |
Save session (resurrect) |
Ctrl+b Ctrl+r |
Restore session (resurrect) |
- Config sourced from Sarctiann/opencode-config
- Custom agents: x-teach, z-forge, z-logic, z-nexus, z-pilot, z-spark, z-ultra, x--free
- MCP servers: Playwright, Augment Context Engine, Neovim
- Plugins: opencode-quota (toast + TUI), opencode-notifier, superpowers
- Skills: agent-model-audit, agent-sync-neovim
- Theme: custom tokyonight
- User-level (
~/.augment/):AGENTS.md+ skills stow-managed viastow-packages/augment/ - Work-profile (
<project>/.augment_work_profile/):AGENTS.md+ skills deployed by Neovim fromnvim/lua/utils/augment-work-profile/on everyon_open_auggiehook
- Policy (
~/.gemini/policies/default.md): stow-managed, loaded viagemini --policy ... - Skills (
~/.gemini/custom-skills/): stow-managed viastow-packages/gemini/, linked to~/.gemini/skills/viagemini skills link
The three Neovim MCP skills (using-neovim, using-neovim-lsp, using-quickfix) in
nvim/lua/utils/opencode-neovim/skills/ are shared across all AI clients:
| Client | Location | Method |
|---|---|---|
| OpenCode | .../opencode-neovim/skills/<name>/SKILL.md |
Native (source of truth) |
| Augment (user) | ~/.augment/skills/<name>.md |
Stow symlink |
| Augment (work) | <work-profile>/skills/<name>.md |
Neovim-deployed symlink |
| Gemini | ~/.gemini/custom-skills/<name>/SKILL.md |
Stow symlink (+ gemini skills link) |
Changes to any skill MUST be replicated to all four destinations (opencode source, augment user-level, augment work-profile, gemini). The content is identical except for client-specific details (prerequisites section, connection instructions).
- macOS (Homebrew)
- Linux (apt, pacman, dnf)
- WSL2 (apt)