Project N.O.M.A.D, is a self-contained, offline survival computer packed with critical tools, knowledge, and AI to keep you informed and empowered—anytime, anywhere.
Go to file
caweis 77c36b3fef
Add offline FDA drug reference (labels, interaction view, conditions, remedies) (#1040)
* feat(drug-reference): offline FDA drug labels, conditions, and remedies

Adds an offline medical-reference feature with three coupled layers:

- Drug Reference: full-text search over openFDA drug-label indications,
  a detail page per label, and a side-by-side single-drug comparison view.
  A two-phase background pipeline downloads the openFDA label parts to the
  storage volume (resumable) and ingests them into the search table.
- Conditions ("When to use what"): a curated spine of first-aid situations
  that maps each situation to matching OTC drugs, each linking back to its
  Drug Reference detail page.
- Curated remedies: hand-authored natural and home-remedy entries drawn from
  US-government public-domain sources (NCCIH, CDC, MedlinePlus, FDA), shown
  with their source links and the same safety disclaimers as the rest of the
  feature.

The drug-reference and conditions layers are intentionally coupled: the drug
detail page shows the situations a drug treats, and the conditions controller
reads the same drug_labels table.

Safety surfaces ship as written. The amber SafetyBanner ("informational only,
not medical advice, not an FDA endorsement, not a drug-interaction checker, in
an emergency call emergency services") renders on the condition pages and the
search page; the detail and comparison pages carry their own "not a cross-drug
interaction checker" callout; and every page carries the openFDA CC0 source
citation and no-FDA-affiliation footer.

Wiring on this branch:
- start/routes.ts: the /drug-reference and /conditions page GETs plus their
  /api/* groups.
- commands/queue/work.ts: the drug-download and drug-ingest queues, both at
  concurrency 1. The two drug queues get a per-queue stall override
  (lockDuration 1_800_000, maxStalledCount 3) because each part is one long
  stream; every other queue keeps the existing 300000 default.
- inertia/pages/home.tsx: Drug Reference and "When to use what" tiles. The
  icon and display_order are a starting point, open to change.
- types/kv_store.ts: the two drugReference.* keys the pipeline reads and writes.
- package.json: yauzl and stream-json (plus their @types), used by the ingest
  job to stream the label JSON out of the downloaded zips.

The app reads the conditions and remedy data from the compiled TS constants in
app/data/; the repo-root collections/*.json files are the browseable mirrors.
The natural-remedies standalone test reads collections/natural_remedies.json to
assert the two stay in sync, so that file is also a test fixture.

The four standalone tests pass (drug_interactions, drug_ingest_status,
conditions, natural_remedies). tsc reports no errors in the feature code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(drug-reference): drop a fork-internal comment reference

* fix(drug-reference): address review — defer FULLTEXT, fallback remedies prop, strip fork refs

- migration: wrap the FULLTEXT ALTER in this.defer so it runs after the
  deferred createTable (was silently swallowed, index never created)
- controller: add remedies:[] to the index() error fallback (required prop)
- strip fork-internal issue/spec references from ported comments
- tsconfig: exclude tests/standalone (node --experimental-strip-types only)
- correct the varchar(768) byte-math comment; extend remedy-spine test

* fix(drug-reference): make the interaction comparison readable at five drugs

The comparison view laid its columns out on an equal-fraction CSS grid
(repeat(N, minmax(0, 1fr))), so each added drug shrank every column; at the
five-drug maximum the FDA interaction text was squeezed into unreadable slivers.

Lay the columns out with flex instead: full-width and stacked on phones, then
fixed-width columns that scroll sideways from the sm: breakpoint up, so they
never shrink below a readable width. Theme the columns with the same palette as
the rest of the page (they were on stock gray), and give the headers a fixed
min-height so columns line up when drug names wrap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(collections): route a 'dataset' tier resource to the drug pipeline

Add an optional `type` discriminator to SpecResource ('zim' | 'dataset',
absent == 'zim'), so the tier installer can carry a DB-ingested resource
alongside ZIM files. ZimService.downloadCategoryTier branches on it: a
'dataset' resource dispatches the existing FDA download+ingest pipeline
instead of RunDownloadJob, guarded against duplicate dispatch by the drug
ingest status. Every existing manifest entry has no `type` and keeps the
exact ZIM path.

Widen InstalledResource.resource_type to include 'dataset' and exclude
dataset rows from the ZIM/map catalog-update scan (datasets aren't
filename-versioned; their freshness path is separate). No dataset rows are
written yet: the InstalledResource 'dataset' row on ingest-ready, the
manifest entry, install-gating, and the downloads-aggregator integration
are follow-up commits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(drug-reference): rework into an opt-in medicine-standard tier dataset

Reshapes the offline FDA drug reference from a built-in feature into curated
content installed by selecting the Medicine / Standard tier, per maintainer
direction.

- install-state: the ingest writes an installed_resources 'dataset' row on
  ready (version = the openFDA export_date), threaded installer to download to
  ingest; the tier-status math and the home-tile gate read it. Manual ingests
  write no row, so install-state stays tied to the curated path.
- manifest: declare the dataset in the medicine-standard tier (runtime fetches
  the remote manifest, so this also needs to land upstream).
- install-gating: the drug-reference home tiles render only when installed.
- uninstall: DrugReferenceService.uninstall() stops the two drug queues, deletes
  the on-disk parts, truncates drug_labels (schema kept), clears the KV markers,
  and drops the install row. Best-effort, logged, scoped to drug data only.
- downloads: the download phase reports the canonical {percent, downloadedBytes,
  totalBytes} shape as one drug-data card in the Active Downloads aggregator with
  cancel/remove; the heavy ingest stays in the IngestStatus surface with an
  Indexing handoff on the card.
- auto-update: a daily DrugAutoUpdateJob compares the manifest export_date and
  re-downloads when newer, gated on installed + no active job.

typecheck clean; the drug standalone suites pass. Three points are flagged in
code for the maintainer: the InstalledResource 'dataset' approach, the tier
home, and the export_date string format.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(downloads): keep the drug download card live across all parts

The Active Downloads card filtered to the deterministic jobId, but the
download's continuations run under auto-generated jobIds (only part 0 uses the
deterministic one). So the card tracked part 0 and then vanished while parts
2..N kept downloading. The queue is concurrency 1, so collapse to whichever
single part is in flight and report the deterministic jobId: one card tracks
aggregate progress through the whole download and cancel/remove still routes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(drug-reference): co-locate a persistent safety note with affirmative remedy guidance

Add RemedySafetyNote at the head of every natural-remedy section — the two on
Drug Reference and the one on "When to use what" — so the "informational only,
not medical advice, seek real medical care in an emergency" framing appears with
the guidance itself, not only in the page-top banner. Replaces the terse
per-section caveat with the same amber alert language as SafetyBanner.

Addresses the upstream #1040 review request that the disclaimer be unmistakable
and present wherever affirmative self-care guidance appears, not a one-time banner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(drug-reference): fold dataset freshness into the content-auto-update path

The drug dataset auto-updated on its own daily cron that ignored the
contentAutoUpdate.* master switch, so it would refresh even with content
auto-update turned off, and it didn't ride the content-update path the way
ZIMs and maps do.

Move the export_date freshness/apply orchestration onto
DrugReferenceService.attemptAutoUpdate(), add
ContentAutoUpdateService.attemptDrugDataset() gated on the same enabled +
window config, and have the hourly ContentAutoUpdateJob drive both. Retire
the standalone DrugAutoUpdateJob. The ZIM/map attempt() path is unchanged.

Addresses the upstream #1040 request to wire the openFDA export_date check
into the content updater so the dataset updates alongside ZIMs and maps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(drug-reference): strengthen the remedy note to consult a clinician before combining with meds

Widen the affirmative-remedy safety note from "talk to a clinician before use"
to explicitly cover using a remedy AND combining one with a medication the user
already takes — the interaction case is the higher-risk path for an off-grid
user self-treating.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(drug-reference): gate affirmative remedy content behind an off-by-default flag

Add drugReference.remediesEnabled (default off), independent of the tier
install. When off, the server emits no remedy data at any boundary — the
drug-reference page prop, conditions show, and the /api/conditions/drugs
situation search — and the "Natural" filter is hidden, so installing the
medicine-standard tier lights up the verbatim FDA label search and the
condition-to-OTC matching but not the hand-authored self-care and herbal
sections. No user-facing toggle: it is flipped on after a clinician content-pass.

Implements the upstream #1040 split-by-risk request: the regulated label content
ships with the tier; the authored remedy guidance stays gated until sign-off.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Jake Turner <52841588+jakeaturner@users.noreply.github.com>
2026-07-21 11:06:18 -07:00
.github feat(creator-packs): gated per-creator video packs, offline via Kiwix (#1106) 2026-07-20 16:21:18 -07:00
admin Add offline FDA drug reference (labels, interaction view, conditions, remedies) (#1040) 2026-07-21 11:06:18 -07:00
collections Add offline FDA drug reference (labels, interaction view, conditions, remedies) (#1040) 2026-07-21 11:06:18 -07:00
install docs(install): make storage-relocation guidance accurate and consistent (#1103) 2026-07-19 22:27:26 -07:00
.dockerignore build: switch to node:22-slim image for libzim compat 2026-02-09 16:23:47 -08:00
.gitignore feat: openwebui+ollama and zim management 2025-07-09 09:08:21 -07:00
.releaserc.json ops: support RC versions 2026-03-03 20:51:38 -08:00
CODE_OF_CONDUCT.md docs: add Contributor Covenant Code of Conduct 2026-03-11 17:07:41 -07:00
CONTRIBUTING.md fix(install): define missing header_red + colors in uninstall/update scripts (#1098) 2026-07-19 21:37:37 -07:00
Dockerfile feat(creator-packs): gated per-creator video packs, offline via Kiwix (#1106) 2026-07-20 16:21:18 -07:00
FAQ.md chore: standardize brand name to Project NOMAD, retire backronym 2026-07-18 09:56:54 -07:00
LICENSE chore: add Apache 2.0 license 2026-03-04 20:05:14 -08:00
README.md chore: standardize brand name to Project NOMAD, retire backronym 2026-07-18 09:56:54 -07:00
package-lock.json chore(release): set version to 1.33.0-rc.1 2026-06-23 04:47:13 +00:00
package.json chore: standardize brand name to Project NOMAD, retire backronym 2026-07-18 09:56:54 -07:00

README.md

Project NOMAD

Knowledge That Never Goes Offline

Website Discord Benchmark


Project NOMAD is a self-contained, offline-first knowledge and education server packed with critical tools, knowledge, and AI to keep you informed and empowered — anytime, anywhere.

Installation & Quickstart

Project NOMAD can be installed on any Debian-based operating system (we recommend Ubuntu). Installation is completely terminal-based, and all tools and resources are designed to be accessed through the browser, so there's no need for a desktop environment if you'd rather setup NOMAD as a "server" and access it through other clients.

Note: sudo/root privileges are required to run the install script

Quick Install (Debian-based OS Only)

sudo apt-get update && \
sudo apt-get install -y curl && \
curl -fsSL https://raw.githubusercontent.com/Crosstalk-Solutions/project-nomad/refs/heads/main/install/install_nomad.sh \
  -o install_nomad.sh && \
sudo bash install_nomad.sh

Project NOMAD is now installed on your device! Open a browser and navigate to http://localhost:8080 (or http://DEVICE_IP:8080) to start exploring!

For a complete step-by-step walkthrough (including Ubuntu installation), see the Installation Guide. For Windows users, see the WSL2 install guide — community-supported path covering native Docker and Docker Desktop install routes.

Advanced Installation

For more control over the installation process, copy and paste the Docker Compose template into a docker-compose.yml file and customize it to your liking (be sure to replace any placeholders with your actual values). Then, run docker compose up -d to start the Command Center and its dependencies. Note: this method is recommended for advanced users only, as it requires familiarity with Docker and manual configuration before starting.

How It Works

NOMAD is a management UI ("Command Center") and API that orchestrates a collection of containerized tools and resources via Docker. It handles installation, configuration, and updates for everything — so you don't have to.

Built-in capabilities include:

  • AI Chat with Knowledge Base — local AI chat powered by Ollama or you can use OpenAI API compatible software such as LM Studio or llama.cpp, with document upload and semantic search (RAG via Qdrant)
  • Information Library — offline Wikipedia, medical references, ebooks, and more via Kiwix
  • Education Platform — Khan Academy courses with progress tracking via Kolibri
  • Offline Maps — downloadable regional maps via ProtoMaps
  • Data Tools — encryption, encoding, and analysis via CyberChef
  • Notes — local note-taking via FlatNotes
  • System Benchmark — hardware scoring with a community leaderboard
  • Supply Depot — a one-click app catalog (PDF tools, file browser, e-book library, password manager, and more) plus the ability to run your own custom Docker containers
  • Automatic Updates — opt-in, hands-off updates for the core software, installed apps, and offline content, on a schedule you control
  • Easy Setup Wizard — guided first-time configuration with curated content collections

NOMAD also includes built-in tools like a Wikipedia content selector, ZIM library manager, and content explorer.

What's Included

Capability Powered By What You Get
Information Library Kiwix Offline Wikipedia, medical references, survival guides, ebooks
AI Assistant Ollama + Qdrant Built-in chat with document upload and semantic search
Education Platform Kolibri Khan Academy courses, progress tracking, multi-user support
Offline Maps ProtoMaps Downloadable regional maps for offline viewing and search
Data Tools CyberChef Encryption, encoding, hashing, and data analysis
Notes FlatNotes Local note-taking with markdown support
System Benchmark Built-in Hardware scoring, Builder Tags, and community leaderboard
Supply Depot Built-in One-click app catalog + bring-your-own custom Docker containers

Device Requirements

While many similar offline survival computers are designed to be run on bare-minimum, lightweight hardware, Project NOMAD is quite the opposite. To install and run the available AI tools, we highly encourage the use of a beefy, GPU-backed device to make the most of your install.

At its core, however, NOMAD is still very lightweight. For a barebones installation of the management application itself, the following minimal specs are required:

Note: Project NOMAD is not sponsored by any hardware manufacturer and is designed to be as hardware-agnostic as possible. The hardware listed below is for example/comparison use only

Minimum Specs

  • Processor: 2 GHz dual-core processor or better
  • RAM: 4GB system memory
  • Storage: At least 5 GB free disk space
  • OS: Debian-based (Ubuntu recommended)
  • Stable internet connection (required during install only)

To run LLMs and other included AI tools:

Optimal Specs

  • Processor: AMD Ryzen 7 or Intel Core i7 or better
  • RAM: 32 GB system memory
  • Graphics: NVIDIA RTX 3060 or AMD equivalent or better (more VRAM = run larger models)
  • Storage: At least 250 GB free disk space (preferably on SSD)
  • OS: Debian-based (Ubuntu recommended)
  • Stable internet connection (required during install only)

For detailed build recommendations at three price points ($150$1,000+), see the Hardware Guide.

Again, Project NOMAD itself is quite lightweight — it's the tools and resources you choose to install with NOMAD that will determine the specs required for your unique deployment

Running AI models on a different host

By default, NOMAD's installer will attempt to setup Ollama on the host when the AI Assistant is installed. However, if you would like to run the AI model on a different host, you can go to the settings of the AI assistant and input a URL for either an ollama or OpenAI-compatible API server (such as LM Studio).
Note that if you use Ollama on a different host, you must start the server with this option: OLLAMA_HOST=0.0.0.0.
Ollama is the preferred way to use the AI assistant, as it has features such as model download that OpenAI API does not support. So when using LM Studio, for example, you will have to use LM Studio to download models. You are responsible for the setup of Ollama/OpenAI server on the other host.

Frequently Asked Questions (FAQ)

For answers to common questions about Project NOMAD, please see our FAQ page.

About Internet Usage & Privacy

Project NOMAD is designed for offline usage. An internet connection is only required during the initial installation (to download dependencies) and if you (the user) decide to download additional tools and resources at a later time. Otherwise, NOMAD does not require an internet connection and has ZERO built-in telemetry.

To test internet connectivity, NOMAD first attempts to make a request to Cloudflare's utility endpoint, https://1.1.1.1/cdn-cgi/trace. If that endpoint is unreachable (for example, because your network blocks 1.1.1.1), it falls back to other endpoints the application already contacts (the GitHub API and the Project NOMAD API) and considers the connection online if any of them respond.

You can override the endpoint used for this check in two ways. The connectivity test URL can be configured from the UI under Settings → Advanced (stored locally on your instance), or you can set the INTERNET_STATUS_TEST_URL environment variable. When set, the environment variable always takes precedence over the UI-configured value. If neither is set, the built-in defaults above are used.

About Security

By design, Project NOMAD is intended to be open and available without hurdles — it includes no authentication. If you decide to connect your device to a local network after install (e.g. for allowing other devices to access its resources), you can block/open ports to control which services are exposed.

Will authentication be added in the future? Maybe. It's not currently a priority, but if there's enough demand for it, we may consider building in an optional authentication layer in a future release to support use cases where multiple users need access to the same instance but with different permission levels (e.g. family use with parental controls, classroom use with teacher/admin accounts, etc.). We have a suggestion for this on our public roadmap, so if this is something you'd like to see, please upvote it here: https://roadmap.projectnomad.us/posts/1/user-authentication-please-build-in-user-auth-with-admin-user-roles

For now, we recommend using network-level controls to manage access if you're planning to expose your NOMAD instance to other devices on a local network. NOMAD is not designed to be exposed directly to the internet, and we strongly advise against doing so unless you really know what you're doing, have taken appropriate security measures, and understand the risks involved.

Contributing

Contributions are welcome and appreciated! Please see CONTRIBUTING.md for guidelines on how to contribute to the project.

Testing Auto-Updates (Dry Run)

The Command Center can automatically install minor/patch updates of itself during a configurable window, after a cool-off period, and only when pre-flight checks pass (sufficient disk for the new image, no downloads or app installs in progress). Major versions always require a manual update.

Because exercising this logic with real version bumps is impractical, an Ace command runs the entire decision pipeline without ever triggering an update. Run it from the admin/ directory:

# 1) Deterministic scenario suite — no network, DB, or Docker required.
#    Proves every branch (major-only, cool-off, prerelease/draft, window wrap, …)
#    and exits non-zero on failure, so it's safe to wire into CI.
node ace auto-update:dry-run --scenarios

# 2) Simulate "what would happen if I were running 1.32.0 right now?"
#    against the LIVE GitHub releases feed and real pre-flight checks:
node ace auto-update:dry-run --current=1.32.0 --force-enabled

# 3) Fully offline simulation with a canned release list and a fixed clock:
node ace auto-update:dry-run --current=1.32.0 --force-enabled \
  --releases-file=./fixtures/releases.json --now=2026-06-04T21:00:00Z \
  --window-start=20:00 --window-end=23:00 --cooloff=72 --skip-preflight

It prints the resolved decision — current version, whether the clock is inside the window, the eligible target (if any), and pre-flight blockers — ending in a clear verdict such as WOULD UPDATE → v1.33.2 or WOULD NOT UPDATE (outside-window): …. No real update is ever requested.

Flag Description
--scenarios Run the built-in deterministic scenario suite and exit
--current=<version> Simulate this currently-running version (e.g. 1.32.0)
--force-enabled Treat auto-update as enabled, ignoring the saved setting
--cooloff=<hours> Override the cool-off period
--window-start=<HH:MM> / --window-end=<HH:MM> Override the update window
--now=<ISO timestamp> Simulate the clock at a specific time
--releases-file=<path> Use a local JSON releases array instead of fetching GitHub (offline)
--skip-preflight Bypass the Docker/disk/queue pre-flight checks

Community & Resources

License

Project NOMAD is licensed under the Apache License 2.0.

Helper Scripts

Once installed, Project NOMAD has a few helper scripts should you ever need to troubleshoot issues or perform maintenance that can't be done through the Command Center. All of these scripts are found in Project NOMAD's install directory, /opt/project-nomad

Start Script - Starts all installed project containers
sudo bash /opt/project-nomad/start_nomad.sh

Stop Script - Stops all installed project containers
sudo bash /opt/project-nomad/stop_nomad.sh

Update Script - Attempts to pull the latest images for the Command Center and its dependencies (i.e. mysql) and recreate the containers. Note: this only updates the Command Center containers. It does not update the installable application containers - that should be done through the Command Center UI
sudo bash /opt/project-nomad/update_nomad.sh
Uninstall Script - Need to start fresh? Use the uninstall script to make your life easy. Note: this cannot be undone!
curl -fsSL https://raw.githubusercontent.com/Crosstalk-Solutions/project-nomad/refs/heads/main/install/uninstall_nomad.sh -o uninstall_nomad.sh && sudo bash uninstall_nomad.sh