Skip to content
TalibMushtaqPublic

About

Nodus — Privacy-first, offline-first distributed encrypted storage with secure sync across devices.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Nodus

Nodus is a hybrid, offline-first peer-to-peer storage system. Files live primarily on a storage node you control, sync directly between your own devices over the local network, and use the Internet only as an enhancement — never a hard dependency.

🚀 Status: active implementation. Core storage, sync, and transit components are built and tested; the web client has a working UI ported from the design prototype. See Current status below.


Why Nodus

Most cloud storage treats the Internet — and a central server — as the source of truth. Nodus flips that:

  • Local-network-first. Your devices talk directly to your Storage Node over Wi-Fi or wired LAN, as long as they're on the same network. No Internet required for day-to-day sync.
  • Internet as enhancement, not dependency. A Relay server provides signaling, cross-network sync, and temporary buffering when devices aren't on the same network — but it never holds plaintext file contents or keys.
  • Recoverable by design. If the Relay's database is lost, it can be fully rebuilt from snapshots held by your Storage Node(s).
  • Convergent, not "last write wins." Independently made offline changes reconcile without an arbitrary winner overwriting the other.

How it works

Four components, one protocol:

Component Directory Stack Status
Web client apps/web Next.js + packages/ui (Tailwind v4) UI ported; session auth wired; live sync TBD
Mobile client apps/mobile React Native / Expo Phase 15–17 complete — native build, session auth, pairing, mDNS, A→B→C→D transfers, upload/download, folders, devices/nodes, recovery, background drain
Storage Node services/storage-node Rust, SQLite Sync + object store implemented
Relay services/relay Go, SQLite, Redis Control plane + buffer implemented
                         INTERNET
                            |
                            v
                 +----------------------+
                 |    Go Relay / API    |
                 | SQLite · Redis       |
                 | Temporary Buffer     |
                 +----------+-----------+
                            |
                       Sync Protocol
                            |
                        Wi-Fi / LAN
                            |
                 +----------v-----------+
                 |   Rust Storage Node  |
                 | SQLite · Object Store|
                 +----------+-----------+
                            |
                     Local WebRTC
                            |
                 +----------v-----------+
                 |   Web / Mobile Client|
                 +----------------------+

Files are split into 8 MB shards, encrypted client-side with AES-256-GCM (unique nonce per shard), and integrity-checked with BLAKE3. The Relay only ever sees encrypted bytes and encrypted key envelopes — never plaintext file keys or shard contents.

Transfer paths

Nodus picks the best available path automatically, falling back as needed:

  1. Path A — Direct Local P2P. Local signaling + WebRTC, no Relay involved.
  2. Path B — Relay-Mediated Signaling. Relay carries SDP/ICE only; file data still flows peer-to-peer.
  3. Path C — Buffer-and-Relay. Encrypted shard is buffered on the Relay and delivered asynchronously when the Storage Node comes back online.
  4. Path D — Local Queue. If both the Storage Node and Relay are unreachable, changes queue locally until one becomes available.

Repository layout

Nodus is a single monorepo — one repo, multiple languages:

nodus/
├─ apps/
│  ├─ web/                 # Next.js web client (ported design UI)
│  ├─ mobile/               # React Native / Expo mobile client (scaffold)
│  └─ docs/                 # Documentation site (Next.js + Fumadocs)
├─ packages/
│  ├─ core/                 # Domain logic: sharding, crypto abstractions
│  ├─ protocol/              # Canonical protocol schemas/types (zod, JSON Schema)
│  ├─ relay-client/          # WebSocket client (web + mobile)
│  ├─ webrtc-transport/      # WebRTC abstraction
│  ├─ transfer-manager/      # Cross-path transfer orchestration + repair
│  ├─ ui/                    # Shared design system (Tailwind v4, ported prototype)
│  └─ config/                # Shared TypeScript tooling (eslint/ts configs)
├─ services/
│  ├─ relay/                 # Go Relay (control plane + buffer)
│  └─ storage-node/           # Rust Storage Node (data plane)
├─ docs/
│  ├─ architecture/
│  ├─ protocol/
│  ├─ security/
│  ├─ decisions/             # ADRs (key hierarchy, recovery, GC, ...)
│  └─ design-port-plan.md    # Prototype → implementation mapping
├─ pnpm-workspace.yaml
└─ turbo.json

