forked from zeroclaw-labs/zeroclaw
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·1132 lines (1001 loc) · 30.3 KB
/
install.sh
File metadata and controls
executable file
·1132 lines (1001 loc) · 30.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
#!/usr/bin/env sh
# ZeroClaw installer
# POSIX preamble: ensure bash is available, then re-exec under bash.
set -eu
_have_cmd() { command -v "$1" >/dev/null 2>&1; }
_run_privileged() {
if [ "$(id -u)" -eq 0 ]; then "$@"
elif _have_cmd sudo; then sudo "$@"
else echo "error: sudo is required to install missing dependencies." >&2; exit 1; fi
}
_is_container_runtime() {
[ -f /.dockerenv ] || [ -f /run/.containerenv ] && return 0
[ -r /proc/1/cgroup ] && grep -Eq '(docker|containerd|kubepods|podman|lxc)' /proc/1/cgroup && return 0
return 1
}
_ensure_bash() {
_have_cmd bash && return 0
echo "==> bash not found; attempting to install it"
if _have_cmd apk; then _run_privileged apk add --no-cache bash
elif _have_cmd apt-get; then _run_privileged apt-get update -qq && _run_privileged apt-get install -y bash
elif _have_cmd dnf; then _run_privileged dnf install -y bash
elif _have_cmd pacman; then
if _is_container_runtime; then
_PACMAN_CFG="$(mktemp /tmp/zeroclaw-pacman.XXXXXX.conf)"
cp /etc/pacman.conf "$_PACMAN_CFG"
grep -Eq '^[[:space:]]*DisableSandboxSyscalls([[:space:]]|$)' "$_PACMAN_CFG" || printf '\nDisableSandboxSyscalls\n' >> "$_PACMAN_CFG"
_run_privileged pacman --config "$_PACMAN_CFG" -Sy --noconfirm
_run_privileged pacman --config "$_PACMAN_CFG" -S --noconfirm --needed bash
rm -f "$_PACMAN_CFG"
else
_run_privileged pacman -Sy --noconfirm
_run_privileged pacman -S --noconfirm --needed bash
fi
else echo "error: unsupported package manager; install bash manually and retry." >&2; exit 1; fi
}
# If not already running under bash, ensure bash exists and re-exec.
if [ -z "${BASH_VERSION:-}" ]; then
_ensure_bash
exec bash "$0" "$@"
fi
# --- From here on, we are running under bash ---
set -euo pipefail
info() {
echo "==> $*"
}
warn() {
echo "warning: $*" >&2
}
error() {
echo "error: $*" >&2
}
usage() {
cat <<'USAGE'
ZeroClaw installer
Usage:
./install.sh [options]
Modes:
Default mode installs/builds ZeroClaw only (requires existing Rust toolchain).
Guided mode asks setup questions and configures options interactively.
Optional bootstrap mode can also install system dependencies and Rust.
Options:
--guided Run interactive guided installer
--no-guided Disable guided installer
--docker Run install in Docker-compatible mode and launch onboarding inside the container
--install-system-deps Install build dependencies (Linux/macOS)
--install-rust Install Rust via rustup if missing
--prefer-prebuilt Try latest release binary first; fallback to source build on miss
--prebuilt-only Install only from latest release binary (no source build fallback)
--force-source-build Disable prebuilt flow and always build from source
--onboard Run onboarding after install
--interactive-onboard Run interactive onboarding (implies --onboard)
--api-key <key> API key for non-interactive onboarding
--provider <id> Provider for non-interactive onboarding (default: openrouter)
--model <id> Model for non-interactive onboarding (optional)
--build-first Alias for explicitly enabling separate `cargo build --release --locked`
--skip-build Skip build step (`cargo build --release --locked` or Docker image build)
--skip-install Skip `cargo install --path . --force --locked`
-h, --help Show help
Examples:
./install.sh
./install.sh --guided
./install.sh --install-system-deps --install-rust
./install.sh --prefer-prebuilt
./install.sh --prebuilt-only
./install.sh --onboard --api-key "sk-..." --provider openrouter [--model "openrouter/auto"]
./install.sh --interactive-onboard
./install.sh --docker
# Remote one-liner
curl -fsSL https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/install.sh | bash
Environment:
ZEROCLAW_CONTAINER_CLI Container CLI command (default: docker; auto-fallback: podman)
ZEROCLAW_DOCKER_DATA_DIR Host path for Docker config/workspace persistence
ZEROCLAW_DOCKER_IMAGE Docker image tag to build/run (default: zeroclaw-bootstrap:local)
ZEROCLAW_API_KEY Used when --api-key is not provided
ZEROCLAW_PROVIDER Used when --provider is not provided (default: openrouter)
ZEROCLAW_MODEL Used when --model is not provided
ZEROCLAW_BOOTSTRAP_MIN_RAM_MB Minimum RAM threshold for source build preflight (default: 2048)
ZEROCLAW_BOOTSTRAP_MIN_DISK_MB Minimum free disk threshold for source build preflight (default: 6144)
ZEROCLAW_DISABLE_ALPINE_AUTO_DEPS
Set to 1 to disable Alpine auto-install of missing prerequisites
USAGE
}
have_cmd() {
command -v "$1" >/dev/null 2>&1
}
get_total_memory_mb() {
case "$(uname -s)" in
Linux)
if [[ -r /proc/meminfo ]]; then
awk '/MemTotal:/ {printf "%d\n", $2 / 1024}' /proc/meminfo
fi
;;
Darwin)
if have_cmd sysctl; then
local bytes
bytes="$(sysctl -n hw.memsize 2>/dev/null || true)"
if [[ "$bytes" =~ ^[0-9]+$ ]]; then
echo $((bytes / 1024 / 1024))
fi
fi
;;
esac
}
get_available_disk_mb() {
local path="${1:-.}"
local free_kb
free_kb="$(df -Pk "$path" 2>/dev/null | awk 'NR==2 {print $4}')"
if [[ "$free_kb" =~ ^[0-9]+$ ]]; then
echo $((free_kb / 1024))
fi
}
detect_release_target() {
local os arch
os="$(uname -s)"
arch="$(uname -m)"
case "$os:$arch" in
Linux:x86_64)
echo "x86_64-unknown-linux-gnu"
;;
Linux:aarch64|Linux:arm64)
echo "aarch64-unknown-linux-gnu"
;;
Linux:armv7l|Linux:armv6l)
echo "armv7-unknown-linux-gnueabihf"
;;
Darwin:x86_64)
echo "x86_64-apple-darwin"
;;
Darwin:arm64|Darwin:aarch64)
echo "aarch64-apple-darwin"
;;
*)
return 1
;;
esac
}
should_attempt_prebuilt_for_resources() {
local workspace="${1:-.}"
local min_ram_mb min_disk_mb total_ram_mb free_disk_mb low_resource
min_ram_mb="${ZEROCLAW_BOOTSTRAP_MIN_RAM_MB:-2048}"
min_disk_mb="${ZEROCLAW_BOOTSTRAP_MIN_DISK_MB:-6144}"
total_ram_mb="$(get_total_memory_mb || true)"
free_disk_mb="$(get_available_disk_mb "$workspace" || true)"
low_resource=false
if [[ "$total_ram_mb" =~ ^[0-9]+$ && "$total_ram_mb" -lt "$min_ram_mb" ]]; then
low_resource=true
fi
if [[ "$free_disk_mb" =~ ^[0-9]+$ && "$free_disk_mb" -lt "$min_disk_mb" ]]; then
low_resource=true
fi
if [[ "$low_resource" == true ]]; then
warn "Source build preflight indicates constrained resources."
if [[ "$total_ram_mb" =~ ^[0-9]+$ ]]; then
warn "Detected RAM: ${total_ram_mb}MB (recommended >= ${min_ram_mb}MB for local source builds)."
else
warn "Unable to detect total RAM automatically."
fi
if [[ "$free_disk_mb" =~ ^[0-9]+$ ]]; then
warn "Detected free disk: ${free_disk_mb}MB (recommended >= ${min_disk_mb}MB)."
else
warn "Unable to detect free disk space automatically."
fi
return 0
fi
return 1
}
resolve_asset_url() {
local asset_name="$1"
local api_url="https://api.github.com/repos/zeroclaw-labs/zeroclaw/releases"
local releases_json download_url
# Fetch up to 10 recent releases (includes prereleases) and find the first
# one that contains the requested asset.
releases_json="$(curl -fsSL "${api_url}?per_page=10" 2>/dev/null || true)"
if [[ -z "$releases_json" ]]; then
return 1
fi
# Parse with simple grep/sed — avoids jq dependency.
download_url="$(printf '%s\n' "$releases_json" \
| tr ',' '\n' \
| grep '"browser_download_url"' \
| sed 's/.*"browser_download_url"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' \
| grep "/${asset_name}\$" \
| head -n 1)"
if [[ -z "$download_url" ]]; then
return 1
fi
echo "$download_url"
}
install_prebuilt_binary() {
local target archive_url temp_dir archive_path extracted_bin install_dir asset_name
if ! have_cmd curl; then
warn "curl is required for pre-built binary installation."
return 1
fi
if ! have_cmd tar; then
warn "tar is required for pre-built binary installation."
return 1
fi
target="$(detect_release_target || true)"
if [[ -z "$target" ]]; then
warn "No pre-built binary target mapping for $(uname -s)/$(uname -m)."
return 1
fi
asset_name="zeroclaw-${target}.tar.gz"
# Try the GitHub API first to find the newest release (including prereleases)
# that actually contains the asset, then fall back to /releases/latest/.
archive_url="$(resolve_asset_url "$asset_name" || true)"
if [[ -z "$archive_url" ]]; then
archive_url="https://github.com/zeroclaw-labs/zeroclaw/releases/latest/download/${asset_name}"
fi
temp_dir="$(mktemp -d -t zeroclaw-prebuilt-XXXXXX)"
archive_path="$temp_dir/${asset_name}"
info "Attempting pre-built binary install for target: $target"
if ! curl -fsSL "$archive_url" -o "$archive_path"; then
warn "Could not download release asset: $archive_url"
rm -rf "$temp_dir"
return 1
fi
if ! tar -xzf "$archive_path" -C "$temp_dir"; then
warn "Failed to extract pre-built archive."
rm -rf "$temp_dir"
return 1
fi
extracted_bin="$temp_dir/zeroclaw"
if [[ ! -x "$extracted_bin" ]]; then
extracted_bin="$(find "$temp_dir" -maxdepth 2 -type f -name zeroclaw -perm -u+x | head -n 1 || true)"
fi
if [[ -z "$extracted_bin" || ! -x "$extracted_bin" ]]; then
warn "Archive did not contain an executable zeroclaw binary."
rm -rf "$temp_dir"
return 1
fi
install_dir="$HOME/.cargo/bin"
mkdir -p "$install_dir"
install -m 0755 "$extracted_bin" "$install_dir/zeroclaw"
rm -rf "$temp_dir"
info "Installed pre-built binary to $install_dir/zeroclaw"
if [[ ":$PATH:" != *":$install_dir:"* ]]; then
warn "$install_dir is not in PATH for this shell."
warn "Run: export PATH=\"$install_dir:\$PATH\""
fi
return 0
}
run_privileged() {
if [[ "$(id -u)" -eq 0 ]]; then
"$@"
elif have_cmd sudo; then
sudo "$@"
else
error "sudo is required to install system dependencies."
return 1
fi
}
is_container_runtime() {
if [[ -f /.dockerenv || -f /run/.containerenv ]]; then
return 0
fi
if [[ -r /proc/1/cgroup ]] && grep -Eq '(docker|containerd|kubepods|podman|lxc)' /proc/1/cgroup; then
return 0
fi
return 1
}
run_pacman() {
if ! have_cmd pacman; then
error "pacman is not available."
return 1
fi
if ! is_container_runtime; then
run_privileged pacman "$@"
return $?
fi
local pacman_cfg_tmp=""
local pacman_rc=0
pacman_cfg_tmp="$(mktemp /tmp/zeroclaw-pacman.XXXXXX.conf)"
cp /etc/pacman.conf "$pacman_cfg_tmp"
if ! grep -Eq '^[[:space:]]*DisableSandboxSyscalls([[:space:]]|$)' "$pacman_cfg_tmp"; then
printf '\nDisableSandboxSyscalls\n' >> "$pacman_cfg_tmp"
fi
if run_privileged pacman --config "$pacman_cfg_tmp" "$@"; then
pacman_rc=0
else
pacman_rc=$?
fi
rm -f "$pacman_cfg_tmp"
return "$pacman_rc"
}
ALPINE_PREREQ_PACKAGES=(
bash
build-base
pkgconf
git
curl
openssl-dev
perl
ca-certificates
)
ALPINE_MISSING_PKGS=()
find_missing_alpine_prereqs() {
ALPINE_MISSING_PKGS=()
if ! have_cmd apk; then
return 0
fi
local pkg=""
for pkg in "${ALPINE_PREREQ_PACKAGES[@]}"; do
if ! apk info -e "$pkg" >/dev/null 2>&1; then
ALPINE_MISSING_PKGS+=("$pkg")
fi
done
}
bool_to_word() {
if [[ "$1" == true ]]; then
echo "yes"
else
echo "no"
fi
}
guided_input_stream() {
if [[ -t 0 ]]; then
echo "/dev/stdin"
return 0
fi
if (: </dev/tty) 2>/dev/null; then
echo "/dev/tty"
return 0
fi
return 1
}
guided_read() {
local __target_var="$1"
local __prompt="$2"
local __silent="${3:-false}"
local __input_source=""
local __value=""
if ! __input_source="$(guided_input_stream)"; then
return 1
fi
if [[ "$__silent" == true ]]; then
if ! read -r -s -p "$__prompt" __value <"$__input_source"; then
return 1
fi
else
if ! read -r -p "$__prompt" __value <"$__input_source"; then
return 1
fi
fi
printf -v "$__target_var" '%s' "$__value"
return 0
}
prompt_yes_no() {
local question="$1"
local default_answer="$2"
local prompt=""
local answer=""
if [[ "$default_answer" == "yes" ]]; then
prompt="[Y/n]"
else
prompt="[y/N]"
fi
while true; do
if ! guided_read answer "$question $prompt "; then
error "guided installer input was interrupted."
exit 1
fi
answer="${answer:-$default_answer}"
case "$(printf '%s' "$answer" | tr '[:upper:]' '[:lower:]')" in
y|yes)
return 0
;;
n|no)
return 1
;;
*)
echo "Please answer yes or no."
;;
esac
done
}
install_system_deps() {
info "Installing system dependencies"
case "$(uname -s)" in
Linux)
if have_cmd apk; then
find_missing_alpine_prereqs
if [[ ${#ALPINE_MISSING_PKGS[@]} -eq 0 ]]; then
info "Alpine prerequisites already installed"
else
info "Installing Alpine prerequisites: ${ALPINE_MISSING_PKGS[*]}"
run_privileged apk add --no-cache "${ALPINE_MISSING_PKGS[@]}"
fi
elif have_cmd apt-get; then
run_privileged apt-get update -qq
run_privileged apt-get install -y build-essential pkg-config git curl
elif have_cmd dnf; then
run_privileged dnf install -y \
gcc \
gcc-c++ \
make \
pkgconf-pkg-config \
git \
curl \
openssl-devel \
perl
elif have_cmd pacman; then
run_pacman -Sy --noconfirm
run_pacman -S --noconfirm --needed \
gcc \
make \
pkgconf \
git \
curl \
openssl \
perl \
ca-certificates
else
warn "Unsupported Linux distribution. Install compiler toolchain + pkg-config + git + curl + OpenSSL headers + perl manually."
fi
;;
Darwin)
if ! xcode-select -p >/dev/null 2>&1; then
info "Installing Xcode Command Line Tools"
xcode-select --install || true
cat <<'MSG'
Please complete the Xcode Command Line Tools installation dialog,
then re-run bootstrap.
MSG
exit 0
fi
if ! have_cmd git; then
warn "git is not available. Install git (e.g., Homebrew) and re-run bootstrap."
fi
;;
*)
warn "Unsupported OS for automatic dependency install. Continuing without changes."
;;
esac
}
install_rust_toolchain() {
if have_cmd cargo && have_cmd rustc; then
info "Rust already installed: $(rustc --version)"
return
fi
if ! have_cmd curl; then
error "curl is required to install Rust via rustup."
exit 1
fi
info "Installing Rust via rustup"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
if [[ -f "$HOME/.cargo/env" ]]; then
# shellcheck disable=SC1090
source "$HOME/.cargo/env"
fi
if ! have_cmd cargo; then
error "Rust installation completed but cargo is still unavailable in PATH."
error "Run: source \"$HOME/.cargo/env\""
exit 1
fi
}
run_guided_installer() {
local os_name="$1"
local provider_input=""
local model_input=""
local api_key_input=""
if ! guided_input_stream >/dev/null; then
error "guided installer requires an interactive terminal."
error "Run from a terminal, or pass --no-guided with explicit flags."
exit 1
fi
echo
echo "ZeroClaw guided installer"
echo "Answer a few questions, then the installer will run automatically."
echo
if [[ "$os_name" == "Linux" ]]; then
if prompt_yes_no "Install Linux build dependencies (toolchain/pkg-config/git/curl)?" "yes"; then
INSTALL_SYSTEM_DEPS=true
fi
else
if prompt_yes_no "Install system dependencies for $os_name?" "no"; then
INSTALL_SYSTEM_DEPS=true
fi
fi
if have_cmd cargo && have_cmd rustc; then
info "Detected Rust toolchain: $(rustc --version)"
else
if prompt_yes_no "Rust toolchain not found. Install Rust via rustup now?" "yes"; then
INSTALL_RUST=true
fi
fi
if prompt_yes_no "Run a separate prebuild before install?" "yes"; then
SKIP_BUILD=false
else
SKIP_BUILD=true
fi
if prompt_yes_no "Install zeroclaw into cargo bin now?" "yes"; then
SKIP_INSTALL=false
else
SKIP_INSTALL=true
fi
if prompt_yes_no "Run onboarding after install?" "no"; then
RUN_ONBOARD=true
if prompt_yes_no "Use interactive onboarding?" "yes"; then
INTERACTIVE_ONBOARD=true
else
INTERACTIVE_ONBOARD=false
if ! guided_read provider_input "Provider [$PROVIDER]: "; then
error "guided installer input was interrupted."
exit 1
fi
if [[ -n "$provider_input" ]]; then
PROVIDER="$provider_input"
fi
if ! guided_read model_input "Model [${MODEL:-leave empty}]: "; then
error "guided installer input was interrupted."
exit 1
fi
if [[ -n "$model_input" ]]; then
MODEL="$model_input"
fi
if [[ -z "$API_KEY" ]]; then
if ! guided_read api_key_input "API key (hidden, leave empty to switch to interactive onboarding): " true; then
echo
error "guided installer input was interrupted."
exit 1
fi
echo
if [[ -n "$api_key_input" ]]; then
API_KEY="$api_key_input"
else
warn "No API key entered. Using interactive onboarding instead."
INTERACTIVE_ONBOARD=true
fi
fi
fi
fi
echo
info "Installer plan"
local install_binary=true
local build_first=false
if [[ "$SKIP_INSTALL" == true ]]; then
install_binary=false
fi
if [[ "$SKIP_BUILD" == false ]]; then
build_first=true
fi
echo " docker-mode: $(bool_to_word "$DOCKER_MODE")"
echo " install-system-deps: $(bool_to_word "$INSTALL_SYSTEM_DEPS")"
echo " install-rust: $(bool_to_word "$INSTALL_RUST")"
echo " build-first: $(bool_to_word "$build_first")"
echo " install-binary: $(bool_to_word "$install_binary")"
echo " onboard: $(bool_to_word "$RUN_ONBOARD")"
if [[ "$RUN_ONBOARD" == true ]]; then
echo " interactive-onboard: $(bool_to_word "$INTERACTIVE_ONBOARD")"
if [[ "$INTERACTIVE_ONBOARD" == false ]]; then
echo " provider: $PROVIDER"
if [[ -n "$MODEL" ]]; then
echo " model: $MODEL"
fi
fi
fi
echo
if ! prompt_yes_no "Proceed with this install plan?" "yes"; then
info "Installation canceled by user."
exit 0
fi
}
resolve_container_cli() {
local requested_cli
requested_cli="${ZEROCLAW_CONTAINER_CLI:-docker}"
if have_cmd "$requested_cli"; then
CONTAINER_CLI="$requested_cli"
return 0
fi
if [[ "$requested_cli" == "docker" ]] && have_cmd podman; then
warn "docker CLI not found; falling back to podman."
CONTAINER_CLI="podman"
return 0
fi
error "Container CLI '$requested_cli' is not installed."
if [[ "$requested_cli" != "docker" ]]; then
error "Set ZEROCLAW_CONTAINER_CLI to an installed Docker-compatible CLI (e.g., docker or podman)."
else
error "Install Docker, install podman, or set ZEROCLAW_CONTAINER_CLI to an available Docker-compatible CLI."
fi
exit 1
}
ensure_docker_ready() {
resolve_container_cli
if ! "$CONTAINER_CLI" info >/dev/null 2>&1; then
error "Container runtime is not reachable via '$CONTAINER_CLI'."
error "Start the container runtime and re-run bootstrap."
exit 1
fi
}
run_docker_bootstrap() {
local docker_image docker_data_dir default_data_dir fallback_image
local config_mount workspace_mount
local -a container_run_user_args container_run_namespace_args
docker_image="${ZEROCLAW_DOCKER_IMAGE:-zeroclaw-bootstrap:local}"
fallback_image="ghcr.io/zeroclaw-labs/zeroclaw:latest"
if [[ "$TEMP_CLONE" == true ]]; then
default_data_dir="$HOME/.zeroclaw-docker"
else
default_data_dir="$WORK_DIR/.zeroclaw-docker"
fi
docker_data_dir="${ZEROCLAW_DOCKER_DATA_DIR:-$default_data_dir}"
DOCKER_DATA_DIR="$docker_data_dir"
mkdir -p "$docker_data_dir/.zeroclaw" "$docker_data_dir/workspace"
if [[ "$SKIP_INSTALL" == true ]]; then
warn "--skip-install has no effect with --docker."
fi
if [[ "$SKIP_BUILD" == false ]]; then
info "Building Docker image ($docker_image)"
DOCKER_BUILDKIT=1 "$CONTAINER_CLI" build --target release -t "$docker_image" "$WORK_DIR"
else
info "Skipping Docker image build"
if ! "$CONTAINER_CLI" image inspect "$docker_image" >/dev/null 2>&1; then
warn "Local Docker image ($docker_image) was not found."
info "Pulling official ZeroClaw image ($fallback_image)"
if ! "$CONTAINER_CLI" pull "$fallback_image"; then
error "Failed to pull fallback Docker image: $fallback_image"
error "Run without --skip-build to build locally, or verify access to GHCR."
exit 1
fi
if [[ "$docker_image" != "$fallback_image" ]]; then
info "Tagging fallback image as $docker_image"
"$CONTAINER_CLI" tag "$fallback_image" "$docker_image"
fi
fi
fi
config_mount="$docker_data_dir/.zeroclaw:/zeroclaw-data/.zeroclaw"
workspace_mount="$docker_data_dir/workspace:/zeroclaw-data/workspace"
if [[ "$CONTAINER_CLI" == "podman" ]]; then
config_mount+=":Z"
workspace_mount+=":Z"
container_run_namespace_args=(--userns keep-id)
container_run_user_args=(--user "$(id -u):$(id -g)")
else
container_run_namespace_args=()
container_run_user_args=(--user "$(id -u):$(id -g)")
fi
info "Docker data directory: $docker_data_dir"
info "Container CLI: $CONTAINER_CLI"
local onboard_cmd=()
if [[ "$INTERACTIVE_ONBOARD" == true ]]; then
info "Launching interactive onboarding in container"
onboard_cmd=(onboard --interactive)
else
if [[ -z "$API_KEY" ]]; then
cat <<'MSG'
==> Onboarding requested, but API key not provided.
Use either:
--api-key "sk-..."
or:
ZEROCLAW_API_KEY="sk-..." ./install.sh --docker
or run interactive:
./install.sh --docker --interactive-onboard
MSG
exit 1
fi
if [[ -n "$MODEL" ]]; then
info "Launching quick onboarding in container (provider: $PROVIDER, model: $MODEL)"
else
info "Launching quick onboarding in container (provider: $PROVIDER)"
fi
onboard_cmd=(onboard --api-key "$API_KEY" --provider "$PROVIDER")
if [[ -n "$MODEL" ]]; then
onboard_cmd+=(--model "$MODEL")
fi
fi
"$CONTAINER_CLI" run --rm -it \
"${container_run_namespace_args[@]+"${container_run_namespace_args[@]}"}" \
"${container_run_user_args[@]}" \
-e HOME=/zeroclaw-data \
-e ZEROCLAW_WORKSPACE=/zeroclaw-data/workspace \
-v "$config_mount" \
-v "$workspace_mount" \
"$docker_image" \
"${onboard_cmd[@]}"
}
SCRIPT_PATH="${BASH_SOURCE[0]:-$0}"
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" >/dev/null 2>&1 && pwd || pwd)"
ROOT_DIR="$SCRIPT_DIR"
REPO_URL="https://github.com/zeroclaw-labs/zeroclaw.git"
ORIGINAL_ARG_COUNT=$#
GUIDED_MODE="auto"
DOCKER_MODE=false
INSTALL_SYSTEM_DEPS=false
INSTALL_RUST=false
PREFER_PREBUILT=false
PREBUILT_ONLY=false
FORCE_SOURCE_BUILD=false
RUN_ONBOARD=false
INTERACTIVE_ONBOARD=false
SKIP_BUILD=false
SKIP_INSTALL=false
PREBUILT_INSTALLED=false
CONTAINER_CLI="${ZEROCLAW_CONTAINER_CLI:-docker}"
API_KEY="${ZEROCLAW_API_KEY:-}"
PROVIDER="${ZEROCLAW_PROVIDER:-openrouter}"
MODEL="${ZEROCLAW_MODEL:-}"
while [[ $# -gt 0 ]]; do
case "$1" in
--guided)
GUIDED_MODE="on"
shift
;;
--no-guided)
GUIDED_MODE="off"
shift
;;
--docker)
DOCKER_MODE=true
shift
;;
--install-system-deps)
INSTALL_SYSTEM_DEPS=true
shift
;;
--install-rust)
INSTALL_RUST=true
shift
;;
--prefer-prebuilt)
PREFER_PREBUILT=true
shift
;;
--prebuilt-only)
PREBUILT_ONLY=true
shift
;;
--force-source-build)
FORCE_SOURCE_BUILD=true
shift
;;
--onboard)
RUN_ONBOARD=true
shift
;;
--interactive-onboard)
RUN_ONBOARD=true
INTERACTIVE_ONBOARD=true
shift
;;
--api-key)
API_KEY="${2:-}"
[[ -n "$API_KEY" ]] || {
error "--api-key requires a value"
exit 1
}
shift 2
;;
--provider)
PROVIDER="${2:-}"
[[ -n "$PROVIDER" ]] || {
error "--provider requires a value"
exit 1
}
shift 2
;;
--model)
MODEL="${2:-}"
[[ -n "$MODEL" ]] || {
error "--model requires a value"
exit 1
}
shift 2
;;
--build-first)
SKIP_BUILD=false
shift
;;
--skip-build)
SKIP_BUILD=true
shift
;;
--skip-install)
SKIP_INSTALL=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
error "unknown option: $1"
echo
usage
exit 1
;;
esac
done
OS_NAME="$(uname -s)"
if [[ "$GUIDED_MODE" == "auto" ]]; then
if [[ "$OS_NAME" == "Linux" && "$ORIGINAL_ARG_COUNT" -eq 0 && -t 0 && -t 1 ]]; then
GUIDED_MODE="on"
else
GUIDED_MODE="off"
fi
fi
if [[ "$DOCKER_MODE" == true && "$GUIDED_MODE" == "on" ]]; then
warn "--guided is ignored with --docker."
GUIDED_MODE="off"
fi
if [[ "$GUIDED_MODE" == "on" ]]; then
run_guided_installer "$OS_NAME"
fi
if [[ "$DOCKER_MODE" == true ]]; then
if [[ "$INSTALL_SYSTEM_DEPS" == true ]]; then
warn "--install-system-deps is ignored with --docker."
fi
if [[ "$INSTALL_RUST" == true ]]; then
warn "--install-rust is ignored with --docker."
fi
else
if [[ "$OS_NAME" == "Linux" && -z "${ZEROCLAW_DISABLE_ALPINE_AUTO_DEPS:-}" ]] && have_cmd apk; then
find_missing_alpine_prereqs
if [[ ${#ALPINE_MISSING_PKGS[@]} -gt 0 && "$INSTALL_SYSTEM_DEPS" == false ]]; then
info "Detected Alpine with missing prerequisites: ${ALPINE_MISSING_PKGS[*]}"
info "Auto-enabling system dependency installation (set ZEROCLAW_DISABLE_ALPINE_AUTO_DEPS=1 to disable)."
INSTALL_SYSTEM_DEPS=true
fi
fi
if [[ "$INSTALL_SYSTEM_DEPS" == true ]]; then
install_system_deps
fi
if [[ "$INSTALL_RUST" == true ]]; then
install_rust_toolchain
fi
fi
WORK_DIR="$ROOT_DIR"
TEMP_CLONE=false
TEMP_DIR=""
cleanup() {
if [[ "$TEMP_CLONE" == true && -n "$TEMP_DIR" && -d "$TEMP_DIR" ]]; then
rm -rf "$TEMP_DIR"
fi
}
trap cleanup EXIT
# Support three launch modes:
# Support two launch modes:
# 1) ./install.sh from repo root
# 2) curl | bash (no local repo => temporary clone)
if [[ ! -f "$WORK_DIR/Cargo.toml" ]]; then
if [[ -f "$(pwd)/Cargo.toml" ]]; then
WORK_DIR="$(pwd)"
else
if ! have_cmd git; then
error "git is required when running bootstrap outside a local repository checkout."
if [[ "$INSTALL_SYSTEM_DEPS" == false ]]; then
error "Re-run with --install-system-deps or install git manually."
fi
exit 1
fi
TEMP_DIR="$(mktemp -d -t zeroclaw-bootstrap-XXXXXX)"
info "No local repository detected; cloning latest master branch"
git clone --depth 1 --branch master "$REPO_URL" "$TEMP_DIR"
WORK_DIR="$TEMP_DIR"
TEMP_CLONE=true
fi
fi
info "ZeroClaw installer"
echo " workspace: $WORK_DIR"
cd "$WORK_DIR"
if [[ "$FORCE_SOURCE_BUILD" == true ]]; then
PREFER_PREBUILT=false
PREBUILT_ONLY=false