Personal dev environment managed with GNU Stow. One command to bootstrap a fresh Mac or Linux machine.
git clone <repo-url> ~/.dotfiles
cd ~/.dotfiles
chmod +x install.sh
./install.shThe install script:
- Installs packages via Homebrew (macOS) or apt (Linux)
- Uses
stow --restowto symlink config packages into$HOME - Installs Oh My Zsh + zsh-vi-mode plugin
- Installs TPM and tmux plugins
- Creates
~/.zshrc.localand~/.gitconfig-localtemplates for machine-specific settings - Sets zsh as default shell
| Directory | Stowed? | Description |
|---|---|---|
nvim/ |
Yes | Neovim config → ~/.config/nvim |
tmux/ |
Yes | tmux config → ~/.tmux.conf |
zsh/ |
Yes | Zsh config → ~/.zshrc |
git/ |
Yes | Git config → ~/.gitconfig, ~/.gitignore_global |
claude/ |
Yes | Claude Code config → ~/.claude/ (settings, skills, MCP servers) |
ssh/ |
Yes | SSH drop-in → ~/.ssh/config.d/dev.conf (remote dev VPS) |
caddy/ |
No | Caddy reverse-proxy config (deployed to the VPS by vps-tunnel.sh) |
cloudflared/ |
No | Cloudflare Tunnel ingress template (real config generated on the VPS) |
iterm2/ |
No | iTerm2 color scheme (imported during install) |
scripts/ |
No | OS-specific install + vps-harden.sh + vps-tunnel.sh |
| Plugin | Purpose |
|---|---|
| tokyonight.nvim | Color scheme |
| telescope.nvim | Fuzzy finder (files, grep, buffers) |
| nvim-treesitter | Syntax highlighting and code analysis |
| nvim-lspconfig + mason.nvim | LSP support with auto-installed servers |
| lsp-zero.nvim | Simplified LSP configuration |
| nvim-cmp | Autocompletion (LSP, buffer, path, snippets) |
| LuaSnip + friendly-snippets | Snippet engine and library |
| conform.nvim | Formatting (prettier, stylua, ruff, djlint, xmlformat) |
| nvim-lint | Linting (eslint_d) |
| nvim-ufo | Code folding |
| nvim-autopairs | Auto-close brackets/quotes |
| nvim-comment | Toggle comments |
| vim-tmux-navigator | Seamless nav between vim splits and tmux panes |
ts_ls, html, cssls, tailwindcss, svelte, lua_ls, graphql, emmet_ls, prismals, pyright
Required env vars (API keys, tokens, etc.) are listed in env.example. After cloning:
make env # scaffolds missing vars into ~/.zshrc.local
vim ~/.zshrc.local # fill in values
source ~/.zshrc.localFiles not tracked in git — created as templates on first install:
~/.zshrc.local— env vars, API keys, local PATHs (Pulumi, LM Studio, etc.)~/.gitconfig-local—includeIfblocks for work repos
The install script is idempotent — safe to run again at any time:
cd ~/.dotfiles && ./install.shDevelop on a cloud VPS over SSH — nvim, tmux and Claude Code run on the box; your
laptop is just a terminal + a window onto the VPS's localhost. Designed to work
from restrictive/travel networks (no Tailscale, no UDP) by riding SSH on 443.
Full step-by-step runbook (with verify gates + lockout rescue): docs/VPS-SETUP.md. The quick version:
On the VPS (fresh Ubuntu 24.04, amd64):
git clone <repo-url> ~/.dotfiles && cd ~/.dotfiles && ./install.sh # installs et
sudo ./scripts/vps-harden.sh # sshd on 443 + et serverOn your Mac: fill in ~/.ssh/config.local (HostName + User for Host dev),
then:
| Command | What it does |
|---|---|
dev |
Connect to the VPS over Eternal Terminal (survives drops/roaming; falls back to ssh). Run tmux / ts <proj> inside it. |
dev-forward |
Auto-forwarder — watches the VPS and forwards every new listening dev port (3000–9999) to your Mac's localhost. The nvim equivalent of VS Code's port forwarding. Leave it running in a spare tab. |
fwd 3000 / unfwd 3000 |
Manually forward/drop a single port into the live connection (no reconnect). |
So npm run dev or docker compose up on the VPS shows up at http://localhost:3000
on your Mac, exactly like local. The shared dev SSH options (Port 443,
ControlMaster, keepalives) live in ssh/.ssh/config.d/dev.conf; only your VPS's
host/user go in the untracked ~/.ssh/config.local.
Secondary to the SSH path — for the things localhost can't do: webhook callbacks (Stripe/Twilio/Meta), testing on your phone, OAuth redirects, sharing. A Cloudflare named tunnel (outbound-only — no inbound ports on the VPS) fronts a Caddy reverse proxy that routes subdomains to local ports.
sender / phone / client ──https──► Cloudflare ──tunnel──► cloudflared ──► Caddy :8080 ──► localhost:3000
Setup (on the VPS, needs a domain on Cloudflare):
./scripts/vps-tunnel.sh your-zone.com # installs caddy + cloudflared, creates tunnel, wires DNS
# then add one Cloudflare Access app for *.dev.your-zone.com (the script prints exact steps)Using it:
| URL | Routes to | Notes |
|---|---|---|
https://p3000.dev.zone |
localhost:3000 |
Any port, zero config — just open p<port>.dev.zone. |
https://name.dev.zone |
a named port | Add pretty routes in /etc/caddy/named.caddy → sudo systemctl reload caddy. |
https://hooks.zone |
webhook target | Not behind Access so Stripe/Twilio/Meta reach it — verify signatures in-app. |
share 3000 (on VPS) |
ephemeral *.trycloudflare.com |
No domain/config; URL changes each run. One-off shares only. |
Access locks *.dev.zone to your email (Cloudflare Zero Trust, free), so dev
apps aren't open to the world. hooks.zone stays public by design. From inside
Syria these Cloudflare-fronted URLs may be flaky — but they're mostly hit by
external senders/devices, and your own previewing uses the SSH-localhost path.