Air-Gapped / Offline Deployment Guide

How to install and operate NovaFabric with no internet connectivity — classified networks, HPC enclaves, regulated environments. Labels follow the docs honesty rule: works today, experimental, planned, or future design.

The short version: NovaFabric is local-first by design. Core local-mode features — capture, validate, replay, diff, lineage on local capsules — require no network, ever. The features that can reach out (TSA, Sigstore, cloud WORM/KMS, OIDC) are all opt-in, all configurable to private endpoints, and all have documented offline modes or honest "not usable offline" answers (§5).


1. No telemetry, no phone-home — the guarantee

Status: contract — accepted ADR-0189.

An air-gap security review can therefore be answered in one line: there is no call-home code path to firewall.


2. Offline package install (pip / uv wheel mirroring)

Status: works today (standard Python packaging — nothing NovaFabric-specific is required).

On a connected staging machine, download wheels for your Python version and platform:

# Pick the extras you actually deploy (see the extras table below)
pip download 'novafabric[server,serve]' -d ./wheelhouse \
    --python-version 3.12 --only-binary=:all:

Transfer wheelhouse/ across the boundary, then install with the index disabled:

pip install --no-index --find-links ./wheelhouse 'novafabric[server,serve]'
# uv equivalent:
uv pip install --no-index --find-links ./wheelhouse 'novafabric[server,serve]'

Alternatively, host the wheels on an internal mirror (devpi, Artifactory, Nexus, simple/ static index) and point PIP_INDEX_URL / UV_INDEX_URL at it — NovaFabric hardcodes no registry.

Extras you may need to include in the mirror (from pyproject.toml; Tier-A/B licensing per ADR-0024):

Extra Brings Air-gap note
server FastAPI, uvicorn, psycopg, alembic, PyJWT Needed for nova server, offline tokens
serve FastAPI, uvicorn, duckdb, pyarrow, python-louvain Local dashboard (the topology extractor needs the last three)
worm-s3 / worm-azure / worm-gcs boto3 / azure-storage-blob / google-cloud-storage Only if you run that WORM backend; S3-compatible works against in-network endpoints (§5)
seal-aws / seal-azure / seal-gcp Cloud KMS SDKs Cloud KMS profiles need the cloud — use the local seal profile offline
seal-postgres psycopg Postgres Merkle log at >1M entries
sigstore sigstore Not usable air-gapped (§5) — omit it
scale duckdb, pyarrow, clickhouse-connect, nats-py, fastavro, pyiceberg, blake3, boto3 Evidence Fabric scale tier
query duckdb Optional nova query accelerator; stdlib sqlite3 fallback is always available
clickhouse clickhouse-connect ClickHouse cost attribution
lineage-migration pyarrow Lineage migration kit
all every non-cloud-vendor, non-agent-framework extra Escape hatch that restores pre-v0.99.0 importability — but it is a superset of the old default (it also pulls compliance, spkg, scale, sigstore, janusgraph…), so it installs more, not less. Includes sigstore, which is unusable air-gapped — prefer naming the narrow extras you actually need
otlp, spkg, lineage-kuzu, … see pyproject.toml As needed

Changed in v0.99.0 — re-check your wheelhouse. duckdb, pyarrow, python-louvain and clickhouse-connect (and, transitively, numpy) are no longer part of the default install; they moved to the extras above (ADR-0222). This makes an air-gapped mirror ~299 MB smaller — but if your existing pip download line names no extras and your deployment imports any of them, they will now be missing from the wheelhouse. Add the extra you need, or use pip download 'novafabric[all]' -d ./wheelhouse to mirror the previous surface. Core commands (capture, validate, replay, diff, lineage, insights, query) need none of them.

Note that [all] also pulls in sigstore, which the table above tells you to omit from an air-gapped mirror (§5), and several other extras you may not want. For an enclave, prefer downloading the narrow extras you actually use — e.g. pip download 'novafabric[server,serve,query]' — over [all].

