#!/usr/bin/env sh # basis-worker · install.sh — one-command setup for a Basis contributor worker. # # WHAT THIS DOES (read-only + idempotent; it never spends, signs, or commits): # 1. Detects OS (Linux / macOS / WSL), arch (x64 / arm64), and the GPU/runtime # (NVIDIA + CUDA + VRAM, Apple Metal, AMD/ROCm, or CPU fallback). # 2. Detects Docker + the NVIDIA Container Toolkit, Ollama / vLLM, Node, Python. # 3. Recommends a runtime + a model class for the detected hardware. # 4. Fetches the basis-worker skill package (SKILL.md + scripts) from # https://basis.watch into ./basis-worker-skill so an agent can run the flow. # 5. Prints the exact next steps: doctor → register → publish offer → start. # # HARD SAFETY RULES (enforced by construction): # - It NEVER asks for, accepts, generates, prints, or stores a PRIVATE KEY or a # SEED PHRASE. The reward wallet is a PUBLIC address only (0x + 40 hex), and it # is supplied LATER, by you, to the register step — never to this installer. # - It NEVER prints, writes, or transmits a worker token / auth key / secret. # - It writes nothing outside the current directory and makes no on-chain action. # - It is safe to re-run: existing files are refreshed, never destroyed wholesale. # # USAGE # curl -fsSL https://basis.watch/api/skills/basis-worker/install.sh -o install.sh # curl -fsSL https://basis.watch/api/skills/basis-worker/install.sh.sha256 -o install.sh.sha256 # sha256sum -c install.sh.sha256 # verify before running (recommended) # sh install.sh # detect + fetch the skill (no install of GPU drivers) # # Flags: # --dir where to place the fetched skill (default ./basis-worker-skill) # --base Basis base URL (default https://basis.watch) # --no-fetch detect + recommend only; do not download the skill package # --help show this help # # This script does NOT install GPU drivers, Docker, Ollama, or vLLM for you — it # detects them and tells you exactly what is missing and where to get it. Driver / # toolkit installation is a deliberate, host-specific operator step. set -eu BASE_URL="https://basis.watch" SKILL_DIR="./basis-worker-skill" DO_FETCH=1 # ── tiny helpers ────────────────────────────────────────────────────────────── say() { printf '%s\n' "$*"; } ok() { printf ' \033[32m✓\033[0m %s\n' "$*"; } no() { printf ' \033[2m·\033[0m %s\n' "$*"; } warn() { printf ' \033[33m!\033[0m %s\n' "$*"; } hdr() { printf '\n\033[1m%s\033[0m\n' "$*"; } have() { command -v "$1" >/dev/null 2>&1; } # ── args ────────────────────────────────────────────────────────────────────── while [ $# -gt 0 ]; do case "$1" in --dir) SKILL_DIR="${2:?--dir needs a path}"; shift 2 ;; --base) BASE_URL="${2:?--base needs a url}"; shift 2 ;; --no-fetch) DO_FETCH=0; shift ;; --help|-h) sed -n '2,38p' "$0" | sed 's/^# \{0,1\}//' exit 0 ;; *) warn "ignoring unknown argument: $1"; shift ;; esac done BASE_URL="$(printf '%s' "$BASE_URL" | sed 's#/$##')" say "" say " basis-worker · installer" say " ────────────────────────" say " Turn this machine into a Basis contributor inference worker." say " Reward wallet = a PUBLIC address only. This installer handles NO key/seed." # ── 1. OS + arch ────────────────────────────────────────────────────────────── hdr "1. Host" UNAME_S="$(uname -s 2>/dev/null || echo unknown)" UNAME_M="$(uname -m 2>/dev/null || echo unknown)" OS="unknown"; IS_WSL=0 case "$UNAME_S" in Linux) OS="linux"; grep -qi microsoft /proc/version 2>/dev/null && IS_WSL=1 ;; Darwin) OS="macos" ;; CYGWIN*|MINGW*|MSYS*) OS="windows" ;; esac ARCH="other" case "$UNAME_M" in x86_64|amd64) ARCH="x64" ;; arm64|aarch64) ARCH="arm64" ;; esac if [ "$IS_WSL" = 1 ]; then ok "OS: Linux (WSL) / $ARCH"; else ok "OS: $OS / $ARCH"; fi # ── 2. Node + Python ────────────────────────────────────────────────────────── hdr "2. Runtimes" NODE_OK=0 if have node; then NODE_V="$(node -p 'process.versions.node' 2>/dev/null || echo '?')" NODE_MAJOR="$(printf '%s' "$NODE_V" | cut -d. -f1)" if [ "${NODE_MAJOR:-0}" -ge 20 ] 2>/dev/null; then ok "Node $NODE_V"; NODE_OK=1 else warn "Node $NODE_V is too old — the worker tooling needs Node >= 20 (https://nodejs.org)"; fi else no "Node not found — install Node >= 20 (https://nodejs.org) to run the skill scripts" fi if have python3; then ok "Python $(python3 -V 2>&1 | awk '{print $2}')"; else no "python3 not found (only needed for a vLLM pip install)"; fi # ── 3. GPU / accelerator ────────────────────────────────────────────────────── hdr "3. Accelerator" GPU_CLASS="cpu" # cpu | nvidia | apple | amd VRAM_GB=0 if have nvidia-smi; then GPU_NAME="$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -n1 || echo '')" VRAM_MIB="$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null | head -n1 || echo 0)" case "$VRAM_MIB" in ''|*[!0-9]*) VRAM_MIB=0 ;; esac VRAM_GB=$(( VRAM_MIB / 1024 )) CUDA_V="$(nvidia-smi 2>/dev/null | sed -n 's/.*CUDA Version: \([0-9.]*\).*/\1/p' | head -n1)" GPU_CLASS="nvidia" ok "NVIDIA GPU: ${GPU_NAME:-detected} (${VRAM_GB} GB VRAM${CUDA_V:+, CUDA $CUDA_V})" elif [ "$OS" = "macos" ] && [ "$ARCH" = "arm64" ]; then GPU_CLASS="apple" ok "Apple Silicon (Metal) — run Ollama natively (no --gpus all)" elif have rocminfo || have rocm-smi; then GPU_CLASS="amd" ok "AMD GPU (ROCm) detected — run Ollama natively; vLLM-ROCm is advanced" else no "No NVIDIA/Apple/AMD accelerator detected — the GPU-free 'echo' runtime still works (CPU)" fi # ── 4. Containers + model runtimes ──────────────────────────────────────────── hdr "4. Containers + model runtimes" NVIDIA_RT=0 if have docker; then ok "Docker: $(docker --version 2>/dev/null | sed 's/,.*//')" if docker info --format '{{.Runtimes}}' 2>/dev/null | grep -qi nvidia; then NVIDIA_RT=1; ok "NVIDIA Container Toolkit present (docker run --gpus all works)" elif [ "$GPU_CLASS" = "nvidia" ]; then warn "NVIDIA Container Toolkit NOT found — needed for 'docker run --gpus all'" warn " install: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html" fi else no "Docker not found (optional — only needed for the containerized Ollama/vLLM path)" fi if have ollama; then ok "Ollama: $(ollama --version 2>/dev/null | head -n1)"; else no "Ollama not installed (recommended real-inference runtime — https://ollama.com)"; fi if have vllm || python3 -c 'import vllm' >/dev/null 2>&1; then ok "vLLM available"; else no "vLLM not detected (optional, high-throughput NVIDIA serving)"; fi # ── 5. Recommendation ───────────────────────────────────────────────────────── hdr "5. Recommendation" RUNTIME="echo"; MODEL_CLASS="(none — echo simulator)"; MODEL_HINT="prove the pipeline with no model first" if [ "$GPU_CLASS" = "nvidia" ] || [ "$GPU_CLASS" = "apple" ] || [ "$GPU_CLASS" = "amd" ]; then RUNTIME="ollama" fi # Map VRAM → a model class. Apple/AMD: use a conservative mid class (unified memory varies). if [ "$GPU_CLASS" = "nvidia" ]; then if [ "$VRAM_GB" -ge 40 ] 2>/dev/null; then MODEL_CLASS="13B–34B instruct (e.g. llama3.1:8b → 34B class)"; MODEL_HINT="80GB cards serve 7B–13B at high throughput; 70B+ needs tensor-parallel across GPUs" elif [ "$VRAM_GB" -ge 20 ] 2>/dev/null; then MODEL_CLASS="7B–13B instruct (e.g. llama3.1:8b)"; MODEL_HINT="comfortable for an 8B model with room for context" elif [ "$VRAM_GB" -ge 10 ] 2>/dev/null; then MODEL_CLASS="7B–8B instruct, quantized (e.g. llama3.2:3b / llama3.1:8b-q4)"; MODEL_HINT="prefer a 3B–8B quantized model" elif [ "$VRAM_GB" -gt 0 ] 2>/dev/null; then MODEL_CLASS="1B–3B instruct (e.g. llama3.2:1b / llama3.2:3b)"; MODEL_HINT="small VRAM — keep the model small or use echo" else MODEL_CLASS="7B–8B instruct (VRAM unknown — start small)"; fi elif [ "$GPU_CLASS" = "apple" ] || [ "$GPU_CLASS" = "amd" ]; then MODEL_CLASS="3B–8B instruct (e.g. llama3.2:3b / llama3.1:8b)"; MODEL_HINT="run Ollama natively; size to your unified/GPU memory" fi ok "Runtime: $RUNTIME" ok "Model: $MODEL_CLASS" no "$MODEL_HINT" no "You advertise a Basis tier (basis-small / basis-default / basis-large) while serving the real local model behind it." # ── 6. Fetch the skill package ──────────────────────────────────────────────── fetch() { # fetch if have curl; then curl -fsSL "$1" -o "$2"; elif have wget; then wget -qO "$2" "$1"; else return 9; fi } if [ "$DO_FETCH" = 1 ]; then hdr "6. Fetch the basis-worker skill" if ! have curl && ! have wget; then warn "Neither curl nor wget is available — cannot fetch. Install one, or copy the skill from the repo (.agents/skills/basis-worker)." else mkdir -p "$SKILL_DIR/scripts" SKILL_URL="$BASE_URL/api/skills/basis-worker" fetched=0 if fetch "$SKILL_URL/SKILL.md" "$SKILL_DIR/SKILL.md"; then ok "SKILL.md → $SKILL_DIR/SKILL.md"; fetched=1; else warn "could not fetch SKILL.md"; fi if fetch "$SKILL_URL/manifest" "$SKILL_DIR/manifest.json"; then ok "manifest → $SKILL_DIR/manifest.json"; fi # Pull each script the download index lists (best-effort; the agent can also # copy them from the repo). The two markdown files are served raw; scripts are # mirrored under /skills/basis-worker/scripts/ for direct fetch. for s in setup-worker.mjs doctor.mjs publish-prices.mjs verify-worker.mjs healthcheck.mjs; do if fetch "$BASE_URL/skills/basis-worker/scripts/$s" "$SKILL_DIR/scripts/$s" 2>/dev/null; then no "scripts/$s" fi done if [ "$fetched" = 1 ]; then ok "Skill package ready in $SKILL_DIR"; fi fi else hdr "6. Fetch" no "Skipped (--no-fetch). Detection + recommendation only." fi # ── 7. Next steps ───────────────────────────────────────────────────────────── hdr "Next steps" say " 1. Read the skill: $SKILL_DIR/SKILL.md (or $BASE_URL/api/skills/basis-worker/SKILL.md)" say " 2. Confirm hardware: node $SKILL_DIR/scripts/doctor.mjs" say " 3. Register (PUBLIC wallet only — never a key/seed):" say " node $SKILL_DIR/scripts/setup-worker.mjs --register \\" say " --reward-wallet 0xYourPublicEvmAddress0000000000000000000000 \\" say " --worker-id my-gpu-1 --base $BASE_URL" say " 4. Publish a signed price/model offer (the market):" say " node $SKILL_DIR/scripts/publish-prices.mjs --worker-id my-gpu-1 --prices ./prices.json --key-file ./worker-reward.key" say " 5. Start the worker (standalone runtime/worker CLI):" say " cd runtime/worker && npm install && BASIS_WORKER_RUNTIME=$RUNTIME npm run dev" say " 6. Check live posture: curl -fsSL $BASE_URL/api/workers/onboarding/status" say "" say " Honest state: worker registration + offer storage are live. PUBLIC worker" say " routing for /api/v1 is READY BUT OFF — a registered worker receives" say " production jobs only when routing is enabled AND a real verified worker is" say " online. On-chain settlement is keeper/operator-driven (not automatic)." say " Rewards are payment for completed verified work — never guaranteed, no yield." say ""