From 91fd9601b862d84e44e1273f526bda62d7323002 Mon Sep 17 00:00:00 2001 From: Sirius DevOps Date: Thu, 10 Sep 2026 13:10:56 -0400 Subject: [PATCH] ci: Forgejo-native ci + release workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The GitHub workflows could never run on this instance: jobs asked for ubuntu-24.04 runners (the Pi runner only registers the 'docker' label, so every job sat in waiting), and release.yml published to github.com, which is not a remote of this repo. - .forgejo/workflows/ci.yml — cargo test + clippy on the 'docker' label, Rust work in a rust:1.87-bookworm sibling container (aarch64 native) - .forgejo/workflows/release.yml — on v*.*.* tags, build aarch64, strip, sha256, publish to the Forgejo release; workflow_dispatch takes a tag to re-publish - scripts/publish-release.sh — idempotent create-or-update release + asset upload via the Forgejo API (replaces same-named assets) - scripts/build-release-local.sh — x86_64 path (no x86_64 runner exists) - scripts/release-body.md — release notes template (@TAG@ substituted) - README: install URLs -> Forgejo, accurate maintainer release procedure - remove .github/workflows/* --- .forgejo/workflows/ci.yml | 47 +++++++++++++++++ .forgejo/workflows/release.yml | 87 +++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 29 ----------- .github/workflows/release.yml | 93 ---------------------------------- .gitignore | 1 + Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 49 ++++++++++++------ scripts/build-release-local.sh | 35 +++++++++++++ scripts/publish-release.sh | 88 ++++++++++++++++++++++++++++++++ scripts/release-body.md | 24 +++++++++ 11 files changed, 318 insertions(+), 139 deletions(-) create mode 100644 .forgejo/workflows/ci.yml create mode 100644 .forgejo/workflows/release.yml delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/release.yml create mode 100755 scripts/build-release-local.sh create mode 100755 scripts/publish-release.sh create mode 100644 scripts/release-body.md diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml new file mode 100644 index 0000000..fd99014 --- /dev/null +++ b/.forgejo/workflows/ci.yml @@ -0,0 +1,47 @@ +name: ci + +# Tests and clippy run on the Pi runner (aarch64) inside a rust container, so +# this CI proves the aarch64 build too. Jobs use the runner's `docker` label. +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +env: + CARGO_TERM_COLOR: never + RUST_IMAGE: rust:1.87-bookworm + CARGO_REGISTRY_VOLUME: onionwire-cargo-registry + CARGO_TARGET_VOLUME: onionwire-target-ci + BUILD_CONTAINER: onionwire-ci + +jobs: + test: + runs-on: docker + timeout-minutes: 120 + steps: + - name: Checkout + uses: https://code.forgejo.org/actions/checkout@v4 + + - name: cargo test + clippy on aarch64 + run: | + set -euo pipefail + docker volume create "$CARGO_REGISTRY_VOLUME" > /dev/null + docker volume create "$CARGO_TARGET_VOLUME" > /dev/null + docker rm -f "$BUILD_CONTAINER" > /dev/null 2>&1 || true + docker create --name "$BUILD_CONTAINER" -i \ + -e CARGO_TARGET_DIR=/target \ + -e CARGO_BUILD_JOBS=2 \ + -e CARGO_TERM_COLOR=never \ + -v "$CARGO_REGISTRY_VOLUME":/usr/local/cargo/registry \ + -v "$CARGO_TARGET_VOLUME":/target \ + -w /src \ + "$RUST_IMAGE" \ + sh -euxc 'mkdir -p /src && tar xzf - -C /src && cd /src \ + && apt-get update \ + && apt-get install -y --no-install-recommends pkg-config libssl-dev \ + && cargo test --locked \ + && cargo clippy --locked --all-targets -- -D warnings' + tar czf - --exclude=./target --exclude=./.git --exclude=./.worktrees . \ + | docker start -a -i "$BUILD_CONTAINER" + docker rm -f "$BUILD_CONTAINER" > /dev/null diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml new file mode 100644 index 0000000..cc9d431 --- /dev/null +++ b/.forgejo/workflows/release.yml @@ -0,0 +1,87 @@ +name: release + +# Native aarch64 build on the Pi runner, published to the Forgejo release. +# x86_64 is NOT built here: there is no x86_64 runner on this instance — build +# it on an x86_64 host with scripts/build-release-local.sh (same release, same +# asset naming), or the release will carry aarch64 only. +on: + push: + tags: + - "v*.*.*" + workflow_dispatch: + inputs: + tag: + description: Existing tag to (re)build and publish + required: true + +env: + CARGO_TERM_COLOR: never + TARGET: aarch64-unknown-linux-gnu + # Job steps run inside node:20-bullseye (the runner's docker label image) with + # the host docker socket mounted, so builds happen in a sibling rust container. + RUST_IMAGE: rust:1.87-bookworm + CARGO_REGISTRY_VOLUME: onionwire-cargo-registry + CARGO_TARGET_VOLUME: onionwire-target-aarch64 + BUILD_CONTAINER: onionwire-release-build + +jobs: + aarch64: + runs-on: docker + timeout-minutes: 120 + steps: + - name: Checkout + uses: https://code.forgejo.org/actions/checkout@v4 + + - name: Build ${{ env.TARGET }} in a rust container + run: | + set -euo pipefail + docker volume create "$CARGO_REGISTRY_VOLUME" > /dev/null + docker volume create "$CARGO_TARGET_VOLUME" > /dev/null + docker rm -f "$BUILD_CONTAINER" > /dev/null 2>&1 || true + # The workspace lives in a per-task volume the host daemon cannot + # resolve, so pipe the source tree in over stdin (tar) and pull the + # binary back out with docker cp. + docker create --name "$BUILD_CONTAINER" -i \ + -e CARGO_TARGET_DIR=/target \ + -e CARGO_BUILD_JOBS=2 \ + -e CARGO_TERM_COLOR=never \ + -v "$CARGO_REGISTRY_VOLUME":/usr/local/cargo/registry \ + -v "$CARGO_TARGET_VOLUME":/target \ + -w /src \ + "$RUST_IMAGE" \ + sh -euxc 'mkdir -p /src && tar xzf - -C /src && cd /src \ + && apt-get update \ + && apt-get install -y --no-install-recommends pkg-config libssl-dev \ + && cargo build --release --locked \ + && strip /target/release/onionwire \ + && ls -l /target/release/onionwire' + tar czf - --exclude=./target --exclude=./.git --exclude=./.worktrees . \ + | docker start -a -i "$BUILD_CONTAINER" + mkdir -p dist + docker cp "$BUILD_CONTAINER:/target/release/onionwire" "dist/onionwire-$TARGET" + docker rm -f "$BUILD_CONTAINER" > /dev/null + + - name: Pack and checksum + run: | + set -euo pipefail + cd dist + file "onionwire-$TARGET" + sha256sum "onionwire-$TARGET" > "onionwire-$TARGET.sha256" + sha256sum -c "onionwire-$TARGET.sha256" + ls -l + + - name: Publish to the Forgejo release + env: + FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }} + TARGET_COMMITISH: ${{ github.sha }} + REPO_API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} + REF: ${{ github.ref_name }} + INPUT_TAG: ${{ github.event.inputs.tag }} + run: | + set -euo pipefail + tag="${INPUT_TAG:-$REF}" + echo "publishing $tag from $REPO_API" + scripts/publish-release.sh \ + "$tag" "OnionWire $tag" \ + scripts/release-body.md \ + "dist/onionwire-$TARGET" "dist/onionwire-$TARGET.sha256" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index b7fcf09..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,29 +0,0 @@ -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 deleted file mode 100644 index 5c50422..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,93 +0,0 @@ -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 — keep the asset filename so sha256sum -c matches - curl -fL -O https://github.com/sirius0xdev/onionwire/releases/download/${{ github.ref_name }}/onionwire-x86_64-unknown-linux-gnu - curl -fL -O https://github.com/sirius0xdev/onionwire/releases/download/${{ github.ref_name }}/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 - ./onionwire-x86_64-unknown-linux-gnu - ``` - - See the README install guide for aarch64, checksums, and building from source. diff --git a/.gitignore b/.gitignore index ea8c4bf..4f96631 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/dist diff --git a/Cargo.lock b/Cargo.lock index d8c06e7..f983937 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2595,7 +2595,7 @@ dependencies = [ [[package]] name = "onionwire" -version = "0.1.1" +version = "0.1.2" dependencies = [ "arti-client", "ed25519-dalek", diff --git a/Cargo.toml b/Cargo.toml index cfaa275..7214959 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "onionwire" -version = "0.1.1" +version = "0.1.2" edition = "2024" rust-version = "1.87" description = "Lean Tor messenger: Arti in-process, identity=pubkey, onion=locator. No XMPP." diff --git a/README.md b/README.md index 8583124..1feaddc 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,13 @@ Arti onion services are still **experimental**. If the hidden service cannot com ### 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). +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://github.com/sirius0xdev/onionwire/releases/latest/download/onionwire-x86_64-unknown-linux-gnu -curl -fL -O https://github.com/sirius0xdev/onionwire/releases/latest/download/onionwire-x86_64-unknown-linux-gnu.sha256 +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 @@ -36,8 +36,8 @@ chmod +x onionwire-x86_64-unknown-linux-gnu **aarch64 (Raspberry Pi, ARM servers):** ```bash -curl -fL -O https://github.com/sirius0xdev/onionwire/releases/latest/download/onionwire-aarch64-unknown-linux-gnu -curl -fL -O https://github.com/sirius0xdev/onionwire/releases/latest/download/onionwire-aarch64-unknown-linux-gnu.sha256 +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 @@ -68,7 +68,7 @@ rustc --version # must be 1.87 or newer ``` ```bash -git clone https://github.com/sirius0xdev/onionwire.git +git clone https://forgejo.siriusdevops.com/sirius/onionwire.git cd onionwire cargo test --locked cargo clippy --locked -- -D warnings @@ -84,7 +84,8 @@ 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. +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 @@ -184,20 +185,38 @@ 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. +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 -# 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 +# 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: +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): -- [`.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. +- [`.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. -Do not run `cargo publish`; `publish = false`. +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 diff --git a/scripts/build-release-local.sh b/scripts/build-release-local.sh new file mode 100755 index 0000000..4d1c7fc --- /dev/null +++ b/scripts/build-release-local.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Build the x86_64 release asset on this host and publish it to the Forgejo +# release for . CI (.forgejo/workflows/release.yml) only covers aarch64 — +# there is no x86_64 runner on the instance, so this is the x86_64 path. +# +# Usage: FORGEJO_TOKEN=... scripts/build-release-local.sh v0.1.2 +# +# Does not create or push the tag: tag and push first, then run this so the +# release body/asset set matches a real tag. Idempotent (assets are replaced). +set -euo pipefail + +tag="${1:?usage: build-release-local.sh }" +target=x86_64-unknown-linux-gnu +root="$(cd "$(dirname "$0")/.." && pwd)" +cd "$root" + +expected="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)" +if [ "$tag" != "v$expected" ]; then + echo "warn: tag $tag does not match Cargo.toml version $expected (--version will report $expected)" +fi + +echo "==> cargo build --release --locked" +cargo build --release --locked + +mkdir -p dist +asset="onionwire-$target" +cp "target/release/onionwire" "dist/$asset" +strip "dist/$asset" +(cd dist && sha256sum "$asset" > "$asset.sha256" && sha256sum -c "$asset.sha256") +file "dist/$asset" +"dist/$asset" --version + +echo "==> publishing to the release for $tag" +scripts/publish-release.sh "$tag" "OnionWire $tag" scripts/release-body.md \ + "dist/$asset" "dist/$asset.sha256" diff --git a/scripts/publish-release.sh b/scripts/publish-release.sh new file mode 100755 index 0000000..60486c4 --- /dev/null +++ b/scripts/publish-release.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# Create-or-update a Forgejo release and (re)upload its assets. +# +# Usage: publish-release.sh [...] +# Env: FORGEJO_TOKEN user PAT with repo write (required) +# REPO_API default https://forgejo.siriusdevops.com/api/v1/repos/sirius/onionwire +# TARGET_COMMITISH default: push-triggered commit (CI) else "main" +# +# Idempotent: re-running for the same tag reuses the release and replaces +# same-named assets instead of failing with 409. +set -euo pipefail + +tag="${1:?usage: publish-release.sh ...}" +name="${2:?missing release name}" +body_file="${3:?missing body file}" +shift 3 + +: "${FORGEJO_TOKEN:?FORGEJO_TOKEN is not set}" +api="${REPO_API:-https://forgejo.siriusdevops.com/api/v1/repos/sirius/onionwire}" +target="${TARGET_COMMITISH:-main}" + +jqp() { python3 -c "import json,sys; d=json.load(sys.stdin); print($1)"; } + +# Body with @TAG@ substituted, JSON-encoded by python (handles newlines/quotes). +python3 - "$body_file" "$tag" "$target" "$name" > /tmp/release-body.json <<'PY' +import json, sys +body = open(sys.argv[1]).read().replace("@TAG@", sys.argv[2]) +print(json.dumps({ + "tag_name": sys.argv[2], + "target_commitish": sys.argv[3], + "name": sys.argv[4], + "body": body, + "draft": False, + "prerelease": False, +})) +PY + +code=$(curl -s -o /tmp/release-rel.json -w '%{http_code}' \ + -H "Authorization: Bearer $FORGEJO_TOKEN" "$api/releases/tags/$tag") +if [ "$code" = "404" ]; then + echo "publish: creating release $tag" + curl -sf -X POST "$api/releases" \ + -H "Authorization: Bearer $FORGEJO_TOKEN" \ + -H 'Content-Type: application/json' \ + --data @/tmp/release-body.json -o /tmp/release-rel.json +elif [ "$code" = "200" ]; then + echo "publish: release $tag exists, updating" + rid=$(jqp "d['id']" < /tmp/release-rel.json) + curl -sf -X PATCH "$api/releases/$rid" \ + -H "Authorization: Bearer $FORGEJO_TOKEN" \ + -H 'Content-Type: application/json' \ + --data @/tmp/release-body.json -o /tmp/release-rel.json +else + echo "publish: unexpected status $code from GET releases/tags/$tag" >&2 + cat /tmp/release-rel.json >&2; exit 1 +fi +rid=$(jqp "d['id']" < /tmp/release-rel.json) +echo "publish: release id $rid ($tag)" + +# Replace same-named assets so re-runs are clean. +for f in "$@"; do + [ -f "$f" ] || { echo "publish: missing asset $f" >&2; exit 1; } + asset=$(basename "$f") + curl -s -H "Authorization: Bearer $FORGEJO_TOKEN" "$api/releases/$rid/assets" \ + | python3 -c " +import json,sys +for a in json.load(sys.stdin): + print(a['id'], a.get('name','')) +" | while read -r aid aname; do + if [ "$aname" = "$asset" ]; then + echo "publish: deleting stale asset $aname ($aid)" + curl -sf -X DELETE -H "Authorization: Bearer $FORGEJO_TOKEN" \ + "$api/releases/$rid/assets/$aid" > /dev/null + fi + done + curl -sf -X POST "$api/releases/$rid/assets?name=$asset" \ + -H "Authorization: Bearer $FORGEJO_TOKEN" \ + -F "attachment=@$f" -o /tmp/release-asset.json + python3 -c " +import json;d=json.load(open('/tmp/release-asset.json')) +print('publish: uploaded', d['name'], d['size'], 'bytes')" +done + +curl -s -H "Authorization: Bearer $FORGEJO_TOKEN" "$api/releases/$rid/assets" \ + | python3 -c " +import json,sys +for a in json.load(sys.stdin): + print('publish: asset ', a['name'], a['size'])" diff --git a/scripts/release-body.md b/scripts/release-body.md new file mode 100644 index 0000000..25efd86 --- /dev/null +++ b/scripts/release-body.md @@ -0,0 +1,24 @@ +# OnionWire @TAG@ + +Prebuilt Linux binaries. No `tor` package required — Arti runs in-process. + +```bash +# aarch64 (Raspberry Pi, ARM servers) — CI builds this one on the Pi runner +curl -fL -O https://forgejo.siriusdevops.com/sirius/onionwire/releases/download/@TAG@/onionwire-aarch64-unknown-linux-gnu +curl -fL -O https://forgejo.siriusdevops.com/sirius/onionwire/releases/download/@TAG@/onionwire-aarch64-unknown-linux-gnu.sha256 +sha256sum -c onionwire-aarch64-unknown-linux-gnu.sha256 +chmod +x onionwire-aarch64-unknown-linux-gnu + +# x86_64 (most PCs / VMs) — built locally with scripts/build-release-local.sh +curl -fL -O https://forgejo.siriusdevops.com/sirius/onionwire/releases/download/@TAG@/onionwire-x86_64-unknown-linux-gnu +curl -fL -O https://forgejo.siriusdevops.com/sirius/onionwire/releases/download/@TAG@/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- --version +``` + +Keep the asset filenames so `sha256sum -c` matches. Both binaries link OpenSSL 3 +dynamically (`libssl.so.3`), which is standard on Debian 12+/Ubuntu 24.04+. + +Building from source is covered in the README install guide.