Files
fips/packaging/common/fips.yaml
ArjenandJohnathan Corgan d208a9db81 feat(ble): lend the radio to the macOS daemon from a login-session agent
macOS never grants Bluetooth to a root launchd daemon: CoreBluetooth
answers it Unauthorized whatever the privacy settings say, so the
packaged daemon could not use the CoreBluetooth backend at all.

When CoreBluetooth refuses the daemon, it listens on ble-agent.sock
beside the control socket (/var/run/fips, group fips), and a per-user
LaunchAgent runs `fips-ble-agent --ble-agent` in the login session,
lending its radio to the daemon over a small framed protocol
(io_macos/agent*.rs). Each agent connection that says a valid hello is
installed as the radio, replacing the previous one, so the transport is
unchanged; a lost agent is a radio switched off. A connection has five
seconds to say hello. The agent re-resolves the socket path on every
reconnect, so one started before the daemon created its runtime
directory does not settle on a fallback path.

The agent connects only once its radio is up, and its hello carries the
PSM CoreBluetooth published, read afresh for every connection. Before the
radio is up `listen` reports only the requested fallback, so a hello sent
then told the daemon PSM 133 while the listener was on another, and kept
telling it so across reconnects and Bluetooth toggles.

fips-ble-agent is a hard link to fips, so Activity Monitor tells the two
processes apart. The agent logs to ~/Library/Logs/fips/ble-agent.log,
rolled like the daemon's log, rather than to a shared path in /tmp
another account could plant a file or symlink in. Install, upgrade and
uninstall stop and start the agent in every GUI session, so none keeps
running a replaced binary. The agent ends on the stop signal the process holds from
the start, so a bootout does not wait out launchd's timeout.

The daemon/agent pair is tested end to end over a real Unix socket and
MockBleIo on every host: listener PSM, advertising, adverts, dialled and
inbound channels both ways, refusal, a lost agent, a connection that
never says hello, and an agent whose radio comes up after it starts.
2026-10-13 20:56:07 +00:00

172 lines
6.8 KiB
YAML

