-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup.sh
More file actions
552 lines (464 loc) · 19.9 KB
/
Copy pathsetup.sh
File metadata and controls
552 lines (464 loc) · 19.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
#!/usr/bin/env bash
# =============================================================================
# Gelegram - Onboarding Setup Script (Linux / macOS)
# =============================================================================
# This script automates the full installation pipeline for Gelegram:
# 1. Checks/installs Node.js (via nvm, brew, or system package manager)
# 2. Checks/installs uv (Python package manager)
# 3. Checks/installs Gemini CLI (npm global)
# 4. Creates Python venv and installs dependencies
# 5. Configures .env (bot token, password, workspace path, gemini CLI path)
# 6. Optionally installs as a background service
# Linux : systemd user service
# macOS : launchd LaunchAgent plist
# 7. Runs gemini auth for Google OAuth (final step)
#
# Usage:
# chmod +x setup.sh && ./setup.sh
#
# The script is idempotent -- safe to re-run. Already-completed steps are
# skipped with a green [OK] message.
# =============================================================================
set -euo pipefail
# -----------------------------------------------------------------------------
# Resolve script directory (follows symlinks)
# -----------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# -----------------------------------------------------------------------------
# Detect OS
# -----------------------------------------------------------------------------
OS="$(uname -s)"
case "$OS" in
Linux*) PLATFORM="linux" ;;
Darwin*) PLATFORM="macos" ;;
*) echo "[X] Unsupported platform: $OS. Use setup.ps1 on Windows."; exit 1 ;;
esac
# -----------------------------------------------------------------------------
# Colour helpers
# -----------------------------------------------------------------------------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
GRAY='\033[0;90m'
BOLD='\033[1m'
NC='\033[0m' # No Colour
write_step() { echo ""; echo -e " ${CYAN}>> $1${NC}"; echo -e " ${GRAY}$(printf '%0.s-' {1..60})${NC}"; }
write_ok() { echo -e " ${GREEN}[OK]${NC} $1"; }
write_notice() { echo -e " ${YELLOW}[i]${NC} $1"; }
write_err() { echo -e " ${RED}[X]${NC} $1"; }
write_info() { echo -e " ${BOLD}$1${NC}"; }
# Prompt helper (compatible with bash and zsh)
ask() {
local prompt="$1"
local default="${2:-}"
local answer
if [ -n "$default" ]; then
read -r -p " $prompt (default: $default): " answer
echo "${answer:-$default}"
else
read -r -p " $prompt: " answer
echo "$answer"
fi
}
# =============================================================================
# BANNER
# =============================================================================
echo ""
echo -e " ${BOLD}========================================================"
echo -e " Gelegram - Onboarding Setup (${PLATFORM})"
echo -e " ========================================================${NC}"
echo ""
# =============================================================================
# PHASE 1: Pre-flight & Detection
# =============================================================================
write_step "Detecting environment"
CURRENT_USER="$(whoami)"
USER_HOME="$HOME"
GEMINI_DIR="$HOME/.gemini"
write_info "User : $CURRENT_USER"
write_info "Home : $USER_HOME"
write_info "Project : $SCRIPT_DIR"
write_info "Platform : $PLATFORM"
if [ -d "$GEMINI_DIR" ]; then
write_ok ".gemini directory found: $GEMINI_DIR"
else
write_notice ".gemini directory not found -- will be created during gemini auth"
fi
# =============================================================================
# PHASE 2: Dependency Installation
# =============================================================================
# -- 2.1 Node.js --------------------------------------------------------------
write_step "Checking Node.js"
if command -v node &>/dev/null; then
NODE_VER="$(node --version)"
write_ok "Node.js already installed: $NODE_VER"
else
write_notice "Node.js not found. Attempting installation..."
# -- Strategy 1: nvm (most portable, user-level)
NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
if [ -s "$NVM_DIR/nvm.sh" ]; then
# shellcheck source=/dev/null
source "$NVM_DIR/nvm.sh"
write_notice "nvm found -- installing Node.js LTS..."
nvm install --lts
nvm use --lts
NODE_VER="$(node --version)"
write_ok "Node.js installed via nvm: $NODE_VER"
elif command -v brew &>/dev/null; then
# -- Strategy 2: Homebrew (macOS / Linux with brew)
write_notice "Installing Node.js via Homebrew..."
brew install node
NODE_VER="$(node --version)"
write_ok "Node.js installed via brew: $NODE_VER"
elif [ "$PLATFORM" = "linux" ]; then
# -- Strategy 3: System package manager
if command -v apt-get &>/dev/null; then
write_notice "Installing Node.js via apt (NodeSource LTS)..."
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs
elif command -v dnf &>/dev/null; then
write_notice "Installing Node.js via dnf..."
sudo dnf install -y nodejs npm
elif command -v pacman &>/dev/null; then
write_notice "Installing Node.js via pacman..."
sudo pacman -S --noconfirm nodejs npm
else
write_err "No supported package manager found (apt/dnf/pacman/brew)."
write_notice "Install nvm first: https://github.com/nvm-sh/nvm"
write_notice "Or install Node.js manually: https://nodejs.org"
exit 1
fi
NODE_VER="$(node --version 2>/dev/null || echo 'unknown')"
write_ok "Node.js installed: $NODE_VER"
elif [ "$PLATFORM" = "macos" ]; then
write_err "Homebrew not found. Install it first: https://brew.sh"
write_notice "Or install nvm: https://github.com/nvm-sh/nvm"
exit 1
fi
# Reload PATH after installation
export PATH="$PATH:/usr/local/bin:/usr/bin"
fi
# -- 2.2 uv -------------------------------------------------------------------
write_step "Checking uv (Python package manager)"
if command -v uv &>/dev/null; then
UV_VER="$(uv --version)"
write_ok "uv already installed: $UV_VER"
else
write_notice "uv not found. Installing..."
curl -LsSf https://astral.sh/uv/install.sh | sh
# uv installs to ~/.local/bin or ~/.cargo/bin -- add to PATH for this session
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
if command -v uv &>/dev/null; then
UV_VER="$(uv --version)"
write_ok "uv installed: $UV_VER"
else
write_err "uv installed but not found on PATH."
write_notice "Restart your terminal and re-run setup.sh"
exit 1
fi
fi
# -- 2.3 Gemini CLI -----------------------------------------------------------
write_step "Checking Gemini CLI"
GEMINI_INSTALLED=false
if npm list -g @google/gemini-cli 2>/dev/null | grep -q "gemini-cli"; then
GEMINI_INSTALLED=true
fi
if ! $GEMINI_INSTALLED && command -v gemini &>/dev/null; then
GEMINI_INSTALLED=true
fi
if $GEMINI_INSTALLED; then
write_ok "Gemini CLI already installed."
else
write_notice "Gemini CLI not found. Installing via npm..."
npm install -g @google/gemini-cli
write_ok "Gemini CLI installed."
fi
# Resolve gemini CLI path (used for .env and auth later)
GEMINI_CLI_PATH=""
if GEMINI_CLI_PATH="$(command -v gemini 2>/dev/null)"; then
write_ok "Gemini CLI path: $GEMINI_CLI_PATH"
else
write_notice "Gemini CLI path not on PATH -- will set to 'gemini' in .env"
GEMINI_CLI_PATH="gemini"
fi
# =============================================================================
# PHASE 3: Python Environment
# =============================================================================
write_step "Setting up Python virtual environment"
VENV_PYTHON="$SCRIPT_DIR/.venv/bin/python"
if [ -f "$VENV_PYTHON" ]; then
write_ok "Virtual environment already exists: .venv"
else
write_notice "Creating virtual environment with uv..."
(cd "$SCRIPT_DIR" && uv venv .venv)
write_ok "Virtual environment created."
fi
# -- Install Python dependencies -----------------------------------------------
write_step "Installing Python dependencies"
REQUIREMENTS="$SCRIPT_DIR/requirements.txt"
if [ ! -f "$REQUIREMENTS" ]; then
write_err "requirements.txt not found at: $REQUIREMENTS"
exit 1
fi
write_notice "Running: uv pip install -r requirements.txt"
(cd "$SCRIPT_DIR" && uv pip install -r requirements.txt --python "$VENV_PYTHON")
write_ok "Dependencies installed."
# =============================================================================
# PHASE 4: Configuration (.env)
# =============================================================================
write_step "Configuring environment (.env)"
ENV_FILE="$SCRIPT_DIR/.env"
ENV_EXAMPLE="$SCRIPT_DIR/.env.example"
if [ -f "$ENV_FILE" ]; then
write_ok ".env file already exists."
write_notice "Skipping configuration prompts (edit .env manually if needed)."
else
if [ ! -f "$ENV_EXAMPLE" ]; then
write_err ".env.example template not found!"
exit 1
fi
echo ""
echo -e " ${BOLD}Please provide the following configuration values.${NC}"
echo -e " ${GRAY}(Get a bot token from @BotFather on Telegram)${NC}"
echo ""
# -- Prompt: Telegram Bot Token
BOT_TOKEN="$(ask "Enter your Telegram Bot Token")"
if [ -z "$BOT_TOKEN" ]; then
write_notice "No token entered -- edit .env manually."
fi
# -- Prompt: Bot Password
BOT_PASSWORD="$(ask "Set a Telegram password (or press Enter to skip)")"
# -- Prompt: Workspace Location
WORKSPACE="$(ask "Workspace directory" "./workdir")"
# BUG FIX: Read template and write with prefix matching instead of sed replacements to prevent escaping/special character failures
while IFS= read -r line || [ -n "$line" ]; do
if [[ "$line" =~ ^TELEGRAM_BOT_TOKEN= ]]; then
if [ -n "$BOT_TOKEN" ]; then
echo "TELEGRAM_BOT_TOKEN=$BOT_TOKEN"
else
echo "$line"
fi
elif [[ "$line" =~ ^BOT_PASSWORD= ]]; then
if [ -n "$BOT_PASSWORD" ]; then
echo "BOT_PASSWORD=$BOT_PASSWORD"
else
echo "$line"
fi
elif [[ "$line" =~ ^GEMINI_WORKING_DIR= ]]; then
echo "GEMINI_WORKING_DIR=$WORKSPACE"
elif [[ "$line" =~ ^GEMINI_CLI_PATH= ]]; then
echo "GEMINI_CLI_PATH=$GEMINI_CLI_PATH"
else
echo "$line"
fi
done < "$ENV_EXAMPLE" > "$ENV_FILE"
write_ok ".env file created successfully."
fi
# =============================================================================
# PHASE 5: Service Installation
# =============================================================================
write_step "Background Service Installation"
echo ""
echo -e " ${BOLD}Install Gelegram as a background service?${NC}"
echo -e " This makes the bot start automatically and survive crashes."
echo ""
INSTALL_SERVICE="$(ask "Install as service? [Y/n]" "Y")"
# Track whether systemd failed so we can print instructions at the end
SYSTEMD_FAILED=false
if [[ "$INSTALL_SERVICE" =~ ^[Yy]$ ]] || [ "$INSTALL_SERVICE" = "Y" ]; then
if [ "$PLATFORM" = "linux" ]; then
# -- systemd user service ------------------------------------------------
write_notice "Checking systemd user session context..."
# Self-heal systemd user session variables if missing (common in headless SSH or su)
if [ -z "${XDG_RUNTIME_DIR:-}" ]; then
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
if [ ! -d "$XDG_RUNTIME_DIR" ]; then
# Fallback if the typical system runtime dir isn't initialized
export XDG_RUNTIME_DIR="/tmp/user-$(id -u)-runtime"
mkdir -p "$XDG_RUNTIME_DIR"
chmod 700 "$XDG_RUNTIME_DIR"
fi
write_notice "Self-healed XDG_RUNTIME_DIR to: $XDG_RUNTIME_DIR"
fi
# Try to infer local D-Bus address if runtime bus socket exists
if [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ] && [ -S "$XDG_RUNTIME_DIR/bus" ]; then
export DBUS_SESSION_BUS_ADDRESS="unix:path=$XDG_RUNTIME_DIR/bus"
fi
write_notice "Creating systemd user service..."
SYSTEMD_DIR="$HOME/.config/systemd/user"
mkdir -p "$SYSTEMD_DIR"
SERVICE_FILE="$SYSTEMD_DIR/gelegram.service"
cat > "$SERVICE_FILE" << EOF
[Unit]
Description=Gelegram - Telegram to Gemini CLI Bot
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
WorkingDirectory=$SCRIPT_DIR
ExecStart=$VENV_PYTHON $SCRIPT_DIR/gateway.py
Restart=always
RestartSec=10
# gateway.py writes its own gateway.log via Python FileHandler.
# Using 'journal' here prevents duplicate lines in gateway.log (doubled-log-lines bug fix).
# View live: journalctl --user -u gelegram -f
StandardOutput=journal
StandardError=journal
Environment=HOME=$HOME
Environment=USER=$CURRENT_USER
Environment=XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR
[Install]
WantedBy=default.target
EOF
write_ok "Service file created: $SERVICE_FILE"
# Test connection to systemd user manager before running command
# to avoid crashing under 'set -e' if D-Bus is genuinely inaccessible.
if systemctl --user daemon-reload >/dev/null 2>&1; then
systemctl --user enable gelegram.service >/dev/null 2>&1 || true
systemctl --user start gelegram.service >/dev/null 2>&1 || true
# Enable lingering so service survives logout
if command -v loginctl &>/dev/null; then
loginctl enable-linger "$CURRENT_USER" >/dev/null 2>&1 || true
fi
write_ok "Service enabled and started."
write_notice "Check status with: systemctl --user status gelegram"
write_notice "View logs with: journalctl --user -u gelegram -f"
write_notice "Stop with: systemctl --user stop gelegram"
else
SYSTEMD_FAILED=true
write_err "Could not connect to systemd user manager."
write_notice "The service file has been created but could not be activated."
write_notice "Skipping automatic service startup. Moving on to Gemini Auth..."
write_notice "Instructions to activate the service will be shown at the end."
fi
elif [ "$PLATFORM" = "macos" ]; then
# -- launchd LaunchAgent -------------------------------------------------
write_notice "Creating launchd LaunchAgent..."
LAUNCH_AGENTS_DIR="$HOME/Library/LaunchAgents"
mkdir -p "$LAUNCH_AGENTS_DIR"
PLIST_FILE="$LAUNCH_AGENTS_DIR/com.gelegram.bot.plist"
cat > "$PLIST_FILE" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.gelegram.bot</string>
<key>ProgramArguments</key>
<array>
<string>$VENV_PYTHON</string>
<string>$SCRIPT_DIR/gateway.py</string>
</array>
<key>WorkingDirectory</key>
<string>$SCRIPT_DIR</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>$SCRIPT_DIR/gateway.log</string>
<key>StandardErrorPath</key>
<string>$SCRIPT_DIR/gateway.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key>
<string>$HOME</string>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
</dict>
</plist>
EOF
write_ok "Plist created: $PLIST_FILE"
# Load it
launchctl unload "$PLIST_FILE" 2>/dev/null || true
launchctl load "$PLIST_FILE"
write_ok "Service loaded and started."
write_notice "Check status with: launchctl list | grep gelegram"
write_notice "View logs with: tail -f $SCRIPT_DIR/gateway.log"
write_notice "Stop with: launchctl unload $PLIST_FILE"
write_notice "Remove with: launchctl unload $PLIST_FILE && rm $PLIST_FILE"
fi
else
write_ok "Skipping service installation."
echo ""
echo -e " ${BOLD}To run the bot manually:${NC}"
echo -e " ${GRAY} source .venv/bin/activate${NC}"
echo -e " ${GRAY} python gateway.py # with watchdog (recommended)${NC}"
echo -e " ${GRAY} python bot.py # without watchdog${NC}"
echo ""
if [ "$PLATFORM" = "linux" ]; then
write_notice "To install the systemd service later: re-run ./setup.sh"
elif [ "$PLATFORM" = "macos" ]; then
write_notice "To install the launchd service later: re-run ./setup.sh"
fi
fi
# =============================================================================
# PHASE 6: Gemini Auth (final step)
# =============================================================================
write_step "Gemini CLI Authentication"
echo ""
echo -e " ${BOLD}Gemini CLI needs Google OAuth to function.${NC}"
echo -e " A browser window will open -- log in with your Google account."
echo -e " ${GRAY}Close the gemini session (Ctrl+C) after authentication completes.${NC}"
echo ""
if command -v gemini &>/dev/null; then
write_notice "Running: gemini auth"
gemini auth || write_notice "gemini auth exited (this may be normal)"
write_ok "Gemini authentication step completed."
else
write_notice "Could not locate gemini CLI to run auth."
write_notice "After setup, run 'gemini auth' manually to authenticate."
fi
echo ""
echo -e " ${YELLOW}REMINDER: Make sure you logged in with your Google account!${NC}"
echo -e " ${YELLOW}If the browser didn't open, run 'gemini auth' manually.${NC}"
echo ""
# =============================================================================
# SUMMARY
# =============================================================================
echo ""
echo -e " ${BOLD}========================================================"
echo -e " ${GREEN} Gelegram Setup Complete!"
echo -e " ${BOLD}========================================================${NC}"
echo ""
echo -e " User : $CURRENT_USER"
echo -e " Home : $USER_HOME"
echo -e " Project : $SCRIPT_DIR"
echo -e " .gemini : $GEMINI_DIR"
echo ""
echo -e " ${YELLOW}IMPORTANT REMINDERS:${NC}"
echo -e " ${YELLOW} 1. If gemini auth didn't complete, run: gemini auth${NC}"
echo -e " ${YELLOW} 2. Message your bot on Telegram -- it will guide you${NC}"
echo -e " ${YELLOW} through identity setup on the first message.${NC}"
echo ""
# -- Show manual service activation steps if systemd user manager was unreachable
if [ "$SYSTEMD_FAILED" = true ]; then
echo -e " ${RED}ACTION REQUIRED: Systemd service was not activated${NC}"
echo -e " ${YELLOW} The service file was created but could not be started${NC}"
echo -e " ${YELLOW} because this session has no user-level D-Bus connection.${NC}"
echo -e " ${YELLOW} This usually happens in SSH or after 'su' to another user.${NC}"
echo ""
echo -e " ${BOLD}To activate the service, run these commands in a NEW login session:${NC}"
echo -e " ${GRAY} (Log out and SSH back in, or open a fresh terminal)${NC}"
echo ""
echo -e " ${CYAN}# Step 1: Enable the service to start on boot${NC}"
echo -e " systemctl --user enable gelegram.service"
echo ""
echo -e " ${CYAN}# Step 2: Start the service now${NC}"
echo -e " systemctl --user start gelegram.service"
echo ""
echo -e " ${CYAN}# Step 3: Keep the service running even after you log out${NC}"
echo -e " loginctl enable-linger $CURRENT_USER"
echo ""
echo -e " ${CYAN}# Step 4: Verify it is running${NC}"
echo -e " systemctl --user status gelegram"
echo ""
echo -e " ${YELLOW}OR, to run the bot manually right now (no service):${NC}"
echo -e " cd $SCRIPT_DIR"
echo -e " source .venv/bin/activate"
echo -e " python gateway.py"
echo ""
fi