mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-08-14 00:47:52 +00:00
Add cold-start CLI installer (#313)
This commit is contained in:
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
# bg-orchestrator.sh — detached parent of all background install work.
|
||||
#
|
||||
# Spawned by install.sh via `nohup ... & disown`. Runs the Rust
|
||||
# toolchain install + extension build sequentially, and in parallel
|
||||
# kicks off model pulls for each model id passed as an argument.
|
||||
#
|
||||
# Usage: bg-orchestrator.sh <model-id> [<model-id> ...]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
OPENJARVIS_HOME="${OPENJARVIS_HOME:-$HOME/.openjarvis}"
|
||||
STATE_DIR="$OPENJARVIS_HOME/.state"
|
||||
SCRIPTS_DIR="$OPENJARVIS_HOME/.scripts"
|
||||
PID_FILE="$STATE_DIR/bg.pid"
|
||||
LOG="$STATE_DIR/bg-orchestrator.log"
|
||||
|
||||
mkdir -p "$STATE_DIR"
|
||||
echo "$$" > "$PID_FILE"
|
||||
|
||||
cleanup() {
|
||||
rm -f "$PID_FILE"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "[$(date -u +%FT%TZ)] bg-orchestrator started, pid=$$" >> "$LOG"
|
||||
|
||||
# Sequential: install rust, then build extension.
|
||||
(
|
||||
"$SCRIPTS_DIR/install-rust.sh" >> "$LOG" 2>&1 \
|
||||
&& "$SCRIPTS_DIR/build-extension.sh" >> "$LOG" 2>&1
|
||||
) &
|
||||
RUST_PID=$!
|
||||
|
||||
# Parallel: each model pull.
|
||||
MODEL_PIDS=()
|
||||
for model in "$@"; do
|
||||
"$SCRIPTS_DIR/pull-model.sh" "$model" >> "$LOG" 2>&1 &
|
||||
MODEL_PIDS+=($!)
|
||||
done
|
||||
|
||||
# Wait for all subprocesses; non-zero exits don't fail the orchestrator
|
||||
# because per-task state files already record success/failure.
|
||||
wait "$RUST_PID" || true
|
||||
for pid in "${MODEL_PIDS[@]}"; do
|
||||
wait "$pid" || true
|
||||
done
|
||||
|
||||
echo "[$(date -u +%FT%TZ)] bg-orchestrator done" >> "$LOG"
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
# build-extension.sh — build the Rust maturin extension into the venv.
|
||||
#
|
||||
# State files under $OPENJARVIS_HOME/.state/:
|
||||
# extension-built — atomic marker written on success
|
||||
# extension-failed — written on failure with stderr tail
|
||||
# extension-build.log — captured stderr/stdout
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
OPENJARVIS_HOME="${OPENJARVIS_HOME:-$HOME/.openjarvis}"
|
||||
# Self-heal PATH for cargo: install-rust.sh installs to ~/.cargo/bin, but
|
||||
# its export doesn't propagate to subsequent subprocess invocations.
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
SRC_DIR="$OPENJARVIS_HOME/src"
|
||||
STATE_DIR="$OPENJARVIS_HOME/.state"
|
||||
LOG="$STATE_DIR/extension-build.log"
|
||||
BUILT="$STATE_DIR/extension-built"
|
||||
FAILED="$STATE_DIR/extension-failed"
|
||||
MANIFEST="$SRC_DIR/rust/crates/openjarvis-python/Cargo.toml"
|
||||
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
if [[ ! -f "$MANIFEST" ]]; then
|
||||
echo "build-extension.sh: manifest not found at $MANIFEST" > "$FAILED"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$SRC_DIR"
|
||||
if uv run maturin develop -m "$MANIFEST" >>"$LOG" 2>&1; then
|
||||
tmp="$BUILT.tmp"
|
||||
date -u +"%Y-%m-%dT%H:%M:%SZ" > "$tmp"
|
||||
mv "$tmp" "$BUILT"
|
||||
rm -f "$FAILED"
|
||||
exit 0
|
||||
else
|
||||
rc=$?
|
||||
{
|
||||
echo "build-extension.sh failed (exit=$rc)"
|
||||
tail -n 50 "$LOG" 2>/dev/null || true
|
||||
} > "$FAILED"
|
||||
rm -f "$BUILT"
|
||||
exit "$rc"
|
||||
fi
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
# install-rust.sh — install Rust toolchain via rustup if cargo not present.
|
||||
#
|
||||
# Idempotent: exits 0 immediately if cargo is on PATH.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if command -v cargo >/dev/null 2>&1; then
|
||||
echo "install-rust.sh: cargo already present, skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "install-rust.sh: installing Rust toolchain via rustup..."
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
|
||||
|
||||
if [[ -d "$HOME/.cargo/bin" ]]; then
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
fi
|
||||
|
||||
if ! command -v cargo >/dev/null 2>&1; then
|
||||
echo "install-rust.sh: cargo still not on PATH after install; check rustup output" >&2
|
||||
exit 1
|
||||
fi
|
||||
Executable
+264
@@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env bash
|
||||
# install.sh — OpenJarvis curl-pipe-bash installer.
|
||||
#
|
||||
# Usage:
|
||||
# curl -fsSL https://openjarvis.ai/install.sh | bash
|
||||
#
|
||||
# Flags (only used in tests / power users):
|
||||
# --no-bg-orchestrator Skip the detached background orchestrator
|
||||
# --minimal Skip foreground model pull (no `qwen3.5:2b`)
|
||||
# --force Re-run all steps even if state file says done
|
||||
#
|
||||
# Environment overrides:
|
||||
# OPENJARVIS_HOME Install dir (default: $HOME/.openjarvis)
|
||||
# OPENJARVIS_REPO_URL git repo URL (default: https://github.com/open-jarvis/OpenJarvis.git)
|
||||
# OPENJARVIS_FORCE_WSL Set 1 to force WSL detection (testing)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ---- args ----
|
||||
SKIP_BG=0
|
||||
MINIMAL=0
|
||||
FORCE=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--no-bg-orchestrator) SKIP_BG=1 ;;
|
||||
--minimal) MINIMAL=1 ;;
|
||||
--force) FORCE=1 ;;
|
||||
*) echo "install.sh: unknown arg: $arg" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ---- root refusal ----
|
||||
if [[ "$(id -u)" -eq 0 ]]; then
|
||||
cat >&2 <<'EOF'
|
||||
install.sh: don't run as root.
|
||||
|
||||
OpenJarvis installs to $HOME/.openjarvis, not /usr/local. Re-run as your
|
||||
regular user (without sudo).
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---- prereq probe ----
|
||||
need() {
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
cat >&2 <<EOF
|
||||
install.sh: '$1' is required but not found.
|
||||
|
||||
Install hints:
|
||||
macOS: xcode-select --install (provides git, curl)
|
||||
Debian/Ubuntu: sudo apt install git curl
|
||||
Fedora/RHEL: sudo dnf install git curl
|
||||
Arch: sudo pacman -S git curl
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
need git
|
||||
need curl
|
||||
|
||||
# ---- env ----
|
||||
OPENJARVIS_HOME="${OPENJARVIS_HOME:-$HOME/.openjarvis}"
|
||||
OPENJARVIS_REPO_URL="${OPENJARVIS_REPO_URL:-https://github.com/open-jarvis/OpenJarvis.git}"
|
||||
SRC_DIR="$OPENJARVIS_HOME/src"
|
||||
VENV_DIR="$OPENJARVIS_HOME/.venv"
|
||||
STATE_DIR="$OPENJARVIS_HOME/.state"
|
||||
SCRIPTS_DIR="$OPENJARVIS_HOME/.scripts"
|
||||
STATE_FILE="$STATE_DIR/install-state.json"
|
||||
|
||||
mkdir -p "$OPENJARVIS_HOME" "$STATE_DIR" "$SCRIPTS_DIR"
|
||||
|
||||
# ---- WSL detection ----
|
||||
WSL=0
|
||||
if [[ "${OPENJARVIS_FORCE_WSL:-0}" == "1" ]]; then
|
||||
WSL=1
|
||||
elif [[ -f /proc/sys/kernel/osrelease ]] && grep -qi "microsoft" /proc/sys/kernel/osrelease 2>/dev/null; then
|
||||
WSL=1
|
||||
fi
|
||||
|
||||
# ---- helpers ----
|
||||
state_done() {
|
||||
[[ -f "$STATE_FILE" ]] && grep -q "\"$1\":[[:space:]]*true" "$STATE_FILE"
|
||||
}
|
||||
|
||||
mark_done() {
|
||||
if [[ ! -f "$STATE_FILE" ]]; then
|
||||
echo '{}' > "$STATE_FILE"
|
||||
fi
|
||||
python3 - "$STATE_FILE" "$1" "$WSL" <<'PYEOF'
|
||||
import json, sys
|
||||
path, key, wsl = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
with open(path) as f:
|
||||
data = json.load(f)
|
||||
data[key] = True
|
||||
data["wsl"] = bool(int(wsl))
|
||||
with open(path, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
PYEOF
|
||||
}
|
||||
|
||||
step() {
|
||||
local name="$1" desc="$2"; shift 2
|
||||
if [[ "$FORCE" -ne 1 ]] && state_done "$name"; then
|
||||
echo "[ok] $desc (already done)"
|
||||
return 0
|
||||
fi
|
||||
echo "[..] $desc"
|
||||
"$@"
|
||||
mark_done "$name"
|
||||
echo "[ok] $desc"
|
||||
}
|
||||
|
||||
# ---- step impls ----
|
||||
install_uv() {
|
||||
if command -v uv >/dev/null 2>&1; then
|
||||
echo " uv already installed"
|
||||
return 0
|
||||
fi
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
if ! command -v uv >/dev/null 2>&1; then
|
||||
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
|
||||
fi
|
||||
}
|
||||
|
||||
clone_repo() {
|
||||
if [[ "$FORCE" -ne 1 ]] && [[ -d "$SRC_DIR/.git" ]]; then
|
||||
echo " repo already at $SRC_DIR"
|
||||
return 0
|
||||
fi
|
||||
git clone --depth 1 "$OPENJARVIS_REPO_URL" "$SRC_DIR"
|
||||
}
|
||||
|
||||
copy_scripts() {
|
||||
cp -f "$SRC_DIR"/scripts/install/*.sh "$SCRIPTS_DIR/"
|
||||
chmod +x "$SCRIPTS_DIR"/*.sh
|
||||
}
|
||||
|
||||
create_venv() {
|
||||
uv venv --python 3.11 "$VENV_DIR"
|
||||
}
|
||||
|
||||
editable_install() {
|
||||
cd "$SRC_DIR"
|
||||
uv pip install --python "$VENV_DIR/bin/python" -e .
|
||||
}
|
||||
|
||||
install_ollama() {
|
||||
if command -v ollama >/dev/null 2>&1; then
|
||||
echo " ollama already installed"
|
||||
return 0
|
||||
fi
|
||||
curl -fsSL https://ollama.com/install.sh | sh
|
||||
}
|
||||
|
||||
start_ollama() {
|
||||
if pgrep -f "ollama serve" >/dev/null 2>&1; then
|
||||
echo " ollama serve already running"
|
||||
return 0
|
||||
fi
|
||||
if [[ "$WSL" -eq 1 ]] || ! command -v systemctl >/dev/null 2>&1; then
|
||||
nohup ollama serve > "$STATE_DIR/ollama.log" 2>&1 &
|
||||
sleep 1
|
||||
else
|
||||
systemctl --user start ollama 2>/dev/null \
|
||||
|| (nohup ollama serve > "$STATE_DIR/ollama.log" 2>&1 & sleep 1)
|
||||
fi
|
||||
}
|
||||
|
||||
pull_default_model() {
|
||||
if [[ "$MINIMAL" -eq 1 ]]; then
|
||||
echo " --minimal set; skipping model pull"
|
||||
return 0
|
||||
fi
|
||||
ollama pull qwen3.5:2b || echo " warning: ollama pull failed; bg-orchestrator will retry"
|
||||
}
|
||||
|
||||
write_config() {
|
||||
"$VENV_DIR/bin/jarvis" _bootstrap --write-config \
|
||||
--engine ollama --model qwen3.5:2b \
|
||||
--prefer-cloud-when-available
|
||||
}
|
||||
|
||||
install_symlinks() {
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
ln -sf "$SCRIPTS_DIR/jarvis-wrapper.sh" "$HOME/.local/bin/jarvis"
|
||||
ln -sf "$SCRIPTS_DIR/jarvis-uninstall.sh" "$HOME/.local/bin/jarvis-uninstall"
|
||||
}
|
||||
|
||||
ensure_path() {
|
||||
case ":$PATH:" in
|
||||
*":$HOME/.local/bin:"*) return 0 ;;
|
||||
esac
|
||||
local rc=""
|
||||
if [[ "$SHELL" == */zsh ]]; then
|
||||
rc="$HOME/.zshrc"
|
||||
else
|
||||
rc="$HOME/.bashrc"
|
||||
fi
|
||||
if grep -q "OpenJarvis" "$rc" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
{
|
||||
echo ''
|
||||
echo '# OpenJarvis'
|
||||
echo 'export PATH="$HOME/.local/bin:$PATH"'
|
||||
} >> "$rc"
|
||||
echo " Added ~/.local/bin to PATH in $rc — run: source $rc"
|
||||
}
|
||||
|
||||
detach_bg_orchestrator() {
|
||||
if [[ "$SKIP_BG" -eq 1 ]]; then
|
||||
echo " --no-bg-orchestrator set; skipping detach"
|
||||
return 0
|
||||
fi
|
||||
local models
|
||||
models=$("$VENV_DIR/bin/python" - <<'PYEOF' 2>/dev/null || true
|
||||
from openjarvis.core.config import detect_hardware, recommend_model
|
||||
hw = detect_hardware()
|
||||
tier = recommend_model(hw, "ollama")
|
||||
TIERS = ["qwen3.5:2b", "qwen3.5:4b", "qwen3.5:9b", "qwen3.5:27b"]
|
||||
out = set([tier]) if tier else set()
|
||||
if tier in TIERS:
|
||||
idx = TIERS.index(tier)
|
||||
if idx + 1 < len(TIERS):
|
||||
out.add(TIERS[idx + 1])
|
||||
print(" ".join(sorted(out)))
|
||||
PYEOF
|
||||
)
|
||||
if [[ -z "$models" ]]; then
|
||||
models=""
|
||||
fi
|
||||
nohup "$SCRIPTS_DIR/bg-orchestrator.sh" $models \
|
||||
> "$STATE_DIR/bg-orchestrator.log" 2>&1 &
|
||||
disown
|
||||
}
|
||||
|
||||
# ---- run ----
|
||||
echo "OpenJarvis installer"
|
||||
echo " install dir: $OPENJARVIS_HOME"
|
||||
echo " WSL2: $WSL"
|
||||
echo
|
||||
|
||||
step install_uv "Install uv" install_uv
|
||||
step clone_repo "Clone OpenJarvis repo" clone_repo
|
||||
step copy_scripts "Copy install scripts" copy_scripts
|
||||
step create_venv "Create venv" create_venv
|
||||
step editable_install "Install OpenJarvis" editable_install
|
||||
step install_ollama "Install Ollama" install_ollama
|
||||
step start_ollama "Start Ollama daemon" start_ollama
|
||||
step pull_default_model "Pull qwen3.5:2b" pull_default_model
|
||||
step write_config "Write config.toml" write_config
|
||||
step install_symlinks "Install symlinks" install_symlinks
|
||||
step ensure_path "Ensure PATH" ensure_path
|
||||
step detach_bg_orchestrator "Detach background work" detach_bg_orchestrator
|
||||
|
||||
cat <<EOF
|
||||
|
||||
Done. Type 'jarvis' to start chatting.
|
||||
|
||||
Background work continues silently:
|
||||
- Rust toolchain + maturin extension build
|
||||
- Bigger model downloads
|
||||
Run 'jarvis doctor' to check status anytime.
|
||||
EOF
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
# jarvis-uninstall.sh — clean removal of OpenJarvis from $HOME.
|
||||
#
|
||||
# Removes:
|
||||
# ~/.openjarvis/
|
||||
# ~/.local/bin/jarvis
|
||||
# ~/.local/bin/jarvis-uninstall
|
||||
#
|
||||
# Does NOT remove: ollama, uv, or the Rust toolchain.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
OPENJARVIS_HOME="${OPENJARVIS_HOME:-$HOME/.openjarvis}"
|
||||
|
||||
if [[ -f "$OPENJARVIS_HOME/.state/bg.pid" ]]; then
|
||||
pid=$(cat "$OPENJARVIS_HOME/.state/bg.pid" 2>/dev/null || echo "")
|
||||
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
||||
echo "Stopping background work (pid=$pid)..."
|
||||
kill "$pid" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v ollama >/dev/null 2>&1; then
|
||||
ollama stop >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
if [[ -d "$OPENJARVIS_HOME" ]]; then
|
||||
rm -rf "$OPENJARVIS_HOME"
|
||||
echo "Removed $OPENJARVIS_HOME"
|
||||
fi
|
||||
|
||||
for f in "$HOME/.local/bin/jarvis" "$HOME/.local/bin/jarvis-uninstall"; do
|
||||
if [[ -L "$f" ]] || [[ -f "$f" ]]; then
|
||||
rm -f "$f"
|
||||
echo "Removed $f"
|
||||
fi
|
||||
done
|
||||
|
||||
cat <<EOF
|
||||
|
||||
OpenJarvis removed.
|
||||
|
||||
Left intact (may be used by other tools):
|
||||
- Ollama (uninstall: brew uninstall ollama / rm -f /usr/local/bin/ollama)
|
||||
- uv (uninstall: rm -rf ~/.local/share/uv ~/.cargo/bin/uv)
|
||||
- Rust toolchain (uninstall: rustup self uninstall)
|
||||
EOF
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# jarvis-wrapper.sh — symlinked to ~/.local/bin/jarvis.
|
||||
# Activates the managed venv and execs the real jarvis CLI.
|
||||
|
||||
OPENJARVIS_HOME="${OPENJARVIS_HOME:-$HOME/.openjarvis}"
|
||||
VENV="$OPENJARVIS_HOME/.venv"
|
||||
|
||||
if [[ ! -d "$VENV" ]]; then
|
||||
echo "jarvis: venv not found at $VENV" >&2
|
||||
echo "Re-run the installer: curl -fsSL https://openjarvis.ai/install.sh | bash" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec "$VENV/bin/jarvis" "$@"
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
# pull-model.sh — pull an Ollama model with state-file lifecycle.
|
||||
#
|
||||
# Usage: pull-model.sh <model-id>
|
||||
#
|
||||
# State files written under $OPENJARVIS_HOME/.state/models/:
|
||||
# <model>.downloading — created on start
|
||||
# <model>.ready — on success (atomic rename)
|
||||
# <model>.failed — on failure after retries
|
||||
# <model>.log — captured stderr
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MODEL="${1:-}"
|
||||
if [[ -z "$MODEL" ]]; then
|
||||
echo "usage: pull-model.sh <model-id>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
OPENJARVIS_HOME="${OPENJARVIS_HOME:-$HOME/.openjarvis}"
|
||||
STATE_DIR="$OPENJARVIS_HOME/.state/models"
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
DOWNLOADING="$STATE_DIR/${MODEL}.downloading"
|
||||
READY="$STATE_DIR/${MODEL}.ready"
|
||||
FAILED="$STATE_DIR/${MODEL}.failed"
|
||||
LOG="$STATE_DIR/${MODEL}.log"
|
||||
|
||||
# Cleanup any prior state for this model.
|
||||
rm -f "$READY" "$FAILED"
|
||||
touch "$DOWNLOADING"
|
||||
|
||||
MAX_RETRIES=3
|
||||
attempt=0
|
||||
last_exit=0
|
||||
|
||||
while [[ $attempt -lt $MAX_RETRIES ]]; do
|
||||
attempt=$((attempt + 1))
|
||||
if ollama pull "$MODEL" >>"$LOG" 2>&1; then
|
||||
# Atomic rename: write to .tmp then mv.
|
||||
tmp="$STATE_DIR/${MODEL}.ready.tmp"
|
||||
date -u +"%Y-%m-%dT%H:%M:%SZ" > "$tmp"
|
||||
mv "$tmp" "$READY"
|
||||
rm -f "$DOWNLOADING"
|
||||
exit 0
|
||||
else
|
||||
last_exit=$?
|
||||
fi
|
||||
done
|
||||
|
||||
# All retries exhausted.
|
||||
{
|
||||
echo "pull-model.sh: $MODEL failed after $MAX_RETRIES attempts (exit=$last_exit)"
|
||||
tail -n 50 "$LOG" 2>/dev/null || true
|
||||
} > "$FAILED"
|
||||
rm -f "$DOWNLOADING"
|
||||
exit "$last_exit"
|
||||
Reference in New Issue
Block a user