diff --git a/.gitignore b/.gitignore index e28f782..1b478ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /bin .DS_Store home/.chezmoidata/brewfile.yml +*.rayconfig diff --git a/README.md b/README.md index 31eb4bf..674dc12 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Shell, Git, editor, and XDG configuration also work on Linux where supported. | Editors | Zed, VS Code, Vim, and Neovim | | Shortcuts | Raycast Caps Lock Hyper translated by Ghostty into tmux commands | | Packages | Homebrew bundle with personal and work profiles | -| Secrets | Owner SSH key encrypted with age; Raycast export encrypted by Raycast | +| Secrets | Owner SSH key encrypted with age; machine-specific data stays local | | macOS | Dock, Finder, keyboard, Safari, and general defaults | ## Install @@ -27,9 +27,13 @@ cd ~/.local/share/chezmoi ./init.sh --macos ``` -Use `WORK=true ./init.sh --macos` for the work package profile. Run -`./init.sh --help` for Git identity and signing-key options. Omit `--macos` to -apply the dotfiles without changing macOS defaults. +Use `WORK=true ./init.sh --macos` for the work package profile. An existing +`~/.gitconfig` is left unchanged and any detected global identity is reused. If +the file does not exist, bootstrap prompts for a missing Git name or email +before creating the default config. Put machine-specific overrides or signing +settings in `~/.gitconfig.local`. On new machines, the root config includes +repository defaults from the managed `~/.config/git/dotfiles.gitconfig`. Omit +`--macos` to apply the dotfiles without changing macOS defaults. ### SSH and age @@ -54,12 +58,15 @@ load the key on first use. Shell startup does not call `ssh-add`. | Component | Once per machine | | --- | --- | -| Raycast | Import `~/.config/raycast/raycast.rayconfig` using Raycast's import action | +| Raycast | Configure Caps Lock as Hyper (`Control+Option+Command`, no Shift) | | Raycast scripts | Add `~/.config/raycast/scripts/` as a Script Command directory | | Neovim | Open once to finish Lazy, Mason, and tree-sitter setup | -The Raycast export passphrase is separate from age. tmux plugins install through -TPM; Continuum and Resurrect handle session restoration. +Raycast exports are intentionally not managed because they can contain private +local data and did not reliably restore settings. To add a Ghostty launcher, +open Raycast's Applications extension, add a Command for Ghostty, and assign the +hotkey there. tmux plugins install through TPM; Continuum and Resurrect handle +session restoration. ## Terminal shortcuts @@ -69,6 +76,8 @@ symbols. | Shortcut | Action | | --- | --- | +| `Command+K` | Clear the active application screen through tmux | +| `Shift+Enter` | Insert a newline in Claude Code and shell prompts | | `Caps+C` | New window | | `Caps+I` | Side-by-side pane | | `Caps+-` | Stacked pane | @@ -99,7 +108,7 @@ Machine-specific overrides use unmanaged `*.local` files such as | --- | --- | | `home/dot_config/ghostty/` | Ghostty and Hyper mappings | | `home/dot_config/tmux/` | tmux, status scripts, and plugins | -| `home/dot_config/raycast/` | Raycast export and Script Commands | +| `home/dot_config/raycast/` | Raycast Script Commands | | `home/dot_config/zed/` | Zed settings and keybindings | | `home/dot_config/Code/User/` | VS Code XDG settings | | `home/dot_config/nvim/` | Neovim configuration | diff --git a/home/.chezmoi.toml.tmpl b/home/.chezmoi.toml.tmpl index b75721a..5d26f6e 100644 --- a/home/.chezmoi.toml.tmpl +++ b/home/.chezmoi.toml.tmpl @@ -13,14 +13,12 @@ recipient = "age1x8xj89ps3n8j2cy6guv2n9l788q0ud39yq0pw72lxehgdl2fdv8qqa0lfw" {{ $name := promptStringOnce . "name" "name" }} {{ $email := promptStringOnce . "email" "email" }} -{{ $signingKey := promptStringOnce . "signingKey" "signingKey" }} {{/* WORK=true selects the work machine setup; anything else (incl. PERSONAL=true) is personal. */}} {{ $work := eq (env "WORK" | lower) "true" }} [data] name = {{ $name | quote }} email = {{ $email | quote }} -signingKey = {{ $signingKey | quote }} profile = {{ ternary "work" "personal" $work | quote }} work = {{ $work }} personal = {{ not $work }} diff --git a/home/create_dot_gitconfig.tmpl b/home/create_dot_gitconfig.tmpl new file mode 100644 index 0000000..83eaeeb --- /dev/null +++ b/home/create_dot_gitconfig.tmpl @@ -0,0 +1,7 @@ +[user] + name = {{ .name | quote }} + email = {{ .email | quote }} + useConfigOnly = true + +[include] + path = {{ joinPath .chezmoi.homeDir ".config/git/dotfiles.gitconfig" | quote }} diff --git a/home/dot_aliases b/home/dot_aliases index 325cdbe..f52359a 100644 --- a/home/dot_aliases +++ b/home/dot_aliases @@ -62,7 +62,7 @@ alias confaliasl='${EDITOR:-nvim} ~/.aliases.local' alias confexports='chezmoi edit --apply ~/.exports' alias confexportsl='${EDITOR:-nvim} ~/.exports.local' alias conffunctionsl='${EDITOR:-nvim} ~/.functions.local' -alias confgit='chezmoi edit --apply ~/.gitconfig' +alias confgit='chezmoi edit --apply ~/.config/git/dotfiles.gitconfig' alias confgitl='${EDITOR:-nvim} ~/.gitconfig.local' alias confzcompl='chezmoi edit --apply ~/.zsh_completions' alias confzcompll='${EDITOR:-nvim} ~/.zsh_completions.local' diff --git a/home/dot_config/ghostty/config b/home/dot_config/ghostty/config.ghostty similarity index 87% rename from home/dot_config/ghostty/config rename to home/dot_config/ghostty/config.ghostty index 0eecfad..e215f74 100644 --- a/home/dot_config/ghostty/config +++ b/home/dot_config/ghostty/config.ghostty @@ -45,12 +45,8 @@ scrollbar = system # Visual bell without sound bell-features = no-system,no-audio,attention,border -# Clear the active tmux pane's history, then redraw its shell prompt. -keybind = super+k=text:\x01:clear-history\r\x0c - -# Zsh's default Ctrl+V widget quotes the following LF into the edit buffer. -# This works through tmux without enabling its extended-key protocol. -keybind = shift+enter=text:\x16\n +# Send the standard clear-screen key through tmux to the active application. +keybind = super+k=text:\x0c # Shell shell-integration = detect diff --git a/home/dot_gitconfig.tmpl b/home/dot_config/git/dotfiles.gitconfig.tmpl similarity index 94% rename from home/dot_gitconfig.tmpl rename to home/dot_config/git/dotfiles.gitconfig.tmpl index 044f4fb..dab0d48 100644 --- a/home/dot_gitconfig.tmpl +++ b/home/dot_config/git/dotfiles.gitconfig.tmpl @@ -1,16 +1,3 @@ -[user] - name = {{ .name | quote }} - email = {{ .email | quote }} - useConfigOnly = true -{{- if .signingKey }} - signingkey = {{ .signingKey | quote }} -{{- end }} - -{{- if .signingKey }} -[commit] - gpgsign = true -{{- end }} - [alias] # Helpers aliases = config --get-regexp "^alias\\." diff --git a/home/dot_config/raycast/raycast.rayconfig b/home/dot_config/raycast/raycast.rayconfig deleted file mode 100644 index 7e5ab9c..0000000 Binary files a/home/dot_config/raycast/raycast.rayconfig and /dev/null differ diff --git a/home/dot_config/tmux/tmux.conf b/home/dot_config/tmux/tmux.conf index 2efa7e6..595f9ec 100644 --- a/home/dot_config/tmux/tmux.conf +++ b/home/dot_config/tmux/tmux.conf @@ -6,7 +6,9 @@ bind C-a send-prefix # Terminal set -g default-terminal "tmux-256color" set -su terminal-features -set -s terminal-features[3] "xterm-ghostty:RGB" +set -s terminal-features[3] "xterm-ghostty:RGB:extkeys" +set -s extended-keys on +set -g focus-events on # Mouse / clipboard set -g mouse on diff --git a/init.sh b/init.sh index 62a662d..2bc9091 100755 --- a/init.sh +++ b/init.sh @@ -3,7 +3,6 @@ set -euo pipefail git_name="${GIT_NAME:-}" git_email="${GIT_EMAIL:-}" -git_signing_key="${GIT_SIGNING_KEY:-}" run_macos=false usage() { @@ -11,23 +10,12 @@ usage() { Usage: ./init.sh [options] Options: - --name="Your Name" Git user.name for this machine - --email="you@example" Git user.email for this machine - --signing-key="KEY" Git user.signingkey (SSH key / GPG key id) --macos Run macOS post-setup after apply -h, --help Show this help -Environment: - GIT_NAME - GIT_EMAIL - GIT_SIGNING_KEY - Notes: - Values for name/email/signing-key are stored by chezmoi in - ~/.config/chezmoi/chezmoi.toml the first time they're provided. - - Omit the flags on subsequent runs to reuse stored values. - Chezmoi will prompt interactively if a value is still missing. + An existing ~/.gitconfig is preserved and its global identity is reused. + If no identity is configured, bootstrap prompts for a Git name and email. EOF exit 0 } @@ -46,15 +34,6 @@ error() { while [[ $# -gt 0 ]]; do case "$1" in - --name=*) - git_name="${1#*=}" - ;; - --email=*) - git_email="${1#*=}" - ;; - --signing-key=*) - git_signing_key="${1#*=}" - ;; --macos) run_macos=true ;; @@ -90,11 +69,23 @@ apply_dotfiles() { exit 1 fi + if [[ -z "${git_name}" ]]; then + git_name="$(git config --global --get user.name 2>/dev/null || true)" + fi + if [[ -z "${git_email}" ]]; then + git_email="$(git config --global --get user.email 2>/dev/null || true)" + fi + local args=(init --apply "${PWD}") - [[ -n "${git_name}" ]] && args+=(--promptString "name=${git_name}") - [[ -n "${git_email}" ]] && args+=(--promptString "email=${git_email}") - [[ -n "${git_signing_key}" ]] && args+=(--promptString "signingKey=${git_signing_key}") + if [[ -e "${HOME}/.gitconfig" ]]; then + # The create-only template will preserve this file; blank values avoid + # asking irrelevant questions when identity comes from conditional includes. + args+=(--promptString "name=${git_name}" --promptString "email=${git_email}") + else + [[ -n "${git_name}" ]] && args+=(--promptString "name=${git_name}") + [[ -n "${git_email}" ]] && args+=(--promptString "email=${git_email}") + fi info "Applying dotfiles from local repo..." chezmoi "${args[@]}" diff --git a/test.sh b/test.sh index a070c8c..4c7ca10 100755 --- a/test.sh +++ b/test.sh @@ -54,7 +54,6 @@ chezmoi execute-template \ --init \ --promptString 'name=CI' \ --promptString 'email=ci@example.invalid' \ - --promptString 'signingKey=' \ < home/.chezmoi.toml.tmpl \ > "${rendered_config}" @@ -64,7 +63,6 @@ chezmoi --config "${rendered_config}" data >/dev/null grep -q '^name = "CI"$' "${rendered_config}" || fail "generated config missing CI name" grep -q '^email = "ci@example.invalid"$' "${rendered_config}" || fail "generated config missing CI email" -grep -q '^signingKey = ""$' "${rendered_config}" || fail "empty signingKey should render as empty string" grep -q '^personal = true$' "${rendered_config}" || fail "default profile should be personal" grep -q '^work = false$' "${rendered_config}" || fail "work should default to false" @@ -77,7 +75,6 @@ WORK=true chezmoi execute-template \ --init \ --promptString 'name=CI' \ --promptString 'email=ci@example.invalid' \ - --promptString 'signingKey=' \ < home/.chezmoi.toml.tmpl \ > "${rendered_work_config}" @@ -85,6 +82,25 @@ grep -q '^profile = "work"$' "${rendered_work_config}" || fail "WORK=true should grep -q '^work = true$' "${rendered_work_config}" || fail "WORK=true should set work = true" grep -q '^personal = false$' "${rendered_work_config}" || fail "WORK=true should set personal = false" +section "Checking safe adoption defaults" + +[[ -f home/create_dot_gitconfig.tmpl ]] || fail "Git defaults must be create-only" +[[ ! -e home/dot_gitconfig.tmpl ]] || fail "Git config must not overwrite an existing setup" +grep -Fq 'name = {{ .name | quote }}' home/create_dot_gitconfig.tmpl || fail "new machines must receive a Git name" +grep -Fq 'email = {{ .email | quote }}' home/create_dot_gitconfig.tmpl || fail "new machines must receive a Git email" +grep -Fq 'joinPath .chezmoi.homeDir ".config/git/dotfiles.gitconfig"' home/create_dot_gitconfig.tmpl || fail "root Git config must include managed defaults" +[[ -f home/dot_config/git/dotfiles.gitconfig.tmpl ]] || fail "managed Git defaults are missing" +grep -Fq "alias confgit='chezmoi edit --apply ~/.config/git/dotfiles.gitconfig'" home/dot_aliases || fail "confgit must edit managed defaults" +[[ ! -e home/dot_config/raycast/raycast.rayconfig ]] || fail "Raycast exports must not be managed" +[[ -f home/dot_config/ghostty/config.ghostty ]] || fail "Ghostty config must use its current filename" +grep -Fq 'keybind = super+k=text:\x0c' home/dot_config/ghostty/config.ghostty || fail "missing tmux-aware Command-K binding" +if grep -Fq 'keybind = shift+enter=' home/dot_config/ghostty/config.ghostty; then + fail "Shift-Enter must use Ghostty and Claude Code's native handling" +fi +grep -Fq 'xterm-ghostty:RGB:extkeys' home/dot_config/tmux/tmux.conf || fail "Ghostty must advertise extended keys to tmux" +grep -Fq 'set -s extended-keys on' home/dot_config/tmux/tmux.conf || fail "tmux must pass modified keys to Claude Code" +grep -Fq 'set -g focus-events on' home/dot_config/tmux/tmux.conf || fail "tmux must pass focus events to Claude Code" + section "Testing chezmoi dry-run apply" # Encrypted entries need the age identity, which only exists on real machines. @@ -96,7 +112,6 @@ chezmoi init \ --keep-going \ --exclude encrypted \ --promptString 'name=CI' \ - --promptString 'email=ci@example.invalid' \ - --promptString 'signingKey=' + --promptString 'email=ci@example.invalid' section "All tests passed"