This commit is contained in:
Velrino 2026-01-30 12:52:08 -03:00 committed by GitHub
commit 988c884072
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 1781 additions and 0 deletions

View File

@ -34,8 +34,56 @@ For the generic Docker flow, see [Docker](/install/docker).
--- ---
## Automated deployment (recommended for beginners)
For a fully automated deployment, use the deployment script.
It handles all the steps below automatically, including Docker setup, gateway configuration, and optional Tailscale HTTPS.
```bash
# Clone the repository first
git clone https://github.com/moltbot/moltbot.git
cd moltbot
# Basic deployment (SSH tunnel access)
./scripts/deploy/google/compute-engine/run.sh \
--project YOUR_PROJECT_ID \
--anthropic-key sk-ant-xxx
# With Tailscale for HTTPS access (no SSH tunnel needed)
./scripts/deploy/google/compute-engine/run.sh \
--project YOUR_PROJECT_ID \
--anthropic-key sk-ant-xxx \
--tailscale
# Full setup: HTTPS + Telegram auto-approval
./scripts/deploy/google/compute-engine/run.sh \
--project YOUR_PROJECT_ID \
--anthropic-key sk-ant-xxx \
--tailscale \
--telegram-user-id YOUR_TELEGRAM_ID
```
**Features:**
- Auto-generates secure tokens (gateway token, keyring password)
- Installs Docker and all dependencies
- Configures `gateway.mode=local` automatically
- Optional Tailscale Serve for HTTPS (avoids browser secure context issues)
- Optional Telegram allowlist (skips manual pairing)
- Container stability checks with auto-restart
**To uninstall:**
```bash
./scripts/deploy/google/compute-engine/uninstall.sh --project YOUR_PROJECT_ID
```
See [scripts/deploy/google/compute-engine/README.md](https://github.com/moltbot/moltbot/blob/main/scripts/deploy/google/compute-engine/README.md) for all options.
---
## Quick path (experienced operators) ## Quick path (experienced operators)
If you prefer manual setup or need custom configuration:
1) Create GCP project + enable Compute Engine API 1) Create GCP project + enable Compute Engine API
2) Create Compute Engine VM (e2-small, Debian 12, 20GB) 2) Create Compute Engine VM (e2-small, Debian 12, 20GB)
3) SSH into the VM 3) SSH into the VM

View File

