From d4fc0d9f5e2bc47ab99a3805ed9726903b83670f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Diego=20Piske?= Date: Thu, 11 Jun 2026 10:11:59 -0300 Subject: [PATCH 01/13] fixes for macOS --- lib/build_setup.sh | 8 ++++++++ lib/mise_setup.sh | 3 +++ 2 files changed, 11 insertions(+) diff --git a/lib/build_setup.sh b/lib/build_setup.sh index de6e79f..5cbb182 100644 --- a/lib/build_setup.sh +++ b/lib/build_setup.sh @@ -17,6 +17,14 @@ case "$OS" in echo " NOTE: A dialog may have opened. Complete the installation and re-run this script." fi + # rust is required for building Ruby from source + if brew list rust > /dev/null 2>&1; then + fmt_ok "rust already installed" + else + fmt_install "rust" + brew install rust + fi + # libyaml is required for building Ruby from source if brew list libyaml > /dev/null 2>&1; then fmt_ok "libyaml already installed" diff --git a/lib/mise_setup.sh b/lib/mise_setup.sh index 2596074..f1ed050 100644 --- a/lib/mise_setup.sh +++ b/lib/mise_setup.sh @@ -60,6 +60,9 @@ fmt_header "Ruby (via mise)" if mise which ruby > /dev/null 2>&1; then fmt_ok "Ruby already available via mise" else + echo 'setting mise ruby.compile=false' + mise settings ruby.compile=false + fmt_install "Ruby (latest stable via mise)" mise use --global ruby@latest fmt_ok "Ruby installed: $(mise exec -- ruby --version)" From 4cf0a6f4852be27c87adaaf1c27c283e379a8879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Diego=20Piske?= Date: Thu, 11 Jun 2026 10:21:37 -0300 Subject: [PATCH 02/13] Install rust on Ubuntu and Arch for building Ruby Replicates the macOS rust install from d4fc0d9 to the Ubuntu (apt) and Arch (pacman) platforms, since rust is required for building Ruby from source. Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/build_setup.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/build_setup.sh b/lib/build_setup.sh index 5cbb182..df3aa7f 100644 --- a/lib/build_setup.sh +++ b/lib/build_setup.sh @@ -40,6 +40,14 @@ case "$OS" in fmt_install "build-essential" sudo apt-get install -y -qq build-essential libssl-dev libreadline-dev zlib1g-dev libyaml-dev fi + + # rust is required for building Ruby from source + if dpkg -s rustc > /dev/null 2>&1; then + fmt_ok "rust already installed" + else + fmt_install "rust" + sudo apt-get install -y -qq rustc cargo + fi ;; arch) if pacman -Qi base-devel > /dev/null 2>&1; then @@ -49,5 +57,12 @@ case "$OS" in sudo pacman -S --noconfirm --needed base-devel fi + # rust is required for building Ruby from source + if pacman -Qi rust > /dev/null 2>&1; then + fmt_ok "rust already installed" + else + fmt_install "rust" + sudo pacman -S --noconfirm --needed rust + fi ;; esac From 1ab17dc945259380cc8cdb731341a13399f66c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Diego=20Piske?= Date: Mon, 15 Jun 2026 18:03:29 -0300 Subject: [PATCH 03/13] Allow skipping CircleCI auth with SKIP_CIRCLECI_AUTH=1 Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/circleci_setup.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/circleci_setup.sh b/lib/circleci_setup.sh index a6bed70..be23d32 100644 --- a/lib/circleci_setup.sh +++ b/lib/circleci_setup.sh @@ -29,6 +29,13 @@ fi # CircleCI Authentication # --------------------------------------------------------------------------- +# Allow skipping authentication when SKIP_CIRCLECI_AUTH is exactly "1". +if [ "${SKIP_CIRCLECI_AUTH:-}" = "1" ]; then + fmt_header "CircleCI Authentication" + fmt_ok "CircleCI CLI: authentication skipped (SKIP_CIRCLECI_AUTH=1)" + return 0 2>/dev/null || exit 0 +fi + fmt_header "CircleCI Authentication" # Capture diagnostic output into a variable to avoid pipe + pipefail issues. From 88e03c762b047067f959e59334fc975ae1dacb87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Diego=20Piske?= Date: Mon, 15 Jun 2026 18:18:57 -0300 Subject: [PATCH 04/13] Simplify CircleCI auth skip to plain if/else Replace the fragile `return 0 2>/dev/null || exit 0` with a structured if/else so skipping auth never risks interrupting the sourced setup.sh. Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/circleci_setup.sh | 48 +++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/circleci_setup.sh b/lib/circleci_setup.sh index be23d32..ca6d62b 100644 --- a/lib/circleci_setup.sh +++ b/lib/circleci_setup.sh @@ -29,35 +29,35 @@ fi # CircleCI Authentication # --------------------------------------------------------------------------- +fmt_header "CircleCI Authentication" + # Allow skipping authentication when SKIP_CIRCLECI_AUTH is exactly "1". +# Only the auth step is skipped; the CLI install above always runs and the +# rest of setup.sh continues normally. if [ "${SKIP_CIRCLECI_AUTH:-}" = "1" ]; then - fmt_header "CircleCI Authentication" fmt_ok "CircleCI CLI: authentication skipped (SKIP_CIRCLECI_AUTH=1)" - return 0 2>/dev/null || exit 0 -fi - -fmt_header "CircleCI Authentication" - -# Capture diagnostic output into a variable to avoid pipe + pipefail issues. -# With pipefail, a failing left-hand side poisons the whole pipeline even when -# grep succeeds, which causes false negatives. -circleci_diag="$(circleci diagnostic 2>&1 || true)" - -if echo "$circleci_diag" | grep -q "OK, got a token"; then - fmt_ok "CircleCI CLI: already authenticated" else - echo " CircleCI CLI needs to be configured." - echo " You will need a personal API token from:" - echo " https://app.circleci.com/settings/user/tokens" - echo "" - circleci setup - + # Capture diagnostic output into a variable to avoid pipe + pipefail issues. + # With pipefail, a failing left-hand side poisons the whole pipeline even when + # grep succeeds, which causes false negatives. circleci_diag="$(circleci diagnostic 2>&1 || true)" - if ! echo "$circleci_diag" | grep -q "OK, got a token"; then + + if echo "$circleci_diag" | grep -q "OK, got a token"; then + fmt_ok "CircleCI CLI: already authenticated" + else + echo " CircleCI CLI needs to be configured." + echo " You will need a personal API token from:" + echo " https://app.circleci.com/settings/user/tokens" echo "" - echo "ERROR: CircleCI CLI authentication failed or was cancelled." - echo "Setup cannot continue without CircleCI access." - exit 1 + circleci setup + + circleci_diag="$(circleci diagnostic 2>&1 || true)" + if ! echo "$circleci_diag" | grep -q "OK, got a token"; then + echo "" + echo "ERROR: CircleCI CLI authentication failed or was cancelled." + echo "Setup cannot continue without CircleCI access." + exit 1 + fi + fmt_ok "CircleCI CLI: authenticated" fi - fmt_ok "CircleCI CLI: authenticated" fi From e9796dff1a33e9958b3a51991a308de46b3ae62c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Diego=20Piske?= Date: Mon, 15 Jun 2026 18:27:41 -0300 Subject: [PATCH 05/13] Re-exec a fresh login shell at end of setup Setup adds the user to new groups (e.g. docker) and writes shell config. Hand off `exec su - $USER` so those changes take effect immediately without re-ssh or re-login. Co-Authored-By: Claude Opus 4.8 (1M context) --- setup.sh | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/setup.sh b/setup.sh index afb8a1a..2d72fd7 100755 --- a/setup.sh +++ b/setup.sh @@ -177,7 +177,18 @@ echo "" echo "${COLOR_GREEN}Setup finished successfully.${COLOR_RESET}" echo "" echo "${COLOR_YELLOW}Next steps:${COLOR_RESET}" -echo " 1. Open a new terminal (or run: source ~/.zshrc)" -echo " 2. Clone a project: cd ~/Work && gh repo clone trusted/" -echo " 3. Run project setup: cd && bin/setup" +echo " 1. Clone a project: cd ~/Work && gh repo clone trusted/" +echo " 2. Run project setup: cd && bin/setup" echo "" + +# --------------------------------------------------------------------------- +# Hand off a fresh login shell +# --------------------------------------------------------------------------- +# +# Setup may add the user to new groups (e.g. docker) and write shell config. +# Re-exec a fresh login shell so those changes take effect immediately, +# without the user having to log out / re-ssh. + +echo "${COLOR_YELLOW}Starting a fresh login shell so new group memberships take effect...${COLOR_RESET}" +echo "" +exec su - "$USER" From e73c16f99ae0a2688aa4c14830121b83ea45849d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Diego=20Piske?= Date: Mon, 15 Jun 2026 18:58:33 -0300 Subject: [PATCH 06/13] Use exec newgrp docker instead of su to avoid password prompt `su - $USER` prompts for the user's password. The only group change that needs to take effect immediately is docker, so re-exec into a shell with the docker group active via newgrp, which needs no password. Co-Authored-By: Claude Opus 4.8 (1M context) --- setup.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/setup.sh b/setup.sh index 2d72fd7..fec4e0f 100755 --- a/setup.sh +++ b/setup.sh @@ -185,10 +185,10 @@ echo "" # Hand off a fresh login shell # --------------------------------------------------------------------------- # -# Setup may add the user to new groups (e.g. docker) and write shell config. -# Re-exec a fresh login shell so those changes take effect immediately, -# without the user having to log out / re-ssh. +# Setup adds the user to the docker group. Re-exec a shell with that group +# active so docker works immediately, without the user having to log out / +# re-ssh. newgrp avoids the password prompt that `su - $USER` would trigger. -echo "${COLOR_YELLOW}Starting a fresh login shell so new group memberships take effect...${COLOR_RESET}" +echo "${COLOR_YELLOW}Starting a shell with the docker group active...${COLOR_RESET}" echo "" -exec su - "$USER" +exec newgrp docker From 1dd13d9d27e3472691bdf75e56ca376f1b8f460f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Diego=20Piske?= Date: Mon, 15 Jun 2026 19:02:30 -0300 Subject: [PATCH 07/13] Re-exec a login shell so shell config and docker group both apply `$SHELL -l` re-reads the user's startup files (bash or zsh) so PATH/env changes from setup take effect. On Linux, wrap it in `sg docker` so the newly added docker group membership is active too. `sg` needs no password (the user is already a member), and is only used when the user is actually in the docker group, so it never blocks on a prompt. Co-Authored-By: Claude Opus 4.8 (1M context) --- setup.sh | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/setup.sh b/setup.sh index fec4e0f..20be4f8 100755 --- a/setup.sh +++ b/setup.sh @@ -185,10 +185,23 @@ echo "" # Hand off a fresh login shell # --------------------------------------------------------------------------- # -# Setup adds the user to the docker group. Re-exec a shell with that group -# active so docker works immediately, without the user having to log out / -# re-ssh. newgrp avoids the password prompt that `su - $USER` would trigger. +# Setup writes shell config (PATH/env) and, on Linux, adds the user to the +# docker group. Re-exec a fresh login shell so both take effect immediately, +# without the user having to log out / re-ssh. +# +# Using `$SHELL -l` re-reads the user's startup files for whichever shell +# they use (login bash reads ~/.bash_profile; login zsh reads +# ~/.zprofile/~/.zshrc). On Linux we wrap it in `sg docker` so the new docker +# group membership is active too — `sg` needs no password since the user is +# already a member (unlike `su - $USER`, which would prompt). + +login_shell="${SHELL:-/bin/bash}" -echo "${COLOR_YELLOW}Starting a shell with the docker group active...${COLOR_RESET}" +echo "${COLOR_YELLOW}Starting a fresh login shell so the changes above take effect...${COLOR_RESET}" echo "" -exec newgrp docker + +if [ "$OS" != "macos" ] && getent group docker 2>/dev/null | grep -qw "$USER"; then + exec sg docker -c "exec $login_shell -l" +else + exec "$login_shell" -l +fi From c4bcb8140312920627aa93f00bc0f87da0a1062e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Diego=20Piske?= Date: Mon, 15 Jun 2026 19:08:45 -0300 Subject: [PATCH 08/13] Add opt-in Claude Code CLI setup and doctor checks Installs Claude Code via the native installer (claude.ai/install.sh) only when OPT_IN_CLAUDE_CODE=1. The opt-in gate lives in the lib/ scripts; doctor skips silently when not opted in. Co-Authored-By: Claude Opus 4.8 (1M context) --- doctor.sh | 3 +++ lib/claude_code_doctor.sh | 27 +++++++++++++++++++++++++++ lib/claude_code_setup.sh | 38 ++++++++++++++++++++++++++++++++++++++ setup.sh | 3 +++ 4 files changed, 71 insertions(+) create mode 100644 lib/claude_code_doctor.sh create mode 100644 lib/claude_code_setup.sh diff --git a/doctor.sh b/doctor.sh index 97b349d..e5a9835 100755 --- a/doctor.sh +++ b/doctor.sh @@ -139,6 +139,9 @@ source "$SCRIPT_DIR/lib/render_doctor.sh" # shellcheck source=lib/registries_doctor.sh source "$SCRIPT_DIR/lib/registries_doctor.sh" +# shellcheck source=lib/claude_code_doctor.sh +source "$SCRIPT_DIR/lib/claude_code_doctor.sh" + # shellcheck source=lib/migrate_doctor.sh source "$SCRIPT_DIR/lib/migrate_doctor.sh" diff --git a/lib/claude_code_doctor.sh b/lib/claude_code_doctor.sh new file mode 100644 index 0000000..b70265f --- /dev/null +++ b/lib/claude_code_doctor.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# +# Doctor check: Claude Code CLI (opt-in). +# Sourced by doctor.sh — do not execute directly. +# Requires: lib/common.sh, doctor helpers (check_pass, check_fail, check_cmd) +# +# This component is OPT-IN. It is only checked when the OPT_IN_CLAUDE_CODE +# environment variable is set to exactly "1". Otherwise it is skipped silently +# so machines that never opted in don't report a spurious failure. + +# Opt-in gate. Keep this check here (not in doctor.sh) so the component owns +# its own enablement logic. Only the literal value "1" enables the check. +if [ "${OPT_IN_CLAUDE_CODE:-}" = "1" ]; then + fmt_header "Claude Code (opt-in)" + + # The native installer installs to ~/.local/bin, which may not be on PATH in + # a non-interactive shell. Add it so the check below can find claude. + # Read-only — no files are modified. + export PATH="$HOME/.local/bin:$PATH" + + check_cmd "claude" "claude" + + if cmd_exists claude; then + version_output="$(claude --version 2>&1 | head -1)" + check_pass "claude reports version: $version_output" + fi +fi diff --git a/lib/claude_code_setup.sh b/lib/claude_code_setup.sh new file mode 100644 index 0000000..4221fd3 --- /dev/null +++ b/lib/claude_code_setup.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# +# Claude Code CLI setup (opt-in). +# Sourced by setup.sh — do not execute directly. +# Requires: lib/common.sh +# +# This component is OPT-IN. It is only installed when the OPT_IN_CLAUDE_CODE +# environment variable is set to exactly "1". Any other value (including +# unset, "0", "true", "yes", etc.) is treated as "do not install". + +fmt_header "Claude Code (opt-in)" + +# Opt-in gate. Keep this check here (not in setup.sh) so the component owns +# its own enablement logic. Only the literal value "1" enables installation. +if [ "${OPT_IN_CLAUDE_CODE:-}" != "1" ]; then + fmt_ok "Claude Code: skipped (set OPT_IN_CLAUDE_CODE=1 to install)" +else + # The native installer installs to ~/.local/bin, which may not be on PATH in + # this non-interactive session yet. Add it so the cmd_exists check below can + # detect an existing install. + export PATH="$HOME/.local/bin:$PATH" + + if cmd_exists claude; then + fmt_ok "claude already installed ($(claude --version 2>/dev/null | head -1))" + else + fmt_install "Claude Code CLI" + # Official cross-platform native installer — installs to ~/.local/bin and + # adds it to the user's shell config. Used on every OS (never Homebrew). + curl -fsSL https://claude.ai/install.sh | bash + + if cmd_exists claude; then + fmt_ok "Claude Code installed ($(claude --version 2>/dev/null | head -1))" + else + echo " WARNING: Claude Code was installed but is not yet on PATH." + echo " It will be available after restarting your shell." + fi + fi +fi diff --git a/setup.sh b/setup.sh index 20be4f8..0c90b2e 100755 --- a/setup.sh +++ b/setup.sh @@ -152,6 +152,9 @@ source "$SCRIPT_DIR/lib/render_setup.sh" # shellcheck source=lib/registries_setup.sh source "$SCRIPT_DIR/lib/registries_setup.sh" +# shellcheck source=lib/claude_code_setup.sh +source "$SCRIPT_DIR/lib/claude_code_setup.sh" + # --------------------------------------------------------------------------- # Migrations # --------------------------------------------------------------------------- From 65f04aee1316c9b785fe77e24b46255ceb4f37ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Diego=20Piske?= Date: Mon, 15 Jun 2026 19:19:09 -0300 Subject: [PATCH 09/13] Persist ~/.local/bin to the login shell rc, not always ~/.zshrc Claude Code installs to ~/.local/bin, which isn't on PATH by default. Write the PATH addition to the rc file of the user's actual login shell (bash -> ~/.bashrc, zsh -> ~/.zshrc) with an OS-based fallback, so it works for bash on Ubuntu/Omarchy and zsh on macOS. Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/claude_code_setup.sh | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/lib/claude_code_setup.sh b/lib/claude_code_setup.sh index 4221fd3..ff7f6ae 100644 --- a/lib/claude_code_setup.sh +++ b/lib/claude_code_setup.sh @@ -15,17 +15,46 @@ fmt_header "Claude Code (opt-in)" if [ "${OPT_IN_CLAUDE_CODE:-}" != "1" ]; then fmt_ok "Claude Code: skipped (set OPT_IN_CLAUDE_CODE=1 to install)" else - # The native installer installs to ~/.local/bin, which may not be on PATH in - # this non-interactive session yet. Add it so the cmd_exists check below can - # detect an existing install. + # The native installer puts the claude binary in ~/.local/bin, which is not + # on PATH by default. Persist it to the rc file of the user's actual login + # shell so claude is available in future shells: bash uses ~/.bashrc, zsh + # uses ~/.zshrc. (On Ubuntu/Arch login bash sources ~/.bashrc via + # ~/.profile / ~/.bash_profile.) Falls back to the OS default shell when + # $SHELL is unset or unrecognized. Idempotent. + case "$(basename "${SHELL:-}")" in + bash) claude_rc="$HOME/.bashrc" ;; + zsh) claude_rc="$HOME/.zshrc" ;; + *) + case "$OS" in + macos) claude_rc="$HOME/.zshrc" ;; + *) claude_rc="$HOME/.bashrc" ;; + esac + ;; + esac + + if [ ! -f "$claude_rc" ]; then + touch "$claude_rc" + fi + + if ! grep -qF '.local/bin' "$claude_rc" 2>/dev/null; then + { + echo "" + echo "# Local user binaries (Claude Code, etc.)" + # shellcheck disable=SC2016 # Intentionally single-quoted: written literally to RC file + echo 'export PATH="$HOME/.local/bin:$PATH"' + } >> "$claude_rc" + echo " Added ~/.local/bin to PATH in $claude_rc" + fi + + # Also add it for this session so the cmd_exists checks below work. export PATH="$HOME/.local/bin:$PATH" if cmd_exists claude; then fmt_ok "claude already installed ($(claude --version 2>/dev/null | head -1))" else fmt_install "Claude Code CLI" - # Official cross-platform native installer — installs to ~/.local/bin and - # adds it to the user's shell config. Used on every OS (never Homebrew). + # Official cross-platform native installer — installs to ~/.local/bin. + # Used on every OS (never Homebrew). curl -fsSL https://claude.ai/install.sh | bash if cmd_exists claude; then From 373278496bdee1298005fc1f41d630f17fd4e609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Diego=20Piske?= Date: Mon, 15 Jun 2026 19:20:00 -0300 Subject: [PATCH 10/13] Document shell-rc selection rule in AGENTS.md Write PATH/env to the user's login shell rc (bash -> ~/.bashrc, zsh -> ~/.zshrc) with an OS fallback, never hardcode ~/.zshrc. Flags the existing mise/brew ~/.zshrc-only writes as a known bug. Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 5a09bcc..2e36a45 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,6 +45,14 @@ Three platforms are supported. All changes must account for all three: When adding a new tool installation, provide install commands for all three platforms. +### Shell configuration + +- When persisting `PATH` or env activation to a shell rc file, write to the rc file of the user's **actual login shell** (`$SHELL`) — never hardcode `~/.zshrc`. Bash → `~/.bashrc`, zsh → `~/.zshrc`. Fall back to the OS default shell when `$SHELL` is unset or unrecognized (macOS → zsh, Ubuntu/Arch → bash). +- Select the rc file with `case "$(basename "${SHELL:-}")"` plus an `$OS`-based fallback. See `lib/claude_code_setup.sh` for the pattern. +- On Ubuntu/Arch, login bash sources `~/.bashrc` via `~/.profile` / `~/.bash_profile`, so `~/.bashrc` is the correct target there. +- Writing only to `~/.zshrc` silently fails for bash users on Ubuntu/Omarchy. (Note: existing `mise_setup.sh` and `packages_setup.sh` still write only to `~/.zshrc` — a known bug to be fixed separately; do not copy that approach into new scripts.) +- Always make rc-file edits idempotent: guard the append with a `grep -qF` for a stable marker. + ### Scope boundaries This repo installs **tool managers, CLI tools, and infrastructure dependencies**: From c86de215c1a11d9bcf5061a92b052cf488ba170f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Diego=20Piske?= Date: Mon, 15 Jun 2026 19:33:59 -0300 Subject: [PATCH 11/13] Write mise/brew shell config to the login shell rc, not always ~/.zshrc mise_setup.sh and packages_setup.sh hardcoded ~/.zshrc for persisting `mise activate` / `brew shellenv`. setup.sh re-execs `$SHELL -l`, and on Ubuntu/Arch the default login shell is bash, so a bash user never sourced those lines -- mise (and the Ruby/Node toolchain it fronts) silently failed to activate in new shells. Centralize the login-shell-rc selection in a login_shell_rc() helper in common.sh (bash -> ~/.bashrc, zsh -> ~/.zshrc, with an $OS fallback) and use it from mise_setup.sh, packages_setup.sh, and claude_code_setup.sh (whose inline case block it replaces). Update AGENTS.md to point at the helper and drop the now-fixed "known bug" note. Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 4 ++-- lib/claude_code_setup.sh | 16 ++-------------- lib/common.sh | 22 ++++++++++++++++++++++ lib/mise_setup.sh | 13 ++++++------- lib/packages_setup.sh | 13 ++++++------- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2e36a45..3182d56 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,9 +48,9 @@ When adding a new tool installation, provide install commands for all three plat ### Shell configuration - When persisting `PATH` or env activation to a shell rc file, write to the rc file of the user's **actual login shell** (`$SHELL`) — never hardcode `~/.zshrc`. Bash → `~/.bashrc`, zsh → `~/.zshrc`. Fall back to the OS default shell when `$SHELL` is unset or unrecognized (macOS → zsh, Ubuntu/Arch → bash). -- Select the rc file with `case "$(basename "${SHELL:-}")"` plus an `$OS`-based fallback. See `lib/claude_code_setup.sh` for the pattern. +- Use the `login_shell_rc` helper in `lib/common.sh` to resolve the target rc path (it does the `$SHELL` + `$OS`-based selection). See `lib/mise_setup.sh`, `lib/packages_setup.sh`, or `lib/claude_code_setup.sh` for usage. - On Ubuntu/Arch, login bash sources `~/.bashrc` via `~/.profile` / `~/.bash_profile`, so `~/.bashrc` is the correct target there. -- Writing only to `~/.zshrc` silently fails for bash users on Ubuntu/Omarchy. (Note: existing `mise_setup.sh` and `packages_setup.sh` still write only to `~/.zshrc` — a known bug to be fixed separately; do not copy that approach into new scripts.) +- Writing only to `~/.zshrc` silently fails for bash users on Ubuntu/Omarchy. Resolve the rc via `login_shell_rc` rather than hardcoding `~/.zshrc`. - Always make rc-file edits idempotent: guard the append with a `grep -qF` for a stable marker. ### Scope boundaries diff --git a/lib/claude_code_setup.sh b/lib/claude_code_setup.sh index ff7f6ae..a8b4c1d 100644 --- a/lib/claude_code_setup.sh +++ b/lib/claude_code_setup.sh @@ -17,20 +17,8 @@ if [ "${OPT_IN_CLAUDE_CODE:-}" != "1" ]; then else # The native installer puts the claude binary in ~/.local/bin, which is not # on PATH by default. Persist it to the rc file of the user's actual login - # shell so claude is available in future shells: bash uses ~/.bashrc, zsh - # uses ~/.zshrc. (On Ubuntu/Arch login bash sources ~/.bashrc via - # ~/.profile / ~/.bash_profile.) Falls back to the OS default shell when - # $SHELL is unset or unrecognized. Idempotent. - case "$(basename "${SHELL:-}")" in - bash) claude_rc="$HOME/.bashrc" ;; - zsh) claude_rc="$HOME/.zshrc" ;; - *) - case "$OS" in - macos) claude_rc="$HOME/.zshrc" ;; - *) claude_rc="$HOME/.bashrc" ;; - esac - ;; - esac + # shell so claude is available in future shells. Idempotent. + claude_rc="$(login_shell_rc)" if [ ! -f "$claude_rc" ]; then touch "$claude_rc" diff --git a/lib/common.sh b/lib/common.sh index a2a92cf..68cb92c 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -84,3 +84,25 @@ if [ "$OS" = "unsupported" ]; then echo "Supported: macOS, Ubuntu/Debian, Arch Linux (including Omarchy)." exit 1 fi + +# --------------------------------------------------------------------------- +# Shell configuration +# --------------------------------------------------------------------------- + +# Path to the rc file of the user's actual login shell. Used when persisting +# PATH/env so it lands where the login shell will source it (bash -> ~/.bashrc, +# zsh -> ~/.zshrc). Falls back to the OS default shell when $SHELL is unset or +# unrecognized (macOS -> zsh, Linux -> bash). On Ubuntu/Arch login bash sources +# ~/.bashrc via ~/.profile / ~/.bash_profile, so ~/.bashrc is the right target. +login_shell_rc() { + case "$(basename "${SHELL:-}")" in + bash) echo "$HOME/.bashrc" ;; + zsh) echo "$HOME/.zshrc" ;; + *) + case "$OS" in + macos) echo "$HOME/.zshrc" ;; + *) echo "$HOME/.bashrc" ;; + esac + ;; + esac +} diff --git a/lib/mise_setup.sh b/lib/mise_setup.sh index f1ed050..94c499c 100644 --- a/lib/mise_setup.sh +++ b/lib/mise_setup.sh @@ -33,19 +33,18 @@ else esac fi -# Ensure mise is activated in ~/.zshrc -if [ ! -f "$HOME/.zshrc" ]; then - touch "$HOME/.zshrc" -fi +# Ensure mise is activated in the user's login shell rc +mise_rc="$(login_shell_rc)" +[ -f "$mise_rc" ] || touch "$mise_rc" -if ! grep -qF "mise activate" "$HOME/.zshrc" 2>/dev/null; then +if ! grep -qF "mise activate" "$mise_rc" 2>/dev/null; then { echo "" echo "# mise version manager" # shellcheck disable=SC2016 # Intentionally single-quoted: written literally to RC file echo 'eval "$(mise activate)"' - } >> "$HOME/.zshrc" - echo " Added mise activation to ~/.zshrc" + } >> "$mise_rc" + echo " Added mise activation to $mise_rc" fi # Activate mise for this session diff --git a/lib/packages_setup.sh b/lib/packages_setup.sh index 4b425d1..7c6260b 100644 --- a/lib/packages_setup.sh +++ b/lib/packages_setup.sh @@ -32,20 +32,19 @@ case "$OS" in fi fi - # Ensure Homebrew activation is persisted in ~/.zshrc + # Ensure Homebrew activation is persisted in the user's login shell rc if cmd_exists brew; then - if [ ! -f "$HOME/.zshrc" ]; then - touch "$HOME/.zshrc" - fi + brew_rc="$(login_shell_rc)" + [ -f "$brew_rc" ] || touch "$brew_rc" - if ! grep -qF "brew shellenv" "$HOME/.zshrc"; then + if ! grep -qF "brew shellenv" "$brew_rc"; then { echo "" echo "# Homebrew" # shellcheck disable=SC2016 # Intentionally single-quoted: written literally to RC file echo 'eval "$(brew shellenv)"' - } >> "$HOME/.zshrc" - echo " Added Homebrew activation to ~/.zshrc" + } >> "$brew_rc" + echo " Added Homebrew activation to $brew_rc" fi fi ;; From c0e6e52625604a7598bbdc657cd4de69a80da458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Diego=20Piske?= Date: Tue, 16 Jun 2026 10:21:51 -0300 Subject: [PATCH 12/13] Wire git HTTPS auth to gh via gh auth setup-git Authenticating gh does not, by itself, teach git to use that token. A repo cloned with `gh repo clone` gets an HTTPS remote, so `git pull` prompts for a username/password unless git's credential helper points at `gh auth git-credential`. gh auth login normally configures this, but it is skipped when gh is authenticated non-interactively (token/GH_TOKEN). Run `gh auth setup-git` after authentication so git delegates GitHub HTTPS auth to gh, and add a doctor check that the helper is configured. Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/git_doctor.sh | 14 ++++++++++++++ lib/git_setup.sh | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/lib/git_doctor.sh b/lib/git_doctor.sh index 6c75ff2..bc8da54 100644 --- a/lib/git_doctor.sh +++ b/lib/git_doctor.sh @@ -37,3 +37,17 @@ if cmd_exists gh; then check_fail "gh --version returned unexpected output: $version_output" fi fi + +# --------------------------------------------------------------------------- +# git credential helper +# --------------------------------------------------------------------------- + +fmt_header "git credential helper" + +if cmd_exists git && cmd_exists gh; then + if git config --get-regexp '^credential\..*github\.com.*\.helper$' 2>/dev/null | grep -q 'gh'; then + check_pass "git is configured to authenticate GitHub HTTPS via gh" + else + check_fail "git credential helper for github.com not set — run 'gh auth setup-git'" + fi +fi diff --git a/lib/git_setup.sh b/lib/git_setup.sh index 40bbaa7..11272c2 100644 --- a/lib/git_setup.sh +++ b/lib/git_setup.sh @@ -99,3 +99,17 @@ else fi fmt_ok "Authenticated with GitHub" fi + +# --------------------------------------------------------------------------- +# git credential helper (use gh for HTTPS GitHub auth) +# --------------------------------------------------------------------------- +# +# Without this, git over HTTPS (e.g. `git pull` on a repo cloned via +# `gh repo clone`) prompts for a username/password even when `gh` is +# authenticated. `gh auth setup-git` wires git's credential helper to +# `gh auth git-credential` for github.com. Idempotent. + +fmt_header "git credential helper" + +gh auth setup-git +fmt_ok "Configured git to authenticate via gh" From 49b264e34b4473a46262b28d8efc681dc149649d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Diego=20Piske?= Date: Tue, 16 Jun 2026 10:22:27 -0300 Subject: [PATCH 13/13] Set global git identity from the authenticated GitHub account After auth, derive user.name/user.email from `gh api user` and set them globally, mirroring GitHub's own fallbacks: login when the display name is hidden, and the id+login noreply address when the email is private. Fill in only values that are not already configured so an identity the user set deliberately is never clobbered. Add a matching doctor check. Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/git_doctor.sh | 16 ++++++++++++++++ lib/git_setup.sh | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/lib/git_doctor.sh b/lib/git_doctor.sh index bc8da54..fefa30b 100644 --- a/lib/git_doctor.sh +++ b/lib/git_doctor.sh @@ -51,3 +51,19 @@ if cmd_exists git && cmd_exists gh; then check_fail "git credential helper for github.com not set — run 'gh auth setup-git'" fi fi + +# --------------------------------------------------------------------------- +# git identity +# --------------------------------------------------------------------------- + +fmt_header "git identity" + +if cmd_exists git; then + git_name="$(git config --global user.name || true)" + git_email="$(git config --global user.email || true)" + if [ -n "$git_name" ] && [ -n "$git_email" ]; then + check_pass "git identity set ($git_name <$git_email>)" + else + check_fail "git identity incomplete — set user.name and user.email globally" + fi +fi diff --git a/lib/git_setup.sh b/lib/git_setup.sh index 11272c2..7a62d55 100644 --- a/lib/git_setup.sh +++ b/lib/git_setup.sh @@ -113,3 +113,39 @@ fmt_header "git credential helper" gh auth setup-git fmt_ok "Configured git to authenticate via gh" + +# --------------------------------------------------------------------------- +# git identity (global user.name / user.email) +# --------------------------------------------------------------------------- +# +# Derive the identity from the authenticated GitHub account. Only fill in +# values that are not already set globally — never clobber an identity the +# user configured deliberately. + +fmt_header "git identity" + +git_name="$(git config --global user.name || true)" +git_email="$(git config --global user.email || true)" + +if [ -n "$git_name" ] && [ -n "$git_email" ]; then + fmt_ok "git identity already set ($git_name <$git_email>)" +else + gh_login="$(gh api user --jq '.login')" + gh_id="$(gh api user --jq '.id')" + + if [ -z "$git_name" ]; then + git_name="$(gh api user --jq '.name // ""')" + # Display name is optional on GitHub -> fall back to the login. + [ -n "$git_name" ] || git_name="$gh_login" + git config --global user.name "$git_name" + fi + + if [ -z "$git_email" ]; then + git_email="$(gh api user --jq '.email // ""')" + # Public email is often hidden -> fall back to the noreply address. + [ -n "$git_email" ] || git_email="${gh_id}+${gh_login}@users.noreply.github.com" + git config --global user.email "$git_email" + fi + + fmt_ok "git identity set ($git_name <$git_email>)" +fi