124 lines
10 KiB
Markdown
124 lines
10 KiB
Markdown
---
|
||
id: doc-8
|
||
title: "Opus 4.7, xhigh analysis"
|
||
type: other
|
||
created_date: "2026-05-04 15:08"
|
||
updated_date: "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 (15–30 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 `.so`s 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
|
||
|
||
- [Underjord: LiveView on Nerves](https://underjord.io/liveview-on-nerves.html)
|
||
- [ElixirForum: Cross Compiling with NIF as compilation dependency (vix/Nerves thread)](https://elixirforum.com/t/cross-compiling-with-nif-as-compilation-dependency/59821)
|
||
- [Nerves docs: Compiling Non-BEAM Code](https://hexdocs.pm/nerves/compiling-non-beam-code.html)
|
||
- [rustler_precompiled Precompilation Guide](https://hexdocs.pm/rustler_precompiled/precompilation_guide.html)
|
||
- [mdex GitHub releases (NIF targets)](https://github.com/leandrocp/mdex/releases)
|
||
- [lumis GitHub releases](https://github.com/leandrocp/lumis)
|
||
- [typst Elixir GitHub releases](https://github.com/Hermanverschooten/typst/releases)
|
||
- [vix HexDocs README](https://hexdocs.pm/vix/readme.html)
|
||
- [exqlite README](https://hexdocs.pm/exqlite/readme.html)
|
||
- [NervesConf 2024 Talks thread](https://elixirforum.com/t/nervesconf-2024-talks/75205)
|
||
- [Customizing Your Nerves System](https://hexdocs.pm/nerves/customizing-systems.html)
|
||
|
||
### SQLite extensions
|
||
|
||
- [Releases · asg017/sqlite-vec](https://github.com/asg017/sqlite-vec/releases)
|
||
- [Fix for musl compile PR #199 · asg017/sqlite-vec](https://github.com/asg017/sqlite-vec/pull/199)
|
||
- [Build and install exqlite with sqlcipher into nerves rpi5 image — ElixirForum](https://elixirforum.com/t/build-and-install-exqlite-with-sqlcipher-into-nerves-rpi5-image/75089)
|
||
- [Concerning warning compiling under Nerves · Issue #166 · elixir-sqlite/exqlite](https://github.com/elixir-sqlite/exqlite/issues/166)
|
||
- [Compiling sqlite-vec — Alex Garcia](https://alexgarcia.xyz/sqlite-vec/compiling.html)
|
||
- [GitHub — joelpaulkoch/sqlite_vec](https://github.com/joelpaulkoch/sqlite_vec)
|
||
- [Advanced Configuration — Nerves](https://hexdocs.pm/nerves/advanced-configuration.html)
|
||
|
||
### Litestream
|
||
|
||
- [Live Read Replication — Litestream (tip)](https://tip.litestream.io/guides/read-replica/)
|
||
- [VFS Read Replicas — Litestream](https://litestream.io/guides/vfs/)
|
||
- [How it works — Litestream](https://litestream.io/how-it-works/)
|
||
- [Tips & Caveats — Litestream](https://litestream.io/tips/)
|
||
- [Alternatives — Litestream](https://litestream.io/alternatives/)
|
||
- [Litestream: Revamped — Fly.io Blog](https://fly.io/blog/litestream-revamped/)
|
||
- [benbjohnson/litestream-read-replica-example (archived 2023)](https://github.com/benbjohnson/litestream-read-replica-example)
|
||
- [Live read replicas — Issue #8](https://github.com/benbjohnson/litestream/issues/8)
|
||
- [LiteFS vs Litestream vs rqlite vs dqlite on VPS in 2025](https://onidel.com/blog/sqlite-replication-vps-2025)
|
||
- [Marmot distributed SQLite replicator](https://github.com/maxpert/marmot)
|
||
- [mvSQLite](https://github.com/losfair/mvsqlite)
|
||
- [cr-sqlite](https://github.com/vlcn-io/cr-sqlite)
|