The Problem

Users need a portable, offline‑first server that bundles education, reference, and AI tools without relying on continuous internet access. Maintaining such a heterogeneous stack (Docker images, AI models, map data, etc.) is error‑prone and hard to orchestrate on a single device.

What This Does

project‑nomad ships a self‑contained web UI and API that orchestrates the required containers, downloads content packs, and exposes an Ollama‑based chat with RAG via Qdrant. The core server lives in admin/bin/server.ts and is configured through admin/.env.example. UI components under admin/inertia/** (React + Tailwind) render the “Command Center”, while service classes in admin/app/services/ implement the heavy lifting (Docker orchestration, model download, benchmark, etc.).

Key files:

  • admin/app/services/ollama_service.ts – wraps Ollama client, downloads models, invokes inference.
  • admin/app/services/docker_service.ts – creates, starts, and monitors the auxiliary containers (Kiwix, Kolibri, Qdrant, etc.).
  • admin/app/controllers/*_controller.ts – HTTP entry points that call the services.

How It Is Wired

Execution starts at admin/bin/server.ts which boots the AdonisJS server and registers the Inertia app (admin/inertia/app/app.tsx). The request flow for a typical AI chat is:

  1. HTTP request → admin/app/controllers/chats_controller.ts → admin/app/services/chat_service.ts.
  2. chat_service calls admin/app/services/ollama_service.ts → dispatchModelDownload (if model missing) → network call via this.ollama.list (outbound) → model inference (counts as one of the 6 functions that call a model).
  3. The response bubbles back through the controller to the client UI component admin/inertia/components/chat/index.tsx.

The most fan‑in module is admin/inertia/lib/api.ts (62 functions, called from six files). Functions with the widest blast radius include catchInternal (called from 59 places) and status (28 places).

Two circular imports exist between admin/app/models/chat_message.ts and admin/app/models/chat_session.ts; breaking this cycle would reduce build‑time coupling.

Deeply nested UI components (e.g., TierSelectionModal.tsx) reach an indentation depth of 10, making future UI tweaks harder to reason about.

How To Use It

# Clone the repo
git clone https://github.com/moses-y/project-nomad.git
cd project-nomad

# Build the production container
docker build -t project-nomad:latest -f Dockerfile .

# Create a .env from the example and edit as needed
cp admin/.env.example .env   # edit .env for any required keys (e.g., DB path)

# Run the container (exposes port 8080)
docker run -p 8080:8080 --env-file .env project-nomad:latest

The server starts on http://localhost:8080. All UI interactions go through the Inertia React frontend; API calls are handled by the AdonisJS controllers under admin/app/controllers/.

Real‑World Use

A field clinic installs the container on a modest Ubuntu workstation, runs the Docker image, and uses the web UI to:

  • Pull the latest offline Wikipedia ZIM file (admin/app/services/zim_service.ts).
  • Start the Kolibri container for educational content.
  • Chat with the local Ollama model to retrieve medical guidance, with the request flowing through ollama_service → Ollama API → response rendered in ChatMessageBubble.tsx.

Code Health & Issues

  • High – Pin third‑party GitHub Actions – .github/workflows/* uses @vN tags; replace with commit SHA.
  • High – Push to default branch – .github/workflows/release.yml pushes directly to main; switch to PR workflow.
  • Medium – Least‑privilege GITHUB_TOKEN – no permissions block in release.yml.
  • Medium – Pin Docker base image – Dockerfile uses node:22-slim; pin by digest.
  • Medium – No dependency‑vulnerability scan – add dependency-review-action or osv-scanner.
  • Medium – Run container as non‑root – add an unprivileged USER in Dockerfile.
  • Medium – Test coverage is minimal – only 1 test file for ~233 source files.
  • Low – Missing job timeouts – add timeout-minutes in build-disk-collector.yml.
  • Low – No repository convention files – add .editorconfig, .gitattributes, formatter config.

No critical findings were reported by the static analysis.

The Bottom Line

project‑nomad delivers a functional offline server stack with clear separation of UI, API, and service layers, but the codebase suffers from duplicated logic, oversized service files, and a few architectural smells (import cycles, deep nesting). It is suitable for teams willing to invest in refactoring and adding test coverage to stabilize the platform for production deployments.