Personal macOS development environment. mise is the only entry point: it
installs every tool, applies dotfiles, and configures the machine through
mise bootstrap. There is no Nix, no task runner, and no project-level mise
config in this repo.
Prerequisites: macOS, Xcode CLI tools (xcode-select --install).
curl -fsSL https://raw.githubusercontent.com/djgoku/dot-files/main/setup.sh | zshOr clone and run locally:
mkdir -p ~/dev/github/djgoku && cd "$_"
git clone https://github.com/djgoku/dot-files.git
cd dot-files && ./setup.shsetup.sh does only what cannot be declared in config: clone the repo and
install mise. It then points mise at the config inside the clone with
MISE_CONFIG_DIR and lets mise create ~/.dot-files and ~/.config/mise
from its own [dotfiles] entries – no path is symlinked by hand – before
running mise bootstrap --yes. It takes --no-clone (use the checkout
already in place) and --prepare-only (stop before bootstrapping); CI uses
both rather than reimplementing the setup.
Once bootstrapped, re-apply at any time with:
mise bootstrap --yes. ├── setup.sh # Fresh-machine bootstrap (3 steps, then hands off) ├── config/mise/ # Machine config -> symlinked to ~/.config/mise │ ├── config.toml # [settings] [tools] [env] [dotfiles] │ └── conf.d/ │ ├── 10-repos.toml # git clones (fzf-tab, tree-sitter-module) │ ├── 20-files.toml # managed directories and generated files │ ├── 30-macos.toml # Finder and NSGlobalDomain defaults │ └── 90-hooks.toml # post-repos, post-tools ├── emacs/ │ ├── init.el # Emacs init │ ├── main.el # Main configuration │ ├── early-init.el │ └── emacs-env/ # Environment selector ├── mise.toml # PROJECT config: this repo's tasks only ├── .github/ │ ├── PklProject # pins com.github.actions (pkl-pantry) │ └── workflows/ # *.pkl are the SOURCES; *.yml are generated ├── gnupg/gpg-agent.conf.tmpl # Rendered to ~/.gnupg/gpg-agent.conf ├── macos/com.apple.Terminal.plist └── zsh/.zshrc
mise auto-discovers mise/config.toml, .mise/conf.d/, and
.config/mise/conf.d/ as project config. Any of those names would mean that
cd-ing into this repo loads the machine config a second time – including its
[bootstrap] stanzas, so [bootstrap.macos.defaults] would go live locally.
config/mise/ matches no discovery rule, so the ~/.config/mise symlink is
the only path by which it loads.
mise bootstrap runs stages in a fixed order:
plugins packages accounts files services firewall compose repos dotfiles mise-shell-activate macos-defaults macos-launchd-agents linux-systemd-units user tools task final-hook
=tools= is stage 15 of 17. Anything that shells out to an installed binary
must run in post-tools or final, not earlier. Two entries here depend on
that ordering:
~/.gnupg/gpg-agent.confinterpolatesmise which pinentry-curses. It renders at the dotfiles stage with a bare-name fallback so the stage cannot hard-fail, thenpost-toolsre-renders it with the resolved absolute path.- The gpg-agent restart runs in
post-tools, wheregpgconfactually exists.
Inspect without changing anything:
mise bootstrap --dry-runRun a single stage:
mise bootstrap --only files --yesNote that bootstrap.directories is applied as part of the files stage;
there is no --only directories. bootstrap.files does not create parent
directories or mkdir -p, so each level is declared explicitly, parent first.
GitHub Actions, in two tiers:
- Validate runs on every push and PR. It installs nothing – it asserts the
five config files load, that every bootstrap stage resolves under
--dry-run, and that the workflow YAML still matches its Pkl source. - Bootstrap runs weekly and on
workflow_dispatch. It runssetup.shfor real on a clean macOS runner, asserts the artifacts, and re-runs bootstrap to prove idempotency.
.github/workflows/*.yml is rendered from the .pkl file beside it using
Pkl and the com.github.actions pkl-pantry package.
Edit the .pkl, then:
mise run render-workflowsThe validate job runs mise run check-workflows, which re-renders and then
runs git diff --exit-code, so a hand-edited .yml fails CI.
Note this repo carries two mise configs with different jobs: mise.toml at the
root is project config (tasks, and the pkl they need), while config/mise/
is machine config and is never loaded from here.
After changing .github/PklProject, re-resolve and commit the lockfile:
pkl project resolve .github/Note pkl does not discover a PklProject in an ancestor directory, which is
why the render task passes --project-dir .github/.
macos-defaultswrites to your real macOS preferences. A sandboxedHOMEdoes not isolate them, becausecfprefsdis keyed by uid. Use--dry-runto inspect.com.apple.Terminal.plistis applied withmode = "copy", notsymlink, becausecfprefsdrewrites the file in place. Quit Terminal before applying or it will overwrite the copy again on quit.bootstrap.reposentries have noref, so mise never pulls – an existing clone with a matching origin counts as current. A dirty worktree fails the stage, so hide local additions with.git/info/exclude.
mise doctorCheck which config files mise is actually reading:
mise config ls