mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-07 01:22:48 -07:00
179 lines
5.0 KiB
Bash
179 lines
5.0 KiB
Bash
#!/usr/bin/env bash
|
|
# GCE startup script — runs on VM boot.
|
|
# Installs Node.js, clones, builds, starts forever agent + bridge.
|
|
# Pass gemini-api-key via instance metadata.
|
|
set -euo pipefail
|
|
|
|
LOG="/var/log/forever-agent-startup.log"
|
|
exec > >(tee -a "$LOG") 2>&1
|
|
echo "=== Forever Agent startup $(date) ==="
|
|
|
|
# Read API key from instance metadata
|
|
META="http://metadata.google.internal/computeMetadata/v1"
|
|
meta() { curl -sf -H "Metadata-Flavor: Google" "$META/instance/attributes/$1" || echo "${2:-}"; }
|
|
proj() { curl -sf -H "Metadata-Flavor: Google" "$META/project/project-id" || echo ""; }
|
|
|
|
GEMINI_API_KEY=$(meta gemini-api-key)
|
|
GIT_BRANCH=$(meta git-branch "afw/forever-gchat")
|
|
GCP_PROJECT=$(meta gcp-project "$(proj)")
|
|
PUBSUB_SUB=$(meta pubsub-subscription "forever-agent-chat-sub")
|
|
|
|
if [ -z "$GEMINI_API_KEY" ]; then
|
|
echo "ERROR: gemini-api-key not set in instance metadata"
|
|
exit 1
|
|
fi
|
|
echo "API key loaded (${#GEMINI_API_KEY} chars)"
|
|
|
|
# Install Node.js + screen (first boot only)
|
|
if ! command -v node &>/dev/null; then
|
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
|
apt-get install -y nodejs git screen
|
|
fi
|
|
echo "Node $(node --version)"
|
|
|
|
# Clone/update repo
|
|
REPO_DIR="/opt/forever-agent"
|
|
if [ ! -d "$REPO_DIR" ]; then
|
|
GIT_TERMINAL_PROMPT=0 git clone -b "$GIT_BRANCH" \
|
|
https://github.com/google-gemini/gemini-cli.git "$REPO_DIR"
|
|
else
|
|
cd "$REPO_DIR" && GIT_TERMINAL_PROMPT=0 git pull --ff-only || true
|
|
fi
|
|
|
|
# npm install && npm run build
|
|
cd "$REPO_DIR"
|
|
npm install 2>&1 | tail -5
|
|
npm run build 2>&1 | tail -10
|
|
|
|
# Pre-create workspace and config to skip interactive prompts
|
|
WORK_DIR="/opt/forever-workspace"
|
|
mkdir -p "$WORK_DIR/.gemini"
|
|
mkdir -p /root/.gemini
|
|
|
|
# Pre-trust the workspace folder (skips "Do you trust this folder?" dialog)
|
|
# --yolo doesn't bypass folder trust, so we must pre-configure it
|
|
cat > /root/.gemini/trustedFolders.json << 'TRUSTEOF'
|
|
{
|
|
"/opt/forever-workspace": "TRUST_FOLDER"
|
|
}
|
|
TRUSTEOF
|
|
|
|
# Disable folder trust + skip session retention dialog
|
|
# Write to both user-level AND workspace-level to be safe
|
|
for SETTINGS_DIR in /root/.gemini "$WORK_DIR/.gemini"; do
|
|
mkdir -p "$SETTINGS_DIR"
|
|
cat > "$SETTINGS_DIR/settings.json" << 'SETTINGSEOF'
|
|
{
|
|
"security": {
|
|
"folderTrust": {
|
|
"enabled": false
|
|
},
|
|
"auth": {
|
|
"selectedType": "gemini-api-key",
|
|
"useExternal": true
|
|
}
|
|
},
|
|
"general": {
|
|
"sessionRetention": {
|
|
"enabled": true,
|
|
"maxAge": "30d",
|
|
"warningAcknowledged": true
|
|
}
|
|
}
|
|
}
|
|
SETTINGSEOF
|
|
done
|
|
|
|
cat > "$WORK_DIR/.gemini/GEMINI.md" << 'GEMINIEOF'
|
|
---
|
|
sisyphus:
|
|
enabled: true
|
|
idleTimeout: 30
|
|
prompt: "continue with the next task"
|
|
---
|
|
|
|
# Mission
|
|
You are a forever-running autonomous agent accessible via Google Chat.
|
|
Process incoming tasks, answer questions, and proactively work on improvements.
|
|
GEMINIEOF
|
|
|
|
# Create systemd service for the chat bridge (Pub/Sub mode)
|
|
cat > /etc/systemd/system/chat-bridge.service << EOF
|
|
[Unit]
|
|
Description=Google Chat Bridge (Pub/Sub)
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
Environment=A2A_URL=http://127.0.0.1:3100
|
|
Environment=GOOGLE_CLOUD_PROJECT=${GCP_PROJECT}
|
|
Environment=PUBSUB_SUBSCRIPTION=${PUBSUB_SUB}
|
|
Environment=GIT_TERMINAL_PROMPT=0
|
|
WorkingDirectory=${REPO_DIR}
|
|
ExecStart=/usr/bin/node ${REPO_DIR}/packages/a2a-server/dist/src/chat-bridge/bridge.js
|
|
Restart=always
|
|
RestartSec=5
|
|
StandardOutput=inherit
|
|
StandardError=inherit
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
# Write env vars to a file — sourced by the wrapper script
|
|
cat > /etc/forever-agent.env << ENVEOF
|
|
export HOME=/root
|
|
export GEMINI_CLI_HOME=/root
|
|
export GOOGLE_API_KEY='${GEMINI_API_KEY}'
|
|
export GEMINI_API_KEY='${GEMINI_API_KEY}'
|
|
export A2A_PORT=3100
|
|
export GIT_TERMINAL_PROMPT=0
|
|
ENVEOF
|
|
chmod 600 /etc/forever-agent.env
|
|
|
|
# Create a wrapper script that sources env and uses 'script' for pseudo-TTY
|
|
cat > /usr/local/bin/start-forever-agent.sh << 'WRAPPEREOF'
|
|
#!/usr/bin/env bash
|
|
set -a
|
|
source /etc/forever-agent.env
|
|
set +a
|
|
# script provides a pseudo-TTY for Ink; output goes to /dev/null (TUI noise)
|
|
exec /usr/bin/script -qfc "node /opt/forever-agent/packages/cli/dist/index.js --forever --a2a-port 3100 --yolo" /dev/null
|
|
WRAPPEREOF
|
|
chmod +x /usr/local/bin/start-forever-agent.sh
|
|
|
|
# Create systemd service for the forever agent
|
|
cat > /etc/systemd/system/forever-agent.service << EOF
|
|
[Unit]
|
|
Description=Gemini CLI Forever Agent
|
|
After=network.target chat-bridge.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
Environment=GOOGLE_API_KEY=${GEMINI_API_KEY}
|
|
Environment=A2A_PORT=3100
|
|
Environment=GIT_TERMINAL_PROMPT=0
|
|
Environment=HOME=/root
|
|
Environment=GEMINI_CLI_HOME=/root
|
|
WorkingDirectory=${WORK_DIR}
|
|
ExecStart=/usr/local/bin/start-forever-agent.sh
|
|
Restart=on-failure
|
|
RestartSec=10
|
|
StandardOutput=null
|
|
StandardError=null
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
# Restart both services (restart, not start — pick up newly built code)
|
|
systemctl daemon-reload
|
|
systemctl enable chat-bridge forever-agent
|
|
systemctl restart chat-bridge
|
|
sleep 2
|
|
systemctl restart forever-agent
|
|
|
|
echo "=== Chat bridge started on port 8081 ==="
|
|
echo "=== Forever agent started in screen session ==="
|
|
echo "=== Startup complete $(date) ==="
|