From b3f83071683e494d018fa1aeb2ccd926bf558dcb Mon Sep 17 00:00:00 2001 From: Sirius DevOps Date: Thu, 10 Sep 2026 03:04:56 -0400 Subject: [PATCH] docs: install guide and GitHub Actions release binaries Add a README install path for CI-built Linux binaries (curl, checksum, PATH) plus source builds. CI runs tests/clippy on main; tags v*.*.* publish stripped x86_64 and aarch64 artifacts. --version/--help so a download can be checked without booting Tor. --- .github/workflows/ci.yml | 29 ++++++ .github/workflows/release.yml | 92 +++++++++++++++++++ Cargo.toml | 2 + README.md | 162 ++++++++++++++++++++++++++++++++-- src/main.rs | 24 +++++ 5 files changed, 304 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b7fcf09 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: ci + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + +jobs: + test: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - uses: Swatinem/rust-cache@v2 + + - name: Test + run: cargo test --locked + + - name: Clippy + run: cargo clippy --locked -- -D warnings diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e97b72a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,92 @@ +name: release + +on: + push: + tags: + - "v*.*.*" + workflow_dispatch: + +permissions: + contents: write + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + name: ${{ matrix.target }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-24.04 + target: x86_64-unknown-linux-gnu + - os: ubuntu-24.04-arm + target: aarch64-unknown-linux-gnu + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - uses: Swatinem/rust-cache@v2 + with: + key: release-${{ matrix.target }} + + - name: Build + run: cargo build --release --locked --target ${{ matrix.target }} + + - name: Pack + run: | + set -euo pipefail + bin="target/${{ matrix.target }}/release/onionwire" + strip "$bin" + asset="onionwire-${{ matrix.target }}" + mkdir -p dist + cp "$bin" "dist/${asset}" + (cd dist && sha256sum "${asset}" > "${asset}.sha256") + ls -l dist + + - uses: actions/upload-artifact@v4 + with: + name: onionwire-${{ matrix.target }} + path: dist/* + if-no-files-found: error + + publish: + needs: build + runs-on: ubuntu-24.04 + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + steps: + - uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + - name: Checksums + run: | + set -euo pipefail + cd dist + ls -l + cat *.sha256 + + - uses: softprops/action-gh-release@v2 + with: + files: dist/* + generate_release_notes: true + fail_on_unmatched_files: true + body: | + Prebuilt Linux binaries. No `tor` package required. + + ```bash + # x86_64 + curl -fL -o onionwire \ + https://github.com/sirius0xdev/onionwire/releases/download/${{ github.ref_name }}/onionwire-x86_64-unknown-linux-gnu + chmod +x onionwire + ./onionwire --version + ./onionwire + ``` + + See the README install guide for aarch64, checksums, and building from source. diff --git a/Cargo.toml b/Cargo.toml index a814cae..5a961a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,9 +2,11 @@ name = "onionwire" version = "0.1.0" edition = "2024" +rust-version = "1.87" description = "Lean Tor messenger: Arti in-process, identity=pubkey, onion=locator. No XMPP." license = "MIT" publish = false +repository = "https://github.com/sirius0xdev/onionwire" [dependencies] arti-client = { version = "0.46", features = ["tokio", "onion-service-client", "onion-service-service"] } diff --git a/README.md b/README.md index 18dc155..f5c97c5 100644 --- a/README.md +++ b/README.md @@ -3,20 +3,130 @@ 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 builds stripped Linux binaries on every `v*.*.*` tag and attaches them to [GitHub Releases](https://github.com/sirius0xdev/onionwire/releases). + +**x86_64 (most PCs / VMs):** + +```bash +curl -fL -o onionwire \ + https://github.com/sirius0xdev/onionwire/releases/latest/download/onionwire-x86_64-unknown-linux-gnu +curl -fL -o onionwire.sha256 \ + https://github.com/sirius0xdev/onionwire/releases/latest/download/onionwire-x86_64-unknown-linux-gnu.sha256 +sha256sum -c onionwire.sha256 +chmod +x onionwire +./onionwire --version +``` + +**aarch64 (Raspberry Pi, ARM servers):** + +```bash +curl -fL -o onionwire \ + https://github.com/sirius0xdev/onionwire/releases/latest/download/onionwire-aarch64-unknown-linux-gnu +curl -fL -o onionwire.sha256 \ + https://github.com/sirius0xdev/onionwire/releases/latest/download/onionwire-aarch64-unknown-linux-gnu.sha256 +sha256sum -c onionwire.sha256 +chmod +x onionwire +./onionwire --version +``` + +Install onto `PATH` if you want `onionwire` as a command: + +```bash +mkdir -p ~/.local/bin +mv onionwire ~/.local/bin/ +# 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-`). Run `sha256sum -c` from the same directory as the binary, or edit the filename in the `.sha256` file to match. + +### 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://github.com/sirius0xdev/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 +``` + +Source of record also lives at `https://forgejo.siriusdevops.com/sirius/onionwire` (same tree). GitHub is where CI publishes downloadable artifacts. + +### 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 ``` -cargo run --release +onionwire ``` -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. +or `cargo run --release`. -Arti bootstraps Tor and publishes a v3 onion service. Onion services in Arti are still **experimental**. OnionWire fails closed if the HS cannot come up — it does not fall back to a `tor` binary or C-tor. +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) + +| Key | Action | +|---|---| +| `F2` | Share: terminal QR + payload | +| `F3` | Paste a friend’s payload | +| `F4` | Rotate **onion** (locator only) | +| `↑` / `↓` | Roster | +| Enter | Send (composer) | +| `Ctrl-Q` | Quit | +| Esc | Cancel overlay / clear composer | + ## F2 QR `F2` shows a terminal QR and the payload: @@ -45,6 +155,19 @@ 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: @@ -55,12 +178,41 @@ v1 stores **plaintext** on disk: 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) + +Push a version tag. GitHub Actions builds Linux binaries and publishes a GitHub Release. + +```bash +# version in Cargo.toml must match the tag without the leading v +git tag v0.1.0 +git push github v0.1.0 # GitHub remote — this is what triggers CI +``` + +Workflows: + +- [`.github/workflows/ci.yml`](.github/workflows/ci.yml) — `cargo test --locked` and clippy on `main` / PRs (ignored Tor-live tests are not run). +- [`.github/workflows/release.yml`](.github/workflows/release.yml) — on `v*.*.*` tags, `cargo build --release` for `x86_64-unknown-linux-gnu` and `aarch64-unknown-linux-gnu`, strip, sha256, attach to the release. + +Do not run `cargo publish`; `publish = false`. + +## 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. -Plan of record: `/home/lancelot/.hermes/plans/2026-09-10_004409-onionwire-lean.md` - ## License MIT diff --git a/src/main.rs b/src/main.rs index 5bb4f7d..3aff8c5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,31 @@ use onionwire::tui::AppExit; +fn print_help() { + let v = env!("CARGO_PKG_VERSION"); + println!("onionwire {v} — Tor messenger (in-process Arti). Runs a TUI; no server."); + println!("Usage: onionwire [--version | --help]"); + println!("Data: $ONIONWIRE_HOME or ~/.local/share/onionwire"); +} + #[tokio::main] async fn main() { + match std::env::args().nth(1).as_deref() { + Some("--version") | Some("-V") => { + println!("onionwire {}", env!("CARGO_PKG_VERSION")); + return; + } + Some("--help") | Some("-h") => { + print_help(); + return; + } + Some(other) => { + eprintln!("onionwire: unknown argument {other}"); + eprintln!("Try: onionwire --help"); + std::process::exit(2); + } + None => {} + } + if let Err(e) = boot().await { eprintln!("onionwire: {e}"); std::process::exit(1);