diff --git a/.env.example b/.env.example index 9d75277..9e23301 100644 --- a/.env.example +++ b/.env.example @@ -64,9 +64,19 @@ # treated as the anonymous admin user. Use only for trusted local deployments. NAVI_AUTH_ENABLED=true +# ── Web UI / server shape ──────────────────────────────────────────────────────── +# NAVI_WEBCLIENT_ENABLED=false removes all web-facing routes (index, static +# mounts, /admin panel, /debug*) and skips the navi_ui MCP server — a pure +# API/WS server for terminal clients. Needs a restart to take effect. +NAVI_WEBCLIENT_ENABLED=true +# Bind address/port for the `navi-server` launcher (and systemd unit). +NAVI_HOST=127.0.0.1 +NAVI_PORT=8000 + # ── Internal navi_ui MCP server ───────────────────────────────────────────────── # Lets the agent push structured UI components (card_grid, form) to the webclient -# via the render_component tool. Started automatically in the app lifespan. +# via the render_component tool. Started automatically in the app lifespan +# (only when the web UI is enabled). NAVI_UI_MCP_ENABLED=true NAVI_UI_MCP_HOST=127.0.0.1 NAVI_UI_MCP_PORT=8001 diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..f452a1b --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,73 @@ +# Deployment + +A self-contained Navi module for a server: dockerized PostgreSQL + the Navi +API server under systemd + `navi-code` in PATH. One command, SSH-only access, +web UI off by default. + +## Install + +```bash +ssh server +git clone -b deploy navi-1 && cd navi-1 +bash deploy/install.sh +``` + +After the install finishes, fill in the one remaining secret and restart: + +```bash +$EDITOR .env # OLLAMA_API_KEY= +sudo systemctl restart navi +navi-code # terminal client, ready to work +``` + +## What the default deployed state looks like + +| Piece | State | +|---|---| +| Navi API server | systemd `navi.service`, `Restart=always`, starts on boot, binds `127.0.0.1:8000` | +| PostgreSQL | docker container `navi-postgres` (pgvector image), `restart: always`, bound to `127.0.0.1:5432`, data in the `navi-pgdata` volume | +| Web UI (`/`, `/assets`, `/admin`, debug panels, navi_ui MCP) | **off** (`NAVI_WEBCLIENT_ENABLED=false`) | +| Auth | **off** (`NAVI_AUTH_ENABLED=false`) — trusted single-server module, the API listens on localhost only | +| Terminal client | `navi-code` symlinked into `/usr/local/bin`, talks to `http://localhost:8000` | +| LLM | Ollama Cloud (`OLLAMA_HOST=https://ollama.com`), model priority lists come from the profile configs | + +## Branch layout + +- `master` — the full project, unconfigured, with all deployment machinery + (`deploy/`, the `NAVI_WEBCLIENT_ENABLED` / `NAVI_HOST` / `NAVI_PORT` flags, + the `navi-server` launcher). +- `deploy` — identical to master except for a committed `.env` with the + deployment defaults filled in. Deploying = `git clone -b deploy`. + +## Re-enabling the web panel later + +Nothing is removed — the webclient and its code are all in the tree: + +```bash +# in .env: +NAVI_WEBCLIENT_ENABLED=true +NAVI_UI_MCP_ENABLED=true +sudo systemctl restart navi +``` + +For remote browser access, tunnel or reverse-proxy to the API port; auth is +off by default, so keep it behind SSH or enable `NAVI_AUTH_ENABLED` first. + +## Day-2 operations + +```bash +systemctl status navi # server supervision +journalctl -u navi -f # live logs +sudo docker compose -f deploy/docker-compose.yml --env-file .env ps +sudo docker exec -it navi-postgres psql -U navi -d navi +``` + +Updating: `git pull && sudo systemctl restart navi` (the install script is +idempotent — re-running it after a pull also refreshes the venv). + +## Files + +- `install.sh` — everything above, in one command +- `docker-compose.yml` — the postgres container +- `env.template` — copied to `.env` on first install; the `deploy` branch + carries a filled-in copy as its only diff from master \ No newline at end of file diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml new file mode 100644 index 0000000..2db9462 --- /dev/null +++ b/deploy/docker-compose.yml @@ -0,0 +1,28 @@ +# Local PostgreSQL for a self-contained Navi deployment. +# The pgvector image ships the `vector` extension; pg_trgm ships in postgres +# contrib. Navi's DDL expects both to be installed (install.sh handles that). +# +# Bound to 127.0.0.1 only: the database is never exposed past the host. +# restart: always — the module must survive reboots (server "lives always"). + +services: + postgres: + image: pgvector/pgvector:pg16 + container_name: navi-postgres + restart: always + environment: + POSTGRES_DB: navi + POSTGRES_USER: navi + POSTGRES_PASSWORD: ${NAVI_DB_PASSWORD:?NAVI_DB_PASSWORD must be set in .env} + ports: + - "127.0.0.1:5432:5432" + volumes: + - navi-pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U navi -d navi"] + interval: 5s + timeout: 3s + retries: 12 + +volumes: + navi-pgdata: \ No newline at end of file diff --git a/deploy/env.template b/deploy/env.template new file mode 100644 index 0000000..e6d7e54 --- /dev/null +++ b/deploy/env.template @@ -0,0 +1,48 @@ +# Navi — deployment .env template. +# install.sh copies this to .env (repo root) on first run and fills in +# NAVI_DB_PASSWORD. Edit the placeholders marked TODO, then restart: +# systemctl restart navi + +# ── LLM: Ollama Cloud ──────────────────────────────────────────────── +# Profiles' config.json already carry cloud-first model priority lists. +OLLAMA_HOST=https://ollama.com +# TODO: your Ollama Cloud API key (https://ollama.com/settings/keys) +OLLAMA_API_KEY= +OLLAMA_DEFAULT_MODEL=glm-5.3-flash:cloud +OLLAMA_NUM_CTX=65536 +OLLAMA_THINK=true +OLLAMA_REQUEST_TIMEOUT=30 + +# Embeddings for memory vector search. Empty host falls back to OLLAMA_HOST +# (ollama.com). If the cloud account has no embedding model, leave empty — +# the server starts with a warning and memory search degrades gracefully. +EMBEDDING_OLLAMA_HOST= +EMBEDDING_OLLAMA_API_KEY= +EMBEDDING_MODEL=nomic-embed-text:latest + +# ── Database (dockerized postgres, see deploy/docker-compose.yml) ──── +# install.sh generates NAVI_DB_PASSWORD and keeps this URL in sync. +DATABASE_URL=postgresql://navi:CHANGEME@127.0.0.1:5432/navi +NAVI_DB_PASSWORD=CHANGEME + +# ── Deployment shape: terminal client only, web UI off ─────────────── +# To re-enable the web panel later: set true and `systemctl restart navi`. +NAVI_WEBCLIENT_ENABLED=false +# navi_ui MCP serves the webclient only — off together with it. +NAVI_UI_MCP_ENABLED=false +# Trusted single-server module: auth off. The server binds 127.0.0.1 only +# (NAVI_HOST below); remote terminals connect via SSH tunnel. +NAVI_AUTH_ENABLED=false + +# Server bind (used by navi-server / the systemd unit) +NAVI_HOST=127.0.0.1 +NAVI_PORT=8000 + +# ─── Client profile ────────────────────────────────────────────────── +NAVI_DEFAULT_PROFILE_ID=navi_code + +# ─── Misc ──────────────────────────────────────────────────────────── +LOG_LEVEL=INFO +SESSION_MESSAGES_WINDOW=1000 +WS_REPLAY_BUFFER_SIZE=500 +CONTEXT_COMPRESSION_ENABLED=true \ No newline at end of file diff --git a/deploy/install.sh b/deploy/install.sh new file mode 100755 index 0000000..2a72ee2 --- /dev/null +++ b/deploy/install.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash +# Navi — one-command deployment for a server module. +# +# ssh server +# git clone -b deploy navi-1 && cd navi-1 +# bash deploy/install.sh +# +# What this does (idempotent — safe to re-run): +# 1. checks prerequisites (python 3.11+, docker, docker compose) +# 2. provisions .env from deploy/env.template (generates a DB password) +# 3. starts dockerized PostgreSQL (pgvector, restart: always, 127.0.0.1 only) +# and installs the vector + pg_trgm extensions +# 4. builds the venv and installs navi (navi-server + navi-code entry points) +# 5. writes + enables + starts the systemd unit (Restart=always — the +# server lives from installation and survives reboots) +# 6. waits for /health, symlinks navi-code/navi-server into PATH +# +# Default deployed state: web UI off, auth off, terminal client only. +# Re-enable the web panel later: set NAVI_WEBCLIENT_ENABLED=true in .env +# and `sudo systemctl restart navi`. + +set -euo pipefail + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_DIR" + +say() { printf '\n\033[1;34m==> %s\033[0m\n' "$*"; } +err() { printf '\033[1;31mERROR: %s\033[0m\n' "$*" >&2; } + +SUDO="" +[ "$(id -u)" -eq 0 ] || SUDO="sudo" + +# ── 1. prerequisites ──────────────────────────────────────────────── +say "Checking prerequisites" + +if ! command -v python3 >/dev/null; then + err "python3 not found — install Python 3.11+ first"; exit 1 +fi +PYOK=$(python3 - <<'EOF' +import sys +print(0 if sys.version_info >= (3, 11) else 1) +EOF +) +if [ "$PYOK" != "0" ]; then + err "python3 is older than 3.11 — upgrade and re-run"; exit 1 +fi + +if ! command -v docker >/dev/null; then + err "docker not found — install Docker (https://docs.docker.com/engine/install/) first"; exit 1 +fi +if ! $SUDO docker compose version >/dev/null 2>&1; then + err "docker compose (v2 plugin) not available"; exit 1 +fi +echo "OK: python3 $(python3 --version | cut -d' ' -f2), docker + compose" + +# ── 2. .env ──────────────────────────────────────────────────────── +say "Provisioning .env" +if [ ! -f .env ]; then + cp deploy/env.template .env + DB_PASS="$(head -c 24 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 32)" + sed -i "s|^NAVI_DB_PASSWORD=.*|NAVI_DB_PASSWORD=${DB_PASS}|" .env + sed -i "s|^DATABASE_URL=.*|DATABASE_URL=postgresql://navi:${DB_PASS}@127.0.0.1:5432/navi|" .env + echo "Created .env with a generated DB password." + echo "TODO left in .env: OLLAMA_API_KEY (Ollama Cloud key)." +else + echo ".env already exists — keeping it." +fi + +# ── 3. PostgreSQL ─────────────────────────────────────────────────── +say "Starting dockerized PostgreSQL (pgvector)" +$SUDO docker compose -f deploy/docker-compose.yml --env-file .env up -d +for i in $(seq 1 30); do + if $SUDO docker exec navi-postgres pg_isready -U navi -d navi >/dev/null 2>&1; then break; fi + sleep 2 +done +$SUDO docker exec navi-postgres psql -U navi -d navi -q \ + -c "CREATE EXTENSION IF NOT EXISTS vector;" \ + -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" +echo "PostgreSQL ready (127.0.0.1:5432, volume navi-pgdata)." + +# ── 4. venv + install ─────────────────────────────────────────────── +say "Building venv and installing navi" +if [ ! -d .venv ]; then + python3 -m venv .venv +fi +./.venv/bin/pip install --upgrade pip -q +./.venv/bin/pip install -e . +echo "Installed entry points: navi-server, navi-code" + +# ── 5. systemd unit ───────────────────────────────────────────────── +say "Installing systemd unit (navi.service)" +RUN_USER="$(id -un)" +cat > /tmp/navi.service </dev/null; then break; fi + if [ "$i" = "60" ]; then HEALTH="fail"; fi + sleep 2 +done +if [ "$HEALTH" != "ok" ]; then + err "server did not become healthy — check: journalctl -u navi -e" + exit 1 +fi +echo "Server healthy at http://127.0.0.1:${NAVI_PORT_CFG}/health" + +say "Symlinking navi-code and navi-server into /usr/local/bin" +for bin in navi-code navi-server; do + $SUDO ln -sf "$REPO_DIR/.venv/bin/$bin" "/usr/local/bin/$bin" +done + +cat <<'EOF' + +Deployment complete. + • navi-code — terminal client (run it from any shell) + • systemctl status navi — server supervision + • journalctl -u navi -f — live logs + +Still TODO (if not filled yet): OLLAMA_API_KEY in .env, then + sudo systemctl restart navi + +Re-enable the web panel later: set NAVI_WEBCLIENT_ENABLED=true in .env +and restart the unit. Nothing else is needed — the code is all here. +EOF \ No newline at end of file diff --git a/docs/config.md b/docs/config.md index a399c56..b83eef7 100644 --- a/docs/config.md +++ b/docs/config.md @@ -96,6 +96,16 @@ |---|---|---|---| | `DATABASE_URL` | str | `""` | PostgreSQL URL (`postgresql://user:pass@host:port/db`). **Required** — Navi requires PostgreSQL 15+ with `pgvector` extension. | +## Server / deployment shape + +| Variable | Type | Default | Description | +|---|---|---|---| +| `NAVI_WEBCLIENT_ENABLED` | bool | `true` | Master switch for the web UI. `false` registers none of the web-facing routes (`/`, `/assets`, `/images`, `/content-viewers`, `/content`, `/admin` panel, `/debug*`) and skips the navi_ui MCP server — a pure API/WS server for terminal clients. The admin JSON API (`/admin/*`) stays, still gated by `require_admin`. Toggling requires a server restart. | +| `NAVI_HOST` | str | `127.0.0.1` | Bind address for the `navi-server` launcher (uvicorn). `127.0.0.1` = local-only; remote clients connect via SSH tunnel or reverse proxy. | +| `NAVI_PORT` | int | `8000` | Bind port for the `navi-server` launcher. | + +See [`deploy/README.md`](../deploy/README.md) for the one-command server deployment (`bash deploy/install.sh`): dockerized PostgreSQL + systemd + `navi-code` in PATH, web UI and auth off by default. + ## Logging | Variable | Type | Default | Description | diff --git a/docs/index.md b/docs/index.md index 593d8a8..7e9494c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -40,6 +40,7 @@ | [`android-client.md`](android-client.md) | Android WebView client — architecture, build/deploy | | [`permissions.md`](permissions.md) | Permission gate — authoritative backend confirmation for destructive tool calls (**design only, not yet implemented**) | | [`auth.md`](auth.md) | Auth: multi-user OAuth via gnexus-auth, or disable with `NAVI_AUTH_ENABLED=false` | +| [`../deploy/README.md`](../deploy/README.md) | Server deployment — one command: dockerized postgres + systemd + navi-code, web UI off | | [`config.md`](config.md) | All environment variables with types and defaults | | [`api.md`](api.md) | REST API endpoints + full WebSocket event schemas and sequences | | [`testing.md`](testing.md) | Test layout, fixtures, how to run the suite |