Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4018a6e73 | ||
|
|
b57cba63d1 | ||
|
|
7bc9f69b1f | ||
|
|
913743923c | ||
|
|
241e8cfca4 |
@@ -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.
|
||||
|
||||
Generated
+1
-1
@@ -104,7 +104,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "archipelago"
|
||||
version = "1.8.5-alpha"
|
||||
version = "1.8.6-alpha"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"archipelago-container",
|
||||
|
||||
@@ -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`).
|
||||
Generated
+2
-2
@@ -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,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
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user