docs: install guide and GitHub Actions release binaries
Some checks are pending
ci / test (push) Waiting to run
Some checks are pending
ci / test (push) Waiting to run
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.
This commit is contained in:
parent
5412e020bb
commit
b3f8307168
5 changed files with 304 additions and 5 deletions
29
.github/workflows/ci.yml
vendored
Normal file
29
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -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
|
||||||
92
.github/workflows/release.yml
vendored
Normal file
92
.github/workflows/release.yml
vendored
Normal file
|
|
@ -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.
|
||||||
|
|
@ -2,9 +2,11 @@
|
||||||
name = "onionwire"
|
name = "onionwire"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
rust-version = "1.87"
|
||||||
description = "Lean Tor messenger: Arti in-process, identity=pubkey, onion=locator. No XMPP."
|
description = "Lean Tor messenger: Arti in-process, identity=pubkey, onion=locator. No XMPP."
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
publish = false
|
publish = false
|
||||||
|
repository = "https://github.com/sirius0xdev/onionwire"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arti-client = { version = "0.46", features = ["tokio", "onion-service-client", "onion-service-service"] }
|
arti-client = { version = "0.46", features = ["tokio", "onion-service-client", "onion-service-service"] }
|
||||||
|
|
|
||||||
162
README.md
162
README.md
|
|
@ -3,20 +3,130 @@
|
||||||
Lean Tor messenger: one Rust process (ratatui + Noise IK + sqlite + in-process Arti).
|
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.
|
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 (`<hash> onionwire-<target>`). 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
|
## 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
|
## 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.
|
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 QR
|
||||||
|
|
||||||
`F2` shows a terminal QR and the payload:
|
`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` — 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.
|
- `/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
|
## Seized laptop
|
||||||
|
|
||||||
v1 stores **plaintext** on disk:
|
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.
|
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
|
## Not in v1
|
||||||
|
|
||||||
Prosody, XMPP, s2s, MAM, carbons, outbox, multi-device, DHT / name server, sqlcipher.
|
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
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|
|
||||||
24
src/main.rs
24
src/main.rs
|
|
@ -1,7 +1,31 @@
|
||||||
use onionwire::tui::AppExit;
|
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]
|
#[tokio::main]
|
||||||
async fn 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 {
|
if let Err(e) = boot().await {
|
||||||
eprintln!("onionwire: {e}");
|
eprintln!("onionwire: {e}");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue