diff --git a/.github/workflows/pub-release.yml b/.github/workflows/pub-release.yml index 7cdb853..193bfd5 100644 --- a/.github/workflows/pub-release.yml +++ b/.github/workflows/pub-release.yml @@ -27,15 +27,45 @@ jobs: - os: ubuntu-latest target: x86_64-unknown-linux-gnu artifact: zeroclaw - - os: macos-latest + archive_ext: tar.gz + cross_compiler: "" + linker_env: "" + linker: "" + - os: ubuntu-latest + target: aarch64-unknown-linux-gnu + artifact: zeroclaw + archive_ext: tar.gz + cross_compiler: gcc-aarch64-linux-gnu + linker_env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER + linker: aarch64-linux-gnu-gcc + - os: ubuntu-latest + target: armv7-unknown-linux-gnueabihf + artifact: zeroclaw + archive_ext: tar.gz + cross_compiler: gcc-arm-linux-gnueabihf + linker_env: CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER + linker: arm-linux-gnueabihf-gcc + - os: macos-15-intel target: x86_64-apple-darwin artifact: zeroclaw - - os: macos-latest + archive_ext: tar.gz + cross_compiler: "" + linker_env: "" + linker: "" + - os: macos-14 target: aarch64-apple-darwin artifact: zeroclaw + archive_ext: tar.gz + cross_compiler: "" + linker_env: "" + linker: "" - os: windows-latest target: x86_64-pc-windows-msvc artifact: zeroclaw.exe + archive_ext: zip + cross_compiler: "" + linker_env: "" + linker: "" steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -46,20 +76,41 @@ jobs: - uses: useblacksmith/rust-cache@f53e7f127245d2a269b3d90879ccf259876842d5 # v3 + - name: Install cross-compilation toolchain (Linux) + if: runner.os == 'Linux' && matrix.cross_compiler != '' + run: | + sudo apt-get update -qq + sudo apt-get install -y ${{ matrix.cross_compiler }} + - name: Build release - run: cargo build --release --locked --target ${{ matrix.target }} + env: + LINKER_ENV: ${{ matrix.linker_env }} + LINKER: ${{ matrix.linker }} + run: | + if [ -n "$LINKER_ENV" ] && [ -n "$LINKER" ]; then + echo "Using linker override: $LINKER_ENV=$LINKER" + export "$LINKER_ENV=$LINKER" + fi + cargo build --release --locked --target ${{ matrix.target }} - name: Check binary size (Unix) if: runner.os != 'Windows' run: | - SIZE=$(stat -f%z target/${{ matrix.target }}/release/${{ matrix.artifact }} 2>/dev/null || stat -c%s target/${{ matrix.target }}/release/${{ matrix.artifact }}) + BIN="target/${{ matrix.target }}/release/${{ matrix.artifact }}" + if [ ! -f "$BIN" ]; then + echo "::error::Expected binary not found: $BIN" + exit 1 + fi + SIZE=$(stat -f%z "$BIN" 2>/dev/null || stat -c%s "$BIN") SIZE_MB=$((SIZE / 1024 / 1024)) echo "Binary size: ${SIZE_MB}MB ($SIZE bytes)" echo "### Binary Size: ${{ matrix.target }}" >> "$GITHUB_STEP_SUMMARY" echo "- Size: ${SIZE_MB}MB ($SIZE bytes)" >> "$GITHUB_STEP_SUMMARY" - if [ "$SIZE" -gt 15728640 ]; then - echo "::error::Binary exceeds 15MB hard limit (${SIZE_MB}MB)" + if [ "$SIZE" -gt 41943040 ]; then + echo "::error::Binary exceeds 40MB safeguard (${SIZE_MB}MB)" exit 1 + elif [ "$SIZE" -gt 15728640 ]; then + echo "::warning::Binary exceeds 15MB advisory target (${SIZE_MB}MB)" elif [ "$SIZE" -gt 5242880 ]; then echo "::warning::Binary exceeds 5MB target (${SIZE_MB}MB)" else @@ -70,19 +121,19 @@ jobs: if: runner.os != 'Windows' run: | cd target/${{ matrix.target }}/release - tar czf ../../../zeroclaw-${{ matrix.target }}.tar.gz ${{ matrix.artifact }} + tar czf ../../../zeroclaw-${{ matrix.target }}.${{ matrix.archive_ext }} ${{ matrix.artifact }} - name: Package (Windows) if: runner.os == 'Windows' run: | cd target/${{ matrix.target }}/release - 7z a ../../../zeroclaw-${{ matrix.target }}.zip ${{ matrix.artifact }} + 7z a ../../../zeroclaw-${{ matrix.target }}.${{ matrix.archive_ext }} ${{ matrix.artifact }} - name: Upload artifact uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: zeroclaw-${{ matrix.target }} - path: zeroclaw-${{ matrix.target }}.* + path: zeroclaw-${{ matrix.target }}.${{ matrix.archive_ext }} retention-days: 7 publish: diff --git a/README.md b/README.md index 53fbc30..629842a 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Local machine quick benchmark (macOS arm64, Feb 2026) normalized for 0.8GHz edge | **Binary Size** | ~28MB (dist) | N/A (Scripts) | ~8MB | **3.4 MB** | | **Cost** | Mac Mini $599 | Linux SBC ~$50 | Linux Board $10 | **Any hardware $10** | -> Notes: ZeroClaw results are measured on release builds using `/usr/bin/time -l`. OpenClaw requires Node.js runtime (typically ~390MB additional memory overhead), while NanoBot requires Python runtime. PicoClaw and ZeroClaw are static binaries. +> Notes: ZeroClaw results are measured on release builds using `/usr/bin/time -l`. OpenClaw requires Node.js runtime (typically ~390MB additional memory overhead), while NanoBot requires Python runtime. PicoClaw and ZeroClaw are static binaries. The RAM figures above are runtime memory; build-time compilation requirements are higher.

