feat(companion): embedded FIPS mesh replaces WireGuard for remote access

- Android/rust/archy-fips-core: leaf-only fips node as a JNI cdylib (fips
  pinned to the fips-native fork rev with VpnService fd support), built by
  gradle via cargo-ndk (arm64), tested on host
- ArchyVpnService: split-tunnel VpnService routing only fd00::/8 (MTU 1280,
  foreground specialUse); FipsManager handles the one-time VPN consent and
  silent auto-start — no settings surface at all
- pairing QR now fully configures the mesh: fnpub/fip/fhost/fudp/ftcp plus
  fanchors (the node's seed-anchor list, npub@addr/transport) so the phone
  can rendezvous through public anchors when the LAN endpoint is unreachable
- default seed anchors gain the two dual-transport join.fips.network test
  anchors (23.182.128.74:443/tcp, 217.77.8.91:443/tcp); anchor adverts are
  Nostr kind-37195 events
- device token rides the password field end-to-end: backend accepts tokens
  wherever it accepts the password, so scan = instant login (WebSocket auth
  + WebView form injection unchanged); token logins skip TOTP
- ServerEntry.meshIp + IPv6-bracketed URLs; WebView retries the mesh address
  on main-frame errors, auto-login and origin checks honor it
- companion v0.5.0 (versionCode 20), arm64 abiFilter; FipsNative.available
  gates everything so non-arm64 still runs as a plain companion

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-07-22 22:06:32 +01:00
co-authored by Claude Fable 5
parent b88609e0ff
commit 4b91765cc7
25 changed files with 1119 additions and 33 deletions
@@ -317,6 +317,7 @@ async function buildPairingUrl(): Promise<string> {
ula?: string | null
udp_port?: number
tcp_port?: number
anchors?: { npub: string; addr: string; transport: string }[]
}>({ method: 'fips.pair-info' })
if (info?.npub) {
params.set('fnpub', info.npub)
@@ -328,6 +329,16 @@ async function buildPairingUrl(): Promise<string> {
}
if (info.udp_port) params.set('fudp', String(info.udp_port))
if (info.tcp_port) params.set('ftcp', String(info.tcp_port))
// Rendezvous anchors (compact: npub@addr/transport, comma-joined).
// Cap keeps the QR at a camera-friendly density; the node lists its
// most reachable anchors first.
const anchors = (info.anchors || []).slice(0, 4)
if (anchors.length) {
params.set(
'fanchors',
anchors.map((a) => `${a.npub}@${a.addr}/${a.transport}`).join(','),
)
}
}
} catch {
// FIPS not provisioned yet — LAN pairing still works; the app can mesh