diff --git a/.saturn/templates-enterprise.json b/.saturn/templates-enterprise.json index ceba4247..782e53fc 100644 --- a/.saturn/templates-enterprise.json +++ b/.saturn/templates-enterprise.json @@ -107,6 +107,12 @@ "thumbnail_image_url": "https://saturn-public-assets.s3.us-east-2.amazonaws.com/example-thumbnails/weights_and_biases.png", "weight": 1900, "recipe_path": "examples/wandb/.saturn/saturn.json" + }, + { + "title": "OpenClaw (Deployment)", + "thumbnail_image_url": "https://saturn-public-assets.s3.us-east-2.amazonaws.com/example-thumbnails/openclaw.png", + "weight": 1950, + "recipe_path": "examples/openclaw/.saturn/saturn.json" } ] } diff --git a/.saturn/templates-hosted.json b/.saturn/templates-hosted.json index ceba4247..782e53fc 100644 --- a/.saturn/templates-hosted.json +++ b/.saturn/templates-hosted.json @@ -107,6 +107,12 @@ "thumbnail_image_url": "https://saturn-public-assets.s3.us-east-2.amazonaws.com/example-thumbnails/weights_and_biases.png", "weight": 1900, "recipe_path": "examples/wandb/.saturn/saturn.json" + }, + { + "title": "OpenClaw (Deployment)", + "thumbnail_image_url": "https://saturn-public-assets.s3.us-east-2.amazonaws.com/example-thumbnails/openclaw.png", + "weight": 1950, + "recipe_path": "examples/openclaw/.saturn/saturn.json" } ] } diff --git a/examples/openclaw/.saturn/bootstrap-openclaw.sh b/examples/openclaw/.saturn/bootstrap-openclaw.sh new file mode 100755 index 00000000..b8e68b6f --- /dev/null +++ b/examples/openclaw/.saturn/bootstrap-openclaw.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +set -eo pipefail + +cat > "$HOME/start-openclaw.sh" <<'SCRIPT' +#!/usr/bin/env bash +set -euo pipefail + +echo "[openclaw] starting setup..." + +: "${OPENCLAW_GATEWAY_TOKEN:?OPENCLAW_GATEWAY_TOKEN is required}" +: "${OPENCLAW_PUBLIC_ORIGIN:?OPENCLAW_PUBLIC_ORIGIN is required}" + +ENABLE_WHATSAPP="${ENABLE_WHATSAPP:-false}" +ENABLE_TELEGRAM="${ENABLE_TELEGRAM:-false}" + +ENABLE_WHATSAPP="$(echo "$ENABLE_WHATSAPP" | tr '[:upper:]' '[:lower:]')" +ENABLE_TELEGRAM="$(echo "$ENABLE_TELEGRAM" | tr '[:upper:]' '[:lower:]')" + +is_true() { + case "$1" in + true|1|yes|y|on) return 0 ;; + false|0|no|n|off|"") return 1 ;; + *) + echo "[openclaw] invalid boolean value: $1" + echo "[openclaw] use true or false" + exit 1 + ;; + esac +} + +detect_ai_provider() { + if [ -n "${OPENAI_API_KEY:-}" ]; then + AI_AUTH_CHOICE="openai-api-key" + AI_MODEL="${OPENCLAW_MODEL:-openai/gpt-5.5}" + elif [ -n "${ANTHROPIC_API_KEY:-}" ]; then + AI_AUTH_CHOICE="anthropic-api-key" + AI_MODEL="${OPENCLAW_MODEL:-anthropic/claude-sonnet-4-5}" + elif [ -n "${GEMINI_API_KEY:-}" ] || [ -n "${GOOGLE_API_KEY:-}" ]; then + AI_AUTH_CHOICE="gemini-api-key" + AI_MODEL="${OPENCLAW_MODEL:-google/gemini-3.1-pro-preview}" + elif [ -n "${MISTRAL_API_KEY:-}" ]; then + AI_AUTH_CHOICE="mistral-api-key" + AI_MODEL="${OPENCLAW_MODEL:-mistral/mistral-large-latest}" + elif [ -n "${OPENROUTER_API_KEY:-}" ]; then + AI_AUTH_CHOICE="openrouter-api-key" + AI_MODEL="${OPENCLAW_MODEL:-openrouter/auto}" + else + echo "[openclaw] No AI provider API key found." + echo "[openclaw] Set one of: OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, GOOGLE_API_KEY, MISTRAL_API_KEY, OPENROUTER_API_KEY." + exit 1 + fi + + echo "[openclaw] selected auth provider: $AI_AUTH_CHOICE" + echo "[openclaw] selected model: $AI_MODEL" +} + +detect_ai_provider + +echo "[openclaw] installing OpenClaw..." + +curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --no-onboard + +echo "[openclaw] installed version:" +openclaw --version + +echo "[openclaw] running onboarding..." + +openclaw onboard --non-interactive \ + --mode local \ + --auth-choice "$AI_AUTH_CHOICE" \ + --secret-input-mode ref \ + --gateway-port 8000 \ + --gateway-bind lan \ + --gateway-auth token \ + --gateway-token-ref-env OPENCLAW_GATEWAY_TOKEN \ + --accept-risk \ + --skip-health + +echo "[openclaw] setting default model..." + +openclaw config set agents.defaults.model.primary "$AI_MODEL" + +echo "[openclaw] setting allowed origin..." + +openclaw config set gateway.controlUi.allowedOrigins "[\"$OPENCLAW_PUBLIC_ORIGIN\"]" --strict-json + +echo "[openclaw] disabling Control UI device pairing for template mode..." + +openclaw config set gateway.controlUi.dangerouslyDisableDeviceAuth true --strict-json + +if is_true "$ENABLE_WHATSAPP"; then + echo "[openclaw] configuring WhatsApp..." + + : "${WHATSAPP_ALLOW_FROM:?WHATSAPP_ALLOW_FROM is required when ENABLE_WHATSAPP=true}" + + openclaw plugins install @openclaw/whatsapp + + openclaw config set channels.whatsapp.dmPolicy allowlist + openclaw config set channels.whatsapp.allowFrom "$WHATSAPP_ALLOW_FROM" --strict-json + openclaw config set channels.whatsapp.groupPolicy disabled + + echo "[openclaw] WhatsApp configured." + echo "[openclaw] After the UI opens, go to Channels and scan the WhatsApp QR code." +else + echo "[openclaw] WhatsApp disabled." +fi + +if is_true "$ENABLE_TELEGRAM"; then + echo "[openclaw] configuring Telegram..." + + : "${TELEGRAM_BOT_TOKEN:?TELEGRAM_BOT_TOKEN is required when ENABLE_TELEGRAM=true}" + : "${TELEGRAM_ALLOW_FROM:?TELEGRAM_ALLOW_FROM is required when ENABLE_TELEGRAM=true}" + + openclaw config set channels.telegram.enabled true + openclaw config set channels.telegram.botToken "$TELEGRAM_BOT_TOKEN" + + openclaw config set channels.telegram.dmPolicy allowlist + openclaw config set channels.telegram.allowFrom "$TELEGRAM_ALLOW_FROM" --strict-json + openclaw config set channels.telegram.groupPolicy disabled + + echo "[openclaw] Telegram configured." + echo "[openclaw] Message your Telegram bot from the allowlisted Telegram user ID." +else + echo "[openclaw] Telegram disabled." +fi + +echo "[openclaw] starting gateway on port 8000..." + +exec openclaw gateway +SCRIPT + +chmod +x "$HOME/start-openclaw.sh" + +echo "Created $HOME/start-openclaw.sh" + +exec "$HOME/start-openclaw.sh" diff --git a/examples/openclaw/.saturn/saturn.json b/examples/openclaw/.saturn/saturn.json new file mode 100644 index 00000000..1b0d10fd --- /dev/null +++ b/examples/openclaw/.saturn/saturn.json @@ -0,0 +1,28 @@ +{ + "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": { + "instance_type": "large", + "command": "bash .saturn/bootstrap-openclaw.sh" + }, + "version": "2022.01.06" +} \ No newline at end of file diff --git a/examples/openclaw/README.md b/examples/openclaw/README.md new file mode 100644 index 00000000..44f7da05 --- /dev/null +++ b/examples/openclaw/README.md @@ -0,0 +1,54 @@ +# OpenClaw 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: + +[https://saturncloud.io/blog/how-to-deploy-openclaw-on-saturncloud/](https://saturncloud.io/blog/how-to-deploy-openclaw-on-saturncloud/) + +## Startup + +`deployment.command`: **`bash .saturn/bootstrap-openclaw.sh`** (runs from **`working_directory`**). + +## Environment variables + +Set values in the deployment **Details** (never commit secrets). + +### Required + +| 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`. | + +### AI provider + +Set **one** API key; that determines the provider. Optional model override: **`OPENCLAW_MODEL`**. + +| 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). | + +### WhatsApp (optional) + +When **`ENABLE_WHATSAPP=true`**: + +| Variable | Description | +|----------|-------------| +| `ENABLE_WHATSAPP` | `true` / `false`. | +| `WHATSAPP_ALLOW_FROM` | JSON array string, e.g. `["+1234567890"]`. | + +### Telegram (optional) + +When **`ENABLE_TELEGRAM=true`**: + +| Variable | Description | +|----------|-------------| +| `ENABLE_TELEGRAM` | `true` / `false`. | +| `TELEGRAM_BOT_TOKEN` | Bot token from BotFather. | +| `TELEGRAM_ALLOW_FROM` | JSON array string, e.g. `["123456789"]`. |