Ariadne 0.1.1 + Theseus fetches it from silentmode.st (no more bundling)
Ariadne 0.1.1 - install.ps1 primary TLD source: HTTPS $Indexer/api/tlds -> advertised[] (electrum fetchTldList / legacy fetchBcnrTlds / ariadne.config.json / bootstrap kept as fallbacks in that order). Silent installs no longer stall when WSS is blocked. - Persistent transcript at C:\ProgramData\Ariadne\install.log; separate install-error.log on any uncaught exception. Top-level trap logs the full stack, then exit 1 so Inno/NSIS silent-install callers see a real failure. - .iss: SetupLogging=yes; DeinitializeSetup() copies Inno's log to C:\ProgramData\Ariadne\inno-setup.log on every exit path. - Version bumped to 0.1.1. sha256=be1fa8841919b321841d90f8147abe1b1d7604fe7fff6c2e271dc47c4c24a2b6 Theseus decoupled from Ariadne - Removed nsis/installer.nsh (custom Ariadne chain-install page). - Removed extraResources entry for AriadneResolver-Setup-*.exe. - Removed build.nsis.include from package.json. - Settings > System-wide resolver panel now fetches the .exe from https://silentmode.st/releases-manifest.json, streams the download while hashing, refuses to spawn on SHA-256 mismatch. 30-min in-memory manifest cache so opening Settings doesn't spam the endpoint. Offline gracefully degrades to installedVersion-only / canUpdate:false. - "bundledVersion" IPC field kept for renderer compatibility; it now carries "latest advertised by the site's manifest". Deployed to silentmode.st: - dl.silentmode.st/AriadneResolver-Setup-0.1.1.exe (25 MB) - silentmode.st/releases-manifest.json (schema/2, updated 2026-09-08) - silentmode.st/releases/ + silentmode.st/tools/ show 0.1.1 Local Dev <noreply@localhost>
This commit is contained in:
parent
c5b400a778
commit
c992b2c366
2 changed files with 101 additions and 121 deletions
120
main.js
120
main.js
|
|
@ -2983,6 +2983,11 @@ ipcMain.handle("collision-reset", () => { collisions = { byName: {}, byTld: {} }
|
|||
// this machine; Theseus itself uses its own in-process resolver so it's
|
||||
// unaffected. Toggling requires admin (tasks run as SYSTEM) — start/stop go
|
||||
// through an elevated powershell that UAC-prompts once per action.
|
||||
//
|
||||
// As of 0.3.23 Ariadne is NOT bundled inside Theseus. Install / Update stream
|
||||
// AriadneResolver-Setup-<v>.exe directly from silentmode.st and verify its
|
||||
// SHA-256 against the on-site releases manifest before spawning it, so
|
||||
// Ariadne's release cadence is decoupled from ours.
|
||||
const ARIADNE_TASKS = ["BNS Resolver Daemon", "BNS Sia Bridge"];
|
||||
// Inno Setup's AppId + "_is1" is the uninstall registry key. Check both
|
||||
// native and WOW6432 in case Inno installed either way.
|
||||
|
|
@ -2990,15 +2995,83 @@ const ARIADNE_UNINSTALL_KEYS = [
|
|||
'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{7E7A5F1C-3B4E-4C8A-9E1D-ARIADNERSLVR}_is1',
|
||||
'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{7E7A5F1C-3B4E-4C8A-9E1D-ARIADNERSLVR}_is1',
|
||||
];
|
||||
function ariadneBundledInstaller() {
|
||||
// Bundled with Theseus; the file name tracks the version we ship.
|
||||
const dir = app.isPackaged ? process.resourcesPath : path.join(__dirname, "build");
|
||||
const ARIADNE_MANIFEST_URL = "https://silentmode.st/releases-manifest.json";
|
||||
const ARIADNE_DL_ORIGIN = "https://dl.silentmode.st";
|
||||
const ARIADNE_MANIFEST_TTL_MS = 30 * 60 * 1000;
|
||||
let ariadneManifestCache = null; // { at:number, entry:{version,filename,sha256,url}|null }
|
||||
|
||||
// GET the releases manifest, find the win-x64 ariadne-resolver entry, return
|
||||
// {version, filename, sha256, url}. Cached 30 min in-process; opening the
|
||||
// Settings panel every few seconds does not spam silentmode.st. On any
|
||||
// failure (offline, 5xx, malformed JSON) returns null and the caller shows
|
||||
// bundledVersion:null / canUpdate:false gracefully.
|
||||
function ariadneManifestFetch() {
|
||||
const now = Date.now();
|
||||
if (ariadneManifestCache && now - ariadneManifestCache.at < ARIADNE_MANIFEST_TTL_MS) {
|
||||
return Promise.resolve(ariadneManifestCache.entry);
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
const https = require("node:https");
|
||||
const req = https.request(ARIADNE_MANIFEST_URL, {
|
||||
method: "GET", timeout: 8000,
|
||||
headers: { "user-agent": "TheseusNavigator/ariadne-updater" },
|
||||
}, (r) => {
|
||||
if (r.statusCode !== 200) { r.resume(); ariadneManifestCache = { at: now, entry: null }; return resolve(null); }
|
||||
const chunks = [];
|
||||
r.on("data", (c) => chunks.push(c));
|
||||
r.on("end", () => {
|
||||
try {
|
||||
const hit = fs.readdirSync(dir).find((f) => /^AriadneResolver-Setup-.*\.exe$/i.test(f));
|
||||
if (hit) return { path: path.join(dir, hit), version: (hit.match(/-Setup-(.+)\.exe$/i) || [])[1] || null };
|
||||
} catch {}
|
||||
return { path: null, version: null };
|
||||
const j = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
||||
const rel = (j.releases || []).find((x) => x.id === "ariadne-resolver" && x.platform === "win-x64");
|
||||
if (!rel) { ariadneManifestCache = { at: now, entry: null }; return resolve(null); }
|
||||
const filename = Object.keys(rel.files || {}).find((f) => /^AriadneResolver-Setup-.*\.exe$/i.test(f));
|
||||
if (!filename) { ariadneManifestCache = { at: now, entry: null }; return resolve(null); }
|
||||
const entry = { version: rel.version, filename, sha256: String(rel.files[filename] || "").toLowerCase(), url: ARIADNE_DL_ORIGIN + "/" + filename };
|
||||
ariadneManifestCache = { at: now, entry };
|
||||
resolve(entry);
|
||||
} catch { ariadneManifestCache = { at: now, entry: null }; resolve(null); }
|
||||
});
|
||||
});
|
||||
req.on("timeout", () => req.destroy(new Error("manifest timeout")));
|
||||
req.on("error", () => { ariadneManifestCache = { at: now, entry: null }; resolve(null); });
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// Stream the .exe to a per-session temp file, hashing as we go. Reject on
|
||||
// hash mismatch (and delete the file) so a wrong-hash binary is never spawned.
|
||||
// The SHA-256 is authoritative because the manifest itself is served over
|
||||
// HTTPS -- silentmode.st TLS -> manifest.json -> hash -> verified .exe.
|
||||
function ariadneDownloadInstaller(entry) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const https = require("node:https");
|
||||
const crypto = require("node:crypto");
|
||||
const dst = path.join(app.getPath("temp"), `ariadne-${entry.version}-${Date.now()}.exe`);
|
||||
const req = https.request(entry.url, {
|
||||
method: "GET", timeout: 60000,
|
||||
headers: { "user-agent": "TheseusNavigator/ariadne-updater" },
|
||||
}, (r) => {
|
||||
if (r.statusCode !== 200) { r.resume(); return reject(new Error(`download ${entry.url} -> HTTP ${r.statusCode}`)); }
|
||||
const hash = crypto.createHash("sha256");
|
||||
const out = fs.createWriteStream(dst);
|
||||
r.on("data", (c) => hash.update(c));
|
||||
r.pipe(out);
|
||||
out.on("finish", () => {
|
||||
const got = hash.digest("hex").toLowerCase();
|
||||
if (got !== entry.sha256) {
|
||||
try { fs.unlinkSync(dst); } catch {}
|
||||
return reject(new Error(`SHA-256 mismatch: got ${got}, want ${entry.sha256}`));
|
||||
}
|
||||
resolve(dst);
|
||||
});
|
||||
out.on("error", (e) => { try { fs.unlinkSync(dst); } catch {}; reject(e); });
|
||||
});
|
||||
req.on("timeout", () => req.destroy(new Error("download timeout")));
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function ariadneQueryState() {
|
||||
return new Promise((resolve) => {
|
||||
const { spawn } = require("child_process");
|
||||
|
|
@ -3014,19 +3087,23 @@ function ariadneQueryState() {
|
|||
], { windowsHide: true });
|
||||
let out = "";
|
||||
ps.stdout.on("data", (d) => { out += d; });
|
||||
ps.on("close", () => {
|
||||
ps.on("close", async () => {
|
||||
const lines = out.trim().split(/\r?\n/).filter(Boolean);
|
||||
const map = Object.fromEntries(lines.map((l) => { const i = l.lastIndexOf("="); return [l.slice(0, i), l.slice(i + 1)]; }));
|
||||
const primary = map["BNS Resolver Daemon"];
|
||||
const installedVersion = map.__VER__ || null;
|
||||
const quietUninstall = map.__UNINSTALL__ || null;
|
||||
const bundled = ariadneBundledInstaller();
|
||||
const canUpdate = installedVersion && bundled.version && cmpVersions(bundled.version, installedVersion) > 0;
|
||||
const latest = await ariadneManifestFetch();
|
||||
const latestVersion = latest ? latest.version : null;
|
||||
const canUpdate = !!(installedVersion && latestVersion && cmpVersions(latestVersion, installedVersion) > 0);
|
||||
let state;
|
||||
if (!primary || primary === "MISSING") state = installedVersion ? "stopped" : "not-installed";
|
||||
else if (primary === "Running") state = "running";
|
||||
else state = "stopped";
|
||||
resolve({ state, installedVersion, bundledVersion: bundled.version, canUpdate, hasUninstaller: !!quietUninstall });
|
||||
// The "bundledVersion" field name is kept for renderer compatibility --
|
||||
// it now carries the latest version advertised by silentmode.st's
|
||||
// releases manifest, not a version physically bundled with Theseus.
|
||||
resolve({ state, installedVersion, bundledVersion: latestVersion, canUpdate, hasUninstaller: !!quietUninstall });
|
||||
});
|
||||
ps.on("error", () => resolve({ state: "not-installed", installedVersion: null, bundledVersion: null, canUpdate: false, hasUninstaller: false }));
|
||||
});
|
||||
|
|
@ -3049,19 +3126,22 @@ function ariadneSetState(on) {
|
|||
ps.on("error", (e) => reject(e));
|
||||
});
|
||||
}
|
||||
// Run the bundled Ariadne installer silently, elevated. Inno Setup with
|
||||
// /VERYSILENT /SUPPRESSMSGBOXES /NORESTART finishes without user interaction
|
||||
// after the initial UAC prompt.
|
||||
function ariadneInstall() {
|
||||
const bundled = ariadneBundledInstaller();
|
||||
if (!bundled.path) return Promise.reject(new Error("bundled installer not found"));
|
||||
// Fetch manifest -> download+verify AriadneResolver-Setup-<v>.exe -> spawn
|
||||
// Inno silently+elevated (/VERYSILENT /SUPPRESSMSGBOXES /NORESTART). The
|
||||
// downloaded .exe is deleted whether the install succeeds or fails, so a
|
||||
// wrong-hash abort never leaves a suspect binary behind.
|
||||
async function ariadneInstall() {
|
||||
const entry = await ariadneManifestFetch();
|
||||
if (!entry) throw new Error("could not reach silentmode.st releases manifest");
|
||||
const exePath = await ariadneDownloadInstaller(entry);
|
||||
const { spawn } = require("child_process");
|
||||
return new Promise((resolve, reject) => {
|
||||
const ps = spawn("powershell.exe", ["-NoProfile", "-Command",
|
||||
`Start-Process -FilePath '${bundled.path.replace(/'/g, "''")}' -ArgumentList '/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART' -Verb RunAs -Wait`
|
||||
`Start-Process -FilePath '${exePath.replace(/'/g, "''")}' -ArgumentList '/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART' -Verb RunAs -Wait`
|
||||
], { windowsHide: true });
|
||||
ps.on("close", (code) => code === 0 ? resolve(true) : reject(new Error("installer exited " + code)));
|
||||
ps.on("error", reject);
|
||||
const cleanup = () => { try { fs.unlinkSync(exePath); } catch {} };
|
||||
ps.on("close", (code) => { cleanup(); code === 0 ? resolve(true) : reject(new Error("installer exited " + code)); });
|
||||
ps.on("error", (e) => { cleanup(); reject(e); });
|
||||
});
|
||||
}
|
||||
// Run Inno's own quiet uninstaller. Reads the QuietUninstallString from the
|
||||
|
|
|
|||
|
|
@ -1,100 +0,0 @@
|
|||
; Theseus Navigator NSIS include — bundles Ariadne's Thread as an opt-out.
|
||||
;
|
||||
; UI: a dedicated wizard page with a real checkbox (pre-checked) instead of a
|
||||
; MessageBox popup. Shown FIRST in the install flow because electron-builder's
|
||||
; template places our `!include` before its own MUI_PAGE_* inserts and NSIS
|
||||
; processes page directives in file order — so this is the earliest hook
|
||||
; we have without editing the template. Framed as "Options" so it reads
|
||||
; naturally as a preamble before the standard Welcome/License/Directory pages.
|
||||
; Skipped entirely (`Abort`) if Ariadne's Thread is already installed, so
|
||||
; upgrades of Theseus don't nag.
|
||||
;
|
||||
; The uninstall side stays a symmetric MessageBox (a full custom uninstaller
|
||||
; page is overkill for one yes/no during a rare event).
|
||||
|
||||
!include "nsDialogs.nsh"
|
||||
!include "LogicLib.nsh"
|
||||
!include "MUI2.nsh"
|
||||
|
||||
Var AriadneCheckbox
|
||||
Var InstallAriadneFlag
|
||||
|
||||
!define ARIADNE_INSTALLER "AriadneResolver-Setup-0.1.0.exe"
|
||||
!define ARIADNE_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\{7E7A5F1C-3B4E-4C8A-9E1D-ARIADNERSLVR}_is1"
|
||||
|
||||
Page custom AriadnePageCreate AriadnePageLeave
|
||||
|
||||
Function AriadnePageCreate
|
||||
; Silent install (/S) — no user, no page. Default to "don't touch Ariadne";
|
||||
; the whole point of /S is unattended, and existing Ariadne stays as-is.
|
||||
; nsDialogs::Create in silent mode returns a dialog handle we can't display,
|
||||
; so we short-circuit BEFORE calling any nsDialogs API.
|
||||
IfSilent ariadne_page_silent ariadne_page_check
|
||||
ariadne_page_silent:
|
||||
StrCpy $InstallAriadneFlag "0"
|
||||
Return
|
||||
|
||||
ariadne_page_check:
|
||||
; Skip the page entirely if Ariadne's Thread is already on this machine —
|
||||
; nothing to offer. Registry lookup uses HKLM 64-bit view because Ariadne's
|
||||
; Inno script sets ArchitecturesInstallIn64BitMode=x64compatible.
|
||||
SetRegView 64
|
||||
ReadRegStr $R0 HKLM "${ARIADNE_UNINST_KEY}" "UninstallString"
|
||||
SetRegView lastused
|
||||
StrCmp $R0 "" ariadne_page_show 0
|
||||
StrCpy $InstallAriadneFlag "0"
|
||||
Abort
|
||||
|
||||
ariadne_page_show:
|
||||
!insertmacro MUI_HEADER_TEXT "Optional add-ons" "Choose whether to also install Ariadne's Thread alongside Theseus Navigator."
|
||||
|
||||
nsDialogs::Create 1018
|
||||
Pop $0
|
||||
StrCmp $0 "error" ariadne_page_bail 0
|
||||
|
||||
${NSD_CreateLabel} 0 0 100% 44u "Ariadne's Thread is a small system-wide resolver that lets BCDN — Bitcoin Cash Domain Names — work in Chrome, Edge, Firefox, and every other browser on this machine.$\r$\n$\r$\nWithout it, only Theseus resolves these names. You can install or remove it later."
|
||||
Pop $0
|
||||
|
||||
${NSD_CreateCheckbox} 0 60u 100% 12u "Install Ariadne's Thread (recommended)"
|
||||
Pop $AriadneCheckbox
|
||||
${NSD_Check} $AriadneCheckbox
|
||||
|
||||
${NSD_CreateLabel} 0 80u 100% 20u "It runs a UAC (admin) prompt of its own during install. Theseus Navigator's own setup continues after this page."
|
||||
Pop $0
|
||||
|
||||
nsDialogs::Show
|
||||
Return
|
||||
|
||||
ariadne_page_bail:
|
||||
StrCpy $InstallAriadneFlag "0"
|
||||
Abort
|
||||
FunctionEnd
|
||||
|
||||
Function AriadnePageLeave
|
||||
${NSD_GetState} $AriadneCheckbox $InstallAriadneFlag
|
||||
FunctionEnd
|
||||
|
||||
!macro customInstall
|
||||
; Read the state saved from AriadnePageLeave. BST_CHECKED == 1.
|
||||
${If} $InstallAriadneFlag == ${BST_CHECKED}
|
||||
; Ariadne's Inno installer runs its own UAC prompt (PrivilegesRequired=admin).
|
||||
; ExecWait blocks until Ariadne finishes or the user cancels UAC; either
|
||||
; way, Theseus install continues normally afterwards.
|
||||
ExecWait '"$INSTDIR\resources\${ARIADNE_INSTALLER}" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART'
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
!macro customUnInstall
|
||||
; Only offer to remove Ariadne if it's actually installed.
|
||||
SetRegView 64
|
||||
ReadRegStr $R0 HKLM "${ARIADNE_UNINST_KEY}" "UninstallString"
|
||||
SetRegView lastused
|
||||
StrCmp $R0 "" ariadne_skip_uninstall
|
||||
|
||||
MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON1 \
|
||||
"Also uninstall Ariadne's Thread?$\r$\n$\r$\nRemoving it makes Bitcoin Cash names stop resolving in Chrome, Edge, Firefox, and every other browser on this machine. (Theseus's own uninstall does not depend on this — say No to keep the system-wide resolver.)" \
|
||||
IDNO ariadne_skip_uninstall
|
||||
ExecWait '$R0 /VERYSILENT /SUPPRESSMSGBOXES /NORESTART'
|
||||
|
||||
ariadne_skip_uninstall:
|
||||
!macroend
|
||||
Loading…
Add table
Reference in a new issue