Personal device mesh: unified file access and remote control across your own devices, built from scratch in Rust.
This is not a generic "virtualize any device" tool — see docs/adr/0001-android-fuse-limitation.md for why that idea doesn't hold up, and what Nexus does instead.
Every PR is reviewed by two automated reviewers — CodeRabbit
and GitHub Copilot — before it merges. Their findings are evaluated on merit,
not rubber-stamped: valid ones are fixed and the threads resolved, off-base ones
are declined with a reason. They've caught real issues here — for example a CI
credential-leak hardening gap (persisted GitHub token + unpinned actions, flagged
by CodeRabbit) and a security-relevant bug where DeleteFile masked a
path-escape rejection as a plain "not found" (flagged by Copilot, fixed + tested).
So bot review is a genuine part of the quality bar, alongside the CI gates
(build + test, fmt, clippy) and branch protection on main.
Goal: mount a phone's storage on a Linux laptop as a real, lazy-loaded
FUSE filesystem — ls, cat, cp all work against it like it's a
local directory, but nothing is actually copied until read.
┌─────────────┐ gRPC (FileService) ┌──────────────┐
│ Android │ ─────────────────────> │ Dell/Linux │
│ nexus-agent │ ListDir/Stat/ReadFile │ nexus-agent │
│ (HOST) │ <───────────────────── │ nexus-fs │
└─────────────┘ │ (FUSE mount) │
└──────────────┘
crates/
├── common/ shared types (DeviceId, FileEntry, errors)
├── proto/ gRPC schema (file_service.proto) + generated code
├── agent/ daemon — runs on every device, implements the HOST role
└── fs/ FUSE client — Linux/macOS only, implements the CLIENT/mount role
The data plane is authenticated + TLS-encrypted (shared secret over a
self-signed cert — see docs/adr/0004).
On first run the agent generates a token and cert in its config dir
($HOME/.config/nexus/, or $NEXUS_CONFIG_DIR); the client needs both.
# Prerequisites: protoc (gRPC codegen) and a FUSE lib (to mount). On Debian/Ubuntu:
# sudo apt install -y protobuf-compiler libfuse3-dev pkg-config
# See CONTRIBUTING.md for other platforms.
# Build everything (capped to 2 parallel jobs — see .cargo/config.toml)
cargo build
# Terminal 1: run the host agent, serving a directory.
# First run generates agent.json (with the auth token) + cert.pem/key.pem
# under ~/.config/nexus/ and logs where they are.
./target/debug/nexus-agent --serve-dir ~/nexus-test-share --port 50051
# Grab the token and cert path the client will need:
TOKEN=$(python3 -c "import json;print(json.load(open('$HOME/.config/nexus/agent.json'))['auth_token'])")
CERT=$HOME/.config/nexus/cert.pem
# Terminal 2: mount it (note: https, plus --token and --ca-cert)
mkdir -p ~/nexus-mount
./target/debug/nexus-mount --remote https://127.0.0.1:50051 --mountpoint ~/nexus-mount \
--token "$TOKEN" --ca-cert "$CERT"
# (--token / --ca-cert can also come from NEXUS_AUTH_TOKEN / NEXUS_CA_CERT.)
# Terminal 3: prove it works (read AND write — the mount is read-write)
ls ~/nexus-mount
cat ~/nexus-mount/some-file.txt
echo "edited from the Dell" > ~/nexus-mount/some-file.txt # writes through to the host
# Unmount when done
fusermount3 -u ~/nexus-mountWrites carry a vector clock; if two devices edit the same file independently
the host keeps both (<name>.conflict-<device>-<ts>) rather than losing one —
see docs/adr/0005 and
docs/adr/0006.
Cross-compilation setup: see
docs/adr/0002-android-cross-compilation.md.
Not yet wired into an actual installable app — currently just a
cross-compiled binary you can push via adb for testing.
- Workspace scaffold
-
FileServiceproto (ListDir, Stat, ReadFile) - Host agent (Linux) — serves a local directory over gRPC
- FUSE client (Linux) — mounts a remote agent's files (read-write)
- Compile-verified on real hardware (Dell Latitude E6530, i7-3520M)
- Loopback test passed (Dell → Dell): byte-exact reads, including a chunk-boundary-spanning offset read on a 200KB file
- Android cross-compilation (
cargo-ndk, arm64-v8a + armeabi-v7a) — see docs/adr/0002 - Android host role tested against real hardware (TECNO KL4,
Android 14): phone served files over gRPC, Dell mounted them via FUSE,
byte-exact verified including a chunk-boundary offset read on a 150KB
file. Run manually via
adb shell(shell uid, not yet a packaged app — see open question below). - Data-plane auth + TLS — shared-secret token over a self-signed cert; host rejects bad/missing tokens before touching the filesystem. Tested loopback (positive + wrong-token/no-token/wrong-cert negatives). See docs/adr/0004.
- Multi-writer conflict detection (vector clocks) — every file carries
a vector clock; concurrent edits are detected and BOTH kept
(
.conflict-*), never silently merged or lost. Proven at the protocol level and through the actual read-write mount (two independent client identities — two mounts on one machine — editing the same file → conflict file, original untouched). See docs/adr/0005 + docs/adr/0006. Reading a file then editing it no longer false-conflicts — the client syncs the clock on a read-intent open (docs/adr/0007), while a blind overwrite of a changed file still conflicts. Remaining gaps named in the ADRs. Deletes are supported with delete-vs-edit conflict detection (tombstones — docs/adr/0008); rename is done (docs/adr/0009); directory-level conflicts are still open. - Full pairing / control plane (device identity, revocation, key rotation) — ADR 0004 is only the shared-secret step, not this
- Layer 1 (remote control / streaming) — not started
- Layer 4 (app-cooperative migration SDK) — not started
The phone-host test above ran the agent manually as the adb shell user
(uid 2000), which has direct sdcard_rw filesystem access and therefore
never touches Android's scoped-storage rules. That result is real, but it
does not prove the agent works once packaged inside an actual app —
an app runs under its own uid with no raw sdcard_rw access, and SAF
(Storage Access Framework) becomes mandatory at that point. This is a
distinct, harder problem layered on top of the FUSE/cross-compilation
work already proven, not yet solved. See
docs/adr/0002 for the
specific open questions this raises (foreground-service wrapper shape,
SAF permission flow, install/update mechanism for the embedded binary).
The data plane now has shared-secret authentication over TLS (ADR 0004): traffic is encrypted, and a client must present the agent's token to read anything — the host rejects bad/missing tokens before touching the filesystem. This is a real step up from the original "open and plaintext" state.
It is not the full control plane, though: one flat secret per agent, no pairing UX, no per-device revocation, no key rotation, and the token sits in plaintext in the agent's config. Treat it as "authenticated, encrypted LAN-trust" — fine for your own LAN, not hardened for hostile networks. See docs/adr/0004 for the precise threat model and the Status checklist for what's next.