Files
music_library/backlog/docs/nerves-deployment/doc-8 - Opus-4.7-xhigh-analysis.md
2026-05-05 10:45:25 +01:00

10 KiB
Raw Permalink Blame History

id, title, type, created_date, updated_date
id title type created_date updated_date
doc-8 Opus 4.7, xhigh analysis other 2026-05-04 15:08 2026-05-04 15:10

Nerves Deployment Research — Findings

Three parallel research agents covered NIFs, SQLite extensions, and Litestream replication semantics. The user's concerns were well-founded: there are real blockers, but most are solvable with custom Nerves system work. The Litestream question reframes the architecture more than expected.

1. Native libraries (NIFs on aarch64-linux-musl)

Library aarch64-gnu aarch64-musl Status
mdex Fine
lumis Fine
typst Nerves-aware — has @nerves_rust_target_triple_mapping in mix.exs
ecto_sqlite3 / exqlite Driver fine; designed with embedded use in mind. Extensions are the issue (see §2)
dominant_colors likely unknown Verify GitHub releases; if missing, request musl target from maintainer
vix partial Major blocker
cloak_ecto n/a n/a Pure Elixir + :crypto

vix is the dominant blocker. Two compounding issues:

  • It loads the NIF at compile time (macro-driven introspection), which breaks cross-compilation on stock Nerves systems. fhunleth (Nerves core) discussed this in a 2023 ElixirForum thread; the conclusion was "no easy Elixir-level fix."
  • libvips itself is not in nerves_system_rpi4 / nerves_system_rpi5. You'd need a custom Nerves system with BR2_PACKAGE_LIBVIPS=y in Buildroot, then VIX_COMPILATION_MODE=PLATFORM_PROVIDED_LIBVIPS.

This is doable (1530 min Buildroot rebuild, well-documented) but it commits you to maintaining a custom Nerves system. That single decision affects everything downstream — once you're building custom systems, ICU and sqlite-vec become "while we're at it" additions.

General Phoenix-on-Nerves status (2025): Pattern is established but niche. Underjord's "LiveView on Nerves" guide is the canonical reference; a poncho project (sibling ui + firmware) is preferred over umbrella. No widely-cited "production Phoenix LiveView on Nerves at non-hobby scale" guide exists — most published examples are thermostats and birdhouses.

2. SQLite extensions

Both extensions are blockers on stock Nerves systems for the same root reason — Nerves uses musl, prebuilt binaries assume glibc.

sqlite-vec (vec0):

  • Upstream publishes linux-aarch64 but it's glibc — won't dlopen on Nerves.
  • No musl prebuilt exists; PR #199 fixes the musl compile but is unmerged.
  • The sqlite_vec Hex wrapper (Joel Paul Koch's) downloads the glibc binary and would fail at runtime.
  • Mitigation: cross-compile inside Buildroot, ship via rootfs_overlay/. Or compile statically into exqlite at firmware build time (recommended pattern from the SQLCipher-on-Nerves ElixirForum thread).

unicode (ICU) extension:

  • ICU is ~30 MB and deliberately excluded from Nerves base systems.
  • Requires a custom Nerves system with BR2_PACKAGE_ICU=y.
  • If you're already building custom for vix, the marginal cost is minimal.

Good news for exqlite: Driver itself ships precompiled musl NIFs (since v0.13.7), supports load_extension, and has been used on Nerves in production (the SQLCipher thread). The complexity is purely in shipping the extension .sos built against the same musl toolchain.

3. Litestream replication — this is the architectural pivot

Headline: Litestream is not a multi-master replicator. It's a one-way backup tool. If the Nerves device writes locally, those writes are lost on the next restore — no conflict detection, no reconciliation, no merge.

Two viable replica modes today (both newer than the docs you may remember):

  1. VFS read replica (stable, in litestream.io/guides/vfs/): Nerves device queries S3-backed replica directly. ~1 minute staleness matches your sync interval. Writes return "litestream is a read only vfs" at the SQLite layer. Requires CGO — verify this works in your Nerves build.
  2. Restore-then-poll: simpler, no CGO. Restore on startup, periodically re-restore. Live replication via the embedded HTTP server existed but the example repo was archived November 2023 — treat as experimental.

For the architectures you'd consider:

  • Read-only Nerves device (browse music library at home, no edits) — Litestream VFS replica works, modulo CGO. This is the simplest path.
  • Read-write Nerves device (record listening, edit notes) — Litestream alone is insufficient. Three options:
    1. Split databases: music library is read-only via Litestream; a separate small SQLite on the device captures writes and pushes to the production API out-of-band. Cleanest fit for your existing app shape, since scrobbles are already a separate domain.
    2. LiteFS (Fly.io's project): true primary/replica with write-forwarding. Requires FUSE — not viable on Nerves without kernel module support, so likely off the table.
    3. Writes go directly to production API: Nerves device calls the prod Phoenix JSON API for any write; reads are local. Requires the device to be online for writes (acceptable for a home device).
  • Other distributed-SQLite options — rqlite (Raft, kills write throughput), Marmot (NATS-based CDC, eventual consistency), cr-sqlite (CRDTs). All exist; all are heavier than the problem warrants for one home device.

Synthesis: where the unknowns concentrate

The three concerns aren't independent — they collapse into one decision: are you willing to maintain a custom Nerves system?

  • If yes: vix, ICU, and sqlite-vec are all solvable in the same Buildroot config. The remaining unknowns are minor (dominant_colors musl target, exact poncho structure for the existing umbrella-style codebase).
  • If no: the application as currently architected cannot run on Nerves. You'd have to drop image processing, drop FTS unicode collation, drop similarity search — at which point it's a different application.

The biggest non-obvious unknown is the Litestream architecture decision. Going read-only on the Nerves side is dramatically simpler than supporting writes, and it covers most of the value (browsing, search, AI chat against the local collection). Splitting writes to a separate small DB that pushes to prod is the natural compromise — and your codebase is already structured to support it (scrobbles are a separate context, notes are a separate schema).

Suggested next step if you want to proceed: prototype a custom nerves_system_rpi5 with libvips + ICU on a feature branch, before touching the application. That single artifact resolves three of the four major risks; everything else is application-layer work that can be done iteratively.


Sources

NIF / Phoenix-on-Nerves

SQLite extensions

Litestream