Container images and the Helm chart ship from GHCR (ghcr.io/novafabric/novafabric, oci://ghcr.io/novafabric/charts/novafabricServer Deployment Guide); mirror them into your private registry (docker pulldocker push registry.internal/...) — nothing in the chart requires the public registry at runtime.


3. Offline identity: ed25519 tokens, no IdP

Status: experimental (ADR-0018; src/novafabric/server/offline_tokens.py). This is the intended auth mode for air-gapped clusters, SLURM batch jobs, and CI without an OIDC provider.

Since v0.61, local no-OIDC mode also has the auto-generated local bearer token (~/.novafabric/.server-token, ADR-0184) — also fully offline. Full setup: Server Deployment Guide — Scenario 4.


4. What works offline with zero configuration

Status: works today (surfaces individually labelled in their own docs; most are experimental-maturity but network-free).


5. Features that reach the network — and their offline modes

Feature Needs network for Offline mode / degradation
RFC 3161 timestamps (NovaSeal tsa_url) HTTPS POST to the timestamp authority (default example config uses the public freetsa.org) Omit tsa_url (or set it ""): NovaSeal still signs with ECDSA, just without a timestamp token — nova verify then reports the timestamp check accordingly, which is expected, not tampering. Or run a private TSA inside the enclave and point tsa_url at it (operator guide §5b, NovaSeal configuration). The timestamp-verification code additionally has an explicit HPC air-gap mode (offline_mode=True skips nonce-store writes and all network calls — src/novafabric/trust/novaseal/timestamp.py)
Sigstore keyless signing/verification (--backend sigstore) Fulcio + Rekor public infrastructure Not usable air-gapped. Use the local ECDSA DSSE backend (the default). Don't mirror the sigstore extra into the enclave
Cloud WORM stores (S3/Azure/GCS) The bucket endpoint Any S3-compatible store inside the network works: every S3-family adapter takes a configurable endpoint_url (Ceph RGW and other S3-compatible OSS; src/novafabric/object_capsule_store/worm/), and the NovaObjectStore reads NOVA_S3_ENDPOINT_URL / NOVA_S3_BUCKET / NOVA_S3_ACCESS_KEY / NOVA_S3_SECRET_KEY (src/novafabric/storage/nova_object_store.py). Azure/GCS backends require their respective (possibly private/Stack) endpoints
Cloud KMS seal profiles (AWS KMS / Azure KV / GCP KMS) The cloud KMS API Use the local seal profile (file-based key) — the fully offline path; HSM options in key management
OIDC login The IdP's issuer/JWKS endpoints An in-network IdP (e.g. Keycloak inside the enclave) works — issuer_url is fully configurable. With no IdP at all, use offline ed25519 tokens (§3)
OTel export (otel_endpoint) Your OTLP collector Empty = disabled (the default). Point it only at an in-network collector
nova lineage emit-openlineage HTTP mode Your OpenLineage endpoint Optional; stdout/file modes are local
Package/image updates PyPI/GHCR Mirror per §2. There are no runtime update checks to disable (§1)

Rule of thumb (and repo policy): everything callable is configurable to a private endpoint, mirror, or proxy — no external service URL is hardcoded as a requirement. If you find a counter-example, file it as a bug.


6. A minimal fully-offline server profile

Status: experimental (each piece labelled above).

# /etc/novafabric/nova-server.yaml
backend: postgres            # in-network Postgres; or sqlite for single-host
server:
  host: "127.0.0.1"          # or your internal interface
  port: 7433
offline_key_path: "/etc/novafabric/keys/offline-key.pem"
# oidc.enabled defaults to false
otel_endpoint: ""            # disabled
# ~/.novafabric/novaseal.yaml — sealing with no TSA
profile: local
key_path: ~/.novafabric/seal.key
cert_path: ~/.novafabric/seal.crt
# tsa_url omitted → ECDSA signing without RFC 3161 timestamps
merkle_db: ~/.novafabric/novaseal-merkle.db
export NOVA_DSN="postgresql://nova:***@pg.internal:5432/novafabric"
nova server start --backend postgres
nova server issue-token --subject batch-runner --roles writer --expires-in 90d

Verify the deployment end-to-end without leaving the enclave:

nova capture -- python agent.py       # capture
nova validate <capsule-dir>           # schema check
nova verify <capsule-dir>             # seal check (signature + Merkle log)
nova doctor --check-storage           # DB/migration state
nova support-bundle                   # diagnostics, offline-safe

7. Honest limitations in an air gap

Signed bundle format (ADR-0249, experimental — first slice)

NovaFabric can now assemble and verify a signed air-gap bundle: one tar whose members are inventoried in a DSSE-signed airgap-manifest.json, verifiable with zero network — the public key, the manifest, and the bytes travel together.

# Build (maintainer side): wheels from dist/ + anything else you name
make airgap-bundle           # needs NOVA_AIRGAP_SIGNING_KEY (ed25519 private key)

# Verify (air-gapped side): names every problem — a tampered member, a
# missing one, or an unsigned stowaway
python scripts/verify_airgap_bundle.py \
    --bundle novafabric-airgap.tar --public-key evidence.pub

A flipped byte in any member fails verification naming that member; a member present in the tar but absent from the signed manifest is reported as a finding, not ignored. What stays planned (tracked in ADR-0249): the CI job that assembles the full closure (dependency wheels per extras profile, container image, Helm chart, docs site, CVE advisory snapshot), the network-disabled install gate, and nova doctor --check-cves offline reporting.