upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-08-26 22:41:53 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-08-26 22:41:53 +0000
commitbbe04dc8c96b4c4766d2771d3438233bf82687c0 (patch)
tree62e5a1febfb8e9bc38d133ac7981e9ef93918484 /scripts
parent991853fa55a749d28d6f9e507ce72b289df92785 (diff)
parent9e39a50d1f110020b0841d31e4b931aa877cc448 (diff)
Merge upstream v1.2.19 into the fork
Merges upstream tag v1.2.19 (9e39a50) into merge/upstream-1.2.18 (991853f). Unlike the 1.2.15->1.2.18 replay, bc3de30 is a true ancestor of our branch via a9b4f89, so this is an ordinary three-way merge with merge-base bc3de30. Delta: 36 files, +3265/-163, the Tor onion transport (managed Tor, signed direct onion endpoints, onion-only dial mode) and the nHDNext restore fix. src/main.cpp, main.h, script.cpp/h and db.cpp/h are byte-identical between the two tags, and the poll() socket loop carries no diff hunks, so the merge is consensus-neutral and the soak-tested socket paths are unchanged. relay/ is untouched; the payout ledger (rpc.cpp) is untouched. 7 conflict blocks in 6 files, resolved as follows. Makefile -- VERSION. Ours: stays 1.2.19+sec with the naming comment updated, so artifacts from this tree cannot be mistaken for the upstream release. src/Makefile, src/makefile.mingw -- HEADERS list. Ours (no market.h) plus their tor.h. NODE_OBJS auto-merged correctly: no market.o, tor.o present. src/selftest.cpp -- known-tests list. Union: payout-ledger (ours) and managed-tor (theirs). src/main_gui.cpp -- one block. Our /dumpaddresses kept; their BtfStopManagedTor() on the StartNode failure path taken. The /rvrelay pin, /noannounce and the marketplace removal auto-merged and were verified intact. src/nostr.cpp -- two blocks in ResolveDescriptor. Signature takes both out-params (our fExchangeOk, their rawOut). The body keeps our drain-to-EOSE read -- returning early poisons the pooled socket for the next caller -- and adds their rawOut capture. Our TLS 1.2 floor (SSL_CTX_set_min_proto_version) is untouched by this merge; upstream 1.2.19 still has no floor of its own. Upstream's release commit also added four build logs and a compiled Python cache (build-1219.log, build-17.log, build-appimage.log, build-node.log, scripts/__pycache__/bitflash_chain.cpython-313.pyc). Removed here; they are build output, not source.
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/build-explorer.py128
-rwxr-xr-xscripts/make-release-checksums.sh2
-rwxr-xr-xscripts/package-windows-tor.sh187
-rw-r--r--scripts/smoke-managed-tor-onion.ps1174
4 files changed, 447 insertions, 44 deletions
diff --git a/scripts/build-explorer.py b/scripts/build-explorer.py
index 97a7f4e..dbb8e17 100755
--- a/scripts/build-explorer.py
+++ b/scripts/build-explorer.py
@@ -4,6 +4,7 @@
4import argparse 4import argparse
5import hashlib 5import hashlib
6import json 6import json
7import shutil
7import sys 8import sys
8from pathlib import Path 9from pathlib import Path
9 10
@@ -148,6 +149,11 @@ def build_explorer(raw_blocks, out_dir):
148 encoding="ascii", 149 encoding="ascii",
149 ) 150 )
150 (out / "index.html").write_text(INDEX_HTML, encoding="utf-8") 151 (out / "index.html").write_text(INDEX_HTML, encoding="utf-8")
152 (out / "style.css").write_text(STYLE_CSS, encoding="utf-8")
153 (out / "explorer.js").write_text(EXPLORER_JS, encoding="utf-8")
154 logo = Path(__file__).resolve().parents[1] / "docs" / "logo.png"
155 if logo.exists():
156 shutil.copyfile(logo, out / "logo.png")
151 return len(blocks), len(blocks) - 1 157 return len(blocks), len(blocks) - 1
152 158
153 159
@@ -167,43 +173,26 @@ INDEX_HTML = r"""<!doctype html>
167<meta charset="utf-8"> 173<meta charset="utf-8">
168<meta name="viewport" content="width=device-width, initial-scale=1"> 174<meta name="viewport" content="width=device-width, initial-scale=1">
169<title>Bitflash Block Explorer</title> 175<title>Bitflash Block Explorer</title>
170<style> 176<meta name="description" content="Read-only Bitflash block explorer generated from local blk*.dat files.">
171 :root { color-scheme: light dark; --bg:#fff; --fg:#111; --mut:#666; --line:#e2e2e2; --card:#fafafa; --acc:#2b6cb0; } 177<link rel="icon" href="logo.png" type="image/png">
172 @media (prefers-color-scheme: dark){ :root{ --bg:#0e0f11; --fg:#e6e6e6; --mut:#9aa0a6; --line:#26282c; --card:#16181b; --acc:#63b3ed; } } 178<link rel="stylesheet" href="style.css">
173 * { box-sizing: border-box; } 179<script src="explorer.js" defer></script>
174 body { font: 15px/1.5 system-ui, sans-serif; margin:0; background:var(--bg); color:var(--fg); }
175 header { padding:20px 24px; border-bottom:1px solid var(--line); display:flex; gap:16px; align-items:baseline; flex-wrap:wrap; }
176 h1 { font-size:18px; margin:0; }
177 h2 { font-size:16px; }
178 .mut { color:var(--mut); }
179 .wrap { padding:16px 24px; max-width:1100px; margin:0 auto; }
180 input { width:100%; max-width:420px; padding:9px 12px; border:1px solid var(--line); border-radius:8px; background:var(--card); color:var(--fg); font-size:14px; }
181 table { width:100%; border-collapse:collapse; margin-top:12px; }
182 th,td { text-align:left; padding:9px 10px; border-bottom:1px solid var(--line); font-variant-numeric:tabular-nums; }
183 th { font-size:12px; text-transform:uppercase; letter-spacing:.05em; color:var(--mut); }
184 tr.blk { cursor:pointer; }
185 tr.blk:hover td { background:var(--card); }
186 code { font-size:12px; word-break:break-all; }
187 .scroll { overflow-x:auto; }
188 .card { border:1px solid var(--line); border-radius:10px; background:var(--card); padding:14px 16px; margin-top:12px; }
189 .kv { display:grid; grid-template-columns:150px 1fr; gap:4px 12px; font-size:13px; }
190 .kv div:nth-child(odd){ color:var(--mut); }
191 .tx { border-top:1px solid var(--line); padding:10px 0; }
192 .io { display:grid; grid-template-columns:1fr 1fr; gap:16px; }
193 @media (max-width:640px){ .io{ grid-template-columns:1fr; } .kv{ grid-template-columns:110px 1fr; } }
194 .pill { display:inline-block; font-size:11px; padding:1px 7px; border-radius:99px; border:1px solid var(--line); color:var(--mut); }
195 button.link { background:none; border:none; color:var(--acc); cursor:pointer; font:inherit; padding:0; }
196 .note { color:var(--mut); font-size:13px; max-width:820px; }
197</style>
198</head> 180</head>
199<body> 181<body>
200<header> 182<header class="topbar">
201 <h1>Bitflash Block Explorer</h1> 183 <a class="brand" href="https://bitflash.network/"><img src="logo.png" width="22" height="22" alt="">Bitflash</a>
202 <span class="mut" id="tip"></span> 184 <nav aria-label="Bitflash sites">
203 <span style="flex:1"></span> 185 <a href="https://bitflash.network/">home</a>
204 <button class="link" id="home" style="display:none">back to blocks</button> 186 <a href="https://git.bitflash.network/bitflash/bitflash">code</a>
187 <a href="https://docs.bitflash.network/">docs</a>
188 <a href="https://releases.bitflash.network/">downloads</a>
189 <a href="https://explorer.bitflash.network/" aria-current="page">explorer</a>
190 <a href="https://status.bitflash.network/">status</a>
191 </nav>
192 <span class="meta" id="tip"></span>
205</header> 193</header>
206<div class="wrap"> 194<div class="wrap">
195 <h1>Block explorer <button class="link" id="home" hidden>back to blocks</button></h1>
207 <p class="note">Read-only view of the main chain: blocks, their transactions, and the coinbase payout. There is no mempool and no per-address history here.</p> 196 <p class="note">Read-only view of the main chain: blocks, their transactions, and the coinbase payout. There is no mempool and no per-address history here.</p>
208 <div id="listview"> 197 <div id="listview">
209 <input id="q" placeholder="Search by height or block hash"> 198 <input id="q" placeholder="Search by height or block hash">
@@ -213,9 +202,63 @@ INDEX_HTML = r"""<!doctype html>
213 </table></div> 202 </table></div>
214 <p class="mut" id="more"></p> 203 <p class="mut" id="more"></p>
215 </div> 204 </div>
216 <div id="detail" style="display:none"></div> 205 <div id="detail" hidden></div>
217</div> 206</div>
218<script> 207</body>
208</html>
209"""
210
211
212STYLE_CSS = r""":root{
213 color-scheme: light dark;
214 --bg:#fff; --bg-2:#f6f6f6; --fg:#111; --dim:#6b6b6b; --line:#d7d7d7; --line-hard:#111;
215 --accent:#0b7a34; --accent-2:#12a047; --bar:3rem;
216 --mono: ui-monospace,"Cascadia Mono","Segoe UI Mono",Consolas,"DejaVu Sans Mono","Liberation Mono","Courier New",monospace;
217}
218@media (prefers-color-scheme: dark){:root{
219 --bg:#000; --bg-2:#0b0b0b; --fg:#dcdcdc; --dim:#8a8a8a; --line:#242424; --line-hard:#4a4a4a;
220 --accent:#2ee06a; --accent-2:#6bff9f;
221}}
222*{box-sizing:border-box;}
223html,body{margin:0;background:var(--bg);color:var(--fg);font:15px/1.55 var(--mono);-webkit-text-size-adjust:100%;}
224a{color:var(--accent);}
225.topbar{display:flex;align-items:center;gap:1rem;flex-wrap:wrap;min-height:var(--bar);
226 padding:.5rem clamp(.9rem,2.5vw,3rem);border-bottom:1px solid var(--line);position:sticky;top:0;background:var(--bg);z-index:5;}
227.brand{display:flex;align-items:center;gap:.6rem;text-decoration:none;color:var(--fg);font-weight:bold;}
228.brand img{width:22px;height:22px;display:block;}
229.topbar nav{display:flex;flex-wrap:wrap;gap:.15rem 1.4rem;font-size:.86rem;}
230.topbar nav a{text-decoration:none;color:var(--dim);}
231.topbar nav a::before{content:"/";color:var(--line-hard);margin-right:.3rem;}
232.topbar nav a:hover{color:var(--fg);text-decoration:underline;}
233.topbar nav a[aria-current="page"]{color:var(--accent);font-weight:bold;}
234.topbar nav a[aria-current="page"]::before{color:var(--accent);}
235.topbar .meta{margin-left:auto;font-size:.76rem;color:var(--dim);letter-spacing:.06em;}
236.wrap{padding:1.4rem clamp(.9rem,2.5vw,3rem) 4rem;}
237h1{font-size:1rem;margin:0 0 .3rem;letter-spacing:.02em;}
238h2{font-size:.95rem;margin:1.2rem 0 .4rem;}
239.mut{color:var(--dim);}
240.note{color:var(--dim);font-size:.82rem;max-width:60rem;margin:.2rem 0 1rem;}
241input{width:100%;max-width:28rem;padding:.5rem .7rem;border:1px solid var(--line);background:var(--bg-2);color:var(--fg);font:inherit;font-size:.86rem;}
242input:focus{outline:none;border-color:var(--accent);}
243.scroll{overflow-x:auto;}
244table{width:100%;border-collapse:collapse;margin-top:.8rem;font-size:.86rem;}
245th,td{text-align:left;padding:.5rem .6rem;border-bottom:1px solid var(--line);font-variant-numeric:tabular-nums;}
246th{font-size:.72rem;text-transform:uppercase;letter-spacing:.1em;color:var(--dim);font-weight:normal;}
247tr.blk{cursor:pointer;}
248tr.blk:hover td{background:var(--bg-2);}
249code{font-size:.8rem;word-break:break-all;color:var(--fg);}
250.card{border:1px solid var(--line);background:var(--bg-2);padding:.9rem 1rem;margin-top:.8rem;}
251.kv{display:grid;grid-template-columns:11rem 1fr;gap:.25rem .8rem;font-size:.82rem;}
252.kv div:nth-child(odd){color:var(--dim);}
253.tx{border-top:1px solid var(--line);padding:.7rem 0;}
254.io{display:grid;grid-template-columns:1fr 1fr;gap:1rem;}
255@media (max-width:640px){.io{grid-template-columns:1fr;}.kv{grid-template-columns:7rem 1fr;}.topbar .meta{margin-left:0;width:100%;}}
256.pill{display:inline-block;font-size:.68rem;padding:.05rem .5rem;border:1px solid var(--line);color:var(--dim);text-transform:uppercase;letter-spacing:.05em;}
257button.link{background:none;border:none;color:var(--accent);cursor:pointer;font:inherit;padding:0;text-decoration:underline;}
258"""
259
260
261EXPLORER_JS = r"""
219var SUM = [], SHOWN = 200; 262var SUM = [], SHOWN = 200;
220function btf(sat){ return (sat/1e8).toFixed(8).replace(/0+$/,'').replace(/\.$/,'')+" BTF"; } 263function btf(sat){ return (sat/1e8).toFixed(8).replace(/0+$/,'').replace(/\.$/,'')+" BTF"; }
221function ts(t){ return new Date(t*1000).toISOString().replace('T',' ').replace('.000Z',' UTC'); } 264function ts(t){ return new Date(t*1000).toISOString().replace('T',' ').replace('.000Z',' UTC'); }
@@ -310,25 +353,22 @@ function openBlock(h){
310 })); 353 }));
311 append(tx, head, io); append(detail, tx); 354 append(tx, head, io); append(detail, tx);
312 }); 355 });
313 document.getElementById('listview').style.display='none'; 356 document.getElementById('listview').hidden = true;
314 detail.style.display='block'; 357 detail.hidden = false;
315 document.getElementById('home').style.display='inline'; 358 document.getElementById('home').hidden = false;
316 window.scrollTo(0,0); 359 window.scrollTo(0,0);
317 }); 360 });
318} 361}
319document.getElementById('home').onclick=function(){ 362document.getElementById('home').onclick=function(){
320 document.getElementById('detail').style.display='none'; 363 document.getElementById('detail').hidden = true;
321 document.getElementById('listview').style.display='block'; 364 document.getElementById('listview').hidden = false;
322 this.style.display='none'; 365 this.hidden = true;
323}; 366};
324document.getElementById('q').oninput=function(){ renderList(this.value); }; 367document.getElementById('q').oninput=function(){ renderList(this.value); };
325fetch('blocks.json').then(function(r){return r.json();}).then(function(d){ 368fetch('blocks.json').then(function(r){return r.json();}).then(function(d){
326 SUM=d.blocks; document.getElementById('tip').textContent='tip height '+d.tipHeight+' - '+d.count+' blocks'; 369 SUM=d.blocks; document.getElementById('tip').textContent='tip height '+d.tipHeight+' - '+d.count+' blocks';
327 renderList(''); 370 renderList('');
328}); 371});
329</script>
330</body>
331</html>
332""" 372"""
333 373
334 374
diff --git a/scripts/make-release-checksums.sh b/scripts/make-release-checksums.sh
index 5b29e2d..9c4f45a 100755
--- a/scripts/make-release-checksums.sh
+++ b/scripts/make-release-checksums.sh
@@ -10,6 +10,7 @@ Usage: scripts/make-release-checksums.sh [--sign] [--local-user KEYID]
10 10
11Creates SHA256SUMS for release assets in the current directory: 11Creates SHA256SUMS for release assets in the current directory:
12 Bitflash-*-windows.zip 12 Bitflash-*-windows.zip
13 Bitflash-*-windows-with-tor.zip
13 Bitflash-*-x86_64.AppImage 14 Bitflash-*-x86_64.AppImage
14 bitflash-node-*-x86_64 15 bitflash-node-*-x86_64
15 16
@@ -44,6 +45,7 @@ fi
44assets=() 45assets=()
45for pattern in \ 46for pattern in \
46 "Bitflash-*-windows.zip" \ 47 "Bitflash-*-windows.zip" \
48 "Bitflash-*-windows-with-tor.zip" \
47 "Bitflash-*-x86_64.AppImage" \ 49 "Bitflash-*-x86_64.AppImage" \
48 "bitflash-node-*-x86_64" 50 "bitflash-node-*-x86_64"
49do 51do
diff --git a/scripts/package-windows-tor.sh b/scripts/package-windows-tor.sh
new file mode 100755
index 0000000..c8cca3b
--- /dev/null
+++ b/scripts/package-windows-tor.sh
@@ -0,0 +1,187 @@
1#!/usr/bin/env bash
2set -euo pipefail
3
4ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5VERSION="${VERSION:-$(sed -n 's/^VERSION[[:space:]]*:= *//p' "$ROOT/Makefile" | head -1)}"
6
7TOR_VERSION="${TOR_VERSION:-15.0.19}"
8TOR_ARCHIVE_NAME="tor-expert-bundle-windows-x86_64-${TOR_VERSION}.tar.gz"
9TOR_URL="${TOR_URL:-https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/${TOR_ARCHIVE_NAME}}"
10TOR_SHA256="${TOR_SHA256:-6AC067402C7B4A3DC37887ED3754B3914B67FDC220C966190683E9CCF91ABF0F}"
11TOR_ASC_URL="${TOR_ASC_URL:-${TOR_URL}.asc}"
12TOR_SIGNING_KEY_FINGERPRINT="${TOR_SIGNING_KEY_FINGERPRINT:-EF6E286DDA85EA2A4BA7DE684E2C6E8793298290}"
13TOR_VERIFY_GPG="${TOR_VERIFY_GPG:-auto}"
14
15BUNDLE_DIR="${BUNDLE_DIR:-$ROOT/Bitflash-${VERSION}-windows}"
16OUT_ZIP="${OUT_ZIP:-$ROOT/Bitflash-${VERSION}-windows-with-tor.zip}"
17CACHE_DIR="${CACHE_DIR:-$ROOT/.cache}"
18ARCHIVE="${TOR_ARCHIVE:-$CACHE_DIR/$TOR_ARCHIVE_NAME}"
19ASC_FILE="${TOR_ASC:-$ARCHIVE.asc}"
20
21usage() {
22 cat <<EOF
23Usage: scripts/package-windows-tor.sh
24
25Creates Bitflash-${VERSION}-windows-with-tor.zip from an existing
26Bitflash-${VERSION}-windows/ release directory.
27
28Environment overrides:
29 VERSION Bitflash release version, default read from Makefile
30 TOR_VERSION Tor Expert Bundle version, default ${TOR_VERSION}
31 TOR_URL Download URL, default official Tor archive URL
32 TOR_ASC_URL Signature URL, default TOR_URL.asc
33 TOR_SHA256 Expected archive SHA256
34 TOR_ARCHIVE Use an existing archive instead of downloading
35 TOR_ASC Use an existing detached signature
36 TOR_VERIFY_GPG auto, required, or off; default auto
37 BUNDLE_DIR Existing unpacked Windows release directory
38 OUT_ZIP Output zip path
39EOF
40}
41
42if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
43 usage
44 exit 0
45fi
46
47need() {
48 command -v "$1" >/dev/null 2>&1 || {
49 echo "missing required command: $1" >&2
50 exit 1
51 }
52}
53
54need sha256sum
55need tar
56need zip
57
58download() {
59 local url="$1"
60 local out="$2"
61 if command -v curl >/dev/null 2>&1; then
62 curl -L --fail --output "$out" "$url"
63 elif command -v wget >/dev/null 2>&1; then
64 wget -O "$out" "$url"
65 else
66 echo "missing required command: curl or wget" >&2
67 exit 1
68 fi
69}
70
71if [ ! -d "$BUNDLE_DIR" ]; then
72 echo "missing Windows release directory: $BUNDLE_DIR" >&2
73 echo "run 'make windows' first, or set BUNDLE_DIR" >&2
74 exit 1
75fi
76if [ ! -f "$BUNDLE_DIR/Bitflash.exe" ]; then
77 echo "missing Bitflash.exe in $BUNDLE_DIR" >&2
78 exit 1
79fi
80BUNDLE_PARENT="$(cd "$(dirname "$BUNDLE_DIR")" && pwd)"
81BUNDLE_NAME="$(basename "$BUNDLE_DIR")"
82OUT_PARENT="$(mkdir -p "$(dirname "$OUT_ZIP")" && cd "$(dirname "$OUT_ZIP")" && pwd)"
83OUT_NAME="$(basename "$OUT_ZIP")"
84OUT_ZIP_ABS="$OUT_PARENT/$OUT_NAME"
85
86mkdir -p "$CACHE_DIR"
87if [ ! -f "$ARCHIVE" ]; then
88 download "$TOR_URL" "$ARCHIVE"
89fi
90
91got="$(sha256sum "$ARCHIVE" | awk '{print tolower($1)}')"
92want="$(printf '%s' "$TOR_SHA256" | tr 'A-F' 'a-f')"
93if [ "$got" != "$want" ]; then
94 echo "Tor archive SHA256 mismatch" >&2
95 echo " expected: $want" >&2
96 echo " got: $got" >&2
97 exit 1
98fi
99
100tmp="$(mktemp -d "${TMPDIR:-/tmp}/bitflash-tor-package.XXXXXX")"
101trap 'rm -rf "$tmp"' EXIT
102
103case "$TOR_VERIFY_GPG" in
104 auto|required|off) ;;
105 *) echo "TOR_VERIFY_GPG must be auto, required, or off" >&2; exit 2 ;;
106esac
107
108if [ "$TOR_VERIFY_GPG" != "off" ]; then
109 if command -v gpg >/dev/null 2>&1; then
110 if [ ! -f "$ASC_FILE" ]; then
111 download "$TOR_ASC_URL" "$ASC_FILE"
112 fi
113 gnupg_home="$tmp/gnupg"
114 mkdir -p "$gnupg_home"
115 chmod 0700 "$gnupg_home"
116 gpg --homedir "$gnupg_home" --batch --auto-key-locate nodefault,wkd,keyserver \
117 --locate-keys torbrowser@torproject.org >/dev/null 2>&1 || {
118 if [ "$TOR_VERIFY_GPG" = "required" ]; then
119 echo "could not import Tor Browser Developers signing key" >&2
120 exit 1
121 fi
122 echo "WARNING: skipped Tor GPG signature verification; signing key import failed" >&2
123 }
124 if gpg --homedir "$gnupg_home" --batch --with-colons --fingerprint "$TOR_SIGNING_KEY_FINGERPRINT" \
125 2>/dev/null | grep -q "^fpr:::::::::${TOR_SIGNING_KEY_FINGERPRINT}:"; then
126 gpg --homedir "$gnupg_home" --batch --verify "$ASC_FILE" "$ARCHIVE" >/dev/null 2>&1 || {
127 echo "Tor archive GPG signature verification failed" >&2
128 exit 1
129 }
130 echo "Tor archive GPG signature verified against ${TOR_SIGNING_KEY_FINGERPRINT}"
131 elif [ "$TOR_VERIFY_GPG" = "required" ]; then
132 echo "Tor signing key fingerprint mismatch" >&2
133 exit 1
134 else
135 echo "WARNING: skipped Tor GPG signature verification; signing key fingerprint was not available" >&2
136 fi
137 elif [ "$TOR_VERIFY_GPG" = "required" ]; then
138 echo "missing required command for TOR_VERIFY_GPG=required: gpg" >&2
139 exit 1
140 else
141 echo "WARNING: gpg not found; relying on pinned SHA256 only" >&2
142 fi
143fi
144
145mkdir -p "$tmp/extract"
146tar -xzf "$ARCHIVE" -C "$tmp/extract"
147
148if [ ! -f "$tmp/extract/tor/tor.exe" ]; then
149 echo "Tor archive did not contain tor/tor.exe" >&2
150 exit 1
151fi
152
153rm -rf "$BUNDLE_DIR/tor"
154mkdir -p "$BUNDLE_DIR/tor"
155cp -R "$tmp/extract/tor/." "$BUNDLE_DIR/tor/"
156if [ -d "$tmp/extract/docs" ]; then
157 mkdir -p "$BUNDLE_DIR/tor/docs"
158 cp -R "$tmp/extract/docs/." "$BUNDLE_DIR/tor/docs/"
159fi
160if [ -d "$tmp/extract/data" ]; then
161 mkdir -p "$BUNDLE_DIR/tor/data-defaults"
162 cp -R "$tmp/extract/data/." "$BUNDLE_DIR/tor/data-defaults/"
163fi
164
165cat > "$BUNDLE_DIR/TOR-EXPERT-BUNDLE.txt" <<EOF
166Bitflash managed Tor bundle
167
168This Windows package includes the Tor Expert Bundle so -managedtor can start
169Tor without requiring a separate Tor install.
170
171Tor Expert Bundle: ${TOR_VERSION}
172Archive URL: ${TOR_URL}
173Archive SHA256: ${TOR_SHA256}
174
175Bitflash uses tor/tor.exe from this package. Runtime state, hidden-service keys,
176and control authentication cookies are written under the user's Bitflash data
177directory, not inside this release directory.
178EOF
179
180rm -f "$OUT_ZIP_ABS"
181(
182 cd "$BUNDLE_PARENT"
183 zip -qr "$OUT_ZIP_ABS" "$BUNDLE_NAME"
184)
185
186echo "Bundled Tor Expert Bundle ${TOR_VERSION}"
187echo "Built: $OUT_ZIP_ABS"
diff --git a/scripts/smoke-managed-tor-onion.ps1 b/scripts/smoke-managed-tor-onion.ps1
new file mode 100644
index 0000000..70658c7
--- /dev/null
+++ b/scripts/smoke-managed-tor-onion.ps1
@@ -0,0 +1,174 @@
1param(
2 [string]$Bitflash,
3 [string]$Tor,
4 [string]$WorkDir,
5 [int]$TimeoutSec = 600,
6 [int]$OnionWarmupSec = 120
7)
8
9$ErrorActionPreference = "Stop"
10
11function Resolve-RepoRoot {
12 $scriptDir = Split-Path -Parent $PSCommandPath
13 return (Resolve-Path (Join-Path $scriptDir "..")).Path
14}
15
16function Get-FreeTcpPort {
17 $listener = [Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback, 0)
18 $listener.Start()
19 try {
20 return ([Net.IPEndPoint]$listener.LocalEndpoint).Port
21 }
22 finally {
23 $listener.Stop()
24 }
25}
26
27function Wait-RegexInFile {
28 param(
29 [string]$Path,
30 [string]$Pattern,
31 [datetime]$Deadline,
32 [string]$Label
33 )
34
35 while ((Get-Date) -lt $Deadline) {
36 if (Test-Path $Path) {
37 $text = Get-Content -Raw -ErrorAction SilentlyContinue $Path
38 if ($null -ne $text) {
39 $m = [regex]::Match($text, $Pattern)
40 if ($m.Success) {
41 return $m
42 }
43 }
44 }
45 Start-Sleep -Milliseconds 500
46 }
47 throw "timed out waiting for $Label"
48}
49
50function Stop-TestProcesses {
51 param(
52 [System.Diagnostics.Process[]]$Processes,
53 [string[]]$DataDirKeys
54 )
55
56 foreach ($p in $Processes) {
57 try {
58 $p.Refresh()
59 if (!$p.HasExited) {
60 Stop-Process -Id $p.Id -Force
61 }
62 }
63 catch {}
64 }
65
66 foreach ($key in $DataDirKeys) {
67 if ([string]::IsNullOrWhiteSpace($key)) {
68 continue
69 }
70 try {
71 Get-CimInstance Win32_Process -Filter "name = 'tor.exe'" |
72 Where-Object { $_.CommandLine -like "*$key*" } |
73 ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
74 }
75 catch {}
76 }
77}
78
79$repo = Resolve-RepoRoot
80if ([string]::IsNullOrWhiteSpace($Bitflash)) {
81 $Bitflash = Join-Path $repo "src\bitflash.exe"
82}
83if ([string]::IsNullOrWhiteSpace($Tor)) {
84 $candidates = @(
85 (Join-Path $repo "src\tor\tor.exe"),
86 (Join-Path $repo "Bitflash-$(Select-String -Path (Join-Path $repo 'Makefile') -Pattern '^VERSION[ ]*:=' | ForEach-Object { ($_.Line -split ':=')[1].Trim() })-windows\tor\tor.exe")
87 )
88 foreach ($candidate in $candidates) {
89 if (Test-Path $candidate) {
90 $Tor = $candidate
91 break
92 }
93 }
94}
95if ([string]::IsNullOrWhiteSpace($WorkDir)) {
96 $WorkDir = Join-Path $env:TEMP ("bitflash-managed-tor-two-node-" + (Get-Date -Format "yyyyMMddHHmmss"))
97}
98
99if (!(Test-Path $Bitflash)) {
100 throw "Bitflash binary not found: $Bitflash"
101}
102if (!(Test-Path $Tor)) {
103 throw "Tor binary not found. Pass -Tor or build the Windows-with-Tor package."
104}
105
106New-Item -ItemType Directory -Force -Path $WorkDir | Out-Null
107$dataA = Join-Path $WorkDir "node-a"
108$dataB = Join-Path $WorkDir "node-b"
109New-Item -ItemType Directory -Force -Path $dataA | Out-Null
110New-Item -ItemType Directory -Force -Path $dataB | Out-Null
111
112$portA = Get-FreeTcpPort
113$portB = Get-FreeTcpPort
114$deadline = (Get-Date).AddSeconds($TimeoutSec)
115$oldPath = $env:PATH
116$env:PATH = "C:\msys64\ucrt64\bin;C:\msys64\usr\bin;" + $env:PATH
117
118$procA = $null
119$procB = $null
120$dataKeys = @((Split-Path $WorkDir -Leaf))
121
122try {
123 $argsA = @(
124 "-datadir=$dataA",
125 "-port=$portA",
126 "-managedtor=$Tor",
127 "-nogui",
128 "-debug",
129 "-checkblocks=0"
130 )
131 $procA = Start-Process -FilePath $Bitflash -ArgumentList $argsA -WorkingDirectory (Split-Path $Bitflash -Parent) -PassThru -WindowStyle Hidden
132 $logA = Join-Path $dataA "debug.log"
133
134 $btfMatch = Wait-RegexInFile -Path $logA -Pattern "Nostr: node \.btf address = ([a-z2-7]+\.btf)" -Deadline $deadline -Label "node A .btf address"
135 $btfA = $btfMatch.Groups[1].Value
136
137 $onionMatch = Wait-RegexInFile -Path $logA -Pattern "Managed Tor hidden service ready: ([a-z2-7]{56}\.onion:\d+)" -Deadline $deadline -Label "node A managed onion"
138 $onionA = $onionMatch.Groups[1].Value
139
140 Wait-RegexInFile -Path $logA -Pattern "Nostr: \.btf self-resolve OK via .*" -Deadline $deadline -Label "node A descriptor self-resolve" | Out-Null
141 if ($OnionWarmupSec -gt 0) {
142 Write-Host "Waiting $OnionWarmupSec second(s) for Tor hidden-service descriptor propagation"
143 Start-Sleep -Seconds $OnionWarmupSec
144 }
145
146 $argsB = @(
147 "-datadir=$dataB",
148 "-port=$portB",
149 "-managedtor=$Tor",
150 "-oniononly",
151 "-connectbtf=$btfA",
152 "-nogui",
153 "-debug",
154 "-checkblocks=0"
155 )
156 $procB = Start-Process -FilePath $Bitflash -ArgumentList $argsB -WorkingDirectory (Split-Path $Bitflash -Parent) -PassThru -WindowStyle Hidden
157 $logB = Join-Path $dataB "debug.log"
158
159 Wait-RegexInFile -Path $logB -Pattern ("connected " + [regex]::Escape($btfA) + " via direct onion ") -Deadline $deadline -Label "node B direct onion connection to node A" | Out-Null
160
161 Write-Host "Managed Tor two-node onion smoke test passed"
162 Write-Host " node A .btf: $btfA"
163 Write-Host " node A onion: $onionA"
164 Write-Host " node A port: $portA"
165 Write-Host " node B port: $portB"
166 Write-Host " work dir: $WorkDir"
167}
168finally {
169 $env:PATH = $oldPath
170 $procs = @()
171 if ($null -ne $procA) { $procs += $procA }
172 if ($null -ne $procB) { $procs += $procB }
173 Stop-TestProcesses -Processes $procs -DataDirKeys $dataKeys
174}