Configuration files and setup scripts for my development environment. Everything is managed with simple copy scripts — no symlinks, no stow, no risk of losing configs.
Your terminal is your workshop. Keep it sharp.
- One-command setup — Clone the repo and run
install.shto bootstrap a new machine with all dotfiles, packages, Oh My Zsh + plugins, and Node.js. - Simple sync — Run
sync.shto capture your latest system dotfiles into the repo for version control. - Automatic backups — Existing dotfiles are backed up before being overwritten during installation.
- Brewfile — Every Homebrew package, cask, Mac App Store app, and VS Code extension tracked in a single file.
- Secrets management — API keys and tokens stay in
~/.secrets, which is never committed to git.
- Clone the repository:
git clone https://github.com/nathanialhenniges/dotfiles.git \
~/Developer/nathanialhenniges/dotfiles- Run the install script:
cd ~/Developer/nathanialhenniges/dotfiles
./install.sh- Restart your terminal to apply changes.
Sync your current system dotfiles into the repo:
./sync.shReview the changes, then commit and push:
git diff
git add .
git commit -m "Update dotfiles"
git pushInstall dotfiles onto a new machine:
./install.shAdd a new dotfile by editing the files array in sync.sh,
then running ./sync.sh to pull it in. For nested paths under
~/.config/, add a cp command in the nested config section
of the script.
| Layer | Technology |
|---|---|
| Shell | zsh + Oh My Zsh |
| Prompt | Oh My Posh (custom theme) |
| Plugins | fzf-tab, zsh-autosuggestions, zsh-syntax-highlighting |
| Node Manager | fnm |
| Package Manager | Homebrew |
| Terminal | Ghostty (Liquid Glass theme) |
| Editor | Visual Studio Code |
| Git | GPG commit signing via GnuPG |
| Env Management | direnv |
| Languages | Node.js, Go, PHP, Terraform |
| Cloud | AWS CLI, Google Cloud SDK |
| Mobile | Android Studio, Xcode, React Native |
- macOS (Apple Silicon or Intel) or Linux (Debian/Ubuntu)
- Git
- Homebrew (installed automatically by
install.shon macOS)
- Clone the repo:
git clone https://github.com/nathanialhenniges/dotfiles.git \
~/Developer/nathanialhenniges/dotfiles- Run the installer:
cd ~/Developer/nathanialhenniges/dotfiles
./install.sh./sync.sh— Pull dotfiles from your system into the repo and regenerate the Brewfile../install.sh— Install Homebrew (macOS) or apt essentials (Linux), Oh My Zsh + plugins, copy dotfiles, and set up Node.js via fnm../server.sh— Bootstrap a remote Linux server with zsh, Oh My Zsh, and minimal server configs fromconfig/server/../server-dev.sh— Bootstrap a remote Linux dev server: everythingserver.shdoes, plus fnm + Node, Docker, and dev CLIs (gh, direnv, bun, go, build-essential, jq, eza, btop).lib/bootstrap.sh— Shared helper functions sourced byserver.shandserver-dev.sh(base packages, Oh My Zsh, plugins, Oh My Posh, config copy, shell switch)../mini.sh— Minimal bootstrap: Homebrew plus a small package set (fnm, fzf, direnv, gh, Oh My Posh), Oh My Zsh + plugins, and dotfiles excluding the server/sharedhosting/ghostty configs../sharedhosting.sh— Bootstrap a shared hosting environment (no root required, bash-based) with configs fromconfig/sharedhosting/.
dotfiles/
├── config/ # Dotfiles mirroring ~/
│ ├── .zshrc # Zsh configuration
│ ├── .zprofile # Zsh profile (Homebrew init)
│ ├── .p10k.zsh # Powerlevel10k prompt config
│ ├── .profile # Shell profile
│ ├── .aliases # Custom shell aliases
│ ├── .gitconfig # Git user and signing config
│ ├── .npmrc # npm registry config
│ ├── .nuxtrc # Nuxt telemetry settings
│ ├── server/ # Server-only configs
│ │ ├── .zshrc # Minimal server zsh config
│ │ └── .aliases # Server-specific aliases
│ ├── sharedhosting/ # Shared hosting configs (no root)
│ │ ├── .bashrc # Bash config for shared hosts
│ │ └── .aliases # Shared hosting aliases
│ ├── agent/ # AI agent config installed by --agent-setup
│ │ ├── claude/
│ │ │ ├── settings.json # Curated Claude Code settings
│ │ │ └── skills/ # Skills pack copied to ~/.claude/skills/
│ │ └── codex/
│ │ └── config.toml # Linux-safe Codex config
│ ├── scripts/ # Custom scripts copied to ~/.scripts/
│ └── .config/
│ ├── ghostty/
│ │ └── config # Ghostty terminal config (Liquid Glass)
│ └── ohmyposh/
│ └── mrdemonwolf.omp.json # Oh My Posh theme
├── lib/
│ └── bootstrap.sh # Shared server bootstrap helpers
├── docs/
│ └── jira-mcp-setup.md # Atlassian/Jira MCP OAuth setup guide
├── Brewfile # Homebrew packages and casks
├── sync.sh # System -> repo sync script
├── install.sh # Repo -> system install script
├── mini.sh # Minimal bootstrap (fnm, fzf, direnv, gh)
├── server.sh # Remote server bootstrap script
├── server-dev.sh # Remote Linux dev-server bootstrap
├── sharedhosting.sh # Shared hosting bootstrap (no root)
├── .gitignore
└── README.md
Bootstrap a remote Linux server with a clean zsh environment using a single command over SSH:
bash <(curl -fsSL https://raw.githubusercontent.com/nathanialhenniges/dotfiles/main/server.sh)This installs zsh, Oh My Zsh + plugins, and copies a minimal
shell config from config/server/ — no macOS tooling, no
Homebrew, no Node.js. Safe to re-run to pull updated configs.
Bootstrap a remote Linux dev server — everything server.sh
sets up, plus a full development toolchain and semi-lockdown
hardening — with one command over SSH:
bash <(curl -fsSL https://raw.githubusercontent.com/nathanialhenniges/dotfiles/main/server-dev.sh)Optional flags:
# explicit hostname
bash <(curl -fsSL .../server-dev.sh) --hostname devbox
# random wolf-themed hostname (fenrir, luna, timber, sirius, …)
bash <(curl -fsSL .../server-dev.sh) --hostname random
# also install the Claude Code + Codex CLIs (npm globals)
bash <(curl -fsSL .../server-dev.sh) --ai
# also install pnpm
bash <(curl -fsSL .../server-dev.sh) --pnpm
# full agent setup: CLIs + plugins + skills + settings + Jira MCP
bash <(curl -fsSL .../server-dev.sh) --agent-setup
# headless Chrome/Chromium for browser automation + screenshots
bash <(curl -fsSL .../server-dev.sh) --chrome--hostname (omit to leave it unchanged) updates both
hostnamectl and the /etc/hosts 127.0.1.1 line. --ai
installs @anthropic-ai/claude-code and @openai/codex
globally via npm, plus bubblewrap (Codex's Linux sandbox);
--pnpm installs pnpm (both run after Node is set up, so npm is
available). Flags combine, e.g. --hostname random --ai --pnpm.
Run --help (or -h) for the full flag list:
bash <(curl -fsSL .../server-dev.sh) --helpReplicates the Claude Code + Codex setup on the box (implies
--ai). It:
- Installs the Claude Code + Codex CLIs (if not already).
- Writes a curated
~/.claude/settings.json(model, permission allowlist, enabled plugins) — without the machine-specific hooks/statusline paths from the desktop config. - Copies the skills pack into
~/.claude/skills/. - Adds the plugin marketplaces and installs the plugins
(
claude plugin marketplace add/install). - Writes a Linux-safe
~/.codex/config.toml(model + reasoning; drops the macOS-only MCP servers). - Installs the same plugins into Codex — adds the same GitHub
marketplaces and installs the plugin set via
codex plugin marketplace add/codex plugin add. - Pre-registers the Atlassian (Jira/Confluence) MCP for both Claude Code and Codex.
--ai (and therefore --agent-setup) also installs
bubblewrap, which Codex needs for its Linux sandbox.
Existing settings.json / config.toml are backed up to
*.backup first.
Jira/Atlassian needs a one-time OAuth login — it can't be provisioned headlessly. See docs/jira-mcp-setup.md.
Prerequisite: create the sudo account and add your SSH public
key to its ~/.ssh/authorized_keys first, then run this as
that account. The script hardens the box — it does not create the
user.
On top of the base zsh environment, this installs:
- fnm + latest LTS Node.js — with
--use-on-cdauto-switching - Docker — via the official
get.docker.comscript; your user is added to thedockergroup (log out/in to apply) - Dev CLIs —
gh,direnv,bun,go,build-essential,jq,eza,btop
It also creates a basic home layout (~/Developer,
~/Downloads; skipped if they exist).
And it applies base hardening (ported from the server-setup Ansible roles):
- Key-only SSH — hardened
sshddrop-in:PermitRootLogin prohibit-password,PasswordAuthentication no,MaxAuthTries 3, keep-alives. Validated withsshd -tbefore restart. - UFW firewall — default-deny inbound, SSH (22) only open. Reach dev app ports via SSH forwarding (below).
- sysctl — anti-spoof, SYN-flood, and ICMP-redirect protections.
- fail2ban — bans SSH brute-force (default
sshdjail). - Unattended security upgrades — hands-free patching, no auto-reboot.
Lockout guard: PasswordAuthentication no is only applied
when the running user already has ~/.ssh/authorized_keys. No
key → password auth is left on and you get a warning, so a
keyless box never becomes unreachable. After it runs, open a
second SSH session to confirm key login works before you
disconnect the first one.
Linux only (exits early on macOS — use install.sh there).
Safe to re-run; hardening is idempotent.
Installs a headless-capable browser so automation tools can drive
it and take screenshots — Google Chrome on amd64, Chromium on
other arches, plus rendering fonts.
Quick smoke test:
google-chrome-stable --headless=new --screenshot=/tmp/shot.png \
--window-size=1280,800 https://example.com && ls -la /tmp/shot.png--chrome installs the browser only. Unlike the Atlassian
MCP, a browser MCP is not registered for you — without one,
Claude Code and Codex cannot drive Chrome. Add it per client:
# Claude Code
claude mcp add chrome-devtools -- npx -y chrome-devtools-mcp@latest# Codex — add to ~/.codex/config.toml
[mcp_servers.chrome-devtools]
command = "npx"
args = ["-y", "chrome-devtools-mcp@latest"]Puppeteer/Playwright scripts can point at the installed binary
(google-chrome-stable) instead of downloading their own.
The script prints these next steps when it finishes:
- Open a second SSH session now to confirm key login still
works before you disconnect the first —
sshdwas just restarted. - Reconnect (or run
zsh) to start using zsh + fnm. - Log out and back in for Docker group membership to apply
(then
docker run hello-worldshould work withoutsudo). gh auth loginto authenticate the GitHub CLI (see the single-org scoping note below).- Forward dev ports from your laptop with
ssh -L(see below).
Because UFW opens only SSH, you reach a dev server's app ports (Vite, Node, etc.) by tunnelling them over your existing SSH connection — no inbound firewall holes needed.
Ad-hoc, per session:
ssh -L 3000:localhost:3000 -L 5173:localhost:5173 user@devboxNow http://localhost:3000 on your laptop hits the server's
port 3000.
Persistent — add this to ~/.ssh/config on your laptop (not
the server) so a bare ssh devbox opens the forwards every time:
Host devbox
HostName <server-ip>
User <your-user>
LocalForward 3000 localhost:3000
LocalForward 5173 localhost:5173
Add one LocalForward <local-port> localhost:<remote-port> line
per port you use.
The script installs the GitHub CLI but does not log you in —
gh auth login is interactive and token-scoped, so it stays a
manual step.
A standard gh auth login authenticates your account, which
means gh can see every organization you belong to. Scope lives on
the token, not the org. To restrict gh to just one org:
- Create a fine-grained personal access token: GitHub → Settings → Developer settings → Fine-grained tokens.
- Set Resource owner to that org and select only its repos (plus whatever repository permissions you need).
- Log in with the token instead of the browser flow:
gh auth login --with-token < token.txtThat token physically cannot touch any other org, so gh is effectively locked to the one you scoped it to.
Bootstrap a shared hosting environment where you have no root access — pure bash, no package installs:
bash <(curl -fsSL https://raw.githubusercontent.com/nathanialhenniges/dotfiles/main/sharedhosting.sh)This copies configs from config/sharedhosting/ to set up a
comfortable shell environment on hosts like cPanel, Plesk, etc.
This repo works automatically with
GitHub Codespaces.
When you create a Codespace, GitHub clones your dotfiles repo
and runs install.sh. The script detects Linux and installs
lightweight essentials (zsh, git, curl, wget, fzf)
via apt instead of Homebrew. macOS-specific shell config
(Homebrew paths, Android SDK, Oh My Posh, etc.) is skipped on
Linux so your shell loads cleanly.
To enable this, go to Settings > Codespaces on GitHub and
set your dotfiles repository to nathanialhenniges/dotfiles.
- Discord: Join my server
Made with love by MrDemonWolf, Inc.