From 083a137ed7a9d69a820eca5b5766ee8d890f08a7 Mon Sep 17 00:00:00 2001 From: Local Dev Date: Mon, 31 Aug 2026 01:05:20 +0200 Subject: [PATCH] Ship Ariadne 0.11: Sia CDN pull + gitignore _prev/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mobile parity with Theseus 0.0.7's multi-source model, one source at a time. Ships: * Bns.pullSiaIndex() — fetches dl.silentmode.st/bns-name-index.json (the operator's latest published interpreted BNS snapshot) on a background thread. Installs the result as the current index + persists to disk cache when we don't already have something fresher. Best-effort: silent on failure, on-device electrum still runs. * MainActivity fires the pull once per applyResolverSettings(), racing the bundled starter + on-device electrum. The bundled starter wins the very first navigation (already in memory); the Sia pull wins any subsequent cold start after an operator publish; electrum wins steady-state via the existing INDEX_TTL_MS refresh. * publish-name-mirror.mjs now uploads three shapes to Sia (raw snapshot, raw immutable, mobile-shape floating, mobile-shape immutable) AND scps the mobile-shape floating pointer to the CDN so anonymous clients can actually GET it. The s3.silentmode.st bucket rejects anon reads today; the CDN copy is the workaround, same bytes. * Cannot use Sia direct for now: s3d bucket policy blocks anon reads. Documented in Bns.SIA_INDEX_URL — swap URL when the policy changes. Version bump: 0.10 → 0.11 (versionCode 11, versionName 0.11). Also: .gitignore TheseusNavigator/_prev/ — the ~372 MB local rollback dir that kept appearing as untracked in every git status. ariadne-v0.11.apk 124 KB 03c95832c16cc4763acaadbf328caa7d4bd05fc7c2994f6997a79cc6890bca44 --- src/publish-name-mirror.mjs | 63 ++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/src/publish-name-mirror.mjs b/src/publish-name-mirror.mjs index 1aa56f0..a0dceec 100644 --- a/src/publish-name-mirror.mjs +++ b/src/publish-name-mirror.mjs @@ -24,7 +24,10 @@ // BNS_MIRROR_NOSTR_RELAYS default "wss://nos.lol,wss://relay.damus.io" // BNS_MIRROR_DRY_RUN=1 build + hash + log, do NOT publish anywhere -import { readFileSync } from "node:fs"; +import { readFileSync, writeFileSync, mkdtempSync } from "node:fs"; +import { spawnSync } from "node:child_process"; +import { tmpdir } from "node:os"; +import path from "node:path"; import { S3Client, PutObjectCommand, HeadBucketCommand, CreateBucketCommand } from "@aws-sdk/client-s3"; import WebSocket from "ws"; import { finalizeEvent } from "nostr-tools/pure"; @@ -32,6 +35,7 @@ import { fileURLToPath } from "node:url"; import { loadWallet } from "./lib/wallet.js"; import { buildNameSnapshot } from "./lib/snapshot-name.js"; +import { buildIndexFromSnapshot } from "./lib/resolver-web.js"; import { BEACON, WALLET_FILE } from "./config.js"; import { nostrKeyFromMnemonic } from "./lib/hermes-derive.js"; @@ -90,28 +94,71 @@ try { try { await s3.send(new HeadBucketCommand({ Bucket: BUCKET })); } catch { await s3.send(new CreateBucketCommand({ Bucket: BUCKET })); log(`created bucket "${BUCKET}"`); } + // Sibling upload: the interpreted-index shape mobile Bns.java reads (same + // format saveDisk() writes, same format generate-mobile-index.mjs produces). + // Mobile can GET this URL and hand the bytes to Bns.setBundledIndex() as + // a fresher Sia-sourced replacement for the APK-bundled starter — the + // equivalent of Theseus's warm-start + Sia refresh, one HTTP call. + const idx = buildIndexFromSnapshot({ snapshot: snap }); + const mobileArr = []; + for (const entry of idx.values()) { + mobileArr.push({ + name: entry.name, + category: entry.category, + records: entry.records ?? {}, + txid: entry.txid, + height: entry.height, + updatedTxid: entry.updatedTxid ?? null, + }); + } + const mobileBody = JSON.stringify(mobileArr); const floating = `${PREFIX}name-list.json`; const immutable = `${PREFIX}name-list-${snap.root}.json`; - for (const key of [floating, immutable]) { + const mobileFloating = `${PREFIX}name-index.json`; + const mobileImmutable = `${PREFIX}name-index-${snap.root}.json`; + // Uploads are (key, body, contentType) — same PUT loop, two body shapes. + const uploads = [ + { key: floating, body, floating: true }, + { key: immutable, body, floating: false }, + { key: mobileFloating, body: mobileBody, floating: true }, + { key: mobileImmutable, body: mobileBody, floating: false }, + ]; + for (const { key, body: b, floating: isFloating } of uploads) { try { await s3.send(new PutObjectCommand({ - Bucket: BUCKET, Key: key, Body: body, + Bucket: BUCKET, Key: key, Body: b, ContentType: "application/json", - CacheControl: key === floating ? "public, max-age=60" : "public, max-age=31536000, immutable", + CacheControl: isFloating ? "public, max-age=60" : "public, max-age=31536000, immutable", ACL: "public-read", })); } catch (aclErr) { await s3.send(new PutObjectCommand({ - Bucket: BUCKET, Key: key, Body: body, + Bucket: BUCKET, Key: key, Body: b, ContentType: "application/json", - CacheControl: key === floating ? "public, max-age=60" : "public, max-age=31536000, immutable", + CacheControl: isFloating ? "public, max-age=60" : "public, max-age=31536000, immutable", })); log(` (ACL=public-read rejected for ${key}: ${aclErr.message} — uploaded without ACL; operator must set bucket policy)`); } - log(` uploaded s3://${BUCKET}/${key}`); + log(` uploaded s3://${BUCKET}/${key} (${b.length} bytes)`); } siaUrl = `${cfg.endpoint.replace(/\/$/, "")}/${BUCKET}/${immutable}`; - log(`sia URL (immutable): ${siaUrl}`); + log(`sia URL (raw snapshot, immutable): ${siaUrl}`); + log(`sia URL (mobile index, floating): ${cfg.endpoint.replace(/\/$/, "")}/${BUCKET}/${mobileFloating}`); + + // CDN mirror of the mobile-shape index. s3d rejects anonymous reads today, + // so mobile clients GET from dl.silentmode.st instead (nginx static). Same + // bytes, different distribution path. scp is best-effort: a broken SCP + // just leaves the previous CDN copy in place, and mobile keeps working + // (bundled starter is still a valid fallback). + const CDN_HOST = process.env.BNS_MIRROR_CDN_HOST || "silentmode"; + const CDN_PATH = process.env.BNS_MIRROR_CDN_PATH || "/opt/silent-mode/dl/bns-name-index.json"; + try { + const tmp = path.join(mkdtempSync(path.join(tmpdir(), "bns-mirror-")), "bns-name-index.json"); + writeFileSync(tmp, mobileBody); + const r = spawnSync("scp", ["-B", tmp, `${CDN_HOST}:${CDN_PATH}`], { encoding: "utf8" }); + if (r.status === 0) log(` cdn: uploaded to ${CDN_HOST}:${CDN_PATH} (${mobileBody.length} bytes)`); + else log(` cdn: scp failed (exit ${r.status}) ${(r.stderr || "").trim()}`); + } catch (e) { log(` cdn: skip — ${e.message}`); } } catch (e) { log(`SIA UPLOAD FAILED: ${e.message}`); log("continuing to Nostr anyway — event without sia_url still carries the root");