From 372dd67a3bb2302370d8035b068c46ffb63915e7 Mon Sep 17 00:00:00 2001 From: Christian Nennemann Date: Wed, 4 Mar 2026 20:52:15 +0100 Subject: [PATCH] feat: add OpenWrt cross-compilation and packaging (Phase F7) - packaging/openwrt/: opkg Makefile, procd init script, uci config - scripts/cross-compile.sh: build for musl targets with size checks - .github/workflows/openwrt.yml: CI cross-compile + 5 MB size gate - docs/openwrt.md: installation and configuration guide - Targets: x86_64-musl, armv7-musleabihf, aarch64-musl - Uses cargo-zigbuild for Docker-free cross-compilation --- .github/workflows/openwrt.yml | 65 +++++++++++ docs/openwrt.md | 146 ++++++++++++++++++++++++ packaging/openwrt/Makefile | 58 ++++++++++ packaging/openwrt/files/quicproquo.init | 45 ++++++++ packaging/openwrt/files/quicproquo.uci | 7 ++ scripts/cross-compile.sh | 91 +++++++++++++++ 6 files changed, 412 insertions(+) create mode 100644 .github/workflows/openwrt.yml create mode 100644 docs/openwrt.md create mode 100644 packaging/openwrt/Makefile create mode 100755 packaging/openwrt/files/quicproquo.init create mode 100644 packaging/openwrt/files/quicproquo.uci create mode 100755 scripts/cross-compile.sh diff --git a/.github/workflows/openwrt.yml b/.github/workflows/openwrt.yml new file mode 100644 index 0000000..1bb524f --- /dev/null +++ b/.github/workflows/openwrt.yml @@ -0,0 +1,65 @@ +name: OpenWrt Cross-Compile + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + MAX_SIZE_MB: 5 + +jobs: + cross-compile: + name: Cross-compile (${{ matrix.target }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: + - x86_64-unknown-linux-musl + - armv7-unknown-linux-musleabihf + - aarch64-unknown-linux-musl + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Install cargo-zigbuild and zig + run: | + pip3 install ziglang + cargo install cargo-zigbuild + + - name: Add target + run: rustup target add ${{ matrix.target }} + + - name: Build (size-optimised) + env: + CARGO_PROFILE_RELEASE_OPT_LEVEL: s + CARGO_PROFILE_RELEASE_LTO: 'true' + CARGO_PROFILE_RELEASE_CODEGEN_UNITS: '1' + CARGO_PROFILE_RELEASE_STRIP: symbols + run: | + cargo zigbuild --release --target ${{ matrix.target }} --bin qpq-server + + - name: Check binary size + run: | + BINARY="target/${{ matrix.target }}/release/qpq-server" + SIZE=$(stat -c%s "$BINARY") + SIZE_MB=$(echo "scale=2; $SIZE / 1048576" | bc) + echo "Binary size: ${SIZE_MB} MB" + MAX_BYTES=$(( ${{ env.MAX_SIZE_MB }} * 1048576 )) + if [ "$SIZE" -gt "$MAX_BYTES" ]; then + echo "::error::Binary exceeds ${MAX_SIZE_MB} MB limit (${SIZE_MB} MB)" + exit 1 + fi + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: qpq-server-${{ matrix.target }} + path: target/${{ matrix.target }}/release/qpq-server + retention-days: 30 diff --git a/docs/openwrt.md b/docs/openwrt.md new file mode 100644 index 0000000..9157607 --- /dev/null +++ b/docs/openwrt.md @@ -0,0 +1,146 @@ +# OpenWrt Deployment Guide + +Run quicproquo on OpenWrt routers for mesh-capable, always-on encrypted messaging at the network edge. + +## Supported Targets + +| Target | Architecture | Common Devices | +|-----------------------------------|----------------|--------------------------| +| `x86_64-unknown-linux-musl` | x86_64 | PC Engines APU, VMs | +| `armv7-unknown-linux-musleabihf` | ARMv7 (hard-float) | RPi 2/3, many routers | +| `aarch64-unknown-linux-musl` | AArch64 | RPi 4/5, modern routers | + +## Prerequisites + +- Rust toolchain (stable) +- One of: `cargo-zigbuild` (recommended) or `cross` + +```bash +# Install cargo-zigbuild (recommended — no Docker required) +pip3 install ziglang +cargo install cargo-zigbuild +``` + +## Cross-Compilation + +### Quick Start + +```bash +# Build for all supported targets +./scripts/cross-compile.sh + +# Build for a specific target +./scripts/cross-compile.sh aarch64-unknown-linux-musl +``` + +### Manual Build + +```bash +# Add the musl target +rustup target add x86_64-unknown-linux-musl + +# Size-optimised release build +CARGO_PROFILE_RELEASE_OPT_LEVEL=s \ +CARGO_PROFILE_RELEASE_LTO=true \ +CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 \ +CARGO_PROFILE_RELEASE_STRIP=symbols \ +cargo zigbuild --release --target x86_64-unknown-linux-musl --bin qpq-server +``` + +The binary lands at `target//release/qpq-server`. Target size: under 5 MB. + +## OpenWrt Package Installation + +### Option 1: Direct binary install (quick) + +```bash +# Copy binary to router +scp target/aarch64-unknown-linux-musl/release/qpq-server root@router:/usr/bin/ + +# Copy init script and config +scp packaging/openwrt/files/quicproquo.init root@router:/etc/init.d/quicproquo +scp packaging/openwrt/files/quicproquo.uci root@router:/etc/config/quicproquo + +# Enable and start +ssh root@router 'chmod +x /etc/init.d/quicproquo && /etc/init.d/quicproquo enable && /etc/init.d/quicproquo start' +``` + +### Option 2: opkg package feed + +Add the feed to your OpenWrt build system: + +```bash +# In your OpenWrt buildroot, add to feeds.conf: +echo "src-link quicproquo /path/to/quicproquo/packaging/openwrt" >> feeds.conf + +# Update and install +./scripts/feeds update quicproquo +./scripts/feeds install quicproquo + +# Select in menuconfig: Network -> quicproquo +make menuconfig +make package/quicproquo/compile V=s +``` + +## Configuration + +The server is configured via UCI at `/etc/config/quicproquo`: + +``` +config server 'server' + option listen '0.0.0.0:7000' + option data_dir '/var/lib/quicproquo' + option log_level 'info' + option tls_cert '/var/lib/quicproquo/server-cert.der' + option tls_key '/var/lib/quicproquo/server-key.der' + option production '1' +``` + +### UCI Options + +| Option | Default | Description | +|--------------|------------------------------------------|----------------------------------| +| `listen` | `0.0.0.0:7000` | QUIC listen address | +| `data_dir` | `/var/lib/quicproquo` | Persistent data directory | +| `log_level` | `info` | RUST_LOG filter | +| `tls_cert` | `/server-cert.der` | TLS certificate path (DER) | +| `tls_key` | `/server-key.der` | TLS private key path (DER) | +| `production` | `1` | Enable production hardening | + +### Service Management + +```bash +# Start / stop / restart +/etc/init.d/quicproquo start +/etc/init.d/quicproquo stop +/etc/init.d/quicproquo restart + +# Enable at boot +/etc/init.d/quicproquo enable + +# View logs +logread -e quicproquo +``` + +## Binary Size Optimization + +The release profile is configured for minimal binary size: + +| Setting | Value | Effect | +|------------------|------------|-------------------------------------| +| `opt-level` | `s` | Optimize for size over speed | +| `lto` | `true` | Full link-time optimization | +| `codegen-units` | `1` | Single codegen unit for better LTO | +| `strip` | `symbols` | Remove debug symbols | + +The CI workflow enforces a 5 MB maximum binary size on every release tag. + +## CI/CD + +The `.github/workflows/openwrt.yml` workflow automatically: + +1. Cross-compiles for all three musl targets +2. Verifies binary size stays under 5 MB +3. Uploads binaries as release artifacts + +Triggered on version tags (`v*`) or manual dispatch. diff --git a/packaging/openwrt/Makefile b/packaging/openwrt/Makefile new file mode 100644 index 0000000..ed76845 --- /dev/null +++ b/packaging/openwrt/Makefile @@ -0,0 +1,58 @@ +# OpenWrt package feed Makefile for quicproquo server. +# +# Usage: +# 1. Add this directory as a custom feed in feeds.conf: +# src-link quicproquo /path/to/quicproquo/packaging/openwrt +# 2. ./scripts/feeds update quicproquo && ./scripts/feeds install quicproquo +# 3. make menuconfig (select Network -> quicproquo) +# 4. make package/quicproquo/compile V=s +# +# The binary is pre-built via cross-compilation (see scripts/cross-compile.sh) +# and the Makefile simply installs it into the ipkg. + +include $(TOPDIR)/rules.mk + +PKG_NAME:=quicproquo +PKG_VERSION:=0.1.0 +PKG_RELEASE:=1 +PKG_MAINTAINER:=quicproquo team +PKG_LICENSE:=MIT + +include $(INCLUDE_DIR)/package.mk + +define Package/quicproquo + SECTION:=net + CATEGORY:=Network + TITLE:=End-to-end encrypted group messenger (server) + DEPENDS:=+libpthread +librt + URL:=https://github.com/nicholasgasior/quicproquo +endef + +define Package/quicproquo/description + Production-grade end-to-end encrypted group messenger using QUIC transport, + MLS (RFC 9420), ML-KEM-768 hybrid post-quantum KEM, and OPAQUE authentication. + This package installs the qpq-server daemon. +endef + +define Package/quicproquo/conffiles +/etc/config/quicproquo +endef + +# Skip standard build — we use pre-compiled static musl binaries. +define Build/Compile +endef + +define Package/quicproquo/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/qpq-server $(1)/usr/bin/qpq-server + + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/quicproquo.init $(1)/etc/init.d/quicproquo + + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/quicproquo.uci $(1)/etc/config/quicproquo + + $(INSTALL_DIR) $(1)/var/lib/quicproquo +endef + +$(eval $(call BuildPackage,quicproquo)) diff --git a/packaging/openwrt/files/quicproquo.init b/packaging/openwrt/files/quicproquo.init new file mode 100755 index 0000000..902fca4 --- /dev/null +++ b/packaging/openwrt/files/quicproquo.init @@ -0,0 +1,45 @@ +#!/bin/sh /etc/rc.common +# procd init script for quicproquo server. +# Reads configuration from /etc/config/quicproquo (uci). + +START=95 +STOP=10 +USE_PROCD=1 + +PROG=/usr/bin/qpq-server +DATA_DIR=/var/lib/quicproquo + +start_service() { + local listen data_dir log_level tls_cert tls_key production + + config_load quicproquo + config_get listen server listen '0.0.0.0:7000' + config_get data_dir server data_dir "$DATA_DIR" + config_get log_level server log_level 'info' + config_get tls_cert server tls_cert "${data_dir}/server-cert.der" + config_get tls_key server tls_key "${data_dir}/server-key.der" + config_get_bool production server production 1 + + [ -d "$data_dir" ] || mkdir -p "$data_dir" + + procd_open_instance + procd_set_param command "$PROG" + + procd_set_param env \ + RUST_LOG="$log_level" \ + QPQ_LISTEN="$listen" \ + QPQ_DATA_DIR="$data_dir" \ + QPQ_TLS_CERT="$tls_cert" \ + QPQ_TLS_KEY="$tls_key" \ + QPQ_PRODUCTION="$production" + + procd_set_param respawn 3600 5 5 + procd_set_param stderr 1 + procd_set_param stdout 1 + procd_set_param user qpq + procd_close_instance +} + +service_triggers() { + procd_add_reload_trigger "quicproquo" +} diff --git a/packaging/openwrt/files/quicproquo.uci b/packaging/openwrt/files/quicproquo.uci new file mode 100644 index 0000000..d85d316 --- /dev/null +++ b/packaging/openwrt/files/quicproquo.uci @@ -0,0 +1,7 @@ +config server 'server' + option listen '0.0.0.0:7000' + option data_dir '/var/lib/quicproquo' + option log_level 'info' + option tls_cert '/var/lib/quicproquo/server-cert.der' + option tls_key '/var/lib/quicproquo/server-key.der' + option production '1' diff --git a/scripts/cross-compile.sh b/scripts/cross-compile.sh new file mode 100755 index 0000000..bdcfd4a --- /dev/null +++ b/scripts/cross-compile.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# Cross-compile quicproquo for musl targets (OpenWrt / embedded Linux). +# +# Produces statically linked, stripped binaries optimised for size. +# Requires: cargo-zigbuild (preferred) or cross. +# +# Usage: +# ./scripts/cross-compile.sh # all targets +# ./scripts/cross-compile.sh x86_64-unknown-linux-musl # single target +# +# Output: target//release/qpq-server (stripped) + +set -euo pipefail + +TARGETS=( + x86_64-unknown-linux-musl + armv7-unknown-linux-musleabihf + aarch64-unknown-linux-musl +) + +MAX_SIZE_MB=5 + +# Size-optimised release profile overrides. +export CARGO_PROFILE_RELEASE_OPT_LEVEL=s +export CARGO_PROFILE_RELEASE_LTO=true +export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 +export CARGO_PROFILE_RELEASE_STRIP=symbols + +# Detect build tool. +if command -v cargo-zigbuild &>/dev/null; then + BUILD_CMD="cargo zigbuild" +elif command -v cross &>/dev/null; then + BUILD_CMD="cross build" +else + echo "ERROR: Install cargo-zigbuild or cross first:" >&2 + echo " cargo install cargo-zigbuild # recommended" >&2 + echo " cargo install cross # alternative" >&2 + exit 1 +fi + +# If arguments provided, use those as targets. +if [ $# -gt 0 ]; then + TARGETS=("$@") +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$PROJECT_ROOT" + +echo "=== quicproquo cross-compilation ===" +echo "Build tool: $BUILD_CMD" +echo "Targets: ${TARGETS[*]}" +echo "" + +FAILED=() +for target in "${TARGETS[@]}"; do + echo "--- Building for $target ---" + if $BUILD_CMD --release --target "$target" --bin qpq-server; then + BINARY="target/$target/release/qpq-server" + if [ -f "$BINARY" ]; then + SIZE=$(stat -c%s "$BINARY" 2>/dev/null || stat -f%z "$BINARY") + SIZE_MB=$(echo "scale=2; $SIZE / 1048576" | bc) + MAX_BYTES=$((MAX_SIZE_MB * 1048576)) + echo " Binary: $BINARY" + echo " Size: ${SIZE_MB} MB" + if [ "$SIZE" -gt "$MAX_BYTES" ]; then + echo " WARNING: Binary exceeds ${MAX_SIZE_MB} MB size limit!" + FAILED+=("$target (size: ${SIZE_MB} MB)") + else + echo " OK: within ${MAX_SIZE_MB} MB limit" + fi + else + echo " ERROR: Binary not found at $BINARY" + FAILED+=("$target (binary not found)") + fi + else + echo " ERROR: Build failed for $target" + FAILED+=("$target (build failed)") + fi + echo "" +done + +if [ ${#FAILED[@]} -gt 0 ]; then + echo "=== FAILURES ===" + for f in "${FAILED[@]}"; do + echo " - $f" + done + exit 1 +fi + +echo "=== All targets built successfully ==="