diff --git a/Android/app/build.gradle.kts b/Android/app/build.gradle.kts index 2b011839..932fc67c 100644 --- a/Android/app/build.gradle.kts +++ b/Android/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "com.archipelago.app" minSdk = 26 targetSdk = 35 - versionCode = 22 - versionName = "0.5.2" + versionCode = 23 + versionName = "0.5.3" vectorDrawables { useSupportLibrary = true diff --git a/Android/app/src/main/java/com/archipelago/app/fips/FipsPreferences.kt b/Android/app/src/main/java/com/archipelago/app/fips/FipsPreferences.kt index feee9ece..d0d4df8e 100644 --- a/Android/app/src/main/java/com/archipelago/app/fips/FipsPreferences.kt +++ b/Android/app/src/main/java/com/archipelago/app/fips/FipsPreferences.kt @@ -12,6 +12,16 @@ import androidx.datastore.preferences.preferencesDataStore private val Context.fipsDataStore: DataStore by preferencesDataStore(name = "fips_prefs") +// Archipelago-operated public anchor (vps2). Baked in so EVERY pairing yields +// both paths — direct LAN p2p to the node AND a public rendezvous for +// away-from-home — even when the scanned node is old enough that its QR +// carries no fanchors. Keep in lockstep with +// core/archipelago/src/fips/anchors.rs (ARCHY_ANCHOR_*). +internal const val ARCHY_ANCHOR_NPUB = + "npub1dptaktwxv0mm245g2lqjykwm5ll0jpc6m3r4242ydfa9z7qe6urs3jvrak" +internal const val ARCHY_ANCHOR_ADDR = "146.59.87.168:8444" +internal const val ARCHY_ANCHOR_TRANSPORT = "tcp" + /** * Mesh identity + known node peers. Follows the same plaintext-DataStore * storage model as ServerPreferences (the server password lives there the @@ -91,6 +101,21 @@ class FipsPreferences(private val context: Context) { })) } } + // Guarantee the public anchor: without it, a QR from an older node + // leaves the phone LAN-only and pairing/connecting dies off-LAN. + if (info.npub != ARCHY_ANCHOR_NPUB && + incoming.none { it.optString("npub") == ARCHY_ANCHOR_NPUB } + ) { + incoming += JSONObject().apply { + put("npub", ARCHY_ANCHOR_NPUB) + put("alias", "Archipelago anchor") + put("addresses", JSONArray().put(JSONObject().apply { + put("transport", ARCHY_ANCHOR_TRANSPORT) + put("addr", ARCHY_ANCHOR_ADDR) + put("priority", 40) + })) + } + } val incomingNpubs = incoming.map { it.optString("npub") }.toSet() context.fipsDataStore.edit { prefs -> val current = JSONArray(prefs[peersKey] ?: "[]") diff --git a/Android/app/src/main/java/com/archipelago/app/ui/screens/ServerConnectScreen.kt b/Android/app/src/main/java/com/archipelago/app/ui/screens/ServerConnectScreen.kt index 4944fdda..ab57fd3b 100644 --- a/Android/app/src/main/java/com/archipelago/app/ui/screens/ServerConnectScreen.kt +++ b/Android/app/src/main/java/com/archipelago/app/ui/screens/ServerConnectScreen.kt @@ -185,8 +185,10 @@ fun ServerConnectScreen( useHttps = false, port = "", ) - // The tunnel + mesh route need a moment on cold start. - for (attempt in 0 until 4) { + // The tunnel + mesh route need a moment on cold start — and on + // a first-ever pairing the VPN consent dialog is on screen at + // the same time, so give the user time to tap it. + for (attempt in 0 until 6) { if (attempt > 0) delay(2000) reachable = testConnection(meshServer) if (reachable) break diff --git a/docs/HANDOFF-2026-07-23-companion-apk-deploy.md b/docs/HANDOFF-2026-07-23-companion-apk-deploy.md index d521f263..0bcf3ec9 100644 --- a/docs/HANDOFF-2026-07-23-companion-apk-deploy.md +++ b/docs/HANDOFF-2026-07-23-companion-apk-deploy.md @@ -18,8 +18,11 @@ user's hands. support; web scan modal hands live scanning to it. - npub-keyed saved servers (pairing contract item 1, PR #106). - Served APK refreshed: `neode-ui/public/packages/archipelago-companion.apk` - is now **0.5.2 / versionCode 22**, which additionally fixes the two field - failures from the user's 5G test (screenshots, 21:54): + is now **0.5.3 / versionCode 23**. On top of the 0.5.1 scanner fixes it + guarantees dual-path peering — the node's LAN endpoint (direct p2p, npub- + keyed dial hints) AND the Archipelago public anchor (vps2, baked into the + app so even an old node's QR can't leave the phone LAN-only) — and fixes + the two field failures from the user's 5G test (screenshots, 21:54): - **Mesh VPN no longer kills the phone's internet** — the IPv6-only TUN never called `allowFamily(AF_INET)`, so Android blocked all IPv4 while the mesh was up. Now allowed (+ `allowBypass`). @@ -39,9 +42,15 @@ user's hands. 3. The demo stack gets its images from CI (run 100 pushed today with the new web bundle) — confirm the Portainer stack re-pulled, or trigger its redeploy, so the demo QR also serves vc21. -4. Re-test the user's exact flow: node UI → Companion popup → download APK - via QR → install (vc21 updates vc19/vc20 in place) → scan the pairing QR. - If the scan still fails on-device with vc21, capture `adb logcat` around - the scan and report back — the parser accepts the npub-first QR - (`fanchors` self-anchor first), so a remaining failure would be camera- - hardware-specific, not format. +4. **Node side is half the 5G story**: away-from-home reachability needs the + NODE connected to the public anchor too. On Framework PT (and any test + node): deploy current main (node-side npub-first `fips.pair-info`), then + verify `sudo -n fipsctl show status` reports the anchor connected — + `fips.reconnect` RPC if not. A phone can dial the anchor perfectly and + still fail if the node never enrolled with it. +5. Re-test the user's exact flows with vc23 (updates any older install in + place): (a) pair ON the LAN, then switch the phone to 5G — the UI must + come up via the mesh ULA; (b) pair while ALREADY on 5G (never on the + node's LAN) — scan, VPN consent, and the connect must succeed through + the anchor. If either fails, capture `adb logcat` around the attempt and + `fipsctl show sessions` on the node, and report back. diff --git a/neode-ui/public/packages/archipelago-companion.apk b/neode-ui/public/packages/archipelago-companion.apk index b4331df4..3d9e0437 100644 Binary files a/neode-ui/public/packages/archipelago-companion.apk and b/neode-ui/public/packages/archipelago-companion.apk differ