-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathstart-openclaw.sh
More file actions
329 lines (284 loc) · 12.3 KB
/
start-openclaw.sh
File metadata and controls
329 lines (284 loc) · 12.3 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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
#!/bin/bash
# Startup script for OpenClaw in Cloudflare Sandbox
# This script:
# 1. Restores config/workspace/skills from R2 via rclone (if configured)
# 2. Runs openclaw onboard --non-interactive to configure from env vars
# 3. Patches config for features onboard doesn't cover (channels, gateway auth)
# 4. Starts a background sync loop (rclone, watches for file changes)
# 5. Starts the gateway
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"
RCLONE_CONF="/root/.config/rclone/rclone.conf"
LAST_SYNC_FILE="/tmp/.last-sync"
echo "Config directory: $CONFIG_DIR"
mkdir -p "$CONFIG_DIR"
# ============================================================
# RCLONE SETUP
# ============================================================
r2_configured() {
[ -n "$R2_ACCESS_KEY_ID" ] && [ -n "$R2_SECRET_ACCESS_KEY" ] && [ -n "$CF_ACCOUNT_ID" ]
}
R2_BUCKET="${R2_BUCKET_NAME:-moltbot-data}"
setup_rclone() {
mkdir -p "$(dirname "$RCLONE_CONF")"
cat > "$RCLONE_CONF" << EOF
[r2]
type = s3
provider = Cloudflare
access_key_id = $R2_ACCESS_KEY_ID
secret_access_key = $R2_SECRET_ACCESS_KEY
endpoint = https://${CF_ACCOUNT_ID}.r2.cloudflarestorage.com
acl = private
no_check_bucket = true
EOF
touch /tmp/.rclone-configured
echo "Rclone configured for bucket: $R2_BUCKET"
}
RCLONE_FLAGS="--transfers=16 --fast-list --s3-no-check-bucket"
# ============================================================
# RESTORE FROM R2
# ============================================================
if r2_configured; then
setup_rclone
echo "Checking R2 for existing backup..."
# Check if R2 has an openclaw config backup
if rclone ls "r2:${R2_BUCKET}/openclaw/openclaw.json" $RCLONE_FLAGS 2>/dev/null | grep -q openclaw.json; then
echo "Restoring config from R2..."
rclone copy "r2:${R2_BUCKET}/openclaw/" "$CONFIG_DIR/" $RCLONE_FLAGS -v 2>&1 || echo "WARNING: config restore failed with exit code $?"
echo "Config restored"
elif rclone ls "r2:${R2_BUCKET}/clawdbot/clawdbot.json" $RCLONE_FLAGS 2>/dev/null | grep -q clawdbot.json; then
echo "Restoring from legacy R2 backup..."
rclone copy "r2:${R2_BUCKET}/clawdbot/" "$CONFIG_DIR/" $RCLONE_FLAGS -v 2>&1 || echo "WARNING: legacy config restore failed with exit code $?"
if [ -f "$CONFIG_DIR/clawdbot.json" ] && [ ! -f "$CONFIG_FILE" ]; then
mv "$CONFIG_DIR/clawdbot.json" "$CONFIG_FILE"
fi
echo "Legacy config restored and migrated"
else
echo "No backup found in R2, starting fresh"
fi
# Restore workspace
REMOTE_WS_COUNT=$(rclone ls "r2:${R2_BUCKET}/workspace/" $RCLONE_FLAGS 2>/dev/null | wc -l)
if [ "$REMOTE_WS_COUNT" -gt 0 ]; then
echo "Restoring workspace from R2 ($REMOTE_WS_COUNT files)..."
mkdir -p "$WORKSPACE_DIR"
rclone copy "r2:${R2_BUCKET}/workspace/" "$WORKSPACE_DIR/" $RCLONE_FLAGS -v 2>&1 || echo "WARNING: workspace restore failed with exit code $?"
echo "Workspace restored"
fi
# Restore skills
REMOTE_SK_COUNT=$(rclone ls "r2:${R2_BUCKET}/skills/" $RCLONE_FLAGS 2>/dev/null | wc -l)
if [ "$REMOTE_SK_COUNT" -gt 0 ]; then
echo "Restoring skills from R2 ($REMOTE_SK_COUNT files)..."
mkdir -p "$SKILLS_DIR"
rclone copy "r2:${R2_BUCKET}/skills/" "$SKILLS_DIR/" $RCLONE_FLAGS -v 2>&1 || echo "WARNING: skills restore failed with exit code $?"
echo "Skills restored"
fi
else
echo "R2 not configured, starting fresh"
fi
# ============================================================
# ONBOARD (only if no config exists yet)
# ============================================================
if [ ! -f "$CONFIG_FILE" ]; then
echo "No existing config found, running openclaw onboard..."
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 \
--cloudflare-ai-gateway-api-key $CLOUDFLARE_AI_GATEWAY_API_KEY"
elif [ -n "$ANTHROPIC_API_KEY" ]; then
AUTH_ARGS="--auth-choice apiKey --anthropic-api-key $ANTHROPIC_API_KEY"
elif [ -n "$OPENAI_API_KEY" ]; then
AUTH_ARGS="--auth-choice openai-api-key --openai-api-key $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'];
if (process.env.OPENCLAW_GATEWAY_TOKEN) {
config.gateway.auth = config.gateway.auth || {};
config.gateway.auth.token = process.env.OPENCLAW_GATEWAY_TOKEN;
}
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
# ============================================================
# BACKGROUND SYNC LOOP
# ============================================================
if r2_configured; then
echo "Starting background R2 sync loop..."
(
MARKER=/tmp/.last-sync-marker
LOGFILE=/tmp/r2-sync.log
touch "$MARKER"
while true; do
sleep 30
CHANGED=/tmp/.changed-files
{
find "$CONFIG_DIR" -newer "$MARKER" -type f -printf '%P\n' 2>/dev/null
find "$WORKSPACE_DIR" -newer "$MARKER" \
-not -path '*/node_modules/*' \
-not -path '*/.git/*' \
-type f -printf '%P\n' 2>/dev/null
} > "$CHANGED"
COUNT=$(wc -l < "$CHANGED" 2>/dev/null || echo 0)
if [ "$COUNT" -gt 0 ]; then
echo "[sync] Uploading changes ($COUNT files) at $(date)" >> "$LOGFILE"
rclone sync "$CONFIG_DIR/" "r2:${R2_BUCKET}/openclaw/" \
$RCLONE_FLAGS --exclude='*.lock' --exclude='*.log' --exclude='*.tmp' --exclude='.git/**' 2>> "$LOGFILE"
if [ -d "$WORKSPACE_DIR" ]; then
rclone sync "$WORKSPACE_DIR/" "r2:${R2_BUCKET}/workspace/" \
$RCLONE_FLAGS --exclude='skills/**' --exclude='.git/**' --exclude='node_modules/**' 2>> "$LOGFILE"
fi
if [ -d "$SKILLS_DIR" ]; then
rclone sync "$SKILLS_DIR/" "r2:${R2_BUCKET}/skills/" \
$RCLONE_FLAGS 2>> "$LOGFILE"
fi
date -Iseconds > "$LAST_SYNC_FILE"
touch "$MARKER"
echo "[sync] Complete at $(date)" >> "$LOGFILE"
fi
done
) &
echo "Background sync loop started (PID: $!)"
fi
# ============================================================
# 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}"
if [ -n "$OPENCLAW_GATEWAY_TOKEN" ]; then
echo "Starting gateway with token auth..."
exec openclaw gateway --port 18789 --verbose --allow-unconfigured --bind lan --token "$OPENCLAW_GATEWAY_TOKEN"
else
echo "Starting gateway with device pairing (no token)..."
exec openclaw gateway --port 18789 --verbose --allow-unconfigured --bind lan
fi