Files
NexusOS/bin/wsl-bootstrap.sh

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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"