-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathopenclaw-install.sh
More file actions
677 lines (582 loc) · 25.3 KB
/
openclaw-install.sh
File metadata and controls
677 lines (582 loc) · 25.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
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
#!/usr/bin/env bash
# =============================================================================
# OpenClaw Install Script
#
# Can be run two ways:
# 1. Pushed into an LXC by openclaw.sh (templates pre-staged in /tmp)
# 2. Directly on any Linux machine via curl|bash:
# bash -c "$(curl -fsSL setupClaw.ivantsov.tech)"
#
# Handles: user creation, dependencies, OpenClaw install, security hardening,
# memory plugin, Tailscale, backups, git-tracked config, validation
# =============================================================================
set -euo pipefail
# -- Remote repo base URL (for standalone curl|bash mode) ----------------------
REPO_RAW="${REPO_RAW:-https://raw.githubusercontent.com/Exploitacious/OpenClaw/refs/heads/master}"
# -- Logging (all output also goes to log file for debugging) ------------------
LOGFILE="/tmp/openclaw-install.log"
exec > >(tee -a "$LOGFILE") 2>&1
# -- Error trap ----------------------------------------------------------------
error_handler() {
local EXIT_CODE=$?
local LINE_NO=$1
echo ""
echo -e " \e[31m\xE2\x9C\x98 Install failed at line ${LINE_NO} (exit code: ${EXIT_CODE})\e[0m"
echo -e " \e[33mFull log available at: ${LOGFILE}\e[0m"
echo -e " \e[33mYou can re-run this script after fixing the issue.\e[0m"
}
trap 'error_handler $LINENO' ERR
# -- Colors & Formatting -------------------------------------------------------
GN="\e[32m"; RD="\e[31m"; BL="\e[36m"; YW="\e[33m"; CL="\e[0m"
CM="${GN}\xE2\x9C\x94${CL}"; CROSS="${RD}\xE2\x9C\x98${CL}"
msg_ok() { printf " ${CM} ${GN}%s${CL}\n" "$1"; }
msg_error() { printf " ${CROSS} ${RD}%s${CL}\n" "$1"; }
msg_info() { printf " ${BL}%s${CL}\n" "$1"; }
msg_warn() { printf " ${YW}%s${CL}\n" "$1"; }
msg_step() { printf "\n ${GN}>>>${CL} %s\n" "$1"; }
msg_dim() { printf " ${DM}%s${CL}\n" "$1"; }
CLAW_USER="claw"
CLAW_HOME="/home/${CLAW_USER}"
# -- Resolve a file: use local /tmp copy first, otherwise fetch from GitHub ----
resolve_template() {
local TPL_NAME="$1"
local DEST="/tmp/${TPL_NAME}"
# Already staged (pushed by openclaw.sh or previous run)
if [[ -f "$DEST" ]]; then
return 0
fi
# Try fetching from GitHub
msg_info "Fetching ${TPL_NAME} from GitHub..."
if curl -fsSL "${REPO_RAW}/templates/${TPL_NAME}" -o "$DEST" 2>/dev/null; then
msg_ok "Downloaded ${TPL_NAME}"
return 0
else
msg_warn "Could not download ${TPL_NAME} -- will use built-in defaults"
return 1
fi
}
# -- Ensure XDG_RUNTIME_DIR exists for systemd user services -------------------
ensure_user_runtime_dir() {
local UID_VAL
UID_VAL=$(id -u "$CLAW_USER")
local RUNTIME_DIR="/run/user/${UID_VAL}"
if [[ ! -d "$RUNTIME_DIR" ]]; then
mkdir -p "$RUNTIME_DIR"
chown "${CLAW_USER}:${CLAW_USER}" "$RUNTIME_DIR"
chmod 700 "$RUNTIME_DIR"
fi
}
# =============================================================================
# Step 1: System Update & Base Dependencies
# =============================================================================
step_system_setup() {
msg_step "Step 1/8: System update & base dependencies"
export DEBIAN_FRONTEND=noninteractive
# Fix locale first (prevents "Wide character" perl warnings in Proxmox)
# Export C.UTF-8 first to suppress bash locale warnings during setup
export LANG=C.UTF-8
export LC_ALL=C.UTF-8
msg_info "Setting locale..."
apt-get update -qq >/dev/null 2>&1
apt-get install -y -qq locales >/dev/null 2>&1
sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen 2>/dev/null || true
locale-gen en_US.UTF-8 >/dev/null 2>&1 || true
update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 >/dev/null 2>&1 || true
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
msg_ok "Locale set to en_US.UTF-8"
msg_info "Updating package lists..."
apt-get update -qq >/dev/null 2>&1
msg_ok "Package lists updated"
msg_info "Upgrading system packages..."
apt-get upgrade -y -qq >/dev/null 2>&1
msg_ok "System packages upgraded"
msg_info "Installing base dependencies..."
apt-get install -y -qq \
build-essential \
python3 \
git \
curl \
wget \
sudo \
jq \
unzip \
ca-certificates \
gnupg \
lsb-release \
cron \
procps \
openssl \
net-tools \
xxd \
>/dev/null 2>&1
msg_ok "Base dependencies installed"
}
# =============================================================================
# Step 2: Create claw User
# =============================================================================
step_create_user() {
msg_step "Step 2/8: Creating '${CLAW_USER}' user"
if id "$CLAW_USER" &>/dev/null; then
msg_ok "User '${CLAW_USER}' already exists"
else
adduser --disabled-password --gecos "OpenClaw Agent" "$CLAW_USER" >/dev/null 2>&1
msg_ok "User '${CLAW_USER}' created"
fi
# Grant sudo (passwordless for automation)
usermod -aG sudo "$CLAW_USER"
echo "${CLAW_USER} ALL=(ALL) NOPASSWD: ALL" > "/etc/sudoers.d/${CLAW_USER}"
chmod 440 "/etc/sudoers.d/${CLAW_USER}"
msg_ok "Sudo privileges granted"
# Enable lingering so user services persist without active login
loginctl enable-linger "$CLAW_USER" 2>/dev/null || true
msg_ok "User lingering enabled (systemd user services persist)"
# Set a default password for initial SSH access
echo "${CLAW_USER}:openclaw" | chpasswd
msg_warn "Default password set to 'openclaw' -- CHANGE THIS after first login"
# Allow password auth for initial setup (user can harden later)
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config 2>/dev/null || true
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config 2>/dev/null || true
systemctl restart sshd 2>/dev/null || true
}
# =============================================================================
# Step 3: Install Node.js (if not handled by OpenClaw installer)
# =============================================================================
step_install_node() {
msg_step "Step 3/8: Ensuring Node.js is available"
# Check if Node.js 22+ is already present
if command -v node &>/dev/null; then
local NODE_VER
NODE_VER=$(node --version 2>/dev/null | sed 's/v//' | cut -d. -f1)
if [[ "$NODE_VER" -ge 22 ]]; then
msg_ok "Node.js $(node --version) already installed"
return 0
fi
fi
# Install Node.js 22.x via NodeSource (same method OpenClaw's installer uses)
msg_info "Installing Node.js 22.x via NodeSource..."
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -s -- >/dev/null 2>&1
apt-get install -y -qq nodejs >/dev/null 2>&1
if command -v node &>/dev/null; then
msg_ok "Node.js $(node --version) installed"
else
msg_warn "Node.js pre-install failed. OpenClaw installer will handle it."
fi
# Ensure npm global dir exists for claw user (avoids permission issues later)
sudo -u "$CLAW_USER" bash -c '
mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global 2>/dev/null || true
'
msg_ok "npm global directory configured for claw user"
}
# =============================================================================
# Step 4: Install OpenClaw
# =============================================================================
step_install_openclaw() {
msg_step "Step 4/8: Installing OpenClaw"
msg_info "Running OpenClaw install script..."
# Use --no-prompt --no-onboard to suppress all interactive prompts
# (same approach NemoClaw uses; OPENCLAW_NO_PROMPT=1 as belt-and-suspenders)
sudo -u "$CLAW_USER" bash -c \
'export PATH="${HOME}/.npm-global/bin:${PATH}" && \
export OPENCLAW_NO_PROMPT=1 && \
curl -fsSL https://openclaw.ai/install.sh | bash -s -- --no-prompt --no-onboard' \
</dev/null 2>&1 | tail -5
# Set PATH for OpenClaw binaries — use /etc/profile.d/ so it survives dotfile replacement
cat > /etc/profile.d/openclaw.sh << 'OCPROFILE'
# OpenClaw PATH (system-wide fallback — survives dotfile replacement)
if [ -d "${HOME}/.npm-global/bin" ]; then
case ":${PATH}:" in
*":${HOME}/.npm-global/bin:"*) ;;
*) export PATH="${HOME}/.npm-global/bin:${PATH}" ;;
esac
export NODE_PATH="${HOME}/.npm-global/lib/node_modules"
fi
# Performance tuning for LXC/VM hosts
export NODE_COMPILE_CACHE="/var/tmp/openclaw-compile-cache"
export OPENCLAW_NO_RESPAWN=1
OCPROFILE
chmod 644 /etc/profile.d/openclaw.sh
mkdir -p /var/tmp/openclaw-compile-cache
# Also add to .bashrc as a convenience (but profile.d is the authoritative source)
sudo -u "$CLAW_USER" bash -c 'cat >> ~/.bashrc << "OCPATH"
# OpenClaw
export PATH="${HOME}/.npm-global/bin:${PATH}"
export NODE_PATH="${HOME}/.npm-global/lib/node_modules"
export NODE_COMPILE_CACHE="/var/tmp/openclaw-compile-cache"
export OPENCLAW_NO_RESPAWN=1
OCPATH'
# Verify OpenClaw actually installed
if sudo -u "$CLAW_USER" bash -c \
'export PATH="${HOME}/.npm-global/bin:${PATH}" && command -v openclaw' \
>/dev/null 2>&1; then
msg_ok "OpenClaw installed and binary verified"
else
msg_error "OpenClaw binary not found after install. Check ${LOGFILE} for details."
exit 1
fi
msg_ok "PATH configured for claw user"
# Ensure XDG_RUNTIME_DIR exists before systemd user operations
ensure_user_runtime_dir
# Install the gateway as a systemd user service
msg_info "Installing OpenClaw gateway service..."
sudo -u "$CLAW_USER" bash -c \
'export PATH="${HOME}/.npm-global/bin:${PATH}" && \
export XDG_RUNTIME_DIR="/run/user/$(id -u)" && \
openclaw gateway install' \
>/dev/null 2>&1 || {
msg_warn "Gateway service install returned non-zero. May need manual setup."
msg_warn "Run: openclaw gateway install (as claw user after login)"
}
msg_ok "Gateway service installed"
}
# =============================================================================
# Step 5: Apply Config Templates
# =============================================================================
step_apply_templates() {
msg_step "Step 5/8: Applying configuration templates"
local OC_DIR="${CLAW_HOME}/.openclaw"
local WS_DIR="${OC_DIR}/workspace"
# Ensure directories exist
sudo -u "$CLAW_USER" mkdir -p "$WS_DIR/memory" "$WS_DIR/skills" "$OC_DIR/credentials"
# Apply openclaw.json template with token generation
if [[ -f /tmp/openclaw.json.tpl ]]; then
# Generate a random gateway token
local GW_TOKEN
GW_TOKEN=$(openssl rand -hex 24 2>/dev/null || head -c 48 /dev/urandom | xxd -p | tr -d '\n' | head -c 48)
# Inject generated token into template
sed "s/__GATEWAY_TOKEN__/${GW_TOKEN}/" /tmp/openclaw.json.tpl > "${OC_DIR}/openclaw.json"
# Lock permissions immediately (contains token)
chown "${CLAW_USER}:${CLAW_USER}" "${OC_DIR}/openclaw.json"
chmod 600 "${OC_DIR}/openclaw.json"
msg_ok "openclaw.json template applied (gateway token generated)"
msg_info "Gateway token: ${GW_TOKEN}"
msg_info "Save this token -- you'll need it for dashboard access"
msg_warn "Telegram bot token still needs manual configuration"
msg_warn "Edit ~/.openclaw/openclaw.json and replace __TELEGRAM_BOT_TOKEN__"
else
msg_warn "No openclaw.json template found -- using OpenClaw defaults"
msg_warn "Run 'openclaw configure' after login to set up config"
fi
# NOTE: SOUL.md, AGENTS.md, and USER.md are NOT pre-written here.
# The hatching process (BOOTSTRAP.md → TUI "Wake up, my friend!") generates these
# interactively, creating the bot's personality from scratch. Pre-writing them
# would bypass the "Wake up, my friend..." first-contact experience.
#
# Our agents.md.tpl (prompt injection defense, sub-agent delegation) is stored
# in ~/OpenClaw/templates/ and can be appended to AGENTS.md AFTER hatching
# via the post-install wizard's finalize step.
# NOTE: USER.md is NOT created here either. Any file in the workspace
# directory before onboard runs can trigger the hasUserContent check and
# prevent BOOTSTRAP.md creation, killing the hatching process.
# USER.md is created by the post-install wizard after hatching completes.
}
# =============================================================================
# Step 6: Memory Plugin (deferred — installed by post-install wizard AFTER hatching)
# =============================================================================
step_install_memory() {
msg_step "Step 6/8: Memory plugin (deferred)"
# CRITICAL: The memory plugin MUST NOT be installed before onboard/hatching.
#
# OpenClaw's workspace initialization (workspace-R-NeOkBt.js lines 314-332)
# checks for existing content in the workspace directory. If it finds any
# (including skills/, memory/, or plugin data files), it sets setupCompletedAt
# immediately and skips BOOTSTRAP.md creation. Without BOOTSTRAP.md, the
# "Wake up, my friend..." hatching dialogue never fires.
#
# The memory-lancedb-hybrid plugin is installed by the post-install wizard
# (openclaw-postinstall.sh) AFTER the bot has been hatched.
msg_info "Memory plugin will be installed after hatching (post-install wizard)"
msg_dim "This prevents workspace content from blocking the hatching process"
}
# =============================================================================
# Step 7: Tailscale Setup
# =============================================================================
step_install_tailscale() {
msg_step "Step 7/8: Installing Tailscale"
msg_info "Installing Tailscale..."
curl -fsSL https://tailscale.com/install.sh | sh >/dev/null 2>&1
msg_ok "Tailscale binary installed"
systemctl enable --now tailscaled >/dev/null 2>&1 || true
msg_ok "tailscaled service enabled"
msg_info "Tailscale is installed but NOT authenticated yet."
msg_info "After first login, run:"
msg_info " sudo tailscale up"
msg_info " sudo tailscale serve --bg 18789"
}
# =============================================================================
# Step 8: Backup Script, Git Tracking, Cron
# =============================================================================
step_setup_maintenance() {
msg_step "Step 8/8: Backups, git tracking, hardening, and validation"
local OC_DIR="${CLAW_HOME}/.openclaw"
# -- Backup Script -----------------------------------------------------------
sudo -u "$CLAW_USER" mkdir -p "${CLAW_HOME}/bin" "${CLAW_HOME}/backups"
cat > "${CLAW_HOME}/bin/backup-openclaw.sh" << 'BACKUP'
#!/bin/bash
# OpenClaw automated backup script
BACKUP_DIR="${HOME}/backups"
DATE=$(date +%Y-%m-%d)
mkdir -p "$BACKUP_DIR"
tar czf "$BACKUP_DIR/openclaw-${DATE}.tar.gz" \
~/.openclaw/openclaw.json \
~/.openclaw/.env \
~/.openclaw/credentials/ \
~/.openclaw/workspace/ \
~/.openclaw/agents/*/agent/auth-profiles.json \
2>/dev/null || true
# Keep only last 7 days of backups
find "$BACKUP_DIR" -name "openclaw-*.tar.gz" -mtime +7 -delete
echo "$(date): Backup completed - openclaw-${DATE}.tar.gz" >> "$BACKUP_DIR/backup.log"
BACKUP
chmod +x "${CLAW_HOME}/bin/backup-openclaw.sh"
chown -R "${CLAW_USER}:${CLAW_USER}" "${CLAW_HOME}/bin" "${CLAW_HOME}/backups"
msg_ok "Backup script created at ~/bin/backup-openclaw.sh"
# -- Cron job (daily at 3am) -------------------------------------------------
sudo -u "$CLAW_USER" bash -c \
'(crontab -l 2>/dev/null | grep -v "backup-openclaw"; echo "0 3 * * * ${HOME}/bin/backup-openclaw.sh") | crontab -' \
2>/dev/null || true
msg_ok "Daily backup cron job set (3:00 AM)"
# -- Cleanup cron (memory files older than 30 days) --------------------------
sudo -u "$CLAW_USER" bash -c \
'(crontab -l 2>/dev/null; echo "0 4 * * * find ~/.openclaw/workspace/memory -name '"'"'*.md'"'"' -mtime +30 -delete 2>/dev/null") | crontab -' \
2>/dev/null || true
msg_ok "Memory cleanup cron set (30-day retention)"
# -- Git-track config --------------------------------------------------------
msg_info "Initializing git tracking for OpenClaw config..."
sudo -u "$CLAW_USER" bash -c "
cd ${OC_DIR} && \
git init -q && \
printf 'agents/*/sessions/\nagents/*/agent/*.jsonl\n*.log\nworkspace/memory/\n' > .gitignore && \
git add .gitignore openclaw.json 2>/dev/null && \
git commit -q -m 'config: initial baseline from helper script' 2>/dev/null
" || true
msg_ok "Config directory git-tracked (rollback ready)"
# -- Update alias (add to both .bashrc and .zshrc if it exists) -------------
local ALIAS_BLOCK='
# OpenClaw shortcuts
alias openclaw-update="pnpm add -g openclaw@latest && systemctl --user restart openclaw-gateway.service"
alias openclaw-logs="openclaw logs --follow"
alias openclaw-status="openclaw gateway status"
alias openclaw-backup="${HOME}/bin/backup-openclaw.sh"'
sudo -u "$CLAW_USER" bash -c "echo '$ALIAS_BLOCK' >> ~/.bashrc"
if [[ -f "${CLAW_HOME}/.zshrc" ]]; then
sudo -u "$CLAW_USER" bash -c "echo '$ALIAS_BLOCK' >> ~/.zshrc"
msg_ok "Shell aliases added to .bashrc and .zshrc"
else
msg_ok "Shell aliases added to .bashrc"
fi
# -- Clone helper repo (for postinstall wizard and future updates) -----------
if [[ ! -d "${CLAW_HOME}/OpenClaw" ]]; then
msg_info "Cloning OpenClaw helper repo..."
sudo -u "$CLAW_USER" git clone -q \
https://github.com/Exploitacious/OpenClaw.git \
"${CLAW_HOME}/OpenClaw" 2>/dev/null || {
msg_warn "Repo clone failed. Post-install wizard can be fetched manually."
msg_warn "Run: git clone https://github.com/Exploitacious/OpenClaw.git ~/OpenClaw"
}
msg_ok "Helper repo cloned to ~/OpenClaw (includes post-install wizard)"
else
msg_info "Updating existing OpenClaw helper repo..."
sudo -u "$CLAW_USER" bash -c "cd ${CLAW_HOME}/OpenClaw && git pull -q" 2>/dev/null || true
msg_ok "Helper repo updated"
fi
}
# =============================================================================
# Step 9: Security Hardening & Validation
# =============================================================================
step_validate() {
msg_step "Finalizing: Security hardening & validation"
local OC_DIR="${CLAW_HOME}/.openclaw"
# -- File Permissions --------------------------------------------------------
msg_info "Setting file permissions..."
chmod 700 "$OC_DIR" 2>/dev/null || true
chmod 600 "${OC_DIR}/openclaw.json" 2>/dev/null || true
chmod 700 "${OC_DIR}/credentials" 2>/dev/null || true
chown -R "${CLAW_USER}:${CLAW_USER}" "$OC_DIR"
msg_ok "File permissions locked (700/600)"
ensure_user_runtime_dir
# -- Run openclaw doctor -----------------------------------------------------
msg_info "Running openclaw doctor..."
sudo -u "$CLAW_USER" bash -c \
'export PATH="${HOME}/.npm-global/bin:${PATH}" && \
export XDG_RUNTIME_DIR="/run/user/$(id -u)" && \
openclaw doctor --fix' \
2>&1 | tail -10 || true
msg_ok "openclaw doctor completed"
# -- Run security audit ------------------------------------------------------
msg_info "Running security audit..."
sudo -u "$CLAW_USER" bash -c \
'export PATH="${HOME}/.npm-global/bin:${PATH}" && \
export XDG_RUNTIME_DIR="/run/user/$(id -u)" && \
openclaw security audit --deep' \
2>&1 | tail -10 || true
msg_ok "Security audit completed"
# -- Verify gateway binding --------------------------------------------------
msg_info "Checking gateway binding..."
if netstat -an 2>/dev/null | grep -q "0.0.0.0:18789"; then
msg_warn "WARNING: Gateway is bound to 0.0.0.0 (all interfaces)!"
msg_warn "Fix: openclaw config set gateway.bind loopback"
else
msg_ok "Gateway binding looks correct"
fi
# -- Final git commit with hardened state ------------------------------------
sudo -u "$CLAW_USER" bash -c "
cd ${OC_DIR} && \
git add -A 2>/dev/null && \
git commit -q -m 'config: post-install hardening complete' 2>/dev/null
" || true
# -- Get container IP for summary ----------------------------------------------
local CONTAINER_IP
CONTAINER_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
echo ""
msg_ok "============================================"
msg_ok " OpenClaw installation complete!"
msg_ok "============================================"
echo ""
echo -e " ${GN}Next steps:${CL}"
echo ""
echo -e " 1. ${YW}Reboot the container to load PATH and services:${CL}"
echo -e " ${BL}reboot${CL} (or from PVE host: ${BL}pct reboot <CT_ID>${CL})"
echo ""
echo -e " 2. Log in:"
echo -e " ${BL}ssh claw@${CONTAINER_IP:-<container-ip>}${CL}"
echo -e " Password: ${YW}openclaw${CL}"
echo ""
echo -e " 3. ${RD}Change the default password immediately:${CL}"
echo -e " ${BL}passwd${CL}"
echo ""
echo -e " 4. ${GN}Run the post-install wizard (sets up everything else):${CL}"
echo -e " ${BL}bash ~/OpenClaw/openclaw-postinstall.sh${CL}"
echo ""
echo -e " The wizard handles: AI providers, model selection, embeddings key,"
echo -e " Telegram bot token, Tailscale auth, and agent personality."
echo ""
echo -e " Or do it all in one shot:"
echo -e " ${BL}bash ~/OpenClaw/openclaw-postinstall.sh \\${CL}"
echo -e " ${BL} --provider anthropic-api-key --provider-key sk-ant-... \\${CL}"
echo -e " ${BL} --openai-key sk-... \\${CL}"
echo -e " ${BL} --telegram-token 123456:ABC... \\${CL}"
echo -e " ${BL} --telegram-user-id YOUR_ID \\${CL}"
echo -e " ${BL} --tailscale-auth-key tskey-auth-...${CL}"
echo ""
}
# =============================================================================
# Preflight Checks
# =============================================================================
preflight_checks() {
echo ""
msg_step "Preflight checks"
local FAIL=false
# -- Root -------------------------------------------------------------------
if [[ "$(id -u)" -eq 0 ]]; then
msg_ok "Running as root"
else
msg_error "Must be run as root (try: sudo bash $0)"
FAIL=true
fi
# -- Distro (Debian/Ubuntu) -------------------------------------------------
if [[ -f /etc/os-release ]]; then
# shellcheck disable=SC1091
. /etc/os-release
if [[ "${ID:-}" == "ubuntu" || "${ID:-}" == "debian" || "${ID_LIKE:-}" == *"debian"* ]]; then
msg_ok "Distro: ${PRETTY_NAME:-$ID}"
else
msg_error "Unsupported distro: ${PRETTY_NAME:-$ID} (need Debian/Ubuntu)"
FAIL=true
fi
else
msg_error "Cannot detect distro (/etc/os-release missing)"
FAIL=true
fi
# -- systemd as PID 1 -------------------------------------------------------
if [[ "$(cat /proc/1/comm 2>/dev/null)" == "systemd" ]]; then
msg_ok "systemd is PID 1"
else
msg_error "systemd not detected (PID 1: $(cat /proc/1/comm 2>/dev/null || echo unknown))"
msg_error "OpenClaw requires systemd for user services and lingering"
FAIL=true
fi
# -- curl -------------------------------------------------------------------
if command -v curl &>/dev/null; then
msg_ok "curl available"
else
msg_warn "curl not found -- will attempt to install via apt"
fi
# -- RAM (warn < 2GB) -------------------------------------------------------
local RAM_KB RAM_MB
RAM_KB=$(grep MemTotal /proc/meminfo 2>/dev/null | awk '{print $2}')
RAM_MB=$(( ${RAM_KB:-0} / 1024 ))
if [[ "$RAM_MB" -ge 2048 ]]; then
msg_ok "RAM: ${RAM_MB} MB"
elif [[ "$RAM_MB" -ge 1024 ]]; then
msg_warn "RAM: ${RAM_MB} MB (2 GB+ recommended, may be tight)"
else
msg_warn "RAM: ${RAM_MB} MB (2 GB+ recommended, install may fail)"
fi
# -- Disk (warn < 2GB free) -------------------------------------------------
local DISK_FREE_KB DISK_FREE_MB
DISK_FREE_KB=$(df / 2>/dev/null | awk 'NR==2 {print $4}')
DISK_FREE_MB=$(( ${DISK_FREE_KB:-0} / 1024 ))
if [[ "$DISK_FREE_MB" -ge 2048 ]]; then
msg_ok "Free disk: ${DISK_FREE_MB} MB"
elif [[ "$DISK_FREE_MB" -ge 1024 ]]; then
msg_warn "Free disk: ${DISK_FREE_MB} MB (2 GB+ recommended)"
else
msg_warn "Free disk: ${DISK_FREE_MB} MB (2 GB+ recommended, may run out)"
fi
# -- Internet (quick check) -------------------------------------------------
if curl -fsS --max-time 5 -o /dev/null https://deb.nodesource.com 2>/dev/null; then
msg_ok "Internet reachable"
elif command -v curl &>/dev/null; then
msg_warn "Internet check failed (may be a firewall or DNS issue)"
else
msg_warn "Cannot verify internet (curl not yet installed)"
fi
# -- Bail on hard failures ---------------------------------------------------
if $FAIL; then
echo ""
msg_error "Preflight failed. Fix the issues above and re-run."
exit 1
fi
echo ""
}
# =============================================================================
# Main
# =============================================================================
main() {
preflight_checks
# -- Detect execution mode ----------------------------------------------------
if [[ -n "${PUSHED_BY_HOST:-}" ]]; then
# Called by openclaw.sh — templates already staged in /tmp
echo ""
echo "==========================================="
echo " OpenClaw Container Setup (via PVE host)"
echo "==========================================="
echo ""
else
# Standalone mode — running directly on an existing machine
echo ""
echo "==========================================="
echo " OpenClaw Standalone Setup"
echo "==========================================="
echo ""
msg_info "Running in standalone mode (not via Proxmox helper)"
msg_info "Templates will be fetched from GitHub if not present"
echo ""
# Pre-fetch templates so step_apply_templates finds them in /tmp
for tpl in openclaw.json.tpl soul.md.tpl agents.md.tpl; do
resolve_template "$tpl" || true
done
fi
step_system_setup
step_create_user
step_install_node
step_install_openclaw
step_apply_templates
step_install_memory
step_install_tailscale
step_setup_maintenance
step_validate
}
main "$@"