53eaed4c7f
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
315 lines
12 KiB
Bash
Executable File
315 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# NexusOS WSL bootstrap — called by install-windows.ps1 after the repo is copied in.
|
|
# Safe to re-run; skips steps that are already done.
|
|
|
|
set -euo pipefail
|
|
|
|
NEXUS_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
cd "$NEXUS_ROOT"
|
|
|
|
step() { echo ""; echo "==> $*"; }
|
|
ok() { echo " ok: $*"; }
|
|
warn() { echo " warn: $*"; }
|
|
|
|
# ── System packages ───────────────────────────────────────────────────────────
|
|
|
|
step "System packages"
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y -qq \
|
|
python3 python3-venv python3-pip python3-tk \
|
|
curl wget git build-essential \
|
|
libsqlite3-dev rsync
|
|
ok "System packages ready"
|
|
|
|
# ── Node.js (via NodeSource LTS) ──────────────────────────────────────────────
|
|
|
|
step "Node.js"
|
|
if command -v node &>/dev/null; then
|
|
ok "Already installed: $(node --version)"
|
|
else
|
|
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - 2>/dev/null
|
|
sudo apt-get install -y -qq nodejs
|
|
ok "Installed: $(node --version)"
|
|
fi
|
|
|
|
# ── Python venv ───────────────────────────────────────────────────────────────
|
|
|
|
step "Python venv (Promethean)"
|
|
if [ ! -d "$NEXUS_ROOT/Promethean" ]; then
|
|
python3 -m venv "$NEXUS_ROOT/Promethean"
|
|
ok "Created"
|
|
else
|
|
ok "Already exists"
|
|
fi
|
|
|
|
source "$NEXUS_ROOT/Promethean/bin/activate"
|
|
pip install --upgrade pip -q
|
|
|
|
step "Python dependencies"
|
|
pip install -r "$NEXUS_ROOT/requirements-wsl.txt" -q
|
|
ok "Installed"
|
|
|
|
# ── Ollama ────────────────────────────────────────────────────────────────────
|
|
|
|
step "Ollama"
|
|
mkdir -p "$NEXUS_ROOT/runtime/logs"
|
|
OLLAMA_BIN="$NEXUS_ROOT/ollama/bin/ollama"
|
|
DEFAULT_MODEL="${NEXUS_DEFAULT_MODEL:-qwen2.5:3b}"
|
|
|
|
case "$(uname -m)" in
|
|
x86_64|amd64) ;;
|
|
*)
|
|
echo "ERROR: bundled Ollama supports x86-64 WSL only (detected $(uname -m))." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
if [ ! -x "$OLLAMA_BIN" ]; then
|
|
chmod +x "$OLLAMA_BIN" 2>/dev/null || true
|
|
fi
|
|
if [ ! -x "$OLLAMA_BIN" ] || ! "$OLLAMA_BIN" --version &>/dev/null; then
|
|
echo "ERROR: bundled Ollama could not run in this WSL distro." >&2
|
|
echo " Expected a Linux x86-64 executable at: $OLLAMA_BIN" >&2
|
|
exit 1
|
|
fi
|
|
ok "Binary OK: $("$OLLAMA_BIN" --version)"
|
|
|
|
# A clean checkout has no model files. Provision one so the first launch can chat.
|
|
OLLAMA_HOST="127.0.0.1:11434"
|
|
OLLAMA_MODELS="$NEXUS_ROOT/models"
|
|
OLLAMA_HOST="$OLLAMA_HOST" OLLAMA_MODELS="$OLLAMA_MODELS" \
|
|
"$OLLAMA_BIN" list 2>/dev/null | awk -v model="$DEFAULT_MODEL" \
|
|
'NR > 1 && $1 == model { found = 1 } END { exit !found }' \
|
|
|| {
|
|
ollama_pid=""
|
|
if ! curl -fsS "http://127.0.0.1:11434/api/tags" >/dev/null 2>&1; then
|
|
OLLAMA_HOST="$OLLAMA_HOST" OLLAMA_MODELS="$NEXUS_ROOT/models" \
|
|
"$OLLAMA_BIN" serve >>"$NEXUS_ROOT/runtime/logs/ollama.log" 2>&1 &
|
|
ollama_pid=$!
|
|
for _ in {1..30}; do
|
|
curl -fsS "http://127.0.0.1:11434/api/tags" >/dev/null 2>&1 && break
|
|
sleep 1
|
|
done
|
|
fi
|
|
|
|
if ! curl -fsS "http://127.0.0.1:11434/api/tags" >/dev/null 2>&1; then
|
|
[ -n "$ollama_pid" ] && kill "$ollama_pid" 2>/dev/null || true
|
|
echo "ERROR: Ollama did not start; see runtime/logs/ollama.log" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! OLLAMA_HOST="$OLLAMA_HOST" OLLAMA_MODELS="$OLLAMA_MODELS" \
|
|
"$OLLAMA_BIN" list 2>/dev/null | awk -v model="$DEFAULT_MODEL" \
|
|
'NR > 1 && $1 == model { found = 1 } END { exit !found }'; then
|
|
echo " pulling default model: $DEFAULT_MODEL"
|
|
if ! OLLAMA_HOST="$OLLAMA_HOST" OLLAMA_MODELS="$OLLAMA_MODELS" \
|
|
"$OLLAMA_BIN" pull "$DEFAULT_MODEL"; then
|
|
[ -n "$ollama_pid" ] && kill "$ollama_pid" 2>/dev/null || true
|
|
echo "ERROR: Could not pull default model $DEFAULT_MODEL." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
[ -n "$ollama_pid" ] && kill "$ollama_pid" 2>/dev/null || true
|
|
ok "Model ready: $DEFAULT_MODEL"
|
|
}
|
|
|
|
# ── Frontend npm deps ─────────────────────────────────────────────────────────
|
|
|
|
step "Frontend dependencies"
|
|
cd "$NEXUS_ROOT/interface/web"
|
|
npm install --silent
|
|
ok "Installed"
|
|
cd "$NEXUS_ROOT"
|
|
|
|
# ── Runtime dirs ─────────────────────────────────────────────────────────────
|
|
|
|
step "Runtime directories"
|
|
mkdir -p "$NEXUS_ROOT/runtime/pids" "$NEXUS_ROOT/runtime/logs"
|
|
ok "Ready"
|
|
|
|
# ── Shell alias (ncp) ─────────────────────────────────────────────────────────
|
|
|
|
step "Shell alias"
|
|
if ! grep -q "nexus-core/management/nexus-cli.sh" "$HOME/.bashrc" 2>/dev/null; then
|
|
cat >> "$HOME/.bashrc" << 'EOF'
|
|
|
|
# NexusOS
|
|
ncp() { ~/nexus-core/management/nexus-cli.sh "$@"; }
|
|
EOF
|
|
ok "ncp alias added to ~/.bashrc"
|
|
else
|
|
ok "ncp already in ~/.bashrc"
|
|
fi
|
|
|
|
step "Promethean command"
|
|
PROMETHEAN_RC="$NEXUS_ROOT/.promethean_bashrc"
|
|
if [ ! -f "$PROMETHEAN_RC" ]; then
|
|
cat > "$PROMETHEAN_RC" << 'EOF'
|
|
# Promethean Terminal shell init
|
|
[[ -f ~/.bashrc ]] && source ~/.bashrc
|
|
export VIRTUAL_ENV_DISABLE_PROMPT=1
|
|
if [[ -f "$HOME/nexus-core/Promethean/bin/activate" ]]; then
|
|
source "$HOME/nexus-core/Promethean/bin/activate"
|
|
fi
|
|
export PS1='\[\e[0;35m\](Promethean)\[\e[0m\] ${debian_chroot:+($debian_chroot)}\[\e[0;37m\]\u@\h\[\e[0m\]:\[\e[1;32m\]\w\[\e[0m\]\$ '
|
|
printf '\e]0;Promethean Terminal\a'
|
|
EOF
|
|
ok "Promethean environment init created"
|
|
else
|
|
ok "Promethean environment init already exists"
|
|
fi
|
|
if ! grep -qF "alias promethean='source ~/nexus-core/.promethean_bashrc'" "$HOME/.bashrc" 2>/dev/null; then
|
|
cat >> "$HOME/.bashrc" << 'EOF'
|
|
|
|
# Promethean
|
|
alias promethean='source ~/nexus-core/.promethean_bashrc'
|
|
EOF
|
|
ok "promethean command added to ~/.bashrc"
|
|
else
|
|
ok "promethean command already in ~/.bashrc"
|
|
fi
|
|
|
|
# ── Windows Terminal profile + desktop shortcut ───────────────────────────────
|
|
|
|
setup_windows() {
|
|
local win_user distro icon_src icon_win_dir icon_win_path
|
|
|
|
win_user=$(powershell.exe -command '$env:USERNAME' 2>/dev/null | tr -d '\r\n')
|
|
distro="${WSL_DISTRO_NAME:-$(uname -n)}"
|
|
|
|
if [ -z "$win_user" ] || [ ! -d "/mnt/c/Users/$win_user" ]; then
|
|
warn "Cannot locate Windows user directory — skipping Windows Terminal setup"
|
|
return 0
|
|
fi
|
|
|
|
# Copy app icon into Windows filesystem so WT can display it
|
|
icon_src="$NEXUS_ROOT/assets/n-small.png"
|
|
icon_win_dir="/mnt/c/Users/$win_user/AppData/Local/NexusOS"
|
|
mkdir -p "$icon_win_dir"
|
|
[ -f "$icon_src" ] && cp "$icon_src" "$icon_win_dir/nexus.png"
|
|
icon_win_path="C:\\Users\\$win_user\\AppData\\Local\\NexusOS\\nexus.png"
|
|
|
|
# Init file so the WT session sources nvm + ncp
|
|
cat > "$HOME/.nexus_init" << 'EOF'
|
|
[[ -f ~/.bashrc ]] && source ~/.bashrc
|
|
EOF
|
|
|
|
# Patch Windows Terminal settings.json via Python (handles JSONC safely)
|
|
python3 - "$win_user" "$distro" "$icon_win_path" << 'PYEOF'
|
|
import json, re, uuid, sys
|
|
from pathlib import Path
|
|
|
|
win_user, distro, icon_path = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
|
|
candidates = [
|
|
f"/mnt/c/Users/{win_user}/AppData/Local/Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState/settings.json",
|
|
f"/mnt/c/Users/{win_user}/AppData/Local/Packages/Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe/LocalState/settings.json",
|
|
]
|
|
|
|
settings_path = next((Path(p) for p in candidates if Path(p).exists()), None)
|
|
if not settings_path:
|
|
print(" warn: Windows Terminal not found — skipping profile")
|
|
sys.exit(0)
|
|
|
|
def strip_jsonc(text):
|
|
output = []
|
|
in_string = False
|
|
escaped = False
|
|
in_line_comment = False
|
|
in_block_comment = False
|
|
index = 0
|
|
while index < len(text):
|
|
char = text[index]
|
|
next_char = text[index + 1] if index + 1 < len(text) else ""
|
|
if in_line_comment:
|
|
if char == "\n":
|
|
in_line_comment = False
|
|
output.append(char)
|
|
elif in_block_comment:
|
|
if char == "*" and next_char == "/":
|
|
in_block_comment = False
|
|
index += 1
|
|
elif in_string:
|
|
output.append(char)
|
|
if escaped:
|
|
escaped = False
|
|
elif char == "\\":
|
|
escaped = True
|
|
elif char == '"':
|
|
in_string = False
|
|
elif char == '"':
|
|
in_string = True
|
|
output.append(char)
|
|
elif char == "/" and next_char == "/":
|
|
in_line_comment = True
|
|
index += 1
|
|
elif char == "/" and next_char == "*":
|
|
in_block_comment = True
|
|
index += 1
|
|
else:
|
|
output.append(char)
|
|
index += 1
|
|
return "".join(output)
|
|
|
|
try:
|
|
with open(settings_path, encoding="utf-8") as f:
|
|
settings_text = strip_jsonc(f.read())
|
|
settings = json.loads(re.sub(r",(\s*[}\]])", r"\1", settings_text))
|
|
except (OSError, json.JSONDecodeError) as error:
|
|
print(f" warn: Could not read Windows Terminal settings: {error}")
|
|
sys.exit(0)
|
|
|
|
profiles = settings.setdefault("profiles", {}).setdefault("list", [])
|
|
if any(p.get("name") == "NexusOS" for p in profiles):
|
|
print(" ok: NexusOS profile already in Windows Terminal")
|
|
sys.exit(0)
|
|
|
|
profiles.insert(0, {
|
|
"guid": "{" + str(uuid.uuid4()) + "}",
|
|
"name": "NexusOS",
|
|
"commandline": f"wsl.exe -d {distro} bash --init-file ~/.nexus_init",
|
|
"startingDirectory": "~",
|
|
"icon": icon_path,
|
|
})
|
|
|
|
try:
|
|
with open(settings_path, "w", encoding="utf-8") as f:
|
|
json.dump(settings, f, indent=4)
|
|
except OSError as error:
|
|
print(f" warn: Could not update Windows Terminal settings: {error}")
|
|
sys.exit(0)
|
|
|
|
print(f" ok: NexusOS profile added to Windows Terminal")
|
|
PYEOF
|
|
|
|
# Desktop shortcut — opens WT to the NexusOS profile and launches Nexus
|
|
powershell.exe -command "
|
|
\$ws = New-Object -ComObject WScript.Shell
|
|
\$s = \$ws.CreateShortcut(\"\$env:USERPROFILE\Desktop\NexusOS.lnk\")
|
|
\$s.TargetPath = 'wt.exe'
|
|
\$s.Arguments = '--profile NexusOS -- bash -c \"cd ~/nexus-core && ./launch_nexus.sh ; exec bash\"'
|
|
\$s.IconLocation = 'wt.exe,0'
|
|
\$s.Save()
|
|
" 2>/dev/null && ok "Desktop shortcut created" || warn "Could not create desktop shortcut"
|
|
}
|
|
|
|
step "Windows integration"
|
|
if grep -qi microsoft /proc/version 2>/dev/null; then
|
|
setup_windows
|
|
else
|
|
ok "Not running in WSL — skipping Windows Terminal setup"
|
|
fi
|
|
|
|
# ── Done ──────────────────────────────────────────────────────────────────────
|
|
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo " NexusOS bootstrap complete!"
|
|
echo ""
|
|
echo " Launch: cd ~/nexus-core && ./launch_nexus.sh"
|
|
echo " Frontend: http://localhost:5173"
|
|
echo " Backend: http://localhost:8000"
|
|
echo ""
|
|
echo " Or use the NexusOS shortcut on your Windows desktop."
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|