Make Nostr-mediated overlay discovery unconditional, mirroring the
philosophy of PR #79's collapse of the tui/ble/gateway features in
favor of platform cfg gates. nostr / nostr-sdk are pure Rust over
WebSockets/TCP, so they build cleanly on every FIPS-supported
platform — there is no need for the parallel feature gate.
The flag was already in `default = [...]`, so no behavior change for
anyone using `cargo build` without `--no-default-features`. Operators
who explicitly disabled the feature will now find Nostr code present
in the binary; the runtime check `node.discovery.nostr.enabled` still
controls whether the runtime starts.
Cargo.toml:
- Remove the `[features]` table entirely.
- Drop `optional = true` from `nostr` and `nostr-sdk`.
Source: 27 cfg sites collapsed across 5 files —
`src/discovery.rs`, `src/discovery/nostr/mod.rs`,
`src/node/handlers/rx_loop.rs`, `src/node/lifecycle.rs`,
`src/node/mod.rs`. Two `#[cfg(not(feature = "nostr-discovery"))]`
fallback blocks (the udp:nat-without-runtime debug-log path and the
"feature not compiled in" warning at startup) were removed as dead
code; the always-on path already handles the missing-runtime case
via `nostr_discovery: Option<NostrDiscovery>`.
Packaging and tooling:
- `packaging/openwrt-ipk/Makefile`: drop a stale `--features gateway`
flag (the `gateway` feature was already removed in PR #79; this
was a leftover that the build path tolerated only because cargo
ignored unknown feature names).
- `testing/scripts/build.sh`: drop `DEFAULT_CARGO_BUILD_ARGS=(--features
nostr-discovery)`; defaults are empty.
- `packaging/common/fips.yaml`: drop the "requires the
nostr-discovery feature" comment from the discovery section.
Bundled cleanup:
- Apply `cargo clippy --fix` against three pre-existing warnings in
`src/discovery/nostr/runtime.rs` and `src/discovery/nostr/stun.rs`
(collapsed `if let Some` chain; two redundant `as i32` casts).
These were always present but masked when the feature gate was
off; they surface now that the code is unconditionally compiled.
- `cargo fmt` settled two minor formatting drift sites in
`src/bin/fips-gateway.rs` and `src/config/mod.rs`.
Tests: 1083 passed, 0 failed, 4 ignored. clippy clean. fmt clean.
The previous default configured systemd-resolved with `resolvectl dns
fips0 [<fips0_addr>]:5354`, intended to bypass an Ubuntu 22 systemd 249
interface-scoping bug. That target collides with the daemon's
mesh-interface filter on Linux: when an IPv6 packet's destination
belongs to a non-loopback interface, the kernel attributes the packet
to that interface in IPV6_PKTINFO (ipi6_ifindex == fips0) even though
loopback delivery is used (tcpdump shows lo). The mesh-interface filter
sees arrival_ifindex == mesh_ifindex and silently drops every query at
trace level — invisible to operators at the default debug level.
Net effect on stock deployments: every .fips query on systemd-resolved
hosts was silently dropped.
Daemon side
-----------
- Default `dns.bind_addr` changes from "::" to "::1" (IPv6 loopback
only). The mesh-interface filter is then defanged on the default
path because loopback isn't reachable from mesh peers. The filter
remains in place defensively for operators who explicitly bind "::"
to expose a mesh-reachable responder.
fips-dns-setup backend unification
----------------------------------
- New `try_global_drop_in` backend writes
/etc/systemd/resolved.conf.d/fips.conf with DNS=[::1]:5354 and
Domains=~fips. Inserted ahead of `try_resolvectl` in the dispatch
chain. The standard loopback path has no interface scoping, so
ipi6_ifindex reports lo and the filter passes.
- All other backends now target [::1]:5354 to match the daemon's
default IPv6-loopback bind:
- try_dns_delegate writes DNS=[::1]:5354
- try_dnsmasq writes server=/fips/::1#5354
- try_nm_dnsmasq writes server=/fips/::1#5354
- Fixed dns-delegate file path: was /etc/systemd/dns-delegate/, must
be /etc/systemd/dns-delegate.d/ (with .d suffix). systemd-resolved
silently ignored the previous path.
- fips-dns-teardown handles the new global-drop-in backend in cleanup.
- The legacy resolvectl per-link backend stays as a fallback,
documented to require careful daemon bind_addr coordination.
fips-gateway upstream pairing
-----------------------------
- gateway.dns.upstream default changes from 127.0.0.1:5354 to
[::1]:5354 to match the daemon's default bind. Linux IPv6 sockets
bound to explicit ::1 do not accept v4-mapped traffic, so the old
default would have caused the gateway's startup DNS reachability
probe to time out and systemd to restart-loop the service.
- Operators who set a non-default daemon `dns.bind_addr` must also
set `gateway.dns.upstream` to match — documented inline.
Documentation
-------------
- packaging/common/fips.yaml and packaging/openwrt-ipk fips.yaml
examples updated; rationale for the bind_addr choice and the
daemon/gateway pairing recorded inline.
Test coverage
-------------
- testing/dns-resolver/test.sh: real-fipsd end-to-end scenario added.
Builds fipsd in a Debian 12 builder image (cached), runs the daemon
with a real TUN in a privileged container, configures DNS via the
setup script, and asserts `dig @127.0.0.53 AAAA <npub>.fips` returns
AAAA. Refactored as a parameterized helper running across Debian
12/13 and Ubuntu 22/24/26 (5 e2e scenarios). Backend-aware
assertions: on systemd >= 258 the expected backend is dns-delegate;
on older systemd it's global-drop-in. Strict content checks fail CI
on any [::1]:5354 drift. fips-gateway also exercised in the
debian12 scenario to lock the gateway-upstream pairing. Renamed all
"fipsd" references to "fips" (project convention).
- testing/deb-install/ (new harness): builds the actual .deb via
cargo-deb in a Debian 12 builder image (cached), installs via apt
across each target distro, verifies maintainer scripts, conffile
placement, binary placement, and end-to-end .fips resolution after
start. Also exercises fips-gateway against the installed daemon to
verify the gateway/daemon default pairing on a real .deb path.
- This is the test layer that was missing — the previous harness only
verified config files were written, never that queries reached the
daemon.
Verified: dns-resolver 78/78 assertions, deb-install 55/55 assertions
across all 5 distros (debian:12, debian:trixie, ubuntu:22.04,
ubuntu:24.04, ubuntu:26.04).
Drop the `tui`, `ble`, and `gateway` cargo features and replace
them with platform cfg gates. Plain `cargo build` now produces
every subsystem appropriate for the target platform with no
feature flags required.
Motivation:
- `default = ["tui", "ble"]` broke `cargo build` on macOS and
Windows because `ble` pulled in `bluer` (BlueZ, Linux-only).
Every non-Linux packager needed `--no-default-features`.
- The feature flags on `ble` and `gateway` were redundant with
their platform-gated deps (`bluer`, `rustables`). The parallel
gating was inconsistent and error-prone.
- `tui` feature protected against a ratatui binary-size concern
that no longer applies in 2026.
Cargo.toml:
- Remove `tui`, `ble`, `gateway` features; `default = []`.
- Promote `ratatui` to a non-optional top-level dependency.
- Move `rustables` from top-level optional into the Linux
target block, non-optional.
- Split `bluer` into its own target block with
`cfg(all(target_os = "linux", not(target_env = "musl")))`
— BlueZ isn't available on musl router targets and
`libdbus-sys` doesn't cross-compile to musl without pkg-config
sysroot setup.
- Drop `required-features` from the `fipstop` and `fips-gateway`
`[[bin]]` entries.
build.rs:
- Emit a `bluer_available` custom cfg when `target_os == "linux"`
and `target_env != "musl"`, for use in place of the verbose
full predicate in source cfg gates.
Source:
- Replace every `#[cfg(feature = "gateway")]` with
`#[cfg(target_os = "linux")]`. Gateway code works on both
glibc and musl Linux (rustables is fine on musl).
- Replace every `#[cfg(feature = "ble")]` with
`#[cfg(bluer_available)]`. BLE-specific code (BluerIo module,
bluer type conversions, BLE transport instance creation,
resolve_ble_addr) is excluded on musl and non-Linux. Generic
`BleAddr`, `BleIo` trait, `MockBleIo`, and `BleTransport<I>`
still compile on all targets.
- `src/bin/fips-gateway.rs`: always compiled, but `main()` is
gated to Linux. Non-Linux stub exits 1 with a diagnostic.
Existing non-Linux packaging scripts don't ship it, so the
stub binary sits unused.
Packaging and CI:
- Drop `--features` and `--no-default-features` flags from every
packaging script and workflow. Defaults now match each
platform's capabilities.
- AUR `fips-git` automatically aligns with stable `PKGBUILD`
(both build with defaults).
Verified: `cargo build --release` with no flags produces all
four binaries on glibc Linux; all unit and integration tests
pass across Linux/macOS/Windows/OpenWrt (musl) in CI.
On Ubuntu 22 (systemd 249), systemd-resolved applies interface-scoped
routing to per-link DNS servers. Configuring `resolvectl dns fips0
127.0.0.1:5354` caused resolved to attempt reaching 127.0.0.1 through
fips0 (a TUN with only fd00::/8 routes), silently failing. The DNS
responder never received queries. Newer systemd versions (250+) have
explicit handling for loopback servers on non-loopback interfaces.
Changes:
- DNS responder default bind_addr changed from "127.0.0.1" to "::"
so it listens on all interfaces, including fips0. Bind logic in
lifecycle.rs now parses bind_addr as IpAddr and constructs a
SocketAddr, handling IPv6 literal formatting. Factored into
Node::bind_dns_socket with explicit IPV6_V6ONLY=0 via socket2, so
IPv4 clients on 127.0.0.1:5354 still reach the responder
regardless of the kernel's net.ipv6.bindv6only sysctl.
- fips-dns-setup resolvectl backend now waits for fips0 to have a
global IPv6 address, then configures resolved with
[<fips0-addr>]:5354. That address is locally delivered by the
kernel regardless of which interface resolved tries to route
through. The dnsmasq and NetworkManager backends still use
127.0.0.1 (they don't have the interface-scoping issue).
- Dropped hardcoded `bind_addr: "127.0.0.1"` from the packaged
fips.yaml (Debian + OpenWrt). The shipped config was overriding
the new default.
- DNS queries are only accepted from the localhost.
Verified end-to-end in a privileged Ubuntu 22.04 systemd container:
dig @127.0.0.53 AAAA <npub>.fips resolves cleanly through
systemd-resolved.
The dns-delegate backend (systemd 258+) still uses 127.0.0.1; it
has not been verified whether that backend has the same routing
issue.
macOS platform:
- Platform-native TUN interface management with shutdown pipe
- Raw Ethernet transport with macOS socket backend (socket_macos.rs)
- EthernetTransport and TransportHandle::Ethernet ungated from Linux-only
- macOS .pkg packaging (build-pkg.sh, launchd plist, uninstall script)
- CI: macOS build and unit test jobs; x86_64 cross-compiled from
macos-latest via rustup target add x86_64-apple-darwin
Gateway feature flag:
- New opt-in `gateway` Cargo feature activates optional `rustables` dep
- `pub mod gateway` and `Config.gateway` gated behind the feature so
macOS builds never pull in Linux-only nftables bindings
- `fips-gateway` bin has `required-features = ["gateway"]`
- All Linux/OpenWrt/AUR packaging passes `--features gateway`
CI / packaging:
- package-linux, package-macos, package-openwrt now trigger on push to
master/maint/next and on pull requests; release uploads remain tag-gated
- Bloom filter routing fix: fall through to tree routing when no candidate
is strictly closer
- MMP intervals: raise MIN to 1s / MAX to 5s with 5-sample cold-start phase
Add fips-gateway binary to CI artifact and Docker build. Systemd
service unit with After=fips.service dependency and security
hardening. Debian and AUR package entries.
OpenWrt packaging: procd init script managing dnsmasq forwarding,
proxy NDP, RA route advertisements for the virtual IP pool, and a
global IPv6 prefix on br-lan to work around Android suppressing AAAA
queries on ULA-only networks. Sysctl config for IPv6 forwarding.
Gateway enabled by default in OpenWrt config. Ethernet transport
enabled by default.
Default gateway config section (commented out) in common fips.yaml.
Add log_level field to NodeConfig (case-insensitive, default: info).
The daemon now loads config before initializing tracing so the
configured level takes effect. RUST_LOG env var still overrides if
set.
Remove hardcoded Environment=RUST_LOG=info from systemd units and
OpenWrt procd init script — these prevented config-driven log levels
from working.
The ble feature (bluer crate) pulls in libdbus-sys which cannot
cross-compile with cargo-zigbuild. Disable default features and
explicitly enable only tui for the OpenWrt package build.
Pin Rust toolchain to 1.94.0 via rust-toolchain.toml for deterministic
compiler output across environments. Set SOURCE_DATE_EPOCH from git
commit timestamp in CI workflows and packaging scripts to normalize
embedded timestamps.
Make packaging archives reproducible: pass --mtime=@$SOURCE_DATE_EPOCH
to tar in .deb, .ipk, and systemd tarball builds. Normalize ownership
to root:root in systemd tarballs. Pin cargo-zigbuild to 0.19.8 for
cross-compilation stability.
Add SHA-256 hash output to CI build and OpenWrt packaging workflows
for binary verification.
Add a complete example running a strfry Nostr relay exclusively over
the FIPS mesh using the sidecar pattern. Includes Docker Compose
stack, network isolation via iptables, .fips DNS resolution, nak CLI,
build script with cross-compilation support, and documentation.
Also fix the package-openwrt.yml workflow path to match the
openwrt → openwrt-ipk directory rename.