fix(fips): harden companion mesh joins
This commit is contained in:
@@ -23,6 +23,10 @@ used by the `.githooks/pre-push` hook), which:
|
||||
**aborts** if any is missing.
|
||||
5. Stages the signed APK at `neode-ui/public/packages/archipelago-companion.apk`,
|
||||
commits, and pushes with `SHIP_COMPANION=1` (the sanctioned pre-push bypass).
|
||||
6. The first-launch companion modal and Android "Share this app" QR point at
|
||||
`http://146.59.87.168:2100/packages/archipelago-companion.apk`. After the
|
||||
repo artifact is built, mirror that exact APK to the VPS2-served path before
|
||||
calling the release done.
|
||||
|
||||
**Never** hand-roll `gradlew assembleDebug` + `cp` to the served path. That path
|
||||
skips the clean build and the signature enforcement and is exactly how a broken
|
||||
@@ -82,13 +86,16 @@ home-screen app layouts wiped by an over-broad action.
|
||||
|
||||
## Verify the published download after shipping
|
||||
|
||||
The download served to nodes is Gitea raw-on-main. Confirm the live bytes match
|
||||
what you built and signed:
|
||||
The checked-in artifact is Gitea raw-on-main. The QR/App Store download served
|
||||
to users is the VPS2 `:2100` URL. Confirm both live byte streams match what you
|
||||
built and signed:
|
||||
|
||||
```bash
|
||||
SERVED=neode-ui/public/packages/archipelago-companion.apk
|
||||
URL=http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/$SERVED
|
||||
curl -sS -o /tmp/live.apk "$URL"
|
||||
shasum -a 256 "$SERVED" /tmp/live.apk # must match
|
||||
apksigner verify -v --min-sdk-version 21 /tmp/live.apk | grep -i "scheme" # v1/v2/v3 = true
|
||||
GITEA_URL=http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/$SERVED
|
||||
QR_URL=http://146.59.87.168:2100/packages/archipelago-companion.apk
|
||||
curl -sS -o /tmp/live-gitea.apk "$GITEA_URL"
|
||||
curl -sS -o /tmp/live-qr.apk "$QR_URL"
|
||||
shasum -a 256 "$SERVED" /tmp/live-gitea.apk /tmp/live-qr.apk # all must match
|
||||
apksigner verify -v --min-sdk-version 21 /tmp/live-qr.apk | grep -i "scheme" # v1/v2/v3 = true
|
||||
```
|
||||
|
||||
@@ -11,8 +11,8 @@ android {
|
||||
applicationId = "com.archipelago.app"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 35
|
||||
versionName = "0.5.15"
|
||||
versionCode = 38
|
||||
versionName = "0.5.18"
|
||||
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
|
||||
@@ -25,6 +25,26 @@ internal const val ARCHY_ANCHOR_NPUB =
|
||||
internal const val ARCHY_ANCHOR_ADDR = "146.59.87.168:8444"
|
||||
internal const val ARCHY_ANCHOR_TRANSPORT = "tcp"
|
||||
|
||||
/**
|
||||
* Public FIPS network anchors (join.fips.network — the dual-transport TCP
|
||||
* pair; keep in lockstep with core/archipelago/src/fips/anchors.rs
|
||||
* fips_network_anchors()). Baked into every pairing at trailing priority so
|
||||
* a degraded/unreachable vps2 anchor can never strand the phone: the mesh
|
||||
* still joins the public tree and routes to the node through it.
|
||||
*/
|
||||
internal val PUBLIC_FIPS_ANCHORS = listOf(
|
||||
Triple(
|
||||
"npub10yffd020a4ag8zcy75f9pruq3rnghvvhd5hphl9s62zgp35s560qrksp9u",
|
||||
"23.182.128.74:443",
|
||||
"tcp",
|
||||
),
|
||||
Triple(
|
||||
"npub1qmc3cvfz0yu2hx96nq3gp55zdan2qclealn7xshgr448d3nh6lks7zel98",
|
||||
"217.77.8.91:443",
|
||||
"tcp",
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* Mesh identity + known node peers. Follows the same plaintext-DataStore
|
||||
* storage model as ServerPreferences (the server password lives there the
|
||||
@@ -153,9 +173,40 @@ class FipsPreferences(private val context: Context) {
|
||||
}))
|
||||
})
|
||||
}
|
||||
// Backfill anchor peers for entries paired before newer QR/app
|
||||
// releases added them. `upsertNodePeer` persists these on re-scan, but
|
||||
// startup must also self-heal old DataStore state so updating the APK is
|
||||
// enough to get off-LAN redundancy.
|
||||
if (merged.length() > 0) {
|
||||
addAnchorIfMissing(merged, ARCHY_ANCHOR_NPUB, "archipelago-anchor", ARCHY_ANCHOR_ADDR, ARCHY_ANCHOR_TRANSPORT, 40)
|
||||
for ((i, anchor) in PUBLIC_FIPS_ANCHORS.withIndex()) {
|
||||
val (npub, addr, transport) = anchor
|
||||
addAnchorIfMissing(merged, npub, "fips-network-anchor-${i + 1}", addr, transport, 50 + i * 10)
|
||||
}
|
||||
}
|
||||
return merged.toString()
|
||||
}
|
||||
|
||||
private fun addAnchorIfMissing(
|
||||
peers: JSONArray,
|
||||
npub: String,
|
||||
alias: String,
|
||||
addr: String,
|
||||
transport: String,
|
||||
priority: Int,
|
||||
) {
|
||||
if ((0 until peers.length()).any { peers.optJSONObject(it)?.optString("npub") == npub }) return
|
||||
peers.put(JSONObject().apply {
|
||||
put("npub", npub)
|
||||
put("alias", alias)
|
||||
put("addresses", JSONArray().put(JSONObject().apply {
|
||||
put("transport", transport)
|
||||
put("addr", addr)
|
||||
put("priority", priority)
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
private fun parsePartyPeers(json: String): List<PartyPeer> = try {
|
||||
val arr = JSONArray(json)
|
||||
(0 until arr.length()).mapNotNull { i ->
|
||||
@@ -249,6 +300,21 @@ class FipsPreferences(private val context: Context) {
|
||||
}))
|
||||
}
|
||||
}
|
||||
// And the public FIPS network anchors at trailing priority, so one
|
||||
// degraded rendezvous (vps2, 2026-07-24) can never strand the phone.
|
||||
for ((i, anchor) in PUBLIC_FIPS_ANCHORS.withIndex()) {
|
||||
val (npub, addr, transport) = anchor
|
||||
if (info.npub == npub || incoming.any { it.optString("npub") == npub }) continue
|
||||
incoming += JSONObject().apply {
|
||||
put("npub", npub)
|
||||
put("alias", "fips-network-anchor-${i + 1}")
|
||||
put("addresses", JSONArray().put(JSONObject().apply {
|
||||
put("transport", transport)
|
||||
put("addr", addr)
|
||||
put("priority", 50 + i * 10)
|
||||
}))
|
||||
}
|
||||
}
|
||||
val incomingNpubs = incoming.map { it.optString("npub") }.toSet()
|
||||
context.fipsDataStore.edit { prefs ->
|
||||
val current = JSONArray(prefs[peersKey] ?: "[]")
|
||||
|
||||
Reference in New Issue
Block a user