No description
Find a file
2026-10-08 22:27:25 +03:00
.forgejo/workflows Revert flatpak to building from source; add appstream package 2026-10-07 21:26:53 +03:00
data feat: embed the carrier icon in the Windows exe 2026-10-03 14:01:10 +03:00
locales/en-US docs: rewrite catalogue copy to the Microsoft Writing Style Guide 2026-10-08 22:27:25 +03:00
scripts refactor: migrate the UI to GTK4 with Relm4 and libadwaita 2026-10-03 00:40:53 +03:00
src feat: extract all user-facing strings into a Fluent catalogue 2026-10-08 22:10:29 +03:00
windows feat: add a one-command windows build script 2026-10-03 12:31:19 +03:00
.gitignore Add Flatpak bundle to the release workflow 2026-10-07 20:39:57 +03:00
build.rs feat: embed the carrier icon in the Windows exe 2026-10-03 14:01:10 +03:00
Cargo.lock feat: extract all user-facing strings into a Fluent catalogue 2026-10-08 22:10:29 +03:00
Cargo.toml feat: extract all user-facing strings into a Fluent catalogue 2026-10-08 22:10:29 +03:00
com.randogoth.carrier.json feat: package the app as a flatpak 2026-10-03 12:17:15 +03:00
devbox.json Revert flatpak to building from source; add appstream package 2026-10-07 21:26:53 +03:00
devbox.lock Revert flatpak to building from source; add appstream package 2026-10-07 21:26:53 +03:00
README.md feat: gate first contact on a pinned server, confirm key changes 2026-10-08 21:36:02 +03:00

carrier

A minimalist desktop client for Smol Mail: one compact window — the list above, the article below — built on fumi-core, the reference CLI's own library, with a Relm4 (GTK4) UI built from libadwaita's own widgets, so it follows the user's theme and accent colour. One 32-byte seed is the only secret; mail stays sealed at rest and opens on demand.

What it does

  • Create or restore an identity from a seed, shown once at creation time.
  • Pin a server's key — paste the operator's, or connect and pin what it presents — then register a username or recall an existing address.
  • Fetch (with cancel), read, reply and send sealed mail; the inbox, the requests tier and sent copies each keep their own folder.
  • Composes closed by any sidebar trip are stashed as drafts — kept beside the store, reopened from a folder that appears only once one exists; Cancel is the only way to throw one away.
  • Custom Key: value frontmatter rows when writing: one line per header under the subject, validated against the frontmatter rules — the client's own fields (subject, reply-to, in-reply-to, accept) can't be shadowed, and rows survive a stash as part of the draft. Received mail shows the sender's own fields in the message detail, between the meta line and the body; replying to such a mail asks whether to carry copies of the fields into the reply, where they stay editable.
  • Accept or block a requester when their mail carried a self-certifying reply address; accept tokens attach to accepted contacts automatically.
  • Contacts with verified/tofu marks and smol:// import; each contact's detail names the server its address lives on and whether that server's key is pinned, with a pin action when it is not. Your own identity opens in the same detail view, its address in the header bar with a QR share beside it, key, fingerprint and URI below.
  • Optional strict first contact: writing to someone new whose address carries no key means their key has to be looked up on their own server, and an unpinned server can answer with one of its own. With the setting on, that send stops and the compose offers to fetch and pin the server's key first, so it can be checked against what the operator published. A smol:// address whose key differs from the one already on file asks before replacing it, either way.
  • Key rotation with a confirm-first step, a network timeout, gsmol backup export/import that never contains the seed, and a log-out that erases the local files and returns to onboarding. Preferences live in a plaintext sidecar beside the store, so they survive a restart.

Layout

src/main.rs   config directory, demo seeding, launch
src/app.rs    state and update; blocking calls run on the worker pool
src/view.rs   the Relm4 component: widgets, the op adapter, the re-render
src/ops.rs    fumi-core calls, key/read-mark files, and the offline tests

State

Everything lives under $XDG_CONFIG_HOME/carrier (default ~/.config/carrier): carrier.key is the master seed, written 0600; carrier.db is fumi-core's sealed store; read.txt is carrier's own read marks, one hex id per line; settings.txt holds the preferences as one key value line each.

Run and verify

devbox run run      # build release and launch
devbox run check    # fmt --check, build, tests
devbox run test     # tests only

The tests run offline against an in-memory store: seed and key parsing, a sealed envelope listed and opened end to end, contact import, a full backup round-trip, pin labels and the date formatter. scripts/devbox-libs.sh puts the GUI stack's runtime libraries (wayland, xkbcommon, EGL, vulkan) on the library path inside the devbox shell — they are dlopened, so nothing that links the binary can provide them.

Status

Work in progress: the UI is unexercised against a live server — the store, crypto and address paths are covered by tests, the network flows (register, fetch, send, rotate) are wired to fumi-core's operations but only smoke-tested as far as launching the window.

Packaging

devbox run package builds and installs the app as a Flatpak from com.randogoth.carrier.json — the binary, the desktop file, the metainfo and the icon all come from the repo, and the tooling (flatpak, flatpak-builder, appstreamcli) lives in the devbox environment. The flatpak runtime is the one dependency outside devbox; install it once:

flatpak install --user flathub org.gnome.Sdk//51 org.freedesktop.Sdk.Extension.rust-stable//26.08

The result is installed into the user flatpak and runs as flatpak run com.randogoth.carrier. Inside the sandbox the app keeps its config under ~/.var/app/com.randogoth.carrier/config/carrier, so a flatpak install starts with a fresh identity rather than the dev build's account.

Windows

Install MSYS2 (winget install MSYS2.MSYS2), open the MSYS2 MINGW64 shell, and run the one-command script from the carrier checkout:

git clone ssh://git@code.randogoth.com:2222/randogoth/carrier.git
cd carrier && windows/setup.sh

windows/setup.sh installs the GTK and Rust stack into MSYS2, clones fumi next to carrier if it is missing, builds, and bundles carrier.exe with the GTK runtime into dist/ and a zip — the complete Windows distribution. The app keeps its config in %APPDATA%\carrier. If MSYS2 updates its core mid-run the shell may close; rerun the script and it continues. Cloning over SSH needs the key in ~/.ssh inside the MSYS2 home.