Add OpenWrt .ipk packaging with CI workflow

Adds packaging/openwrt/ with everything needed to produce and distribute
FIPS .ipk packages for OpenWrt routers:

- Makefile: OpenWrt feed package definition using rust/host toolchain,
  maps OpenWrt ARCH to Rust musl target triples
- build-ipk.sh: local build script using cargo-zigbuild + direct tar
  assembly — no OpenWrt SDK or Docker required. Handles macOS BSD tar
  (ustar format, resource fork suppression) and portable ar header
  generation for cross-platform .ipk creation. Accepts PKG_VERSION
  env var override for CI use.
- files/: procd init script, default fips.yaml (persistent identity,
  br-lan and wwan interface examples), firewall helper, dnsmasq .fips
  forwarding, br_netfilter sysctl, hotplug for fips0, UCI defaults for
  first-boot setup, sysupgrade keeplist
- .github/workflows/package-openwrt.yml: CI workflow (ubuntu-latest +
  cargo-zigbuild) building aarch64 and x86_64 .ipk packages using
  build-ipk.sh; llvm-strip for cross-compiled binaries; triggers on
  every push; publishes to GitHub Releases on version tags. MIPS
  targets disabled pending portable-atomic crate (32-bit MIPS lacks
  native AtomicU64), with nightly/rust-src toolchain steps prepared
  for re-enablement.
- .gitignore: add reference/, dist/, *.ipk
This commit is contained in:
origami74@gmail.com
2026-03-12 16:48:57 +00:00
committed by Johnathan Corgan
parent 6ab8b35755
commit 46ccd9744e
13 changed files with 929 additions and 1 deletions

127
.github/workflows/package-openwrt.yml vendored Normal file
View File

