- Rust 92%
- Python 7.5%
- Shell 0.5%
| data | ||
| debian | ||
| docs | ||
| examples | ||
| LICENSES | ||
| po | ||
| resources | ||
| scripts | ||
| src | ||
| tests | ||
| .gitignore | ||
| AGENTS.md | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CONTRIBUTING.md | ||
| README.md | ||
ModemLibre
- Application ID:
org.spacecruft.modemlibre - Cargo package / GUI binary:
modemlibre0.1.1 - License: GPL-3.0-or-later
- Repository: https://spacecruft.org/librem/ModemLibre
ModemLibre is a local, read-only modem diagnostics application for the Librem 5, PureOS Crimson/Phosh, Wayland, and adaptive workstation use. It requires no active data connection, online account, remote service, or telemetry.
Current scope
The selected Phase 4 slice adds MM-SIG-001, DER-RF-001, and fixed-query QMI
support to the Phase 2/3 shared snapshot. Five adaptive destinations—Overview,
Modem, Connection, Cells, and Diagnostics & Export—share one bounded observation
owner. Cells provides serving, unclassified, component-carrier, and neighbor
surfaces, per-RAT units/source age, targeted refresh, independent stale/error/
unsupported states, and canonical Overview RAT/band, signal, and serving-cell
links. Advanced details are lazy; the 24-neighbor display limit does not truncate
acquisition or snapshot completeness. Diagnostics & Export remains the only
development placeholder.
The strict ModemManager client uses numeric-child introspection and exact
asynchronous Properties.Get calls. Cached modem SignalQuality is read without
Signal.Setup or Signal.SetupThresholds. Registration, emergency-only,
bearer/IP, credential-disposal, and path-plus-epoch behavior are retained. There
is no recursive bearer discovery, GetManagedObjects, GetAll, per-SMS access,
content-bearing InterfacesAdded, InitialEpsBearerSettings, bearer Stats,
NetworkManager, counter/sysfs, call/IMS/messaging/USSD/CBS, or MM extended-signal/
cell query or UI. Optional location/time retains its separate explicit request
path. Phase 5 AT is not implemented. Bounded 2026-10-30 physical BM818-A1
research established a local ID_MM_PORT_TYPE_AT_PPP route and exact firmware/
RAT/serving-cell responses under support-contract-6. This selects fixed local
read-only operations for implementation; it does not add a production AT backend,
external service, arbitrary command route, E1 evidence, complete neighbors, or
active-call-RAT support. The planned mediated route uses fixed AT-only protocol
2 and org.spacecruft.modemlibre.at.read, independently authorized from QMI
protocol 1/qmi.read; that AT protocol and policy are not implemented.
Native package and QMI privilege boundary
The native Debian .deb is the only current package target. One ModemLibre
package contains the unprivileged GUI, privileged QMI helper, system D-Bus
activation, polkit policy, and required service integration. It declares ordinary
OS dependencies, including stock ModemManager and qmicli/qmi-proxy; it does not
bundle those services or require a second ModemLibre helper package or manual
helper installation. Installation must not replace or modify NetworkManager,
ModemManager, qmi-proxy, or their existing configuration.
The fixed integration identifiers are:
| Item | Value |
|---|---|
| Helper service | org.spacecruft.modemlibre.QmiHelper |
| Helper object | /org/spacecruft/modemlibre/QmiHelper |
| Helper executable | /usr/libexec/modemlibre-qmi-helper |
| polkit action | org.spacecruft.modemlibre.qmi.read |
| Helper protocol | 1 |
The GUI never runs as root and has no production direct-qmicli, direct-QMI-device,
sudo, or pkexec fallback. The system D-Bus activates the helper as root. On the
first fixed batch of each GUI launch, the helper obtains one polkit authorization
for the exact caller's D-Bus unique name. Subsequent batches in that launch reuse
the in-memory launch session without another interactive prompt; denial does not
trigger repeated prompts. Disconnecting the GUI or restarting the helper forgets
the session. Each batch still independently validates the selected modem's
current Ports relationship and stock /usr/bin/qmicli 1.32.2 before modem
operations. Callers cannot choose arbitrary operations, device paths, executables,
arguments, or environment.
Only these eight fixed operations are allowed through qmi-proxy:
--dms-get-ids,--dms-get-revision, and--dms-get-band-capabilities;--nas-get-serving-system,--nas-get-system-info,--nas-get-rf-band-info,--nas-get-signal-info, and--nas-get-cell-location-info.
The helper uses one same-modem QMI CDC-WDM port (MMModemPortType QMI = 6, not
AT = 3), fixed argument arrays without a shell, and a cleared environment with
only LC_ALL=C, LANG=C, PATH=/usr/bin:/bin, and TZ=UTC. Workers, output,
time, parsing, collections, and per-resource execution are bounded. Authorization
denial, version rejection, independent operation failure, cancellation, timeout,
close, suspend, owner/device/Ports changes, and stale completion are explicit.
Cleanup must demonstrate pipe EOF and no surviving helper-spawned qmicli
descendants. QMI failure does not erase MM data or successful sibling operations.
MM and QMI acquisition occurs once during initial load. Later backend/property/ lifecycle changes invalidate or queue stale work without issuing another query; only the user's reload action refreshes observations. Revealing a value is a UI- only operation and never triggers a backend query.
qmi-proxy is not a read-only operation allowlist. The fixed helper API and lack of a GUI bypass are required package properties; their installed behavior must be validated rather than inferred from source or package metadata.
Snapshots, RF, privacy, and export
Immutable revisions use snapshot schema 2, event model 2, parser 1.2.0, supported-table metadata 3.0.0, export schema 1, and export dictionary 2. The typed cell-location projection is LTE-centric; emitted content without a selected typed projection remains bounded sensitive raw-only evidence. QMI arguments, unknown fields, parser warnings, status/timing, and stdout/stderr are memory-only and never exported.
RF calculations use exact integer tables for LTE EARFCN, UTRA FDD/TDD UARFCN and TDD options, fixed GERAN ARFCN, and 28-bit LTE ECI. NR is raw-only; dynamic GSM and NB-IoT mappings are excluded. Missing or ambiguous context remains unresolved instead of becoming a guessed value. Sources and table provenance are pinned in docs/references.md.
Sensitive identifiers are masked by default and export-redacted. In-memory export is deny-by-default: object identities are omitted and raw payloads, commands, parser warnings, raw/free-form errors, credentials, message content, and history never export. Raw Diagnostics, clipboard, snapshot-file, and export actions remain absent. A future export must use portal-mediated user file selection; portals are also applicable to native applications and do not justify blanket home access.
Validation status
Tagged 0.1.1 build and offline archive checks pass. The release commit is
a051af2c4eaf98e01bd60685beb520aa181fe678, exactly matching annotated tag 0.1.1.
The final artifact is build/native-source/modemlibre_0.1.1_arm64.deb, SHA-256
276e0778667580315281db19b5f399184dd20b689ab6fa69d3a1671d0a2e5a4f. Its post-tag
build and offline release suite passed 288/288 tests. The archive passed all
12 required-file and integration/boundary checks; helper linkage excludes
GTK/libadwaita, and systemd-analyze verify passed.
This candidate authorizes once per GUI D-Bus connection/launch and retains that decision only until disconnect/helper restart. It also makes post-initial MM/QMI acquisition manual-only: backend changes invalidate or queue work, and only the user reload action queries again. Resume re-enables Refresh without starting an acquisition.
Current installed-package/helper core checks for P4-BM818-A1-QMI-CELLS have a
bounded A1 pass. The exact 0.1.1 archive was already installed and its GUI/helper
payloads match the installed binaries. The unprivileged GUI, root D-Bus helper,
polkit allow/failed-authorization paths, same-launch authorization reuse, all eight
live QMI operations, typed provenance, and tested idle/no-survivor cleanup passed on
the phone. The canonical A1-only reduced scope and frozen candidate are defined in
docs/support-matrix.md. Its EV-P4-A1 bundle is
incomplete, including exact current QMI DMS firmware and real fixtures, so the claim
remains BLOCKED and unadvertised. This is not a full Phase 4,
real-fixture-bundle, exceptional-lifecycle, or E1 result. Lintian, sustained
performance, and the remaining physical/support gates are NOT RUN or BLOCKED
as detailed in docs/device-validation.md.
Current source evidence also includes formatting, strict all-target/all-feature Clippy, two serialized 288/288 test runs, both locked release binaries, and all seven notice checks. Supplemental isolated X11 testing also passed populated and empty structural AT-SPI plus nine structural/shell invocations at 360×720, 740×300, 1100×760, and 640×360. Shell assertions used a disposable navigation adapter and reveal timed out; these are not unmodified-harness or full-phone passes. The hardened development-only capture path passed independent privacy/ correctness re-review, 6/6 Rust sanitizer tests, 29/29 Python tests, and a clean-launcher self-test. No real capture was attempted because exact current QMI firmware provenance was not confirmed; no real fixture files were written. Exact source, artifact, and check details live in docs/device-validation.md.
Historical source evidence includes the 2026-10-28 native Crimson gate (238 tests), the earlier Phase 4 host gate (212 tests), and the 2026-10-29 checkout suite (247/247 tests), with the other checks and limits recorded in docs/device-validation.md. Historical device evidence includes limited real MM Overview/manual Modem checks and the explicitly authorized development-checkout fixed-root qmicli route. That sudo route is retired development evidence only, not a production fallback or native-helper validation. Retired Flatpak results in the evidence log are historical only and are not Debian/helper passes.
Full-phone acceptance, real A1/E1 fixtures, sustained performance, comprehensive phone accessibility/AT, remaining native package authorization, and Phase 5 AT remain BLOCKED or NOT RUN as applicable. Synthetic fixtures are not real A1/E1 captures.
Compatibility and dependency pins
| Component | Selected version or minimum |
|---|---|
| Rust | edition 2024; MSRV 1.92; recorded Crimson development toolchain 1.99.1 |
| gtk4-rs / libadwaita-rs | 0.11.5 / 0.9.2 |
| glib-rs / gio-rs | 0.22.10; gettext-rs 0.8.0; roxmltree 0.21.x |
| Native API minima | GLib/Gio 2.62, GTK 4.8, libadwaita 1.2 |
| Measured Crimson native libraries | GTK 4.8.3, libadwaita 1.2.2, GLib/Gio 2.74.6 |
| Target OS/session | PureOS Crimson; Phosh 0.34.0-2pureos6; phoc 0.33.0+ds-1pureos6 |
| Modem APIs | ModemManager 1.20.4-1pureos2; NetworkManager 1.42.4-1+deb12u1pureos1 is an unselected inventory pin |
| QMI route | stock /usr/bin/qmicli 1.32.2 through qmi-proxy, mediated by the fixed native helper; P4-BM818-A1-QMI-CELLS bounded installed core PASS, full fixture/lifecycle/E1 evidence NOT RUN/BLOCKED |
| BM818 route | A1 PPP-AT route/firmware/RAT/serving-cell research PASS under VAL-AT-001; production AT NOT RUN. E1, complete neighbors, active-call RAT, and lifecycle/package claims BLOCKED/NOT RUN |
Crimson is the selected stable target; other PureOS suites are not inferred to be compatible. Record daemon, plugin, modem firmware, helper, and portal versions for each device claim. Host results do not establish phone compatibility, and one BM818 variant's evidence does not cover another.
Prerequisites
For on-device details see docs/BUILD-LIBREM5-ON-DEVICE.md. The following are setup instructions, not actions performed by this documentation update. Install packages only when explicitly desired:
sudo apt update
sudo apt install build-essential pkg-config libgtk-4-dev libadwaita-1-dev \
gettext desktop-file-utils appstream dpkg-dev debhelper lintian \
libglib2.0-dev dbus modemmanager systemd libqmi-utils libqmi-proxy polkitd
Use Rust 1.92 or newer; Crimson's older packaged Rust is insufficient. See the on-device guide for a user-local rustup toolchain. UI/AT-SPI checks also need an isolated display and AT-SPI bindings. Do not treat a host display as Phosh or physical-touch evidence.
Build, format, lint, and test
Run the canonical native gate from the repository root:
cargo fmt --all -- --check
cargo clippy --all-targets --all-features --locked -- -D warnings
cargo test --locked -- --test-threads=1
cargo build --release --locked
cargo build --release --locked --no-default-features --bin modemlibre-qmi-helper
Apply cargo fmt --all first when formatting changed. These are source/host
checks, not installed-helper or real-modem evidence.
Regenerate dependency inputs with Cargo rather than hand-editing them:
cargo generate-lockfile
cargo vendor --locked
Regenerate Cargo.lock only for intentional dependency changes and vendor inputs
only when required for the reviewed offline source package. Debian builds use
locked offline Cargo with the reviewed vendored source replacement and separate
GUI/helper commands; the helper uses --no-default-features to exclude GTK/
libadwaita. See docs/packaging.md.
Translations and metadata
Source, GTK UI, desktop, AppStream, and polkit action strings are included in gettext. The German catalog is intentionally partial; untranslated strings fall back to the source language and fuzzy matches are not used blindly.
sh scripts/update-pot.sh
msgmerge --update po/de.po po/modemlibre.pot
sh scripts/install-translations.sh build/prefix build/i18n
msgfmt --check -o /dev/null po/de.po
desktop-file-validate build/prefix/share/applications/org.spacecruft.modemlibre.desktop
appstreamcli validate --pedantic \
build/prefix/share/metainfo/org.spacecruft.modemlibre.metainfo.xml
GNU gettext 0.24 has no Rust parser, so update-pot.sh uses its C parser for the
current gettext("...") calls and Glade/Desktop/AppStream extractors for other
inputs. Review the POT after changing Rust string syntax. Compiled catalogs and
merged metadata are build outputs, not committed files.
Run from source
cargo run --locked
The GUI runs as the normal desktop user. Full QMI operation requires the installed
native package's protocol-compatible helper integration and successful polkit
authorization; there is no source-tree direct/sudo production fallback. Set
MODEMLIBRE_LOCALEDIR to the absolute build/prefix/share/locale path for
development translations. There is no development GSettings schema.
Native Debian package
The canonical staged source/binary package workflow is:
sh scripts/build-native.sh
candidate=build/native-source/modemlibre_VERSION_ARCH.deb
dpkg-deb --info "$candidate"
dpkg-deb --contents "$candidate"
lintian "$candidate"
PYTHONDONTWRITEBYTECODE=1 python3 tests/debian_package_smoke.py "$candidate"
The build script checks debian/changelog and rejects a Cargo/changelog version
mismatch, prepares explicit staged source and locked vendor inputs, then runs
dpkg-buildpackage -b -us -uc there. The 3.0 (native) source requires a version
without a Debian revision suffix (0.1.1, not 0.1.1-1). Routine agent-managed
releases increment only the third version digit; the user directs first/second-
digit changes. Update debian/changelog and all version inputs, optionally pre-
build/test, commit the intended source with a
Co-Authored-By: <provider>/<actual-model> trailer identifying the model that
performed the work, create the exact matching tag, then run the final build from
that tagged revision. Every agent-created commit names its actual model. Before
committing, audit tracked content for secrets and real device identifiers and use
synthetic fixture identifiers. The build script performs no Git mutations.
A separately provisioned toolchain not registered with dpkg requires the explicit
-d build flag, as
explained in the on-device guide. Replace the placeholder with the one exact
candidate artifact when running or recording validation. Source preparation also
verifies locked crate identities/checksums and deterministically generates the
installed THIRD_PARTY_NOTICES from upstream notices; that attribution aid is
not a legal-compliance determination. Install only that authorized candidate
through apt so declared dependencies are resolved:
sudo apt install "$PWD/$candidate"
Then exercise modemlibre as the unprivileged desktop user. Package inspection,
installation, service activation, polkit denial/allowance, all-eight-live-QMI,
and cleanup checks are mandatory separate gates described in
docs/testing.md and docs/packaging.md.
Do not manually copy a helper, create a second helper package, or alter system
service/proxy configuration.
Design and evidence
- docs/architecture.md — implementation and privilege boundaries.
- docs/backend-access.md — exact backend/helper access and read-only contract.
- docs/testing.md — source, private-fixture, installed
.deb, and device gates. - docs/packaging.md — native one-package workflow.
- docs/mobile-ui.md — adaptive and accessibility requirements.
- docs/validation-contract.md and docs/support-matrix.md — selected scope and acceptance rules, not execution evidence.
- docs/device-validation.md — current native-helper checklist and dated historical results.
- docs/references.md — version-matched primary references.
Contributors must not commit built outputs, expose prohibited content, weaken read-only or privilege boundaries, or claim unrun checks. See CONTRIBUTING.md.