-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathsetup.sh
More file actions
executable file
·1076 lines (946 loc) · 36.3 KB
/
Copy pathsetup.sh
File metadata and controls
executable file
·1076 lines (946 loc) · 36.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
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
# ClawMax Setup Script v2
# Automated installation and configuration for ClawMax Dashboard
# Supports: local dev with Email OTP or bypass
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -f "$SCRIPT_DIR/SYSTEM/openclaw-version.sh" ]; then
. "$SCRIPT_DIR/SYSTEM/openclaw-version.sh"
fi
# Keep local setup aligned with the branch-tested OpenClaw target when present.
OPENCLAW_GIT_REF="${OPENCLAW_GIT_REF:-${CLAWMAX_OPENCLAW_TARGET:-1116ae97662cce066dd130bc07d925fdd1dd3f32}}"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
print_header() { echo -e "\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n${BLUE}$1${NC}\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; }
print_success() { echo -e "${GREEN}✓${NC} $1"; }
print_error() { echo -e "${RED}✗${NC} $1"; }
print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
ask_yn() { read -p " $1 (y/N): " -n 1 -r; echo; [[ $REPLY =~ ^[Yy]$ ]]; }
remove_path_with_optional_sudo() {
local target="$1"
local label="$2"
if [ ! -e "$target" ] && [ ! -L "$target" ]; then
return 0
fi
if rm -rf "$target" 2>/dev/null; then
print_success "Removed $label"
return 0
fi
if [ "$INTERACTIVE" = true ] && command -v sudo >/dev/null 2>&1; then
print_info "Requesting admin access to remove $label"
if sudo rm -rf "$target"; then
print_success "Removed $label"
return 0
fi
fi
print_warning "Could not remove $label automatically"
print_info "Remove manually: sudo rm -rf \"$target\""
return 1
}
collect_podman_machine_names() {
if ! command -v podman >/dev/null 2>&1; then
return 0
fi
local machine_json=""
machine_json="$(podman machine list --format json 2>/dev/null || true)"
if [ -z "$machine_json" ]; then
return 0
fi
printf '%s\n' "$machine_json" \
| tr '{},' '\n' \
| sed -n 's/.*"Name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p'
}
cleanup_orphaned_podman_machine_files() {
if ! command -v podman >/dev/null 2>&1; then
print_info "Podman not installed, skipping Podman cleanup"
return 0
fi
local machine_names existing_names
machine_names="$(collect_podman_machine_names)"
existing_names="$(printf '%s\n' "$machine_names" | sed '/^$/d')"
local search_dirs=(
"$HOME/.local/share/containers/podman/machine"
"$HOME/.config/containers/podman/machine"
"$HOME/Library/Containers/podman/machine"
)
local removed=0
local found_any=false
for dir in "${search_dirs[@]}"; do
[ -d "$dir" ] || continue
found_any=true
while IFS= read -r orphan; do
[ -n "$orphan" ] || continue
local base machine_name
base="$(basename "$orphan")"
machine_name=""
case "$base" in
efi-bl-*)
machine_name="${base#efi-bl-}"
;;
*-ignition.sock)
machine_name="${base%-ignition.sock}"
;;
esac
[ -n "$machine_name" ] || continue
if printf '%s\n' "$existing_names" | grep -Fxq "$machine_name"; then
continue
fi
rm -f "$orphan" 2>/dev/null || true
if [ ! -e "$orphan" ]; then
removed=$((removed + 1))
fi
done < <(find "$dir" -type f \( -name 'efi-bl-*' -o -name '*-ignition.sock' \) 2>/dev/null)
done
if [ "$found_any" = false ]; then
print_info "No Podman machine directories found"
elif [ "$removed" -gt 0 ]; then
print_success "Removed $removed orphaned Podman machine file(s)"
else
print_info "No orphaned Podman machine files found"
fi
}
# Non-interactive mode (CI)
INTERACTIVE=true
if [ ! -t 0 ] || [ "$CI" = "true" ] || [ "$1" = "--non-interactive" ]; then
INTERACTIVE=false
fi
# ============================================================================
# Uninstall mode
# ============================================================================
if [ "$1" = "--uninstall" ] || [ "$1" = "uninstall" ]; then
if [ -t 1 ] && [ -n "${TERM:-}" ]; then clear 2>/dev/null || true; fi
echo -e "${CYAN}"
cat << "EOF"
____ _ __ __
/ ___| | __ ___ _| \/ | __ ___ __
| | | |/ _` \ \ /\ / / |\/| |/ _` \ \/ /
| |___| | (_| |\ V V /| | | | (_| |> <
\____|_|\__,_| \_/\_/ |_| |_|\__,_/_/\_\
Multiagent Orchestration Platform
EOF
echo -e "${NC}"
print_info "ClawMax Uninstall"
echo -e "${YELLOW}This will remove ClawMax and OpenClaw from your system.${NC}"
echo ""
echo " What happens:"
echo " - ClawMax/OpenClaw runtime setup is removed from this machine"
echo " - Local dashboard dependencies and generated runtime config are removed"
echo " - Your work product is preserved unless you manually delete it later"
echo ""
echo " The following will be removed:"
echo " - OpenClaw CLI (npm global or Homebrew)"
echo " - OpenClaw runtime config and local metadata (~/.openclaw/*.json, logs, temp state)"
echo " - Dashboard dependencies (SYSTEM/dashboard/node_modules/)"
echo " - Dashboard build artifacts (SYSTEM/dashboard/dist/)"
echo " - Dashboard .env (SYSTEM/dashboard/.env)"
echo ""
echo -e " ${BOLD}NOT removed:${NC}"
echo " - This git repository (delete manually if desired)"
echo " - Your workspace data (WORKSPACES/) — preserved for safety"
echo " - Agent directories and files created by you or your agents (~/.openclaw/agents/, ~/.openclaw/workspace/, ~/.openclaw/workspaces/)"
echo ""
echo " To cancel:"
echo " - Type anything other than ${BOLD}yes${NC} at the confirmation prompt"
echo " - Or press ${BOLD}Ctrl+C${NC} before the uninstall starts"
echo ""
if [ "$INTERACTIVE" = true ]; then
read -p " Are you sure you want to uninstall? (yes/N): " CONFIRM
if [ "$CONFIRM" != "yes" ]; then
echo ""
print_info "Uninstall cancelled."
exit 0
fi
else
print_warning "Running in non-interactive uninstall mode — proceeding without a confirmation prompt."
fi
echo ""
print_header "Stopping Dashboard"
if [ -f "SYSTEM/stop.sh" ]; then
bash SYSTEM/stop.sh 2>/dev/null || true
print_success "Dashboard stopped"
else
print_info "No stop script found, skipping"
fi
print_header "Removing OpenClaw CLI"
if command -v openclaw &> /dev/null; then
# Try npm uninstall first
if npm list -g openclaw &> /dev/null; then
npm uninstall -g openclaw 2>/dev/null && print_success "Removed openclaw (npm)" || print_warning "npm uninstall failed"
fi
# Try brew uninstall
if command -v brew &> /dev/null && brew list --formula maximilien-ai/openclaw/openclaw &> /dev/null 2>&1; then
brew uninstall maximilien-ai/openclaw/openclaw 2>/dev/null && print_success "Removed openclaw (Homebrew)" || print_warning "brew uninstall failed"
fi
# Verify removal
if command -v openclaw &> /dev/null; then
print_warning "openclaw still found at $(which openclaw) — remove manually"
fi
else
print_info "OpenClaw CLI not installed, skipping"
fi
print_header "Removing OpenClaw Data"
OPENCLAW_DIR="$HOME/.openclaw"
if [ -d "$OPENCLAW_DIR" ]; then
rm -f \
"$OPENCLAW_DIR/openclaw.json" \
"$OPENCLAW_DIR/openclaw.json.bak" \
"$OPENCLAW_DIR/dashboard-workspaces.json" \
"$OPENCLAW_DIR/dashboard-workspaces.json.bak" \
"$OPENCLAW_DIR/gateway-control-ui-token" \
"$OPENCLAW_DIR/gateway.log" \
"$OPENCLAW_DIR/gateway.pid" \
"$OPENCLAW_DIR/gateway.sock" \
"$OPENCLAW_DIR/gateway-state.json" \
"$OPENCLAW_DIR/doctor-report.json" \
"$OPENCLAW_DIR/auth-token" 2>/dev/null || true
if [ -d "$OPENCLAW_DIR/logs" ]; then
rm -rf "$OPENCLAW_DIR/logs"
fi
if [ -d "$OPENCLAW_DIR/tmp" ]; then
rm -rf "$OPENCLAW_DIR/tmp"
fi
print_success "Removed OpenClaw config and runtime metadata"
print_info "Preserved ~/.openclaw/agents, ~/.openclaw/workspace, and ~/.openclaw/workspaces"
else
print_info "No ~/.openclaw/ directory found"
fi
print_header "Cleaning Dashboard"
if [ -d "SYSTEM/dashboard/node_modules" ]; then
rm -rf SYSTEM/dashboard/node_modules
print_success "Removed node_modules/"
fi
if [ -d "SYSTEM/dashboard/dist" ]; then
rm -rf SYSTEM/dashboard/dist
print_success "Removed dist/"
fi
if [ -f "SYSTEM/dashboard/.env" ]; then
# Back up .env before removing (may have user's API keys)
cp SYSTEM/dashboard/.env SYSTEM/dashboard/.env.uninstall-backup
rm -f SYSTEM/dashboard/.env
print_success "Removed .env (backup saved as .env.uninstall-backup)"
fi
# Clean log files
rm -f /tmp/dashboard.log /tmp/ngrok.log 2>/dev/null
if [ -d "SYSTEM/dashboard/server/logs" ]; then
rm -rf SYSTEM/dashboard/server/logs
print_success "Removed server logs"
fi
# Remove Homebrew tap
if command -v brew &> /dev/null && brew tap | grep -q "maximilien-ai/openclaw"; then
brew untap maximilien-ai/openclaw 2>/dev/null && print_success "Removed Homebrew tap" || true
fi
print_header "Cleaning Podman Residue"
cleanup_orphaned_podman_machine_files
print_header "Removing Packaged App Artifacts"
remove_path_with_optional_sudo "/Applications/ClawMax.app" "ClawMax.app"
remove_path_with_optional_sudo "/usr/local/bin/clawmax" "clawmax launcher"
echo ""
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN} Uninstall complete${NC}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo " Your workspace data in WORKSPACES/ has been preserved."
echo " Your agent/workspace files in ~/.openclaw/ have been preserved."
echo " To fully remove ClawMax, delete this directory:"
echo -e " ${BOLD}rm -rf $(pwd)${NC}"
echo ""
echo " To reinstall later: git clone + ./setup.sh"
echo ""
exit 0
fi
# Welcome
if [ -t 1 ] && [ -n "${TERM:-}" ]; then clear 2>/dev/null || true; fi
echo -e "${CYAN}"
cat << "EOF"
____ _ __ __
/ ___| | __ ___ _| \/ | __ ___ __
| | | |/ _` \ \ /\ / / |\/| |/ _` \ \/ /
| |___| | (_| |\ V V /| | | | (_| |> <
\____|_|\__,_| \_/\_/ |_| |_|\__,_/_/\_\
Multiagent Orchestration Platform
EOF
echo -e "${NC}"
print_info "ClawMax Setup"
echo ""
# Bootstrap mode: allow setup.sh to hand off to the release installer when invoked
# as `./setup.sh latest` or `./setup.sh vX.Y.Z`.
case "${1:-}" in
latest|v[0-9]*)
if [ -f "./install.sh" ]; then
exec ./install.sh "$@"
fi
;;
esac
# Must be in ClawMax directory
if [ ! -d "SYSTEM/dashboard" ]; then
print_error "Not in a ClawMax directory. Please run from the clawmax repo root."
exit 1
fi
CLAWMAX_DIR="$(pwd)"
SETUP_BACKEND_PORT="${DASHBOARD_PORT:-3001}"
SETUP_FRONTEND_PORT="${DASHBOARD_CLIENT_PORT:-5173}"
SETUP_FRONTEND_URL="${DASHBOARD_APP_URL:-http://localhost:${SETUP_FRONTEND_PORT}}"
SETUP_PUBLIC_URL="${DASHBOARD_PUBLIC_URL:-http://localhost:${SETUP_BACKEND_PORT}}"
SETUP_CORS_ORIGIN="${CORS_ORIGIN:-$SETUP_FRONTEND_URL}"
# ============================================================================
# 1. Prerequisites
# ============================================================================
print_header "1. Checking Prerequisites"
# Helper: offer to install a missing dependency
offer_install() {
local name="$1"
local brew_pkg="$2"
local url="$3"
if [ "$INTERACTIVE" = true ]; then
echo ""
echo -e " ${BOLD}$name is required but not installed.${NC}"
echo ""
if command -v brew &> /dev/null && [ -n "$brew_pkg" ]; then
echo -e " Press ${BOLD}Enter${NC} to install via Homebrew (brew install $brew_pkg)"
echo -e " Press ${BOLD}s${NC} to skip and install manually"
echo -e " Get it from: $url"
echo ""
read -p " [Enter/s]: " -n 1 -r; echo
if [[ -z "$REPLY" || "$REPLY" == $'\n' ]]; then
print_info "Installing $name via Homebrew..."
if brew install "$brew_pkg"; then
print_success "$name installed"
return 0
else
print_error "Homebrew install failed"
return 1
fi
fi
else
echo -e " Installing from: $url"
echo -e " Press ${BOLD}Enter${NC} after installing, or ${BOLD}s${NC} to skip"
read -p " [Enter/s]: " -n 1 -r; echo
if [[ -z "$REPLY" || "$REPLY" == $'\n' ]]; then
# Check again after user says they installed
return 0
fi
fi
return 1
else
print_error "$name not found — install from $url"
return 1
fi
}
# Node.js
if command -v node &> /dev/null; then
NODE_VERSION=$(node --version)
NODE_MAJOR=$(echo "$NODE_VERSION" | cut -d'.' -f1 | sed 's/v//')
if [ "$NODE_MAJOR" -lt 18 ]; then
print_error "Node.js 18+ required (found $NODE_VERSION)"
if ! offer_install "Node.js 18+" "node" "https://nodejs.org/"; then
exit 1
fi
fi
print_success "Node.js: $(node --version)"
else
if offer_install "Node.js" "node" "https://nodejs.org/"; then
if command -v node &> /dev/null; then
print_success "Node.js: $(node --version)"
else
print_error "Node.js still not found after install attempt"
exit 1
fi
else
exit 1
fi
fi
# npm (comes with Node.js)
if command -v npm &> /dev/null; then
print_success "npm: v$(npm --version)"
else
print_error "npm not found (should come with Node.js)"
exit 1
fi
# git
if command -v git &> /dev/null; then
print_success "git: v$(git --version | cut -d' ' -f3)"
else
if offer_install "git" "git" "https://git-scm.com/"; then
if command -v git &> /dev/null; then
print_success "git: v$(git --version | cut -d' ' -f3)"
else
print_error "git still not found"
exit 1
fi
else
exit 1
fi
fi
echo ""
# ============================================================================
# 2. OpenClaw CLI
# ============================================================================
print_header "2. OpenClaw CLI"
OPENCLAW_INSTALLED=false
OPENCLAW_HOMEBREW_FORMULA="maximilien-ai/openclaw/openclaw"
OPENCLAW_BREW_MANAGED=false
if command -v openclaw &> /dev/null; then
OPENCLAW_INSTALLED=true
OPENCLAW_VER=$(openclaw --version 2>&1 | head -1 | grep -o '[0-9]\{4\}\.[0-9]*\.[0-9]*' || echo "unknown")
print_success "OpenClaw CLI: $OPENCLAW_VER"
if command -v brew &> /dev/null && brew list --formula "$OPENCLAW_HOMEBREW_FORMULA" &> /dev/null 2>&1; then
OPENCLAW_BREW_MANAGED=true
print_info "OpenClaw is managed by Homebrew — refreshing to latest tap formula..."
brew tap maximilien-ai/openclaw >/dev/null 2>&1 || true
brew update --quiet maximilien-ai/openclaw >/dev/null 2>&1 || brew update --quiet >/dev/null 2>&1 || true
if brew upgrade --formula "$OPENCLAW_HOMEBREW_FORMULA"; then
hash -r 2>/dev/null || true
OPENCLAW_VER=$(openclaw --version 2>&1 | head -1 | grep -o '[0-9]\{4\}\.[0-9]*\.[0-9]*' || echo "unknown")
print_success "OpenClaw CLI updated via Homebrew: $OPENCLAW_VER"
else
print_warning "Homebrew upgrade did not complete cleanly — keeping existing OpenClaw install"
fi
fi
else
print_warning "OpenClaw CLI not found — required for agent management"
echo ""
if [ "$INTERACTIVE" = true ]; then
echo -e " Choose an installation method:"
echo ""
echo -e " ${BOLD}1)${NC} npm global install ${GREEN}← recommended (fastest)${NC}"
echo " npm install -g github:openclaw/openclaw#$OPENCLAW_GIT_REF"
echo ""
echo -e " ${BOLD}2)${NC} Homebrew (Maximilien-ai tap)"
echo " brew tap maximilien-ai/openclaw && brew install openclaw"
echo ""
echo -e " ${BOLD}3)${NC} Skip for now (dashboard will work but agent features limited)"
echo ""
echo -e " ${YELLOW}Note:${NC} Tracks the upstream OpenClaw project (https://openclaw.ai)."
echo " For production, we recommend staying current with upstream releases."
echo ""
echo -e " Press ${BOLD}Enter${NC} for option 1 (recommended)"
read -p " Choice (1/2/3) [1]: " -n 1 -r; echo
REPLY=${REPLY:-1}
case $REPLY in
1|"")
print_info "Installing OpenClaw via npm (fastest, pinned to tested ref)..."
if npm install -g "github:openclaw/openclaw#$OPENCLAW_GIT_REF"; then
hash -r 2>/dev/null || true
OPENCLAW_INSTALLED=true
print_success "OpenClaw CLI installed"
else
print_warning "npm install failed — trying Homebrew..."
brew tap maximilien-ai/openclaw || true
brew update --quiet maximilien-ai/openclaw >/dev/null 2>&1 || brew update --quiet >/dev/null 2>&1 || true
if brew install --formula "$OPENCLAW_HOMEBREW_FORMULA"; then
hash -r 2>/dev/null || true
OPENCLAW_INSTALLED=true
print_success "OpenClaw CLI installed via Homebrew"
else
print_warning "Installation failed"
echo " Try manually: npm install -g github:openclaw/openclaw#$OPENCLAW_GIT_REF"
fi
fi
;;
2)
print_info "Installing OpenClaw via Homebrew..."
brew tap maximilien-ai/openclaw || true
brew update --quiet maximilien-ai/openclaw >/dev/null 2>&1 || brew update --quiet >/dev/null 2>&1 || true
if brew install --formula "$OPENCLAW_HOMEBREW_FORMULA"; then
hash -r 2>/dev/null || true
OPENCLAW_INSTALLED=true
print_success "OpenClaw CLI installed via Homebrew"
else
print_warning "Homebrew install failed — try: npm install -g github:openclaw/openclaw#$OPENCLAW_GIT_REF"
fi
;;
*)
print_info "Skipped OpenClaw installation"
;;
esac
else
# Non-interactive: try npm install automatically
print_info "Installing OpenClaw CLI..."
if npm install -g "github:openclaw/openclaw#$OPENCLAW_GIT_REF" 2>/dev/null; then
hash -r 2>/dev/null || true
OPENCLAW_INSTALLED=true
print_success "OpenClaw CLI installed"
else
print_warning "OpenClaw install failed — try: npm install -g github:openclaw/openclaw#$OPENCLAW_GIT_REF"
fi
fi
fi
# Verify installation
if [ "$OPENCLAW_INSTALLED" = false ] && command -v openclaw &> /dev/null; then
OPENCLAW_INSTALLED=true
fi
if [ "$OPENCLAW_INSTALLED" = true ]; then
OPENCLAW_VER=$(openclaw --version 2>&1 | head -1 | grep -o '[0-9]\{4\}\.[0-9]*\.[0-9]*' || echo "installed")
print_success "OpenClaw CLI ready: $OPENCLAW_VER"
if "$SCRIPT_DIR/SYSTEM/ensure-openclaw-default-plugins.sh"; then
print_success "Default OpenClaw plugins ready"
else
print_warning "Default OpenClaw plugin setup failed — agent chat may be blocked by stale channel configuration"
fi
elif [ "$OPENCLAW_INSTALLED" = false ]; then
print_warning "OpenClaw CLI not installed — agent start/stop/chat will not work"
echo -e " Install later: ${BOLD}npm install -g github:openclaw/openclaw#$OPENCLAW_GIT_REF${NC}"
fi
echo ""
# ============================================================================
# Agent Runtimes (Claude Code / Factory Droid) — optional, non-fatal
# ============================================================================
print_header "Agent Runtimes (Claude Code / Factory Droid)"
echo " OpenClaw is ClawMax's default agent runtime. Claude Code and Factory"
echo " Droid are optional per-agent runtimes — pin one per agent later in the"
echo " agent editor, or set a workspace default in Integrations > Runtime."
echo " setup.sh only reports what's detected here; it does not install them."
echo ""
if command -v claude &> /dev/null; then
print_success "Claude Code CLI: $(claude --version 2>&1 | head -1)"
else
print_info "Claude Code CLI not found (optional)"
echo " Install later: npm install -g @anthropic-ai/claude-code"
echo " Or set CLAUDE_BIN to an existing installation's path"
fi
if command -v droid &> /dev/null; then
print_success "Factory Droid CLI: $(droid --version 2>&1 | head -1)"
else
print_info "Factory Droid CLI not found (optional)"
echo " Install later: curl -fsSL https://app.factory.ai/cli | sh"
echo " Or set DROID_BIN to an existing installation's path"
fi
echo ""
# ============================================================================
# 3. Mode Selection
# ============================================================================
print_header "3. Setup Mode"
AUTH_MODE="${AUTH_MODE:-}"
OTP_ALLOWED_EMAILS=""
OTP_DEV_MODE=""
if [ "$INTERACTIVE" = true ]; then
echo -e " ${BOLD}1) Local dev with bypass${NC} — fastest, no login, good default for developers"
echo -e " ${BOLD}2) Local dev with Email OTP${NC} — realistic auth testing with login codes"
echo ""
while true; do
read -p " Choose auth mode [1-2]: " AUTH_CHOICE
case "${AUTH_CHOICE}" in
1)
AUTH_MODE="bypass"
print_success "Local dev bypass selected"
break
;;
2)
AUTH_MODE="email_otp"
OTP_DEV_MODE="log"
read -p " OTP login email (default: dev@example.com): " OTP_ALLOWED_EMAILS
OTP_ALLOWED_EMAILS=${OTP_ALLOWED_EMAILS:-dev@example.com}
print_success "Local dev Email OTP selected"
print_info "Codes will be written to .clawmax-otp-dev.json at the repo root"
break
;;
*)
print_warning "Choose 1 or 2."
;;
esac
done
else
if [ -z "$AUTH_MODE" ]; then
AUTH_MODE="bypass"
fi
case "$AUTH_MODE" in
bypass)
print_info "Non-interactive: using local dev bypass"
;;
email_otp)
OTP_DEV_MODE="${OTP_DEV_MODE:-log}"
OTP_ALLOWED_EMAILS="${OTP_ALLOWED_EMAILS:-dev@example.com}"
print_info "Non-interactive: using Email OTP"
;;
*)
print_error "Non-interactive setup supports AUTH_MODE=bypass or AUTH_MODE=email_otp"
exit 1
;;
esac
fi
echo ""
# ============================================================================
# 4. Keys & BYOK
# ============================================================================
print_header "4. Keys & BYOK"
echo " setup.sh no longer collects provider or monitoring keys."
echo " It also avoids collecting optional shared runtime credentials during setup."
echo " This keeps open-source setup consistent across clean machines and reduces friction."
echo ""
echo -e " ${BOLD}Recommended path:${NC}"
echo " 1. Finish setup and start the dashboard"
echo " 2. Add model keys later in BYOK or Keys & Secrets"
echo " 3. Optionally edit SYSTEM/dashboard/.env later for shared runtime keys"
echo ""
echo " This also means partner integrations are opt-in by default."
echo ""
print_info "No provider API keys will be written by setup.sh"
echo ""
# ============================================================================
# 5. Auth Notes
# ============================================================================
GITHUB_CLIENT_ID="${GITHUB_CLIENT_ID:-}"
GITHUB_CLIENT_SECRET="${GITHUB_CLIENT_SECRET:-}"
print_header "5. Auth Notes"
if [ "$AUTH_MODE" = "github_oauth" ]; then
echo " GitHub OAuth was selected."
echo ""
echo " setup.sh does not collect GitHub OAuth credentials interactively."
echo " Finish setup first, then add these to SYSTEM/dashboard/.env if you want GitHub login:"
echo ""
echo " GITHUB_CLIENT_ID=..."
echo " GITHUB_CLIENT_SECRET=..."
echo ""
echo " Callback URL:"
echo " $SETUP_PUBLIC_URL/api/auth/github/callback"
echo ""
echo " Frontend URL:"
echo " $SETUP_FRONTEND_URL"
echo ""
echo " Detailed guide: SYSTEM/docs/OAUTH_SETUP.md"
elif [ "$AUTH_MODE" = "bypass" ]; then
print_info "Dev mode: OAuth bypassed (BYPASS_OAUTH=true)"
elif [ "$AUTH_MODE" = "email_otp" ] && [ "$OTP_DEV_MODE" = "log" ]; then
print_info "Dev mode: Email OTP enabled (codes written to .clawmax-otp-dev.json)"
fi
echo ""
# ============================================================================
# 6. Workspace Setup
# ============================================================================
print_header "6. Workspace Configuration"
WORKSPACE="$CLAWMAX_DIR/WORKSPACES/default"
# Create workspace structure
for dir in WORKSPACES/default WORKSPACES/default/AGENTS WORKSPACES/default/WORKFLOWS WORKSPACES/default/ORG WORKSPACES/default/TEMPLATES WORKSPACES/default/PARTNERS WORKSPACES/default/SKILLS/custom WORKSPACES/default/SYSTEM; do
mkdir -p "$CLAWMAX_DIR/$dir"
done
print_success "Workspace structure created: $WORKSPACE"
if [ -d "$CLAWMAX_DIR/PARTNERS" ]; then
print_success "Built-in partner definitions found"
else
mkdir -p "$CLAWMAX_DIR/PARTNERS"
print_warning "Repo PARTNERS/ directory was missing — created an empty directory"
fi
# OpenClaw config
OPENCLAW_CONFIG="$HOME/.openclaw/openclaw.json"
if [ ! -f "$OPENCLAW_CONFIG" ]; then
mkdir -p "$HOME/.openclaw"
cat > "$OPENCLAW_CONFIG" << 'OCEOF'
{
"agents": {}
}
OCEOF
print_success "Created OpenClaw config"
else
print_success "OpenClaw config exists"
fi
echo ""
# ============================================================================
# 7. Install Dependencies
# ============================================================================
print_header "7. Installing Dashboard Dependencies"
cd "$CLAWMAX_DIR/SYSTEM/dashboard"
if [ -d "node_modules" ] && [ ! "package.json" -nt "node_modules" ]; then
print_success "Dependencies up to date"
else
print_info "Running npm install..."
npm install --loglevel warn
print_success "Dependencies installed"
fi
cd "$CLAWMAX_DIR"
echo ""
# ============================================================================
# 8. Generate .env
# ============================================================================
print_header "8. Environment Configuration"
ENV_FILE="$CLAWMAX_DIR/SYSTEM/dashboard/.env"
# Always regenerate to capture new settings
cat > "$ENV_FILE" << ENVEOF
# ClawMax Dashboard Environment — generated by setup.sh
# Edit this file to update keys and settings
# Workspace
OPENCLAW_WORKSPACE=$WORKSPACE
NODE_ENV=development
DASHBOARD_PORT=$SETUP_BACKEND_PORT
CORS_ORIGIN=$SETUP_CORS_ORIGIN
DASHBOARD_APP_URL=$SETUP_FRONTEND_URL
DASHBOARD_PUBLIC_URL=$SETUP_PUBLIC_URL
# Auth mode
ENVEOF
if [ "$AUTH_MODE" = "bypass" ]; then
cat >> "$ENV_FILE" << 'ENVEOF'
DASHBOARD_AUTH_MODE=bypass
BYPASS_OAUTH=true
DASHBOARD_AUTH_DISABLED=true
# ⚠ Dev mode: no login required. Do NOT use in production.
ENVEOF
elif [ "$AUTH_MODE" = "email_otp" ]; then
JWT_SECRET=$(openssl rand -hex 32 2>/dev/null || cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | fold -w 64 | head -n 1)
cat >> "$ENV_FILE" << ENVEOF
DASHBOARD_AUTH_MODE=email_otp
BYPASS_OAUTH=false
OTP_ALLOWED_EMAILS=$OTP_ALLOWED_EMAILS
OTP_EXPIRY_MINUTES=15
OTP_FROM_EMAIL=max@clawmax.ai
OTP_EMAIL_SUBJECT="Your ClawMax login code"
JWT_SECRET=$JWT_SECRET
ENVEOF
if [ "$OTP_DEV_MODE" = "log" ]; then
cat >> "$ENV_FILE" << 'ENVEOF'
OTP_DEV_MODE=log
# Local dev: latest code is also written to .clawmax-otp-dev.json
ENVEOF
else
cat >> "$ENV_FILE" << 'ENVEOF'
# RESEND_API_KEY=re_xxx
ENVEOF
fi
else
JWT_SECRET=$(openssl rand -hex 32 2>/dev/null || cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | fold -w 64 | head -n 1)
cat >> "$ENV_FILE" << ENVEOF
DASHBOARD_AUTH_MODE=github_oauth
BYPASS_OAUTH=false
# Add GitHub OAuth credentials later if you want GitHub login:
# GITHUB_CLIENT_ID=
# GITHUB_CLIENT_SECRET=
JWT_SECRET=$JWT_SECRET
ENVEOF
fi
cat >> "$ENV_FILE" << ENVEOF
# Hosted provider keys are optional here.
# Add them later in BYOK / Keys & Secrets, or uncomment these for shared runtime defaults:
# SYSTEM_OPENAI_API_KEY=sk-...
# SYSTEM_ANTHROPIC_API_KEY=sk-ant-...
# Allow system keys for user agent execution (dev convenience)
ALLOW_SYSTEM_KEYS_FOR_USER_EXECUTION=true
# BYOK: users can also provide keys in-browser via the BYOK wizard
# Agent turns have no timeout: they run until the work finishes or someone stops them.
# CLAWMAX_WORKFLOW_AGENT_TIMEOUT_MS is no longer read -- setting it does nothing.
# Partner integrations shown in Workspaces Integrations (comma-separated)
# Default is opt-in: leave empty until a user selects partners explicitly.
WORKSPACES_INTEGRATIONS_THIRD_PARTIES=
# Extra partner definition roots (optional)
# Built-in partner definitions ship in repo PARTNERS/
CLAWMAX_EXTRA_PARTNER_DIRS=$WORKSPACE/PARTNERS
ENVEOF
cat >> "$ENV_FILE" << 'ENVEOF'
# Comet Opik — optional monitoring (https://www.comet.com/signup)
# Add these later if you want runtime tracing and budget data:
# OPIK_API_KEY=
# OPIK_WORKSPACE=clawmax
# OPIK_PROJECT_NAME=<choose-a-project-name>
ENVEOF
print_success "Created .env file"
if [ "$AUTH_MODE" = "email_otp" ]; then
print_info "OTP login email: $OTP_ALLOWED_EMAILS"
if [ "$OTP_DEV_MODE" = "log" ]; then
print_info "Dev OTP codes will be written to: $CLAWMAX_DIR/.clawmax-otp-dev.json"
else
print_info "Add RESEND_API_KEY to SYSTEM/dashboard/.env to send live OTP emails"
fi
fi
# Dashboard token — keep the canonical file in SYSTEM/dashboard so the server
# and test harness resolve the same token on first boot. Preserve a legacy
# workspace copy as a compatibility fallback for older scripts.
TOKEN_FILE="$CLAWMAX_DIR/SYSTEM/dashboard/.dashboard-token"
LEGACY_TOKEN_FILE="$WORKSPACE/.dashboard-token"
if [ ! -f "$TOKEN_FILE" ]; then
TOKEN=$(openssl rand -hex 32 2>/dev/null || cat /dev/urandom | LC_ALL=C tr -dc 'a-f0-9' | fold -w 64 | head -n 1)
echo "$TOKEN" > "$TOKEN_FILE"
chmod 600 "$TOKEN_FILE"
print_success "Generated dashboard token"
else
TOKEN="$(cat "$TOKEN_FILE" 2>/dev/null || true)"
print_success "Dashboard token exists"
fi
if [ -n "$TOKEN" ]; then
echo "$TOKEN" > "$LEGACY_TOKEN_FILE"
chmod 600 "$LEGACY_TOKEN_FILE"
fi
echo ""
# ============================================================================
# 9. Gateway Setup
# ============================================================================
print_header "9. OpenClaw Gateway"
if [ "$OPENCLAW_INSTALLED" = true ]; then
print_info "The gateway enables agent skills and tool-use (required for GitHub, Senso, etc.)"
# Configure gateway mode
GATEWAY_MODE=$(openclaw config get gateway.mode 2>/dev/null || echo "")
if [ -z "$GATEWAY_MODE" ] || [ "$GATEWAY_MODE" = "unset" ] || [ "$GATEWAY_MODE" = "undefined" ]; then
openclaw config set gateway.mode local 2>/dev/null
print_success "Gateway mode set to local"
else
print_success "Gateway mode: $GATEWAY_MODE"
fi
# Generate auth token if missing
GATEWAY_TOKEN=$(openclaw config get gateway.auth.token 2>/dev/null || echo "")
if [ -z "$GATEWAY_TOKEN" ] || [ "$GATEWAY_TOKEN" = "unset" ] || [ "$GATEWAY_TOKEN" = "undefined" ]; then
# Generate a random token
GW_TOKEN=$(openssl rand -hex 32 2>/dev/null || head -c 64 /dev/urandom | od -An -tx1 | tr -d ' \n')
openclaw config set gateway.auth.token "$GW_TOKEN" 2>/dev/null
print_success "Gateway auth token generated"
else
print_success "Gateway auth token configured"
fi
# Install gateway service on macOS only.
if [ "$(uname -s)" = "Darwin" ]; then
if ! launchctl list 2>/dev/null | grep -q "ai.openclaw.gateway"; then
openclaw gateway install 2>/dev/null && print_success "Gateway service installed" || print_warning "Gateway service install failed"
else
print_success "Gateway service already installed"
fi
else
print_info "Skipping gateway service install on $(uname -s) — using direct gateway startup"
fi
# Start/restart gateway
if openclaw gateway status 2>&1 | grep -q "running"; then
print_success "Gateway already running"
else
print_info "Starting gateway..."
openclaw gateway restart 2>/dev/null &
GW_PID=$!
# Wait up to 15s for gateway to start
GW_READY=false
for i in {1..15}; do
sleep 1
if lsof -ti:18789 > /dev/null 2>&1; then
GW_READY=true
break
fi
echo -n "."
done
echo ""
if [ "$GW_READY" = true ]; then
print_success "Gateway running on port 18789"
else
print_warning "Gateway didn't start in 15s — agents will use --local mode (chat works, skills limited)"
echo -e " Fix later: ${BOLD}openclaw config set gateway.mode local && openclaw gateway restart${NC}"
fi
fi
# Create session store if missing
SESSIONS_DIR="$HOME/.openclaw/agents/main/sessions"
if [ ! -d "$SESSIONS_DIR" ]; then
mkdir -p "$SESSIONS_DIR"
print_success "Created session store"
fi
else
print_warning "Gateway requires OpenClaw CLI — agents will use --local mode"
fi
echo ""
# ============================================================================
# 10. Verify
# ============================================================================
print_header "10. Verifying Setup"
VALID=true
[ ! -d "$WORKSPACE/AGENTS" ] && print_error "AGENTS/ missing" && VALID=false
[ ! -d "$CLAWMAX_DIR/SYSTEM/dashboard/node_modules" ] && print_error "Dependencies missing" && VALID=false
[ ! -f "$ENV_FILE" ] && print_error ".env missing" && VALID=false
# Check critical components
if command -v openclaw &> /dev/null; then
print_success "OpenClaw CLI: $(openclaw --version 2>&1 | head -1 | grep -o '[0-9]\{4\}\.[0-9]*\.[0-9]*' || echo 'installed')"
else
print_warning "OpenClaw CLI not installed — agent features limited"
fi
if lsof -ti:18789 > /dev/null 2>&1; then
print_success "Gateway running on port 18789 (skills enabled)"
else
print_warning "Gateway not running — agents will chat but can't use skills"
fi
if [ "$VALID" = true ]; then
print_success "All core components verified"
else
print_error "Setup incomplete — review errors above"
exit 1
fi
echo ""
# ============================================================================
# 11. Summary
# ============================================================================
print_header "Setup Complete!"
echo -e "${GREEN}✓ ClawMax is ready!${NC}\n"
# Offer to start the dashboard
if [ "$INTERACTIVE" = true ]; then
if ask_yn "Start the dashboard now?"; then
print_info "Starting ClawMax dashboard..."
./SYSTEM/start.sh
echo ""
echo -e " ${GREEN}Dashboard is running!${NC}"
echo -e " Open ${CYAN}http://localhost:5173${NC} in your browser"