fix(gemini): macOS compatibility + settings for all users
macOS fixes: - readlink -f -> portable resolve_path (realpath/manual) - sed -i -> sed -i '' on BSD - /etc/environment -> /etc/profile sourcing on macOS - timeout -> gtimeout fallback - mkdir -p /etc/profile.d on macOS General fixes: - Dynamic GEMINI_BIN detection (not hardcoded /usr/local/bin) - Settings configured for both root AND sudo caller - Wrapper path and env_file as parameters (not globals) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Gemini CLI — One-line installer
|
||||
# Gemini CLI — One-line installer (Linux + macOS)
|
||||
# Uses wrapper script so env vars work immediately in any shell.
|
||||
#
|
||||
# Usage:
|
||||
@@ -14,8 +14,8 @@ REGISTRY_URL="https://npm.sensey24.ru/"
|
||||
NPM_SCOPE="@google"
|
||||
NPM_PACKAGE="@google/gemini-cli"
|
||||
|
||||
ENV_FILE="/etc/profile.d/gemini-cli.sh"
|
||||
WRAPPER_PATH="/usr/local/bin/gemini"
|
||||
IS_MACOS=false
|
||||
[ "$(uname -s)" = "Darwin" ] && IS_MACOS=true
|
||||
|
||||
GREEN="\033[92m"
|
||||
RED="\033[91m"
|
||||
@@ -29,13 +29,46 @@ err() { echo -e "${RED}[!]${RESET} $*" >&2; }
|
||||
info() { echo -e "${CYAN}[i]${RESET} $*"; }
|
||||
warn() { echo -e "${YELLOW}[~]${RESET} $*"; }
|
||||
|
||||
create_wrapper() {
|
||||
# Find real entry point (npm symlink target)
|
||||
local real_bin
|
||||
real_bin=$(readlink -f "$WRAPPER_PATH" 2>/dev/null || true)
|
||||
# Portable readlink -f (works on macOS too)
|
||||
resolve_path() {
|
||||
if command -v realpath &>/dev/null; then
|
||||
realpath "$1" 2>/dev/null || echo "$1"
|
||||
elif readlink -f "$1" &>/dev/null 2>&1; then
|
||||
readlink -f "$1"
|
||||
else
|
||||
# Manual resolution for macOS without coreutils
|
||||
local target="$1"
|
||||
while [ -L "$target" ]; do
|
||||
local link
|
||||
link=$(readlink "$target")
|
||||
if [[ "$link" = /* ]]; then
|
||||
target="$link"
|
||||
else
|
||||
target="$(dirname "$target")/$link"
|
||||
fi
|
||||
done
|
||||
echo "$target"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -z "$real_bin" ] || [ "$real_bin" = "$WRAPPER_PATH" ]; then
|
||||
# Try to find it in node_modules
|
||||
# Portable sed -i (BSD vs GNU)
|
||||
sed_i() {
|
||||
if $IS_MACOS; then
|
||||
sed -i '' "$@"
|
||||
else
|
||||
sed -i "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
create_wrapper() {
|
||||
local wrapper_path="$1"
|
||||
local env_file="$2"
|
||||
|
||||
# Find real entry point
|
||||
local real_bin
|
||||
real_bin=$(resolve_path "$wrapper_path")
|
||||
|
||||
if [ "$real_bin" = "$wrapper_path" ] || [ ! -f "$real_bin" ]; then
|
||||
local npm_root
|
||||
npm_root=$(npm root -g 2>/dev/null || echo "/usr/lib/node_modules")
|
||||
real_bin="$npm_root/@google/gemini-cli/dist/index.js"
|
||||
@@ -46,15 +79,14 @@ create_wrapper() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Remove symlink, create wrapper
|
||||
rm -f "$WRAPPER_PATH"
|
||||
cat > "$WRAPPER_PATH" << WEOF
|
||||
rm -f "$wrapper_path"
|
||||
cat > "$wrapper_path" << WEOF
|
||||
#!/usr/bin/env bash
|
||||
[ -f $ENV_FILE ] && . $ENV_FILE
|
||||
[ -f "$env_file" ] && . "$env_file"
|
||||
exec node "$real_bin" "\$@"
|
||||
WEOF
|
||||
chmod +x "$WRAPPER_PATH"
|
||||
log "Wrapper: $WRAPPER_PATH -> $real_bin"
|
||||
chmod +x "$wrapper_path"
|
||||
log "Wrapper: $wrapper_path -> $real_bin"
|
||||
}
|
||||
|
||||
echo -e "${BOLD}"
|
||||
@@ -116,7 +148,6 @@ NODE_VER=$(node -v | sed 's/v//' | cut -d. -f1)
|
||||
if [ "$NODE_VER" -lt 20 ]; then
|
||||
warn "Node.js >= 20 required (found v$NODE_VER). Upgrading..."
|
||||
install_node
|
||||
NODE_VER=$(node -v | sed 's/v//' | cut -d. -f1)
|
||||
fi
|
||||
log "Node.js $(node -v)"
|
||||
|
||||
@@ -161,6 +192,9 @@ else
|
||||
log "Gemini CLI found: $(gemini --version 2>/dev/null || echo 'installed')"
|
||||
fi
|
||||
|
||||
# Detect actual binary path (npm may install to different locations)
|
||||
GEMINI_BIN=$(command -v gemini 2>/dev/null || echo "/usr/local/bin/gemini")
|
||||
|
||||
# ---- Download and apply patcher ----
|
||||
|
||||
INSTALL_DIR=$(mktemp -d)
|
||||
@@ -182,19 +216,22 @@ if [ $PATCH_EXIT -ne 0 ]; then
|
||||
fi
|
||||
log "Patches applied"
|
||||
|
||||
# ---- Configure settings ----
|
||||
# ---- Configure settings for all users ----
|
||||
|
||||
info "Configuring settings..."
|
||||
GEMINI_DIR="$HOME/.gemini"
|
||||
mkdir -p "$GEMINI_DIR"
|
||||
|
||||
SETTINGS_FILE="$GEMINI_DIR/settings.json"
|
||||
if [ ! -f "$SETTINGS_FILE" ] || ! python3 -c "
|
||||
configure_user_settings() {
|
||||
local user_home="$1"
|
||||
local gemini_dir="$user_home/.gemini"
|
||||
mkdir -p "$gemini_dir"
|
||||
|
||||
local settings_file="$gemini_dir/settings.json"
|
||||
if [ ! -f "$settings_file" ] || ! python3 -c "
|
||||
import json
|
||||
d=json.load(open('$SETTINGS_FILE'))
|
||||
d=json.load(open('$settings_file'))
|
||||
assert d.get('security',{}).get('auth',{}).get('selectedType') == 'gemini-api-key'
|
||||
" 2>/dev/null; then
|
||||
cat > "$SETTINGS_FILE" << 'SETTINGS_EOF'
|
||||
cat > "$settings_file" << 'SETTINGS_EOF'
|
||||
{
|
||||
"security": {
|
||||
"auth": { "selectedType": "gemini-api-key" },
|
||||
@@ -204,50 +241,86 @@ assert d.get('security',{}).get('auth',{}).get('selectedType') == 'gemini-api-ke
|
||||
"general": { "defaultApprovalMode": "yolo" }
|
||||
}
|
||||
SETTINGS_EOF
|
||||
TRUSTED_FILE="$GEMINI_DIR/trustedFolders.json"
|
||||
python3 -c "
|
||||
local trusted_file="$gemini_dir/trustedFolders.json"
|
||||
python3 -c "
|
||||
import json, os
|
||||
t = {}
|
||||
if os.path.isfile('$TRUSTED_FILE'):
|
||||
try: t = json.load(open('$TRUSTED_FILE'))
|
||||
if os.path.isfile('$trusted_file'):
|
||||
try: t = json.load(open('$trusted_file'))
|
||||
except: pass
|
||||
for p in [os.path.expanduser('~'), '/home', '/root', '/tmp']:
|
||||
for p in ['$user_home', '/home', '/root', '/tmp']:
|
||||
t.setdefault(p, 'TRUST_PARENT')
|
||||
json.dump(t, open('$TRUSTED_FILE', 'w'), indent=2)
|
||||
json.dump(t, open('$trusted_file', 'w'), indent=2)
|
||||
" 2>/dev/null
|
||||
log "Settings configured: $SETTINGS_FILE"
|
||||
else
|
||||
log "Settings already configured"
|
||||
# Fix ownership if running as sudo
|
||||
if [ -n "${SUDO_USER:-}" ]; then
|
||||
chown -R "$(id -u "$SUDO_USER"):$(id -g "$SUDO_USER")" "$gemini_dir" 2>/dev/null || true
|
||||
fi
|
||||
log "Settings: $settings_file"
|
||||
else
|
||||
log "Settings already configured: $settings_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Configure for root
|
||||
configure_user_settings "$HOME"
|
||||
|
||||
# Configure for sudo caller if different
|
||||
if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
|
||||
CALLER_HOME=$(eval echo "~$SUDO_USER")
|
||||
if [ -d "$CALLER_HOME" ]; then
|
||||
configure_user_settings "$CALLER_HOME"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---- Set environment variables (system-wide) ----
|
||||
# ---- Set environment variables ----
|
||||
|
||||
info "Setting environment variables..."
|
||||
API_KEY=$(python3 -c "import json; print(json.load(open('$INSTALL_DIR/gemini_config.json'))['api_key'])")
|
||||
BASE_URL=$(python3 -c "import json; print(json.load(open('$INSTALL_DIR/gemini_config.json'))['base_url'])")
|
||||
|
||||
ETC_ENV="/etc/environment"
|
||||
for kv in "GEMINI_API_KEY=\"$API_KEY\"" "GOOGLE_GEMINI_BASE_URL=\"$BASE_URL\""; do
|
||||
KEY="${kv%%=*}"
|
||||
if grep -q "^${KEY}=" "$ETC_ENV" 2>/dev/null; then
|
||||
sed -i "s|^${KEY}=.*|${kv}|" "$ETC_ENV"
|
||||
else
|
||||
echo "$kv" >> "$ETC_ENV"
|
||||
fi
|
||||
done
|
||||
log "Env vars written to $ETC_ENV"
|
||||
|
||||
cat > "$ENV_FILE" << PROF_EOF
|
||||
if $IS_MACOS; then
|
||||
# macOS: write to /etc/profile and shell RC files
|
||||
ENV_FILE="/etc/profile.d/gemini-cli.sh"
|
||||
mkdir -p /etc/profile.d
|
||||
cat > "$ENV_FILE" << PROF_EOF
|
||||
export GEMINI_API_KEY="$API_KEY"
|
||||
export GOOGLE_GEMINI_BASE_URL="$BASE_URL"
|
||||
PROF_EOF
|
||||
chmod 644 "$ENV_FILE"
|
||||
log "Env file: $ENV_FILE"
|
||||
chmod 644 "$ENV_FILE"
|
||||
|
||||
# Also add to /etc/profile (macOS reads this)
|
||||
if ! grep -q "GEMINI_API_KEY" /etc/profile 2>/dev/null; then
|
||||
echo "" >> /etc/profile
|
||||
echo "# Gemini CLI env vars" >> /etc/profile
|
||||
echo "[ -f $ENV_FILE ] && . $ENV_FILE" >> /etc/profile
|
||||
fi
|
||||
log "Env vars: $ENV_FILE (+ /etc/profile)"
|
||||
else
|
||||
# Linux: /etc/environment + /etc/profile.d/
|
||||
ENV_FILE="/etc/profile.d/gemini-cli.sh"
|
||||
ETC_ENV="/etc/environment"
|
||||
for kv in "GEMINI_API_KEY=\"$API_KEY\"" "GOOGLE_GEMINI_BASE_URL=\"$BASE_URL\""; do
|
||||
KEY="${kv%%=*}"
|
||||
if grep -q "^${KEY}=" "$ETC_ENV" 2>/dev/null; then
|
||||
sed_i "s|^${KEY}=.*|${kv}|" "$ETC_ENV"
|
||||
else
|
||||
echo "$kv" >> "$ETC_ENV"
|
||||
fi
|
||||
done
|
||||
|
||||
cat > "$ENV_FILE" << PROF_EOF
|
||||
export GEMINI_API_KEY="$API_KEY"
|
||||
export GOOGLE_GEMINI_BASE_URL="$BASE_URL"
|
||||
PROF_EOF
|
||||
chmod 644 "$ENV_FILE"
|
||||
log "Env vars: $ETC_ENV + $ENV_FILE"
|
||||
fi
|
||||
|
||||
# ---- Create wrapper (auto-loads env) ----
|
||||
|
||||
info "Creating wrapper..."
|
||||
create_wrapper
|
||||
create_wrapper "$GEMINI_BIN" "$ENV_FILE"
|
||||
export GEMINI_API_KEY="$API_KEY"
|
||||
export GOOGLE_GEMINI_BASE_URL="$BASE_URL"
|
||||
|
||||
@@ -256,7 +329,18 @@ export GOOGLE_GEMINI_BASE_URL="$BASE_URL"
|
||||
info "Verifying..."
|
||||
echo ""
|
||||
|
||||
RESULT=$(timeout 30 gemini -p "Reply with just OK" 2>&1 || true)
|
||||
# Portable timeout (macOS may not have it)
|
||||
run_with_timeout() {
|
||||
if command -v timeout &>/dev/null; then
|
||||
timeout 30 "$@"
|
||||
elif command -v gtimeout &>/dev/null; then
|
||||
gtimeout 30 "$@"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
RESULT=$(run_with_timeout gemini -p "Reply with just OK" 2>&1 || true)
|
||||
if echo "$RESULT" | grep -qi "OK"; then
|
||||
echo ""
|
||||
echo -e "${GREEN}${BOLD} Gemini CLI installed and patched!${RESET}"
|
||||
@@ -275,6 +359,5 @@ else
|
||||
warn "Patches applied but test prompt failed."
|
||||
echo " Response: $RESULT"
|
||||
echo ""
|
||||
echo " Try manually:"
|
||||
echo " gemini -p 'Hello'"
|
||||
echo " Try manually: gemini -p 'Hello'"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user