mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-22 07:48:26 +00:00
Router-to-router radio backhaul over an open 802.11s mesh interface, with FIPS providing all encryption (Noise IK), authentication, and routing on top of bare L2 neighbor links. The mesh runs OPEN with mesh_fwding 0 — SAE would duplicate the Noise layer and force ath10k raw mode, and FIPS is the routing layer — so the Noise handshake is the real auth/encryption boundary and FIPS's spanning tree does the routing. fips-mesh-setup: an opt-in UCI helper that creates a per-radio mesh-point interface (radio0 -> fips-mesh0, radio1 -> fips-mesh1; trailing-digit derivation with a free-index fallback and a collision guard). Radio setup stays opt-in — a package must not commandeer radios on install. 'remove' takes an optional radio and otherwise removes all instances. Dual-band routers get one instance per radio; FIPS treats the two backhaul paths as failover, not multipath: it keeps one active link per peer (cross-connection resolution picks a single winner), and the second band stands by, re-establishing the peer after keepalive timeout — traffic never uses both bands at once. fips.yaml ships the mesh0/mesh1 Ethernet-transport entries commented out, so a stock install that never creates fips-mesh* logs no per-boot "interface missing" bind warning. fips-mesh-setup uncomments the matching meshN block when it creates the interface and re-comments it on remove, so the flash-and-drop-in flow needs no manual config edit. The file is rewritten 0600-first (it may hold an inline nsec) via an atomic replace. Two field-found silent non-peering causes are surfaced by the helper and the guide: - Same channel: mesh points only peer on a shared channel, and 'auto' lets each radio pick its own. The helper prints the radio's band/channel and warns loudly on 'auto' with the exact uci command to pin one; the how-to gains an ordered no-peers triage (channel mismatch, on-air scan check, DFS CAC wait, regdomain). - STA channel capture: a client (sta) interface drags the whole radio to its upstream AP's channel, so a mesh pinned elsewhere never joins and does not recover until the STA disconnects. The helper warns when the target radio carries a STA; the guide documents the incompatibility of a roaming uplink with a fixed-channel mesh on the same radio. Both the create and remove paths run 'wifi reload', which briefly drops every client AP on all radios; the how-to sets that expectation. Regression test: the shipped OpenWrt fips.yaml must parse via the real Config deserializer in both states — as shipped (mesh inactive) and after the uncomment the helper performs. Packaging: the helper is installed across the ipk/apk/buildroot paths (three synced copies), with the CI structural checks and shellcheck targets extended to cover it. Full guide in docs/how-to/set-up-80211s-mesh-backhaul.md.
298 lines
12 KiB
Bash
Executable File
298 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
# Build a FIPS .apk package for OpenWrt without the OpenWrt SDK.
|
|
#
|
|
# apk-tools (.apk) is the mandatory package manager from OpenWrt 25 onward; it
|
|
# is also available opt-in on 24.10, where opkg (.ipk) remains the default. The
|
|
# .ipk package in ../openwrt-ipk/ still covers OpenWrt 24.x and earlier; this
|
|
# .apk package is what you need on 25+. Unlike the .ipk format (a plain tar.gz
|
|
# of tarballs that we
|
|
# assemble by hand in ../openwrt-ipk/build-ipk.sh), the .apk container is the
|
|
# apk-tools v3 ADB format, which is impractical to hand-roll. Instead we drive
|
|
# the official `apk mkpkg` applet — the same tool OpenWrt's build system calls
|
|
# in include/package-pack.mk — so no SDK is required, only the `apk` binary.
|
|
#
|
|
# Usage:
|
|
# ./packaging/openwrt-apk/build-apk.sh [--arch <name>]
|
|
#
|
|
# Architectures (--arch): aarch64 [default], x86_64, mipsel, mips, arm
|
|
# (the apk CI matrix ships aarch64 + x86_64; the rest are buildable locally).
|
|
#
|
|
# Output: dist/fips_<version>_<openwrt-arch>.apk
|
|
#
|
|
# Prerequisites:
|
|
# cargo install cargo-zigbuild (Rust musl cross-compilation)
|
|
# apk-tools v3 `apk` binary on PATH, or pointed at via APK_BIN=/path/to/apk
|
|
# (build from source — see README.md; CI builds apk-tools 3.0.5).
|
|
# fakeroot (optional but recommended; makes packaged files root-owned).
|
|
#
|
|
# Install on a router (packages are unsigned, like our .ipk):
|
|
# scp -O dist/fips_<version>_<arch>.apk root@192.168.1.1:/tmp/
|
|
# ssh root@192.168.1.1 apk add --allow-untrusted /tmp/fips_<version>_<arch>.apk
|
|
|
|
set -euo pipefail
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Arguments
|
|
# ---------------------------------------------------------------------------
|
|
|
|
ARCH="aarch64"
|
|
BIN_DIR="" # if set, use prebuilt binaries from here instead of compiling
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--arch) ARCH="$2"; shift 2 ;;
|
|
--arch=*) ARCH="${1#*=}"; shift ;;
|
|
--bin-dir) BIN_DIR="$2"; shift 2 ;;
|
|
--bin-dir=*) BIN_DIR="${1#*=}"; shift ;;
|
|
*) echo "Unknown argument: $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Architecture mapping
|
|
#
|
|
# RUST_TARGET — passed to cargo --target
|
|
# OPENWRT_ARCH — apk "arch:" field and the package filename
|
|
#
|
|
# Kept in sync with ../openwrt-ipk/build-ipk.sh (same target table).
|
|
# ---------------------------------------------------------------------------
|
|
|
|
case "$ARCH" in
|
|
aarch64)
|
|
RUST_TARGET="aarch64-unknown-linux-musl"
|
|
OPENWRT_ARCH="aarch64_cortex-a53"
|
|
;;
|
|
mipsel)
|
|
RUST_TARGET="mipsel-unknown-linux-musl"
|
|
OPENWRT_ARCH="mipsel_24kc"
|
|
;;
|
|
mips)
|
|
RUST_TARGET="mips-unknown-linux-musl"
|
|
OPENWRT_ARCH="mips_24kc"
|
|
;;
|
|
arm)
|
|
RUST_TARGET="arm-unknown-linux-musleabihf"
|
|
OPENWRT_ARCH="arm_cortex-a7"
|
|
;;
|
|
x86_64)
|
|
RUST_TARGET="x86_64-unknown-linux-musl"
|
|
OPENWRT_ARCH="x86_64"
|
|
;;
|
|
*)
|
|
echo "Unknown arch: $ARCH" >&2
|
|
echo "Valid: aarch64, mipsel, mips, arm, x86_64" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Paths
|
|
# ---------------------------------------------------------------------------
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
# The installed-filesystem payload (init scripts, config, sysctl, etc.) is
|
|
# shared with the .ipk package; there is one canonical copy in openwrt-ipk/.
|
|
FILES_DIR="$PROJECT_ROOT/packaging/openwrt-ipk/files"
|
|
DIST_DIR="$PROJECT_ROOT/dist"
|
|
|
|
PKG_NAME="fips"
|
|
# Human-readable version for the filename (e.g. v0.4.0 or master.123.abcdef0),
|
|
# mirroring the .ipk artifacts and the CI/NIP-94 plumbing.
|
|
PKG_VERSION="${PKG_VERSION:-$(cd "$PROJECT_ROOT" && git describe --tags --always --dirty 2>/dev/null || echo "0.1.0")}"
|
|
# apk-tools-compatible version embedded inside the package metadata.
|
|
APK_VERSION="${APK_VERSION:-$(cd "$PROJECT_ROOT" && sh "$SCRIPT_DIR/apk-version.sh" auto)}"
|
|
|
|
APK_BIN="${APK_BIN:-apk}"
|
|
if ! command -v "$APK_BIN" >/dev/null 2>&1; then
|
|
echo "Error: apk-tools binary not found (looked for '$APK_BIN')." >&2
|
|
echo " Build apk-tools v3 from source or set APK_BIN=/path/to/apk." >&2
|
|
echo " See packaging/openwrt-apk/README.md." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> Building $PKG_NAME $PKG_VERSION (apk version $APK_VERSION) for $OPENWRT_ARCH ($RUST_TARGET)"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. Obtain binaries
|
|
#
|
|
# Either use a directory of prebuilt binaries (--bin-dir; CI cross-compiles
|
|
# once in a shared job and hands them to both the .ipk and .apk packagers), or
|
|
# compile from source here for a self-contained local build.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
if [ -n "$BIN_DIR" ]; then
|
|
RELEASE_DIR="$BIN_DIR"
|
|
echo "==> Using prebuilt binaries from $RELEASE_DIR"
|
|
for bin in fips fipsctl fipstop fips-gateway; do
|
|
[ -f "$RELEASE_DIR/$bin" ] || {
|
|
echo "Error: prebuilt binary not found: $RELEASE_DIR/$bin" >&2
|
|
exit 1
|
|
}
|
|
done
|
|
else
|
|
if ! command -v cargo-zigbuild &>/dev/null; then
|
|
echo "Error: cargo-zigbuild not found." >&2
|
|
echo " Install: cargo install cargo-zigbuild" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! rustup target list --installed | grep -q "^$RUST_TARGET$"; then
|
|
echo "==> Adding Rust target $RUST_TARGET..."
|
|
rustup target add "$RUST_TARGET"
|
|
fi
|
|
|
|
echo "==> Compiling..."
|
|
cd "$PROJECT_ROOT"
|
|
cargo zigbuild \
|
|
--release \
|
|
--target "$RUST_TARGET" \
|
|
--bin fips \
|
|
--bin fipsctl \
|
|
--bin fipstop \
|
|
--bin fips-gateway
|
|
|
|
RELEASE_DIR="$PROJECT_ROOT/target/$RUST_TARGET/release"
|
|
|
|
echo "==> Stripping binaries..."
|
|
STRIP="${LLVM_STRIP:-strip}"
|
|
for bin in fips fipsctl fipstop fips-gateway; do
|
|
"$STRIP" "$RELEASE_DIR/$bin" 2>/dev/null || true
|
|
done
|
|
fi
|
|
|
|
SIZE=$(du -sh "$RELEASE_DIR/fips" | cut -f1)
|
|
echo " fips: $SIZE"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. Stage the installed filesystem tree (--files root for apk mkpkg)
|
|
# ---------------------------------------------------------------------------
|
|
# This block is the same payload as ../openwrt-ipk/build-ipk.sh; keep the two
|
|
# in sync. The CI apk structural check asserts every path below is present.
|
|
|
|
WORK_DIR="$(mktemp -d)"
|
|
trap 'rm -rf "$WORK_DIR"' EXIT
|
|
|
|
STAGE_DIR="$WORK_DIR/root" # becomes the package's filesystem
|
|
SCRIPTS_DIR="$WORK_DIR/scripts" # maintainer scripts (metadata, not payload)
|
|
mkdir -p "$STAGE_DIR" "$SCRIPTS_DIR"
|
|
|
|
install -d "$STAGE_DIR/usr/bin"
|
|
install -m 0755 "$RELEASE_DIR/fips" "$STAGE_DIR/usr/bin/fips"
|
|
install -m 0755 "$RELEASE_DIR/fipsctl" "$STAGE_DIR/usr/bin/fipsctl"
|
|
install -m 0755 "$RELEASE_DIR/fipstop" "$STAGE_DIR/usr/bin/fipstop"
|
|
install -m 0755 "$RELEASE_DIR/fips-gateway" "$STAGE_DIR/usr/bin/fips-gateway"
|
|
install -m 0755 "$FILES_DIR/usr/bin/fips-mesh-setup" "$STAGE_DIR/usr/bin/fips-mesh-setup"
|
|
|
|
install -d "$STAGE_DIR/etc/init.d"
|
|
install -m 0755 "$FILES_DIR/etc/init.d/fips" "$STAGE_DIR/etc/init.d/fips"
|
|
install -m 0755 "$FILES_DIR/etc/init.d/fips-gateway" "$STAGE_DIR/etc/init.d/fips-gateway"
|
|
|
|
install -d "$STAGE_DIR/etc/fips"
|
|
install -m 0600 "$FILES_DIR/etc/fips/fips.yaml" "$STAGE_DIR/etc/fips/fips.yaml"
|
|
install -m 0755 "$FILES_DIR/etc/fips/firewall.sh" "$STAGE_DIR/etc/fips/firewall.sh"
|
|
|
|
# The shared fips.yaml ships ethernet.wan.interface: "eth0", the OpenWrt 24
|
|
# default. This .apk package targets OpenWrt 25+ (DSA), where the WAN port is
|
|
# named "wan", so ship "wan" as the default. Patching the staged copy keeps the
|
|
# as-installed config correct for the platform without maintaining a second copy
|
|
# of the file; operators can still edit /etc/fips/fips.yaml for non-standard boards.
|
|
sed -i 's|interface: "eth0"|interface: "wan"|' "$STAGE_DIR/etc/fips/fips.yaml"
|
|
|
|
install -d "$STAGE_DIR/etc/dnsmasq.d"
|
|
install -m 0644 "$FILES_DIR/etc/dnsmasq.d/fips.conf" "$STAGE_DIR/etc/dnsmasq.d/fips.conf"
|
|
|
|
install -d "$STAGE_DIR/etc/sysctl.d"
|
|
install -m 0644 "$FILES_DIR/etc/sysctl.d/fips-bridge.conf" "$STAGE_DIR/etc/sysctl.d/fips-bridge.conf"
|
|
install -m 0644 "$FILES_DIR/etc/sysctl.d/fips-gateway.conf" "$STAGE_DIR/etc/sysctl.d/fips-gateway.conf"
|
|
|
|
install -d "$STAGE_DIR/etc/hotplug.d/net"
|
|
install -m 0755 "$FILES_DIR/etc/hotplug.d/net/99-fips" "$STAGE_DIR/etc/hotplug.d/net/99-fips"
|
|
|
|
install -d "$STAGE_DIR/etc/uci-defaults"
|
|
install -m 0755 "$FILES_DIR/etc/uci-defaults/90-fips-setup" "$STAGE_DIR/etc/uci-defaults/90-fips-setup"
|
|
|
|
install -d "$STAGE_DIR/lib/upgrade/keep.d"
|
|
install -m 0644 "$FILES_DIR/lib/upgrade/keep.d/fips" "$STAGE_DIR/lib/upgrade/keep.d/fips"
|
|
|
|
# ---- conffiles ----
|
|
# apk mkpkg discovers config files from /lib/apk/packages/<name>.conffiles
|
|
# inside the --files tree (same mechanism OpenWrt's package-pack.mk uses).
|
|
# Listing fips.yaml here makes apk preserve user edits across upgrades, the
|
|
# apk equivalent of opkg's conffiles handling.
|
|
install -d "$STAGE_DIR/lib/apk/packages"
|
|
cat > "$STAGE_DIR/lib/apk/packages/${PKG_NAME}.conffiles" <<'EOF'
|
|
/etc/fips/fips.yaml
|
|
EOF
|
|
|
|
# ---- maintainer scripts ----
|
|
# Map our opkg maintainer scripts onto apk's lifecycle phases:
|
|
# opkg postinst -> apk post-install (enable + start services)
|
|
# opkg prerm -> apk pre-deinstall (stop + disable services)
|
|
|
|
cat > "$SCRIPTS_DIR/post-install" <<'EOF'
|
|
#!/bin/sh
|
|
# Run first-boot UCI setup (the script deletes itself when done).
|
|
if [ -x /etc/uci-defaults/90-fips-setup ]; then
|
|
/etc/uci-defaults/90-fips-setup && rm -f /etc/uci-defaults/90-fips-setup
|
|
fi
|
|
|
|
/etc/init.d/fips enable
|
|
/etc/init.d/fips start
|
|
/etc/init.d/fips-gateway enable
|
|
/etc/init.d/fips-gateway start
|
|
exit 0
|
|
EOF
|
|
|
|
cat > "$SCRIPTS_DIR/pre-deinstall" <<'EOF'
|
|
#!/bin/sh
|
|
/etc/init.d/fips-gateway stop 2>/dev/null || true
|
|
/etc/init.d/fips-gateway disable 2>/dev/null || true
|
|
/etc/init.d/fips stop 2>/dev/null || true
|
|
/etc/init.d/fips disable 2>/dev/null || true
|
|
exit 0
|
|
EOF
|
|
|
|
chmod 0755 "$SCRIPTS_DIR/post-install" "$SCRIPTS_DIR/pre-deinstall"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. Assemble the .apk via apk mkpkg
|
|
# ---------------------------------------------------------------------------
|
|
# fakeroot makes the packaged files root-owned even though CI runs unprivileged.
|
|
|
|
DESCRIPTION="FIPS Mesh Network Daemon. Distributed, decentralized mesh networking over UDP, TCP, and raw Ethernet, with a TUN interface (fips0), ULA IPv6 addressing, and a .fips DNS responder."
|
|
DEPENDS="kmod-tun kmod-br-netfilter kmod-nft-nat kmod-nf-conntrack ip-full"
|
|
|
|
PKG_FILENAME="${PKG_NAME}_${PKG_VERSION}_${OPENWRT_ARCH}.apk"
|
|
mkdir -p "$DIST_DIR"
|
|
|
|
FAKEROOT=""
|
|
if command -v fakeroot >/dev/null 2>&1; then
|
|
FAKEROOT="fakeroot"
|
|
else
|
|
echo "Warning: fakeroot not found — packaged files will be owned by the build user." >&2
|
|
fi
|
|
|
|
$FAKEROOT "$APK_BIN" mkpkg \
|
|
--info "name:$PKG_NAME" \
|
|
--info "version:$APK_VERSION" \
|
|
--info "description:$DESCRIPTION" \
|
|
--info "arch:$OPENWRT_ARCH" \
|
|
--info "license:MIT" \
|
|
--info "origin:$PKG_NAME" \
|
|
--info "url:https://github.com/jmcorgan/fips" \
|
|
--info "maintainer:FIPS Network" \
|
|
--info "depends:$DEPENDS" \
|
|
--script "post-install:$SCRIPTS_DIR/post-install" \
|
|
--script "pre-deinstall:$SCRIPTS_DIR/pre-deinstall" \
|
|
--files "$STAGE_DIR" \
|
|
--output "$DIST_DIR/$PKG_FILENAME"
|
|
|
|
echo ""
|
|
echo "==> Done: dist/$PKG_FILENAME"
|
|
echo " $(du -sh "$DIST_DIR/$PKG_FILENAME" | cut -f1)"
|
|
echo ""
|
|
echo "Install on router (OpenWrt 25+, or 24.10 with apk enabled):"
|
|
echo " scp -O dist/$PKG_FILENAME root@192.168.1.1:/tmp/"
|
|
echo " ssh root@192.168.1.1 apk add --allow-untrusted /tmp/$PKG_FILENAME"
|