-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathstart-openclaw.sh
More file actions
228 lines (196 loc) · 8.57 KB
/
start-openclaw.sh
File metadata and controls
228 lines (196 loc) · 8.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#!/bin/bash
# Startup script for OpenClaw in Cloudflare Sandbox
# This script:
# 1. Runs openclaw onboard --non-interactive to configure from env vars
# 2. Patches config for features onboard doesn't cover (channels, gateway auth)
# 3. Starts the gateway
#
# NOTE: Persistence (backup/restore) is handled by the Sandbox SDK at the
# Worker level, not inside the container. The Worker calls createBackup()
# and restoreBackup() which use squashfs snapshots stored in R2.
# No rclone or R2 credentials are needed inside the container.
set -e
if pgrep -f "openclaw gateway" > /dev/null 2>&1; then
echo "OpenClaw gateway is already running, exiting."
exit 0
fi
CONFIG_DIR="/root/.openclaw"
CONFIG_FILE="$CONFIG_DIR/openclaw.json"
WORKSPACE_DIR="/root/clawd"
SKILLS_DIR="/root/clawd/skills"
echo "Config directory: $CONFIG_DIR"
mkdir -p "$CONFIG_DIR"
# ============================================================
# ONBOARD (only if no config exists yet)
# ============================================================
if [ ! -f "$CONFIG_FILE" ]; then
echo "No existing config found, running openclaw onboard..."
# Determine auth choice — openclaw onboard reads the actual key values
# from environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.)
# so we only pass --auth-choice, never the key itself, to avoid
# exposing secrets in process arguments visible via ps/proc.
AUTH_ARGS=""
if [ -n "$CLOUDFLARE_AI_GATEWAY_API_KEY" ] && [ -n "$CF_AI_GATEWAY_ACCOUNT_ID" ] && [ -n "$CF_AI_GATEWAY_GATEWAY_ID" ]; then
AUTH_ARGS="--auth-choice cloudflare-ai-gateway-api-key --cloudflare-ai-gateway-account-id $CF_AI_GATEWAY_ACCOUNT_ID --cloudflare-ai-gateway-gateway-id $CF_AI_GATEWAY_GATEWAY_ID"
elif [ -n "$ANTHROPIC_API_KEY" ]; then
AUTH_ARGS="--auth-choice apiKey"
elif [ -n "$OPENAI_API_KEY" ]; then
AUTH_ARGS="--auth-choice openai-api-key"
fi
openclaw onboard --non-interactive --accept-risk \
--mode local \
$AUTH_ARGS \
--gateway-port 18789 \
--gateway-bind lan \
--skip-channels \
--skip-skills \
--skip-health
echo "Onboard completed"
else
echo "Using existing config"
fi
# ============================================================
# PATCH CONFIG (channels, gateway auth, trusted proxies)
# ============================================================
# openclaw onboard handles provider/model config, but we need to patch in:
# - Channel config (Telegram, Discord, Slack)
# - Gateway token auth
# - Trusted proxies for sandbox networking
# - Base URL override for legacy AI Gateway path
node << 'EOFPATCH'
const fs = require('fs');
const configPath = '/root/.openclaw/openclaw.json';
console.log('Patching config at:', configPath);
let config = {};
try {
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
} catch (e) {
console.log('Starting with empty config');
}
config.gateway = config.gateway || {};
config.channels = config.channels || {};
// Gateway configuration
config.gateway.port = 18789;
config.gateway.mode = 'local';
config.gateway.trustedProxies = ['10.1.0.0'];
config.gateway.controlUi = config.gateway.controlUi || {};
config.gateway.controlUi.allowedOrigins = ['*'];
if (process.env.OPENCLAW_GATEWAY_TOKEN) {
config.gateway.auth = config.gateway.auth || {};
config.gateway.auth.token = process.env.OPENCLAW_GATEWAY_TOKEN;
}
// Allow any origin to connect to the gateway control UI.
// The gateway runs inside a Cloudflare Container behind the Worker, which
// proxies requests from the public workers.dev domain. Without this,
// openclaw >= 2026.2.26 rejects WebSocket connections because the browser's
// origin (https://....workers.dev) doesn't match the gateway's localhost.
// Security is handled by CF Access + gateway token auth, not origin checks.
config.gateway.controlUi = config.gateway.controlUi || {};
config.gateway.controlUi.allowedOrigins = ['*'];
if (process.env.OPENCLAW_DEV_MODE === 'true') {
config.gateway.controlUi = config.gateway.controlUi || {};
config.gateway.controlUi.allowInsecureAuth = true;
}
// Legacy AI Gateway base URL override:
// ANTHROPIC_BASE_URL is picked up natively by the Anthropic SDK,
// so we don't need to patch the provider config. Writing a provider
// entry without a models array breaks OpenClaw's config validation.
// AI Gateway model override (CF_AI_GATEWAY_MODEL=provider/model-id)
// Adds a provider entry for any AI Gateway provider and sets it as default model.
// Examples:
// workers-ai/@cf/meta/llama-3.3-70b-instruct-fp8-fast
// openai/gpt-4o
// anthropic/claude-sonnet-4-5
if (process.env.CF_AI_GATEWAY_MODEL) {
const raw = process.env.CF_AI_GATEWAY_MODEL;
const slashIdx = raw.indexOf('/');
const gwProvider = raw.substring(0, slashIdx);
const modelId = raw.substring(slashIdx + 1);
const accountId = process.env.CF_AI_GATEWAY_ACCOUNT_ID;
const gatewayId = process.env.CF_AI_GATEWAY_GATEWAY_ID;
const apiKey = process.env.CLOUDFLARE_AI_GATEWAY_API_KEY;
let baseUrl;
if (accountId && gatewayId) {
baseUrl = 'https://gateway.ai.cloudflare.com/v1/' + accountId + '/' + gatewayId + '/' + gwProvider;
if (gwProvider === 'workers-ai') baseUrl += '/v1';
} else if (gwProvider === 'workers-ai' && process.env.CF_ACCOUNT_ID) {
baseUrl = 'https://api.cloudflare.com/client/v4/accounts/' + process.env.CF_ACCOUNT_ID + '/ai/v1';
}
if (baseUrl && apiKey) {
const api = gwProvider === 'anthropic' ? 'anthropic-messages' : 'openai-completions';
const providerName = 'cf-ai-gw-' + gwProvider;
config.models = config.models || {};
config.models.providers = config.models.providers || {};
config.models.providers[providerName] = {
baseUrl: baseUrl,
apiKey: apiKey,
api: api,
models: [{ id: modelId, name: modelId, contextWindow: 131072, maxTokens: 8192 }],
};
config.agents = config.agents || {};
config.agents.defaults = config.agents.defaults || {};
config.agents.defaults.model = { primary: providerName + '/' + modelId };
console.log('AI Gateway model override: provider=' + providerName + ' model=' + modelId + ' via ' + baseUrl);
} else {
console.warn('CF_AI_GATEWAY_MODEL set but missing required config (account ID, gateway ID, or API key)');
}
}
// Telegram configuration
// Overwrite entire channel object to drop stale keys from old R2 backups
// that would fail OpenClaw's strict config validation (see #47)
if (process.env.TELEGRAM_BOT_TOKEN) {
const dmPolicy = process.env.TELEGRAM_DM_POLICY || 'pairing';
config.channels.telegram = {
botToken: process.env.TELEGRAM_BOT_TOKEN,
enabled: true,
dmPolicy: dmPolicy,
};
if (process.env.TELEGRAM_DM_ALLOW_FROM) {
config.channels.telegram.allowFrom = process.env.TELEGRAM_DM_ALLOW_FROM.split(',');
} else if (dmPolicy === 'open') {
config.channels.telegram.allowFrom = ['*'];
}
}
// Discord configuration
// Discord uses a nested dm object: dm.policy, dm.allowFrom (per DiscordDmConfig)
if (process.env.DISCORD_BOT_TOKEN) {
const dmPolicy = process.env.DISCORD_DM_POLICY || 'pairing';
const dm = { policy: dmPolicy };
if (dmPolicy === 'open') {
dm.allowFrom = ['*'];
}
config.channels.discord = {
token: process.env.DISCORD_BOT_TOKEN,
enabled: true,
dm: dm,
};
}
// Slack configuration
if (process.env.SLACK_BOT_TOKEN && process.env.SLACK_APP_TOKEN) {
config.channels.slack = {
botToken: process.env.SLACK_BOT_TOKEN,
appToken: process.env.SLACK_APP_TOKEN,
enabled: true,
};
}
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
console.log('Configuration patched successfully');
EOFPATCH
# ============================================================
# START GATEWAY
# ============================================================
echo "Starting OpenClaw Gateway..."
echo "Gateway will be available on port 18789"
rm -f /tmp/openclaw-gateway.lock 2>/dev/null || true
rm -f "$CONFIG_DIR/gateway.lock" 2>/dev/null || true
echo "Dev mode: ${OPENCLAW_DEV_MODE:-false}"
# Gateway token (if set) is already written to openclaw.json by the config
# patch above (gateway.auth.token). We deliberately avoid passing --token on
# the command line because CLI arguments are visible to all processes in the
# container via ps/proc.
if [ -n "$OPENCLAW_GATEWAY_TOKEN" ]; then
echo "Starting gateway with token auth..."
else
echo "Starting gateway with device pairing (no token)..."
fi
exec openclaw gateway --port 18789 --verbose --allow-unconfigured --bind lan