An Ansible playbook that sets up a WSL2 Ubuntu 26.04 distro: zsh, git, ssh, docker, herdr, agent CLIs and the rest of the tooling, for a personal or a work machine. Safe to run again at any time.
On Windows, once:
- Create the SSH key, in PowerShell:
ssh-keygen -t ed25519. The playbook copies it into the distro and adds it to GitHub. - Install a Nerd Font (Hack) and pick it in Windows Terminal; the prompt needs its icons.
- Work machine only: export the Zscaler root certificate to
C:\Users\<you>\zscaler.cer: opencertmgr.msc, then Trusted Root Certification Authorities, Certificates, right-click the Zscaler root, All Tasks, Export. Either format works.
In the distro:
bash -c "$(curl -fsSL https://raw.githubusercontent.com/irish1986/dotfiles/main/scripts/setup)" -- --profile work # or personalThe first run clones the repo to ~/.dotfiles, creates ~/.config/dotfiles/local.yml and ~/.dotfiles/.env, and stops. Fill in your git identity there (and, at work, the package mirrors in ~/.dotfiles/.env), then run it again:
~/.dotfiles/scripts/setupLater runs take any ansible-playbook option:
~/.dotfiles/scripts/setup --tags zsh,git # only those roles
~/.dotfiles/scripts/setup --check --diff # preview, change nothing
~/.dotfiles/scripts/setup --profile personal # switch profileTo let the playbook add the SSH key to GitHub, put a token in the env file (see Tokens and secrets), then run --tags ssh. gh reads it in place of a login:
GH_TOKEN=<token> # fine-grained: Git SSH keys and SSH signing keys, read and writegh auth login -s admin:public_key,admin:ssh_signing_key works too, if you would rather keep a stored login.
main.yml the playbook: loads the layers, then runs the roles in order
profiles/ base.yml for every machine, plus personal.yml or work.yml
roles/ certificates, system, zsh, tools, ssh, git, herdr
scripts/setup the bootstrap
local.example.yml the local file template
.env.sample the env file template: tokens and corporate URLs
specs/adr/ why it is shaped this way
specs/CONTEXT.md the vocabulary
Each role is tasks/main.yml, tasks/verify.yml and the files it uses; defaults/main.yml lists what can be changed.
What a machine gets is layered (ADR 0002); a later layer overrides values and appends to lists:
profiles/base.yml: every machine.profiles/personal.ymlorprofiles/work.yml: what that kind of machine adds.~/.config/dotfiles/local.yml: the local file. Your identity, the profile name, and anything that must not be public. Never committed; seelocal.example.yml.
The work network inspects TLS with Zscaler and blocks some public registries (ADR 0007):
zscaler.ceris trusted before anything downloads, byscripts/setupand then by thecertificatesrole. A run fails while it is missing. If your Windows user name differs from the Linux one, setdotfiles_windows_userin the local file.- The package mirrors go in the env file,
~/.dotfiles/.env(.env.samplelists them).scripts/setupexports it before installing ansible-core, and every shell exports it, so uv and npm use the mirrors without further configuration.
Tokens and corporate URLs live in one place, the env file ~/.dotfiles/.env (ADR 0010). It is gitignored, mode 0600, created from .env.sample on the first run, and exported by every shell and by scripts/setup. Uncomment what the machine needs and open a new shell.
gh, Copilot CLI, ggshield and snyk log in from it, each reading its own variable; nothing runs a login (grype, syft, hadolint and zizmor need no token):
GH_TOKEN=<token> # gh and Copilot CLI, fine-grained (github_pat_): see .env.sample for its permissions
GITGUARDIAN_API_KEY=<token> # ggshield
SNYK_TOKEN=<token> # snykEvery run checks these logins and prints one warning naming each tool whose token is unset, a placeholder, or rejected. After editing the env file, open a new shell and check them alone with ~/.dotfiles/scripts/setup --tags auth.
Add an entry to a profile (ADR 0004): profiles/base.yml for every machine, personal.yml or work.yml for one kind. roles/tools/defaults/main.yml documents each kind. Then run ~/.dotfiles/scripts/setup --tags tools. Removing an entry stops managing the tool; uninstall it by hand.
The tools role merges the servers in each profile into the configured agent's user-level MCP config, preserving other settings and servers. Copilot CLI gets Snyk and Terraform on every profile; its GitHub MCP server is built in. Personal machines also configure the hosted GitHub MCP server, Snyk, and Terraform for Claude Code. Restart the CLI after setup to discover new servers.
- GitHub uses the hosted server and Claude Code's OAuth flow; Copilot CLI uses its built-in server and existing Copilot login.
- Snyk runs
npx -y snyk@latest mcp -t stdioand usesSNYK_TOKENfrom the env file. - Terraform runs HashiCorp's pinned Docker image. HCP Terraform credentials are not configured.
Server definitions live in tools_agent_mcp_servers in the profiles; the Terraform image version is pinned in roles/tools/defaults/main.yml. Run ~/.dotfiles/scripts/setup --tags mcp to update only MCP configuration.
prek run --all-files # every lint hook
ansible-playbook main.yml --check # preview, change nothingCI converges each profile twice in an ubuntu:26.04 container; the second run must change nothing (ADR 0009).
Commit conventions are in CONTRIBUTING.md. There are no releases: main is the version.
Heavily influenced by ALT-F4-LLC and TechDufus.
MIT — see LICENSE.