ci: Forgejo-native ci + release workflows
Some checks failed
ci / test (push) Failing after 1m6s

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/*
This commit is contained in:
Sirius DevOps 2026-09-10 13:10:56 -04:00
parent b9f2dc3806
commit 91fd9601b8
No known key found for this signature in database
11 changed files with 318 additions and 139 deletions

47
.forgejo/workflows/ci.yml Normal file
View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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.

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target /target
/dist

2
Cargo.lock generated
View file

@ -2595,7 +2595,7 @@ dependencies = [
[[package]] [[package]]
name = "onionwire" name = "onionwire"
version = "0.1.1" version = "0.1.2"
dependencies = [ dependencies = [
"arti-client", "arti-client",
"ed25519-dalek", "ed25519-dalek",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "onionwire" name = "onionwire"
version = "0.1.1" version = "0.1.2"
edition = "2024" edition = "2024"
rust-version = "1.87" 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."

View file

@ -21,13 +21,13 @@ Arti onion services are still **experimental**. If the hidden service cannot com
### Option A — download a release binary ### 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):** **x86_64 (most PCs / VMs):**
```bash ```bash
curl -fL -O https://github.com/sirius0xdev/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
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.sha256
sha256sum -c onionwire-x86_64-unknown-linux-gnu.sha256 sha256sum -c onionwire-x86_64-unknown-linux-gnu.sha256
chmod +x onionwire-x86_64-unknown-linux-gnu chmod +x onionwire-x86_64-unknown-linux-gnu
./onionwire-x86_64-unknown-linux-gnu --version ./onionwire-x86_64-unknown-linux-gnu --version
@ -36,8 +36,8 @@ chmod +x onionwire-x86_64-unknown-linux-gnu
**aarch64 (Raspberry Pi, ARM servers):** **aarch64 (Raspberry Pi, ARM servers):**
```bash ```bash
curl -fL -O https://github.com/sirius0xdev/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
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.sha256
sha256sum -c onionwire-aarch64-unknown-linux-gnu.sha256 sha256sum -c onionwire-aarch64-unknown-linux-gnu.sha256
chmod +x onionwire-aarch64-unknown-linux-gnu chmod +x onionwire-aarch64-unknown-linux-gnu
./onionwire-aarch64-unknown-linux-gnu --version ./onionwire-aarch64-unknown-linux-gnu --version
@ -68,7 +68,7 @@ rustc --version # must be 1.87 or newer
``` ```
```bash ```bash
git clone https://github.com/sirius0xdev/onionwire.git git clone https://forgejo.siriusdevops.com/sirius/onionwire.git
cd onionwire cd onionwire
cargo test --locked cargo test --locked
cargo clippy --locked -- -D warnings cargo clippy --locked -- -D warnings
@ -84,7 +84,8 @@ cargo install --path . --locked --force
# lands in ~/.cargo/bin/onionwire # 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 ### Two instances on one machine
@ -184,20 +185,38 @@ Threat model: [`docs/THREAT_MODEL.md`](docs/THREAT_MODEL.md).
## Releases (maintainers) ## 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 ```bash
# version in Cargo.toml must match the tag without the leading v # 1. version in Cargo.toml must match the tag without the leading v
git tag v0.1.0 cargo update --offline -p onionwire # keep Cargo.lock at the new version
git push github v0.1.0 # GitHub remote — this is what triggers CI 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=<pat> 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). - [`.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).
- [`.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/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 ## Troubleshooting

35
scripts/build-release-local.sh Executable file
View file

@ -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 <tag>. 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 <tag>}"
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"

88
scripts/publish-release.sh Executable file
View file

@ -0,0 +1,88 @@
#!/usr/bin/env bash
# Create-or-update a Forgejo release and (re)upload its assets.
#
# Usage: publish-release.sh <tag> <release-name> <body-file> <asset> [<asset>...]
# 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 <tag> <name> <body-file> <asset>...}"
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'])"

24
scripts/release-body.md Normal file
View file

@ -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-<target> --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.