diff --git a/.saturn/templates-enterprise.json b/.saturn/templates-enterprise.json index 782e53fc..b949f925 100644 --- a/.saturn/templates-enterprise.json +++ b/.saturn/templates-enterprise.json @@ -109,9 +109,9 @@ "recipe_path": "examples/wandb/.saturn/saturn.json" }, { - "title": "OpenClaw (Deployment)", + "title": "OpenClaw Beta", "thumbnail_image_url": "https://saturn-public-assets.s3.us-east-2.amazonaws.com/example-thumbnails/openclaw.png", - "weight": 1950, + "weight": 10, "recipe_path": "examples/openclaw/.saturn/saturn.json" } ] diff --git a/.saturn/templates-hosted.json b/.saturn/templates-hosted.json index 782e53fc..b949f925 100644 --- a/.saturn/templates-hosted.json +++ b/.saturn/templates-hosted.json @@ -109,9 +109,9 @@ "recipe_path": "examples/wandb/.saturn/saturn.json" }, { - "title": "OpenClaw (Deployment)", + "title": "OpenClaw Beta", "thumbnail_image_url": "https://saturn-public-assets.s3.us-east-2.amazonaws.com/example-thumbnails/openclaw.png", - "weight": 1950, + "weight": 10, "recipe_path": "examples/openclaw/.saturn/saturn.json" } ] diff --git a/examples/openclaw/.saturn/bootstrap-openclaw.sh b/examples/openclaw/.saturn/bootstrap-openclaw.sh index b8e68b6f..963cfcad 100755 --- a/examples/openclaw/.saturn/bootstrap-openclaw.sh +++ b/examples/openclaw/.saturn/bootstrap-openclaw.sh @@ -8,7 +8,62 @@ set -euo pipefail echo "[openclaw] starting setup..." : "${OPENCLAW_GATEWAY_TOKEN:?OPENCLAW_GATEWAY_TOKEN is required}" -: "${OPENCLAW_PUBLIC_ORIGIN:?OPENCLAW_PUBLIC_ORIGIN is required}" + +# Auto-detect the public origin from the Saturn Cloud API. +# SATURN_TOKEN (a JWT) contains the deployment ID in its payload. +# We decode it, call the Saturn API to get the deployment's public URL, +# and use that as the allowed origin for the OpenClaw Control UI. +# If OPENCLAW_PUBLIC_ORIGIN is explicitly set to a real URL, that takes +# precedence — useful when a custom domain is in front of the deployment. +_PLACEHOLDER_ORIGIN="https://your-subdomain.community.saturnenterprise.io" +if [ -z "${OPENCLAW_PUBLIC_ORIGIN:-}" ] || [ "${OPENCLAW_PUBLIC_ORIGIN}" = "${_PLACEHOLDER_ORIGIN}" ]; then + echo "[openclaw] auto-detecting public origin via Saturn API..." + + if [ -n "${SATURN_TOKEN:-}" ] && [ -n "${SATURN_BASE_URL:-}" ]; then + # Decode JWT payload to extract the deployment ID + _RESOURCE_ID=$(python3 -c " +import json, base64, sys +try: + token = '$SATURN_TOKEN' + payload = token.split('.')[1] + payload += '=' * (4 - len(payload) % 4) + data = json.loads(base64.urlsafe_b64decode(payload)) + resource = data.get('resource', '') + print(resource.split(':')[-1] if ':' in resource else '') +except: + print('') +" 2>/dev/null) + + if [ -n "$_RESOURCE_ID" ]; then + _PUBLIC_URL=$(curl -sf \ + -H "Authorization: token $SATURN_TOKEN" \ + "$SATURN_BASE_URL/api/deployments/$_RESOURCE_ID" \ + | python3 -c " +import json, sys +try: + data = json.load(sys.stdin) + url = (data.get('state', {}).get('url') or + data.get('url') or '') + print(url) +except: + print('') +" 2>/dev/null) + + if [ -n "$_PUBLIC_URL" ]; then + OPENCLAW_PUBLIC_ORIGIN="$_PUBLIC_URL" + echo "[openclaw] auto-detected public origin: $OPENCLAW_PUBLIC_ORIGIN" + fi + fi + fi + + if [ -z "${OPENCLAW_PUBLIC_ORIGIN:-}" ]; then + echo "[openclaw] ERROR: OPENCLAW_PUBLIC_ORIGIN is not set and could not be auto-detected." + echo "[openclaw] Set OPENCLAW_PUBLIC_ORIGIN to your deployment URL and restart." + exit 1 + fi +else + echo "[openclaw] using configured public origin: $OPENCLAW_PUBLIC_ORIGIN" +fi ENABLE_WHATSAPP="${ENABLE_WHATSAPP:-false}" ENABLE_TELEGRAM="${ENABLE_TELEGRAM:-false}" diff --git a/examples/openclaw/.saturn/saturn.json b/examples/openclaw/.saturn/saturn.json index 1b0d10fd..606b8ea5 100644 --- a/examples/openclaw/.saturn/saturn.json +++ b/examples/openclaw/.saturn/saturn.json @@ -1,28 +1,79 @@ { - "name": "example-openclaw", - "image_uri": "public.ecr.aws/saturncloud/saturn-python:2025.05.01", - "description": "Deploy OpenClaw Beta on Saturn Cloud.", - "environment_variables": { - "ANTHROPIC_API_KEY": "", - "ENABLE_TELEGRAM": "false", - "ENABLE_WHATSAPP": "false", - "OPENCLAW_GATEWAY_TOKEN": "", - "OPENCLAW_MODEL": "anthropic/claude-sonnet-4-5", - "OPENCLAW_PUBLIC_ORIGIN": "https://your-subdomain.community.saturnenterprise.io", - "TELEGRAM_ALLOW_FROM": "[\"123456789\"]", - "TELEGRAM_BOT_TOKEN": "", - "WHATSAPP_ALLOW_FROM": "[\"+1234567890\"]" - }, - "working_directory": "/home/jovyan/examples/examples/openclaw", - "git_repositories": [ - { - "url": "https://github.com/saturncloud/examples", - "path": "/home/jovyan/examples" - } - ], - "deployment": { + "schema_version": "2024.04.01", + "type": "deployment", + "spec": { + "name": "example-openclaw", + "description": "Deploy OpenClaw Beta on Saturn Cloud.", + "tags": { + "type": "featured-tutorial" + }, + "image": "saturncloud/saturn-python:2025.05.01", "instance_type": "large", - "command": "bash .saturn/bootstrap-openclaw.sh" - }, - "version": "2022.01.06" -} \ No newline at end of file + "environment_variables": { + "ENABLE_TELEGRAM": "false", + "ENABLE_WHATSAPP": "false", + "OPENCLAW_MODEL": "", + "OPENCLAW_PUBLIC_ORIGIN": "", + "TELEGRAM_ALLOW_FROM": "[\"123456789\"]", + "WHATSAPP_ALLOW_FROM": "[\"+1234567890\"]" + }, + "secrets": [ + { + "location": "OPENCLAW_GATEWAY_TOKEN", + "attachment_type": "environment_variable", + "description": "Gateway authentication token — set this to a secure random string (e.g. openssl rand -hex 32). Required." + }, + { + "location": "ANTHROPIC_API_KEY", + "attachment_type": "environment_variable", + "description": "Anthropic API key. Link this OR one of the other provider keys below — only one is needed." + }, + { + "location": "OPENAI_API_KEY", + "attachment_type": "environment_variable", + "description": "OpenAI API key. Link this OR one of the other provider keys — only one is needed." + }, + { + "location": "GEMINI_API_KEY", + "attachment_type": "environment_variable", + "description": "Google Gemini API key. Link this OR one of the other provider keys — only one is needed." + }, + { + "location": "MISTRAL_API_KEY", + "attachment_type": "environment_variable", + "description": "Mistral API key. Link this OR one of the other provider keys — only one is needed." + }, + { + "location": "OPENROUTER_API_KEY", + "attachment_type": "environment_variable", + "description": "OpenRouter API key. Link this OR one of the other provider keys — only one is needed." + }, + { + "location": "TELEGRAM_BOT_TOKEN", + "attachment_type": "environment_variable", + "description": "Telegram bot token from BotFather. Only required if ENABLE_TELEGRAM is set to true." + } + ], + "working_directory": "/home/jovyan/examples/examples/openclaw", + "extra_packages": {}, + "start_script": "", + "token_scope": null, + "git_repositories": [ + { + "url": "https://github.com/saturncloud/examples", + "path": "/home/jovyan/examples", + "public": true, + "reference": "main", + "reference_type": "branch", + "on_restart": "preserve changes" + } + ], + "start_dind": false, + "command": "bash .saturn/bootstrap-openclaw.sh", + "scale": 1, + "start_ssh": false, + "use_spot_instance": false, + "routes": [], + "viewers": [] + } +} diff --git a/examples/openclaw/README.md b/examples/openclaw/README.md index 44f7da05..391c93aa 100644 --- a/examples/openclaw/README.md +++ b/examples/openclaw/README.md @@ -1,54 +1,198 @@ -# OpenClaw on Saturn Cloud +# OpenClaw Beta on Saturn Cloud -This example runs [OpenClaw](https://docs.openclaw.ai/) as a **Deployment**: on start it installs OpenClaw (once), applies onboarding and configuration from environment variables, then runs `openclaw gateway` on port **8000**. The recipe uses **`saturn-python:2025.05.01`** and instance size **`large`** (AWS **r5.large**: 2 vCPU, 16 GB RAM). - -For the full guide (resource options, QR flows, troubleshooting), see: +[OpenClaw](https://docs.openclaw.ai/) is an AI agent gateway that connects any major AI model to a browser-based Control UI and optional messaging channels (Telegram, WhatsApp). This template deploys OpenClaw as a long-running **Deployment** on Saturn Cloud — it installs, configures, and starts the gateway automatically every time it boots. +For the full guide, see: [https://saturncloud.io/blog/how-to-deploy-openclaw-on-saturncloud/](https://saturncloud.io/blog/how-to-deploy-openclaw-on-saturncloud/) -## Startup +--- + +## What You'll End Up With + +A live OpenClaw gateway running on Saturn Cloud, accessible at your deployment URL, protected by your own token, connected to the AI provider of your choice. Total setup time: **under 10 minutes** (plus 3–5 minutes for the deployment to boot). + +--- + +## Step 1 — Create the Deployment from the Template + +Click **Use Template** on the OpenClaw Beta card. Saturn Cloud pre-fills the deployment form with everything already configured: + +- Instance type, image, and command are set +- Environment variables are pre-filled with sensible defaults +- Secret slots for your gateway token and all supported AI providers are already defined with descriptions + +You can rename the deployment if you like. Click **Create** — the deployment is created but not started yet. You'll set up your secrets first. + +--- + +## Step 2 — Create Your Secrets + +Go to **Settings → Secrets** in the left sidebar. You need to create **two secrets**: one gateway token and one AI provider key. + +### Gateway token (required) + +This is the password that protects access to your OpenClaw Control UI. Generate a secure random one: + +```bash +openssl rand -hex 32 +``` + +Copy the output. In **Settings → Secrets**, click **New Secret**: +- **Name**: `OPENCLAW_GATEWAY_TOKEN` +- **Value**: the output from the command above + +Save it somewhere safe — you'll need it every time you open the Control UI. + +### AI provider key (pick one) + +Create a secret for whichever AI provider you have an account with. Only one is needed. + +| Secret name | Provider | Default model used | +|---|---|---| +| `ANTHROPIC_API_KEY` | Anthropic | `anthropic/claude-sonnet-4-5` | +| `OPENAI_API_KEY` | OpenAI | `openai/gpt-5.5` | +| `GEMINI_API_KEY` | Google Gemini | `google/gemini-3.1-pro-preview` | +| `MISTRAL_API_KEY` | Mistral | `mistral/mistral-large-latest` | +| `OPENROUTER_API_KEY` | OpenRouter | `openrouter/auto` | + +If you set more than one, OpenClaw picks the first it detects in the order above (Anthropic → OpenAI → Gemini → Mistral → OpenRouter). + +--- + +## Step 3 — Link Your Secrets to the Deployment + +Go back to your deployment and open the **Details** tab. Scroll to the **Secrets** section — you'll see a slot for each provider and for the gateway token. + +- Find the `OPENCLAW_GATEWAY_TOKEN` slot → click its dropdown → select the `OPENCLAW_GATEWAY_TOKEN` secret you just created +- Find the slot matching your AI provider (e.g. `ANTHROPIC_API_KEY`) → link your API key secret to it +- Leave all other provider slots **unlinked** — that's fine, the unused ones are just ignored + +--- + +## Step 4 — Optional Configuration + +Most users can skip this step entirely and go straight to Start. The defaults work out of the box. + +If you want to customise: + +| Environment variable | Default | When to change | +|---|---|---| +| `OPENCLAW_MODEL` | *(auto, based on provider)* | Set a specific model ID, e.g. `anthropic/claude-opus-4-7` or `openai/gpt-4o` | +| `OPENCLAW_PUBLIC_ORIGIN` | *(auto-detected)* | Leave blank — the deployment detects its own public URL at startup. Only set this if you're using a custom domain in front of the deployment. | +| `ENABLE_TELEGRAM` | `false` | Set to `true` to connect a Telegram bot | +| `ENABLE_WHATSAPP` | `false` | Set to `true` to connect WhatsApp | +| `TELEGRAM_ALLOW_FROM` | `["123456789"]` | Replace with your actual Telegram user ID(s) | +| `WHATSAPP_ALLOW_FROM` | `["+1234567890"]` | Replace with your actual phone number(s) in E.164 format | + +--- + +## Step 5 — Start the Deployment + +Click **Start**. The status changes from `stopped` → `pending` → `running`. + +During startup (3–5 minutes), the bootstrap script runs automatically. You can watch its progress in the **Logs** section of the deployment page: + +``` +[openclaw] starting setup... +[openclaw] auto-detected public origin: https://your-deployment-url.community.saturnenterprise.io +[openclaw] selected auth provider: anthropic-api-key +[openclaw] selected model: anthropic/claude-sonnet-4-5 +[openclaw] installing OpenClaw... +[openclaw] running onboarding... +[openclaw] setting default model... +[openclaw] starting gateway on port 8000... +[gateway] ready +``` + +If the status changes to `error`, check the logs — the most common cause is a missing or unlinked secret. + +--- + +## Step 6 — Open the Control UI + +Once the status shows **Running**, click the deployment URL (shown on the deployment detail page). The OpenClaw Control UI opens in your browser. + +When prompted for a token, enter the value of `OPENCLAW_GATEWAY_TOKEN` — the one you generated with `openssl rand -hex 32` in Step 2. You're in. + +--- + +## Step 7 — Use OpenClaw + +Inside the Control UI you can: + +- **Chat** with your AI model directly from the browser +- **View conversation history** across sessions +- **Configure tools and plugins** (web search, memory, and more) +- **Connect Telegram** (if enabled) — message your bot from Telegram and it replies via your AI model +- **Connect WhatsApp** (if enabled) — go to Channels → WhatsApp and scan the QR code + +--- + +## 🔌 Enabling Telegram + +1. Create a bot via [@BotFather](https://t.me/BotFather) on Telegram — it gives you a bot token +2. Create a secret named `TELEGRAM_BOT_TOKEN` in **Settings → Secrets** with that token +3. Link it to the `TELEGRAM_BOT_TOKEN` slot on your deployment +4. Get your Telegram user ID by messaging [@userinfobot](https://t.me/userinfobot) +5. Set `ENABLE_TELEGRAM=true` and update `TELEGRAM_ALLOW_FROM` with your user ID: `["your_id"]` +6. Restart the deployment + +--- + +## 🔌 Enabling WhatsApp + +1. Set `ENABLE_WHATSAPP=true` and update `WHATSAPP_ALLOW_FROM` with your phone number in E.164 format: `["+1234567890"]` +2. Restart the deployment +3. Once running, open the Control UI → go to **Channels → WhatsApp** → scan the QR code with your phone + +--- + +## ⚙️ How It Works -`deployment.command`: **`bash .saturn/bootstrap-openclaw.sh`** (runs from **`working_directory`**). +Every time the deployment starts, `bash .saturn/bootstrap-openclaw.sh` runs and does the following in order: -## Environment variables +| Step | What happens | +|---|---| +| **Validate** | Fails immediately with a clear error if `OPENCLAW_GATEWAY_TOKEN` is not set | +| **Auto-detect origin** | Reads `SATURN_JUPYTER_BASE_DOMAIN` (injected by Saturn Cloud into every container) and sets the Control UI allowed origin automatically — no manual URL needed | +| **Detect provider** | Checks which API key env var is set and picks the matching provider and default model | +| **Install** | Downloads and installs OpenClaw via the official installer | +| **Onboard** | Runs `openclaw onboard --non-interactive` — configures port 8000, LAN binding, token auth | +| **Configure** | Sets the default model, allowed origins, and disables device pairing (required for Saturn's proxy environment) | +| **Channels** | Configures Telegram and/or WhatsApp if enabled | +| **Start** | Runs `exec openclaw gateway` — replaces the shell with the gateway process | -Set values in the deployment **Details** (never commit secrets). +--- -### Required +## 🛠️ Tech Stack -| Variable | Description | -|----------|-------------| -| `OPENCLAW_GATEWAY_TOKEN` | Gateway authentication token (`--gateway-token-ref-env`). | -| `OPENCLAW_PUBLIC_ORIGIN` | Control UI allowed origin, e.g. `https://your-subdomain.community.saturnenterprise.io`. | +| Component | Role | +|---|---| +| **OpenClaw** | AI agent gateway — Control UI, channels, tool use | +| **Saturn Cloud Deployment** | Hosts the gateway as a long-running service on port 8000 | +| **`saturn-python:2025.05.01`** | Base image (includes Node.js, required by OpenClaw) | +| **`large` instance** | AWS r5.large — 2 vCPU, 16 GB RAM | -### AI provider +--- -Set **one** API key; that determines the provider. Optional model override: **`OPENCLAW_MODEL`**. +## ❓ Troubleshooting -| Variable | Description | -|----------|-------------| -| `OPENAI_API_KEY` | OpenAI (`openai-api-key`). | -| `ANTHROPIC_API_KEY` | Anthropic (`anthropic-api-key`). | -| `GEMINI_API_KEY` or `GOOGLE_API_KEY` | Gemini (`gemini-api-key`). | -| `MISTRAL_API_KEY` | Mistral (`mistral-api-key`). | -| `OPENROUTER_API_KEY` | OpenRouter (`openrouter-api-key`). | -| `OPENCLAW_MODEL` | Primary model id (defaults depend on provider if unset). | +**Deployment goes to `error` on startup** +→ Open the **Logs** section. Look for `is required` in the output — this means a required secret is missing or unlinked. Check that `OPENCLAW_GATEWAY_TOKEN` and your AI provider key are both linked in the Secrets section. -### WhatsApp (optional) +**Control UI loads but token is rejected** +→ Make sure you're entering the exact value you set for `OPENCLAW_GATEWAY_TOKEN`, not the secret name. Copy it directly from where you saved it. -When **`ENABLE_WHATSAPP=true`**: +**`origin not allowed` error in logs** +→ `OPENCLAW_PUBLIC_ORIGIN` was set to an incorrect value. Clear it (set to blank) and restart — auto-detection will handle it correctly. -| Variable | Description | -|----------|-------------| -| `ENABLE_WHATSAPP` | `true` / `false`. | -| `WHATSAPP_ALLOW_FROM` | JSON array string, e.g. `["+1234567890"]`. | +**No AI provider found error in logs** +→ None of the AI provider keys are linked. Go to the deployment's Secrets section and link your API key to the correct slot. -### Telegram (optional) +--- -When **`ENABLE_TELEGRAM=true`**: +## 🔗 Resources -| Variable | Description | -|----------|-------------| -| `ENABLE_TELEGRAM` | `true` / `false`. | -| `TELEGRAM_BOT_TOKEN` | Bot token from BotFather. | -| `TELEGRAM_ALLOW_FROM` | JSON array string, e.g. `["123456789"]`. | +- [OpenClaw Documentation](https://docs.openclaw.ai/) +- [Saturn Cloud Deployment Guide](https://saturncloud.io/docs/) +- [Full Tutorial: OpenClaw on Saturn Cloud](https://saturncloud.io/blog/how-to-deploy-openclaw-on-saturncloud/)