docs: add license, third-party notices, AI declaration, and a download section
Some checks failed
CI / macos (push) Successful in 44s
CI / linux (push) Successful in 4m16s
CI / windows (push) Has been cancelled

This commit is contained in:
randogoth 2026-10-08 23:02:57 +03:00
parent 433573693c
commit 6172df3e00
6 changed files with 1485 additions and 42 deletions

View file

@ -1,33 +1,39 @@
# carrier
A minimalist desktop client for [Smol Mail](../smolmail): one compact window — the list above, the article below — built on [fumi-core](../fumi), the reference CLI's own library, with a [Relm4](https://relm4.org) (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.
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) [![Built with Devbox](https://www.jetify.com/img/devbox/shield_galaxy.svg)](https://github.com/jetify-com/devbox/) [![AI-DECLARATION: copilot](https://img.shields.io/badge/䷼%20AI--DECLARATION-copilot-fee2e2?labelColor=fee2e2)](https://ai-declaration.md)
A simple desktop client for [Smol Mail](../smolmail). One compact window: the message list above, the article below. It's built on [fumi-core](../fumi), the reference CLI's own library, with a [Relm4](https://relm4.org) (GTK4) UI made from libadwaita's own widgets, so it follows your theme and accent colour. One 32-byte seed is the only secret; mail stays sealed at rest and opens on demand.
![carrier showing an inbox and an open message](docs/screenshots/carrier.png)
## 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.
- Create an identity from a seed, or restore one you already have. The seed shows once, at creation.
- Pin a server's key, either by pasting the operator's key or connecting and trusting what the server presents. Then register a username, or recall an address you already own.
- Fetch, read, reply to and send sealed mail. Your inbox, requests and sent mail each live in their own folder.
- Close a compose window from the sidebar and carrier saves it as a draft automatically. Drafts live beside your mail store, in a folder that shows up once you have one; Cancel is the only way to throw one away.
- Add your own `Key: value` headers when writing mail, one per line under the subject. carrier checks each line against the frontmatter rules and won't let you override its own fields (subject, reply-to, in-reply-to, accept). When you open mail with custom headers, they show up between the meta line and the body, and replying offers to carry them into your draft, where you can still edit them.
- Accept or block someone who writes to you with a self-certifying reply address. Accepting also attaches an accept token to that contact automatically.
- Import contacts from a smol:// link and see whether they're verified or just trust-on-first-use. Each contact's detail view names the server their address lives on, whether you've pinned that server's key, and a button to pin it if you haven't. Your own identity opens the same way, with a QR code next to your address in the header bar and your key, fingerprint and URI below.
- Turn on strict first contact to make carrier pause before writing to someone new whose address carries no key. With it on, carrier offers to fetch and pin the server's key first, so you can check it against what the operator published, before the send goes out. A smol:// address with a key that differs from the one on file asks before replacing it, strict mode or not.
- Rotate your key with a confirmation step and a network timeout. Export and import gsmol backups that never contain the seed. Log out to erase your local files and return to onboarding. Your preferences live in a small sidecar file next to the store, so they survive a restart.
## Layout
## Download
```
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
Each release carries its binaries on the forge: [downloads for carrier](https://code.randogoth.com/randogoth/carrier/releases).
### Flatpak
```bash
flatpak install --user carrier-x86_64.flatpak
flatpak run com.randogoth.carrier
```
## State
### Windows
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.
Download the [installer](https://code.randogoth.com/randogoth/carrier/releases/download/v0.2.0/carrier-windows-0.2.0-setup.exe) and run it
## Run and verify
## Build
```
devbox run run # build release and launch
@ -35,38 +41,21 @@ 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.
Tests run offline against an in-memory store and cover seed and key parsing, listing and opening a sealed envelope end to end, importing a contact, a full backup round-trip, pin labels and the date formatter. `scripts/devbox-libs.sh` adds the GUI stack's runtime libraries (wayland, xkbcommon, EGL, vulkan) to the library path inside the devbox shell, since they're loaded at runtime and nothing that links the binary can provide them.
## Status
### Packaging
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:
Run `devbox run package` to build carrier and install it as a Flatpak, using `com.randogoth.carrier.json`. It picks up the binary, desktop file, metainfo and icon from the repo, and devbox provides the tooling (`flatpak`, `flatpak-builder`, `appstreamcli`). The one thing outside devbox is the flatpak runtime itself; 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.
After that, carrier lives in your user flatpak and runs with `flatpak run com.randogoth.carrier`. Inside the sandbox it keeps its config under `~/.var/app/com.randogoth.carrier/config/carrier`, so a flatpak install starts with a fresh identity rather than your 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:
Install MSYS2 (`winget install MSYS2.MSYS2`), open the **MSYS2 MINGW64** shell, and run the setup script from your 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.
`windows/setup.sh` installs the GTK and Rust stack into MSYS2, clones fumi next to carrier if you don't already have it, builds carrier, and bundles carrier.exe with the GTK runtime into `dist/` and a zip, a complete Windows distribution. carrier keeps its config in `%APPDATA%\carrier`. If MSYS2 updates its core mid-run, the shell may close; just rerun the script and it picks up where it left off. Cloning over SSH needs your key in `~/.ssh` inside the MSYS2 home.