@@ -0,0 +1,127 @@
name: OpenWrt Package
on:
push:
workflow_dispatch:
inputs:
arch:
description: "Target architecture (leave empty to build all)"
required: false
default: ""
env:
CARGO_TERM_COLOR: always
jobs:
build:
name: Build .ipk (${{ matrix.openwrt_arch }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- build_arch: aarch64
openwrt_arch: aarch64_cortex-a53
rust_target: aarch64-unknown-linux-musl
rust_channel: stable
# MT3000, MT6000, Flint 2, RPi 3/4/5
# MIPS disabled: 32-bit MIPS lacks AtomicU64; needs portable-atomic crate
# - build_arch: mipsel
# openwrt_arch: mipsel_24kc
# rust_target: mipsel-unknown-linux-musl
# rust_channel: nightly
# - build_arch: mips
# openwrt_arch: mips_24kc
# rust_target: mips-unknown-linux-musl
# rust_channel: nightly
- build_arch: x86_64
openwrt_arch: x86_64
rust_target: x86_64-unknown-linux-musl
rust_channel: stable
# x86 routers / VMs
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Derive package version
id: version
run: |
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
VERSION="${GITHUB_REF_NAME#v}"
else
BRANCH=$(echo "$GITHUB_REF_NAME" | sed 's|/|-|g')
HEIGHT=$(git rev-list --count HEAD)
HASH=$(git rev-parse --short HEAD)
VERSION="${BRANCH}.${HEIGHT}.${HASH}"
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "filename=fips_${VERSION}_${{ matrix.openwrt_arch }}.ipk" >> "$GITHUB_OUTPUT"
- name: Install Rust toolchain (stable)
if: matrix.rust_channel == 'stable'
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.rust_target }}
- name: Install Rust toolchain (nightly, Tier 3)
if: matrix.rust_channel == 'nightly'
uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
- name: Cache Cargo registry + build
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target/${{ matrix.rust_target }}
key: openwrt-${{ matrix.rust_target }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
openwrt-${{ matrix.rust_target }}-
- name: Install cargo-zigbuild
run: cargo install cargo-zigbuild --locked
- name: Install zig (required by cargo-zigbuild)
uses: goto-bus-stop/setup-zig@v2
- name: Install llvm-strip
run: sudo apt-get install -y --no-install-recommends llvm
- name: Build .ipk
env:
PKG_VERSION: ${{ steps.version.outputs.version }}
LLVM_STRIP: llvm-strip
run: ./packaging/openwrt/build-ipk.sh --arch ${{ matrix.build_arch }}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.version.outputs.filename }}
path: dist/${{ steps.version.outputs.filename }}
retention-days: 30
release:
name: Publish GitHub Release
runs-on: ubuntu-latest
needs: build
if: startsWith(github.ref, 'refs/tags/')
permissions:
contents: write
steps:
- name: Download all .ipk artifacts
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Create release
uses: softprops/action-gh-release@v2
with:
files: dist/*.ipk
generate_release_notes: true

7
.gitignore vendored
View File

@@ -15,4 +15,9 @@
.claude/
deploy/
vps.env
vps.env
reference/
dist/
*.ipk

127
packaging/openwrt/Makefile Normal file
View File

@@ -0,0 +1,127 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=fips
PKG_VERSION:=0.1.0
PKG_RELEASE:=1
# Pin to a specific commit for reproducible builds.
# Update PKG_SOURCE_VERSION and PKG_MIRROR_HASH when upgrading.
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/fips-network/fips.git
PKG_SOURCE_VERSION:=master
PKG_MIRROR_HASH:=skip
PKG_MAINTAINER:=FIPS Network
PKG_LICENSE:=MIT
PKG_LICENSE_FILES:=LICENSE
# Rust host toolchain must be present in the build system.
# In the OpenWrt build system, enable via: make menuconfig → Advanced → Rust
PKG_BUILD_DEPENDS:=rust/host
PKG_BUILD_PARALLEL:=1
include $(INCLUDE_DIR)/package.mk
# ---------------------------------------------------------------------------
# Map OpenWrt ARCH to Rust target triple.
# All OpenWrt targets use musl libc.
# ---------------------------------------------------------------------------
ifeq ($(ARCH),aarch64)
RUST_TARGET:=aarch64-unknown-linux-musl
else ifeq ($(ARCH),x86_64)
RUST_TARGET:=x86_64-unknown-linux-musl
else ifeq ($(ARCH),mipsel)
RUST_TARGET:=mipsel-unknown-linux-musl
else ifeq ($(ARCH),mips)
RUST_TARGET:=mips-unknown-linux-musl
else ifeq ($(ARCH),arm)
# OpenWrt ARM targets predominantly use hardfloat ABI.
# Override RUST_TARGET in your build if your target uses softfloat.
RUST_TARGET:=arm-unknown-linux-musleabihf
else
$(error Unsupported architecture: $(ARCH). Add a RUST_TARGET mapping in packaging/openwrt/Makefile.)
endif
RUST_RELEASE_DIR:=$(PKG_BUILD_DIR)/target/$(RUST_TARGET)/release
define Package/fips
SECTION:=net
CATEGORY:=Network
TITLE:=FIPS Mesh Network Daemon
URL:=https://github.com/fips-network/fips
DEPENDS:=+kmod-tun +kmod-br-netfilter
endef
define Package/fips/description
FIPS is a distributed, decentralized mesh networking daemon. It routes
traffic across nodes connected over UDP, TCP, or raw Ethernet (EtherType
0x2121). Provides a TUN interface (fips0) with ULA IPv6 mesh addressing
and a local DNS responder for .fips name resolution.
Three binaries are installed:
fips — mesh daemon
fipsctl — CLI control and inspection tool
fipstop — live TUI dashboard (requires a terminal)
endef
# ---------------------------------------------------------------------------
# Build
#
# We write a temporary .cargo/config.toml to set the cross-linker without
# polluting the source tree. CARGO_HOME is scoped to the staging directory
# to avoid touching the developer's ~/.cargo during SDK builds.
# ---------------------------------------------------------------------------
define Build/Compile
mkdir -p $(PKG_BUILD_DIR)/.cargo
printf '[target.$(RUST_TARGET)]\nlinker = "$(TARGET_CC)"\n' \
> $(PKG_BUILD_DIR)/.cargo/config.toml
cd $(PKG_BUILD_DIR) && \
CARGO_HOME=$(STAGING_DIR_HOST)/share/cargo \
cargo build \
--release \
--target $(RUST_TARGET) \
--bin fips \
--bin fipsctl \
--bin fipstop
endef
define Package/fips/install
# Binaries
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(RUST_RELEASE_DIR)/fips $(1)/usr/bin/fips
$(INSTALL_BIN) $(RUST_RELEASE_DIR)/fipsctl $(1)/usr/bin/fipsctl
$(INSTALL_BIN) $(RUST_RELEASE_DIR)/fipstop $(1)/usr/bin/fipstop
# procd init script
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) $(CURDIR)/files/etc/init.d/fips $(1)/etc/init.d/fips
# Default config — installed as CONF so opkg will not overwrite it on upgrade
$(INSTALL_DIR) $(1)/etc/fips
$(INSTALL_CONF) $(CURDIR)/files/etc/fips/fips.yaml $(1)/etc/fips/fips.yaml
# Firewall helper script (called by UCI include and hotplug)
$(INSTALL_BIN) $(CURDIR)/files/etc/fips/firewall.sh $(1)/etc/fips/firewall.sh
# dnsmasq drop-in: forward .fips queries to the FIPS DNS responder
$(INSTALL_DIR) $(1)/etc/dnsmasq.d
$(INSTALL_DATA) $(CURDIR)/files/etc/dnsmasq.d/fips.conf $(1)/etc/dnsmasq.d/fips.conf
# sysctl: enable br_netfilter so AF_PACKET sees frames on bridge member ports
$(INSTALL_DIR) $(1)/etc/sysctl.d
$(INSTALL_DATA) $(CURDIR)/files/etc/sysctl.d/fips-bridge.conf $(1)/etc/sysctl.d/fips-bridge.conf
# Hotplug: apply firewall rules when the FIPS TUN interface comes up
$(INSTALL_DIR) $(1)/etc/hotplug.d/net
$(INSTALL_BIN) $(CURDIR)/files/etc/hotplug.d/net/99-fips $(1)/etc/hotplug.d/net/99-fips
# UCI defaults: one-time first-boot firewall and module setup
$(INSTALL_DIR) $(1)/etc/uci-defaults
$(INSTALL_BIN) $(CURDIR)/files/etc/uci-defaults/90-fips-setup $(1)/etc/uci-defaults/90-fips-setup
# sysupgrade: preserve /etc/fips/ (config + identity key) across firmware upgrades
$(INSTALL_DIR) $(1)/lib/upgrade/keep.d
$(INSTALL_DATA) $(CURDIR)/files/lib/upgrade/keep.d/fips $(1)/lib/upgrade/keep.d/fips
endef
$(eval $(call BuildPackage,fips))

174
packaging/openwrt/README.md Normal file
View File

@@ -0,0 +1,174 @@
# FIPS OpenWrt Package
This directory is an OpenWrt feed package that builds and installs FIPS on any
OpenWrt 22.03+ router via the standard `opkg` package system.
For ad-hoc deployment without the build system, see
[`deploy/native/`](../../deploy/native/README.md) instead.
## Package contents
| Installed path | Purpose |
|---|---|
| `/usr/bin/fips` | Mesh daemon |
| `/usr/bin/fipsctl` | CLI control tool (`fipsctl peers`, `fipsctl links`, …) |
| `/usr/bin/fipstop` | Live TUI dashboard |
| `/etc/init.d/fips` | procd service (auto-start, crash respawn) |
| `/etc/fips/fips.yaml` | Node configuration (edit before first start) |
| `/etc/fips/firewall.sh` | Firewall helper — accepts traffic on `fips0` |
| `/etc/dnsmasq.d/fips.conf` | Forwards `.fips` DNS queries to the daemon |
| `/etc/sysctl.d/fips-bridge.conf` | `br_netfilter` settings for Ethernet transport |
| `/etc/hotplug.d/net/99-fips` | Applies firewall rules when `fips0` comes up |
| `/etc/uci-defaults/90-fips-setup` | First-boot kernel module and firewall setup |
| `/lib/upgrade/keep.d/fips` | Preserves `/etc/fips/` across `sysupgrade` |
## Requirements
### Build host
| Requirement | Notes |
|---|---|
| OpenWrt SDK 22.03+ | Older versions lack fw4 / nftables support |
| Rust host toolchain | Enable in `make menuconfig` → Advanced → Rust, or install rustup |
| Rust target for your router | Added automatically by the Makefile via `rustup target add` |
### Router
| Requirement | Notes |
|---|---|
| `kmod-tun` | Required for `fips0` TUN interface |
| `kmod-br-netfilter` | Required for Ethernet transport on bridge member ports |
Both kernel modules are listed as package dependencies (`DEPENDS`) and will be
installed automatically by `opkg`.
## Target architectures
The Makefile maps the OpenWrt `ARCH` variable to the correct Rust musl target:
| OpenWrt `ARCH` | Rust target |
|---|---|
| `aarch64` | `aarch64-unknown-linux-musl` |
| `x86_64` | `x86_64-unknown-linux-musl` |
| `mipsel` | `mipsel-unknown-linux-musl` |
| `mips` | `mips-unknown-linux-musl` |
| `arm` | `arm-unknown-linux-musleabihf` |
To add a missing architecture, add an `ifeq` block in `Makefile` mapping the
OpenWrt `ARCH` value to the Rust target triple.
## Building with the OpenWrt SDK
### 1. Obtain the SDK
Download the SDK for your router's target from
[downloads.openwrt.org](https://downloads.openwrt.org) and extract it.
### 2. Add this package
Copy or symlink this directory into the SDK's `package/` tree:
```bash
# From inside the SDK root:
ln -s /path/to/fips/packaging/openwrt package/fips
```
Or add the FIPS repository as a feed in `feeds.conf`:
```
src-git-full fips https://github.com/fips-network/fips.git
```
Then update and install feeds:
```bash
./scripts/feeds update fips
./scripts/feeds install -a -p fips
```
### 3. Build
```bash
make package/fips/compile V=s
```
The resulting `.ipk` is placed in `bin/packages/<arch>/`.
### 4. Pin the source version
For reproducible production builds, replace `PKG_SOURCE_VERSION:=master` in
`Makefile` with a specific commit SHA and set `PKG_MIRROR_HASH` to the correct
hash (or keep `skip` for development):
```makefile
PKG_SOURCE_VERSION:=bf117dfabc123... # full 40-char SHA
PKG_MIRROR_HASH:=skip
```
## Installing on the router
```bash
scp bin/packages/<arch>/fips_0.1.0-1_<arch>.ipk root@192.168.1.1:/tmp/
ssh root@192.168.1.1 opkg install /tmp/fips_0.1.0-1_<arch>.ipk
```
## First-time configuration
Edit `/etc/fips/fips.yaml` on the router before starting the daemon:
```bash
ssh root@192.168.1.1
vi /etc/fips/fips.yaml
```
The default config enables:
- Persistent identity (key generated on first start, saved to `/etc/fips/fips.key`)
- TUN interface `fips0`
- DNS responder on `127.0.0.1:5354`
- UDP transport on `0.0.0.0:2121`
For Ethernet transport, uncomment the `ethernet:` section and set the correct
physical interface names for your router. **Always use physical port names
(`eth0`, `eth1`), never bridge names (`br-lan`).** See
[`deploy/native/README.md`](../../deploy/native/README.md) for details.
## Service management
```bash
/etc/init.d/fips start
/etc/init.d/fips stop
/etc/init.d/fips restart
/etc/init.d/fips enable # start at boot (already enabled by opkg postinstall)
/etc/init.d/fips disable
```
## Inspection and logs
```bash
# Peer table
fipsctl peers
# Active sessions
fipsctl sessions
# Transport links
fipsctl links
# Live TUI dashboard
fipstop
# Daemon logs (OpenWrt syslog)
logread | grep fips
```
## Upgrading
Install the new `.ipk` over the existing one:
```bash
opkg install --force-reinstall fips_<new-version>_<arch>.ipk
```
The config in `/etc/fips/fips.yaml` and the identity key `/etc/fips/fips.key`
are preserved by `opkg` (the yaml is installed as a conffile; the key is not a
package file). Both survive `sysupgrade` via `/lib/upgrade/keep.d/fips`.

265
packaging/openwrt/build-ipk.sh Executable file
View File

@@ -0,0 +1,265 @@
#!/bin/bash
# Build a FIPS .ipk package for OpenWrt without the OpenWrt SDK.
#
# Uses cargo-zigbuild for cross-compilation and assembles the .ipk directly.
# An .ipk is just an ar archive containing two tarballs — no SDK required.
#
# Usage:
# ./packaging/openwrt/build-ipk.sh [--arch <name>]
#
# Architectures (--arch):
# aarch64 GL.iNet MT3000/MT6000, RPi 3/4/5, most modern routers [default]
# mipsel Older MIPS routers (TP-Link, Netgear, GL.iNet AR750)
# mips MIPS big-endian routers (ath79)
# arm 32-bit ARM routers (Cortex-A7)
# x86_64 x86 routers / VMs
#
# Output: dist/fips_<version>_<openwrt-arch>.ipk
#
# Prerequisites:
# cargo install cargo-zigbuild
# rustup target add <rust-triple> (added automatically if missing)
set -euo pipefail
# ---------------------------------------------------------------------------
# Arguments
# ---------------------------------------------------------------------------
ARCH="aarch64"
while [[ $# -gt 0 ]]; do
case "$1" in
--arch) ARCH="$2"; shift 2 ;;
--arch=*) ARCH="${1#*=}"; shift ;;
*) echo "Unknown argument: $1" >&2; exit 1 ;;
esac
done
# ---------------------------------------------------------------------------
# Architecture mapping
#
# RUST_TARGET — passed to cargo --target
# OPENWRT_ARCH — goes in the .ipk control file and filename
# ---------------------------------------------------------------------------
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)"
FILES_DIR="$SCRIPT_DIR/files"
DIST_DIR="$PROJECT_ROOT/dist"
PKG_NAME="fips"
PKG_VERSION="${PKG_VERSION:-$(cd "$PROJECT_ROOT" && git describe --tags --always --dirty 2>/dev/null || echo "0.1.0")}"
echo "==> Building $PKG_NAME $PKG_VERSION for $OPENWRT_ARCH ($RUST_TARGET)"
# ---------------------------------------------------------------------------
# Prerequisites
# ---------------------------------------------------------------------------
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
# ---------------------------------------------------------------------------
# 1. Build
# ---------------------------------------------------------------------------
echo "==> Compiling..."
cd "$PROJECT_ROOT"
cargo zigbuild \
--release \
--target "$RUST_TARGET" \
--bin fips \
--bin fipsctl \
--bin fipstop
RELEASE_DIR="$PROJECT_ROOT/target/$RUST_TARGET/release"
echo "==> Stripping binaries..."
STRIP="${LLVM_STRIP:-strip}"
for bin in fips fipsctl fipstop; do
"$STRIP" "$RELEASE_DIR/$bin" 2>/dev/null || true
done
SIZE=$(du -sh "$RELEASE_DIR/fips" | cut -f1)
echo " fips: $SIZE after strip"
# ---------------------------------------------------------------------------
# 2. Assemble .ipk
# ---------------------------------------------------------------------------
# An .ipk is an ar archive with three members:
# debian-binary — format version ("2.0\n")
# control.tar.gz — package metadata, conffiles, pre/post scripts
# data.tar.gz — the actual filesystem tree
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
CONTROL_DIR="$WORK_DIR/control"
DATA_DIR="$WORK_DIR/data"
mkdir -p "$CONTROL_DIR" "$DATA_DIR"
# ---- data tree ----
install -d "$DATA_DIR/usr/bin"
install -m 0755 "$RELEASE_DIR/fips" "$DATA_DIR/usr/bin/fips"
install -m 0755 "$RELEASE_DIR/fipsctl" "$DATA_DIR/usr/bin/fipsctl"
install -m 0755 "$RELEASE_DIR/fipstop" "$DATA_DIR/usr/bin/fipstop"
install -d "$DATA_DIR/etc/init.d"
install -m 0755 "$FILES_DIR/etc/init.d/fips" "$DATA_DIR/etc/init.d/fips"
install -d "$DATA_DIR/etc/fips"
install -m 0600 "$FILES_DIR/etc/fips/fips.yaml" "$DATA_DIR/etc/fips/fips.yaml"
install -m 0755 "$FILES_DIR/etc/fips/firewall.sh" "$DATA_DIR/etc/fips/firewall.sh"
install -d "$DATA_DIR/etc/dnsmasq.d"
install -m 0644 "$FILES_DIR/etc/dnsmasq.d/fips.conf" "$DATA_DIR/etc/dnsmasq.d/fips.conf"
install -d "$DATA_DIR/etc/sysctl.d"
install -m 0644 "$FILES_DIR/etc/sysctl.d/fips-bridge.conf" "$DATA_DIR/etc/sysctl.d/fips-bridge.conf"
install -d "$DATA_DIR/etc/hotplug.d/net"
install -m 0755 "$FILES_DIR/etc/hotplug.d/net/99-fips" "$DATA_DIR/etc/hotplug.d/net/99-fips"
install -d "$DATA_DIR/etc/uci-defaults"
install -m 0755 "$FILES_DIR/etc/uci-defaults/90-fips-setup" "$DATA_DIR/etc/uci-defaults/90-fips-setup"
install -d "$DATA_DIR/lib/upgrade/keep.d"
install -m 0644 "$FILES_DIR/lib/upgrade/keep.d/fips" "$DATA_DIR/lib/upgrade/keep.d/fips"
# ---- control files ----
PKG_SIZE=$(du -sk "$DATA_DIR" | cut -f1)
cat > "$CONTROL_DIR/control" <<EOF
Package: $PKG_NAME
Version: $PKG_VERSION
Architecture: $OPENWRT_ARCH
Maintainer: FIPS Network
Section: net
Priority: optional
Depends: kmod-tun, kmod-br-netfilter
Description: FIPS Mesh Network Daemon
Distributed, decentralized mesh networking over UDP, TCP, and raw Ethernet.
Provides a TUN interface (fips0) with ULA IPv6 addressing and a DNS
responder for .fips name resolution.
Installed-Size: $PKG_SIZE
EOF
# Mark fips.yaml as a conffile so opkg won't overwrite user edits on upgrade.
cat > "$CONTROL_DIR/conffiles" <<EOF
/etc/fips/fips.yaml
EOF
cat > "$CONTROL_DIR/postinst" <<'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
exit 0
EOF
chmod 0755 "$CONTROL_DIR/postinst"
cat > "$CONTROL_DIR/prerm" <<'EOF'
#!/bin/sh
/etc/init.d/fips stop 2>/dev/null || true
/etc/init.d/fips disable 2>/dev/null || true
exit 0
EOF
chmod 0755 "$CONTROL_DIR/prerm"
# ---- pack ----
PKG_FILENAME="${PKG_NAME}_${PKG_VERSION}_${OPENWRT_ARCH}.ipk"
IPK_WORK="$WORK_DIR/ipk"
mkdir -p "$IPK_WORK"
echo "2.0" > "$IPK_WORK/debian-binary"
# Detect a tar that supports --format=gnu.
# On macOS, Homebrew's GNU tar is installed as 'gtar'; the system tar is BSD.
# Our filenames are short so BSD tar (ustar) works too, but gnu is preferred
# to match ipkg-build exactly and to embed numeric UID/GID.
# COPYFILE_DISABLE=1 suppresses macOS resource-fork (._*) files; no-op on Linux.
if command -v gtar &>/dev/null; then
# Homebrew GNU tar on macOS
TAR_CMD="gtar"
TAR_EXTRA_FLAGS="--format=gnu --numeric-owner"
elif tar --version 2>/dev/null | grep -q 'GNU tar'; then
# System tar is GNU tar (Linux)
TAR_CMD="tar"
TAR_EXTRA_FLAGS="--format=gnu --numeric-owner"
else
# macOS BSD tar (libarchive). Its default format is PAX (typeflag 0x78),
# which OpenWrt's busybox tar cannot handle. Force ustar explicitly.
TAR_CMD="tar"
TAR_EXTRA_FLAGS="--format=ustar"
fi
ipk_tar() {
# ipk_tar <output.tar.gz> <source-dir> [paths...]
local out="$1" src="$2"; shift 2
COPYFILE_DISABLE=1 "$TAR_CMD" $TAR_EXTRA_FLAGS -czf "$out" -C "$src" "$@"
}
ipk_tar "$IPK_WORK/control.tar.gz" "$CONTROL_DIR" .
ipk_tar "$IPK_WORK/data.tar.gz" "$DATA_DIR" .
# The outer .ipk container is a gzip-compressed tar — NOT an ar archive.
# (Debian .deb uses ar; OpenWrt .ipk uses tar.gz.)
# Entries must be named with ./ prefix, as ipkg-build produces.
mkdir -p "$DIST_DIR"
ipk_tar "$DIST_DIR/$PKG_FILENAME" "$IPK_WORK" ./debian-binary ./control.tar.gz ./data.tar.gz
echo ""
echo "==> Done: dist/$PKG_FILENAME"
echo " $(du -sh "$DIST_DIR/$PKG_FILENAME" | cut -f1)"
echo ""
echo "Install on router:"
echo " scp -O dist/$PKG_FILENAME root@192.168.1.1:/tmp/"
echo " ssh root@192.168.1.1 opkg install /tmp/$PKG_FILENAME"

View File

@@ -0,0 +1,11 @@
# FIPS mesh DNS — forward .fips queries to the local FIPS DNS responder.
#
# server= forward all .fips queries to 127.0.0.1:5354
# rebind-domain-ok= disable DNS-rebind protection for .fips; FIPS node
# addresses live in fd00::/8 (ULA), which dnsmasq blocks by
# default as a rebind-attack countermeasure.
#
# If your fips.yaml sets dns.port to something other than 5354, update the
# port number below to match.
server=/fips/127.0.0.1#5354
rebind-domain-ok=/fips/

View File

@@ -0,0 +1,64 @@
# FIPS Node Configuration
#
# Edit this file before starting the FIPS daemon.
# Full reference: https://github.com/jmcorgan/fips/blob/master/docs/design/fips-configuration.md
#
# After editing, restart the daemon:
# /etc/init.d/fips restart
node:
identity:
# A persistent keypair is generated on first start and saved to
# /etc/fips/fips.key (private) and /etc/fips/fips.pub (public).
# The router keeps the same FIPS identity across reboots.
#
# To use an explicit key instead, comment out 'persistent' and set:
# nsec: "your-64-char-hex-private-key"
persistent: true
tun:
enabled: true
name: fips0
mtu: 1280
dns:
enabled: true
bind_addr: "127.0.0.1"
port: 5354
transports:
udp:
bind_addr: "0.0.0.0:2121"
# Ethernet transport — uncomment and set your interface names.
# Use physical port names, NOT bridge names (e.g. eth1, not br-lan).
# Run 'ip link show' on the router to identify port names.
# See: https://github.com/jmcorgan/fips/blob/master/deploy/native/README.md
#
# ethernet:
# wan:
# interface: "eth0" # WAN
# discovery: true
# announce: true
# auto_connect: true
# accept_connections: true
# wwan:
# interface: "phy0-sta0" # WWAN
# discovery: true
# announce: true
# auto_connect: true
# accept_connections: true
# lan:
# interface: "br-lan" # LAN
# discovery: true
# announce: true
# auto_connect: true
# accept_connections: true
peers: []
# Static peers for bootstrapping (add at least one if using UDP-only):
# - npub: "npub1..."
# alias: "gateway"
# addresses:
# - transport: udp
# addr: "1.2.3.4:2121"

View File

@@ -0,0 +1,33 @@
#!/bin/sh
# FIPS firewall rules — accept all traffic on the FIPS TUN interface.
#
# Called by:
# - /etc/hotplug.d/net/99-fips when fips0 comes up
# - the UCI firewall include on every firewall reload
#
# Supports both fw4 (nftables, OpenWrt 22.03+) and older iptables builds.
TUN="fips0"
if command -v nft >/dev/null 2>&1 && nft list table inet fw4 >/dev/null 2>&1; then
for chain in input output forward; do
case "$chain" in
input|forward) match="iifname \"$TUN\"" ;;
output) match="oifname \"$TUN\"" ;;
esac
nft list chain inet fw4 "$chain" 2>/dev/null | grep -q "$match" || \
nft insert rule inet fw4 "$chain" $match accept comment "\"fips\""
done
# Also accept forwarded traffic leaving via fips0
nft list chain inet fw4 forward 2>/dev/null | grep -q "oifname \"$TUN\"" || \
nft insert rule inet fw4 forward oifname "$TUN" accept comment '"fips"'
fi
if command -v iptables >/dev/null 2>&1; then
iptables -C INPUT -i "$TUN" -j ACCEPT 2>/dev/null || \
iptables -I INPUT 1 -i "$TUN" -j ACCEPT
iptables -C OUTPUT -o "$TUN" -j ACCEPT 2>/dev/null || \
iptables -I OUTPUT 1 -o "$TUN" -j ACCEPT
iptables -C FORWARD -i "$TUN" -j ACCEPT 2>/dev/null || \
iptables -I FORWARD 1 -i "$TUN" -j ACCEPT
fi

View File

@@ -0,0 +1,8 @@
#!/bin/sh
# FIPS hotplug — apply firewall rules when the FIPS TUN interface comes up.
# The kernel fires net hotplug events for every interface including TUN devices.
[ "$INTERFACE" = "fips0" ] || exit 0
[ "$ACTION" = "add" ] || [ "$ACTION" = "register" ] || exit 0
/etc/fips/firewall.sh

View File

@@ -0,0 +1,28 @@
#!/bin/sh /etc/rc.common
# FIPS mesh daemon — procd init script for OpenWrt
USE_PROCD=1
START=95
STOP=10
PROG=/usr/bin/fips
CONFIG=/etc/fips/fips.yaml
start_service() {
# Ensure TUN module is loaded before starting the daemon.
modprobe tun 2>/dev/null || true
procd_open_instance
procd_set_param command "$PROG" --config "$CONFIG"
procd_set_param env RUST_LOG=info
# Respawn: restart after 5 s, give up after 5 consecutive failures within
# a 3600 s window, then reset the failure counter and try again.
procd_set_param respawn 3600 5 5
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance
}
reload_service() {
restart
}

View File

@@ -0,0 +1,12 @@
# FIPS: bridge netfilter settings
#
# kmod-br-netfilter must be loaded for AF_PACKET sockets to receive frames
# on bridge member ports (e.g. eth1 when it is a member of br-lan).
# Without it, the bridge's rx_handler intercepts frames before they reach
# the packet socket layer.
#
# We load br_netfilter for the AF_PACKET visibility benefit but disable its
# IP/IPv6/ARP call hooks to avoid double-processing of routed traffic.
net.bridge.bridge-nf-call-iptables=0
net.bridge.bridge-nf-call-ip6tables=0
net.bridge.bridge-nf-call-arptables=0

View File

@@ -0,0 +1,72 @@
#!/bin/sh
# FIPS first-boot setup — runs once after package installation.
# Configures the firewall and kernel modules for FIPS operation.
# This script is executed by /etc/rc.d/S19sysctl on first boot and
# then deleted by the UCI defaults mechanism.
# ---------------------------------------------------------------------------
# 1. Kernel modules
# ---------------------------------------------------------------------------
# kmod-tun is listed as a package dependency, but ensure the module is loaded.
modprobe tun 2>/dev/null || true
echo "tun" > /etc/modules.d/tun
# kmod-br-netfilter makes AF_PACKET visible on bridge member ports.
modprobe br_netfilter 2>/dev/null || true
echo "br_netfilter" > /etc/modules.d/br-netfilter
# Apply the sysctl settings shipped in /etc/sysctl.d/fips-bridge.conf now
# (the file will be applied automatically on subsequent boots).
sysctl -p /etc/sysctl.d/fips-bridge.conf 2>/dev/null || true
# ---------------------------------------------------------------------------
# 2. Firewall — add fips0 to the lan zone (fw4 / UCI)
# ---------------------------------------------------------------------------
# fw4 (nftables) matches zones by device name, so adding fips0 as a 'device'
# to the lan zone makes all traffic on the TUN interface accepted without
# needing raw nft rules in the base chains.
z=0
while uci -q get "firewall.@zone[$z]" >/dev/null 2>&1; do
if [ "$(uci -q get "firewall.@zone[$z].name" 2>/dev/null)" = "lan" ]; then
uci -q del_list "firewall.@zone[$z].device=fips0" 2>/dev/null || true
uci add_list "firewall.@zone[$z].device=fips0"
break
fi
z=$((z + 1))
done
# Install a firewall include so /etc/fips/firewall.sh is re-applied on every
# firewall reload (belt-and-suspenders for iptables-based OpenWrt builds).
FOUND=0
j=0
while uci -q get "firewall.@include[$j]" >/dev/null 2>&1; do
if [ "$(uci -q get "firewall.@include[$j].path" 2>/dev/null)" = "/etc/fips/firewall.sh" ]; then
FOUND=1
break
fi
j=$((j + 1))
done
if [ "$FOUND" = "0" ]; then
uci add firewall include
uci set "firewall.@include[-1].path=/etc/fips/firewall.sh"
uci set "firewall.@include[-1].reload=1"
fi
uci commit firewall
# ---------------------------------------------------------------------------
# 3. dnsmasq UCI registration
# ---------------------------------------------------------------------------
# /etc/dnsmasq.d/fips.conf already handles runtime forwarding.
# Register via UCI as well so the settings survive a full dnsmasq config
# regeneration (e.g. after a firmware upgrade that rebuilds dnsmasq.conf).
uci -q del_list dhcp.@dnsmasq[0].server="/fips/127.0.0.1#5354" 2>/dev/null || true
uci add_list dhcp.@dnsmasq[0].server="/fips/127.0.0.1#5354"
uci -q del_list dhcp.@dnsmasq[0].rebind_domain="fips" 2>/dev/null || true
uci add_list dhcp.@dnsmasq[0].rebind_domain="fips"
uci commit dhcp
exit 0

View File

@@ -0,0 +1,2 @@
# Preserve FIPS configuration and persistent identity key across sysupgrade.
/etc/fips/