Initial commit: NexusOS - local AI assistant platform
This commit is contained in:
@@ -0,0 +1,675 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Load nvm so npm/node resolve to the managed version
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
# shellcheck source=/dev/null
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
|
||||
NEXUS_ROOT="$HOME/nexus-core"
|
||||
PID_DIR="$NEXUS_ROOT/runtime/pids"
|
||||
LOG_DIR="$NEXUS_ROOT/runtime"
|
||||
PYTHON="$NEXUS_ROOT/Promethean/bin/python3"
|
||||
FRONTEND_DIR="$NEXUS_ROOT/interface/web"
|
||||
|
||||
mkdir -p "$PID_DIR" "$LOG_DIR"
|
||||
|
||||
BACKEND_PID="$PID_DIR/backend.pid"
|
||||
MEMORY_PID="$PID_DIR/memory.pid"
|
||||
FRONTEND_PID="$PID_DIR/frontend.pid"
|
||||
|
||||
BACKEND_LOG="$LOG_DIR/backend.log"
|
||||
MEMORY_LOG="$LOG_DIR/memory.log"
|
||||
FRONTEND_LOG="$LOG_DIR/frontend.log"
|
||||
|
||||
# -----------------------------
|
||||
# INTERNAL HELPERS
|
||||
# -----------------------------
|
||||
|
||||
_launch() {
|
||||
# _launch <name> <pid_file> <log_file> <work_dir> <cmd> [args...]
|
||||
local name="$1" pid_file="$2" log_file="$3" work_dir="$4"
|
||||
shift 4
|
||||
|
||||
if [ -f "$pid_file" ] && kill -0 "$(cat "$pid_file")" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
: > "$log_file"
|
||||
( cd "$work_dir" && exec "$@" ) >> "$log_file" 2>&1 &
|
||||
echo $! > "$pid_file"
|
||||
}
|
||||
|
||||
_check() {
|
||||
# _check <label> <pid_file> <log_file>
|
||||
# label should be the full service name, e.g. "NEXUS BACKEND SERVICE"
|
||||
local label="$1" pid_file="$2" log_file="$3"
|
||||
local pid
|
||||
pid=$(cat "$pid_file" 2>/dev/null)
|
||||
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
echo "$label STARTED"
|
||||
return 0
|
||||
else
|
||||
echo "$label FAILED TO START"
|
||||
[ -s "$log_file" ] && tail -8 "$log_file" | sed 's/^/ /'
|
||||
rm -f "$pid_file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
_wait_for_port() {
|
||||
# _wait_for_port <name> <port> <pid_file> <log_file>
|
||||
# Polls until the service responds on the given port, then prints ready.
|
||||
# On timeout, falls back to _check for the error log.
|
||||
local name="$1" port="$2" pid_file="$3" log_file="$4"
|
||||
local timeout=30 i=0
|
||||
if curl -s --max-time 1 "http://localhost:$port/" > /dev/null 2>&1; then
|
||||
echo " $name already running (:$port)"
|
||||
return 0
|
||||
fi
|
||||
while ! curl -s --max-time 1 "http://localhost:$port/" > /dev/null 2>&1; do
|
||||
i=$((i + 1))
|
||||
if [ "$i" -ge "$timeout" ]; then
|
||||
echo " $name timed out after ${timeout}s"
|
||||
_check "$name" "$pid_file" "$log_file"
|
||||
return 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo "$name STARTED"
|
||||
}
|
||||
|
||||
_pid_is_ours() {
|
||||
# _pid_is_ours <pid> <pattern> [pattern2]
|
||||
# True only if the live PID's command line matches one of the service
|
||||
# patterns. PID files outlive reboots; the OS recycles the number onto an
|
||||
# unrelated process (often a desktop-session process), so a bare `kill -0`
|
||||
# liveness check is not enough — TERMing a recycled PID can log the user out.
|
||||
local pid="$1" p1="$2" p2="$3" cmd
|
||||
[ -r "/proc/$pid/cmdline" ] || return 1
|
||||
cmd=$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null) || return 1
|
||||
[ -n "$p1" ] && [[ "$cmd" == *"$p1"* ]] && return 0
|
||||
[ -n "$p2" ] && [[ "$cmd" == *"$p2"* ]] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
_stop_service() {
|
||||
# _stop_service <label> <pid_file> <port> <pattern> [extra_pattern]
|
||||
#
|
||||
# PID-based stopping is unreliable here: the frontend PID file tracks npm,
|
||||
# but the server is a `node vite` GRANDCHILD (npm -> sh -c vite -> node)
|
||||
# that reparents and survives a parent kill; uvicorn --reload leaves a
|
||||
# worker child holding the port. So we stop in three escalating passes and
|
||||
# treat the PORT as the source of truth for whether the service is down.
|
||||
local label="$1" pid_file="$2" port="$3" pat="$4" pat2="$5"
|
||||
|
||||
# 1. Graceful: SIGTERM the tracked PID and its direct children.
|
||||
if [ -f "$pid_file" ]; then
|
||||
local pid; pid=$(cat "$pid_file" 2>/dev/null)
|
||||
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null \
|
||||
&& _pid_is_ours "$pid" "$pat" "$pat2"; then
|
||||
pkill -TERM -P "$pid" 2>/dev/null || true
|
||||
kill -TERM "$pid" 2>/dev/null || true
|
||||
fi
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
|
||||
# 2. Sweep stragglers by command line — catches reparented grandchildren,
|
||||
# --reload workers, and instances started outside this script.
|
||||
[ -n "$pat" ] && pkill -TERM -f "$pat" 2>/dev/null
|
||||
[ -n "$pat2" ] && pkill -TERM -f "$pat2" 2>/dev/null
|
||||
|
||||
# 3. Backstop: whatever still holds the port IS the service — kill it by
|
||||
# port. This is what makes stop reliable no matter how the process tree
|
||||
# was shaped or whether the PID file was accurate.
|
||||
if [ -n "$port" ]; then
|
||||
if ! _wait_for_port_close "$port"; then
|
||||
fuser -k "${port}/tcp" 2>/dev/null
|
||||
[ -n "$pat" ] && pkill -KILL -f "$pat" 2>/dev/null
|
||||
[ -n "$pat2" ] && pkill -KILL -f "$pat2" 2>/dev/null
|
||||
_wait_for_port_close "$port" || true
|
||||
fi
|
||||
if curl -s --max-time 1 "http://localhost:$port/" >/dev/null 2>&1; then
|
||||
echo "$label STILL RUNNING (:$port) — try 'ncp kill'"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
echo "$label STOPPED"
|
||||
return 0
|
||||
}
|
||||
|
||||
_wait_for_port_close() {
|
||||
# _wait_for_port_close <port>
|
||||
# Waits until the port stops responding. Silent — caller prints the result.
|
||||
local port="$1" timeout=15 i=0
|
||||
while curl -s --max-time 1 "http://localhost:$port/" > /dev/null 2>&1; do
|
||||
i=$((i + 1))
|
||||
[ "$i" -ge "$timeout" ] && return 1
|
||||
sleep 1
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
_status() {
|
||||
# _status <name> <pid_file> [port]
|
||||
local name="$1" pid_file="$2" port="${3:-}"
|
||||
if [ -f "$pid_file" ] && kill -0 "$(cat "$pid_file")" 2>/dev/null; then
|
||||
echo " $name: RUNNING (PID $(cat "$pid_file"))"
|
||||
elif [ -n "$port" ] && curl -s --max-time 1 "http://localhost:$port/" > /dev/null 2>&1; then
|
||||
echo " $name: RUNNING (port :$port, PID stale — consider ncp kill)"
|
||||
else
|
||||
echo " $name: STOPPED"
|
||||
fi
|
||||
}
|
||||
|
||||
# -----------------------------
|
||||
# KILL COMMAND
|
||||
# -----------------------------
|
||||
|
||||
kill_services() {
|
||||
echo "Force-killing all Nexus processes..."
|
||||
local ports=(8000 8001 5173 11434)
|
||||
local names=("SYNAPSE" "MEMORY" "INTERFACE" "OLLAMA")
|
||||
for i in "${!ports[@]}"; do
|
||||
port="${ports[$i]}"
|
||||
name="${names[$i]}"
|
||||
if fuser -k "${port}/tcp" 2>/dev/null; then
|
||||
echo " KILLED: ${name} (:${port})"
|
||||
else
|
||||
echo " NOT RUNNING: ${name} (:${port})"
|
||||
fi
|
||||
done
|
||||
pkill -9 -f "uvicorn synapse" 2>/dev/null || true
|
||||
pkill -9 -f "npm run dev" 2>/dev/null || true
|
||||
pkill -9 -f "vite --host" 2>/dev/null || true
|
||||
pkill -9 -f "ollama serve" 2>/dev/null || true
|
||||
rm -f "$PID_DIR"/*.pid
|
||||
echo "Done."
|
||||
}
|
||||
|
||||
# -----------------------------
|
||||
# START COMMANDS
|
||||
# -----------------------------
|
||||
|
||||
start_ollama() {
|
||||
# Ollama runs as its own `ollama serve` process, but the backend's
|
||||
# OllamaManager owns model/GPU selection — so drive it through the backend
|
||||
# endpoint (the same path the control panel uses) rather than launching the
|
||||
# binary directly. Requires the backend to be up.
|
||||
echo "Starting OLLAMA..."
|
||||
if curl -s --max-time 30 -X POST http://localhost:8000/ollama/start > /dev/null 2>&1; then
|
||||
echo "NEXUS OLLAMA STARTED"
|
||||
else
|
||||
echo " OLLAMA start request failed (backend not reachable on :8000)"
|
||||
fi
|
||||
}
|
||||
|
||||
start_memory() {
|
||||
_launch "NEXUS MEMORY SERVICE" "$MEMORY_PID" "$MEMORY_LOG" "$NEXUS_ROOT" \
|
||||
"$PYTHON" -m uvicorn synapse.memory.service:app --host 0.0.0.0 --port 8001 --reload
|
||||
_wait_for_port "NEXUS MEMORY SERVICE" 8001 "$MEMORY_PID" "$MEMORY_LOG"
|
||||
}
|
||||
|
||||
start_backend() {
|
||||
_launch "NEXUS BACKEND SERVICE" "$BACKEND_PID" "$BACKEND_LOG" "$NEXUS_ROOT" \
|
||||
"$PYTHON" -m uvicorn synapse.main:sio_app --host 0.0.0.0 --port 8000 --reload
|
||||
_wait_for_port "NEXUS BACKEND SERVICE" 8000 "$BACKEND_PID" "$BACKEND_LOG"
|
||||
# AI is manual now — start it with `start --ai` or the web UI button.
|
||||
}
|
||||
|
||||
start_frontend() {
|
||||
_launch "NEXUS FRONTEND SERVICE" "$FRONTEND_PID" "$FRONTEND_LOG" "$FRONTEND_DIR" \
|
||||
npm run dev -- --host 0.0.0.0
|
||||
_wait_for_port "NEXUS FRONTEND SERVICE" 5173 "$FRONTEND_PID" "$FRONTEND_LOG"
|
||||
}
|
||||
|
||||
start_all() {
|
||||
# Memory + backend launch in parallel, wait for both before starting frontend
|
||||
_launch "NEXUS MEMORY SERVICE" "$MEMORY_PID" "$MEMORY_LOG" "$NEXUS_ROOT" \
|
||||
"$PYTHON" -m uvicorn synapse.memory.service:app --host 0.0.0.0 --port 8001 --reload
|
||||
_launch "NEXUS BACKEND SERVICE" "$BACKEND_PID" "$BACKEND_LOG" "$NEXUS_ROOT" \
|
||||
"$PYTHON" -m uvicorn synapse.main:sio_app --host 0.0.0.0 --port 8000 --reload
|
||||
_wait_for_port "NEXUS MEMORY SERVICE" 8001 "$MEMORY_PID" "$MEMORY_LOG"
|
||||
_wait_for_port "NEXUS BACKEND SERVICE" 8000 "$BACKEND_PID" "$BACKEND_LOG"
|
||||
# AI is manual now — start it with `start --ai` or the web UI button.
|
||||
_launch "NEXUS FRONTEND SERVICE" "$FRONTEND_PID" "$FRONTEND_LOG" "$FRONTEND_DIR" \
|
||||
npm run dev -- --host 0.0.0.0
|
||||
_wait_for_port "NEXUS FRONTEND SERVICE" 5173 "$FRONTEND_PID" "$FRONTEND_LOG"
|
||||
}
|
||||
|
||||
# -----------------------------
|
||||
# STOP COMMANDS
|
||||
# -----------------------------
|
||||
|
||||
stop_ollama() {
|
||||
# Prefer the backend endpoint for a clean OllamaManager shutdown; if the
|
||||
# backend is already down, kill `ollama serve` directly so it never lingers
|
||||
# holding VRAM/RAM. Must run BEFORE the backend is torn down.
|
||||
if curl -s --max-time 5 -X POST http://localhost:8000/ollama/stop > /dev/null 2>&1; then
|
||||
echo "NEXUS OLLAMA STOPPED"
|
||||
elif pgrep -f "ollama serve" > /dev/null 2>&1; then
|
||||
pkill -TERM -f "ollama serve" 2>/dev/null
|
||||
echo "NEXUS OLLAMA STOPPED (direct)"
|
||||
fi
|
||||
}
|
||||
|
||||
stop_memory() {
|
||||
_stop_service "NEXUS MEMORY SERVICE" "$MEMORY_PID" 8001 "uvicorn synapse.memory"
|
||||
}
|
||||
|
||||
stop_backend() {
|
||||
stop_ollama
|
||||
_stop_service "NEXUS BACKEND SERVICE" "$BACKEND_PID" 8000 "uvicorn synapse.main"
|
||||
}
|
||||
|
||||
stop_frontend() {
|
||||
_stop_service "NEXUS FRONTEND SERVICE" "$FRONTEND_PID" 5173 "vite --host" "npm run dev"
|
||||
}
|
||||
|
||||
stop_all() {
|
||||
stop_memory
|
||||
stop_backend
|
||||
stop_frontend
|
||||
}
|
||||
|
||||
# -----------------------------
|
||||
# STATUS COMMAND
|
||||
# -----------------------------
|
||||
|
||||
status_services() {
|
||||
echo "Nexus Service Status:"
|
||||
echo
|
||||
echo "Backend:"
|
||||
_status "Synapse " "$BACKEND_PID" 8000
|
||||
_status "Memory service" "$MEMORY_PID" 8001
|
||||
echo
|
||||
echo "Frontend:"
|
||||
_status "Vite " "$FRONTEND_PID" 5173
|
||||
echo
|
||||
echo "Model server:"
|
||||
if curl -s --max-time 1 "http://localhost:11434/api/tags" > /dev/null 2>&1; then
|
||||
echo " Ollama : RUNNING (:11434)"
|
||||
else
|
||||
echo " Ollama : STOPPED"
|
||||
fi
|
||||
}
|
||||
|
||||
# -----------------------------
|
||||
# LOGS COMMAND
|
||||
# -----------------------------
|
||||
|
||||
show_logs() {
|
||||
case "$1" in
|
||||
--frontend|-f)
|
||||
echo "=== FRONTEND LOGS ==="
|
||||
tail -n 50 "$FRONTEND_LOG"
|
||||
;;
|
||||
--backend|-b)
|
||||
echo "=== BACKEND LOGS ==="
|
||||
tail -n 50 "$BACKEND_LOG"
|
||||
;;
|
||||
--memory|-m)
|
||||
echo "=== MEMORY SERVICE LOGS ==="
|
||||
tail -n 50 "$MEMORY_LOG"
|
||||
;;
|
||||
""|all)
|
||||
echo "=== MEMORY SERVICE LOGS ==="
|
||||
tail -n 30 "$MEMORY_LOG"
|
||||
echo
|
||||
echo "=== BACKEND LOGS ==="
|
||||
tail -n 30 "$BACKEND_LOG"
|
||||
echo
|
||||
echo "=== FRONTEND LOGS ==="
|
||||
tail -n 30 "$FRONTEND_LOG"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown logs target: '$1'"
|
||||
echo "Usage: ncp logs [frontend|backend|memory|all]"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# -----------------------------
|
||||
# DOCTOR COMMAND
|
||||
# -----------------------------
|
||||
|
||||
doctor() {
|
||||
echo "Running Nexus Diagnostics..."
|
||||
echo
|
||||
|
||||
echo "Checking directories..."
|
||||
[ -d "$NEXUS_ROOT" ] && echo " ✔ Nexus root found" || echo " ✘ Missing Nexus root"
|
||||
[ -d "$FRONTEND_DIR" ] && echo " ✔ Frontend directory found" || echo " ✘ Missing frontend directory"
|
||||
|
||||
echo
|
||||
echo "Checking Python venv..."
|
||||
[ -x "$PYTHON" ] && echo " ✔ Promethean venv found ($($PYTHON --version 2>&1))" || echo " ✘ Promethean venv missing at $PYTHON"
|
||||
|
||||
echo
|
||||
echo "Checking Node & npm..."
|
||||
command -v node >/dev/null && echo " ✔ Node installed ($(node -v))" || echo " ✘ Node missing"
|
||||
command -v npm >/dev/null && echo " ✔ npm installed ($(npm -v))" || echo " ✘ npm missing"
|
||||
[ -d "$FRONTEND_DIR/node_modules" ] && echo " ✔ Frontend node_modules installed" || echo " ✘ Frontend node_modules missing (run: ncp update)"
|
||||
|
||||
echo
|
||||
echo "Checking Uvicorn..."
|
||||
"$PYTHON" -m uvicorn --version >/dev/null 2>&1 && echo " ✔ Uvicorn installed" || echo " ✘ Uvicorn missing"
|
||||
|
||||
echo
|
||||
echo "Checking backend service..."
|
||||
( cd "$NEXUS_ROOT" && "$PYTHON" -c "from synapse.main import sio_app" ) >/dev/null 2>&1 \
|
||||
&& echo " ✔ Backend module importable" \
|
||||
|| echo " ✘ Backend module failed to import"
|
||||
|
||||
echo
|
||||
echo "Checking memory service..."
|
||||
[ -d "$NEXUS_ROOT/synapse/memory" ] && echo " ✔ Memory module directory found" || echo " ✘ Missing memory module directory"
|
||||
( cd "$NEXUS_ROOT" && "$PYTHON" -c "from synapse.memory.service import app" ) >/dev/null 2>&1 \
|
||||
&& echo " ✔ Memory service module importable" \
|
||||
|| echo " ✘ Memory service module failed to import"
|
||||
[ -w "$NEXUS_ROOT/synapse/memory" ] && echo " ✔ Memory database directory writable" || echo " ✘ Memory database directory not writable"
|
||||
|
||||
echo
|
||||
echo "Checking Ollama..."
|
||||
[ -x "$OLLAMA_BIN" ] && echo " ✔ Ollama binary found ($OLLAMA_BIN)" || echo " ✘ Ollama binary missing at $OLLAMA_BIN"
|
||||
[ -d "$OLLAMA_MODELS_DIR" ] && echo " ✔ Ollama models directory found ($OLLAMA_MODELS_DIR)" || echo " ✘ Ollama models directory missing at $OLLAMA_MODELS_DIR"
|
||||
|
||||
echo
|
||||
status_services
|
||||
}
|
||||
|
||||
# -----------------------------
|
||||
# HELP COMMAND
|
||||
# -----------------------------
|
||||
|
||||
show_help() {
|
||||
echo "Nexus Command Tree"
|
||||
echo
|
||||
echo "Usage: ncp <command> [options]"
|
||||
echo
|
||||
echo "Commands:"
|
||||
echo " panel Launch the Nexus Control Panel"
|
||||
echo
|
||||
echo " chat <message> Send a message, stream the reply"
|
||||
echo " memory list List memory facts"
|
||||
echo " add <text> Add a fact (--section <name>)"
|
||||
echo " rm <id> Delete a fact by id"
|
||||
echo " playbook list List playbooks (* = active)"
|
||||
echo " show <id> Print a playbook's goal + instructions"
|
||||
echo " history [query] Recent conversations (optional keyword)"
|
||||
echo
|
||||
echo " start Start ALL Nexus services (memory + backend + frontend)"
|
||||
echo " --memory, -m Start only the memory service"
|
||||
echo " --frontend,-f Start only the frontend"
|
||||
echo " --backend, -b Start only the backend"
|
||||
echo " --ai, -a Start the AI (Ollama) — manual; not started by default"
|
||||
echo
|
||||
echo " stop Stop ALL Nexus services"
|
||||
echo " --memory, -m Stop only the memory service"
|
||||
echo " --frontend,-f Stop only the frontend"
|
||||
echo " --backend, -b Stop only the backend"
|
||||
echo
|
||||
echo " kill Force-kill all Nexus processes by port (nuclear option)"
|
||||
echo " refresh Restart all services"
|
||||
echo
|
||||
echo " status Show service status"
|
||||
echo " logs Show logs for all services"
|
||||
echo " --memory, -m Memory service logs"
|
||||
echo " --frontend,-f Frontend logs"
|
||||
echo " --backend, -b Backend logs"
|
||||
echo
|
||||
echo " doctor Run Nexus diagnostics"
|
||||
echo " update Update Nexus dependencies"
|
||||
echo " clean Remove runtime files and caches"
|
||||
echo
|
||||
echo " models"
|
||||
echo " list List installed models"
|
||||
echo " available Show models available to install"
|
||||
echo " install <name> Pull a model into Nexus"
|
||||
echo
|
||||
echo " backup Backup Nexus to Gitea (git commit + push)"
|
||||
echo " backup -f, full Backup + snapshot live desktop wiring/notes"
|
||||
echo " backup -c Dry run: what a backup would commit/push"
|
||||
echo " restore Restore Nexus from Gitea (git pull + rebuild)"
|
||||
echo " restore -c Dry run: what a restore would apply"
|
||||
echo
|
||||
echo " help, -h Show this help message"
|
||||
}
|
||||
|
||||
# -----------------------------
|
||||
# UPDATE COMMAND
|
||||
# -----------------------------
|
||||
|
||||
update_nexus() {
|
||||
echo "Updating Project Nexus..."
|
||||
echo
|
||||
|
||||
cd "$NEXUS_ROOT" || exit 1
|
||||
|
||||
echo "Skipping git pull — update only refreshes dependencies."
|
||||
|
||||
echo
|
||||
echo "Updating backend Python dependencies..."
|
||||
if [ -f "requirements-amd.txt" ]; then
|
||||
"$PYTHON" -m pip install -r requirements-amd.txt
|
||||
else
|
||||
echo "No requirements-amd.txt found."
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Updating frontend dependencies..."
|
||||
if [ -d "$FRONTEND_DIR" ]; then
|
||||
cd "$FRONTEND_DIR"
|
||||
npm install
|
||||
else
|
||||
echo "Frontend directory missing — skipping npm install."
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Running post-update diagnostics..."
|
||||
doctor
|
||||
}
|
||||
|
||||
# -----------------------------
|
||||
# BACKUP / RESTORE COMMANDS
|
||||
# -----------------------------
|
||||
|
||||
# Backup/restore go through git → Gitea now (bin/sync.py), not rsync-to-router,
|
||||
# so no SSH/WireGuard reachability gate is needed — git handles its own
|
||||
# connectivity and errors over HTTPS. sync.py is the same entry point the Windows
|
||||
# box uses; it calls bin/restore-linux.sh and bin/backup-linux.sh for the XFCE
|
||||
# desktop half, which only runs here.
|
||||
#
|
||||
# sync.py is stdlib-only, so system python3 works when the venv isn't built yet.
|
||||
sync_py() {
|
||||
local py="$PYTHON"
|
||||
[ -x "$py" ] || py=python3
|
||||
"$py" "$NEXUS_ROOT/bin/sync.py" "$@"
|
||||
}
|
||||
|
||||
restore_nexus() {
|
||||
echo "This pulls the latest backup from Gitea and rebuilds the environment"
|
||||
echo "(venv, npm, theme, panel). Local commits must be pushed or stashed first."
|
||||
printf "Continue? [y/N] "
|
||||
read -r confirm
|
||||
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
|
||||
echo "Restore cancelled."
|
||||
return
|
||||
fi
|
||||
sync_py restore
|
||||
}
|
||||
|
||||
# -----------------------------
|
||||
# MODELS COMMANDS
|
||||
# -----------------------------
|
||||
|
||||
OLLAMA_BIN="$NEXUS_ROOT/ollama/bin/ollama"
|
||||
OLLAMA_MODELS_DIR="$NEXUS_ROOT/models"
|
||||
|
||||
models_list() {
|
||||
if ! curl -sf http://localhost:11434/api/tags > /dev/null 2>&1; then
|
||||
echo "Ollama is not running. Start the backend first with: ncp start -b"
|
||||
return 1
|
||||
fi
|
||||
echo "Installed models:"
|
||||
echo
|
||||
curl -s http://localhost:11434/api/tags | "$PYTHON" -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
models = data.get('models', [])
|
||||
if not models:
|
||||
print(' No models installed.')
|
||||
else:
|
||||
for m in models:
|
||||
size_mb = m['size'] // 1024 // 1024
|
||||
size = f'{size_mb / 1024:.1f} GB' if size_mb >= 1024 else f'{size_mb} MB'
|
||||
print(f' {m[\"name\"]:<35} {size}')
|
||||
"
|
||||
}
|
||||
|
||||
models_available() {
|
||||
echo "Available models (via Ollama library):"
|
||||
echo
|
||||
printf " %-30s %-10s %s\n" "MODEL" "SIZE" "DESCRIPTION"
|
||||
printf " %-30s %-10s %s\n" "-----" "----" "-----------"
|
||||
printf " %-30s %-10s %s\n" "gemma3:1b" "~815 MB" "Google Gemma 3 — fast, lightweight"
|
||||
printf " %-30s %-10s %s\n" "gemma3:4b" "~3.3 GB" "Google Gemma 3 — balanced"
|
||||
printf " %-30s %-10s %s\n" "gemma3:12b" "~8.1 GB" "Google Gemma 3 — capable"
|
||||
printf " %-30s %-10s %s\n" "llama3.2:1b" "~1.3 GB" "Meta Llama 3.2 — fast, lightweight"
|
||||
printf " %-30s %-10s %s\n" "llama3.2:3b" "~2.0 GB" "Meta Llama 3.2 — compact, capable"
|
||||
printf " %-30s %-10s %s\n" "llama3.1:8b" "~4.7 GB" "Meta Llama 3.1 — strong general use"
|
||||
printf " %-30s %-10s %s\n" "mistral:latest" "~4.1 GB" "Mistral 7B — solid all-rounder"
|
||||
printf " %-30s %-10s %s\n" "mistral-nemo" "~7.1 GB" "Mistral Nemo 12B — strong reasoning"
|
||||
printf " %-30s %-10s %s\n" "qwen2.5:3b" "~2.0 GB" "Alibaba Qwen 2.5 — great at code"
|
||||
printf " %-30s %-10s %s\n" "qwen2.5:7b" "~4.7 GB" "Alibaba Qwen 2.5 — strong coder"
|
||||
printf " %-30s %-10s %s\n" "phi4-mini" "~2.5 GB" "Microsoft Phi-4 Mini — efficient"
|
||||
printf " %-30s %-10s %s\n" "phi4:14b" "~8.9 GB" "Microsoft Phi-4 — strong reasoning"
|
||||
printf " %-30s %-10s %s\n" "deepseek-r1:7b" "~4.7 GB" "DeepSeek R1 — reasoning model"
|
||||
printf " %-30s %-10s %s\n" "deepseek-r1:14b" "~9.0 GB" "DeepSeek R1 — strong reasoning"
|
||||
printf " %-30s %-10s %s\n" "codellama:7b" "~3.8 GB" "Meta Code Llama — code focused"
|
||||
printf " %-30s %-10s %s\n" "nomic-embed-text" "~274 MB" "Text embeddings model"
|
||||
echo
|
||||
echo "Install any model with: ncp models install <model>"
|
||||
echo "Browse more at: https://ollama.com/library"
|
||||
}
|
||||
|
||||
models_install() {
|
||||
local model="$1"
|
||||
if [ -z "$model" ]; then
|
||||
echo "Usage: ncp models install <model>"
|
||||
echo "Run 'ncp models available' to see options."
|
||||
return 1
|
||||
fi
|
||||
if [ ! -x "$OLLAMA_BIN" ]; then
|
||||
echo "Ollama binary not found at $OLLAMA_BIN"
|
||||
return 1
|
||||
fi
|
||||
echo "Pulling '$model' into $OLLAMA_MODELS_DIR ..."
|
||||
echo
|
||||
OLLAMA_MODELS="$OLLAMA_MODELS_DIR" "$OLLAMA_BIN" pull "$model"
|
||||
echo
|
||||
echo "Done. Run 'ncp models list' to verify."
|
||||
}
|
||||
|
||||
# -----------------------------
|
||||
# CLEAN COMMAND
|
||||
# -----------------------------
|
||||
|
||||
clean_nexus() {
|
||||
echo "Cleaning Nexus runtime files..."
|
||||
echo
|
||||
|
||||
echo "Removing PID files..."
|
||||
rm -f "$PID_DIR"/*.pid
|
||||
|
||||
echo "Removing logs..."
|
||||
rm -f "$LOG_DIR"/*.log
|
||||
|
||||
echo "Removing Python cache..."
|
||||
find "$NEXUS_ROOT" -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null
|
||||
|
||||
echo "Removing Node/Vite cache..."
|
||||
find "$FRONTEND_DIR" -type d -name ".vite" -exec rm -rf {} + 2>/dev/null
|
||||
|
||||
echo
|
||||
echo "Cleanup complete."
|
||||
}
|
||||
|
||||
# -----------------------------
|
||||
# COMMAND TREE ROUTER
|
||||
# -----------------------------
|
||||
|
||||
subcommand="$1"
|
||||
shift
|
||||
|
||||
case "$subcommand" in
|
||||
|
||||
panel) python3 "$NEXUS_ROOT/management/controlpanel.py" ;;
|
||||
|
||||
chat|memory|playbook|history)
|
||||
"$PYTHON" "$NEXUS_ROOT/management/nexus_api.py" "$subcommand" "$@" ;;
|
||||
|
||||
start)
|
||||
case "$1" in
|
||||
--memory|-m) start_memory ;;
|
||||
--frontend|-f) start_frontend ;;
|
||||
--backend|-b) start_backend ;;
|
||||
--ai|-a) start_ollama ;;
|
||||
""|all) start_all ;;
|
||||
*) show_help ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
stop)
|
||||
case "$1" in
|
||||
--memory|-m) stop_memory ;;
|
||||
--frontend|-f) stop_frontend ;;
|
||||
--backend|-b) stop_backend ;;
|
||||
""|all) stop_all ;;
|
||||
*) show_help ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
refresh) stop_all && start_all ;;
|
||||
kill) kill_services ;;
|
||||
status) status_services ;;
|
||||
logs) show_logs "$1" ;;
|
||||
doctor) doctor ;;
|
||||
update) update_nexus ;;
|
||||
clean) clean_nexus ;;
|
||||
help|-h|"") show_help ;;
|
||||
|
||||
nvidia-reqs) "$PYTHON" "$NEXUS_ROOT/bin/gen-nvidia-reqs.py" ;;
|
||||
|
||||
backup)
|
||||
case "$1" in
|
||||
-f|full) sync_py backup --full ;;
|
||||
-c|--claude|check) sync_py backup --check ;;
|
||||
"") sync_py backup ;;
|
||||
*) echo "Usage: ncp backup [-f|full|-c]" ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
restore)
|
||||
case "$1" in
|
||||
-f|full) restore_nexus ;;
|
||||
# Dry run: no confirmation prompt, it changes nothing.
|
||||
-c|--claude|check) sync_py restore --check ;;
|
||||
"") restore_nexus ;;
|
||||
*) echo "Usage: ncp restore [-f|full|-c]" ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
models)
|
||||
case "$1" in
|
||||
list) models_list ;;
|
||||
available|search) models_available ;;
|
||||
install) models_install "$2" ;;
|
||||
*) echo "Usage: ncp models <list|available|install <model>>" ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown Nexus command: '$subcommand'"
|
||||
echo "Use 'ncp help' for available commands."
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user