diff --git a/.chezmoiignore b/.chezmoiignore deleted file mode 100644 index 4355b42..0000000 --- a/.chezmoiignore +++ /dev/null @@ -1,20 +0,0 @@ -README.md -LICENSE -CLAUDE.md -.github -.gitignore - -# Destination side files chezmoi must not touch -.claude/local -.claude/settings.local.json -.claude/hooks -.claude/projects -.claude/scripts -.claude/shell-snapshots -.claude/statsig -.claude/todos -.zsh/env_local.zsh -.worktree -firebase-debug.log -.netrwhist -.DS_Store diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d03c1e..643d328 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,96 +18,107 @@ jobs: sudo apt-get update sudo apt-get install -y shellcheck zsh - - name: Install chezmoi - run: sh -c "$(curl -fsLS get.chezmoi.io)" -- -b /usr/local/bin - - - name: Render chezmoi templates and run shellcheck + - name: shellcheck (non-zsh shell scripts) run: | - rendered_dir="$(mktemp -d)" - # Render *.tmpl run scripts so shellcheck can parse them - for tmpl in run_*.sh.tmpl; do - [ -f "$tmpl" ] || continue - out="$rendered_dir/$(basename "${tmpl%.tmpl}")" - chezmoi execute-template --source="$PWD" < "$tmpl" > "$out" - done - - # shellcheck does not support zsh; filter zsh-shebanged scripts and - # the vendored fzf-git.sh out of the target set. + # The only .sh files are zsh-targeted or vendored (fzf-git.sh); shellcheck + # does not support zsh, so filter those out. targets=() while IFS= read -r -d '' f; do case "$f" in - */dot_zsh/functions/fzf-git.sh) continue ;; + */functions/fzf-git.sh) continue ;; esac if head -n1 "$f" | grep -qE '^#!.*\bzsh\b'; then continue fi targets+=("$f") - done < <( - find . -type f -name "*.sh" -not -path "./.git/*" -print0 - find "$rendered_dir" -type f -name "*.sh" -print0 - ) + done < <(find . -type f -name "*.sh" -not -path "./.git/*" -print0) if [ ${#targets[@]} -gt 0 ]; then shellcheck "${targets[@]}" else - echo "No shellcheck-eligible scripts in repo (all .sh files are zsh)." + echo "No shellcheck-eligible scripts (all .sh files are zsh or vendored)." fi - name: Check zsh syntax run: | - find . -type f \( -name "*.zsh" -o -name "dot_zshrc" -o -name "executable_*" \) -not -path "./.git/*" -print0 \ + find home -type f \( -name "*.zsh" -o -name ".zshrc" \) -print0 \ | while IFS= read -r -d '' file; do zsh -n "$file" || exit 1 done + zsh -n home/.zsh/bin/reload + + - name: shellcheck mise task scripts + # --severity=warning: ignore info/style notes (e.g. SC2016 on the + # intentional literal backticks in echo messages); fail on warning+. + run: shellcheck --severity=warning mise-tasks/* + + - name: Check mise task script syntax + run: | + for f in mise-tasks/*; do + bash -n "$f" || exit 1 + done - chezmoi-verify: - name: chezmoi verify + mise-bootstrap-verify: + name: mise bootstrap verify runs-on: macos-latest + env: + MISE_EXPERIMENTAL: "1" + CI: "1" steps: - uses: actions/checkout@v4 - - name: Install chezmoi - run: sh -c "$(curl -fsLS get.chezmoi.io)" -- -b /usr/local/bin + # The macOS runner ships an older mise that predates `mise bootstrap` + # (introduced in 2026.6.6). Install a recent mise explicitly and put it + # first on PATH so it shadows the pre-installed one. + - name: Install mise + uses: jdx/mise-action@v2 + with: + version: 2026.6.11 + install: false + cache: false - - name: chezmoi doctor - run: chezmoi --source="$GITHUB_WORKSPACE" doctor || true + - name: Show mise version + run: mise --version - - name: Apply to ephemeral HOME and verify rendered files + - name: Validate mise.toml (tasks parse, formatting) + run: | + mise trust "$GITHUB_WORKSPACE/mise.toml" + mise -C "$GITHUB_WORKSPACE" tasks ls + mise -C "$GITHUB_WORKSPACE" fmt --check + + - name: Apply dotfiles to ephemeral HOME and verify symlinks resolve run: | export HOME="$(mktemp -d)" - export CI=1 - export XDG_CACHE_HOME="$HOME/.cache" - chezmoi init --apply --source="$GITHUB_WORKSPACE" - - test -f "$HOME/.zshrc" - test -f "$HOME/.config/mise/config.toml" - test -f "$HOME/.config/ghostty/config" - test -f "$HOME/.hammerspoon/init.lua" - test -f "$HOME/Library/Application Support/Code/User/settings.json" + mise trust "$GITHUB_WORKSPACE/mise.toml" + mise -C "$GITHUB_WORKSPACE" dotfiles apply -y + + # Symlinks must resolve to real files (-e follows symlinks) + test -e "$HOME/.zshrc" + test -e "$HOME/.config/mise/config.toml" + test -e "$HOME/.config/ghostty/config" + test -e "$HOME/.hammerspoon/init.lua" + test -e "$HOME/Library/Application Support/Code/User/settings.json" test -x "$HOME/.zsh/bin/reload" + test -L "$HOME/.zshrc" # must be a symlink, not a copy grep -F 'idiomatic_version_file_enable_tools = ["ruby"]' "$HOME/.config/mise/config.toml" grep -F 'appName = "Ghostty"' "$HOME/.hammerspoon/init.lua" - chezmoi verify --source="$GITHUB_WORKSPACE" + - name: Full bootstrap plan parses (dry-run, no side effects) + run: | + export HOME="$(mktemp -d)" + mise trust "$GITHUB_WORKSPACE/mise.toml" + mise -C "$GITHUB_WORKSPACE" bootstrap --dry-run --yes - - name: Idempotency check + - name: Idempotency check (dotfiles status clean after apply) run: | export HOME="$(mktemp -d)" - export CI=1 - export XDG_CACHE_HOME="$HOME/.cache" - chezmoi init --apply --source="$GITHUB_WORKSPACE" - chezmoi apply --source="$GITHUB_WORKSPACE" - status_output="$(chezmoi status --source="$GITHUB_WORKSPACE")" - if [ -n "$status_output" ]; then - echo "::error::chezmoi status is non-empty after re-apply" - echo "$status_output" - exit 1 - fi - diff_output="$(chezmoi diff --source="$GITHUB_WORKSPACE")" - if [ -n "$diff_output" ]; then - echo "::error::chezmoi diff is non-empty after re-apply" - echo "$diff_output" + mise trust "$GITHUB_WORKSPACE/mise.toml" + mise -C "$GITHUB_WORKSPACE" dotfiles apply -y + mise -C "$GITHUB_WORKSPACE" dotfiles apply -y + status_output="$(mise -C "$GITHUB_WORKSPACE" dotfiles status 2>&1)" + echo "$status_output" + if echo "$status_output" | grep -qiE 'pending|differs|missing|conflict'; then + echo "::error::dotfiles status not clean after re-apply" exit 1 fi - chezmoi verify --source="$GITHUB_WORKSPACE" diff --git a/.gitignore b/.gitignore index dcf7785..aa30304 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,21 @@ .netrwhist .DS_Store -# Local environment settings -dot_zsh/env_local.zsh +# Local environment settings (machine-specific; lives inside the symlinked ~/.zsh) +home/.zsh/env_local.zsh # Worktree directories .worktree/ -# Claude AI (local settings only) -dot_claude/local/ -dot_claude/settings.local.json -dot_claude/hooks/ -dot_claude/projects/ -dot_claude/scripts/ -dot_claude/shell-snapshots/ -dot_claude/statsig/ -dot_claude/todos/ +# Claude AI (local settings only; ~/.claude is a real dir, only settings.json is symlinked) +home/.claude/local/ +home/.claude/settings.local.json +home/.claude/hooks/ +home/.claude/projects/ +home/.claude/scripts/ +home/.claude/shell-snapshots/ +home/.claude/statsig/ +home/.claude/todos/ # Firebase firebase-debug.log diff --git a/.mise.toml b/.mise.toml deleted file mode 100644 index 6115459..0000000 --- a/.mise.toml +++ /dev/null @@ -1,9 +0,0 @@ -[tools] -# Flutter development -flutter = "latest" - -# Rust development -rust = "stable" - -# Vim/Neovim -vim = "latest" \ No newline at end of file diff --git a/dot_Brewfile b/Brewfile similarity index 100% rename from dot_Brewfile rename to Brewfile diff --git a/CLAUDE.md b/CLAUDE.md index 99556e9..37979ff 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,113 +4,85 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Repository Overview -This is a dotfiles repository for a macOS development environment, managed by [chezmoi](https://www.chezmoi.io/). Dotfiles, run-once installers, and run-onchange configuration scripts are organized using chezmoi's filename conventions (`dot_*`, `private_*`, `executable_*`, `run_once_*`, `run_onchange_*`). +This is a dotfiles repository for a macOS development environment, managed by [`mise bootstrap`](https://mise.jdx.dev/bootstrap.html). A single `mise.toml` at the repo root declares everything: language runtimes (`[tools]`), dotfile symlinks (`[dotfiles]`), macOS defaults (`[bootstrap.macos.defaults]`), lifecycle hooks (`[bootstrap.hooks]`), and tasks (`[tasks]`). Homebrew packages live in `Brewfile`. -Language runtimes (Flutter, Rust, Node, Ruby) are managed by [mise](https://mise.jdx.dev/) via `dot_config/mise/config.toml`. Homebrew packages are defined in `dot_Brewfile` and installed by `run_onchange_install-brew-packages.sh.tmpl`. +> `mise bootstrap` is **experimental** (requires `experimental = true` / `MISE_EXPERIMENTAL=1`) and was introduced in mise **2026.6.6**. mise itself is installed via Homebrew (`brew 'mise'` in `Brewfile`); bootstrap is a mise subcommand, so mise must exist before it can run. ## Commands ### Setup and Installation ```bash -# First time on a new machine -brew install chezmoi -chezmoi init --apply ry-itto/dotfiles +# First time on a new machine (mise must already be installed via brew) +brew install mise +ghq get ry-itto/dotfiles +cd "$(ghq root)/github.com/ry-itto/dotfiles" +MISE_EXPERIMENTAL=1 mise bootstrap --yes # env var needed on first run only -# Pull latest changes from the repo and re-apply -chezmoi update +# Pull latest changes and re-apply +git pull && mise bootstrap --yes -# See what would change before applying -chezmoi diff +# Preview without making changes +mise bootstrap --dry-run -# Apply pending changes -chezmoi apply +# Apply only the dotfile symlinks +mise dotfiles apply ``` ### Editing Managed Files -After migrating to chezmoi, editing files directly under `$HOME` does **not** sync back to this repository. Always use one of: +Dotfiles are **symlinks** into `home/` (not copies). Editing `~/.zshrc` edits `home/.zshrc` in this repo directly — no apply step is needed for content changes. Run `mise bootstrap` only when adding new `[tools]` / `[dotfiles]` / packages. -```bash -# Open the source file in $EDITOR -chezmoi edit ~/.zshrc - -# Or jump to the source directory and edit there -cd "$(chezmoi source-path)" -$EDITOR dot_zshrc -chezmoi apply -``` +> **Do not delete or move the cloned repo** — the symlinks point into it and would break. ### Adding New Files -```bash -# Move an existing $HOME file into chezmoi management -chezmoi add ~/.somefile -``` +Add the source file under `home/` (mirroring its `$HOME` path), then add a `[dotfiles]` entry in `mise.toml` and run `mise dotfiles apply`. `mise dotfiles add ~/.somefile` can do both steps. ## Architecture ### Source Layout -The repository **is** the chezmoi source directory. chezmoi reads filename prefixes to decide where each file goes in `$HOME`: - -- `dot_` → `~/.` (e.g. `dot_zshrc` → `~/.zshrc`) -- `executable_` → preserves +x bit on apply -- `private_` → applied with mode 0600/0700 -- `.tmpl` → rendered with chezmoi's template engine before apply -- `run_once_.sh` → executed once per machine -- `run_onchange_.sh` → executed when the script's content changes - -### Top-Level Files +- `mise.toml` — bootstrap config: `[tools]`, `[dotfiles]`, `[bootstrap.macos.defaults]`, `[bootstrap.hooks]`, `[tasks]`. +- `Brewfile` — Homebrew formulae **and casks**. mise's native `[bootstrap.packages]` only resolves formulae, so the Brewfile is the source of truth and is installed via the `brew-bundle` task. +- `home/` — source tree mirroring `$HOME`. Each file/dir is symlinked to its target by `[dotfiles]` (e.g. `home/.zshrc` → `~/.zshrc`). Executable bits are preserved through the symlink. +- `home/.config/mise/config.toml` — the user's **global** mise settings (`idiomatic_version_file_enable_tools`, `experimental = true`), symlinked to `~/.config/mise/config.toml`. Distinct from the repo-root `mise.toml` (the bootstrap orchestrator). -**Managed dotfiles** (chezmoi targets): -- `dot_zshrc` — entrypoint that sources modules under `~/.zsh/` -- `dot_zsh/` — modular Zsh config: `alias.zsh`, `env.zsh`, `style.zsh`, `plugin.zsh`, `functions/`, `bin/executable_reload` -- `dot_gitconfig`, `dot_Brewfile`, `dot_commit_template` -- `dot_vim/`, `dot_hammerspoon/`, `dot_claude/` -- `dot_config/nvim/`, `dot_config/starship.toml`, `dot_config/mise/config.toml` -- `private_Library/private_Application Support/Code/User/settings.json` — VSCode user settings +### Bootstrap Step Order -**Run scripts** (executed during `chezmoi apply`): -- `run_onchange_install-brew-packages.sh.tmpl` — re-runs when `dot_Brewfile` changes -- `run_onchange_configure-macos-defaults.sh` — `defaults write` for NSGlobalDomain, Finder, key repeat, Caps Lock → Control -- `run_onchange_configure-xcode.sh` — `defaults write` for Xcode build settings -- `run_once_install-zplug.sh` — bootstrap zplug -- `run_once_install-dein.sh` — bootstrap dein.vim -- `run_once_install-mise-tools.sh` — runs `mise install` for tools defined in `dot_config/mise/config.toml` +`mise bootstrap` runs steps in this fixed order (see `mise bootstrap --help`): -**Configuration**: -- `.chezmoiignore` — paths chezmoi should skip during apply (README, scripts/, CI files, destination-side local files) +1. `[bootstrap.packages]` install + `post-packages` hook → `mise run brew-bundle` (Homebrew formulae + casks from `Brewfile`). +2. `[dotfiles]` apply → symlinks under `home/`. +3. `[bootstrap.macos.defaults]` + `post-defaults` hook → declarative defaults, then `mise run macos-extra` for imperative settings (Caps Lock → Control, Xcode dynamic core count, `xcodes install`). +4. `mise install` → language runtimes in `[tools]` (Flutter, Rust, Vim). +5. `bootstrap` task → vim/zsh plugin managers (dein.vim, zplug). -**Repository support files** (excluded from `chezmoi apply` via `.chezmoiignore`): -- `.github/workflows/ci.yml` — lint, chezmoi-verify -- `README.md`, `CLAUDE.md`, `LICENSE` +Hooks delegate to tasks because hooks do **not** expand `{{config_root}}` and lack `$MISE_PROJECT_ROOT`; tasks do (and `mise run` works from a hook since the hook's cwd is the repo root). -### Run Script Execution Order +### Tasks (`[tasks]` in mise.toml) -`chezmoi apply` runs `run_*` scripts in lexical order of their filename. The current ordering ensures: +- `brew-bundle` — `brew bundle` from `Brewfile`. Uses `{{config_root}}/Brewfile`. +- `macos-extra` — imperative macOS settings with no declarative form. +- `bootstrap` — dein.vim + zplug installers. Runs **every** bootstrap, so each step self-gates on an existence check for idempotency. -1. `run_onchange_configure-macos-defaults.sh` -2. `run_onchange_configure-xcode.sh` -3. `run_onchange_install-brew-packages.sh` (installs `mise` via Brewfile) -4. `run_once_install-dein.sh` -5. `run_once_install-mise-tools.sh` (skips with a notice if `mise` is not yet on PATH) -6. `run_once_install-zplug.sh` +All tasks exit early when `CI` is set (`[ -n "${CI:-}" ] && exit 0`). -If `mise` is not yet installed when `run_once_install-mise-tools.sh` runs, the script exits cleanly. Re-running `chezmoi apply` after the brew bundle finishes will trigger it again. +### CI (`.github/workflows/ci.yml`) -All run scripts honor `CI=1` and exit early in CI to avoid expensive operations. +- **lint** (ubuntu): shellcheck for non-zsh `.sh`, `zsh -n` syntax check on `home/` zsh files. +- **mise-bootstrap-verify** (macOS): `brew install mise`, validate `mise.toml` (`tasks ls`, `fmt --check`), `mise dotfiles apply` to an ephemeral HOME and assert symlinks resolve, `mise bootstrap --dry-run`, and an idempotency check. Runs with `CI=1` and `MISE_EXPERIMENTAL=1`. ## Development Stack - **iOS Development**: Xcode, XcodeGen, xcbeautify (Homebrew) -- **Flutter / Rust / Node / Ruby**: managed by mise (`dot_config/mise/config.toml`) -- **Web Development**: Node.js (via mise), npm/yarn ecosystem -- **General**: Git, GitHub CLI, Neovim, Starship prompt +- **Flutter / Rust / Vim**: managed by mise (`[tools]` in `mise.toml`) +- **General**: Git, GitHub CLI, Neovim, Starship prompt, Ghostty, Hammerspoon ## Key Design Principles -1. **Single source of truth**: chezmoi manages all dotfiles; mise manages all language runtimes. +1. **Single source of truth**: `mise bootstrap` (`mise.toml`) manages dotfiles, packages, defaults, and tools; Homebrew owns package installation via `Brewfile`. 2. **macOS-only**: no OS branching. `defaults write` and other macOS-specific commands run unconditionally. -3. **Idempotency**: `run_once_*` scripts gate themselves on existence checks; `run_onchange_*` scripts re-run only when their content (or referenced files) change. -4. **Hand-off to upstream tools**: chezmoi delegates package management to Homebrew (`brew bundle`) and mise (`mise install`) rather than reimplementing version logic. +3. **Idempotency**: declarative steps (dotfiles, defaults, tools) converge; the `bootstrap` task self-gates on existence checks because it runs on every bootstrap. +4. **Hand-off to upstream tools**: bootstrap delegates package management to Homebrew (`brew bundle`) and runtime management to mise (`mise install`) rather than reimplementing version logic. +5. **Symlinks, not copies**: dotfiles live in `home/` and are symlinked into `$HOME`; the repo must remain present at its clone location. diff --git a/README.md b/README.md index 2ee81bd..1d5d09d 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,92 @@ # dotfiles -My personal dotfiles for macOS development environment, managed by [chezmoi](https://www.chezmoi.io/). +My personal dotfiles for macOS development environment, managed by [`mise bootstrap`](https://mise.jdx.dev/bootstrap.html). ## 🚀 Quick Start ```bash -# Install chezmoi if you don't have it -brew install chezmoi +# Install mise if you don't have it +brew install mise -# Initialize and apply this repository -chezmoi init --apply ry-itto/dotfiles +# Clone and bootstrap (MISE_EXPERIMENTAL is required the first time — see below) +ghq get ry-itto/dotfiles # or: git clone https://github.com/ry-itto/dotfiles +cd "$(ghq root)/github.com/ry-itto/dotfiles" +MISE_EXPERIMENTAL=1 mise bootstrap --yes ``` ## 📋 Requirements - macOS (this configuration is macOS-only) - Command Line Tools for Xcode +- [mise](https://mise.jdx.dev/) ≥ `2026.6.6` (the `mise bootstrap` command was introduced there) - Internet connection for downloading packages ## 🛠 Installation ```bash -brew install chezmoi -chezmoi init --apply ry-itto/dotfiles +brew install mise +ghq get ry-itto/dotfiles +cd "$(ghq root)/github.com/ry-itto/dotfiles" +MISE_EXPERIMENTAL=1 mise bootstrap --yes ``` -`chezmoi init --apply` will: +`mise bootstrap` runs these steps in order (see `mise bootstrap --help`): -1. Clone this repository into `~/.local/share/chezmoi` -2. Render dotfiles into `$HOME` (e.g. `dot_zshrc` → `~/.zshrc`) -3. Run `run_onchange_install-brew-packages.sh` to install Homebrew bundle from `~/.Brewfile` -4. Run `run_onchange_configure-macos-defaults.sh` and `run_onchange_configure-xcode.sh` to apply system defaults -5. Run `run_once_install-zplug.sh`, `run_once_install-dein.sh` to bootstrap shell/editor plugin managers -6. Run `run_once_install-mise-tools.sh` to install any tools defined in `dot_config/mise/config.toml` (グローバルでは言語ランタイムを固定しない方針 — 詳細は [プログラミング言語の管理方針](#-プログラミング言語の管理方針)) +1. **Homebrew packages** — the `post-packages` hook runs `mise run brew-bundle`, which installs formulae **and casks** from the `Brewfile` (mise's native `[bootstrap.packages]` only supports formulae, so the `Brewfile` stays the source of truth). +2. **Dotfiles** — `[dotfiles]` symlinks the sources under `home/` into `$HOME` (e.g. `home/.zshrc` → `~/.zshrc`). +3. **macOS defaults** — `[bootstrap.macos.defaults]` writes Finder/keyboard/Xcode settings; the `post-defaults` hook runs `mise run macos-extra` for the imperative bits (Caps Lock → Control, Xcode dynamic core count, `xcodes install`). +4. **Tools** — `mise install` installs the language runtimes in `[tools]` (グローバルでは言語ランタイムを固定しない方針 — 詳細は [プログラミング言語の管理方針](#-プログラミング言語の管理方針)). +5. **`bootstrap` task** — installs the vim/zsh plugin managers (dein.vim, zplug). + +> **Why `MISE_EXPERIMENTAL=1`?** `mise bootstrap` is experimental. On an already-bootstrapped machine `experimental = true` lives in `~/.config/mise/config.toml` (symlinked from `home/.config/mise/config.toml`), but on a fresh machine that file does not exist yet, so the **first** bootstrap must set the env var. + +> **One-time mise consolidation:** if a self-installed mise exists at `~/.local/bin/mise` (from `curl mise.run`), it can shadow the Homebrew mise on `PATH`. This repo standardises on the Homebrew mise — remove the self-installed one (`rm ~/.local/bin/mise`) so `mise activate` and `mise bootstrap` use the Homebrew build. ## 🔄 Daily Operations ```bash -# Pull latest changes from this repo and re-apply -chezmoi update +# Pull latest changes and re-apply everything +git -C "$(ghq root)/github.com/ry-itto/dotfiles" pull +mise bootstrap --yes -# Edit a managed file (opens source file in $EDITOR) -chezmoi edit ~/.zshrc +# Preview what bootstrap would do (no changes made) +mise bootstrap --dry-run -# See what would change before applying -chezmoi diff +# Apply only the dotfile symlinks +mise dotfiles apply -# Add a new file from $HOME into management -chezmoi add ~/.somefile +# Show dotfile / package / defaults status +mise dotfiles status +mise bootstrap packages status ``` -> **Note:** After migrating to chezmoi, editing files in `$HOME` directly does **not** sync back to this repository. Use `chezmoi edit` or edit the source file under `~/.local/share/chezmoi`, then `chezmoi apply`. +> **Note:** dotfiles are **symlinks** into this repository (not copies). Editing `~/.zshrc` edits `home/.zshrc` in the repo directly — no separate "apply" step is needed for content changes. The flip side: **do not delete or move the cloned repo**, or the symlinks will break. New entries (`[tools]`, `[dotfiles]`, packages) still require a `mise bootstrap` run. ## 📂 Directory Structure ``` . -├── dot_zshrc # → ~/.zshrc -├── dot_zsh/ # → ~/.zsh/ -│ ├── alias.zsh -│ ├── env.zsh -│ ├── plugin.zsh -│ ├── style.zsh -│ ├── functions/ -│ └── bin/executable_reload # → ~/.zsh/bin/reload (chmod +x) -├── dot_gitconfig -├── dot_Brewfile -├── dot_vim/ -├── dot_hammerspoon/ -├── dot_claude/ # → ~/.claude/ -├── dot_config/ -│ ├── ghostty/config -│ ├── nvim/ -│ ├── starship.toml -│ └── mise/config.toml # mise settings -├── private_Library/ -│ └── private_Application Support/Code/User/settings.json # → VSCode settings -├── run_onchange_install-brew-packages.sh.tmpl -├── run_onchange_configure-macos-defaults.sh -├── run_onchange_configure-xcode.sh -├── run_once_install-zplug.sh -├── run_once_install-dein.sh -├── run_once_install-mise-tools.sh -├── .chezmoiignore # files to skip during apply +├── mise.toml # bootstrap config: [tools] / [dotfiles] / [bootstrap.*] / [tasks] +├── Brewfile # Homebrew formulae + casks (installed via the brew-bundle task) +├── home/ # source tree mirroring $HOME (symlinked by `mise dotfiles apply`) +│ ├── .zshrc # → ~/.zshrc +│ ├── .zsh/ # → ~/.zsh/ +│ │ ├── alias.zsh +│ │ ├── env.zsh +│ │ ├── plugin.zsh +│ │ ├── style.zsh +│ │ ├── functions/ +│ │ └── bin/reload # → ~/.zsh/bin/reload (executable) +│ ├── .gitconfig +│ ├── .vim/ +│ ├── .hammerspoon/ +│ ├── .claude/settings.json # → ~/.claude/settings.json (only this file is symlinked) +│ ├── .config/ +│ │ ├── ghostty/config +│ │ ├── nvim/ +│ │ ├── starship.toml +│ │ └── mise/config.toml # mise settings (incl. experimental = true) +│ └── Library/Application Support/Code/User/settings.json # → VSCode settings └── .github/workflows/ci.yml ``` @@ -108,7 +112,7 @@ chezmoi add ~/.somefile ### Zsh -Modular configuration in `dot_zsh/`: +Modular configuration in `home/.zsh/`: - `alias.zsh`: Custom command aliases - `env.zsh`: Environment variables and PATH setup @@ -117,18 +121,18 @@ Modular configuration in `dot_zsh/`: ### Git -`dot_gitconfig` provides commit template, GitHub CLI helpers, and standard pull/credential settings. +`home/.gitconfig` provides commit template, GitHub CLI helpers, and standard pull/credential settings. ### Vim -Vim setup is wired up via dein.vim. Plugin manifests live under `dot_vim/rc/`. +Vim setup is wired up via dein.vim. Plugin manifests live under `home/.vim/rc/`. ## 🔧 Customization 1. **Fork this repository** to create your own version -2. **Edit configurations** under `~/.local/share/chezmoi/` (or via `chezmoi edit`) -3. **Apply changes** with `chezmoi apply` -4. **Modify packages** in `dot_Brewfile` +2. **Edit configurations** under `home/` (or edit the symlinked file in `$HOME` directly — it's the same file) +3. **Apply new entries** (tools, dotfiles, packages) with `mise bootstrap` — content edits to already-symlinked files need no re-apply +4. **Modify packages** in `Brewfile` ## 🧭 プログラミング言語の管理方針 @@ -138,7 +142,7 @@ Vim setup is wired up via dein.vim. Plugin manifests live under `dot_vim/rc/`. - **プロジェクト側で指定されている場合**: そのプロジェクトの `mise.toml` / `.tool-versions` / `.node-version` / `.ruby-version` などに従い、mise(または各プロジェクト指定の方法)で導入する。 - **その他、ローカルで一時的に必要になった場合**: `mise use -g @` などで都度グローバルに入れる。dotfiles 側にはコミットしない。 -- **dotfiles 管理下の `dot_config/mise/config.toml`**: 言語ランタイムのバージョンは記述しない。mise 自体の設定(例: `idiomatic_version_file_enable_tools`)に限る。 +- **dotfiles 管理下の `home/.config/mise/config.toml`**: 言語ランタイムのバージョンは記述しない。mise 自体の設定(例: `idiomatic_version_file_enable_tools`、`experimental`)に限る。なお、この dotfiles 環境自体が使う Flutter / Rust / Vim はリポジトリ直下の `mise.toml` の `[tools]` で定義する。 ### 理由 diff --git a/dot_config/mise/config.toml b/dot_config/mise/config.toml deleted file mode 100644 index 47ea4f7..0000000 --- a/dot_config/mise/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[settings] -idiomatic_version_file_enable_tools = ["ruby"] diff --git a/dot_claude/settings.json b/home/.claude/settings.json similarity index 100% rename from dot_claude/settings.json rename to home/.claude/settings.json diff --git a/dot_config/ghostty/config b/home/.config/ghostty/config similarity index 100% rename from dot_config/ghostty/config rename to home/.config/ghostty/config diff --git a/home/.config/mise/config.toml b/home/.config/mise/config.toml new file mode 100644 index 0000000..d2ba803 --- /dev/null +++ b/home/.config/mise/config.toml @@ -0,0 +1,5 @@ +[settings] +idiomatic_version_file_enable_tools = ["ruby"] +# Required by `mise bootstrap` (experimental). On a fresh machine this file does +# not exist yet, so the first bootstrap must be run with MISE_EXPERIMENTAL=1. +experimental = true diff --git a/dot_config/nvim/autoload/plugins/nvim_lsp.vim b/home/.config/nvim/autoload/plugins/nvim_lsp.vim similarity index 100% rename from dot_config/nvim/autoload/plugins/nvim_lsp.vim rename to home/.config/nvim/autoload/plugins/nvim_lsp.vim diff --git a/dot_config/nvim/init.vim b/home/.config/nvim/init.vim similarity index 100% rename from dot_config/nvim/init.vim rename to home/.config/nvim/init.vim diff --git a/dot_config/starship.toml b/home/.config/starship.toml similarity index 100% rename from dot_config/starship.toml rename to home/.config/starship.toml diff --git a/dot_gitconfig b/home/.gitconfig similarity index 100% rename from dot_gitconfig rename to home/.gitconfig diff --git a/dot_hammerspoon/init.lua b/home/.hammerspoon/init.lua similarity index 100% rename from dot_hammerspoon/init.lua rename to home/.hammerspoon/init.lua diff --git a/dot_vim/README.md b/home/.vim/README.md similarity index 100% rename from dot_vim/README.md rename to home/.vim/README.md diff --git a/dot_vim/ftdetect/cocoapods.vim b/home/.vim/ftdetect/cocoapods.vim similarity index 100% rename from dot_vim/ftdetect/cocoapods.vim rename to home/.vim/ftdetect/cocoapods.vim diff --git a/dot_vim/ftdetect/fastlane.vim b/home/.vim/ftdetect/fastlane.vim similarity index 100% rename from dot_vim/ftdetect/fastlane.vim rename to home/.vim/ftdetect/fastlane.vim diff --git a/dot_vim/ftplugin/dart.vim b/home/.vim/ftplugin/dart.vim similarity index 100% rename from dot_vim/ftplugin/dart.vim rename to home/.vim/ftplugin/dart.vim diff --git a/dot_vim/ftplugin/ruby.vim b/home/.vim/ftplugin/ruby.vim similarity index 100% rename from dot_vim/ftplugin/ruby.vim rename to home/.vim/ftplugin/ruby.vim diff --git a/dot_vim/ftplugin/yaml.vim b/home/.vim/ftplugin/yaml.vim similarity index 100% rename from dot_vim/ftplugin/yaml.vim rename to home/.vim/ftplugin/yaml.vim diff --git a/dot_vim/init/bindings.vim b/home/.vim/init/bindings.vim similarity index 100% rename from dot_vim/init/bindings.vim rename to home/.vim/init/bindings.vim diff --git a/dot_vim/init/setting.vim b/home/.vim/init/setting.vim similarity index 100% rename from dot_vim/init/setting.vim rename to home/.vim/init/setting.vim diff --git a/dot_vim/rc/dein.toml b/home/.vim/rc/dein.toml similarity index 100% rename from dot_vim/rc/dein.toml rename to home/.vim/rc/dein.toml diff --git a/dot_vim/rc/dein_lazy.toml b/home/.vim/rc/dein_lazy.toml similarity index 100% rename from dot_vim/rc/dein_lazy.toml rename to home/.vim/rc/dein_lazy.toml diff --git a/dot_zsh/alias.zsh b/home/.zsh/alias.zsh similarity index 100% rename from dot_zsh/alias.zsh rename to home/.zsh/alias.zsh diff --git a/dot_zsh/bin/executable_reload b/home/.zsh/bin/reload similarity index 100% rename from dot_zsh/bin/executable_reload rename to home/.zsh/bin/reload diff --git a/dot_zsh/env.zsh b/home/.zsh/env.zsh similarity index 94% rename from dot_zsh/env.zsh rename to home/.zsh/env.zsh index 3cd417d..15b2c40 100644 --- a/dot_zsh/env.zsh +++ b/home/.zsh/env.zsh @@ -81,9 +81,8 @@ setopt pushd_ignore_dups # フローコントロール無効 setopt no_flow_control -# mise -export PATH="$HOME/.local/bin:$PATH" -eval "$(~/.local/bin/mise activate zsh)" +# mise (installed via Homebrew; resolved from PATH after `brew shellenv` above) +eval "$(mise activate zsh)" # flutter sdk export FLUTTER_ROOT="$(mise where flutter 2>/dev/null || echo '')" diff --git a/dot_zsh/functions/functions.zsh b/home/.zsh/functions/functions.zsh similarity index 100% rename from dot_zsh/functions/functions.zsh rename to home/.zsh/functions/functions.zsh diff --git a/dot_zsh/functions/fzf-git.sh b/home/.zsh/functions/fzf-git.sh similarity index 100% rename from dot_zsh/functions/fzf-git.sh rename to home/.zsh/functions/fzf-git.sh diff --git a/dot_zsh/functions/ghq.zsh b/home/.zsh/functions/ghq.zsh similarity index 100% rename from dot_zsh/functions/ghq.zsh rename to home/.zsh/functions/ghq.zsh diff --git a/dot_zsh/functions/git.zsh b/home/.zsh/functions/git.zsh similarity index 100% rename from dot_zsh/functions/git.zsh rename to home/.zsh/functions/git.zsh diff --git a/dot_zsh/functions/github.zsh b/home/.zsh/functions/github.zsh similarity index 100% rename from dot_zsh/functions/github.zsh rename to home/.zsh/functions/github.zsh diff --git a/dot_zsh/functions/wtp.zsh b/home/.zsh/functions/wtp.zsh similarity index 100% rename from dot_zsh/functions/wtp.zsh rename to home/.zsh/functions/wtp.zsh diff --git a/dot_zsh/plugin.zsh b/home/.zsh/plugin.zsh similarity index 100% rename from dot_zsh/plugin.zsh rename to home/.zsh/plugin.zsh diff --git a/dot_zsh/style.zsh b/home/.zsh/style.zsh similarity index 100% rename from dot_zsh/style.zsh rename to home/.zsh/style.zsh diff --git a/dot_zshrc b/home/.zshrc similarity index 100% rename from dot_zshrc rename to home/.zshrc diff --git a/private_Library/private_Application Support/Code/User/settings.json b/home/Library/Application Support/Code/User/settings.json similarity index 100% rename from private_Library/private_Application Support/Code/User/settings.json rename to home/Library/Application Support/Code/User/settings.json diff --git a/mise-tasks/bootstrap b/mise-tasks/bootstrap new file mode 100755 index 0000000..a568711 --- /dev/null +++ b/mise-tasks/bootstrap @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +#MISE description="Install vim/zsh plugin managers (dein, zplug)" +# +# Runs at bootstrap step 8 (after tools). `mise bootstrap` runs the task named +# `bootstrap` on every invocation, so each step self-gates on an existence check. +set -eu + +[ -n "${CI:-}" ] && exit 0 + +# dein.vim +if [ ! -d "$HOME/.cache/dein" ]; then + sh -c "$(curl -fsSL https://raw.githubusercontent.com/Shougo/dein-installer.vim/master/installer.sh)" +fi + +# zplug +if [ ! -d "${ZPLUG_HOME:-$HOME/.zplug}" ]; then + curl -sL --proto-redir -all,https https://raw.githubusercontent.com/zplug/installer/master/installer.zsh | zsh +fi diff --git a/mise-tasks/brew-bundle b/mise-tasks/brew-bundle new file mode 100755 index 0000000..d038d86 --- /dev/null +++ b/mise-tasks/brew-bundle @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +#MISE description="Install Homebrew packages (formulae + casks) from Brewfile" +# +# mise's native [bootstrap.packages] only resolves Homebrew formulae, so the +# Brewfile (formulae + casks + HEAD) is the source of truth. Run from the +# `post-packages` bootstrap hook (mise run brew-bundle). +set -eu + +[ -n "${CI:-}" ] && exit 0 + +if ! type brew >/dev/null 2>&1; then + echo '`brew` not found. Install Homebrew' + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +fi + +if brew bundle check --file "$MISE_PROJECT_ROOT/Brewfile"; then + echo '`brew` dependencies are satisfied' +else + echo 'Install `brew` dependencies...' + brew bundle --file "$MISE_PROJECT_ROOT/Brewfile" -v +fi diff --git a/mise-tasks/macos-extra b/mise-tasks/macos-extra new file mode 100755 index 0000000..2844c93 --- /dev/null +++ b/mise-tasks/macos-extra @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +#MISE description="Imperative macOS settings: Caps Lock -> Control, Xcode dynamic build, xcodes" +# +# Settings that have no declarative form in [bootstrap.macos.defaults]. Run from +# the `post-defaults` bootstrap hook (mise run macos-extra). +set -eu + +[ -n "${CI:-}" ] && exit 0 + +# Map Caps Lock to Control +defaults write com.apple.keyboard.modifiermapping.1452-640-0 -array-add '{ "HIDKeyboardModifierMappingSrc" = 2; "HIDKeyboardModifierMappingDst" = 4; }' + +# Use all available cores for Xcode parallel builds +defaults write com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks "$(sysctl -n hw.ncpu)" + +# Install latest Xcode when no toolchain is present (requires `xcodes`) +if ! type xcodebuild >/dev/null 2>&1 && type xcodes >/dev/null 2>&1; then + xcodes install --latest +fi diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..55a62f2 --- /dev/null +++ b/mise.toml @@ -0,0 +1,71 @@ +# mise bootstrap configuration for this dotfiles repository. +# +# Set up a new machine (mise itself must already be installed, e.g. `brew install mise`): +# +# git clone && cd +# MISE_EXPERIMENTAL=1 mise bootstrap --yes +# +# `mise bootstrap` is experimental and must be enabled. On an already +# bootstrapped machine `experimental = true` lives in ~/.config/mise/config.toml +# (symlinked from home/.config/mise/config.toml); on a fresh machine that file +# does not exist yet, so pass MISE_EXPERIMENTAL=1 the first time. +# +# Bootstrap step order (see `mise bootstrap --help`): +# 1. [bootstrap.packages] install + post-packages hook -> Homebrew via Brewfile +# 2. [dotfiles] apply -> symlinks below +# 3. [bootstrap.macos.defaults] + post-defaults hook -> macOS settings +# 7. mise install ([tools]) -> language runtimes +# 8. task `bootstrap` -> vim/zsh plugin managers + +[tools] +# Flutter development +flutter = "latest" +# Rust development +rust = "stable" +# Vim/Neovim +vim = "latest" + +# --------------------------------------------------------------------------- +# Dotfiles — symlink sources under home/ to their $HOME targets. +# Applied by `mise bootstrap` (or `mise dotfiles apply`). +# Whole directories that are entirely ours are symlinked as a unit; files that +# share a parent with non-managed content are symlinked individually. +# --------------------------------------------------------------------------- +[dotfiles] +"~/.zshrc" = { mode = "symlink", source = "home/.zshrc" } +"~/.zsh" = { mode = "symlink", source = "home/.zsh" } +"~/.vim" = { mode = "symlink", source = "home/.vim" } +"~/.gitconfig" = { mode = "symlink", source = "home/.gitconfig" } +"~/.hammerspoon" = { mode = "symlink", source = "home/.hammerspoon" } +"~/.claude/settings.json" = { mode = "symlink", source = "home/.claude/settings.json" } +"~/.config/nvim" = { mode = "symlink", source = "home/.config/nvim" } +"~/.config/ghostty" = { mode = "symlink", source = "home/.config/ghostty" } +"~/.config/starship.toml" = { mode = "symlink", source = "home/.config/starship.toml" } +"~/.config/mise/config.toml" = { mode = "symlink", source = "home/.config/mise/config.toml" } +"~/Library/Application Support/Code/User/settings.json" = { mode = "symlink", source = "home/Library/Application Support/Code/User/settings.json" } + +# --------------------------------------------------------------------------- +# macOS defaults (declarative). Replaces run_onchange_configure-macos-defaults.sh +# and the static defaults of run_onchange_configure-xcode.sh. Imperative bits +# (Caps Lock remap, dynamic core count, xcodes install) live in the +# post-defaults hook / macos-extra task below. +# --------------------------------------------------------------------------- +[bootstrap.macos.defaults] +"NSGlobalDomain" = { AppleShowAllExtensions = true, KeyRepeat = 2, InitialKeyRepeat = 25, ApplePressAndHoldEnabled = false } +"com.apple.finder" = { AppleShowAllFiles = true } +"com.apple.dt.Xcode" = { ShowBuildOperationDuration = true, DVTTextEditorTrimWhitespaceOnlyLines = true } + +# --------------------------------------------------------------------------- +# Hooks — delegate to file tasks under mise-tasks/. (Hooks do not expand +# {{config_root}} and lack $MISE_PROJECT_ROOT, but their cwd is the repo root +# and `mise run` works, so the logic lives in the task scripts where +# $MISE_PROJECT_ROOT is available.) +# --------------------------------------------------------------------------- +[bootstrap.hooks] +post-packages = "mise run brew-bundle" +post-defaults = "mise run macos-extra" + +# Tasks are defined as executable scripts under mise-tasks/ (auto-discovered): +# mise-tasks/brew-bundle — Homebrew formulae + casks from Brewfile +# mise-tasks/macos-extra — imperative macOS settings (Caps Lock, Xcode, xcodes) +# mise-tasks/bootstrap — vim/zsh plugin managers; runs at bootstrap step 8 diff --git a/run_once_install-dein.sh b/run_once_install-dein.sh deleted file mode 100755 index a9fc50e..0000000 --- a/run_once_install-dein.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/zsh -set -eu - -[[ -n "${CI:-}" ]] && exit 0 - -INSTALLATION_DIR="$HOME/.cache/dein" - -if [ -d "$INSTALLATION_DIR" ]; then - exit 0 -fi - -tmp_dir=$(mktemp -d) -trap 'rm -rf "$tmp_dir"' EXIT - -sh -c "$(curl -fsSL https://raw.githubusercontent.com/Shougo/dein-installer.vim/master/installer.sh)" diff --git a/run_once_install-mise-tools.sh b/run_once_install-mise-tools.sh deleted file mode 100755 index 97dbbf8..0000000 --- a/run_once_install-mise-tools.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/zsh -set -eu - -[[ -n "${CI:-}" ]] && exit 0 - -if ! command -v mise >/dev/null 2>&1; then - echo 'mise is not installed yet. Skipping tool install. Run `chezmoi apply` again after `brew bundle --global` finishes.' - exit 0 -fi - -mise install diff --git a/run_once_install-zplug.sh b/run_once_install-zplug.sh deleted file mode 100755 index bbdafac..0000000 --- a/run_once_install-zplug.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/zsh -set -eu - -[[ -n "${CI:-}" ]] && exit 0 - -if [ ! -d "${ZPLUG_HOME:-$HOME/.zplug}" ]; then - curl -sL --proto-redir -all,https https://raw.githubusercontent.com/zplug/installer/master/installer.zsh | zsh -fi diff --git a/run_onchange_configure-macos-defaults.sh b/run_onchange_configure-macos-defaults.sh deleted file mode 100755 index 3d75051..0000000 --- a/run_onchange_configure-macos-defaults.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/zsh -set -eu - -[[ -n "${CI:-}" ]] && exit 0 - -# NSGlobalDomain -# Show all file extensions -defaults write NSGlobalDomain AppleShowAllExtensions -bool true - -# Key repeat speed -defaults write NSGlobalDomain KeyRepeat -int 2 - -# Initial key repeat delay -defaults write NSGlobalDomain InitialKeyRepeat -int 25 - -# Show hidden files in Finder -defaults write com.apple.finder AppleShowAllFiles TRUE - -# Disable press-and-hold for keys (allows key repeat) -defaults write -g ApplePressAndHoldEnabled -bool false - -# Map Caps Lock to Control -defaults write com.apple.keyboard.modifiermapping.1452-640-0 -array-add '{ "HIDKeyboardModifierMappingSrc" = 2; "HIDKeyboardModifierMappingDst" = 4; }' diff --git a/run_onchange_configure-xcode.sh b/run_onchange_configure-xcode.sh deleted file mode 100755 index 3ed6f4d..0000000 --- a/run_onchange_configure-xcode.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/zsh -set -eu - -[[ -n "${CI:-}" ]] && exit 0 - -if ! type "xcodebuild" > /dev/null; then - xcodes install --latest -fi - -# Show build duration in Xcode -defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES - -# Use all available cores for parallel builds -cores=$(sysctl -n hw.ncpu) -defaults write com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks "$cores" - -# Trim whitespace-only lines -defaults write com.apple.dt.Xcode DVTTextEditorTrimWhitespaceOnlyLines -bool true diff --git a/run_onchange_install-brew-packages.sh.tmpl b/run_onchange_install-brew-packages.sh.tmpl deleted file mode 100755 index 8c1fbf1..0000000 --- a/run_onchange_install-brew-packages.sh.tmpl +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/zsh -set -eu - -[[ -n "${CI:-}" ]] && exit 0 - -if ! type "brew" > /dev/null; then - echo '`brew` not found. Install Homebrew' - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -fi - -if brew bundle check --global; then - echo '`brew` dependencies are satisfied' -else - echo 'Install `brew` dependencies...' - brew bundle --global -v -fi - -# brewfile hash: {{ include "dot_Brewfile" | sha256sum }}