The TypeScript apps and packages are managed by Turborepo + pnpm. The Rust Storage Node and Go Relay live in the same repository under services/ but sit outside the pnpm/Turborepo workspace, with their own native tooling.

Documentation

The official documentation site lives in apps/docs (Next.js + Fumadocs) and covers everything end to end — user guides, self-hosting, and the full CLI/API/protocol reference:

pnpm --filter docs dev      # http://localhost:3001

Content is authored as MDX under apps/docs/content/docs/. The engineering docs in docs/ (ADRs, protocol, architecture) remain canonical and are mirrored into the site's Reference and ADR sections.

For end-user install steps — Android APK, Storage Node binary, and the self-hosted server — see docs/install.md.

Current status

Implemented and tested (see CHANGELOG.md for detail):

  • Design foundations — five ADRs locked in docs/decisions/: key hierarchy, recovery mechanism, conflict-resolution UX, mobile local discovery, and GC policy.
  • Protocol (packages/protocol) — canonical wire schemas with runtime (zod) validation and generated JSON Schema, versioned and documented.
  • Storage Node (services/storage-node, Rust) — SQLite schema, run-time configurable data directory, content-addressed object store with atomic writes, crash recovery, reconciliation, and automatic GC per ADR-0005.
  • Relay (services/relay, Go) — REST API (Argon2id password auth, opaque server-side sessions via HttpOnly cookie), WebSocket hub with presence, and a transient encrypted shard buffer with TTL sweep.
  • Transfer sync (services/relay ↔ services/storage-node) — incremental sync, full snapshot / relay rebuild (Path C), buffer-and-relay transfers, and the repair (data-return) path.
  • Transfer Manager (packages/transfer-manager) — path selection, fan-out, and repair orchestration for the four transfer paths.
  • Web client (apps/web) — the Figma prototype (nodus-design/) ported to a Tailwind v4 shared design system (packages/ui); six dashboard routes plus a real auth wizard (Phase 7a §3), accessible (axe-clean) in light and dark themes. /auth signs in / creates accounts through app/api/auth/* route handlers that proxy the Relay and set the HttpOnly session cookie; / and the dashboard group require a valid session. /pair (Phase 7a §4) is server-guarded and pairs Storage Nodes through session-authenticated proxies (/api/nodes, /api/pairing/*) — no client-side Bearer/JWT.

Full build order and the phase-by-phase checklist:

  • nodus_implementation_plan.md — the complete architecture and design plan (protocol, schemas, key envelopes, state machines, failure guarantees, etc.)
  • Todo.md — the phase-by-phase implementation checklist derived from the plan

Core design principle

Internet is an enhancement, not a dependency.

Internet available:     Client <-> Relay <-> Storage Node
Internet unavailable:   Client <-> Storage Node
Storage Node offline:   Client -> Relay Buffer -> Storage Node
Relay corrupted:        Storage Node -> Snapshot/Rebuild -> New Relay DB

Getting started

Prerequisites

Tool Version Used for
Node.js + pnpm Node ≥ 20, pnpm 11 Web client, TypeScript packages
Rust stable toolchain + cargo Storage Node
Go ≥ 1.22 Relay
Docker Compose v2 Redis for the Relay

1. Install dependencies & hooks

make install-hooks      # gofmt/rustfmt/clippy pre-push hooks
pnpm install            # TypeScript workspace (apps + packages)

2. Run the web client

cd apps/web
pnpm dev                # http://localhost:3000

The dashboard, overview, and settings routes render with mock data; /auth is the real session-cookie wizard. pnpm build && pnpm start for a production-style build. To run the web client against the Relay locally, start the Relay with SESSION_COOKIE_SECURE=false (plain HTTP) and point the web app at it via RELAY_URL (defaults to http://localhost:8080).

3. Run the Relay (Go)

cd services/relay
docker compose up -d    # Redis 7 (health-checked); the database is a local SQLite file
go run .

The relay listens on :8080 by default. Configuration via env vars:

Env Default
PORT 8080
DB_PATH ${TMPDIR}/nodus-relay/relay.db
REDIS_URL redis://localhost:6379/0

Migrations run automatically on startup.

4. Run the Storage Node (Rust)

Install the latest prebuilt binary (Linux x86_64):

curl -fsSL https://raw.githubusercontent.com/TalibMushtaq/nodus/main/scripts/install-node.sh | sh
# installs nodus to ~/.local/bin; pin a version with `sh -s -- --version 0.1.0`

Then run nodus (interactive) or nodus node start (service). To build from source instead:

cd services/storage-node
cargo build --release
./target/release/nodus                      # interactive CLI
./target/release/nodus --data-dir ~/NodusBackup

cargo run works too for local development.

On a terminal, a bare nodus opens the interactive CLI: on first run it asks for the Relay URL and pairing code (mint one in the web app under Devices → “+ Add Storage Node”), then presents a menu — run the node, view the storage summary, list files/folders, show status, or pair/re-pair. The file and folder listings read this node's local SQLite and show ids and sizes only, since names are end-to-end encrypted and the node never holds a file key.

Non-interactive invocations (systemd, pipes) skip the menu and boot the daemon directly; nodus node start always boots the daemon and nodus node pair --relay <url> --code <code> pairs without prompting. Without --data-dir the first run prompts for the data directory and defaults to ~/NodusBackup.

5. Deploy the server unit (single origin)

The self-hosted unit packages the Next.js web app, the Go Relay, the docs site, SQLite, Redis, and Caddy behind three public origins (web, relay, docs):

cp deploy/.env.example deploy/.env
docker compose -f deploy/docker-compose.yml --env-file deploy/.env up --build -d
curl -fsS http://localhost/health

Set WEB_HOST/RELAY_HOST/DOCS_HOST, PUBLIC_RELAY_URL, NEXT_PUBLIC_RELAY_URL, and ALLOWED_ORIGINS for production. See deploy/README.md for routing, TLS, and operations details.

Factory reset

Both resets are destructive and require typing the exact phrase purge everything, then force everything to be paired again.

Storage Node — in the interactive CLI choose Factory reset (delete everything), or from the running shell:

nodus> factory-reset

This deletes the node's catalogue, objects, identity, and config. The node gets a new node_id, so it must be paired with the Relay again.

Relay — stop the running Relay first, then:

cd services/relay
go run . --factory-reset            # prompts: type "purge everything"
# or non-interactively:
echo 'purge everything' | go run . --factory-reset

This deletes the SQLite database file and its -wal/-shm sidecars (rebuilt from the baseline migration on the next start), flushes the Relay's Redis database, and clears the shard buffer. Every account, device, and Storage Node must register and pair again.

The prompt prints the exact targets it is about to destroy — the database file, Redis host and index, and the buffer path — so a DB_PATH or REDIS_URL pointed at the wrong place is visible before anything is deleted. Two guards also apply:

  • The reset refuses to run while the Relay is still running, because it holds an exclusive lock on the database file. Stop it first, or pass --factory-reset-force.
  • BUFFER_DIR is validated before anything is deleted. A relative path, the filesystem root, a system directory (/etc, /usr, /tmp, …), your home directory, the working directory, or a path that resolves through a symlink into a system tree is refused outright — os.RemoveAll is recursive, so those are the values that would delete far more than the shard buffer. Use a dedicated subdirectory such as /var/lib/nodus/buffer.

Tests

pnpm test                                # all JS/TS, Relay (Go), and Storage Node (Rust) tests

This requires Node/pnpm, Go, and Cargo. Relay tests that need Redis are skipped by default. To include them, start the Redis dependency and provide TEST_REDIS_URL. The Relay's database is a per-test SQLite file, so no database service is needed:

docker compose -f services/relay/docker-compose.yml up -d
TEST_REDIS_URL='redis://localhost:6379/0' pnpm test

License

MIT

About

Nodus — Privacy-first, offline-first distributed encrypted storage with secure sync across devices.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages