feat(deploy): add GCP Compute Engine deployment scripts
One-command deployment for Moltbot on GCP Compute Engine: - Automated VM creation, Docker setup, and gateway configuration - Optional Tailscale integration for HTTPS (no SSH tunnel needed) - Optional Telegram allowlist for auto-approved users - Auto-generates secure tokens (gateway token, keyring password) - Container stability checks with auto-restart - Uninstall script included
This commit is contained in:
parent
9688454a30
commit
ad9324ddbb
@ -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)
|
||||
|
||||
If you prefer manual setup or need custom configuration:
|
||||
|
||||
1) Create GCP project + enable Compute Engine API
|
||||
2) Create Compute Engine VM (e2-small, Debian 12, 20GB)
|
||||
3) SSH into the VM
|
||||
|
||||
576
scripts/deploy/google/compute-engine/README.md
Normal file
576
scripts/deploy/google/compute-engine/README.md
Normal 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`.
|
||||
979
scripts/deploy/google/compute-engine/run.sh
Executable file
979
scripts/deploy/google/compute-engine/run.sh
Executable 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
|
||||
178
scripts/deploy/google/compute-engine/uninstall.sh
Executable file
178
scripts/deploy/google/compute-engine/uninstall.sh
Executable 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
|
||||
Loading…
Reference in New Issue
Block a user