Compare commits

...
5 Commits
Author SHA1 Message Date
archipelago d4018a6e73 chore: prepare release v1.8.6-alpha 2026-08-31 15:34:51 -04:00
archipelago b57cba63d1 Merge remote-tracking branch 'gitea-vps2/main'
Demo images / Build & push demo images (push) Failing after 36s
2026-08-31 15:12:59 -04:00
archipelago 7bc9f69b1f fix(settings): start What's New history at v1.8.0 2026-08-31 15:12:18 -04:00
lfg2025 913743923c Merge pull request 'docs: deploy handoff — companion 0.5.28 to the live surfaces' (#150) from companion/0.5.28-deploy-handoff into main 2026-08-31 19:01:58 +00:00
Dorian 241e8cfca4 docs: handoff — deploy companion 0.5.28 (vc48) to the live surfaces
For the archi-dev-box agent: companion 0.5.28 is on main (PR #149) and
Gitea raw serves it (verified byte-identical, v1+v2+v3). Remaining: the
foundation server's static /packages mirror (the real-node QR download
URL — currently 0.5.27), node web-bundle redeploys (same as the
2026-07-23 flow), and confirming the demo stack flipped after CI's
webhook redeploy. Exact commands, expected shasum, and final verify
block included.
2026-08-31 20:01:44 +01:00
9 changed files with 205 additions and 1848 deletions
+8
View File
@@ -1,5 +1,13 @@
# Changelog
## v1.8.6-alpha (2026-08-31)
- **Companion 0.5.28 is included in the node download this time, with the work that missed v1.8.5.** The companion hub can back up and restore its node list, act as a NIP-46 remote signer, and shows each paired node's FIPS mesh address with tap-to-copy. For Termux users, the included `fipssh` helper turns a durable node npub into its mesh address, so `fipssh user@npub1…` can reach SSH once that node has explicitly allowed port 22. The node-side “SSH over mesh” firewall toggle is not claimed here—it still needs implementation and remains off by default.
- **What's New now starts cleanly at v1.8.0 and is guaranteed to be newest-first.** Older alpha history no longer overwhelms the useful recent changes, the three stray v1.7 entries that appeared above current releases are gone, and the release check now fails if either the ordering or the v1.8.0 history floor drifts again.
- **A release can no longer advertise itself before its files exist.** New releases are prepared behind a pending manifest; the publisher uploads the backend and frontend, downloads both back and verifies their size and hash, and only then promotes the signed manifest to the path nodes read. The manifest generator also includes every curated What's New item instead of silently stopping after the first ten physical changelog lines.
## v1.8.5-alpha (2026-08-30)
- **Cuprate — an independent Monero node — is now an app.** Monero consensus validated by a second, unrelated codebase (Rust), the same layer of security-in-depth Bitcoin gets from Knots. Review caught two problems before anything shipped: the unrestricted RPC that can move funds stayed bound to the container's loopback (never published to the node, let alone the LAN — anything on the node could previously have reached it), and its restricted RPC moved off port 18089 to avoid colliding with Penpot. Honest caveat: upstream has cut no stable release yet, so the pin tracks an exact preview build (0.1.0-preview-18-g618ff14) and moves to their first tagged release when there is one.
+1 -1
View File
@@ -104,7 +104,7 @@ dependencies = [
[[package]]
name = "archipelago"
version = "1.8.5-alpha"
version = "1.8.6-alpha"
dependencies = [
"anyhow",
"archipelago-container",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "archipelago"
version = "1.8.5-alpha"
version = "1.8.6-alpha"
edition = "2021"
license.workspace = true
description = "Archipelago Bitcoin Node OS - Native backend"
@@ -0,0 +1,74 @@
# HANDOFF — deploy companion 0.5.28 (vc48) to the live surfaces
**For: the agent on archi-dev-box.** Companion 0.5.28 shipped to `main`
today (PR #149, merge `9f1a289d` — backup & restore #128, NIP-46 remote
signer #139, companion-gated install pitch #61 residual, hub sub-pages).
The dev box verified everything it can reach; three live surfaces remain,
same shape as the 2026-07-23 deploy handoff
([`HANDOFF-2026-07-23-companion-apk-deploy.md`](HANDOFF-2026-07-23-companion-apk-deploy.md)).
## Already done and verified (do not redo)
- `neode-ui/public/packages/archipelago-companion.apk` on `main` is
**0.5.28 / versionCode 48**, clean build via `Android/ship-companion.sh`,
**v1+v2+v3 signatures verified**, meta json refreshed beside it.
- Gitea raw-on-main serves it byte-identical:
`shasum -a 256` = `fc786b46c704c5752f04fe603371365524c749734f17bd8858cf02fa2dbc34ca`
(2 bytes: 28,206,999… file size ≈ 28.2 MB).
- The foundation server's **raw-proxy** path already serves 0.5.28 (verified
via `https://source.archipelago-foundation.org/lfg2025/archy/raw/branch/main/neode-ui/public/packages/archipelago-companion.json`).
- Demo CI (`demo-images.yml`) fired on the push and redeploys the stack via
the Portainer webhook — should flip on its own; confirm only.
- Signing key unchanged (cert SHA-256 `d622e07e…ec2664d`), so phones update
**in place** over any 0.5.27 install.
## 1. Foundation server static `/packages/` mirror — the real-node QR URL
`https://source.archipelago-foundation.org/packages/archipelago-companion.apk`
is a **static dir** on the release server (openresty; still 0.5.27,
last-modified 2026-08-17). This is the exact URL real nodes' companion QR
downloads (`DEFAULT_DOWNLOAD_URL` in `CompanionIntroOverlay.vue`) — it must
flip before the release is done.
```bash
# Find the webroot once:
grep -rl "packages" /etc/openresty /etc/nginx 2>/dev/null
find / -name archipelago-companion.apk -not -path '/proc/*' 2>/dev/null
# Mirror the exact bytes from Gitea raw-on-main (no rebuild, no re-sign):
cd <that webroot>
curl -fsS -o archipelago-companion.apk http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/neode-ui/public/packages/archipelago-companion.apk
curl -fsS -o archipelago-companion.json http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/neode-ui/public/packages/archipelago-companion.json
shasum -a 256 archipelago-companion.apk
# MUST print: fc786b46c704c5752f04fe603371365524c749734f17bd8858cf02fa2dbc34ca
```
## 2. Node web-bundle redeploys
Same as 2026-07-23: redeploy the web-ui bundle from current `main` to the
active nodes — web root `/opt/archipelago/web-ui/` (NOT a `neode-ui/`
subfolder), at minimum every node the user pairs against. The APK rides in
the bundle's `packages/` dir, so this is also what makes each node's own
served QR download 0.5.28.
## 3. Confirm the demo flipped
`curl -s http://146.59.87.168:2100/packages/archipelago-companion.json`
should read 0.5.28/48 once CI's Portainer webhook redeploy lands; trigger a
stack redeploy if it lags.
## Final verify (all three must show 0.5.28 / 48)
```bash
aapt2 dump badging <downloaded apk> | head -1 # versionCode='48' versionName='0.5.28-debug'
apksigner verify -v --min-sdk-version 21 <downloaded apk> | grep scheme # v1/v2/v3 true
curl -s https://source.archipelago-foundation.org/packages/archipelago-companion.json
curl -s http://146.59.87.168:2100/packages/archipelago-companion.json
```
Then the user's on-device end-to-end: scan the node's companion QR →
installs vc48 in place → hub → Backup & Restore / Remote Signer.
Testing notes for the new features live in the closed tracker issues
(#61/#128/#139) and `docs/companion-backup-restore.md` /
`docs/companion-nip46-remote-signer.md` (the signer's e2e harness:
`Android/tools/nip46-test-client.py`).
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "neode-ui",
"version": "1.8.5-alpha",
"version": "1.8.6-alpha",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "neode-ui",
"version": "1.8.5-alpha",
"version": "1.8.6-alpha",
"dependencies": {
"@scure/bip39": "^2.2.0",
"@types/dompurify": "^3.0.5",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "neode-ui",
"private": true,
"version": "1.8.5-alpha",
"version": "1.8.6-alpha",
"type": "module",
"scripts": {
"start": "./start-dev.sh",
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,29 @@
{
"changelog": [
"**Companion 0.5.28 is included in the node download this time, with the work that missed v1.8.5.** The companion hub can back up and restore its node list, act as a NIP-46 remote signer, and shows each paired node's FIPS mesh address with tap-to-copy. For Termux users, the included `fipssh` helper turns a durable node npub into its mesh address, so `fipssh user@npub1…` can reach SSH once that node has explicitly allowed port 22. The node-side “SSH over mesh” firewall toggle is not claimed here—it still needs implementation and remains off by default.",
"**What's New now starts cleanly at v1.8.0 and is guaranteed to be newest-first.** Older alpha history no longer overwhelms the useful recent changes, the three stray v1.7 entries that appeared above current releases are gone, and the release check now fails if either the ordering or the v1.8.0 history floor drifts again.",
"**A release can no longer advertise itself before its files exist.** New releases are prepared behind a pending manifest; the publisher uploads the backend and frontend, downloads both back and verifies their size and hash, and only then promotes the signed manifest to the path nodes read. The manifest generator also includes every curated What's New item instead of silently stopping after the first ten physical changelog lines."
],
"components": [
{
"current_version": "1.8.6-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.6-alpha/archipelago",
"name": "archipelago",
"new_version": "1.8.6-alpha",
"sha256": "6171e1f46b0e2c2754a18e668f16810338a09b71a68dc365d65173ca1e4d400a",
"size_bytes": 63930112
},
{
"current_version": "1.8.6-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.6-alpha/archipelago-frontend-1.8.6-alpha.tar.gz",
"name": "archipelago-frontend-1.8.6-alpha.tar.gz",
"new_version": "1.8.6-alpha",
"sha256": "1a5b54c516f91a4ea4491e08182f0f9521ee7e27935388814007f91695f06e0f",
"size_bytes": 97769923
}
],
"release_date": "2026-08-31",
"signature": "c21dc5cb5a6caa3253c08835b65d0417a312bc8c51acdf92df990ad2110ae762b77287c479a523a8521827ec7b4695114ff9c39924cf1d30d0d622abb53b9208",
"signed_by": "did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT",
"version": "1.8.6-alpha"
}
+46 -20
View File
@@ -11,8 +11,9 @@ a block (from the curated CHANGELOG bullets) and inserts it newest-first.
python3 scripts/sync-whats-new.py --check # exit 1 if anything is missing
Dev-process bullets ("Validation passed…/pending…") are dropped — the modal is
user-facing. Only CHANGELOG versions are managed; older hand-written blocks
(pre-CHANGELOG history) are never touched or removed.
user-facing. The visible history deliberately starts at v1.8.0-alpha; older
blocks are removed so this remains a concise product history rather than an
unbounded archive.
"""
import re
import sys
@@ -25,6 +26,7 @@ MODAL = REPO / "neode-ui/src/views/settings/AccountInfoSection.vue"
MONTHS = ["", "January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"]
MIN_VISIBLE_VERSION = (1, 8, 0)
HEADER_RE = re.compile(r"^## (v\d+\.\d+\.\d+\S*) \((\d{4})-(\d{2})-(\d{2})\)")
@@ -47,7 +49,10 @@ def parse_changelog():
if m:
ver, y, mo, d = m.groups()
cur = {"ver": ver, "date": f"{MONTHS[int(mo)]} {int(d)}, {y}", "bullets": []}
entries.append(cur)
if version_key(ver) >= MIN_VISIBLE_VERSION:
entries.append(cur)
else:
cur = None
continue
if cur is not None and line.startswith("- "):
text = line[2:].strip()
@@ -77,8 +82,8 @@ def version_key(version):
return tuple(map(int, match.groups()))
def sort_modal_blocks():
"""Sort complete release-note blocks newest-first, preserving gaps."""
def sort_modal_blocks(entries):
"""Re-render current release-note blocks newest-first and remove old history."""
lines = MODAL.read_text().splitlines(keepends=True)
marker = re.compile(r"^\s*<!-- (v\d+\.\d+\.\d+\S*) -->\s*$")
blocks = []
@@ -102,14 +107,24 @@ def sort_modal_blocks():
else:
raise RuntimeError(f"unclosed What's New block for {match.group(1)}")
sorted_segments = [b[3] for b in sorted(blocks, key=lambda b: version_key(b[2]), reverse=True)]
output = []
cursor = 0
for (start, end, _version, _segment), replacement in zip(blocks, sorted_segments):
output.extend(lines[cursor:start])
output.extend(replacement)
cursor = end
output.extend(lines[cursor:])
if not blocks:
return False
for previous, following in zip(blocks, blocks[1:]):
gap = "".join(lines[previous[1]:following[0]])
if gap.strip():
raise RuntimeError("unexpected content between What's New release blocks")
by_version = {entry["ver"]: entry for entry in entries}
retained = [b for b in blocks if version_key(b[2]) >= MIN_VISIBLE_VERSION]
sorted_segments = [
render_block(by_version[b[2]]).splitlines(keepends=True)
if b[2] in by_version else b[3]
for b in sorted(retained, key=lambda b: version_key(b[2]), reverse=True)
]
output = lines[:blocks[0][0]]
for segment in sorted_segments:
output.extend(segment)
output.extend(lines[blocks[-1][1]:])
changed = output != lines
if changed:
MODAL.write_text("".join(output))
@@ -117,8 +132,11 @@ def sort_modal_blocks():
def to_html(text):
text = text.replace("`", "") # drop markdown code ticks (plain prose)
return html.escape(text, quote=False) # & < > (Vue template-safe)
text = text.replace("`", "")
escaped = html.escape(text, quote=False) # & < > (Vue template-safe)
escaped = re.sub(r"\*\*(.+?)\*\*", r"<strong>\1</strong>", escaped)
escaped = re.sub(r"(?<!\*)\*([^*]+?)\*(?!\*)", r"<em>\1</em>", escaped)
return escaped
def render_block(entry):
@@ -162,10 +180,15 @@ def main():
missing = [e for e in entries if e["ver"] not in have]
expected_order = sorted(displayed, key=version_key, reverse=True)
out_of_order = displayed != expected_order
too_old = [v for v in displayed if version_key(v) < MIN_VISIBLE_VERSION]
if not missing and not out_of_order:
print("What's New modal is in sync with CHANGELOG.md "
f"({len(entries)} changelog versions, all present and newest-first).")
if not missing and not out_of_order and not too_old:
changed = False if check else sort_modal_blocks(entries)
if changed:
print("Re-rendered What's New blocks from the curated changelog.")
else:
print("What's New modal is in sync with CHANGELOG.md "
f"({len(entries)} changelog versions, all present and newest-first).")
return 0
names = ", ".join(e["ver"] for e in missing)
@@ -176,6 +199,9 @@ def main():
if out_of_order:
print("FAIL: What's New entries are not newest-first; the modal currently "
f"opens at {displayed[0]} instead of {expected_order[0]}", file=sys.stderr)
if too_old:
print("FAIL: What's New contains entries older than the v1.8.0 history floor: "
+ ", ".join(too_old), file=sys.stderr)
print("Run: python3 scripts/sync-whats-new.py", file=sys.stderr)
return 1
@@ -193,8 +219,8 @@ def main():
MODAL.write_text("".join(lines))
print(f"Inserted {len(missing)} block(s): {names}")
if sort_modal_blocks():
print("Sorted What's New blocks newest-first.")
if sort_modal_blocks(entries):
print("Normalized What's New blocks (v1.8.0+ only, newest-first).")
return 0