From 23eb82662db2fc8612986c2cce5e2ae182d2b361 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Mon, 13 Apr 2026 21:51:23 -0700 Subject: [PATCH 1/5] enhance bootstrap script: validate required secrets and update model configurations --- terraform/digitalOcean/bootstrap.sh | 45 ++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/terraform/digitalOcean/bootstrap.sh b/terraform/digitalOcean/bootstrap.sh index e5613b9..042ddcd 100644 --- a/terraform/digitalOcean/bootstrap.sh +++ b/terraform/digitalOcean/bootstrap.sh @@ -1,6 +1,16 @@ #!/bin/bash set -e +# ── Validate required secrets ──────────────────────────────── +if [[ -z "${telegram_bot_token}" || "${telegram_bot_token}" == "" ]]; then + echo "ERROR: telegram_bot_token is empty. Ensure 'direnv allow' is run before 'terraform apply'" + exit 1 +fi +if [[ -z "${openclaw_gateway_token}" || "${openclaw_gateway_token}" == "" ]]; then + echo "ERROR: openclaw_gateway_token is empty. Ensure 'direnv allow' is run before 'terraform apply'" + exit 1 +fi + # ── Set required environment variables for cloud-init context ─ export HOME=/root export USER=root @@ -50,20 +60,29 @@ cat > /root/.openclaw/openclaw.json << JSONEOF "agents": { "defaults": { "model": { - "primary": "openrouter/meta-llama/llama-3.3-70b-instruct:free", + "primary": "openrouter/deepseek/deepseek-v3.2", "fallbacks": [ + "openrouter/meta-llama/llama-3.3-70b-instruct:free", "openrouter/auto" ] }, "models": { - "openrouter/meta-llama/llama-3.3-70b-instruct:free": {"alias": "llama"}, - "openrouter/cognitivecomputations/dolphin-mistral-24b-venice-edition:free": {"alias": "uncensored"}, - "openrouter/google/gemma-4-31b-it:free": {"alias": "gemma"}, - "openrouter/nousresearch/hermes-3-llama-3.1-405b:free": {"alias": "hermes"}, - "openrouter/nvidia/nemotron-3-super-120b-a12b:free": {"alias": "nemotron"}, - "openrouter/openai/gpt-oss-120b:free": {"alias": "gpt"}, - "openrouter/qwen/qwen3-coder:free": {"alias": "coder"}, - "openrouter/auto": {"alias": "auto"} + "anthropic/claude-opus-4-6": {"alias": "opus"}, + "anthropic/claude-sonnet-4-6": {"alias": "sonnet"}, + "anthropic/claude-haiku-4-5-20251001": {"alias": "haiku"}, + "openai/gpt-5.4": {"alias": "gpt5"}, + "openai/gpt-4o": {"alias": "gpt4o"}, + "openai/gpt-4o-mini": {"alias": "mini"}, + "google/gemini-2.5-pro": {"alias": "gemini-pro"}, + "google/gemini-2.5-flash": {"alias": "flash"}, + "deepseek/deepseek-v3.2": {"alias": "deepseek"}, + "deepseek/deepseek-r1": {"alias": "r1"}, + "mistralai/devstral-small": {"alias": "devstral"}, + "meta-llama/llama-3.3-70b-instruct:free": {"alias": "llama"}, + "nvidia/nemotron-3-super-120b-a12b:free": {"alias": "nemotron"}, + "qwen/qwen3-coder:free": {"alias": "coder"}, + "cognitivecomputations/dolphin-mistral-24b-venice-edition:free": {"alias": "uncensored"}, + "openrouter/auto": {"alias": "auto"} }, "compaction": { "mode": "safeguard", @@ -155,6 +174,10 @@ openclaw onboard --non-interactive --accept-risk --install-daemon # ── 13. Restore config (onboard may have modified it) ──────── write_config +# ── 13.5. Clear agent cache to ensure fresh model list ─────── +# This ensures Telegram plugin loads all available models (not just free ones) +rm -rf /root/.openclaw/agents + # ── 14. Sync gateway token to systemd unit ─────────────────── # This bakes the correct OPENCLAW_GATEWAY_TOKEN into the service file, # preventing the "gateway token mismatch" loop on restart. @@ -166,7 +189,7 @@ systemctl --user restart openclaw-gateway.service # ── 16. Auto-approve Telegram Native Approvals scope ───────── # After the gateway starts, the Telegram plugin requests an upgrade from -# operator.read → operator.approvals. Without approval, privileged commands +# operator.read -> operator.approvals. Without approval, privileged commands # like /model return "You are not authorized". We wait for the pending # request to appear in devices/pending.json, approve it, then restart. echo "Waiting for Telegram Native Approvals pairing request..." @@ -223,4 +246,4 @@ PYEOF systemctl --user restart openclaw-gateway.service echo "Gateway restarted with operator.approvals approved." -echo "OpenClaw setup complete!" +echo "OpenClaw setup complete!" \ No newline at end of file From 16aaba5f8550dd01ee6efcd973383bd1def3ee74 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Fri, 17 Apr 2026 18:34:53 -0700 Subject: [PATCH 2/5] add Slack support: include app and bot tokens in environment and configuration --- .claude/scheduled_tasks.lock | 1 + terraform/digitalOcean/.envrc | 2 + terraform/digitalOcean/bootstrap.sh | 231 +++++++++--------- terraform/digitalOcean/main.tf | 2 + .../digitalOcean/terraform.tfvars.example | 14 ++ terraform/digitalOcean/variables.tf | 12 + 6 files changed, 147 insertions(+), 115 deletions(-) create mode 100644 .claude/scheduled_tasks.lock diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock new file mode 100644 index 0000000..3f365bc --- /dev/null +++ b/.claude/scheduled_tasks.lock @@ -0,0 +1 @@ +{"sessionId":"1928f2b1-2a69-434f-8236-20fed6157487","pid":85121,"acquiredAt":1776407914180} \ No newline at end of file diff --git a/terraform/digitalOcean/.envrc b/terraform/digitalOcean/.envrc index 9d352ad..5707998 100644 --- a/terraform/digitalOcean/.envrc +++ b/terraform/digitalOcean/.envrc @@ -8,3 +8,5 @@ export TF_VAR_telegram_bot_token="$TELEGRAM_BOT_TOKEN" export TF_VAR_openclaw_gateway_token="$OPENCLAW_GATEWAY_TOKEN" export TF_VAR_brave_api_key="${BRAVE_API_KEY:-}" export TF_VAR_telegram_owner_id="${TELEGRAM_OWNER_ID:-}" +export TF_VAR_slack_app_token="${SLACK_APP_TOKEN:-}" +export TF_VAR_slack_bot_token="${SLACK_BOT_TOKEN:-}" diff --git a/terraform/digitalOcean/bootstrap.sh b/terraform/digitalOcean/bootstrap.sh index 042ddcd..3930317 100644 --- a/terraform/digitalOcean/bootstrap.sh +++ b/terraform/digitalOcean/bootstrap.sh @@ -1,46 +1,29 @@ #!/bin/bash set -e -# ── Validate required secrets ──────────────────────────────── -if [[ -z "${telegram_bot_token}" || "${telegram_bot_token}" == "" ]]; then - echo "ERROR: telegram_bot_token is empty. Ensure 'direnv allow' is run before 'terraform apply'" - exit 1 -fi -if [[ -z "${openclaw_gateway_token}" || "${openclaw_gateway_token}" == "" ]]; then - echo "ERROR: openclaw_gateway_token is empty. Ensure 'direnv allow' is run before 'terraform apply'" - exit 1 -fi - -# ── Set required environment variables for cloud-init context ─ export HOME=/root export USER=root export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin -# ── 1. Update system packages ──────────────────────────────── +# ── 1. System setup ────────────────────────────────────────── apt-get update -y -# ── 2. Add swap to prevent OOM during npm install ──────────── fallocate -l ${swap_size} /swapfile -chmod 600 /swapfile -mkswap /swapfile -swapon /swapfile +chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile echo '/swapfile none swap sw 0 0' >> /etc/fstab -# ── 3. Install Node.js 24 ──────────────────────────────────── curl -fsSL https://deb.nodesource.com/setup_24.x | bash - apt-get install -y nodejs -# ── 4. Install OpenClaw (skip onboarding, done later) ──────── +# ── 2. Install OpenClaw ────────────────────────────────────── export OPENCLAW_ONBOARD_NON_INTERACTIVE=1 export OPENCLAW_INSTALL_METHOD=npm -curl -fsSL https://openclaw.bot/install.sh | bash -s -- \ - --install-method npm --no-onboard +curl -fsSL https://openclaw.bot/install.sh | bash -s -- --install-method npm --no-onboard -# ── 5. Install missing Telegram dependencies ───────────────── -# OpenClaw's npm install doesn't bundle these; gateway crashes without them -npm install -g grammy @grammyjs/runner @grammyjs/transformer-throttler +npm install -g grammy @grammyjs/runner @grammyjs/transformer-throttler \ + @slack/bolt @slack/socket-mode @slack/web-api -# ── 6. Write OpenClaw config file ──────────────────────────── +# ── 3. Write config ────────────────────────────────────────── mkdir -p /root/.openclaw write_config() { @@ -48,76 +31,63 @@ cat > /root/.openclaw/openclaw.json << JSONEOF { "gateway": { "bind": "lan", - "auth": { - "mode": "token", - "token": "${openclaw_gateway_token}" - }, + "auth": { "mode": "token", "token": "${openclaw_gateway_token}" }, "mode": "local", - "remote": { - "token": "${openclaw_gateway_token}" - } + "remote": { "token": "${openclaw_gateway_token}" } }, "agents": { "defaults": { "model": { - "primary": "openrouter/deepseek/deepseek-v3.2", + "primary": "openrouter/openai/gpt-4o-mini", "fallbacks": [ + "openrouter/anthropic/claude-haiku-4.5", + "openrouter/deepseek/deepseek-v3.2", "openrouter/meta-llama/llama-3.3-70b-instruct:free", "openrouter/auto" ] }, "models": { - "anthropic/claude-opus-4-6": {"alias": "opus"}, - "anthropic/claude-sonnet-4-6": {"alias": "sonnet"}, - "anthropic/claude-haiku-4-5-20251001": {"alias": "haiku"}, - "openai/gpt-5.4": {"alias": "gpt5"}, - "openai/gpt-4o": {"alias": "gpt4o"}, - "openai/gpt-4o-mini": {"alias": "mini"}, - "google/gemini-2.5-pro": {"alias": "gemini-pro"}, - "google/gemini-2.5-flash": {"alias": "flash"}, - "deepseek/deepseek-v3.2": {"alias": "deepseek"}, - "deepseek/deepseek-r1": {"alias": "r1"}, - "mistralai/devstral-small": {"alias": "devstral"}, - "meta-llama/llama-3.3-70b-instruct:free": {"alias": "llama"}, - "nvidia/nemotron-3-super-120b-a12b:free": {"alias": "nemotron"}, - "qwen/qwen3-coder:free": {"alias": "coder"}, - "cognitivecomputations/dolphin-mistral-24b-venice-edition:free": {"alias": "uncensored"}, - "openrouter/auto": {"alias": "auto"} + "openrouter/anthropic/claude-opus-4.6": {"alias": "opus"}, + "openrouter/anthropic/claude-sonnet-4.6": {"alias": "sonnet"}, + "openrouter/anthropic/claude-haiku-4.5": {"alias": "haiku"}, + "openrouter/openai/gpt-5.4": {"alias": "gpt5"}, + "openrouter/openai/gpt-4o": {"alias": "gpt4o"}, + "openrouter/openai/gpt-4o-mini": {"alias": "mini"}, + "openrouter/google/gemini-2.5-pro": {"alias": "gemini-pro"}, + "openrouter/google/gemini-2.5-flash": {"alias": "flash"}, + "openrouter/deepseek/deepseek-v3.2": {"alias": "deepseek"}, + "openrouter/deepseek/deepseek-r1": {"alias": "r1"}, + "openrouter/mistralai/devstral-small": {"alias": "devstral"}, + "openrouter/meta-llama/llama-3.3-70b-instruct:free": {"alias": "llama"}, + "openrouter/nvidia/nemotron-3-super-120b-a12b:free": {"alias": "nemotron"}, + "openrouter/qwen/qwen3-coder:free": {"alias": "coder"}, + "openrouter/cognitivecomputations/dolphin-mistral-24b-venice-edition:free": {"alias": "uncensored"}, + "openrouter/auto": {"alias": "auto"} }, - "compaction": { - "mode": "safeguard", - "reserveTokensFloor": 20000 - } + "compaction": { "mode": "safeguard", "reserveTokensFloor": 4000 } } }, "tools": { "web": { - "search": { - "enabled": true, - "provider": "brave" - }, - "fetch": { - "enabled": false - } + "search": { "enabled": true, "provider": "brave" }, + "fetch": { "enabled": false } }, "deny": ["browser"] }, "plugins": { "load": { "paths": [ - "/usr/lib/node_modules/openclaw/dist/extensions/telegram" + "/usr/lib/node_modules/openclaw/dist/extensions/telegram"%{if slack_app_token != "" && slack_bot_token != ""}, + "/usr/lib/node_modules/openclaw/dist/extensions/slack"%{endif} ] }, "entries": { - "telegram": { "enabled": true }, + "telegram": { "enabled": true }%{if slack_app_token != "" && slack_bot_token != ""}, + "slack": { "enabled": true }%{endif}, "openrouter": { "enabled": true }, "brave": { "enabled": true, - "config": { - "webSearch": { - "apiKey": "${brave_api_key}" - } - } + "config": { "webSearch": { "apiKey": "${brave_api_key}" } } } } }, @@ -128,10 +98,23 @@ cat > /root/.openclaw/openclaw.json << JSONEOF "default": { "botToken": "${telegram_bot_token}", "dmPolicy": "open", - "groupPolicy": "open"${telegram_owner_id != "" ? ",\n \"allowFrom\": [\"${telegram_owner_id}\"]" : ""} + "groupPolicy": "open"%{if telegram_owner_id != ""}, + "allowFrom": ["${telegram_owner_id}"]%{endif} } } - } + }%{if slack_app_token != "" && slack_bot_token != ""}, + "slack": { + "enabled": true, + "accounts": { + "default": { + "appToken": "${slack_app_token}", + "botToken": "${slack_bot_token}", + "mode": "socket", + "dmPolicy": "open", + "groupPolicy": "open" + } + } + }%{endif} } } JSONEOF @@ -139,61 +122,89 @@ JSONEOF write_config -# ── 7. Write environment variables ─────────────────────────── +mkdir -p /root/.openclaw/workspace +if ! grep -q "ALWAYS_REPLY_IN_DM" /root/.openclaw/workspace/AGENTS.md 2>/dev/null; then +cat >> /root/.openclaw/workspace/AGENTS.md << 'AGENTSEOF' + +## Channel Output Rule (OpenClaw) + +- ALWAYS_REPLY_IN_DM: For any direct message on Telegram/Slack, always send at least one plain-text assistant message. +- Never end a DM turn with tool calls only, empty payload, or metadata-only output. +- If uncertain, send a brief fallback text: "I can help with that. Could you share a bit more detail?" +AGENTSEOF +fi + +# ── 4. Write .env and export secrets ───────────────────────── cat > /root/.openclaw/.env << ENVEOF OPENROUTER_API_KEY=${openrouter_api_key} TELEGRAM_BOT_TOKEN=${telegram_bot_token} +SLACK_APP_TOKEN=${slack_app_token} +SLACK_BOT_TOKEN=${slack_bot_token} OPENCLAW_GATEWAY_TOKEN=${openclaw_gateway_token} BRAVE_API_KEY=${brave_api_key} OPENCLAW_ONBOARD_NON_INTERACTIVE=1 ENVEOF -# ── 8. Export env vars for subsequent commands ─────────────── export OPENROUTER_API_KEY=${openrouter_api_key} export TELEGRAM_BOT_TOKEN=${telegram_bot_token} +export SLACK_APP_TOKEN=${slack_app_token} +export SLACK_BOT_TOKEN=${slack_bot_token} export OPENCLAW_GATEWAY_TOKEN=${openclaw_gateway_token} export BRAVE_API_KEY=${brave_api_key} -# ── 9. Install Telegram plugin ─────────────────────────────── -openclaw plugins install @openclaw/telegram - -# ── 10. Auto-fix common config issues ──────────────────────── +# ── 5. Onboard ─────────────────────────────────────────────── openclaw doctor --fix || true -# ── 11. Enable systemd user services for root (required in cloud-init) ── -# cloud-init has no active login session; linger + XDG_RUNTIME_DIR are needed -# for systemctl --user to work. loginctl enable-linger root export XDG_RUNTIME_DIR=/run/user/0 mkdir -p "$XDG_RUNTIME_DIR" systemctl start user@0.service || true -# ── 12. Non-interactive onboard + install systemd daemon ───── -openclaw onboard --non-interactive --accept-risk --install-daemon +openclaw onboard --non-interactive --accept-risk --install-daemon || true -# ── 13. Restore config (onboard may have modified it) ──────── +# Restore config (onboard may have modified it) write_config -# ── 13.5. Clear agent cache to ensure fresh model list ─────── -# This ensures Telegram plugin loads all available models (not just free ones) -rm -rf /root/.openclaw/agents +# ── Write agent auth-profiles (OpenRouter key) ─────────────── +# The agent reads auth from auth-profiles.json, NOT from .env +mkdir -p /root/.openclaw/agents/main/agent +python3 << AUTHEOF +import json +env = {} +with open("/root/.openclaw/.env") as f: + for line in f: + line = line.strip() + if line and not line.startswith("#") and "=" in line: + k, v = line.split("=", 1) + env[k.strip()] = v.strip() +key = env.get("OPENROUTER_API_KEY", "") +if key: + path = "/root/.openclaw/agents/main/agent/auth-profiles.json" + with open(path, "w") as f: + json.dump({"openrouter": {"apiKey": key}}, f, indent=2) + print(f"auth-profiles.json created (key prefix: {key[:15]}...)") +else: + print("WARNING: OPENROUTER_API_KEY not found in .env!") +AUTHEOF -# ── 14. Sync gateway token to systemd unit ─────────────────── -# This bakes the correct OPENCLAW_GATEWAY_TOKEN into the service file, -# preventing the "gateway token mismatch" loop on restart. openclaw gateway install --force -# ── 15. Reload systemd and start gateway ───────────────────── +mkdir -p /root/.config/systemd/user/openclaw-gateway.service.d +cat > /root/.config/systemd/user/openclaw-gateway.service.d/override.conf << 'OVERRIDEEOF' +[Service] +TimeoutStartSec=180 +TimeoutStopSec=60 +RestartSec=5 +OVERRIDEEOF + systemctl --user daemon-reload systemctl --user restart openclaw-gateway.service -# ── 16. Auto-approve Telegram Native Approvals scope ───────── -# After the gateway starts, the Telegram plugin requests an upgrade from -# operator.read -> operator.approvals. Without approval, privileged commands -# like /model return "You are not authorized". We wait for the pending -# request to appear in devices/pending.json, approve it, then restart. -echo "Waiting for Telegram Native Approvals pairing request..." -sleep 35 +# ── 6. Auto-approve operator.approvals scope ───────────────── +# Telegram/Slack plugins request scope upgrade after gateway starts. +# Without approval, /model returns "You are not authorized". +echo "Waiting for approval requests..." +sleep 120 python3 << 'PYEOF' import json, sys, time @@ -201,7 +212,6 @@ import json, sys, time PENDING_FILE = "/root/.openclaw/devices/pending.json" PAIRED_FILE = "/root/.openclaw/devices/paired.json" -# Wait up to 60s for a pending request pending = {} for _ in range(12): try: @@ -214,36 +224,27 @@ for _ in range(12): time.sleep(5) if not pending: - print("No pending pairing requests found — skipping approval step") + print("No pending requests — skipping") sys.exit(0) -request = list(pending.values())[0] -device_id = request.get("deviceId") -print(f"Approving operator.approvals for device: {device_id}") - with open(PAIRED_FILE) as f: paired = json.load(f) -if device_id not in paired: - print(f"Device {device_id} not in paired.json — skipping") - sys.exit(0) - -device = paired[device_id] -for key in ("scopes", "approvedScopes"): - if "operator.approvals" not in device.get(key, []): - device.setdefault(key, []).append("operator.approvals") +for request in pending.values(): + device_id = request.get("deviceId") + if device_id not in paired: + continue + device = paired[device_id] + for key in ("scopes", "approvedScopes"): + if "operator.approvals" not in device.get(key, []): + device.setdefault(key, []).append("operator.approvals") + print(f"Approved: {device_id}") with open(PAIRED_FILE, "w") as f: json.dump(paired, f, indent=2) -print(f"paired.json updated — scopes: {device['scopes']}") - with open(PENDING_FILE, "w") as f: json.dump({}, f) -print("pending.json cleared") PYEOF -# Restart so gateway picks up the newly approved scope systemctl --user restart openclaw-gateway.service -echo "Gateway restarted with operator.approvals approved." - -echo "OpenClaw setup complete!" \ No newline at end of file +echo "OpenClaw setup complete!" diff --git a/terraform/digitalOcean/main.tf b/terraform/digitalOcean/main.tf index 8d25895..cc8c532 100644 --- a/terraform/digitalOcean/main.tf +++ b/terraform/digitalOcean/main.tf @@ -68,6 +68,8 @@ resource "digitalocean_droplet" "openclaw" { brave_api_key = var.brave_api_key swap_size = var.swap_size telegram_owner_id = var.telegram_owner_id + slack_app_token = var.slack_app_token + slack_bot_token = var.slack_bot_token }) } diff --git a/terraform/digitalOcean/terraform.tfvars.example b/terraform/digitalOcean/terraform.tfvars.example index 1321610..8610593 100644 --- a/terraform/digitalOcean/terraform.tfvars.example +++ b/terraform/digitalOcean/terraform.tfvars.example @@ -28,6 +28,20 @@ openrouter_api_key = "sk-or-v1-your-key-here" telegram_bot_token = "1234567890:your-token-here" openclaw_gateway_token = "your-secret-gateway-token" +# ── Telegram (Optional) ───────────────────────────────────── +# Your Telegram numeric user ID (get from @userinfobot) +# Grants /model and other privileged commands. Leave empty ("") to disable +telegram_owner_id = "" + +# ── Slack (Optional) ───────────────────────────────────────── +# Slack App-Level Token for Socket Mode (from Slack App settings) +# Must start with "xapp-". Leave empty ("") to disable Slack plugin +slack_app_token = "" + +# Slack Bot User OAuth Token (from Slack App settings) +# Must start with "xoxb-". Leave empty ("") to disable Slack plugin +slack_bot_token = "" + # ── Web Search ─────────────────────────────────────────────── # Brave Search API key (https://api.search.brave.com/) — free tier: 1000 req/month # Leave empty ("") to fall back to DuckDuckGo (no key needed) diff --git a/terraform/digitalOcean/variables.tf b/terraform/digitalOcean/variables.tf index 07a5b11..984aeef 100644 --- a/terraform/digitalOcean/variables.tf +++ b/terraform/digitalOcean/variables.tf @@ -59,4 +59,16 @@ variable "brave_api_key" { variable "telegram_owner_id" { description = "Your Telegram numeric user ID (get it from @userinfobot). Grants /model and other privileged commands." default = "" +} + +variable "slack_app_token" { + description = "Slack App-Level Token for Socket Mode connection (starts with 'xapp-')" + sensitive = true + default = "" +} + +variable "slack_bot_token" { + description = "Slack Bot User OAuth Token for sending messages (starts with 'xoxb-')" + sensitive = true + default = "" } \ No newline at end of file From 8d360d7ba9b4fff6a08652ef02ca5a0a09148b24 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Fri, 17 Apr 2026 20:47:57 -0700 Subject: [PATCH 3/5] add Slack configuration: include app-level and bot tokens in .env.example and update bootstrap script --- .env.example | 10 ++- terraform/digitalOcean/bootstrap.sh | 94 +++++++++++++++-------------- terraform/digitalOcean/variables.tf | 2 +- 3 files changed, 60 insertions(+), 46 deletions(-) diff --git a/.env.example b/.env.example index d488560..d29f69f 100644 --- a/.env.example +++ b/.env.example @@ -19,4 +19,12 @@ BRAVE_API_KEY=BSA-your-brave-api-key-here # Your Telegram numeric user ID (get from @userinfobot) # Grants privileged commands like /model in Telegram chat -TELEGRAM_OWNER_ID=your-telegram-user-id \ No newline at end of file +TELEGRAM_OWNER_ID=your-telegram-user-id + +# Slack App-Level Token for Socket Mode (from Slack App settings → Basic Information → App-Level Tokens) +# Must start with "xapp-". Leave empty to disable Slack plugin +SLACK_APP_TOKEN= + +# Slack Bot User OAuth Token (from Slack App settings → OAuth & Permissions) +# Must start with "xoxb-". Leave empty to disable Slack plugin +SLACK_BOT_TOKEN= \ No newline at end of file diff --git a/terraform/digitalOcean/bootstrap.sh b/terraform/digitalOcean/bootstrap.sh index 3930317..1a04f07 100644 --- a/terraform/digitalOcean/bootstrap.sh +++ b/terraform/digitalOcean/bootstrap.sh @@ -105,15 +105,11 @@ cat > /root/.openclaw/openclaw.json << JSONEOF }%{if slack_app_token != "" && slack_bot_token != ""}, "slack": { "enabled": true, - "accounts": { - "default": { - "appToken": "${slack_app_token}", - "botToken": "${slack_bot_token}", - "mode": "socket", - "dmPolicy": "open", - "groupPolicy": "open" - } - } + "mode": "socket", + "appToken": "${slack_app_token}", + "botToken": "${slack_bot_token}", + "dmPolicy": "open", + "groupPolicy": "open" }%{endif} } } @@ -165,26 +161,24 @@ openclaw onboard --non-interactive --accept-risk --install-daemon || true # Restore config (onboard may have modified it) write_config +# ── Fix models.json baseUrl (onboard writes wrong /v1 instead of /api/v1) ── +# openclaw onboard generates models.json with baseUrl https://openrouter.ai/v1 +# which is the web UI, not the API. The correct endpoint is /api/v1. +MODELS_JSON=/root/.openclaw/agents/main/agent/models.json +if [ -f "$MODELS_JSON" ]; then + sed -i 's|https://openrouter.ai/v1|https://openrouter.ai/api/v1|g' "$MODELS_JSON" + echo "Fixed models.json baseUrl: /v1 -> /api/v1" +fi + # ── Write agent auth-profiles (OpenRouter key) ─────────────── # The agent reads auth from auth-profiles.json, NOT from .env mkdir -p /root/.openclaw/agents/main/agent -python3 << AUTHEOF -import json -env = {} -with open("/root/.openclaw/.env") as f: - for line in f: - line = line.strip() - if line and not line.startswith("#") and "=" in line: - k, v = line.split("=", 1) - env[k.strip()] = v.strip() -key = env.get("OPENROUTER_API_KEY", "") -if key: - path = "/root/.openclaw/agents/main/agent/auth-profiles.json" - with open(path, "w") as f: - json.dump({"openrouter": {"apiKey": key}}, f, indent=2) - print(f"auth-profiles.json created (key prefix: {key[:15]}...)") -else: - print("WARNING: OPENROUTER_API_KEY not found in .env!") +cat > /root/.openclaw/agents/main/agent/auth-profiles.json << AUTHEOF +{ + "openrouter": { + "apiKey": "${openrouter_api_key}" + } +} AUTHEOF openclaw gateway install --force @@ -223,27 +217,39 @@ for _ in range(12): pass time.sleep(5) -if not pending: - print("No pending requests — skipping") - sys.exit(0) - -with open(PAIRED_FILE) as f: - paired = json.load(f) +# Even if no pending requests, approve operator.approvals for all paired devices. +# The scope request may have been missed if pairing happened before the gateway started. +try: + with open(PAIRED_FILE) as f: + paired = json.load(f) +except Exception: + paired = {} for request in pending.values(): device_id = request.get("deviceId") - if device_id not in paired: - continue - device = paired[device_id] - for key in ("scopes", "approvedScopes"): - if "operator.approvals" not in device.get(key, []): - device.setdefault(key, []).append("operator.approvals") - print(f"Approved: {device_id}") - -with open(PAIRED_FILE, "w") as f: - json.dump(paired, f, indent=2) -with open(PENDING_FILE, "w") as f: - json.dump({}, f) + if device_id in paired: + for key in ("scopes", "approvedScopes"): + if "operator.approvals" not in paired[device_id].get(key, []): + paired[device_id].setdefault(key, []).append("operator.approvals") + print(f"Approved via pending: {device_id}") + +# Ensure all paired operator devices have operator.approvals regardless of pending state +for device_id, device in paired.items(): + if "operator" in device.get("roles", []): + for key in ("scopes", "approvedScopes"): + if "operator.approvals" not in device.get(key, []): + device.setdefault(key, []).append("operator.approvals") + print(f"Granted operator.approvals to {device_id[:16]}...") + +if paired: + with open(PAIRED_FILE, "w") as f: + json.dump(paired, f, indent=2) +if pending: + with open(PENDING_FILE, "w") as f: + json.dump({}, f) + +if not pending and not paired: + print("No paired devices found — skipping") PYEOF systemctl --user restart openclaw-gateway.service diff --git a/terraform/digitalOcean/variables.tf b/terraform/digitalOcean/variables.tf index 984aeef..9be24e5 100644 --- a/terraform/digitalOcean/variables.tf +++ b/terraform/digitalOcean/variables.tf @@ -71,4 +71,4 @@ variable "slack_bot_token" { description = "Slack Bot User OAuth Token for sending messages (starts with 'xoxb-')" sensitive = true default = "" -} \ No newline at end of file +} From 12e3a59f872fe6c0a1e004abeae9f4e9c950c3ca Mon Sep 17 00:00:00 2001 From: PCBZ Date: Fri, 17 Apr 2026 21:52:00 -0700 Subject: [PATCH 4/5] remove scheduled tasks lock file --- .claude/scheduled_tasks.lock | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .claude/scheduled_tasks.lock diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock deleted file mode 100644 index 3f365bc..0000000 --- a/.claude/scheduled_tasks.lock +++ /dev/null @@ -1 +0,0 @@ -{"sessionId":"1928f2b1-2a69-434f-8236-20fed6157487","pid":85121,"acquiredAt":1776407914180} \ No newline at end of file From 28f847e71d774f312ecbdd808b3045b45d53f292 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Fri, 17 Apr 2026 22:04:37 -0700 Subject: [PATCH 5/5] update README: enhance Slack support details and configuration instructions --- README.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 01fca3d..407569f 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,14 @@ [![OpenRouter](https://img.shields.io/badge/OpenRouter-Free%20Tier-ff6b35?logoColor=white)](https://openrouter.ai) [![OpenClaw](https://img.shields.io/badge/OpenClaw-2026-00e5cc?logoColor=white)](https://openclaw.bot) [![Telegram](https://img.shields.io/badge/Telegram-Bot-26a5e4?logo=telegram&logoColor=white)](https://telegram.org) +[![Slack](https://img.shields.io/badge/Slack-Bot-4a154b?logo=slack&logoColor=white)](https://slack.com) -One-command deployment of an [OpenClaw](https://openclaw.bot) AI agent as a Telegram bot on DigitalOcean. After `terraform apply`, the bot is fully operational with no manual SSH steps required. +One-command deployment of an [OpenClaw](https://openclaw.bot) AI agent on DigitalOcean with Telegram support and Slack support. After `terraform apply`, the bot is fully operational with no manual SSH steps required. ## Features - Telegram bot with DM and group chat support +- Slack bot support (Socket Mode) - Web search via Brave Search (falls back to DuckDuckGo) - 8 switchable free LLM models via `/model ` - Secrets managed via `.env` — never committed @@ -26,6 +28,8 @@ One-command deployment of an [OpenClaw](https://openclaw.bot) AI agent as a Tele - DigitalOcean account + API token - OpenRouter API key - Telegram bot token (from [@BotFather](https://t.me/BotFather)) +- Slack App-Level token (starts with `xapp-`) +- Slack Bot User OAuth token (starts with `xoxb-`) ## Setup @@ -44,6 +48,8 @@ Edit `.env` and fill in your values: | `OPENCLAW_GATEWAY_TOKEN` | Any strong random string | | `BRAVE_API_KEY` | From [api.search.brave.com](https://api.search.brave.com) — optional, falls back to DuckDuckGo | | `TELEGRAM_OWNER_ID` | Your Telegram user ID from [@userinfobot](https://t.me/userinfobot) — grants `/model` and other privileged commands | +| `SLACK_APP_TOKEN` | Slack App-Level token (starts with `xapp-`) | +| `SLACK_BOT_TOKEN` | Slack Bot User OAuth token (starts with `xoxb-`) | ### 2. Configure infrastructure @@ -90,17 +96,6 @@ Send a message to your bot on Telegram to confirm it's working. In Telegram, use `/model `: -| Alias | Model | -|---|---| -| `llama` | Llama 3.3 70B (default) | -| `gemma` | Gemma 4 31B | -| `hermes` | Hermes 3 Llama 405B | -| `nemotron` | Nemotron Super 120B | -| `gpt` | GPT-OSS 120B | -| `coder` | Qwen3 Coder | -| `uncensored` | Dolphin Mistral 24B | -| `auto` | OpenRouter auto-select | - All models are free tier on OpenRouter (rate limits apply). ## Security Notes