mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-19 08:14:35 -07:00
feat: one-command deployment script and parameterized startup
- deploy-forever-agent.sh: creates Pub/Sub, static IP, IAM, VM in one command - teardown-forever-agent.sh: cleans up all resources - gce-startup.sh: reads config from instance metadata instead of hardcoding
This commit is contained in:
Executable
+193
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env bash
|
||||
# deploy-forever-agent.sh — One-command deployment of Gemini CLI Forever Agent on GCE.
|
||||
#
|
||||
# Creates a GCE VM running Gemini CLI in --forever mode with a Google Chat bridge
|
||||
# via Cloud Pub/Sub. The agent auto-resumes tasks (Sisyphus mode) and runs in YOLO
|
||||
# (auto-approve all tools).
|
||||
#
|
||||
# Prerequisites:
|
||||
# - gcloud CLI installed and authenticated
|
||||
# - A Google Cloud project with billing enabled
|
||||
# - A Gemini API key (from aistudio.google.com)
|
||||
# - A Google Chat app configured (see instructions printed at the end)
|
||||
#
|
||||
# Usage:
|
||||
# export GEMINI_API_KEY="your-api-key"
|
||||
# ./scripts/deploy-forever-agent.sh
|
||||
#
|
||||
# Optional env vars (defaults shown):
|
||||
# PROJECT - GCP project ID (defaults to gcloud config)
|
||||
# REGION - GCP region (default: us-central1)
|
||||
# ZONE - GCP zone (default: us-central1-a)
|
||||
# VM_NAME - VM name (default: forever-agent)
|
||||
# MACHINE_TYPE - VM machine type (default: e2-medium)
|
||||
# GIT_BRANCH - Branch to clone (default: afw/forever-gchat)
|
||||
# PUBSUB_TOPIC - Pub/Sub topic name (default: forever-agent-chat)
|
||||
# PUBSUB_SUB - Pub/Sub subscription name (default: forever-agent-chat-sub)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# --- Configuration ---
|
||||
|
||||
PROJECT="${PROJECT:-$(gcloud config get-value project 2>/dev/null)}"
|
||||
REGION="${REGION:-us-central1}"
|
||||
ZONE="${ZONE:-us-central1-a}"
|
||||
VM_NAME="${VM_NAME:-forever-agent}"
|
||||
MACHINE_TYPE="${MACHINE_TYPE:-e2-medium}"
|
||||
GIT_BRANCH="${GIT_BRANCH:-afw/forever-gchat}"
|
||||
PUBSUB_TOPIC="${PUBSUB_TOPIC:-forever-agent-chat}"
|
||||
PUBSUB_SUB="${PUBSUB_SUB:-forever-agent-chat-sub}"
|
||||
|
||||
if [ -z "${GEMINI_API_KEY:-}" ]; then
|
||||
echo "ERROR: GEMINI_API_KEY is required. Get one from https://aistudio.google.com"
|
||||
echo "Usage: GEMINI_API_KEY=... ./scripts/deploy-forever-agent.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$PROJECT" ]; then
|
||||
echo "ERROR: No GCP project set. Run: gcloud config set project YOUR_PROJECT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
STARTUP_SCRIPT="${SCRIPT_DIR}/gce-startup.sh"
|
||||
|
||||
if [ ! -f "$STARTUP_SCRIPT" ]; then
|
||||
echo "ERROR: Startup script not found at $STARTUP_SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Deploying Gemini CLI Forever Agent ==="
|
||||
echo " Project: $PROJECT"
|
||||
echo " Zone: $ZONE"
|
||||
echo " VM: $VM_NAME ($MACHINE_TYPE)"
|
||||
echo " Branch: $GIT_BRANCH"
|
||||
echo " Pub/Sub: $PUBSUB_TOPIC / $PUBSUB_SUB"
|
||||
echo ""
|
||||
|
||||
# --- Enable required APIs ---
|
||||
|
||||
echo "--- Enabling APIs..."
|
||||
gcloud services enable \
|
||||
compute.googleapis.com \
|
||||
pubsub.googleapis.com \
|
||||
chat.googleapis.com \
|
||||
--project="$PROJECT" --quiet
|
||||
|
||||
# --- Create Pub/Sub topic + subscription ---
|
||||
|
||||
echo "--- Setting up Pub/Sub..."
|
||||
if ! gcloud pubsub topics describe "$PUBSUB_TOPIC" --project="$PROJECT" &>/dev/null; then
|
||||
gcloud pubsub topics create "$PUBSUB_TOPIC" --project="$PROJECT"
|
||||
echo " Created topic: $PUBSUB_TOPIC"
|
||||
else
|
||||
echo " Topic already exists: $PUBSUB_TOPIC"
|
||||
fi
|
||||
|
||||
if ! gcloud pubsub subscriptions describe "$PUBSUB_SUB" --project="$PROJECT" &>/dev/null; then
|
||||
gcloud pubsub subscriptions create "$PUBSUB_SUB" \
|
||||
--topic="$PUBSUB_TOPIC" \
|
||||
--project="$PROJECT" \
|
||||
--ack-deadline=60
|
||||
echo " Created subscription: $PUBSUB_SUB"
|
||||
else
|
||||
echo " Subscription already exists: $PUBSUB_SUB"
|
||||
fi
|
||||
|
||||
# Grant Google Chat SA permission to publish to the topic
|
||||
echo "--- Granting Chat API publish permission..."
|
||||
gcloud pubsub topics add-iam-policy-binding "$PUBSUB_TOPIC" \
|
||||
--member="serviceAccount:chat-api-push@system.gserviceaccount.com" \
|
||||
--role="roles/pubsub.publisher" \
|
||||
--project="$PROJECT" --quiet 2>/dev/null || true
|
||||
|
||||
# --- Reserve static IP ---
|
||||
|
||||
echo "--- Setting up static IP..."
|
||||
IP_NAME="${VM_NAME}-ip"
|
||||
if ! gcloud compute addresses describe "$IP_NAME" --region="$REGION" --project="$PROJECT" &>/dev/null; then
|
||||
gcloud compute addresses create "$IP_NAME" \
|
||||
--region="$REGION" \
|
||||
--project="$PROJECT"
|
||||
echo " Reserved static IP: $IP_NAME"
|
||||
else
|
||||
echo " Static IP already exists: $IP_NAME"
|
||||
fi
|
||||
STATIC_IP=$(gcloud compute addresses describe "$IP_NAME" --region="$REGION" --project="$PROJECT" --format="value(address)")
|
||||
echo " IP address: $STATIC_IP"
|
||||
|
||||
# --- Grant VM service account permissions ---
|
||||
|
||||
echo "--- Setting up IAM permissions..."
|
||||
# Get the default compute service account
|
||||
PROJECT_NUMBER=$(gcloud projects describe "$PROJECT" --format="value(projectNumber)")
|
||||
COMPUTE_SA="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"
|
||||
|
||||
# VM needs Pub/Sub subscriber (to pull messages)
|
||||
gcloud projects add-iam-policy-binding "$PROJECT" \
|
||||
--member="serviceAccount:${COMPUTE_SA}" \
|
||||
--role="roles/pubsub.subscriber" \
|
||||
--quiet 2>/dev/null || true
|
||||
|
||||
# VM needs Chat API access (to push responses back)
|
||||
# Note: chat.bot scope is requested at VM level; the SA also needs
|
||||
# roles/chat.app or the Chat API enabled. We enable the API above.
|
||||
|
||||
echo " Granted pubsub.subscriber to $COMPUTE_SA"
|
||||
|
||||
# --- Create the VM ---
|
||||
|
||||
echo "--- Creating VM..."
|
||||
if gcloud compute instances describe "$VM_NAME" --zone="$ZONE" --project="$PROJECT" &>/dev/null; then
|
||||
echo " VM already exists. Updating startup script and resetting..."
|
||||
gcloud compute instances add-metadata "$VM_NAME" \
|
||||
--zone="$ZONE" \
|
||||
--project="$PROJECT" \
|
||||
--metadata="gemini-api-key=${GEMINI_API_KEY},git-branch=${GIT_BRANCH},gcp-project=${PROJECT},pubsub-subscription=${PUBSUB_SUB}" \
|
||||
--metadata-from-file="startup-script=${STARTUP_SCRIPT}"
|
||||
gcloud compute instances reset "$VM_NAME" --zone="$ZONE" --project="$PROJECT"
|
||||
else
|
||||
gcloud compute instances create "$VM_NAME" \
|
||||
--zone="$ZONE" \
|
||||
--project="$PROJECT" \
|
||||
--machine-type="$MACHINE_TYPE" \
|
||||
--image-family=ubuntu-2404-lts-amd64 \
|
||||
--image-project=ubuntu-os-cloud \
|
||||
--boot-disk-size=20GB \
|
||||
--address="$IP_NAME" \
|
||||
--scopes=cloud-platform \
|
||||
--metadata="gemini-api-key=${GEMINI_API_KEY},git-branch=${GIT_BRANCH},gcp-project=${PROJECT},pubsub-subscription=${PUBSUB_SUB}" \
|
||||
--metadata-from-file="startup-script=${STARTUP_SCRIPT}"
|
||||
echo " Created VM: $VM_NAME"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Deployment complete! ==="
|
||||
echo ""
|
||||
echo "The VM is building now (~5 minutes for first boot, ~1 minute for subsequent boots)."
|
||||
echo ""
|
||||
echo "Monitor progress:"
|
||||
echo " gcloud compute instances get-serial-port-output $VM_NAME --zone=$ZONE --project=$PROJECT | tail -20"
|
||||
echo ""
|
||||
echo "=== Google Chat App Setup ==="
|
||||
echo ""
|
||||
echo "1. Go to: https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat"
|
||||
echo " (or search 'Google Chat API' in the GCP console)"
|
||||
echo ""
|
||||
echo "2. Click 'Configuration' tab and set:"
|
||||
echo " - App name: Forever Agent (or your preferred name)"
|
||||
echo " - Avatar URL: (optional)"
|
||||
echo " - Description: Gemini CLI forever agent"
|
||||
echo " - Functionality: check 'Spaces and group conversations' + 'Direct messages'"
|
||||
echo " - Connection settings: Cloud Pub/Sub"
|
||||
echo " - Topic name: projects/${PROJECT}/topics/${PUBSUB_TOPIC}"
|
||||
echo " - Visibility: your domain or specific users"
|
||||
echo ""
|
||||
echo "3. Save and add the bot to a Google Chat space or DM it directly."
|
||||
echo ""
|
||||
echo "Resources created:"
|
||||
echo " VM: $VM_NAME ($ZONE) — $STATIC_IP"
|
||||
echo " Static IP: $IP_NAME ($REGION)"
|
||||
echo " Pub/Sub: $PUBSUB_TOPIC / $PUBSUB_SUB"
|
||||
echo ""
|
||||
echo "To tear down: ./scripts/teardown-forever-agent.sh"
|
||||
+11
-7
@@ -9,10 +9,14 @@ exec > >(tee -a "$LOG") 2>&1
|
||||
echo "=== Forever Agent startup $(date) ==="
|
||||
|
||||
# Read API key from instance metadata
|
||||
GEMINI_API_KEY=$(curl -sf -H "Metadata-Flavor: Google" \
|
||||
"http://metadata.google.internal/computeMetadata/v1/instance/attributes/gemini-api-key" || echo "")
|
||||
CHAT_PROJECT_NUMBER=$(curl -sf -H "Metadata-Flavor: Google" \
|
||||
"http://metadata.google.internal/computeMetadata/v1/instance/attributes/chat-project-number" || echo "")
|
||||
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"
|
||||
@@ -30,7 +34,7 @@ echo "Node $(node --version)"
|
||||
# Clone/update repo
|
||||
REPO_DIR="/opt/forever-agent"
|
||||
if [ ! -d "$REPO_DIR" ]; then
|
||||
GIT_TERMINAL_PROMPT=0 git clone -b afw/forever-gchat \
|
||||
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
|
||||
@@ -102,8 +106,8 @@ After=network.target
|
||||
[Service]
|
||||
Type=simple
|
||||
Environment=A2A_URL=http://127.0.0.1:3100
|
||||
Environment=GOOGLE_CLOUD_PROJECT=adamfweidman-test
|
||||
Environment=PUBSUB_SUBSCRIPTION=forever-agent-chat-sub
|
||||
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
|
||||
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# teardown-forever-agent.sh — Remove all Forever Agent GCE resources.
|
||||
#
|
||||
# Usage: ./scripts/teardown-forever-agent.sh
|
||||
#
|
||||
# Optional env vars (defaults shown):
|
||||
# PROJECT - GCP project ID (defaults to gcloud config)
|
||||
# REGION - GCP region (default: us-central1)
|
||||
# ZONE - GCP zone (default: us-central1-a)
|
||||
# VM_NAME - VM name (default: forever-agent)
|
||||
# PUBSUB_TOPIC - Pub/Sub topic name (default: forever-agent-chat)
|
||||
# PUBSUB_SUB - Pub/Sub subscription name (default: forever-agent-chat-sub)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT="${PROJECT:-$(gcloud config get-value project 2>/dev/null)}"
|
||||
REGION="${REGION:-us-central1}"
|
||||
ZONE="${ZONE:-us-central1-a}"
|
||||
VM_NAME="${VM_NAME:-forever-agent}"
|
||||
PUBSUB_TOPIC="${PUBSUB_TOPIC:-forever-agent-chat}"
|
||||
PUBSUB_SUB="${PUBSUB_SUB:-forever-agent-chat-sub}"
|
||||
IP_NAME="${VM_NAME}-ip"
|
||||
|
||||
echo "=== Tearing down Forever Agent ==="
|
||||
echo " Project: $PROJECT"
|
||||
echo " VM: $VM_NAME ($ZONE)"
|
||||
echo ""
|
||||
|
||||
read -p "Are you sure? This will delete the VM, static IP, and Pub/Sub resources. [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Aborted."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "--- Deleting VM..."
|
||||
gcloud compute instances delete "$VM_NAME" --zone="$ZONE" --project="$PROJECT" --quiet 2>/dev/null || echo " VM not found"
|
||||
|
||||
echo "--- Releasing static IP..."
|
||||
gcloud compute addresses delete "$IP_NAME" --region="$REGION" --project="$PROJECT" --quiet 2>/dev/null || echo " Static IP not found"
|
||||
|
||||
echo "--- Deleting Pub/Sub subscription..."
|
||||
gcloud pubsub subscriptions delete "$PUBSUB_SUB" --project="$PROJECT" --quiet 2>/dev/null || echo " Subscription not found"
|
||||
|
||||
echo "--- Deleting Pub/Sub topic..."
|
||||
gcloud pubsub topics delete "$PUBSUB_TOPIC" --project="$PROJECT" --quiet 2>/dev/null || echo " Topic not found"
|
||||
|
||||
echo ""
|
||||
echo "=== Teardown complete ==="
|
||||
echo "Note: The Google Chat app configuration must be removed manually from the GCP console."
|
||||
Reference in New Issue
Block a user