My dotfiles for CachyOS and macOS, managed by mise. Most
configuration lives under ~/.config.
These are my dotfiles, so don't expect everything to work out of the box on your machine. There's a lot of cool stuff here (in my opinion), so feel free to clone, fork, and edit to your heart's content.
| Category | Source | Config |
|---|---|---|
| Terminal | Ghostty | config.ghostty |
| Shell | fish | config.fish |
| Plugin Manager | Fisher | fish_plugins |
| Prompt | Starship | starship.toml |
| Multiplexer | herdr | config.toml |
| Editor | Neovim | nvim |
| Tool Manager | mise | config.toml |
| Version Control | Git | config |
| Diff Viewer | delta | delta |
| File Viewer | bat | config |
| Search | ripgrep | ripgreprc |
| TL;DR | tealdeer | config.toml |
| SSH | OpenSSH | config.tmpl, allowed_signers.tmpl |
| Secrets | fnox | config.toml |
| Git Hooks | hk | config.pkl |
| arch btw | fastfetch | config.jsonc |
A few other tools I use:
Bootstrap installs system packages and mise tools, links the configuration, fills in Git and SSH templates, sets fish as the login shell, and starts the local history watcher. Your machine settings select the desktop apps and account setup.
To try the environment without installing it on your machine, use the container instead.
Git and curl. The config-based Git hooks require Git 2.54 or newer.
Install mise to use it directly;
install.sh installs it if needed.
Set this machine's environments in ~/.config/mise/miserc.toml. Keep
~/.config/mise as a regular directory so it can hold your local settings
alongside the shared files.
For my work MacBook:
env = ["desktop", "work"]For my personal CachyOS desktop:
env = ["desktop", "personal", "cachyos"]Headless machines don't need any of these environments. desktop adds graphical
apps, Fastmail, and 1Password: the app, its CLI, and the SSH agent and commit
signing the app provides. desktop can be selected on its own; add personal
or work to configure accounts, secrets, and agent integrations. personal and
work both set up my two 1Password accounts, but choose different defaults for
FNOX_PROFILE and OP_ACCOUNT. They rely on the 1Password app, so select them
only alongside desktop. personal also clones my private homelab repository
to ~/homelab. cachyos adds packages from the CachyOS repositories and
removes some bundled packages that this setup replaces. restricted suits
managed machines with limits on what you can install or sync. Select it on its
own, in place of desktop: on macOS it adds only Ghostty, Obsidian, Zen, and
the Fira Code font to the base packages, and it switches history sync to manual.
This file stays on the machine and applies to mise in other projects too. Use
-E to
choose environments for one command,
such as ./install.sh -E restricted, without changing the saved choice. The
repo's .miserc.toml selects the Linux or macOS configuration automatically.
Shared mise files are linked individually with symlink-each. That leaves
~/.config/mise as a regular directory, with room for your own miserc.toml
and config.local.toml.
Choose one of the methods below. If you have shared history to restore, use
Restore from history before running a normal bootstrap.
You don't need to sign in to 1Password just to apply the dotfiles. For the
personal and work setup tasks, add both accounts to the 1Password desktop
app and unlock it. On Linux, enable Unlock using system authentication in
Settings > Security. Turn on Integrate with 1Password CLI and
Integrate with MCP clients in Settings > Developer. See the
app integration guide.
Bootstrap uses the CLI to request access through the app, which prompts for authorization when needed. If an account isn't available, bootstrap stops with setup instructions. Authentication errors remain visible and stop setup. After sign-in, bootstrap restores the age identity if missing and validates secrets before configuring agent integrations.
For deliberately configured
manual CLI authentication,
bootstrap also accepts accounts added with op account add. Those sessions are
available to secret restoration and validation inside bootstrap; they do not
sign in your calling shell.
On a personal machine, sign in to GitHub first, as in
Restore from history, so bootstrap can clone the
private homelab repository.
Clone and bootstrap with mise:
mise bootstrap \
--from https://github.com/nettlesh/dotfiles.git \
--from-dir "$HOME/.dotfiles"For an existing checkout:
cd ~/.dotfiles
mise trust
mise bootstrapGit email is optional during setup. To set it now or later, run
git_email=you@example.com mise bootstrap --only dotfiles. The dotfiles step
saves a supplied email in mise.local.toml, which Git ignores, and later runs
reuse it. A dry run doesn't save the email.
Clone the repo and run the installer:
git clone https://github.com/nettlesh/dotfiles.git ~/.dotfiles
cd ~/.dotfiles
./install.shIn an interactive terminal, the script prompts for your email unless one is saved. Press Enter to skip it. To supply it directly:
GIT_EMAIL=you@example.com ./install.shThe script passes GIT_EMAIL to mise as git_email, marks the configuration as
trusted, and runs mise bootstrap.
Install Git and mise, then clone the public repo. Restore your history before running the normal setup so the watcher doesn't start a separate history first.
git clone https://github.com/nettlesh/dotfiles.git ~/.dotfiles
cd ~
mise x gh -- gh auth login --hostname github.com --git-protocol https --web
GIT_CONFIG_GLOBAL="$HOME/.gitconfig" \
GH_PATH="$HOME/.local/share/mise/shims/gh" \
mise x gh -- gh auth setup-git --hostname github.com
mise bootstrap \
--adopt https://github.com/YOUR-USER/dotfiles-history.git --only dotfilesGH_PATH makes Git's credential helper call gh through its mise shim rather
than one installed version's path, so it keeps working after gh updates.
This uses mise's history adoption workflow to restore tracked files to their original paths under your home directory. Review any reported conflicts before continuing.
Choose the machine settings, then bootstrap from the restored checkout:
cd ~/.dotfiles
mise trust
mise bootstrapOr use ./install.sh for the email prompt. This repo tracks the source of the
global mise configuration at ~/.dotfiles/mise/config.toml, rather than its
link under ~/.config/mise. Running setup from the checkout creates that link
and installs the tools and services.
If the machine already has its own history, inspect it before adopting another. mise doesn't automatically merge unrelated histories; keep the existing history and follow its recovery guidance.
From ~/.dotfiles, preview changes or apply only dotfiles:
mise bootstrap --dry-run
mise bootstrap --only dotfilesThe installer accepts the same options:
./install.sh --dry-run
./install.sh --only dotfilesThe installer's dry run still installs mise if needed and trusts the
configuration before passing --dry-run to bootstrap.
If existing files conflict with this setup, mise reports them and stops. Review
those paths before allowing replacements with --force-dotfiles. See the
bootstrap reference for the available
options.
Check for missing setup:
mise bootstrap status --missing
mise bootstrap dotfiles status --missingYour saved environments apply to these commands too.
Run the Arch Linux dotfiles image without installing the dotfiles on your host.
You'll need Docker. The image targets linux/amd64; ARM hosts need x86-64
emulation.
The image installs system packages from mise.linux.toml and tools from the
root and global mise configurations and their lockfiles. Tools are installed
under the sebastian user's home directory. Mount project directories rather
than replacing the home directory, which would hide the installed tools and
dotfiles.
The container starts fish as an ordinary user. It includes the configured Docker packages but does not start Docker, containerd, or the mise history watcher. No desktop, personal, work, or CachyOS environment is selected.
The release workflow publishes versioned images
to ghcr.io/nettlesh/dotfiles. Once an image is published, replace VERSION
with its release version:
docker pull --platform linux/amd64 ghcr.io/nettlesh/dotfiles:VERSION
docker run --platform linux/amd64 --rm -it ghcr.io/nettlesh/dotfiles:VERSIONFrom a checkout of this repo:
docker build --platform linux/amd64 -t dotfiles .
docker run --platform linux/amd64 --rm -it dotfilesThe image has no Git email configured. To create commits, supply your email when starting either the local or GHCR image:
docker run --platform linux/amd64 --rm -it \
-e GIT_AUTHOR_EMAIL=you@example.com \
-e GIT_COMMITTER_EMAIL=you@example.com \
dotfilesThe history watcher saves edits to the tracked files under ~/.dotfiles,
including changes you haven't committed to the public repo. These saves, called
checkpoints, live in a separate Git history. Full setup starts the watcher after
installing the tools it needs.
You can keep that history local or share it through your own private repository. Normal setup doesn't connect one for you.
History tracks shared setup inputs: the root mise layers, application
configuration bundles, scripts, templates, dependency manifests, and their
lockfiles. Complete application bundles include their supporting configuration,
such as nvim/.stylua.toml. Vendored integration scripts are inputs too:
bootstrap deploys the saved copies. Rendered files, installed plugins, caches,
credentials, and machine-local settings stay outside history. This follows
mise's
source and template tracking workflow.
Keep each lockfile with the configuration that owns its tool requests:
mise.lock for the root project and mise/mise.lock for the global tools. A
shared environment that declares tools needs its corresponding lockfile in Git
and history; a layer containing only system packages does not. Local lockfiles
stay ignored alongside their local configuration. See mise's
lockfile reference.
Repository-wide checks, agent instructions, documentation, and build and release files stay in ordinary Git. History supplements the public checkout, so restoring from history starts by cloning that repository.
After setup, sign in to GitHub and configure its Git credential helper:
mise x gh -- gh auth login --hostname github.com --git-protocol https --web
GIT_CONFIG_GLOBAL="$HOME/.gitconfig" \
GH_PATH="$HOME/.local/share/mise/shims/gh" \
mise x gh -- gh auth setup-git --hostname github.comReview tracked files and saved versions:
mise bootstrap dotfiles paths
mise bootstrap dotfiles history show --filesCreate an empty private repository, then connect it using your own URL:
mise bootstrap dotfiles origin set \
https://github.com/YOUR-USER/dotfiles-history.git --sync sync
mise bootstrap dotfiles statusReview earlier checkpoints as well as the current files: all saved versions are shared. The watcher will push your saves and apply changes from your other machines automatically. The repository connection and Git credentials stay local to this machine. See the mise history guide for manual syncing and conflict recovery.
Incoming changes can edit files in your public checkout. Review and commit them
to the public repo when you're ready. History sync doesn't commit or push that
repo, install packages, or regenerate files from templates. Run mise bootstrap
from ~/.dotfiles when a restored configuration needs those steps, or
mise bootstrap dotfiles apply for dotfiles alone.
Your local Git email, account credentials, and private keys aren't included in this history. Set those up separately on each machine.
Most shared files are symlinked, so editing them changes the source in this repo.
The Neovim configuration is inspired by
MiniMax, with mini.nvim providing most
editing features and Neovim's built-in vim.pack managing plugins.
init.lua loads options, theme settings, and handwritten keymaps from lua/.
The numbered files in plugin/ configure plugins in startup order. Edit
theme.lua for colors and popup styling. Edit
keymaps.lua for mappings and their descriptions. Space
is the leader key; pause after it to see the available groups.
Language servers and formatters are managed outside Neovim with mise. Conform formats on save, using StyLua for Lua. It falls back to language-server formatting for everything else.
For shared Git settings, edit git/config or use
git config --file ~/.dotfiles/git/config .... git config --global can write
to the separate ~/.gitconfig, where the GitHub credential helper is
configured.
Your Git identity is generated at ~/.config/git/identity from a template and
your local email. If no email is supplied, the template omits it. Git's
user.useConfigOnly
setting prevents Git from guessing an email when creating commits; configure one
or supply GIT_AUTHOR_EMAIL and GIT_COMMITTER_EMAIL as needed. Change those
inputs instead of copying the generated file into the repo. Desktop signing
settings live in ~/.config/git/1password; keep signing enabled only in that
include. The SSH allowed-signers entry is generated only when an email is
configured.
delta's settings and the cuttlefish theme live in git/delta, included from
git/config. Its colours are ANSI names, so they follow the terminal palette.
fish adds the system tool directories to PATH before mise activation and for
non-interactive shells. mise declares the same directories for environments it
builds. Minimal zsh and bash configurations load the mise environment for AI
coding agents, which run commands in zsh or bash rather than fish. Codex keeps
the exports from its shell snapshot. Claude Code keeps only aliases, functions,
and options, so a SessionStart hook persists the exports from ~/.bashrc
through CLAUDE_ENV_FILE.
Shared agent skills live in agents/skills and are linked into the Claude Code
and Codex skill directories.
The personal and work setup tasks give Claude Code and Codex the same setup:
the hk, mise, and fnox MCP servers, plus the Cloudflare and 1Password plugins.
The 1Password plugin connects to the app's
local MCP server. Codex
installs it from 1Password's GitHub marketplace, because the plugin directory's
copy leaves out the MCP server. restricted registers only the hk and mise
servers. Claude Code records the plugins it installs in claude/settings.json,
so commit those changes.
Repository-local skills live in .agents/skills. The
adding-tools skill covers evaluating
tool adoption or replacement and integrating selected tools into these dotfiles.
Claude Code shares it through .claude/skills/adding-tools, a relative symlink
to the same skill directory.
mise links each tool's skills into .agents/skills, and .gitignore keeps
those links out of version control, so a new handwritten skill needs its own
exception there. After changing hk versions, run mise skills sync --global to
refresh the skills in ~/.agents/skills. See mise's
Packslip skills documentation.
The herdr integration scripts and registrations are kept in this repo; setup doesn't run the herdr integration installer.
For source checks, mise run check runs the linters and mise run fix applies
their automatic fixes. Install the project tools with mise install --locked
before running checks. The tool requests live in mise.toml, with versions and
checksums in mise.lock.
| Tool | Purpose | Configuration |
|---|---|---|
| jactionlint | Workflow syntax and expressions | Builtins.jactionlint in hk.pkl |
| zizmor | GitHub Actions security | Builtins.zizmor in hk.pkl |
| Hadolint | Dockerfile linting | Builtins.hadolint in hk.pkl |
| Betterleaks | Secrets in source files and Git history | Builtins.betterleaks in hk.pkl; history scan in CI |
| Trivy | Image vulnerabilities, secrets, and configuration | .github/actions/verify-image/action.yml |
.betterleaks.toml holds the exceptions for 1Password package declarations. CI
points HK_CONFIG_DIR at the repository's hk directory so the shared hygiene
checks run without deploying the dotfiles first.
CI checks source files and Git history and verifies
Linux and macOS bootstrap without optional environments. Pushes to main also
build and scan the dotfiles image. Pull requests skip that slower job, so a
Dockerfile change is first built after it merges.
Releases run after all CI jobs pass on main. The release job tests and scans
its image before publishing it. Docker reuses the tested build's cache and
attaches the SBOM to the image.
