b0aa0438af
Captures the known-good state after theme consolidation and consistency reconciliation: - NexusOS GTK/xfwm4/icon theme assets (assets/themes, management/Mint-Y-Nexus) - Tightened right-click menus, visible separators, no menu icons - assets/themes/install-theme.sh: idempotent restore of all wiring (symlinks, xfconf xsettings+xfwm4, GTK 3/4 settings.ini) - .gitignore excludes venv/ollama/models/runtime/db Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
560 lines
17 KiB
Bash
Executable File
560 lines
17 KiB
Bash
Executable File
#!/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
|
|
echo " $name is already running (PID $(cat "$pid_file"))"
|
|
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
|
|
}
|
|
|
|
_stop() {
|
|
# _stop <label> <pid_file>
|
|
local label="$1" pid_file="$2"
|
|
if [ -f "$pid_file" ]; then
|
|
local pid
|
|
pid=$(cat "$pid_file")
|
|
if kill -0 "$pid" 2>/dev/null; then
|
|
kill "$pid" 2>/dev/null
|
|
echo "$label STOPPED"
|
|
else
|
|
echo "$label NOT RUNNING"
|
|
fi
|
|
rm -f "$pid_file"
|
|
else
|
|
echo "$label NOT RUNNING"
|
|
fi
|
|
}
|
|
|
|
_status() {
|
|
# _status <name> <pid_file>
|
|
local name="$1" pid_file="$2"
|
|
if [ -f "$pid_file" ] && kill -0 "$(cat "$pid_file")" 2>/dev/null; then
|
|
echo " $name: RUNNING (PID $(cat "$pid_file"))"
|
|
else
|
|
echo " $name: STOPPED"
|
|
fi
|
|
}
|
|
|
|
# -----------------------------
|
|
# KILL COMMAND
|
|
# -----------------------------
|
|
|
|
kill_services() {
|
|
echo "Force-killing all Nexus processes..."
|
|
local ports=(8000 8001 5173)
|
|
local names=("SYNAPSE" "MEMORY" "INTERFACE")
|
|
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
|
|
rm -f "$PID_DIR"/*.pid
|
|
echo "Done."
|
|
}
|
|
|
|
# -----------------------------
|
|
# START COMMANDS
|
|
# -----------------------------
|
|
|
|
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
|
|
sleep 3
|
|
_check "NEXUS MEMORY SERVICE" "$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
|
|
sleep 3
|
|
_check "NEXUS BACKEND SERVICE" "$BACKEND_PID" "$BACKEND_LOG"
|
|
}
|
|
|
|
start_frontend() {
|
|
_launch "NEXUS FRONTEND SERVICE" "$FRONTEND_PID" "$FRONTEND_LOG" "$FRONTEND_DIR" \
|
|
npm run dev -- --host 0.0.0.0
|
|
sleep 5
|
|
_check "NEXUS FRONTEND SERVICE" "$FRONTEND_PID" "$FRONTEND_LOG"
|
|
}
|
|
|
|
start_all() {
|
|
# Memory + backend launch in parallel, then single wait for both
|
|
_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
|
|
sleep 3
|
|
_check "NEXUS MEMORY SERVICE" "$MEMORY_PID" "$MEMORY_LOG"
|
|
_check "NEXUS BACKEND SERVICE" "$BACKEND_PID" "$BACKEND_LOG"
|
|
# Frontend last (Vite takes a moment longer)
|
|
_launch "NEXUS FRONTEND SERVICE" "$FRONTEND_PID" "$FRONTEND_LOG" "$FRONTEND_DIR" \
|
|
npm run dev -- --host 0.0.0.0
|
|
sleep 5
|
|
_check "NEXUS FRONTEND SERVICE" "$FRONTEND_PID" "$FRONTEND_LOG"
|
|
}
|
|
|
|
# -----------------------------
|
|
# STOP COMMANDS
|
|
# -----------------------------
|
|
|
|
stop_memory() {
|
|
_stop "NEXUS MEMORY SERVICE" "$MEMORY_PID"
|
|
}
|
|
|
|
stop_backend() {
|
|
_stop "NEXUS BACKEND SERVICE" "$BACKEND_PID"
|
|
}
|
|
|
|
stop_frontend() {
|
|
_stop "NEXUS FRONTEND SERVICE" "$FRONTEND_PID"
|
|
}
|
|
|
|
stop_all() {
|
|
stop_memory
|
|
stop_backend
|
|
stop_frontend
|
|
}
|
|
|
|
# -----------------------------
|
|
# STATUS COMMAND
|
|
# -----------------------------
|
|
|
|
status_services() {
|
|
echo "Nexus Service Status:"
|
|
echo
|
|
_status "Memory service" "$MEMORY_PID"
|
|
_status "Backend " "$BACKEND_PID"
|
|
_status "Frontend " "$FRONTEND_PID"
|
|
}
|
|
|
|
# -----------------------------
|
|
# 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"
|
|
|
|
echo
|
|
echo "Checking Uvicorn..."
|
|
"$PYTHON" -m uvicorn --version >/dev/null 2>&1 && echo " ✔ Uvicorn installed" || echo " ✘ Uvicorn missing"
|
|
|
|
echo
|
|
status_services
|
|
}
|
|
|
|
# -----------------------------
|
|
# HELP COMMAND
|
|
# -----------------------------
|
|
|
|
show_help() {
|
|
echo "Nexus Command Tree"
|
|
echo
|
|
echo "Usage: ncp <command> [options]"
|
|
echo
|
|
echo "Commands:"
|
|
echo " chat Open the interactive chat TUI"
|
|
echo " playbook Browse and edit playbooks"
|
|
echo " panel Launch the Nexus Control Panel"
|
|
echo
|
|
echo " start Start ALL Nexus services (memory + backend + frontend)"
|
|
echo " start --memory, -m Start only the memory service"
|
|
echo " start --frontend,-f Start only the frontend"
|
|
echo " start --backend, -b Start only the backend"
|
|
echo
|
|
echo " stop Stop ALL Nexus services"
|
|
echo " stop --memory, -m Stop only the memory service"
|
|
echo " stop --frontend,-f Stop only the frontend"
|
|
echo " stop --backend, -b Stop only the backend"
|
|
echo
|
|
echo " kill Force-kill all Nexus processes by port (nuclear option)
|
|
refresh Restart all services"
|
|
echo
|
|
echo " status Show service status"
|
|
echo " logs Show logs for all services"
|
|
echo " logs --memory, -m Memory service logs"
|
|
echo " logs --frontend,-f Frontend logs"
|
|
echo " logs --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 vital Nexus files to router (smart/incremental)"
|
|
echo " backup -f, full Full backup of all project files"
|
|
echo " restore Restore vital Nexus files from router backup"
|
|
echo " restore -f, full Full restore of all project files"
|
|
echo
|
|
echo " help, -h Show this help message"
|
|
}
|
|
|
|
# -----------------------------
|
|
# UPDATE COMMAND
|
|
# -----------------------------
|
|
|
|
update_nexus() {
|
|
echo "Updating Project Nexus..."
|
|
echo
|
|
|
|
cd "$NEXUS_ROOT" || exit 1
|
|
|
|
if [ -d ".git" ]; then
|
|
echo "Pulling latest changes from Git..."
|
|
git pull
|
|
else
|
|
echo "No Git repository found — skipping git pull."
|
|
fi
|
|
|
|
echo
|
|
echo "Updating backend Python dependencies..."
|
|
if [ -f "amd_requirements.txt" ]; then
|
|
"$PYTHON" -m pip install -r amd_requirements.txt
|
|
else
|
|
echo "No amd_requirements.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
|
|
# -----------------------------
|
|
|
|
ensure_router_reachable() {
|
|
echo "Checking router connectivity..."
|
|
if ssh -q -o BatchMode=yes -o ConnectTimeout=5 router exit 2>/dev/null; then
|
|
echo "Router is reachable."
|
|
return 0
|
|
fi
|
|
|
|
echo "Router unreachable. Activating WireGuard (wgs_client)..."
|
|
nmcli connection up wgs_client
|
|
|
|
echo "Waiting for VPN to establish..."
|
|
sleep 5
|
|
|
|
if ssh -q -o BatchMode=yes -o ConnectTimeout=10 router exit 2>/dev/null; then
|
|
echo "Router reachable via VPN."
|
|
return 0
|
|
fi
|
|
|
|
echo "Router still unreachable after VPN activation."
|
|
return 1
|
|
}
|
|
|
|
backup_nexus() {
|
|
ensure_router_reachable || return 1
|
|
echo "Backing up Nexus to router..."
|
|
bash "$NEXUS_ROOT/bin/backup-smart.sh"
|
|
}
|
|
|
|
backup_nexus_full() {
|
|
ensure_router_reachable || return 1
|
|
echo "Full backup of Nexus to router..."
|
|
bash "$NEXUS_ROOT/bin/backup.sh"
|
|
}
|
|
|
|
restore_nexus() {
|
|
local script="$1"
|
|
ensure_router_reachable || return 1
|
|
echo "WARNING: This will overwrite local files with the remote backup."
|
|
printf "Continue? [y/N] "
|
|
read -r confirm
|
|
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
|
|
echo "Restore cancelled."
|
|
return
|
|
fi
|
|
bash "$NEXUS_ROOT/bin/$script"
|
|
}
|
|
|
|
# -----------------------------
|
|
# 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) "$PYTHON" "$NEXUS_ROOT/management/nexus-chat.py" ;;
|
|
playbook) "$PYTHON" "$NEXUS_ROOT/management/nexus-playbook.py" ;;
|
|
|
|
start)
|
|
case "$1" in
|
|
--memory|-m) start_memory ;;
|
|
--frontend|-f) start_frontend ;;
|
|
--backend|-b) start_backend ;;
|
|
""|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) backup_nexus_full ;;
|
|
"") backup_nexus ;;
|
|
*) echo "Usage: ncp backup [-f|full]" ;;
|
|
esac
|
|
;;
|
|
|
|
restore)
|
|
case "$1" in
|
|
-f|full) restore_nexus restore-full.sh ;;
|
|
"") restore_nexus restore.sh ;;
|
|
*) echo "Usage: ncp restore [-f|full]" ;;
|
|
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
|