# OnionWire Lean Tor messenger: one Rust process (ratatui + Noise IK + sqlite + in-process Arti). Two people scan a QR, then chat. No hosted server. Identity is a public key; the onion is only a locator. You do **not** install a `tor` daemon, `torrc`, Prosody, or XMPP. OnionWire embeds Arti and publishes its own v3 onion. ## Install Pick one: a CI-built binary (fastest), or build from source. ### Requirements | Need | Notes | |---|---| | Linux | x86_64 or aarch64. TUI needs a real terminal (not a pipe). | | Network | First run bootstraps Tor via Arti. Offline start will fail closed. | | Not needed | `tor` package, `torrc`, Prosody, an XMPP account, a DHT. | Arti onion services are still **experimental**. If the hidden service cannot come up, OnionWire exits — it does not fall back to C-tor. ### Option A — download a release binary CI (`.forgejo/workflows/release.yml`, Forgejo Actions on the Pi runner) builds the **aarch64** binary on every `v*.*.*` tag and attaches it to the [Forgejo release](https://forgejo.siriusdevops.com/sirius/onionwire/releases). There is no x86_64 runner on this instance, so the **x86_64** asset is built and uploaded by `scripts/build-release-local.sh` on an x86_64 host — both land on the same release page. **x86_64 (most PCs / VMs):** ```bash curl -fL -O https://forgejo.siriusdevops.com/sirius/onionwire/releases/latest/download/onionwire-x86_64-unknown-linux-gnu curl -fL -O https://forgejo.siriusdevops.com/sirius/onionwire/releases/latest/download/onionwire-x86_64-unknown-linux-gnu.sha256 sha256sum -c onionwire-x86_64-unknown-linux-gnu.sha256 chmod +x onionwire-x86_64-unknown-linux-gnu ./onionwire-x86_64-unknown-linux-gnu --version ``` **aarch64 (Raspberry Pi, ARM servers):** ```bash curl -fL -O https://forgejo.siriusdevops.com/sirius/onionwire/releases/latest/download/onionwire-aarch64-unknown-linux-gnu curl -fL -O https://forgejo.siriusdevops.com/sirius/onionwire/releases/latest/download/onionwire-aarch64-unknown-linux-gnu.sha256 sha256sum -c onionwire-aarch64-unknown-linux-gnu.sha256 chmod +x onionwire-aarch64-unknown-linux-gnu ./onionwire-aarch64-unknown-linux-gnu --version ``` Install onto `PATH` if you want `onionwire` as a command (keep the asset filename until after `sha256sum -c`): ```bash mkdir -p ~/.local/bin install -m 755 onionwire-x86_64-unknown-linux-gnu ~/.local/bin/onionwire # ensure ~/.local/bin is on PATH, then: onionwire ``` The binary is dynamically linked against glibc (Ubuntu 24.04 builders). If `./onionwire` dies with `GLIBC_… not found`, use Option B on that machine. Checksum files are `sha256sum` format (` onionwire-`). Download both files with `curl -O` so the names match, then `sha256sum -c`. ### Option B — build from source Needs [Rust](https://rustup.rs/) **1.87+** (`edition = "2024"`). A distro `rustc` is fine if `rustc --version` reports 1.87 or newer. Do not install `tor` for this app. ```bash # rustup (if you do not already have cargo) curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source "$HOME/.cargo/env" rustc --version # must be 1.87 or newer ``` ```bash git clone https://forgejo.siriusdevops.com/sirius/onionwire.git cd onionwire cargo test --locked cargo clippy --locked -- -D warnings cargo build --release --locked ./target/release/onionwire --version ./target/release/onionwire ``` Install the binary: ```bash cargo install --path . --locked --force # lands in ~/.cargo/bin/onionwire ``` Build needs `pkg-config` and OpenSSL 3 headers (`libssl-dev` / `openssl-devel`) — the released binaries link `libssl.so.3` dynamically. ### Two instances on one machine Each process needs its own data dir (identity + onion + sqlite): ```bash ONIONWIRE_HOME=/tmp/ow-a ./onionwire ONIONWIRE_HOME=/tmp/ow-b ./onionwire ``` Then F2 on A, F3-paste on B (and the other way around). ## First run ``` onionwire ``` or `cargo run --release`. On start you should see `onionwire: bootstrapping Arti…` on stderr. First bootstrap can take a minute. Data lives in `ONIONWIRE_HOME` if set, otherwise `~/.local/share/onionwire/` (`onionwire.db` + Arti state, mode 0700). First open creates an ed25519 identity key. That key **is** you. ## Friends are keys A friend is an ed25519 pubkey (`UNIQUE(pubkey)`). The `.onion` on that row is only where they are reachable right now. Re-scanning the same `k` updates the locator; it never creates a second person. ## Keys (TUI) Focus starts on the composer so typing works immediately. `Tab` cycles panes; `j`/`k` and arrows only move the focused pane (roster or chat). `1`/`2`/`3` switch focus when you are not typing in the composer. `?` opens help from every screen except paste (so a payload can contain `?`). | Key | Action | |---|---| | `Tab` / `Shift-Tab` | Cycle focus: roster → chat → composer | | `1` / `2` / `3` | Focus roster / chat / composer (when not typing) | | `j` `k` / `↑` `↓` | Move or scroll the focused pane | | `g` / `G` | Jump to top / bottom of the focused pane | | `?` | Keybinding help (`Esc` closes) | | `F2` | Share: terminal QR + payload | | `F3` | Paste a friend’s payload | | `F4` | Rotate **onion** (locator only) | | Enter | Run `/wipe` or `/wipe-all` from the composer | | `Esc` | Close overlay / back to Main / clear composer | | `Ctrl-Q` | Quit | ## F2 QR `F2` shows a terminal QR and the payload: `onionwire:v1:k=…:o=…:spk=…:sig=…` `F3` pastes a payload. Unknown `k` asks for approval. Same `k` already in the roster updates `onion` only. ## F4 rotate onion `F4` is a locator change, not a new identity. Confirm by typing `ROTATE` (Enter alone does nothing). - Your identity fingerprint stays the same. - A new onion is published; the old one is hard-cut (no dual-host grace). - Online friends get a signed `loc` frame. - Offline friends cannot find you until they rescan the new QR. There is no directory. ## Fail closed If a peer’s onion is down, send fails. v1 has no outbox, no retry queue, no DHT, no name server. ## Wipe Composer (bottom of the roster screen): - `/wipe` — confirm by typing `WIPE`. Overwrites the message log and `VACUUM`s. Identity key and friends stay. - `/wipe-all` — confirm by typing `WIPEALL`. Deletes the data dir. Next start is a **new person** (new identity key). Esc cancels. Nothing is wiped without confirm. ## Uninstall ```bash # binary (whichever you used) rm -f ~/.local/bin/onionwire ~/.cargo/bin/onionwire # optional: destroy identity, friends, and message log # (same as /wipe-all — you become a new person next run) rm -rf ~/.local/share/onionwire ``` If you set `ONIONWIRE_HOME`, delete that directory instead. ## Seized laptop v1 stores **plaintext** on disk: - message log (sqlite `messages.plaintext`) - your identity secret key (`self.identity_sk`) - friend public keys and current locators Full-disk encryption plus `/wipe` / `/wipe-all` is the mitigation. There is no sqlcipher in v1. Threat model: [`docs/THREAT_MODEL.md`](docs/THREAT_MODEL.md). ## Releases (maintainers) Tags trigger Forgejo Actions on the self-hosted Pi runner. The x86_64 asset has no runner on this instance, so it is built locally and attached to the same release. ```bash # 1. version in Cargo.toml must match the tag without the leading v cargo update --offline -p onionwire # keep Cargo.lock at the new version git commit -am "chore: release vX.Y.Z" && git push origin main # 2. tag and push — the release workflow builds + publishes aarch64 git tag -a vX.Y.Z -m "OnionWire vX.Y.Z" git push origin vX.Y.Z # 3. attach x86_64 from an x86_64 host (idempotent — safe to re-run) FORGEJO_TOKEN= scripts/build-release-local.sh vX.Y.Z ``` Workflows (Forgejo Actions, `.forgejo/workflows/` — jobs run on the `docker` label of the Pi runner and do their Rust work in a `rust:1.87-bookworm` sibling container): - [`.forgejo/workflows/ci.yml`](.forgejo/workflows/ci.yml) — `cargo test --locked` + `cargo clippy --all-targets -- -D warnings` on `main` and PRs (ignored Tor-live tests are not run). - [`.forgejo/workflows/release.yml`](.forgejo/workflows/release.yml) — on `v*.*.*` tags: builds `aarch64-unknown-linux-gnu` natively, strips, sha256, publishes to the release. Also runnable via `workflow_dispatch` with a tag input to re-publish. Scripts: - [`scripts/publish-release.sh`](scripts/publish-release.sh) — create-or-update a release and (re)upload assets via the Forgejo API. Needs `FORGEJO_TOKEN` (repo secret in CI, env locally). - [`scripts/build-release-local.sh`](scripts/build-release-local.sh) — x86_64 build + strip + checksum + publish. - [`scripts/release-body.md`](scripts/release-body.md) — release notes template (`@TAG@` is substituted). Do not run `cargo publish`; `publish = false`. CI needs the repo secret `FORGEJO_TOKEN` (an instance user PAT with repo write) to publish releases. ## Troubleshooting | Symptom | What to do | |---|---| | `GLIBC_… not found` | Binary is newer than your libc. Build from source (Option B). | | `sha256sum: FAILED` | Re-download both the binary and `.sha256`; run the check in the same directory. | | Hang on `bootstrapping Arti…` | Need outbound network. First consensus fetch is slow. Wait a couple of minutes; if it never publishes, it fails closed — no C-tor fallback. | | `onionwire: unknown argument` | No subcommands. Flags are `--version` / `--help` only, then the TUI. | | Blank / broken TUI | Run in a real terminal emulator, not `nohup` / systemd without a TTY. | | Two chats, same laptop | Separate `ONIONWIRE_HOME` per process. | | Friend cannot find you after F4 | Expected if they were offline. They must F3-paste the new QR. Same `k` updates the row. | ## Not in v1 Prosody, XMPP, s2s, MAM, carbons, outbox, multi-device, DHT / name server, sqlcipher. ## License MIT