diff --git a/AGENTS.md b/AGENTS.md index 5a09bcc..3182d56 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). +- 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. 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 This repo installs **tool managers, CLI tools, and infrastructure dependencies**: 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/1password_setup.sh b/lib/1password_setup.sh index a1e14ba..3b6ed6f 100644 --- a/lib/1password_setup.sh +++ b/lib/1password_setup.sh @@ -51,6 +51,53 @@ fmt_header "1Password Authentication" if op whoami > /dev/null 2>&1; then fmt_ok "1Password: already signed in" +elif [ "$OS" = "macos" ]; then + # On macOS, op must authenticate through the 1Password desktop app's CLI + # integration — NOT manually-added accounts or service accounts. When that + # integration is off, `op signin` drops into an interactive "add an account + # manually? [Y/n]" prompt that it reads from /dev/tty directly — so it can't + # be answered by piping stdin, and setup hangs on it. We must never reach it. + # + # Instead, gate on `op account list`, which only enumerates configured + # accounts and never prompts: + # - integration OFF => no accounts listed. Ask the user to enable it, retry. + # - integration ON => the app's account(s) are listed. + # Only once an account exists do we call `op signin`, which then triggers the + # desktop Authorize popup (a GUI dialog, not a tty prompt): exit 0 = + # Authorized, non-zero = Cancelled. + echo " 1Password CLI authenticates through the 1Password desktop app." + echo "" + + # Wait until the desktop-app CLI integration exposes at least one account. + while true; do + accounts="$(op account list --format=json 2>/dev/null || true)" + accounts="$(printf '%s' "$accounts" | tr -d '[:space:]')" + if [ -n "$accounts" ] && [ "$accounts" != "[]" ]; then + break + fi + + echo " ${COLOR_YELLOW}The 1Password CLI app integration is not enabled.${COLOR_RESET}" + echo "" + echo " In the 1Password desktop app:" + echo " 1. Open Settings (Cmd + ,)" + echo " 2. Select the Developer tab" + echo " 3. Enable \"Integrate with 1Password CLI\"" + echo "" + read -r -p " Press Return once it's enabled to try again... " < /dev/tty + echo "" + done + + # Integration is on and an account is available. Trigger the Authorize popup; + # the user must click Authorize (Cancel => non-zero exit, so we retry). + until op signin -f > /dev/null 2>&1; do + echo " ${COLOR_YELLOW}1Password authorization was not completed.${COLOR_RESET}" + echo " When the 1Password popup appears, click \"Authorize\" (not Cancel)." + echo "" + read -r -p " Press Return to try again... " < /dev/tty + echo "" + done + + fmt_ok "1Password: signed in" else echo " 1Password CLI needs to be authenticated." echo " This will open a browser/system auth prompt." diff --git a/lib/build_setup.sh b/lib/build_setup.sh index de6e79f..ab976b7 100644 --- a/lib/build_setup.sh +++ b/lib/build_setup.sh @@ -8,13 +8,12 @@ fmt_header "Build Essentials" case "$OS" in macos) - # Xcode Command Line Tools are required for native compilation - if xcode-select -p > /dev/null 2>&1; then - fmt_ok "Xcode Command Line Tools already installed" + # 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 "Xcode Command Line Tools" - xcode-select --install 2>/dev/null || true - echo " NOTE: A dialog may have opened. Complete the installation and re-run this script." + fmt_install "rust" + brew install rust fi # libyaml is required for building Ruby from source @@ -32,6 +31,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 @@ -41,5 +48,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 diff --git a/lib/circleci_setup.sh b/lib/circleci_setup.sh index a6bed70..ca6d62b 100644 --- a/lib/circleci_setup.sh +++ b/lib/circleci_setup.sh @@ -31,26 +31,33 @@ 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" +# 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_ok "CircleCI CLI: authentication skipped (SKIP_CIRCLECI_AUTH=1)" 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 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..a8b4c1d --- /dev/null +++ b/lib/claude_code_setup.sh @@ -0,0 +1,55 @@ +#!/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 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. Idempotent. + claude_rc="$(login_shell_rc)" + + 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. + # 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/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/git_doctor.sh b/lib/git_doctor.sh index 6c75ff2..fefa30b 100644 --- a/lib/git_doctor.sh +++ b/lib/git_doctor.sh @@ -37,3 +37,33 @@ 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 + +# --------------------------------------------------------------------------- +# 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 40bbaa7..7a62d55 100644 --- a/lib/git_setup.sh +++ b/lib/git_setup.sh @@ -99,3 +99,53 @@ 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" + +# --------------------------------------------------------------------------- +# 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 diff --git a/lib/mise_setup.sh b/lib/mise_setup.sh index 2596074..a381318 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 @@ -60,6 +59,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)" @@ -84,8 +86,17 @@ fmt_ok "Node.js LTS active: $(mise exec -- node --version)" fmt_header "Yarn (via corepack)" +# corepack downloads the real package-manager binary the first time a shim +# (yarn) runs. By default it stops to ask "about to download ... continue?" on +# stderr and blocks on stdin — which silently hangs setup, especially when the +# prompt is hidden behind a redirect. 0 makes corepack download without asking. +export COREPACK_ENABLE_DOWNLOAD_PROMPT=0 + if cmd_exists yarn; then - fmt_ok "yarn already available ($(yarn --version 2>/dev/null))" + # Don't run `yarn --version` here: on a machine where the shim exists but the + # binary isn't materialized yet, that first invocation triggers a download + # (and used to hang on the prompt above). `command -v` is enough to confirm. + fmt_ok "yarn already available" else fmt_install "Enabling corepack for yarn" mise exec -- corepack enable diff --git a/lib/packages_setup.sh b/lib/packages_setup.sh index 4b425d1..aa0a3ee 100644 --- a/lib/packages_setup.sh +++ b/lib/packages_setup.sh @@ -8,6 +8,12 @@ fmt_header "Package Manager" case "$OS" in macos) + # Never prompt for confirmation on brew install/upgrade — setup must run + # unattended. Export for this session so every brew call below inherits it; + # persistence is handled after Homebrew is activated. (The Homebrew install + # script itself is silenced separately with NONINTERACTIVE=1 below.) + export HOMEBREW_NO_ASK=1 + # Activate Homebrew for this session if it exists but isn't on PATH yet. # This covers re-runs in the same terminal before the shell RC is sourced. if ! cmd_exists brew; then @@ -22,7 +28,28 @@ case "$OS" in fmt_ok "Homebrew already installed" else fmt_install "Homebrew" - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + + # The installer needs sudo to create /opt/homebrew. In NONINTERACTIVE + # mode it never prompts for a password (it probes with `sudo -n`) and so + # aborts with "Need sudo access" unless credentials are already cached. + # Prime the sudo timestamp here — a single up-front password prompt — so + # the install runs unattended WITHOUT running all of setup.sh as root. + echo " Homebrew needs administrator access. You may be prompted for your password once." + sudo -v + + # Keep the sudo timestamp fresh for the length of the install, which can + # outlast sudo's default 5-minute cache. `$$` inside the subshell is the + # main setup process, so the keep-alive stops when setup exits. The + # `|| true` guards against set -e when credentials eventually lapse. + ( while kill -0 "$$" 2>/dev/null; do sudo -n true 2>/dev/null || true; sleep 50; done ) & + sudo_keepalive_pid=$! + + # NONINTERACTIVE=1 skips the installer's "Press RETURN to continue" + # confirmation. (HOMEBREW_NO_ASK only affects brew install/upgrade, not + # this bootstrap script.) + NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + + kill "$sudo_keepalive_pid" 2>/dev/null || true # Add Homebrew to PATH for this session if [ -f /opt/homebrew/bin/brew ]; then @@ -32,20 +59,31 @@ 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 + + # Persist HOMEBREW_NO_ASK so brew never prompts for confirmation, even + # outside setup (guarded separately so it lands on machines whose rc + # already has the brew shellenv line). + if ! grep -qF "HOMEBREW_NO_ASK" "$brew_rc"; then + { + echo "" + echo "# Homebrew: never prompt for confirmation" + echo "export HOMEBREW_NO_ASK=1" + } >> "$brew_rc" + echo " Added HOMEBREW_NO_ASK to $brew_rc" fi fi ;; diff --git a/setup.sh b/setup.sh index afb8a1a..9a43881 100755 --- a/setup.sh +++ b/setup.sh @@ -14,6 +14,28 @@ set -euo pipefail +# --------------------------------------------------------------------------- +# Xcode Command Line Tools (macOS only) +# --------------------------------------------------------------------------- +# +# git, Homebrew, and compiling native gems all depend on the Xcode Command +# Line Tools, so install them before anything else. This runs before +# common.sh is sourced — OS detection and the fmt_ helpers don't exist yet — +# so detect macOS directly with uname and use plain echo. + +if [ "$(uname -s)" = "Darwin" ] && ! xcode-select -p > /dev/null 2>&1; then + echo "Installing Xcode Command Line Tools..." + xcode-select --install || true + + # xcode-select --install returns immediately and runs a GUI installer. + # Block until the tools are actually present so later steps can rely on them. + until xcode-select -p > /dev/null 2>&1; do + sleep 5 + done + + echo "Xcode Command Line Tools installed." +fi + # --------------------------------------------------------------------------- # Resolve script directory (works for both local execution and curl pipe) # --------------------------------------------------------------------------- @@ -152,6 +174,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 # --------------------------------------------------------------------------- @@ -177,7 +202,28 @@ 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 "" + +# --------------------------------------------------------------------------- +# Restart your shell +# --------------------------------------------------------------------------- +# +# Setup wrote shell config (PATH and tool activation) to your login shell rc. +# Those changes only apply to newly started shells, so the terminal you ran +# this from — and any others already open — are stale. Forcing a fresh shell +# in-place proved fragile and clunky, so just ask the user to restart. + echo "" +echo "${COLOR_RED}⚠️ ⚠️ ⚠️ ACTION REQUIRED ⚠️ ⚠️ ⚠️${COLOR_RESET}" +echo "" +echo "${COLOR_RED}Close ALL open terminal windows and tabs, then open a new one so the${COLOR_RESET}" +echo "${COLOR_RED}setup changes (PATH, tool activation, etc.) fully take effect.${COLOR_RESET}" +echo "" + +if [ "$OS" != "macos" ] && getent group docker 2>/dev/null | grep -qw "$USER"; then + echo "${COLOR_RED}You were also added to the 'docker' group — on Linux you may need to${COLOR_RESET}" + echo "${COLOR_RED}fully log out and back in (or reboot) for that to take effect.${COLOR_RESET}" + echo "" +fi