ZeroClaw vs OpenClaw Comparison @@ -173,11 +173,32 @@ Or skip the steps above and install everything (system deps, Rust, ZeroClaw) in curl -LsSf https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/main/scripts/install.sh | bash ``` +#### Compilation resource requirements + +Building from source needs more resources than running the resulting binary: + +| Resource | Minimum | Recommended | +|---|---|---| +| **RAM + swap** | 2 GB | 4 GB+ | +| **Free disk** | 6 GB | 10 GB+ | + +If your host is below the minimum, use pre-built binaries: + +```bash +./bootstrap.sh --prefer-prebuilt +``` + +To require binary-only install with no source fallback: + +```bash +./bootstrap.sh --prebuilt-only +``` + #### Optional - **Docker** — required only if using the [Docker sandboxed runtime](#runtime-support-current) (`runtime.kind = "docker"`). Install via your package manager or [docker.com](https://docs.docker.com/engine/install/). -> **Note:** The default `cargo build --release` uses `codegen-units=1` for compatibility with low-memory devices (e.g., Raspberry Pi 3 with 1GB RAM). For faster builds on powerful machines, use `cargo build --profile release-fast`. +> **Note:** The default `cargo build --release` uses `codegen-units=1` to lower peak compile pressure. For faster builds on powerful machines, use `cargo build --profile release-fast`. @@ -201,6 +222,12 @@ cd zeroclaw # Optional: bootstrap dependencies + Rust on fresh machines ./bootstrap.sh --install-system-deps --install-rust +# Optional: pre-built binary first (recommended on low-RAM/low-disk hosts) +./bootstrap.sh --prefer-prebuilt + +# Optional: binary-only install (no source build fallback) +./bootstrap.sh --prebuilt-only + # Optional: run onboarding in the same flow ./bootstrap.sh --onboard --api-key "sk-..." --provider openrouter [--model "openrouter/auto"] @@ -216,6 +243,25 @@ curl -fsSL https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/main/scripts Details: [`docs/one-click-bootstrap.md`](docs/one-click-bootstrap.md) (toolchain mode may request `sudo` for system packages). +### Pre-built binaries + +Release assets are published for: + +- Linux: `x86_64`, `aarch64`, `armv7` +- macOS: `x86_64`, `aarch64` +- Windows: `x86_64` + +Download the latest assets from: + + +Example (ARM64 Linux): + +```bash +curl -fsSLO https://github.com/zeroclaw-labs/zeroclaw/releases/latest/download/zeroclaw-aarch64-unknown-linux-gnu.tar.gz +tar xzf zeroclaw-aarch64-unknown-linux-gnu.tar.gz +install -m 0755 zeroclaw "$HOME/.cargo/bin/zeroclaw" +``` + ```bash git clone https://github.com/zeroclaw-labs/zeroclaw.git cd zeroclaw diff --git a/docs/one-click-bootstrap.md b/docs/one-click-bootstrap.md index 25cd108..c9001f7 100644 --- a/docs/one-click-bootstrap.md +++ b/docs/one-click-bootstrap.md @@ -2,7 +2,7 @@ This page defines the fastest supported path to install and initialize ZeroClaw. -Last verified: **February 18, 2026**. +Last verified: **February 20, 2026**. ## Option 0: Homebrew (macOS/Linuxbrew) @@ -23,6 +23,31 @@ What it does by default: 1. `cargo build --release --locked` 2. `cargo install --path . --force --locked` +### Resource preflight and pre-built flow + +Source builds typically require at least: + +- **2 GB RAM + swap** +- **6 GB free disk** + +When resources are constrained, bootstrap now attempts a pre-built binary first. + +```bash +./bootstrap.sh --prefer-prebuilt +``` + +To require binary-only installation and fail if no compatible release asset exists: + +```bash +./bootstrap.sh --prebuilt-only +``` + +To bypass pre-built flow and force source compilation: + +```bash +./bootstrap.sh --force-source-build +``` + ## Dual-mode bootstrap Default behavior is **app-only** (build/install ZeroClaw) and expects existing Rust toolchain. @@ -37,6 +62,9 @@ Notes: - `--install-system-deps` installs compiler/build prerequisites (may require `sudo`). - `--install-rust` installs Rust via `rustup` when missing. +- `--prefer-prebuilt` tries release binary download first, then falls back to source build. +- `--prebuilt-only` disables source fallback. +- `--force-source-build` disables pre-built flow entirely. ## Option B: Remote one-liner diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index ab7cfbf..7fd02aa 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -2,7 +2,7 @@ This guide focuses on common setup/runtime failures and fast resolution paths. -Last verified: **February 19, 2026**. +Last verified: **February 20, 2026**. ## Installation / Bootstrap @@ -32,6 +32,48 @@ Fix: ./bootstrap.sh --install-system-deps ``` +### Build fails on low-RAM / low-disk hosts + +Symptoms: + +- `cargo build --release` is killed (`signal: 9`, OOM killer, or `cannot allocate memory`) +- Build crashes after adding swap because disk space runs out + +Why this happens: + +- Runtime memory (<5MB for common operations) is not the same as compile-time memory. +- Full source build can require **2 GB RAM + swap** and **6+ GB free disk**. +- Enabling swap on a tiny disk can avoid RAM OOM but still fail due to disk exhaustion. + +Preferred path for constrained machines: + +```bash +./bootstrap.sh --prefer-prebuilt +``` + +Binary-only mode (no source fallback): + +```bash +./bootstrap.sh --prebuilt-only +``` + +If you must compile from source on constrained hosts: + +1. Add swap only if you also have enough free disk for both swap + build output. +1. Limit cargo parallelism: + +```bash +CARGO_BUILD_JOBS=1 cargo build --release --locked +``` + +1. Reduce heavy features when Matrix is not required: + +```bash +cargo build --release --locked --no-default-features --features hardware +``` + +1. Cross-compile on a stronger machine and copy the binary to the target host. + ### Build is very slow or appears stuck Symptoms: diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index f256fa6..a081a61 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -28,6 +28,9 @@ Options: --docker Run bootstrap in Docker 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 API key for non-interactive onboarding @@ -41,6 +44,8 @@ Examples: ./bootstrap.sh ./bootstrap.sh --docker ./bootstrap.sh --install-system-deps --install-rust + ./bootstrap.sh --prefer-prebuilt + ./bootstrap.sh --prebuilt-only ./bootstrap.sh --onboard --api-key "sk-..." --provider openrouter [--model "openrouter/auto"] ./bootstrap.sh --interactive-onboard @@ -53,6 +58,8 @@ Environment: 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) USAGE } @@ -60,6 +67,155 @@ 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 +} + +install_prebuilt_binary() { + local target archive_url temp_dir archive_path extracted_bin install_dir + + 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 + + archive_url="https://github.com/zeroclaw-labs/zeroclaw/releases/latest/download/zeroclaw-${target}.tar.gz" + temp_dir="$(mktemp -d -t zeroclaw-prebuilt-XXXXXX)" + archive_path="$temp_dir/zeroclaw-${target}.tar.gz" + + 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 "$@" @@ -221,10 +377,14 @@ REPO_URL="https://github.com/zeroclaw-labs/zeroclaw.git" 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 API_KEY="${ZEROCLAW_API_KEY:-}" PROVIDER="${ZEROCLAW_PROVIDER:-openrouter}" MODEL="${ZEROCLAW_MODEL:-}" @@ -243,6 +403,18 @@ while [[ $# -gt 0 ]]; do 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 @@ -314,16 +486,6 @@ else fi fi -if [[ "$DOCKER_MODE" == false ]] && ! have_cmd cargo; then - error "cargo is not installed." - cat <<'MSG' >&2 -Install Rust first: https://rustup.rs/ -or re-run with: - ./bootstrap.sh --install-rust -MSG - exit 1 -fi - WORK_DIR="$ROOT_DIR" TEMP_CLONE=false TEMP_DIR="" @@ -364,6 +526,15 @@ echo " workspace: $WORK_DIR" cd "$WORK_DIR" +if [[ "$FORCE_SOURCE_BUILD" == true ]]; then + PREFER_PREBUILT=false + PREBUILT_ONLY=false +fi + +if [[ "$PREBUILT_ONLY" == true ]]; then + PREFER_PREBUILT=true +fi + if [[ "$DOCKER_MODE" == true ]]; then ensure_docker_ready if [[ "$RUN_ONBOARD" == false ]]; then @@ -389,6 +560,39 @@ DONE exit 0 fi +if [[ "$FORCE_SOURCE_BUILD" == false ]]; then + if [[ "$PREFER_PREBUILT" == false && "$PREBUILT_ONLY" == false ]]; then + if should_attempt_prebuilt_for_resources "$WORK_DIR"; then + info "Attempting pre-built binary first due to resource preflight." + PREFER_PREBUILT=true + fi + fi + + if [[ "$PREFER_PREBUILT" == true ]]; then + if install_prebuilt_binary; then + PREBUILT_INSTALLED=true + SKIP_BUILD=true + SKIP_INSTALL=true + elif [[ "$PREBUILT_ONLY" == true ]]; then + error "Pre-built-only mode requested, but no compatible release asset is available." + error "Try again later, or run with --force-source-build on a machine with enough RAM/disk." + exit 1 + else + warn "Pre-built install unavailable; falling back to source build." + fi + fi +fi + +if [[ "$PREBUILT_INSTALLED" == false && ( "$SKIP_BUILD" == false || "$SKIP_INSTALL" == false ) ]] && ! have_cmd cargo; then + error "cargo is not installed." + cat <<'MSG' >&2 +Install Rust first: https://rustup.rs/ +or re-run with: + ./bootstrap.sh --install-rust +MSG + exit 1 +fi + if [[ "$SKIP_BUILD" == false ]]; then info "Building release binary" cargo build --release --locked @@ -406,6 +610,8 @@ fi ZEROCLAW_BIN="" if have_cmd zeroclaw; then ZEROCLAW_BIN="zeroclaw" +elif [[ -x "$HOME/.cargo/bin/zeroclaw" ]]; then + ZEROCLAW_BIN="$HOME/.cargo/bin/zeroclaw" elif [[ -x "$WORK_DIR/target/release/zeroclaw" ]]; then ZEROCLAW_BIN="$WORK_DIR/target/release/zeroclaw" fi