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:
delta-cloud-208e
2026-03-08 11:03:23 +00:00
parent 089f6e0be5
commit f8d1213440
2 changed files with 211 additions and 78 deletions

View File

@@ -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

View File

@@ -1,7 +1,6 @@
#!/usr/bin/env bash
# Gemini CLI — Updater
# Gemini CLI — Updater (Linux + macOS)
# Re-installs latest version from registry + re-applies patches.
# Uses wrapper script so env vars work immediately in any shell.
#
# Usage: sudo bash ugemini_update.sh
set -euo pipefail
@@ -12,8 +11,8 @@ NPM_SCOPE="@google"
NPM_PACKAGE="@google/gemini-cli"
REPO_RAW="https://git.sensey24.ru/aibot777/unlimitedcoding/raw/branch/master/gemini"
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"
CYAN="\033[96m"
@@ -27,24 +26,59 @@ info() { echo -e "${CYAN}[i]${RESET} $*"; }
warn() { echo -e "${YELLOW}[~]${RESET} $*"; }
err() { echo -e "${RED}[!]${RESET} $*" >&2; }
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
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
}
sed_i() {
if $IS_MACOS; then
sed -i '' "$@"
else
sed -i "$@"
fi
}
create_wrapper() {
local npm_root
npm_root=$(npm root -g 2>/dev/null || echo "/usr/lib/node_modules")
local real_bin="$npm_root/@google/gemini-cli/dist/index.js"
local wrapper_path="$1"
local env_file="$2"
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"
fi
if [ ! -f "$real_bin" ]; then
warn "Could not find gemini entry point at $real_bin"
return 1
fi
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}"
@@ -78,6 +112,8 @@ else
exit 1
fi
GEMINI_BIN=$(command -v gemini 2>/dev/null || echo "/usr/local/bin/gemini")
# ---- Download and apply patches ----
TEMP_DIR=$(mktemp -d)
@@ -91,30 +127,44 @@ curl -fsSL -H "Authorization: token ${GITEA_TOKEN}" "$REPO_RAW/gemini_config.jso
info "Applying patches..."
python3 "$TEMP_DIR/gemini_patcher.py" --apply --config "$TEMP_DIR/gemini_config.json"
# ---- Set env vars system-wide ----
# ---- Set env vars ----
API_KEY=$(python3 -c "import json; print(json.load(open('$TEMP_DIR/gemini_config.json'))['api_key'])")
BASE_URL=$(python3 -c "import json; print(json.load(open('$TEMP_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
cat > "$ENV_FILE" << PROF_EOF
if $IS_MACOS; then
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"
chmod 644 "$ENV_FILE"
if ! grep -q "GEMINI_API_KEY" /etc/profile 2>/dev/null; then
echo "" >> /etc/profile
echo "[ -f $ENV_FILE ] && . $ENV_FILE" >> /etc/profile
fi
else
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"
fi
# ---- Create wrapper (auto-loads env) ----
# ---- Create wrapper ----
create_wrapper
create_wrapper "$GEMINI_BIN" "$ENV_FILE"
NEW_VER=$(gemini --version 2>/dev/null || echo "unknown")
log "Version: $OLD_VER -> $NEW_VER"