@ -0,0 +1,576 @@
# Deploy Moltbot to Google Compute Engine
This script automates the deployment of Moltbot to Google Compute Engine, following the official documentation at [`docs/platforms/gcp.md`](../../../../docs/platforms/gcp.md).
---
## For Beginners: What is all this?
### Architecture Overview
```mermaid
graph TB
subgraph You["👤 YOU (user)"]
Laptop["💻 Laptop<br/>(browser)"]
Phone["📱 Phone<br/>(Telegram)"]
Tablet["📱 Tablet<br/>(browser)"]
end
subgraph GCP["☁️ GOOGLE CLOUD PLATFORM"]
subgraph VM["🖥️ VM (Compute Engine)<br/>Debian 12, 4GB RAM"]
subgraph Docker["🐳 DOCKER"]
Gateway["🤖 MOLTBOT GATEWAY<br/>(port 18789)<br/>─────────────<br/>• Receives Telegram messages<br/>• Processes with Claude AI<br/>• Responds automatically<br/>• Web UI for config"]
end
subgraph Volumes["📁 Mounted Volumes"]
Config["~/.clawdbot/<br/>(config, tokens)"]
Workspace["~/clawd/<br/>(agent workspace)"]
end
end
end
Laptop -->|"HTTPS<br/>(Tailscale)"| Gateway
Phone -->|"Telegram API"| Gateway
Tablet -->|"HTTPS<br/>(Tailscale)"| Gateway
Gateway --> Config
Gateway --> Workspace
```
### What does each technology do?
| Technology | What it is | Simple explanation |
|------------|-----------|---------------------|
| **Google Cloud Platform (GCP)** | Cloud computing | "Renting a computer in the cloud" - you pay ~$25/mo to have a server running 24/7 |
| **Compute Engine (VM)** | Virtual machine | A virtual computer inside GCP with Debian Linux, 4GB RAM, 20GB disk |
| **Docker** | Container runtime | "Isolated box for applications" - ensures Moltbot works the same everywhere |
| **Tailscale** | Personal VPN | Connects your devices securely, provides free HTTPS, only you can access |
| **Gateway Token** | Authentication | "Moltbot password" - even with the URL, you need the token to enter |
| **Anthropic API (Claude)** | AI service | "The bot's brain" - the AI that generates responses, pay per usage |
### Message flow in Telegram
```mermaid
sequenceDiagram
participant You as 👤 You
participant TG as 📱 Telegram
participant GW as 🤖 Moltbot Gateway
participant AI as 🧠 Anthropic API
You->>TG: 1. "Hello, Claude"
TG->>GW: 2. Forward message
GW->>AI: 3. Send to Claude
AI->>GW: 4. Generate response
GW->>TG: 5. Send reply
TG->>You: 6. "Hello! How can I help?"
```
### Why use Tailscale?
```mermaid
graph LR
subgraph Without["❌ WITHOUT TAILSCALE"]
B1["🌐 Browser"] -->|"HTTP"| IP1["http://35.192.x.x:18789"]
IP1 -->|"❌ BLOCKED"| Error["Insecure connection<br/>WebSocket requires HTTPS"]
end
```
```mermaid
graph LR
subgraph With["✅ WITH TAILSCALE"]
B2["🌐 Browser"] -->|"HTTPS"| TS["🔐 Tailscale<br/>Private VPN"]
TS -->|"Encrypted"| VM2["🖥️ VM on GCP"]
VM2 --> OK["✅ Works!<br/>Auto SSL + Private access"]
end
```
### Access Modes Compared
| Mode | Security | Convenience | How it works |
|------|----------|-------------|--------------|
| **SSH Tunnel** (default) | ⭐⭐⭐⭐⭐ | ⭐⭐ | Run SSH command every time, access via `localhost:18789` |
| **Tailscale** (recommended) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Install once, automatic HTTPS, only your devices can access |
| **Public IP** (not recommended) | ⭐⭐ | ⭐⭐⭐⭐ | Anyone can try to access, UI doesn't work (needs HTTPS) |
### What the script does automatically
```mermaid
flowchart TD
Start["🚀 run.sh --project X --tailscale --telegram-user-id Y"] --> Step1
subgraph Step1["Step 1: Verification"]
V1["✓ gcloud CLI installed?"]
V2["✓ Authenticated on GCP?"]
V3["✓ Project has billing?"]
end
Step1 --> Step2
subgraph Step2["Step 2: Create VM"]
VM1["Create e2-medium (4GB RAM)"]
VM2["Install Docker"]
VM3["Configure firewall"]
end
Step2 --> Step3
subgraph Step3["Step 3: Configure Moltbot"]
C1["Clone repository"]
C2["Create .env with keys"]
C3["Auto-generate tokens"]
end
Step3 --> Step4
subgraph Step4["Step 4: Build & Start"]
B1["docker compose build"]
B2["docker compose up -d"]
B3["Verify stability (3 checks)"]
end
Step4 --> Step5
subgraph Step5["Step 5: Tailscale (if --tailscale)"]
T1["Install Tailscale on VM host"]
T2["Authenticate (link in terminal)"]
T3["Run 'tailscale serve' on host"]
T4["Automatic HTTPS → localhost:18789"]
end
Step5 --> Step6
subgraph Step6["Step 6: Telegram (if --telegram-user-id)"]
TG1["Set dmPolicy = allowlist"]
TG2["Add your user ID to allowlist"]
end
Step6 --> Result["✅ DONE!<br/>URL: https://moltbot-gateway.tailnet-xxx.ts.net<br/>Telegram: Message @YourBot!"]
```
### Estimated Costs
| Service | Cost | Notes |
|---------|------|-------|
| **GCP VM** (e2-medium) | ~$25/mo | 4GB RAM, runs 24/7 |
| **GCP Disk** (20GB) | ~$2/mo | Persistent storage |
| **GCP Network** | ~$1-5/mo | Depends on usage |
| **Anthropic API** | ~$5-20/mo | Claude 3.5 Sonnet: $3/1M input, $15/1M output |
| **Tailscale** | FREE | Up to 100 devices |
| **Telegram** | FREE | |
| **TOTAL** | **~$33-52/mo** | |
---
## What the Script Does (follows docs/platforms/gcp.md)
The script automates the deployment steps from the official documentation:
1. **Checks prerequisites** - gcloud CLI, authentication, billing
2. **Enables APIs** - Compute Engine API
3. **Creates VM** - e2-medium, Debian 12, 20GB (configurable)
4. **Installs Docker** - Via startup script
5. **Clones repository** - `git clone` on the VM
6. **Creates persistent directories** - `~/.clawdbot` and `~/clawd`
7. **Configures .env** - All required variables including `GOG_KEYRING_PASSWORD`
8. **Creates docker-compose.yml** - With volume mounts for persistence
9. **Creates Dockerfile.gcp** - Optimized for GCP deployment
10. **Builds and launches** - `docker compose build && docker compose up -d`
11. **Verifies gateway** - Shows startup logs
## Prerequisites
- [Google Cloud CLI](https://cloud.google.com/sdk/docs/install) installed and authenticated
- A Google Cloud project with billing enabled
- An Anthropic API key ([get one here](https://console.anthropic.com/))
## Quick Start
```bash
# Recommended: Full setup with Tailscale HTTPS + Telegram auto-approval
./scripts/deploy/google/compute-engine/run.sh \
--project YOUR_PROJECT_ID \
--anthropic-key sk-ant-xxx \
--tailscale \
--telegram-user-id YOUR_TELEGRAM_USER_ID
# Production deployment (e2-medium, SSH tunnel access - most secure)
./scripts/deploy/google/compute-engine/run.sh \
--project YOUR_PROJECT_ID \
--anthropic-key sk-ant-xxx
# Restrict access to your IP only (recommended - auto-detects and confirms)
./scripts/deploy/google/compute-engine/run.sh \
--project YOUR_PROJECT_ID \
--anthropic-key sk-ant-xxx \
--my-ip
# Free tier deployment (e2-micro, may OOM under load)
./scripts/deploy/google/compute-engine/run.sh \
--project YOUR_PROJECT_ID \
--machine-type e2-micro \
--anthropic-key sk-ant-xxx
# With .env file
./scripts/deploy/google/compute-engine/run.sh \
--project YOUR_PROJECT_ID \
--env-file .env
# With HTTPS via Tailscale (recommended for direct browser access)
./scripts/deploy/google/compute-engine/run.sh \
--project YOUR_PROJECT_ID \
--anthropic-key sk-ant-xxx \
--tailscale
```
To find your Telegram user ID, message [@userinfobot](https://t.me/userinfobot) on Telegram.
To find your IP address:
```bash
curl -4 ifconfig.me
```
## Options
| Option | Default | Description |
|--------|---------|-------------|
| `--project PROJECT_ID` | (required) | Google Cloud project ID |
| `--zone ZONE` | `us-central1-a` | Compute Engine zone |
| `--instance NAME` | `moltbot-gateway` | Instance name |
| `--machine-type TYPE` | `e2-medium` | Machine type (4GB RAM, needed for builds) |
| `--disk-size SIZE` | `20GB` | Boot disk size |
| `--env-file FILE` | - | Load environment variables from file |
| `--anthropic-key KEY` | - | Anthropic API key |
| `--gateway-token TOKEN` | auto-generated | Gateway authentication token |
| `--my-ip` | - | Auto-detect your IP and restrict firewall (API only, UI needs HTTPS) |
| `--allowed-ip IP` | - | Restrict firewall to specific IP (API only, UI needs HTTPS) |
| `--tailscale` | - | Install Tailscale for HTTPS access (recommended for UI) |
| `--telegram-user-id ID` | - | Telegram user ID to auto-approve (no pairing needed) |
## Access Modes
### SSH Tunnel (default, most secure)
By default, the gateway binds to loopback only (`127.0.0.1:18789`). Access via SSH tunnel:
```bash
# Create tunnel from your laptop
gcloud compute ssh moltbot-gateway --zone=us-central1-a --project=YOUR_PROJECT -- -L 18789:127.0.0.1:18789
# Open in browser
http://127.0.0.1:18789/
```
**Pros:** Most secure, no ports exposed to internet
**Cons:** Requires SSH tunnel every time you access the UI
### Tailscale (recommended for direct browser access)
With `--tailscale` flag, the script:
1. Installs Tailscale on the VM host (before Docker build)
2. Authenticates Tailscale (you click the link in terminal)
3. Runs `sudo tailscale serve --bg --yes 18789` on the host
4. Docker container binds to `0.0.0.0:18789` for Tailscale to proxy
```bash
./scripts/deploy/google/compute-engine/run.sh \
--project YOUR_PROJECT \
--anthropic-key sk-ant-xxx \
--tailscale
# Follow the authentication link that appears in the terminal
# Then access via: https://moltbot-gateway.your-tailnet.ts.net/
```
After deployment, you can access the Gateway UI directly via the Tailscale URL - no port number needed (Tailscale Serve handles HTTPS on port 443).
**Pros:** HTTPS works, direct browser access, secure, no SSH tunnel needed
**Cons:** Requires Tailscale account (free)
### IP Restricted Access (API only - UI requires HTTPS)
With `--my-ip` flag, the script auto-detects your IP and asks for confirmation.
**Note:** Due to browser security (secure context), the UI only works via localhost or HTTPS. Use SSH tunnel or Tailscale for UI access.
```bash
# Deploy with auto-detected IP restriction (recommended)
./scripts/deploy/google/compute-engine/run.sh \
--project YOUR_PROJECT \
--anthropic-key sk-ant-xxx \
--my-ip
# The script will:
# 1. Detect your IP (e.g., 186.220.38.207)
# 2. Ask: "Restrict access to IP 186.220.38.207? [Y/n]"
# 3. If yes, proceed with deployment restricted to your IP
# Or specify IP manually:
./scripts/deploy/google/compute-engine/run.sh \
--project YOUR_PROJECT \
--anthropic-key sk-ant-xxx \
--allowed-ip 186.220.38.207
# Access directly (no tunnel needed)
http://INSTANCE_IP:18789/?token=YOUR_GATEWAY_TOKEN
```
**Pros:** Direct access to UI without SSH tunnel, secure (only your IP can connect)
**Cons:** If your IP changes, you need to update the firewall rule:
```bash
# Update allowed IP
gcloud compute firewall-rules update moltbot-gateway \
--project=YOUR_PROJECT \
--source-ranges="NEW_IP/32"
```
### Public Access (not recommended)
With `--public` flag, the gateway is exposed to all IPs:
```bash
# Deploy with public access (open to everyone)
./scripts/deploy/google/compute-engine/run.sh --project YOUR_PROJECT --anthropic-key sk-ant-xxx --public
# Access directly
http://INSTANCE_IP:18789/?token=YOUR_GATEWAY_TOKEN
```
**Warning:** Anyone with the token can access your gateway. Only use this if you understand the security implications.
### How to Find Your IP Address
```bash
# Use curl
curl -4 ifconfig.me
```
This will return something like `186.220.38.207`.
**Tip:** Use `--my-ip` flag during deployment to auto-detect and confirm your IP automatically.
### Managing Allowed IPs
#### Add or change your IP
If your IP changes, update the firewall rule:
```bash
# Update to new single IP
gcloud compute firewall-rules update moltbot-gateway \
--project=YOUR_PROJECT \
--source-ranges="NEW_IP/32"
```
#### Allow multiple IPs
To allow multiple IPs (e.g., home and office):
```bash
# Allow multiple IPs (comma-separated)
gcloud compute firewall-rules update moltbot-gateway \
--project=YOUR_PROJECT \
--source-ranges="186.220.38.207/32,200.100.50.25/32"
```
#### Allow an IP range
To allow a range of IPs:
```bash
# Allow a /24 subnet (e.g., 186.220.38.0 - 186.220.38.255)
gcloud compute firewall-rules update moltbot-gateway \
--project=YOUR_PROJECT \
--source-ranges="186.220.38.0/24"
```
#### Check current allowed IPs
```bash
gcloud compute firewall-rules describe moltbot-gateway \
--project=YOUR_PROJECT \
--format="value(sourceRanges)"
```
## What Persists Where
| Component | Location | Persistence | Notes |
|-----------|----------|-------------|-------|
| Gateway config | `~/.clawdbot/` | Host volume | `moltbot.json`, tokens |
| OAuth tokens | `~/.clawdbot/` | Host volume | Model auth profiles |
| WhatsApp session | `~/.clawdbot/` | Host volume | Preserves QR login |
| Gmail keyring | `~/.clawdbot/` | Host volume | Requires `GOG_KEYRING_PASSWORD` |
| Agent workspace | `~/clawd/` | Host volume | Code and artifacts |
| Application | Docker image | Rebuilt on update | Node.js app and dependencies |
## Machine Types
| Type | vCPU | Memory | Monthly Cost | Notes |
|------|------|--------|--------------|-------|
| e2-micro | 0.25 | 1 GB | Free tier | ❌ OOM during build and runtime |
| e2-small | 0.5 | 2 GB | ~$12/mo | ❌ OOM during Docker build |
| e2-medium | 1 | 4 GB | ~$25/mo | ✅ **Default** - works for build and runtime |
### Why e2-medium?
The Docker build process (`pnpm install`) requires ~3GB of RAM. Smaller machines will be killed by the OS (OOM - Out of Memory) during the build phase.
- **e2-micro (1GB)**: Will OOM during build and often during runtime
- **e2-small (2GB)**: Will OOM during Docker build (`pnpm install`)
- **e2-medium (4GB)**: Works for build and runtime ✅
After the initial build, runtime memory usage is lower (~1-2GB). If cost is a concern, you can:
1. Build with `e2-medium`
2. After successful deployment, downgrade to `e2-small` for runtime
## Post-Deployment Commands
### SSH to instance
```bash
gcloud compute ssh moltbot-gateway --zone=us-central1-a --project=YOUR_PROJECT
```
### View logs
```bash
gcloud compute ssh moltbot-gateway --zone=us-central1-a --project=YOUR_PROJECT \
--command='cd ~/moltbot && docker compose logs -f'
```
### Restart gateway
```bash
gcloud compute ssh moltbot-gateway --zone=us-central1-a --project=YOUR_PROJECT \
--command='cd ~/moltbot && docker compose restart'
```
### Update Moltbot
```bash
gcloud compute ssh moltbot-gateway --zone=us-central1-a --project=YOUR_PROJECT \
--command='cd ~/moltbot && git pull && docker compose build && docker compose up -d'
```
## Telegram Auto-Approval
By default, Moltbot requires users to "pair" before using the bot via Telegram. With `--telegram-user-id`, you can pre-approve your Telegram account:
```bash
./scripts/deploy/google/compute-engine/run.sh \
--project YOUR_PROJECT \
--anthropic-key sk-ant-xxx \
--telegram-user-id 123456789
```
This sets:
- `channels.telegram.dmPolicy: "allowlist"` - Only allowlisted users can message the bot
- `channels.telegram.allowFrom: ["123456789"]` - Your user ID is pre-approved
**How to find your Telegram user ID:**
1. Message [@userinfobot](https://t.me/userinfobot) on Telegram
2. It will reply with your user ID (a number like `123456789`)
## Uninstall
```bash
# Delete instance and firewall rule
./scripts/deploy/google/compute-engine/uninstall.sh --project YOUR_PROJECT
# Keep firewall for future deployments
./scripts/deploy/google/compute-engine/uninstall.sh --project YOUR_PROJECT --keep-firewall
```
## Troubleshooting
### SSH connection refused
SSH key propagation can take 1-2 minutes after VM creation. Wait and retry.
### Instance not starting
Check the serial console output:
```bash
gcloud compute instances get-serial-port-output moltbot-gateway \
--zone=us-central1-a --project=YOUR_PROJECT
```
### Out of memory (OOM)
If you see `exit code: 137` or `Killed` during Docker build, the VM ran out of memory.
**Solution:** Upgrade to `e2-medium` (4GB RAM):
```bash
# Stop the VM
gcloud compute instances stop moltbot-gateway --zone=us-central1-a --project=YOUR_PROJECT
# Change machine type to e2-medium (4GB RAM)
gcloud compute instances set-machine-type moltbot-gateway \
--zone=us-central1-a --project=YOUR_PROJECT \
--machine-type=e2-medium
# Start the VM
gcloud compute instances start moltbot-gateway --zone=us-central1-a --project=YOUR_PROJECT
# Re-run the build
gcloud compute ssh moltbot-gateway --zone=us-central1-a --project=YOUR_PROJECT \
--command='cd ~/moltbot && docker compose build && docker compose up -d'
```
**Note:** The default is now `e2-medium` to avoid this issue. See [Machine Types](#machine-types) for details.
### Docker build fails
SSH into the instance and check disk space:
```bash
gcloud compute ssh moltbot-gateway --zone=us-central1-a --project=YOUR_PROJECT
df -h
docker system prune -a # Clean up old images
```
## Security Recommendations
1. **Use `--tailscale`** (recommended) - Automatic HTTPS, private access via your tailnet
2. **Use SSH tunnel** - Most secure, but requires tunnel for each access
3. **Use `--allowed-ip`** - Restricts to your IP only (API only, UI needs HTTPS)
4. **Avoid `--public`** - Open to all IPs, only use if you understand the risks
5. **Rotate tokens** - Change `CLAWDBOT_GATEWAY_TOKEN` periodically
6. **Use `--telegram-user-id`** - Pre-approve only your Telegram account (no open pairing)
## Tailscale HTTPS Details
When using `--tailscale`, the script sets up automatic HTTPS via Tailscale Serve:
1. **VM Host**: Tailscale daemon runs on the VM, authenticated to your tailnet
2. **Tailscale Serve**: The script runs `sudo tailscale serve --bg --yes 18789` on the HOST
3. **Docker Container**: Binds to `0.0.0.0:18789` so Tailscale can proxy to it
The architecture is:
```
Internet → Tailscale (HTTPS:443) → localhost:18789 → Docker (gateway)
```
Tailscale Serve runs on the HOST (not in the container) because it requires root permissions.
### Manual Tailscale Setup (if not using `--tailscale`)
If you deployed without `--tailscale` and want to add it later:
```bash
# SSH to instance
gcloud compute ssh moltbot-gateway --zone=us-central1-a --project=YOUR_PROJECT
# Install Tailscale on the VM host
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --hostname=moltbot-gateway
# Configure Tailscale Serve to proxy HTTPS to the gateway port
sudo tailscale serve --bg --yes 18789
# Verify it's working
tailscale serve status
```
This gives you a secure URL like `https://moltbot-gateway.your-tailnet.ts.net`.

View File

@ -0,0 +1,979 @@
#!/usr/bin/env bash
#
# Deploy Moltbot to Google Compute Engine (follows docs/platforms/gcp.md)
#
# Usage:
# ./scripts/deploy/google/compute-engine/run.sh [OPTIONS]
#
# Options:
# --project PROJECT_ID Google Cloud project ID (required)
# --zone ZONE Compute Engine zone (default: us-central1-a)
# --instance NAME Instance name (default: moltbot-gateway)
# --machine-type TYPE Machine type (default: e2-medium, 4GB RAM for builds)
# --disk-size SIZE Boot disk size (default: 20GB)
# --env-file FILE Upload .env file to the instance
# --anthropic-key KEY Anthropic API key
# --gateway-token TOKEN Gateway token (auto-generated if not provided)
# --public Expose gateway publicly to all IPs (not recommended)
# --allowed-ip IP Expose gateway but restrict to specific IP
# --my-ip Auto-detect your IP, confirm, and restrict access to it (recommended)
# --tailscale Install Tailscale for HTTPS access (recommended)
# --telegram-token TOKEN Telegram bot token (from @BotFather)
# --telegram-user-id ID Your Telegram user ID (auto-approves you, skips pairing)
# --help Show this help message
#
# Examples:
# # Production deployment (e2-small, SSH tunnel access - most secure)
# ./scripts/deploy/google/compute-engine/run.sh --project my-project --anthropic-key sk-ant-xxx
#
# # Restrict access to your IP only (recommended - auto-detects and confirms)
# ./scripts/deploy/google/compute-engine/run.sh --project my-project --anthropic-key sk-ant-xxx --my-ip
#
# # Free tier deployment (e2-micro, may OOM under load)
# ./scripts/deploy/google/compute-engine/run.sh --project my-project --machine-type e2-micro --anthropic-key sk-ant-xxx
#
# # With .env file
# ./scripts/deploy/google/compute-engine/run.sh --project my-project --env-file .env
#
# Prerequisites:
# - gcloud CLI installed and authenticated
# - Google Cloud project with billing enabled
#
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Default values (following docs/platforms/gcp.md)
ZONE="us-central1-a"
INSTANCE_NAME="moltbot-gateway"
MACHINE_TYPE="e2-medium"
DISK_SIZE="20GB"
PROJECT_ID=""
ENV_FILE=""
ARG_ANTHROPIC_KEY=""
ARG_GATEWAY_TOKEN=""
GENERATED_GATEWAY_TOKEN=""
GENERATED_GOG_PASSWORD=""
PUBLIC_ACCESS=false
ALLOWED_IP=""
AUTO_DETECT_IP=false
INSTALL_TAILSCALE=false
TELEGRAM_BOT_TOKEN=""
TELEGRAM_USER_ID=""
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Logging functions
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
log_step() { echo -e "\n${CYAN}━━━ $1 ━━━${NC}"; }
log_detail() { echo -e " ${NC}$1"; }
show_help() {
head -38 "$0" | tail -36 | sed 's/^#//' | sed 's/^ //'
exit 0
}
detect_my_ip() {
echo ""
log_info "Detecting your public IP address..."
local my_ip
my_ip=$(curl -4 -s ifconfig.me 2>/dev/null || curl -4 -s ipv4.icanhazip.com 2>/dev/null || echo "")
if [[ -n "$my_ip" ]]; then
echo ""
echo " Your IPv4 address: $my_ip"
echo ""
ALLOWED_IP="$my_ip"
PUBLIC_ACCESS=true
else
log_error "Could not determine your IP address."
log_detail "Try manually with: --allowed-ip YOUR_IP"
exit 1
fi
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--project) PROJECT_ID="$2"; shift 2 ;;
--zone) ZONE="$2"; shift 2 ;;
--instance) INSTANCE_NAME="$2"; shift 2 ;;
--machine-type) MACHINE_TYPE="$2"; shift 2 ;;
--disk-size) DISK_SIZE="$2"; shift 2 ;;
--env-file) ENV_FILE="$2"; shift 2 ;;
--anthropic-key) ARG_ANTHROPIC_KEY="$2"; shift 2 ;;
--gateway-token) ARG_GATEWAY_TOKEN="$2"; shift 2 ;;
--public) PUBLIC_ACCESS=true; shift ;;
--allowed-ip) ALLOWED_IP="$2"; PUBLIC_ACCESS=true; shift 2 ;;
--my-ip) AUTO_DETECT_IP=true; shift ;;
--tailscale) INSTALL_TAILSCALE=true; shift ;;
--telegram-token) TELEGRAM_BOT_TOKEN="$2"; shift 2 ;;
--telegram-user-id) TELEGRAM_USER_ID="$2"; shift 2 ;;
--help|-h) show_help ;;
*) log_error "Unknown option: $1"; exit 1 ;;
esac
done
# Validate required arguments
if [[ -z "$PROJECT_ID" ]]; then
log_error "Project ID is required. Use --project PROJECT_ID"
echo ""
echo "Usage: $0 --project YOUR_PROJECT_ID [OPTIONS]"
exit 1
fi
# Auto-detect IP if --my-ip is set
if [[ "$AUTO_DETECT_IP" == "true" ]]; then
detect_my_ip
echo ""
read -p "Restrict access to IP $ALLOWED_IP? [Y/n] " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Nn]$ ]]; then
log_error "Aborted. Use --allowed-ip IP to specify a different IP."
exit 1
fi
log_success "Will restrict access to: $ALLOWED_IP"
fi
# Check prerequisites
check_prerequisites() {
log_step "Checking prerequisites"
log_info "Checking if gcloud CLI is installed..."
if ! command -v gcloud &> /dev/null; then
log_error "gcloud CLI is not installed!"
log_detail "Install from: https://cloud.google.com/sdk/docs/install"
exit 1
fi
log_detail "gcloud CLI found: $(which gcloud)"
log_info "Checking gcloud authentication..."
if ! gcloud auth print-access-token &> /dev/null; then
log_error "Not authenticated with gcloud!"
log_detail "Run: gcloud auth login"
exit 1
fi
log_detail "Authenticated as: $(gcloud config get-value account 2>/dev/null)"
log_info "Checking billing status..."
local billing_enabled
billing_enabled=$(gcloud billing projects describe "$PROJECT_ID" --format="value(billingEnabled)" 2>/dev/null || echo "false")
if [[ "$billing_enabled" != "True" ]]; then
log_error "Billing is not enabled for project: $PROJECT_ID"
log_detail "Enable billing at: https://console.cloud.google.com/billing/linkedaccount?project=$PROJECT_ID"
exit 1
fi
log_detail "Billing is enabled"
log_success "Prerequisites OK"
}
# Enable required APIs
enable_apis() {
log_step "Enabling required APIs"
log_info "Enabling Compute Engine API..."
gcloud services enable compute.googleapis.com --project="$PROJECT_ID" 2>&1 || true
log_success "Compute Engine API enabled"
}
# Create or get instance
create_instance() {
log_step "Setting up Compute Engine instance"
# Check if instance already exists
log_info "Checking if instance exists..."
if gcloud compute instances describe "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" &>/dev/null; then
log_warn "Instance already exists: $INSTANCE_NAME"
# Check current machine type and upgrade if needed
local current_type
current_type=$(gcloud compute instances describe "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" --format='value(machineType)' | sed 's|.*/||')
log_detail "Current machine type: $current_type"
# Upgrade if machine type is too small (e2-micro or e2-small cause OOM)
if [[ "$current_type" == "e2-micro" || "$current_type" == "e2-small" ]]; then
log_warn "Machine type $current_type has insufficient memory for builds (OOM risk)"
log_info "Upgrading to $MACHINE_TYPE (4GB RAM)..."
# Stop the instance
log_detail "Stopping instance..."
if ! gcloud compute instances stop "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" --quiet; then
log_error "Failed to stop instance for upgrade"
exit 1
fi
# Change machine type
log_detail "Changing machine type to $MACHINE_TYPE..."
if ! gcloud compute instances set-machine-type "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" --machine-type="$MACHINE_TYPE"; then
log_error "Failed to change machine type"
exit 1
fi
# Start the instance
log_detail "Starting instance..."
if ! gcloud compute instances start "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE"; then
log_error "Failed to start instance"
exit 1
fi
log_success "Upgraded to $MACHINE_TYPE"
# Wait for instance to be ready
log_info "Waiting for instance to be ready..."
sleep 30
else
log_detail "Machine type OK: $current_type"
fi
return 0
fi
log_info "Creating new instance: $INSTANCE_NAME"
log_detail "Zone: $ZONE"
log_detail "Machine type: $MACHINE_TYPE"
log_detail "Disk size: $DISK_SIZE"
log_detail "Image: Debian 12"
if gcloud compute instances create "$INSTANCE_NAME" \
--project="$PROJECT_ID" \
--zone="$ZONE" \
--machine-type="$MACHINE_TYPE" \
--image-family=debian-12 \
--image-project=debian-cloud \
--boot-disk-size="$DISK_SIZE" \
--boot-disk-type=pd-standard \
--tags=moltbot-server \
--metadata=startup-script='#!/bin/bash
# Install Docker (following docs/platforms/gcp.md step 5)
apt-get update
apt-get install -y git curl ca-certificates
curl -fsSL https://get.docker.com | sh
usermod -aG docker $(logname 2>/dev/null || echo "")
echo "Docker installation complete"
'; then
log_success "Instance created: $INSTANCE_NAME"
else
log_error "Failed to create instance"
exit 1
fi
# Wait for instance to be ready
log_info "Waiting for instance to be ready..."
sleep 30
# Wait for Docker to be installed
log_info "Waiting for Docker installation..."
local max_attempts=30
local attempt=0
while [[ $attempt -lt $max_attempts ]]; do
if gcloud compute ssh "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" --command="which docker" &>/dev/null; then
log_success "Docker installed on instance"
break
fi
attempt=$((attempt + 1))
log_detail "Waiting... ($attempt/$max_attempts)"
sleep 10
done
if [[ $attempt -ge $max_attempts ]]; then
log_warn "Startup script may still be running. Will continue anyway."
fi
}
# Create firewall rule (only if --public flag is used)
setup_firewall() {
if [[ "$PUBLIC_ACCESS" != "true" ]]; then
log_step "Firewall setup (loopback mode)"
log_info "Gateway will be bound to loopback only"
log_detail "Access via SSH tunnel: gcloud compute ssh $INSTANCE_NAME --zone=$ZONE --project=$PROJECT_ID -- -L 18789:127.0.0.1:18789"
# Delete existing firewall rule if it exists (security: don't leave port open)
if gcloud compute firewall-rules describe moltbot-gateway --project="$PROJECT_ID" &>/dev/null; then
log_info "Removing existing firewall rule (not needed in loopback mode)..."
gcloud compute firewall-rules delete moltbot-gateway --project="$PROJECT_ID" --quiet 2>/dev/null || true
log_detail "Firewall rule removed"
fi
return
fi
# Determine source ranges
local source_ranges="0.0.0.0/0"
local access_mode="public access mode - open to all IPs"
if [[ -n "$ALLOWED_IP" ]]; then
source_ranges="${ALLOWED_IP}/32"
access_mode="restricted access mode - only $ALLOWED_IP"
fi
log_step "Setting up firewall ($access_mode)"
log_info "Checking firewall rule..."
if gcloud compute firewall-rules describe moltbot-gateway --project="$PROJECT_ID" &>/dev/null; then
log_detail "Firewall rule already exists, updating source ranges..."
if gcloud compute firewall-rules update moltbot-gateway \
--project="$PROJECT_ID" \
--source-ranges="$source_ranges"; then
log_success "Firewall rule updated with source: $source_ranges"
else
log_error "Failed to update firewall rule"
exit 1
fi
# Verify the update worked
local current_ranges
current_ranges=$(gcloud compute firewall-rules describe moltbot-gateway --project="$PROJECT_ID" --format="value(sourceRanges)" 2>/dev/null || echo "")
if [[ "$current_ranges" != *"$source_ranges"* ]] && [[ "$source_ranges" != "0.0.0.0/0" ]]; then
log_warn "Firewall may not have updated correctly. Current: $current_ranges, Expected: $source_ranges"
fi
else
log_info "Creating firewall rule for port 18789..."
if gcloud compute firewall-rules create moltbot-gateway \
--project="$PROJECT_ID" \
--allow=tcp:18789 \
--source-ranges="$source_ranges" \
--target-tags=moltbot-server \
--description="Allow Moltbot gateway traffic"; then
log_success "Firewall rule created with source: $source_ranges"
else
log_error "Failed to create firewall rule"
exit 1
fi
fi
}
# Configure Moltbot on the instance (following docs/platforms/gcp.md)
configure_moltbot() {
log_step "Configuring Moltbot (following docs/platforms/gcp.md)"
# Resolve tokens
local anthropic_key=""
local gateway_token=""
local gog_keyring_password=""
# Get Anthropic key
if [[ -n "$ARG_ANTHROPIC_KEY" ]]; then
anthropic_key="$ARG_ANTHROPIC_KEY"
elif [[ -n "${ANTHROPIC_API_KEY:-}" ]]; then
anthropic_key="$ANTHROPIC_API_KEY"
elif [[ -n "$ENV_FILE" ]] && [[ -f "$ENV_FILE" ]]; then
anthropic_key=$(grep "^ANTHROPIC_API_KEY=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2- || true)
fi
if [[ -z "$anthropic_key" ]]; then
log_error "Anthropic API key is required!"
log_detail "Provide via --anthropic-key, ANTHROPIC_API_KEY env var, or in .env file"
exit 1
fi
log_detail "Anthropic API key: ${anthropic_key:0:10}..."
# Get or generate gateway token
if [[ -n "$ARG_GATEWAY_TOKEN" ]]; then
gateway_token="$ARG_GATEWAY_TOKEN"
elif [[ -n "${CLAWDBOT_GATEWAY_TOKEN:-}" ]]; then
gateway_token="$CLAWDBOT_GATEWAY_TOKEN"
elif [[ -n "$ENV_FILE" ]] && [[ -f "$ENV_FILE" ]]; then
gateway_token=$(grep "^CLAWDBOT_GATEWAY_TOKEN=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2- || true)
fi
if [[ -z "$gateway_token" ]]; then
log_info "Generating gateway token..."
gateway_token=$(openssl rand -hex 32)
GENERATED_GATEWAY_TOKEN="$gateway_token"
log_detail "Token generated: ${gateway_token:0:16}..."
else
log_detail "Gateway token: ${gateway_token:0:16}..."
fi
# Get or generate GOG keyring password
if [[ -n "$ENV_FILE" ]] && [[ -f "$ENV_FILE" ]]; then
gog_keyring_password=$(grep "^GOG_KEYRING_PASSWORD=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2- || true)
fi
if [[ -z "$gog_keyring_password" ]]; then
log_info "Generating GOG keyring password..."
gog_keyring_password=$(openssl rand -hex 32)
GENERATED_GOG_PASSWORD="$gog_keyring_password"
log_detail "GOG password generated"
fi
# Determine bind mode
local bind_mode="lan"
local port_binding="127.0.0.1:18789:18789"
if [[ "$PUBLIC_ACCESS" == "true" ]] || [[ "$INSTALL_TAILSCALE" == "true" ]]; then
# Tailscale needs access to the gateway, so bind to all interfaces
bind_mode="lan"
port_binding="18789:18789"
fi
# Get remote username
local remote_user
remote_user=$(gcloud compute ssh "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" --command="whoami" 2>/dev/null || echo "")
if [[ -z "$remote_user" ]]; then
log_error "Could not determine remote username"
exit 1
fi
log_detail "Remote user: $remote_user"
# Build extra env vars from file (two formats: .env and docker-compose)
local extra_env_vars=""
local extra_env_file=""
if [[ -n "$ENV_FILE" ]] && [[ -f "$ENV_FILE" ]]; then
log_info "Reading additional variables from $ENV_FILE..."
while IFS='=' read -r key value || [[ -n "$key" ]]; do
# Skip comments and empty lines
[[ -z "$key" || "$key" =~ ^# ]] && continue
# Skip already handled keys
[[ "$key" == "ANTHROPIC_API_KEY" || "$key" == "CLAWDBOT_GATEWAY_TOKEN" || "$key" == "GOG_KEYRING_PASSWORD" ]] && continue
# Add to docker-compose format
extra_env_vars="${extra_env_vars} - ${key}=${value}"$'\n'
# Add to .env file format
extra_env_file="${extra_env_file}${key}=${value}"$'\n'
log_detail "Added: $key"
done < "$ENV_FILE"
fi
# Add Telegram bot token if provided via command line
if [[ -n "$TELEGRAM_BOT_TOKEN" ]]; then
extra_env_vars="${extra_env_vars} - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}"$'\n'
extra_env_file="${extra_env_file}TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}"$'\n'
log_detail "Added: TELEGRAM_BOT_TOKEN"
fi
# Build Tailscale volume mount if enabled
local tailscale_volume=""
if [[ "$INSTALL_TAILSCALE" == "true" ]]; then
tailscale_volume=" - /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock"$'\n'
fi
# Create remote setup script (following docs/platforms/gcp.md steps 6-11)
log_info "Setting up Moltbot on instance..."
local setup_script='#!/bin/bash
set -e
REMOTE_USER="'"$remote_user"'"
HOME_DIR="/home/$REMOTE_USER"
INSTALL_TAILSCALE="'"$INSTALL_TAILSCALE"'"
echo "=== Step 6: Create persistent host directories ==="
mkdir -p "$HOME_DIR/.clawdbot"
mkdir -p "$HOME_DIR/clawd"
chown -R "$REMOTE_USER:$REMOTE_USER" "$HOME_DIR/.clawdbot"
chown -R "$REMOTE_USER:$REMOTE_USER" "$HOME_DIR/clawd"
echo "Created ~/.clawdbot and ~/clawd"
echo "=== Step 7: Clone Moltbot repository ==="
cd "$HOME_DIR"
if [[ -d "moltbot" ]]; then
echo "Repository already exists, pulling latest..."
cd moltbot
git pull || true
else
git clone https://github.com/moltbot/moltbot.git
cd moltbot
fi
chown -R "$REMOTE_USER:$REMOTE_USER" "$HOME_DIR/moltbot"
echo "=== Step 8: Configure environment variables ==="
cat > "$HOME_DIR/moltbot/.env" << EOF
# Moltbot GCP Deployment Configuration
# Generated by deploy script (following docs/platforms/gcp.md)
CLAWDBOT_IMAGE=moltbot:latest
CLAWDBOT_GATEWAY_TOKEN='"$gateway_token"'
CLAWDBOT_GATEWAY_BIND='"$bind_mode"'
CLAWDBOT_GATEWAY_PORT=18789
CLAWDBOT_CONFIG_DIR=$HOME_DIR/.clawdbot
CLAWDBOT_WORKSPACE_DIR=$HOME_DIR/clawd
GOG_KEYRING_PASSWORD='"$gog_keyring_password"'
XDG_CONFIG_HOME=/home/node/.clawdbot
ANTHROPIC_API_KEY='"$anthropic_key"'
NODE_ENV=production
# Extra variables from --env-file or --telegram-token
'"$extra_env_file"'
EOF
chown "$REMOTE_USER:$REMOTE_USER" "$HOME_DIR/moltbot/.env"
chmod 600 "$HOME_DIR/moltbot/.env"
echo "Created .env file"
echo "=== Step 9: Create docker-compose.yml ==="
cat > "$HOME_DIR/moltbot/docker-compose.yml" << EOF
services:
moltbot-gateway:
image: \${CLAWDBOT_IMAGE}
build:
context: .
dockerfile: Dockerfile.gcp
restart: unless-stopped
env_file:
- .env
environment:
- HOME=/home/node
- NODE_ENV=production
- TERM=xterm-256color
- CLAWDBOT_GATEWAY_BIND=\${CLAWDBOT_GATEWAY_BIND}
- CLAWDBOT_GATEWAY_PORT=\${CLAWDBOT_GATEWAY_PORT}
- CLAWDBOT_GATEWAY_TOKEN=\${CLAWDBOT_GATEWAY_TOKEN}
- GOG_KEYRING_PASSWORD=\${GOG_KEYRING_PASSWORD}
- XDG_CONFIG_HOME=\${XDG_CONFIG_HOME}
- ANTHROPIC_API_KEY=\${ANTHROPIC_API_KEY}
- PATH=/home/linuxbrew/.linuxbrew/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
'"$extra_env_vars"'
volumes:
- \${CLAWDBOT_CONFIG_DIR}:/home/node/.clawdbot
- \${CLAWDBOT_WORKSPACE_DIR}:/home/node/clawd
'"$tailscale_volume"' ports:
- "'"$port_binding"'"
command:
[
"node",
"dist/index.js",
"gateway",
"--bind",
"\${CLAWDBOT_GATEWAY_BIND}",
"--port",
"\${CLAWDBOT_GATEWAY_PORT}"
]
EOF
chown "$REMOTE_USER:$REMOTE_USER" "$HOME_DIR/moltbot/docker-compose.yml"
echo "Created docker-compose.yml"
echo "=== Step 10: Create Dockerfile.gcp ==="
# Build Tailscale installation command if enabled
TAILSCALE_INSTALL=""
if [[ "$INSTALL_TAILSCALE" == "true" ]]; then
TAILSCALE_INSTALL="# Install Tailscale CLI (for tailscale serve)
RUN curl -fsSL https://tailscale.com/install.sh | sh"
fi
cat > "$HOME_DIR/moltbot/Dockerfile.gcp" << EOF
FROM node:22-bookworm
# Install system dependencies
RUN apt-get update && apt-get install -y socat curl && rm -rf /var/lib/apt/lists/*
$TAILSCALE_INSTALL
# Install Bun (required for build scripts)
RUN curl -fsSL https://bun.sh/install | bash
ENV PATH="/root/.bun/bin:\${PATH}"
# Optional: Add external binaries here if needed for specific skills
# Example (uncomment and adjust URL if binary exists):
# RUN curl -L https://example.com/binary.tar.gz | tar -xz -C /usr/local/bin
WORKDIR /app
# Copy package files first for better caching
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY ui/package.json ./ui/package.json
COPY patches ./patches
COPY scripts ./scripts
RUN corepack enable
RUN pnpm install --frozen-lockfile
COPY . .
RUN CLAWDBOT_A2UI_SKIP_MISSING=1 pnpm build
RUN pnpm ui:install
RUN pnpm ui:build
ENV NODE_ENV=production
# Security: Run as non-root user
USER node
CMD ["node", "dist/index.js"]
EOF
chown "$REMOTE_USER:$REMOTE_USER" "$HOME_DIR/moltbot/Dockerfile.gcp"
echo "Created Dockerfile.gcp"
echo "=== Step 11: Pre-configure gateway ==="
# Write config.json directly to the volume BEFORE starting the container
# This prevents the "Missing config" error that causes container restart loops
echo "Writing gateway configuration..."
# Build the config JSON
CONFIG_JSON="{
\"gateway\": {
\"mode\": \"local\",
\"auth\": {
\"mode\": \"token\"
},
\"controlUi\": {
\"allowInsecureAuth\": true
}
}"
# Add Telegram config if token is present
if grep -q "TELEGRAM_BOT_TOKEN" "$HOME_DIR/moltbot/.env" 2>/dev/null; then
# Check if we have a user ID for allowlist
if [[ "'"$TELEGRAM_USER_ID"'" != "''" ]]; then
CONFIG_JSON="$CONFIG_JSON,
\"channels\": {
\"telegram\": {
\"enabled\": true,
\"dmPolicy\": \"allowlist\",
\"allowFrom\": [\"'"$TELEGRAM_USER_ID"'\"]
}
}"
echo "Telegram configured with allowlist for user '"$TELEGRAM_USER_ID"'"
else
CONFIG_JSON="$CONFIG_JSON,
\"channels\": {
\"telegram\": {
\"enabled\": true
}
}"
echo "Telegram enabled (pairing required)"
fi
fi
CONFIG_JSON="$CONFIG_JSON
}"
# Write config to the mounted volume
echo "$CONFIG_JSON" > "$HOME_DIR/.clawdbot/config.json"
chown "$REMOTE_USER:$REMOTE_USER" "$HOME_DIR/.clawdbot/config.json"
chmod 600 "$HOME_DIR/.clawdbot/config.json"
echo "Config written to ~/.clawdbot/config.json"
echo "=== Step 12: Build and launch ==="
cd "$HOME_DIR/moltbot"
# Ensure user can run docker
usermod -aG docker "$REMOTE_USER" 2>/dev/null || true
# Build the image
echo "Building Docker image (this may take several minutes)..."
docker compose build
# Start the gateway (config already exists, so it will start successfully)
echo "Starting Moltbot gateway..."
docker compose up -d --force-recreate moltbot-gateway
# Wait for gateway to be ready
echo "Waiting for gateway to be ready..."
for i in {1..30}; do
if curl -sf http://localhost:18789/health >/dev/null 2>&1; then
echo "Gateway is ready!"
break
fi
echo " Waiting... ($i/30)"
sleep 2
done
echo ""
echo "=== Step 13: Verify Gateway ==="
sleep 5
docker compose logs --tail=20 moltbot-gateway
echo ""
echo "Setup complete!"
'
# Run setup script on instance
echo "$setup_script" | gcloud compute ssh "$INSTANCE_NAME" \
--project="$PROJECT_ID" \
--zone="$ZONE" \
--command="sudo bash"
log_success "Moltbot configured and started"
# Verify service is running and stays running
log_info "Verifying gateway status (checking stability)..."
local check_attempts=0
local max_checks=6
local stable_count=0
local required_stable=3
while [[ $check_attempts -lt $max_checks ]]; do
sleep 5
check_attempts=$((check_attempts + 1))
local container_state
container_state=$(gcloud compute ssh "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" \
--command="cd ~/moltbot && docker compose ps --format '{{.State}}' moltbot-gateway 2>/dev/null" 2>/dev/null || echo "")
if [[ "$container_state" == "running" ]]; then
stable_count=$((stable_count + 1))
log_detail "Container running ($stable_count/$required_stable checks)..."
if [[ $stable_count -ge $required_stable ]]; then
log_success "Moltbot gateway is running and stable"
return 0
fi
else
stable_count=0
log_warn "Container not running (state: ${container_state:-unknown}), attempt $check_attempts/$max_checks"
# Try to restart if container stopped
if [[ $check_attempts -lt $max_checks ]]; then
log_info "Attempting to restart container..."
gcloud compute ssh "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" \
--command="cd ~/moltbot && docker compose up -d moltbot-gateway" 2>/dev/null || true
fi
fi
done
# If we get here, container is unstable
log_error "Gateway container is not stable after $max_checks attempts"
log_detail "Check logs with:"
log_detail "gcloud compute ssh $INSTANCE_NAME --zone=$ZONE --project=$PROJECT_ID --command='cd ~/moltbot && docker compose logs --tail=50 moltbot-gateway'"
exit 1
}
# Install and configure Tailscale for HTTPS access
install_tailscale() {
if [[ "$INSTALL_TAILSCALE" != "true" ]]; then
return
fi
log_step "Installing Tailscale for HTTPS access"
log_info "Waiting for apt lock to be released (startup script may still be running)..."
gcloud compute ssh "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" \
--command='for i in {1..30}; do sudo fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 || break; echo "Waiting for apt lock... ($i/30)"; sleep 5; done' 2>&1
log_info "Installing Tailscale and jq on instance..."
gcloud compute ssh "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" \
--command='sudo apt-get update && sudo apt-get install -y jq && curl -fsSL https://tailscale.com/install.sh | sh' 2>&1 || {
log_warn "First attempt failed, retrying after 10s..."
sleep 10
gcloud compute ssh "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" \
--command='sudo apt-get update && sudo apt-get install -y jq && curl -fsSL https://tailscale.com/install.sh | sh' 2>&1 || true
}
log_info "Starting Tailscale (follow the auth link)..."
echo ""
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ TAILSCALE AUTHENTICATION REQUIRED ║"
echo "║ Click the link below to authenticate: ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""
gcloud compute ssh "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" \
--command='sudo tailscale up --hostname=moltbot-gateway' 2>&1
# Get Tailscale hostname (try jq first, fallback to grep)
TAILSCALE_HOSTNAME=$(gcloud compute ssh "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" \
--command='tailscale status --json 2>/dev/null | jq -r ".Self.DNSName // empty" 2>/dev/null | sed "s/\.$//"' 2>/dev/null || echo "")
if [[ -z "$TAILSCALE_HOSTNAME" ]]; then
log_warn "Could not get Tailscale hostname automatically"
log_detail "Get it with: tailscale status"
return
fi
log_success "Tailscale connected: $TAILSCALE_HOSTNAME"
# Configure Tailscale Serve on HOST (must run with sudo on host, not from container)
# Container cannot run tailscale serve due to permission issues
log_info "Configuring Tailscale Serve to proxy HTTPS to port 18789..."
gcloud compute ssh "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" \
--command='sudo tailscale serve --bg --yes 18789' 2>&1 || {
log_warn "Could not configure Tailscale Serve automatically"
log_detail "Run manually on instance: sudo tailscale serve --bg --yes 18789"
}
log_success "Tailscale Serve configured"
log_success "Access URL: https://$TAILSCALE_HOSTNAME/"
}
# Get instance external IP
get_instance_ip() {
gcloud compute instances describe "$INSTANCE_NAME" \
--project="$PROJECT_ID" \
--zone="$ZONE" \
--format="value(networkInterfaces[0].accessConfigs[0].natIP)"
}
# Main
main() {
echo ""
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ Moltbot Compute Engine Deployment ║"
echo "║ (following docs/platforms/gcp.md) ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""
echo "Configuration:"
echo " Project: $PROJECT_ID"
echo " Zone: $ZONE"
echo " Instance: $INSTANCE_NAME"
echo " Machine type: $MACHINE_TYPE"
echo " Disk size: $DISK_SIZE"
if [[ "$PUBLIC_ACCESS" == "true" ]]; then
if [[ -n "$ALLOWED_IP" ]]; then
echo " Access mode: Restricted to IP $ALLOWED_IP"
else
echo " Access mode: Public (open to all IPs)"
fi
else
echo " Access mode: Loopback (SSH tunnel)"
fi
[[ "$INSTALL_TAILSCALE" == "true" ]] && echo " Tailscale: Yes (HTTPS)"
[[ -n "$TELEGRAM_USER_ID" ]] && echo " Telegram user: $TELEGRAM_USER_ID (auto-approved)"
[[ -n "$ENV_FILE" ]] && echo " Env file: $ENV_FILE"
echo ""
check_prerequisites
enable_apis
create_instance
setup_firewall
install_tailscale # Must run BEFORE Docker so Tailscale socket exists
configure_moltbot
local instance_ip
instance_ip=$(get_instance_ip)
echo ""
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ Deployment Complete! ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""
log_success "Instance IP: $instance_ip"
echo ""
# Show generated secrets if we created them
if [[ -n "${GENERATED_GATEWAY_TOKEN:-}" ]] || [[ -n "${GENERATED_GOG_PASSWORD:-}" ]]; then
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ IMPORTANT: Save these generated secrets! ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""
if [[ -n "${GENERATED_GATEWAY_TOKEN:-}" ]]; then
echo " CLAWDBOT_GATEWAY_TOKEN=$GENERATED_GATEWAY_TOKEN"
fi
if [[ -n "${GENERATED_GOG_PASSWORD:-}" ]]; then
echo " GOG_KEYRING_PASSWORD=$GENERATED_GOG_PASSWORD"
fi
echo ""
echo " Save these securely - you'll need the gateway token to connect."
echo ""
fi
# Access instructions based on mode
if [[ "$INSTALL_TAILSCALE" == "true" ]] && [[ -n "${TAILSCALE_HOSTNAME:-}" ]]; then
echo "Access (Tailscale HTTPS - secure, private network):"
echo ""
echo " Prerequisites:"
echo " 1. Install Tailscale on your device: https://tailscale.com/download"
echo " 2. Log in with the same account used during deployment"
echo ""
echo " Open dashboard (copy this URL):"
if [[ -n "${GENERATED_GATEWAY_TOKEN:-}" ]]; then
echo " https://$TAILSCALE_HOSTNAME/?token=$GENERATED_GATEWAY_TOKEN"
else
echo " https://$TAILSCALE_HOSTNAME/?token=YOUR_GATEWAY_TOKEN"
fi
echo ""
echo " Security: Only devices on YOUR Tailnet can access this URL."
echo " Even with the URL+token, others cannot connect."
elif [[ "$PUBLIC_ACCESS" == "true" ]]; then
echo "Access (public mode - WARNING: exposed to internet):"
echo ""
echo " API/CLI access:"
echo " http://$instance_ip:18789"
echo ""
echo " Test health:"
echo " curl http://$instance_ip:18789/health"
echo ""
echo " Browser dashboard (requires SSH tunnel for WebSocket):"
echo " gcloud compute ssh $INSTANCE_NAME --zone=$ZONE --project=$PROJECT_ID -- -L 18789:127.0.0.1:18789"
echo " Then open: http://127.0.0.1:18789/"
echo ""
if [[ -n "${GENERATED_GATEWAY_TOKEN:-}" ]]; then
echo " Token: $GENERATED_GATEWAY_TOKEN"
fi
echo ""
echo " WARNING: HTTP over public IP does not work in browser dashboard."
echo " Browser requires HTTPS or localhost. Use --tailscale for HTTPS."
else
echo "Access (SSH tunnel mode - recommended for security):"
echo ""
echo " Step 1: Create SSH tunnel from your laptop:"
echo " gcloud compute ssh $INSTANCE_NAME --zone=$ZONE --project=$PROJECT_ID -- -L 18789:127.0.0.1:18789"
echo ""
echo " Step 2: Open in browser:"
echo " http://127.0.0.1:18789/"
echo ""
echo " Step 3: Enter your gateway token"
if [[ -n "${GENERATED_GATEWAY_TOKEN:-}" ]]; then
echo " Token: $GENERATED_GATEWAY_TOKEN"
fi
fi
# Show Telegram info if configured
if [[ -n "$TELEGRAM_USER_ID" ]]; then
echo ""
echo "Telegram:"
echo " Your user ID ($TELEGRAM_USER_ID) is pre-approved."
echo " Just message your bot - no pairing needed!"
fi
echo ""
echo "Useful commands:"
echo ""
echo " SSH to instance:"
echo " gcloud compute ssh $INSTANCE_NAME --zone=$ZONE --project=$PROJECT_ID"
echo ""
echo " View logs:"
echo " gcloud compute ssh $INSTANCE_NAME --zone=$ZONE --project=$PROJECT_ID --command='cd ~/moltbot && docker compose logs -f'"
echo ""
echo " Restart gateway:"
echo " gcloud compute ssh $INSTANCE_NAME --zone=$ZONE --project=$PROJECT_ID --command='cd ~/moltbot && docker compose restart'"
echo ""
echo " Update Moltbot:"
echo " gcloud compute ssh $INSTANCE_NAME --zone=$ZONE --project=$PROJECT_ID --command='cd ~/moltbot && git pull && docker compose build && docker compose up -d'"
echo ""
echo "What persists where:"
echo " ~/.clawdbot → Gateway config, OAuth tokens, WhatsApp session"
echo " ~/clawd → Agent workspace, code artifacts"
echo " Docker image → Application and dependencies"
echo ""
echo "Cloud Console:"
echo " https://console.cloud.google.com/compute/instancesDetail/zones/$ZONE/instances/$INSTANCE_NAME?project=$PROJECT_ID"
echo ""
# Show quick access URL at the very end for easy copy/click
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ 🚀 QUICK ACCESS - Copy and paste this URL: ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""
if [[ "$INSTALL_TAILSCALE" == "true" ]] && [[ -n "${TAILSCALE_HOSTNAME:-}" ]]; then
if [[ -n "${GENERATED_GATEWAY_TOKEN:-}" ]]; then
echo " https://${TAILSCALE_HOSTNAME}/?token=${GENERATED_GATEWAY_TOKEN}"
else
echo " https://${TAILSCALE_HOSTNAME}/"
fi
echo ""
echo " (Requires Tailscale installed on your device)"
else
echo " Step 1: Run this command in a NEW terminal:"
echo " gcloud compute ssh $INSTANCE_NAME --zone=$ZONE --project=$PROJECT_ID -- -L 18789:127.0.0.1:18789"
echo ""
echo " Step 2: Open this URL in your browser:"
if [[ -n "${GENERATED_GATEWAY_TOKEN:-}" ]]; then
echo " http://127.0.0.1:18789/?token=${GENERATED_GATEWAY_TOKEN}"
else
echo " http://127.0.0.1:18789/"
fi
fi
echo ""
}
main

View File

@ -0,0 +1,178 @@
#!/usr/bin/env bash
#
# Uninstall Moltbot from Google Compute Engine
#
# Usage:
# ./scripts/deploy/google/compute-engine/uninstall.sh [OPTIONS]
#
# Options:
# --project PROJECT_ID Google Cloud project ID (required)
# --zone ZONE Compute Engine zone (default: us-central1-a)
# --instance NAME Instance name (default: moltbot-gateway)
# --keep-firewall Don't delete firewall rule
# --help Show this help message
#
# Examples:
# # Delete everything
# ./scripts/deploy/google/compute-engine/uninstall.sh --project my-project
#
# # Keep firewall rule for future deployments
# ./scripts/deploy/google/compute-engine/uninstall.sh --project my-project --keep-firewall
#
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Default values (matching run.sh defaults)
ZONE="us-central1-a"
INSTANCE_NAME="moltbot-gateway"
PROJECT_ID=""
KEEP_FIREWALL=false
# Logging functions
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
log_step() { echo -e "\n${CYAN}━━━ $1 ━━━${NC}"; }
log_detail() { echo -e " ${NC}$1"; }
show_help() {
head -22 "$0" | tail -20 | sed 's/^#//' | sed 's/^ //'
exit 0
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--project) PROJECT_ID="$2"; shift 2 ;;
--zone) ZONE="$2"; shift 2 ;;
--instance) INSTANCE_NAME="$2"; shift 2 ;;
--keep-firewall) KEEP_FIREWALL=true; shift ;;
--help|-h) show_help ;;
*) log_error "Unknown option: $1"; exit 1 ;;
esac
done
# Validate required arguments
if [[ -z "$PROJECT_ID" ]]; then
log_error "Project ID is required. Use --project PROJECT_ID"
echo ""
echo "Usage: $0 --project YOUR_PROJECT_ID [OPTIONS]"
exit 1
fi
# Check prerequisites
check_prerequisites() {
log_step "Checking prerequisites"
log_info "Checking if gcloud CLI is installed..."
if ! command -v gcloud &> /dev/null; then
log_error "gcloud CLI is not installed!"
log_detail "Install from: https://cloud.google.com/sdk/docs/install"
exit 1
fi
log_detail "gcloud CLI found: $(which gcloud)"
log_info "Checking gcloud authentication..."
if ! gcloud auth print-access-token &> /dev/null; then
log_error "Not authenticated with gcloud!"
log_detail "Run: gcloud auth login"
exit 1
fi
log_detail "Authenticated as: $(gcloud config get-value account 2>/dev/null)"
log_success "Prerequisites OK"
}
# Delete Compute Engine instance
delete_instance() {
log_step "Deleting Compute Engine instance"
log_info "Checking if instance exists..."
if gcloud compute instances describe "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" &>/dev/null; then
log_detail "Instance found: $INSTANCE_NAME"
log_info "Deleting instance..."
if gcloud compute instances delete "$INSTANCE_NAME" \
--project="$PROJECT_ID" \
--zone="$ZONE" \
--quiet; then
log_success "Instance deleted: $INSTANCE_NAME"
else
log_error "Failed to delete instance"
exit 1
fi
else
log_warn "Instance not found: $INSTANCE_NAME (already deleted?)"
fi
}
# Delete firewall rule
delete_firewall() {
if [[ "$KEEP_FIREWALL" == "true" ]]; then
log_step "Keeping firewall rule (--keep-firewall flag)"
log_info "Firewall rule will be preserved for future deployments"
return
fi
log_step "Deleting firewall rule"
log_info "Checking firewall rule..."
if gcloud compute firewall-rules describe moltbot-gateway --project="$PROJECT_ID" &>/dev/null; then
log_detail "Firewall rule found, deleting..."
if gcloud compute firewall-rules delete moltbot-gateway \
--project="$PROJECT_ID" \
--quiet 2>/dev/null; then
log_success "Firewall rule deleted"
else
log_warn "Failed to delete firewall rule"
fi
else
log_detail "Firewall rule not found (skipping)"
fi
}
# Main
main() {
echo ""
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ Moltbot Compute Engine Uninstaller ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""
echo "Configuration:"
echo " Project: $PROJECT_ID"
echo " Zone: $ZONE"
echo " Instance: $INSTANCE_NAME"
echo " Keep firewall: $KEEP_FIREWALL"
echo ""
check_prerequisites
delete_instance
delete_firewall
echo ""
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ Uninstall Complete ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""
log_success "Moltbot has been removed from Compute Engine"
echo ""
if [[ "$KEEP_FIREWALL" == "true" ]]; then
echo "Note: Firewall rule was preserved. To delete it later:"
echo " gcloud compute firewall-rules delete moltbot-gateway --project=$PROJECT_ID"
echo ""
fi
echo "To redeploy:"
echo " ./scripts/deploy/google/compute-engine/run.sh --project $PROJECT_ID --anthropic-key YOUR_KEY"
echo ""
}
main