# FIPS Node Configuration
node:
identity:
# By default, a new ephemeral keypair is generated on each start.
# Uncomment persistent to keep the same identity across restarts;
# on first start a keypair is saved to fips.key/fips.pub next to
# this config file (mode 0600/0644).
# persistent: true
#
# Or set an explicit key (overrides persistent):
# nsec: "nsec1..."
# Mesh-lookup protocol (node.lookup.*): the overlay coordinate-lookup engine
# (mesh address -> coordinates). Defaults shown; uncomment to override.
# lookup:
# ttl: 64
# attempt_timeouts_secs: [1, 2, 4, 8]
# recent_expiry_secs: 10
# backoff_base_secs: 0
# backoff_max_secs: 0
# forward_min_interval_secs: 2
rendezvous:
# Optional Nostr-mediated overlay endpoint rendezvous.
# nostr:
# enabled: true
# policy: configured_only # disabled | configured_only | open
# open_discovery_max_pending: 64 # caps queued open-rendezvous retries
# app: "fips-overlay-v1"
# advertise: true
# advert_relays:
# - "wss://relay.damus.io"
# - "wss://nos.lol"
# - "wss://offchain.pub"
# dm_relays:
# - "wss://relay.damus.io"
# - "wss://nos.lol"
# - "wss://offchain.pub"
# # Optional override. If omitted, FIPS uses the built-in STUN list.
# # Built-in relay/STUN defaults are best-effort and should be
# # overridden by operators for production use.
# stun_servers:
# - "stun:stun.l.google.com:19302"
# - "stun:stun.cloudflare.com:3478"
# - "stun:global.stun.twilio.com:3478"
#
# Optional mDNS-based LAN rendezvous for sub-second same-LAN pairing.
# Opt-in (default false): default-off avoids a per-LAN identity
# broadcast on nodes that have deliberately disabled other rendezvous
# channels, and avoids any multicast surprise on upgrade. Requires an
# operational UDP transport (the advertised port is the one peers dial).
# lan:
# enabled: false
# # Optional application/network scope carried in the LAN-only TXT
# # record. Browsers that set a scope ignore adverts for other scopes.
# # Kept separate from the Nostr rendezvous `app` tag so relay-visible
# # adverts can stay generic while LAN rendezvous stays per-private-network.
# # scope: "lab-floor-3"
# # Advanced: overrides the mDNS service type. Leave unset in normal
# # use — only needed to run multiple isolated services on one
# # interface (e.g. test isolation on loopback).
# # service_type: "_fips._udp.local."
tun:
enabled: true
name: fips0
mtu: 1280
dns:
enabled: true
# bind_addr defaults to "::1" (IPv6 loopback). The shipped
# fips-dns-setup script configures systemd-resolved with a global
# /etc/systemd/resolved.conf.d/fips.conf drop-in pointing at
# [::1]:5354.
#
# Set "::" to serve .fips names to other hosts on this machine's
# networks (e.g. LAN clients behind a gateway host). Every host that
# can reach port 5354 can then query it. On Linux, macOS and FreeBSD,
# with the daemon's own TUN, queries arriving on the mesh interface are
# dropped, so mesh peers are not served, unless the daemon logged
# "Mesh interface index unresolved; DNS mesh filter disabled" at
# start; on Windows, or with an application-supplied TUN, they are.
# Replies follow the routing table, so a host on those networks that
# forges a mesh source address (fd00::/8) has the reply sent into the
# mesh from this node's mesh address.
# bind_addr: "::1"
port: 5354
transports:
udp:
bind_addr: "0.0.0.0:2121"
# advertise_on_nostr: true
# public: false # false => advertise udp:nat; true => advertise bound host:port
# accept_connections: true # default; refuse inbound msg1 when false
# outbound_only: false # true => bind ephemeral, no listener on a
# # known port. Forces advertise_on_nostr=false
# # and accept_connections=false. Pure-client
# # posture; bind_addr is ignored.
tcp:
# Accepts inbound connections. No static outbound peers.
bind_addr: "0.0.0.0:8443"
# advertise_on_nostr: true
# Ethernet transport — uncomment and set your interface name.
# ethernet:
# interface: "eth0"
# listen: true
# announce: true
# auto_connect: true
# accept_connections: true
# Bluetooth Low Energy transport. On Linux (glibc builds) it needs BlueZ
# (bluetoothd) running. On macOS it uses CoreBluetooth: `adapter` is
# ignored, only one `ble` instance is allowed, and the packaged daemon
# gets the radio from the fips-ble-agent LaunchAgent, so BLE works while a
# user in the `fips` group is logged in. Not available on FreeBSD, Windows
# or OpenWrt.
# ble:
# adapter: "hci0"
# mtu: 2048
# advertise: true
# scan: true
# auto_connect: true
# accept_connections: true
# Nym transport — outbound-only connections through the Nym mixnet for
# sender/receiver privacy. This is a privacy posture, not a NAT-traversal
# or failover transport: it only dials out (no inbound listener) and is
# chosen for its anonymity properties. Requires a nym-socks5-client
# running separately as its own process; FIPS dials it over SOCKS5.
# nym:
# socks5_addr: "127.0.0.1:1080" # nym-socks5-client SOCKS5 address
# connect_timeout_ms: 300000 # outbound connect timeout (300s);
# # mixnet round-trips can take minutes
# mtu: 1400 # per-connection MTU
# startup_timeout_secs: 120 # wait for nym-socks5-client readiness
# Outbound LAN gateway. Allows non-FIPS hosts on the LAN to reach
# mesh destinations via DNS-allocated virtual IPs and kernel NAT.
# Requires: IPv6 forwarding enabled, fips daemon running with DNS.
# gateway:
# enabled: true
# pool: "fd01::/112"
# lan_interface: "eth0"
# dns:
# listen: "[::1]:5365"
# # upstream must match the daemon's dns.bind_addr above. The
# # default "[::1]:5354" matches the daemon's default. If you set
# # the daemon to bind on a wildcard ("::") or specific address,
# # update upstream to match — explicit ::1 sockets do not accept
# # v4 traffic.
# upstream: "[::1]:5354"
# ttl: 60
# pool_grace_period: 60
# conntrack:
# tcp_established: 432000
# udp_timeout: 30
# udp_assured: 180
# icmp_timeout: 30
peers: []
# Static peers for bootstrapping (UDP or TCP):
# - npub: "npub1qmc3cvfz0yu2hx96nq3gp55zdan2qclealn7xshgr448d3nh6lks7zel98"
# alias: "gateway"
# via_nostr: true
# addresses:
# - transport: udp
# addr: "test-us01.fips.network:2121" # IP or hostname (e.g., "peer.example.com:2121")
# - transport: udp
# addr: "nat" # Use node.rendezvous.nostr for Nostr/STUN hole punching
# connect_policy: auto_connect