diff --git a/setup/install.ps1 b/setup/install.ps1 index 4d66102..e1f5261 100644 --- a/setup/install.ps1 +++ b/setup/install.ps1 @@ -9,6 +9,10 @@ param( # icann-first : ICANN wins, BCNR fallback on NXDOMAIN. # bns-first is accepted as an alias for bcnr-first. [ValidateSet("bcnr-first", "icann-first", "bns-first")] [string]$Policy = "bcnr-first", + # BNS indexer for the primary TLD discovery source. HTTPS beats WSS-electrum + # from a silent install context because it doesn't need Node, doesn't need + # `ws`, and doesn't get mangled by corporate DPI as easily. + [string]$Indexer = "https://navigate.st", # Opt-in: turn off Firefox DNS-over-HTTPS. Only needed to resolve names that # ALSO exist in ICANN (a reclaimed .de) via BCNR in Firefox -- BCNR-only names # (all .bch, etc.) already work with DoH on, because DoH returns NXDOMAIN and @@ -21,8 +25,41 @@ param( [switch]$CloseFirefox ) $ErrorActionPreference = "Stop" + +# ---- persistent install log -------------------------------------------------- +# Silent-install callers (Theseus's NSIS chain-install, `.exe /VERYSILENT`, Inno +# `runhidden`) discard our stdout/stderr, so historically any failure between +# "files copied" and "scheduled task registered" was invisible. This appends a +# timestamped transcript to a well-known path so a broken install always leaves +# forensics behind. Errors starting the transcript are non-fatal: an unloggable +# install must still complete or fail visibly. +$LogDir = Join-Path $env:ProgramData 'Ariadne' +try { New-Item -ItemType Directory -Force -Path $LogDir | Out-Null } catch {} +$LogFile = Join-Path $LogDir 'install.log' +$ErrFile = Join-Path $LogDir 'install-error.log' +try { Start-Transcript -Path $LogFile -Append -Force -IncludeInvocationHeader | Out-Null } catch {} +Write-Host "" +Write-Host ("==== install.ps1 " + (Get-Date -Format 's') + " Mode=$Mode Policy=$Policy Indexer=$Indexer ====") + +# Script-scope trap: any uncaught exception is written to install-error.log +# (separate file so a post-mortem doesn't have to grep through the whole +# transcript), the transcript is closed cleanly, then the exception is +# re-thrown so the caller (Inno's [Run], NSIS's ExecWait, or the user's PS) +# still sees a non-zero exit code and can act on it. +trap { + try { + $stamp = Get-Date -Format 's' + $msg = "[FATAL $stamp] $($_ | Out-String)`nScriptStack:`n$($_.ScriptStackTrace)`n" + Add-Content -Path $ErrFile -Value $msg -ErrorAction SilentlyContinue + Write-Host $msg -ForegroundColor Red + } catch {} + try { Stop-Transcript | Out-Null } catch {} + exit 1 +} + if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent() ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + try { Stop-Transcript | Out-Null } catch {} throw "Run this from an elevated (Administrator) PowerShell." } @@ -38,32 +75,63 @@ if (-not (Test-Path $node)) { $daemon = Join-Path $root "src\daemon\bnsd.js" $stateFile = Join-Path $PSScriptRoot "install-state.json" -# Discover the set of currently activated BCNR TLDs. Primary source is the -# per-TLD registry on the TLD beacon (Decentralized.DNS/DESIGN-tld-registry.md -# ยง7 fetchTldList); fallback to the legacy tlds.bch list-NFT -# (fetchBcnrTlds); final fallback to the local ariadne.config.json / built-in -# bootstrap set. This drives both the CA's nameConstraints extension and the -# surgical-mode NRPT rules below, so adding a new TLD on-chain is picked up by -# simply re-running this installer (no code release). -Push-Location $root -$activatedJson = & $node --input-type=module -e "import('./src/lib/resolver-web.js').then(async m=>{const ws=(await import('ws')).default;try{const list=await m.fetchTldList({WebSocket:ws});if(list&&list.length&&!(list.length===1&&list[0]==='bch')){console.log(JSON.stringify(list));return}}catch(e){console.error('fetchTldList:',String(e))}try{const legacy=await m.fetchBcnrTlds({WebSocket:ws});console.log(JSON.stringify(legacy||[]))}catch(e){console.error('fetchBcnrTlds:',String(e));console.log('[]')}}).catch(e=>{console.error(String(e));console.log('[]')})" 2>$null -Pop-Location -try { $ACTIVATED_TLDS = @(($activatedJson | ConvertFrom-Json) | ForEach-Object { [string]$_ }) } catch { $ACTIVATED_TLDS = @() } +# Discover the set of currently activated BCNR TLDs. Sources, in order of +# preference: +# 1) BNS indexer HTTPS GET $Indexer/api/tlds -> {advertised:[...]} +# Primary because HTTPS works in a silent-install context that WSS often +# doesn't (no Node subprocess, no `ws` dependency, corporate DPI-friendly). +# The indexer is authoritative -- it mirrors the on-chain TLD beacon. +# 2) Node subprocess: fetchTldList (per-TLD beacon registry, chain-direct). +# 3) Node subprocess: fetchBcnrTlds (legacy tlds.bch list-NFT, chain-direct). +# 4) ariadne.config.json shipped with the installer. +# 5) Built-in bootstrap set [bch]. +# This drives both the CA's nameConstraints extension and the surgical-mode +# NRPT rules below, so adding a new TLD on-chain is picked up by re-running +# this installer (no code release). +$ACTIVATED_TLDS = @() +$tldSource = "" + +# 1) HTTP indexer -- fastest and most reliable path. +try { + $indexerUrl = $Indexer.TrimEnd('/') + "/api/tlds" + Write-Host "[..] fetching TLD list from $indexerUrl" + # TLS 1.2 for Windows PowerShell 5.1 (default is TLS 1.0/1.1 on older boxes). + try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 } catch {} + $resp = Invoke-RestMethod -Uri $indexerUrl -TimeoutSec 10 -UseBasicParsing -Headers @{ 'User-Agent' = 'AriadneResolverInstaller/1' } + if ($resp -and $resp.advertised -and $resp.advertised.Count -gt 0) { + $ACTIVATED_TLDS = @($resp.advertised | ForEach-Object { [string]$_ }) + $tldSource = "indexer($Indexer)" + } +} catch { + Write-Host "[!] indexer unreachable: $($_.Exception.Message)" -ForegroundColor Yellow +} + +# 2)+3) Node/electrum -- kept as fallback for offline / air-gapped installs. +if ($ACTIVATED_TLDS.Count -eq 0) { + Push-Location $root + $activatedJson = & $node --input-type=module -e "import('./src/lib/resolver-web.js').then(async m=>{const ws=(await import('ws')).default;try{const list=await m.fetchTldList({WebSocket:ws});if(list&&list.length&&!(list.length===1&&list[0]==='bch')){console.log(JSON.stringify(list));return}}catch(e){console.error('fetchTldList:',String(e))}try{const legacy=await m.fetchBcnrTlds({WebSocket:ws});console.log(JSON.stringify(legacy||[]))}catch(e){console.error('fetchBcnrTlds:',String(e));console.log('[]')}}).catch(e=>{console.error(String(e));console.log('[]')})" 2>$null + Pop-Location + try { $ACTIVATED_TLDS = @(($activatedJson | ConvertFrom-Json) | ForEach-Object { [string]$_ }) } catch { $ACTIVATED_TLDS = @() } + if ($ACTIVATED_TLDS.Count -gt 0) { $tldSource = "electrum" } +} + +# 4)+5) Config-shipped list, then bootstrap. if ($ACTIVATED_TLDS.Count -eq 0) { - # Final fallback: read the ariadne.config.json tlds array we ship with. try { $cfg = Get-Content (Join-Path $root "ariadne.config.json") -Raw | ConvertFrom-Json if ($cfg.tlds -and $cfg.tlds.Count -gt 0) { $ACTIVATED_TLDS = @($cfg.tlds | ForEach-Object { [string]$_ }) - Write-Host "[!] on-chain TLD registry unreachable -- using ariadne.config.json fallback" -ForegroundColor Yellow + $tldSource = "ariadne.config.json" + Write-Host "[!] all on-chain sources unreachable -- using ariadne.config.json fallback" -ForegroundColor Yellow } } catch {} if ($ACTIVATED_TLDS.Count -eq 0) { Write-Host "[!] no TLD source available -- using bootstrap set: [bch]" -ForegroundColor Yellow $ACTIVATED_TLDS = @("bch") + $tldSource = "bootstrap" } } -Write-Host "[ok] activated TLDs (chain > config > bootstrap): $($ACTIVATED_TLDS -join ', ')" +Write-Host "[ok] activated TLDs (source: $tldSource): $($ACTIVATED_TLDS -join ', ')" # Generate the name-constrained local root CA now (on THIS machine; the key never # ships) so the trust-store import below always finds a valid, constrained root @@ -223,3 +291,4 @@ if ($FirefoxNoDoH) { $state | ConvertTo-Json -Depth 5 | Set-Content $stateFile Clear-DnsClientCache Write-Host "[done] try: https://hello.bch (uninstall: .\setup\uninstall.ps1)" +try { Stop-Transcript | Out-Null } catch {}