- Focus: Developer Advocacy with agentic AI, engineering, docs, tutorials and recordings.
- Software: New and old productivity tools, AI agents, and frameworks, e.g. Ghostty, Starship, neovim, Mise, etc.
- Hardware: Macbook Pro 14", M4 Max, 64 GB RAM, 1 TB (performance model).
- Remote office: The Macbook is connected to @dnsmichi's remote office setup with CalDigit TS5 Plus, Magic Keyboard, Apple Trackpad, Samsung Odyssey 49", Razer soundbar, and more, described in the all-remote workspace setup.
- Security: No credentials and keys in .env, .gitconfig, or other config files.
This is an opinionated setup, optimized for efficiency and productivity. Fork it and modify it for your own needs.
Archive:
- 2026-09: The setup with Oh-My-ZSH and Powerlevel10k on the previous M1 model is documented in this blog post at this commit
- 2023-05: The setup for the previous 16", 2019 Intel model can be found at this commit. The setup is explained in-depth in dotfiles - Document and automate your Macbook setup.
A quick overview to pick what is interesting for your own setup. Each area links to its details.
- One switch for light and dark: Ghostty, Starship, Neovim, and VS Code all follow the macOS appearance setting with matching themes. Switching macOS between light and dark mode switches every tool at once, which keeps maintenance simple and makes demos and screenshots easy to control.
- One setup script: setup.sh installs Homebrew and the Brewfile packages, and links the tracked configuration into your home folder, so edits land in this repository. Rerun it any time to update packages and restore links; it never overwrites local changes, and shows a diff instead.
- Terminal: Ghostty with tmux, and plain Zsh with a few small configuration files instead of a framework. The Starship prompt, Atuin shell history, and zoxide keep it keyboard-driven and fast.
- Editors: Neovim with LazyVim for the terminal, and VS Code with a tracked extension list.
- Credentials: 1Password for SSH keys, commit signing, and API keys, so no secrets live on disk.
- GitLab workflow: the GitLab CLI and repo-sync.sh, which clones your groups and projects in one go.
- Agentic AI: GitLab Duo, Claude, and Glean. Shared agent skills are linked into each tool, and AGENTS.md documents this repository's rules for coding agents.
- Languages and tools: mise for Node.js, Python, Ruby, Go, Rust, and Java with Maven, and Homebrew for everything else in the Brewfile.
- macOS: opt-in preferences for keyboard, trackpad, Finder, and screenshots.
Follow the steps top down on a new Macbook. Each step builds on the previous one.
To track progress, create an issue from the new-macbook-setup template,
which lists every step and sub-section as a tickable checklist.
Step-by-step instructions for a new Macbook, grouped into four phases. Each phase builds on the previous one, from a fresh macOS install to a fully configured workstation.
Install the apps, compilers, and package managers, then link the tracked configuration from this repository into the home directory.
The GitLab work environment uses endpoint management for Macbooks. Follow the laptop management instructions to set up Okta, FileVault encryption, security profiles, and required packages (1Password, Chrome, Zoom, etc.) automatically.
Install the remaining apps manually. Their configuration is linked by setup.sh in a later step.
| Type | Tools |
|---|---|
| Credentials | 1Password, 1Password for Safari/Chrome |
| Terminal | Ghostty: enable automatic updates when asked |
| Editor | VS Code: its own updater controls the release cycle. JetBrains IDE Toolbox (license required for IntelliJ IDEA, PyCharm, GoLand, RubyMine, CLion, RustRover, Rider, DataGrip, etc.) |
| Backup | Google Drive for Desktop |
| Containers | Rancher Desktop |
| Browser | Google Chrome, Safari |
| DevRel | Adobe Creative Cloud (Premiere Pro, etc.) - enterprise license, Screen Studio (approved license) - handbook |
Agentic AI tools are set up in Agentic AI.
Install Git, compilers, and the macOS SDK with the Xcode Command Line Tools. Open Ghostty and run:
xcode-select --installThe Command Line Tools are enough for all compilers used here, including Neovim's treesitter parsers. Full Xcode is not needed: it requires an Apple account login, which the GitLab-managed profile does not use.
Clone this repository over HTTPS. SSH works only after the 1Password SSH agent is set up in 1Password SSH agent and CLI.
mkdir -p ~/dev/work
cd ~/dev/work
git clone https://gitlab.com/dnsmichi/dotfiles.git
cd dotfilesLanguages and frameworks are managed with mise,
for example, Node.js, Ruby, Python, etc. Install it before running setup.sh,
because the linked .zshrc activates mise in every new shell.
curl https://mise.run | sh
~/.local/bin/mise --version./setup.shThe script:
- Checks for the Xcode Command Line Tools.
- Installs Homebrew when it is missing.
- Installs and updates the packages in Brewfile.
- Links the tracked configuration into the home directory
~, with the same paths as in this repository, e.g..config/starship.toml. - Links each agentic skill under skills/ into
~/.claude/skills/,~/.agents/skills/, and~/.gitlab/duo/skills/for Claude Code, Codex, and GitLab Duo.
setup.sh is safe to rerun. It does not modify macOS defaults. If a link has
been replaced with a regular file, the script stops and shows a recursive diff
instead of overwriting local changes. Merge any wanted changes into this
repository, remove the local file, and rerun the script.
Open a new Ghostty window afterwards, so the linked shell configuration is loaded.
Globally installed languages are configured in .config/mise/config.toml. Navigate into this repository and run:
mise installConnect 1Password for SSH keys, commit signing, and secrets, then authenticate with GitLab.com and clone the repositories used for work.
Store private SSH keys in 1Password only, and never on disk.
Open 1Password Settings > Developer:
- Tick
Use the SSH Agent. - Select
Show titlefor SSH keys, andOpen SSH URLs with Ghostty. - In
Developer Integrations, tickIntegrate with 1Password CLI. The 1Password CLIopis installed by the Brewfile, and unlocks with Touch ID.
setup.sh already linked the matching configuration:
- .ssh/config uses the 1Password SSH agent and selects the public key .ssh/gitlab_work.pub for gitlab.com. 1Password provides the private key.
- .gitconfig signs commits with the same key through 1Password.
To retrieve other secrets on the terminal, see Retrieve secrets with 1Password CLI.
Switch the remote from HTTPS to SSH, then test the SSH key and commit signing:
git remote set-url origin git@gitlab.com:dnsmichi/dotfiles.git
ssh -T git@gitlab.com
git commit --allow-empty -m "Test commit signing" && git log --show-signature -1Remove the empty test commit with git reset --soft HEAD~1 when it is not needed.
The GitLab CLI glab is installed by the Brewfile. Authenticate against GitLab.com:
glab auth login --hostname gitlab.com --git-protocol httpsSelect Web and follow the OAuth browser popup to approve.
Git uses this login for HTTPS remotes on gitlab.com: .gitconfig sets
glab auth git-credential as the credential helper, so no token is stored separately.
HTTPS also works for coding agents in a sandbox that cannot reach the 1Password SSH agent.
Then clone the GitLab groups and projects:
./repo-sync.shrepo-sync.sh clones repositories into
~/dev/work/<group>/<subgroup>/<project>, based on two inventories:
- repos/groups.txt: groups cloned recursively, including subgroups. Archived projects and projects shared from other groups are skipped.
- repos/projects.txt: individually selected projects.
For fast typing, the script also links ~/dev/da to ~/dev/work/gitlab-da
for Developer Advocacy work. Shortcuts are listed in SHORTCUTS at the top
of the script; existing folders or other links at that path are reported, never replaced.
The script is safe to rerun. Existing clones are fetched and fast-forwarded when
their working tree is clean; repositories with local changes or diverged branches
are reported and left alone. Set REPOSITORIES_DIR to clone somewhere else.
Private projects whose paths are already publicly known can stay in the tracked
lists. Keep other private or confidential paths in repos/groups.local.txt and
repos/projects.local.txt, which use the same format and are not tracked.
Agentic AI tools for daily work, demos, and integration guides.
setup.sh already linked the agentic skills from skills/ for all tools below.
Provisioned access as team member, and Developer Advocate in gitlab.com/gitlab-da. See the GitLab Duo Agent Platform docs.
Included in glab. The ZSH alias duo runs glab duo cli.
See GitLab Duo CLI in the Dev Advocacy Handbook.
Follow the Developer Advocacy handbook, and the Claude handbook page for Claude Desktop.
curl -fsSL https://claude.ai/install.sh | bashRun claude and set the following:
- Theme / text style:
Autoto follow Ghostty and Starship defaults. - Login method: Account with subscription. Follow the OAuth login popup.
Follow the handbook for access and setup. Install the Glean Desktop app for local work.
Open Glean settings in Desktop and disable Show in menu bar. It might accidentally show internal
docs/calendar invites in a screenshare. For the Chrome plugin, disable Glean as default new tab page
for the same reasons.
Follow the Developer Advocacy handbook. Codex is installed with the Node.js version managed by mise, so reinstall it after Node.js upgrades.
npm install -g @openai/codexCursor, Kiro, Devin, etc. are installed manually when required, e.g. for testing GitLab MCP Server client tools.
Personal preferences for macOS and apps, and restoring data from the previous Macbook. None of these steps are required for the tools above to work.
macOS preferences live in .macos. Review the script before applying it, then run:
zsh .macosIt covers keyboard and trackpad behavior, Finder paths and extensions, Dock visibility, immediate authentication after sleep, screenshots, muted system UI sounds, and the macOS application firewall. It does not change shell startup files, power settings, hostnames, hidden system directories, or application-specific preferences. Log out and back in to apply all changes.
Use visudo, which checks the syntax before saving. A broken sudoers file
locks you out of sudo.
sudo visudo -f /private/etc/sudoers.d/michaelmichael ALL=(ALL) NOPASSWD: ALL
TinyCast is installed by the Brewfile. It provides app launcher, search and emoji picker as a Spotlight alternative. The current shortcuts are:
Cmd+Spacelaunches TinyCast.Option+2opens the emoji picker.
On a fresh macOS setup:
- Open
Settings > Keyboard > Keyboard Shortcuts. - Disable the Spotlight shortcuts so
Cmd+Spacecan launch TinyCast. - Start TinyCast and confirm its launcher shortcut is
Cmd+Space. - Open TinyCast, search for Settings, and open it.
- Select Emoji & Symbols.
- Enable Search Emoji & Symbols, click its shortcut field, and press
Option+2.
Enable the VS Code CLI code first. Open VS Code:
Command Palette → Shell Command: Install 'code' command in PATH
Install the tracked extension inventory from vscode/extensions.txt:
bash ./vscode-extensions-install.sh1Password overrides the screenshot shortcut cmd+shift+4+space by default with shift+cmd+space.
Clear it in 1Password → Settings → General → Global keyboard shortcuts → Show Quick Access.
Open Finder and navigate into Settings > Sidebar to add
- User home (user name)
- System root (Macbook name)
Follow the tools and tips handbook for Zoom, and apply these additional settings:
Settings > Meetings & webinars: UntickAsk me to confirm when leaving.Settings > Meetings & webinars: TickKeep my microphone muted.Settings > Keyboard Shortcuts: Mute/Unmute my audio:option 1.
Grant the macOS permissions from inside Zoom, so macOS prompts for each one when it is needed:
- Start Zoom and open a new meeting.
- Allow microphone input and audio output when asked.
- Share your screen. Zoom asks for screen recording access; click through to
System Settings > Privacy & Security > Screen & System Audio Recording, enable Zoom, and restart Zoom when asked.
Start Rancher Desktop with macOS login, so containers are ready without opening the app.
Open Preferences > Application > Behavior and set:
Startup: TickAutomatically start at login.Background: TickStart in the background.
Install Elgato Control Center for the Key Lights, and Elgato Camera Hub for the Facecam and Prompter, from the Elgato downloads page.
Open Elgato Control Center from the menu bar and let it search for lights. Lights on the same network are found automatically and flash when they are found.
If a light is not found:
- Keep the Mac on the same router as the lights, not a guest network.
- Pair new or reset lights from the macOS Wi-Fi menu under
New Accessory. Key Light MK.I only supports 2.4 GHz Wi-Fi. - Reset a light as a last resort: hold the power button for 10 seconds until it blinks 3 times, then pair it again.
The Prompter is not a native macOS display. It needs the DisplayLink software, which Camera Hub points to during setup. Grant it the permissions it asks for, such as screen recording, so the Prompter shows up as a second display.
Download the latest encrypted backup from Google Drive and follow
Restore an encrypted backup. Copy back only what is needed, for example
.zsh_history and personal AI tool configuration, then import the shell history
into Atuin:
atuin import autoTools and setup for contributing to GitLab itself.
They build on mise and the gitlab-org/gitlab clone
from the Setup steps.
Follow the one-line installation and use mise.
Alternatively, use GDK-in-a-box with Docker containers.
TODO: Use Caproni for GitLab to develop GitLab locally in a Kubernetes cluster with edit mode.
The CI/CD pipelines for GitLab docs use linting which can be installed locally to test problems faster.
yarn global add markdownlint-cli2
yarn global add markdownlint-cli
mise plugin add vale && mise install valeThe VS Code editor integration is managed through vscode-extensions-install.sh.
cd ~/dev/work/gitlab-org/gitlab
yarn install
./scripts/lint-doc.shWhat is configured, where the configuration lives, and how to use it after setup.
The terminal, shell, and prompt tools used every day, and their tracked configuration.
| Type | Tools |
|---|---|
| Terminal app | Ghostty |
| Multiplexer | tmux |
| Shell | ZSH and Starship |
| Shell history | Atuin (Ctrl-R) in addition to ZSH history (cursor up/down) |
| Navigation | zoxide (z <dir>) and fzf |
| Editor | Neovim aliased to vim |
The Brewfile installs JetBrainsMono Nerd Font, which provides the optional
symbols used by Starship and Ghostty.
Ghostty's tracked configuration lives in .config/ghostty and uses matching light and dark themes based on the macOS appearance setting.
Starship's configuration lives in .config/starship.toml. It uses the same colors as Ghostty, supports light and dark themes, and is configured to match a GitLab work profile.
Press Ctrl-R to search history with Atuin. Up and Down keep Zsh's native
history navigation. Native history settings save
commands incrementally to ~/.zsh_history, without importing commands from
other active terminal sessions into the current shell.
Completion settings load Homebrew completions
and initialize Zsh's completion system. Tab offers case-insensitive matching,
then partial and substring matching, with selectable, grouped results.
The completion cache lives under ${XDG_CACHE_HOME:-$HOME/.cache}/zsh.
.zshrc loads history, key bindings, completion, aliases, and functions explicitly,
followed by tool integrations. Syntax highlighting loads last. EDITOR and VISUAL
are both set to nvim for commands that open an external editor.
Key bindings select emacs-style line editing.
Otherwise Zsh switches to vi mode because EDITOR is nvim, and Cmd+Left/Right
stop working: Ghostty sends them as Ctrl-A and Ctrl-E. The file also maps
Home and End (Fn+Left/Right) to the line start and end, and forward delete
(Fn+Backspace) to delete the character under the cursor. Plain Zsh does not
bind these keys.
Neovim and VS Code configuration, both following the macOS light and dark appearance.
The setup started off a fresh git clone following the LazyVim docs. .config/nvim is modified and linked into the home directory. The editor uses the Gruvbox hard-contrast theme and follows the macOS light/dark appearance setting, matching the Ghostty themes.
LazyVim compiles treesitter parsers with the Command Line Tools. If compiling fails, see treesitter troubleshooting.
VS Code user settings are tracked in .config/vscode/settings.json.
setup.sh links that file to VS Code's macOS user-settings location.
The editor follows macOS light and dark appearance automatically, using the built-in
Light Modern and Dark Modern themes.
Secrets are addressed with references in the format op://<vault>/<item>/<field>.
Short item names without spaces are easier to type.
Find the vault and item name:
op item list --format json | jq -r '.[] | select(.title | test("example"; "i")) | "\(.vault.name)\t\(.title)"'List the field names of an item, without printing their values:
op item get example-api-key --vault <vault> --format json | jq -r '.fields[].label'Pipe a secret into a command, so it never shows on screen or in shell history:
op read "op://<vault>/example-api-key/password" | some-cli login --with-api-keyRecurring tasks to keep this repository and the backups up to date.
After installing extensions manually, discover what is present with:
bash ./vscode-extensions-install.sh --discoverReview the result, then update the tracked inventory from the installed set with:
bash ./vscode-extensions-install.sh --update-inventoryReview the generated inventory before committing it; manually installed extensions can be adopted later without changing the script.
Use Google Drive for Desktop, Chrome profile sync, and 1Password for credentials/SSH keys.
Keep personal .codex, .claude, etc. agentic AI tools configuration in the home
directory and private backups. Do not symlink these configurations into this
public repository. Public, reusable skills can be linked from skills/.
Close applications that write to these directories before copying. Run this from the home directory, adjusting the source list for files that exist on the machine. Each backup gets a new directory outside the repository:
cd "$HOME"
umask 077
backup_dir="$HOME/backup/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$backup_dir/home"
cp -RL .ssh .env .claude .codex .agents .config \
.zsh_history .claude.json .ansible "$backup_dir/home/"cp -RL copies the files behind symlinks, including linked dotfiles and skills.
Review any copy errors before continuing; running applications can leave sockets
that cannot be copied. This is a backup of the selected paths, not the whole home
directory.
Create a tarball, then encrypt it with 7z from the Brewfile's p7zip package:
tar -czf "$backup_dir/home.tar.gz" -C "$backup_dir" home
7z a -t7z -mhe=on -p "$backup_dir/home.tar.gz.7z" "$backup_dir/home.tar.gz"
7z t "$backup_dir/home.tar.gz.7z"Enter a strong password at the prompt and save it in 1Password. Upload only
home.tar.gz.7z to Google Drive after verification succeeds.
Download the encrypted archive and extract it into a new local directory. Replace the archive path below with the downloaded file:
mkdir -p "$HOME/backup"
restore_dir="$(mktemp -d "$HOME/backup/restore.XXXXXX")"
7z x /path/to/home.tar.gz.7z -o"$restore_dir"
tar -xzf "$restore_dir/home.tar.gz" -C "$restore_dir"Inspect the restored files in "$restore_dir/home" before copying anything back
into the home directory. Do not copy back files that setup.sh manages as links,
such as .zshrc or .config/starship.toml.
Problems seen on this and previous Macbooks, with their causes and fixes.
When opening Ghostty, Zsh may prompt:
zsh compinit: insecure directories, run compaudit for list.
Ignore insecure directories and continue [y] or abort compinit [n]?
Run the following to identify the affected directories:
autoload -Uz compaudit
compaudit/opt/homebrew/share is owned by the current user but
group-writable. Check its permissions and remove group write access:
ls -ld /opt/homebrew/share
chmod g-w /opt/homebrew/shareRun compaudit again; no output means the check passed. Open a new Ghostty
tab to confirm completion initializes without prompting. This fix needs no
sudo, recursive permission changes, or completion-cache deletion.
If the audit lists different paths, inspect their ownership and permissions
before changing them. Keep the normal compinit security checks enabled.
See Homebrew's Zsh completion guidance.
macOS SDK mismatches can cause the treesitter plugins to fail to compile against the current macOS version:
/Library/Developer/CommandLineTools/SDKs/MacOSX27.0.sdk/usr/lib/libSystem.B.tbd:4:20: error: unknown architecture
arm64e.x1-macos, arm64e.x1-maccatalyst ]
^~~~~~~~~~~~~~~
in '/Library/Developer/CommandLineTools/SDKs/MacOSX27.0.sdk/usr/lib/libSystem.tbd'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Cause: /Library/Developer/CommandLineTools/SDKs/ holds a leftover MacOSX27.0.sdk.
The Command Line Tools 26.6 update added MacOSX26.5.sdk but left the old one in place.
xcrun always picks the newest SDK, so clang uses the 27.0 SDK.
The installed linker is older and can't read it. That's the unknown architecture arm64e.x1-macos error.
Workaround: .zshrc points the compiler at the matching SDK with
export SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX26.5.sdk.
Update the path there when the SDK version changes. Open a new terminal, start nvim,
and run :TSUpdate, or :TSInstall! yaml for just the parser that failed.
Permanent fix: remove the leftover SDK, then remove the SDKROOT workaround from .zshrc:
sudo rm -rf /Library/Developer/CommandLineTools/SDKs/MacOSX27.0.sdk /Library/Developer/CommandLineTools/SDKs/MacOSX27.sdkIf Ghostty needs to remove protected application data, such as leftover
files under ~/Library/, grant it access in:
System Settings > Privacy & Security > Full Disk Access
Enable Ghostty, restart it, and rerun the cleanup command. sudo alone cannot
bypass macOS privacy protection for these directories.
A USB audio device can stop sending input after another app restarts or reconnects
devices, for example Elgato Camera Hub. The setup here uses a Shure SM7B with a
Cloudlifter CL-1 and a PreSonus Studio 24c USB audio interface. Zoom still shows the
microphone, but the input level in System Settings > Sound > Input stays flat.
Restart the macOS audio service. macOS starts it again immediately:
sudo killall coreaudiodThen rejoin the Zoom meeting, and check that the microphone is still selected next to the Mute button.
If DNS causes problems on macOS:
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
sudo killall -9 mDNSResponderOn major version upgrades, binaries might be incompatible or need a local rebuild. You can enforce a reinstall by running the two commands below, the second command only reinstalls all application casks.
brew reinstall $(brew list)
brew reinstall $(brew list --cask)When compilers break, reinstall the Command Line Tools.
sudo rm -rf /Library/Developer/CommandLineTools
sudo xcode-select --installAfter macOS upgrades, Git and other developer tools may fail with:
xcrun: error: invalid active developer path
Reinstall the Command Line Tools and explicitly agree to their terms of service:
xcode-select --installThe settings in .macos use macOS internal APIs on the command line. Sometimes the configuration settings change, for example with the Trackpad on macOS Ventura. To debug and capture which settings are in effect, create a new Git repository somewhere, and persist the system settings output.
mkdir -p $HOME/dev/work/system-settings
cd $HOME/dev/work/system-settings
git init
defaults read > settings.txt
git add settings.txt
git commit -av -m "Initial settings"Then navigate into the Systems settings GUI, change parameters, export the system settings into the same file, and analyze the Git diff to figure out the correct parameter names and values.
defaults read > settings.txt
git diffExample from June 2023
- Starship Gruvbox Rainbow preset https://starship.rs/presets/gruvbox-rainbow
- .macos inspiration
The main repository is hosted on GitLab.com, mirrored to GitHub.com: https://gitlab.com/dnsmichi/dotfiles
