Public dotfiles for fish, git, jed, VS Code, and various other tools.
.dotfiles/
├── bin/ # Shell scripts added directly to $PATH
├── fish/
│ ├── config.fish # Main fish config; loads .private overlay if present
│ ├── conf.d/ # Fish startup snippets
│ ├── completions/ # Fish completions
│ └── functions/ # Fish functions (autoloaded)
├── home/ # Dotfiles symlinked into $HOME by update
├── etc/ # Tool-specific config (git templates, VS Code, etc.)
├── skills/ # Agent skill definitions
├── tests/ # TAP tests for bin/ scripts
└── update # Idempotent install script
update symlinks home/.* into $HOME, installs packages, and wires up
tool-specific config. It is safe to run multiple times.
SECURITY NOTE: The .dotfiles repository is public. To prevent the leakage
of sensitive information (such as API keys, corporate tool configurations, or
internal pathnames), two private companion repositories can optionally be
cloned: ~/.private and ~/.corp.
This architecture is specifically designed to help both humans and AI agents understand what goes where and ensures private information remains strictly out of the public repository. This repository works fine without them.
| Repo | Intended for |
|---|---|
~/.dotfiles |
Public config, tools, and scripts |
~/.private |
Personal secrets, API keys, private tooling |
~/.corp |
Work machine config, internal tooling |
update processes overlays in this order: .dotfiles → .private → .corp.
Later layers win on conflict. A file in ~/.corp always beats the same path in
~/.private or ~/.dotfiles.
Two functions handle overlay merging, with different semantics:
-
link_overlay_path rel dst— symlinksdstto the single highest-priority source that providesrel. Used for whole-file configs where only one version makes sense (e.g.etc/starship/starship.toml). -
link_overlay_files rel dst— iterates every overlay in order and symlinks each file it finds underrel/intodst/. All overlays contribute; later layers win on filename collision. Used when multiple overlays may each add files to a directory (e.g.etc/code/).
home/ dotfiles are linked by iterating all overlays in order with ln -sf, so
the last overlay to provide a given filename wins.
| Path | Mechanism | Notes |
|---|---|---|
home/.* |
link_home_dotfiles (all overlays) |
Last overlay wins per filename |
etc/agents/AGENTS.md |
overlay_path (highest wins) |
Symlinked to ~/.codex/, ~/.gemini/, ~/.claude/ |
etc/shpool/config.toml |
link_overlay_path |
|
etc/starship/starship.toml |
link_overlay_path |
|
etc/ghostty/config |
link_overlay_path |
|
etc/code/ |
link_overlay_files |
VS Code settings.json, keybindings.json, etc. |
etc/macos/KeyBindings |
overlay_path |
rsync'd (not symlinked) due to macOS bug |
etc/macos/Solarized.clr |
overlay_path |
Copied to ~/Library/Colors/ |
skills/*/ |
all overlays | Each skill dir linked into ~/.agents/skills/ and ~/.claude/skills/; later overlays shadow by name |
After the main install, update runs ~/.private/update and ~/.corp/update
if they exist and are executable. These hooks handle overlay-specific setup that
cannot be expressed as file overlays (package installs, auth setup, etc.).
.private/ # same structure for .corp
├── fish/
│ ├── conf.d/ # Startup snippets sourced by config.fish
│ ├── completions/ # Completions prepended to fish_complete_path
│ ├── functions/ # Functions prepended to fish_function_path
│ └── secrets.fish # API keys and tokens (chmod 600)
├── home/ # Dotfiles symlinked into $HOME (e.g. .gitconfig.local)
├── etc/ # Tool-specific config (see overlay-aware paths above)
├── skills/ # Agent skills that shadow ~/.dotfiles/skills/ by name
└── update # Optional hook run at end of ./update
fish/config.fish prepends ~/.private/fish/functions (and .corp
equivalents) and ~/.private/fish/completions to the fish search paths, and
sources any ~/.private/fish/conf.d/*.fish snippets at shell startup.
To install: clone your private repos to ~/.private and/or ~/.corp, then run
./update again.
Secrets (API keys, tokens) live in ~/.private/fish/secrets.fish and are not
sourced in full at shell startup. Three fish functions provide on-demand access:
| Function | Usage | Description |
|---|---|---|
setsecret |
setsecret NAME [NAME...] |
Load secret(s) into the current shell |
getsecret |
getsecret NAME |
Print secret value(s) to stdout (array-safe) |
envsecret |
envsecret NAME [...] [--] CMD |
Run a command with secret(s) injected |
envsecret is the safest option for scripts: secrets are injected into the
child process and never leak into the calling shell.
A ~/.private/fish/conf.d/ snippet uses setsecret --if-unset to auto-load a
small set of everyday secrets at shell startup.
Run setsecret --help, getsecret --help, or envsecret --help for details.
direnv handles per-project environment switching,
configured via ~/.direnvrc (symlinked from home/.direnvrc). To activate it
in a project, create an .envrc file in the project root.
Node.js versions are managed by bin/node-install and direnv.
node-install 22 # installs latest 22.x into ~/.local/share/node/versionsAdd to .envrc:
use node 22
layout node # adds node_modules/.bin to PATHPython environments use uv. Add to .envrc:
layout uv # creates .venv if absent, activates itcd $HOME
git clone https://github.com/ithinkihaveacat/dotfiles.git .dotfiles
cd .dotfiles
git remote set-url origin --push git@github.com:ithinkihaveacat/dotfiles.git
./updateAfter cloning ~/.private and/or ~/.corp (if available), run ./update again
so the overlay is applied.
Note:
updatemay overwrite unmanaged files in locations such as~/Library/KeyBindings. It is otherwise safe to run multiple times.
- Ubuntu/Debian:
sudo apt-get install git - macOS:
xcode-select --install
- Ubuntu/Debian:
sudo apt-get install fish - macOS:
brew install fish - Other: https://fishshell.com/
Standard apt packages lag significantly (Ubuntu 24.04: 3.7.0, Debian bookworm/Raspberry Pi OS: 3.6.0, Debian trixie: 4.0.2). For fish 4.2+, install from the OpenSUSE Build Service:
curl -fsSL https://download.opensuse.org/repositories/shells:fish:release:4/Debian_Unstable/Release.key | \ gpg --dearmor | sudo tee /usr/share/keyrings/fish-shell.gpg > /dev/null echo 'deb [signed-by=/usr/share/keyrings/fish-shell.gpg] https://download.opensuse.org/repositories/shells:/fish:/release:/4/Debian_Unstable/ /' | \ sudo tee /etc/apt/sources.list.d/fish-shell.list sudo apt update && sudo apt install -y fish
- Terminal: Import
etc/macos/Solarized Dark.terminaland set it as the default profile. - Keyboard: System Preferences > Keyboard > Shortcuts > Services > File and Folders: enable "New Terminal at Folder".
- Text Replacements: If not shared via iCloud, restore from
etc/macos/Text Replacements.plist— see Back up and share text replacements on Mac. - Lock Screen: Add to Menu Bar via Keychain Access preferences.
- Volume: Add to Menu Bar via Control Center > Sound > Always Show in Menu Bar.
- Time Machine: Disable local snapshots:
sudo tmutil disablelocal - Hot Corners: Disable via System Preferences.
- Fonts: See https://typography.guru/journal/awesome-catalina-fonts/.
- iA Writer theme: https://ia.net/writer/templates/
- Network Link Conditioner (for simulating degraded network conditions): https://developer.apple.com/download/more/?q=Additional%20Tools
# Emacs keybindings across GTK apps
gsettings set org.gnome.desktop.interface gtk-key-theme "Emacs"Compose key: Set to Caps Lock via Settings > Keyboard. Enables e.g. Caps
Lock + --- → em dash.
Complete setup sequence for a fresh Raspberry Pi OS install.
-
Use the Raspberry Pi Imager to write the OS to an SD card or SSD.
- For a headless install: Raspberry Pi OS (other) > Raspberry Pi OS Lite.
- In OS customisation: enable SSH (Remote access section).
- (Optional) Configure Wi-Fi.
-
SSH in:
ssh mjs@lil.local(substitute your username and hostname).- (Optional) If using Ghostty, copy the terminfo from your local machine:
infocmp -x xterm-ghostty | ssh mjs@lil.local -- tic -x -
- (Optional) If using Ghostty, copy the terminfo from your local machine:
-
Update the OS:
sudo apt-get update && sudo apt-get upgrade -yReboot if
/var/run/reboot-requiredexists:sudo reboot -
Install fish (see Prerequisites for the OpenSUSE Build Service instructions to get fish 4.2+, or use the older distro version):
sudo apt-get install fish
-
Install git:
sudo apt-get install git -
Install Tailscale:
curl -fsSL https://tailscale.com/install.sh | shthensudo tailscale up -
(Optional) Install Node.js and npm:
sudo apt-get install nodejs npm(for newer versions see Node.js). -
(Optional) Disable WiFi and Bluetooth: add these lines under
[all]in/boot/firmware/config.txt:dtoverlay=disable-wifi dtoverlay=disable-bt -
(Optional) Change hostname: edit
/etc/hostnameand/etc/hosts. -
Reboot after any optional steps above:
sudo reboot
Once complete, proceed to Installation.
Tip: Use VS Code's Remote - SSH extension to edit files on the Pi.
Put manually installed binaries in ~/.local/bin.