fix(installer): ship all app build contexts and refresh GitWorkshop dependencies

This commit is contained in:
archipelago
2026-09-30 09:09:21 -04:00
parent eb39391223
commit 27d81e956d
8 changed files with 127 additions and 35 deletions
+5 -3
View File
@@ -16,9 +16,11 @@ lookup relays from the defaults. It does not replace GitWorkshop's NIP-34,
GRASP, repository browser, issue, pull-request, or review interfaces.
The separate dependency patch refreshes the npm lockfile and moves `fflate` to
0.8.3, `react-router-dom` to 7.18.3, and Vitest to 5.0.0. The resulting clean
install reports zero npm advisories; its type-check, 152 unit tests, and
Archipelago subpath production build pass. Keeping this mechanical security
0.8.3, `react-router-dom` to 7.18.3, and Vitest to 5.0.0. On 2026-09-30 the lockfile was refreshed again for `brace-expansion`
1.1.21/5.0.12, `fast-uri` 3.1.8 and `ip-address` 10.7.2 after fresh node
installs failed the retained dependency audit. The resulting clean install
reports zero npm advisories; its type-check, 152 unit tests, and Archipelago
subpath production build pass. The complete image also builds on the X250. Keeping this mechanical security
update separate makes both the upstream integration and future dependency
refreshes auditable.
@@ -1,5 +1,5 @@
diff --git a/package-lock.json b/package-lock.json
index 20631bb..0933917 100644
index 20631bb..86b6f86 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -63,7 +63,7 @@
@@ -495,9 +495,9 @@ index 20631bb..0933917 100644
- "version": "5.0.7",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
- "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
+ "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -678,9 +678,9 @@ index 20631bb..0933917 100644
- "version": "1.1.15",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
- "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
+ "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
+ "version": "1.1.21",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz",
+ "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -833,9 +833,9 @@ index 20631bb..0933917 100644
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
- "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==",
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
+ "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
+ "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
"funding": [
{
"type": "github",
@@ -859,9 +859,9 @@ index 20631bb..0933917 100644
- "version": "5.0.7",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
- "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
+ "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -893,9 +893,9 @@ index 20631bb..0933917 100644
- "version": "10.2.0",
- "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
- "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
+ "version": "10.7.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz",
+ "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==",
+ "version": "10.7.2",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz",
+ "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==",
"license": "MIT",
"engines": {
"node": ">= 12"
@@ -1445,4 +1445,3 @@ index bd7190c..6aa5a6f 100644
import { vi } from "vitest";
// Mock window.matchMedia
@@ -2607,21 +2607,14 @@ if [ -f "$SCRIPT_DIR/../../scripts/image-versions.sh" ]; then
echo " ✅ Bundled image-versions.sh"
fi
# Bundle docker UI source files for building custom UIs on first boot
# Always bundle — these are tiny HTML/CSS files, not container images
if true; then
DOCKER_UI_DIR="$SCRIPT_DIR/../../docker"
if [ -d "$DOCKER_UI_DIR" ]; then
echo " Bundling docker UI source files..."
mkdir -p "$ARCH_DIR/docker"
for ui_dir in bitcoin-ui lnd-ui electrs-ui; do
if [ -d "$DOCKER_UI_DIR/$ui_dir" ]; then
cp -r "$DOCKER_UI_DIR/$ui_dir" "$ARCH_DIR/docker/"
echo " ✅ Bundled $ui_dir source"
fi
done
fi
fi
# Build-source apps need their complete contexts even on unbundled ISOs.
# Keep this identical to the OTA runtime payload; a per-app allowlist silently
# omitted GitWorkshop, FIPS and Cuprate and made fresh installs fail at 70%.
DOCKER_UI_DIR="$SCRIPT_DIR/../../docker"
[ -d "$DOCKER_UI_DIR" ] || { echo "Missing docker build sources" >&2; exit 1; }
mkdir -p "$ARCH_DIR/docker"
cp -a "$DOCKER_UI_DIR/." "$ARCH_DIR/docker/"
python3 "$SCRIPT_DIR/../../scripts/check-app-build-contexts.py" "$ARCH_DIR"
if [ "$UNBUNDLED" = "1" ]; then
echo " ✅ Unbundled build ready (Tor setup included, no container images)"
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env python3
"""Validate build-source apps against an OTA/ISO runtime payload before shipping."""
import sys
from pathlib import Path
import yaml
def check(root: Path) -> int:
root = root.resolve()
manifests = sorted((root / 'apps').glob('*/manifest.y*ml'))
if not manifests:
raise ValueError(f'No app manifests in {root / "apps"}')
count = 0
for manifest in manifests:
app = yaml.safe_load(manifest.read_text())['app']
build = app.get('container', {}).get('build')
if not build:
continue
context = Path(build['context'])
if context.is_absolute():
context = root / context.relative_to('/opt/archipelago')
else:
context = manifest.parent / context
context = context.resolve()
if not context.is_relative_to(root) or not context.is_dir():
raise ValueError(f'{app["id"]}: missing or out-of-payload build context: {context}')
dockerfile = (context / build.get('dockerfile', 'Dockerfile')).resolve()
if not dockerfile.is_relative_to(context) or not dockerfile.is_file():
raise ValueError(f'{app["id"]}: missing or out-of-context Dockerfile: {dockerfile}')
count += 1
return count
if __name__ == '__main__':
try:
count = check(Path(sys.argv[1] if len(sys.argv) > 1 else '.'))
except (ValueError, KeyError, OSError, yaml.YAMLError) as error:
sys.exit(f'Invalid app build payload: {error}')
print(f'Validated {count} app build contexts and Dockerfiles.')
+1
View File
@@ -101,6 +101,7 @@ if [ -z "$FRONTEND_ARCHIVE" ]; then
cp -r "$PROJECT_ROOT/$runtime_path" "$RUNTIME_DIR/$runtime_path"
fi
done
python3 "$PROJECT_ROOT/scripts/check-app-build-contexts.py" "$RUNTIME_DIR"
# KEEP IN SYNC with the `for unit in [...]` array in
# core/archipelago/src/bootstrap.rs (run_runtime_assets). A unit that
# bootstrap installs but this list does not ship simply never reaches a
+7
View File
@@ -64,6 +64,13 @@ for f in live/vmlinuz live/initrd.img live/filesystem.squashfs \
fi
done
# Verify the mounted artifact carries every manifest-declared build source.
if python3 "$REPO/scripts/check-app-build-contexts.py" "$MNT/archipelago"; then
ok "app build contexts and Dockerfiles"
else
bad "incomplete app build payload"
fi
# ── GRUB must boot the live system ───────────────────────────────────
if grep -q "boot=live" "$MNT/boot/grub/grub.cfg" 2>/dev/null; then
ok "grub.cfg has boot=live"
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
"""Exercise release payload checks with complete, incomplete and escaping contexts."""
import importlib.util
import shutil
import tempfile
import unittest
from pathlib import Path
REPO = Path(__file__).resolve().parents[2]
spec = importlib.util.spec_from_file_location('contexts', REPO / 'scripts/check-app-build-contexts.py')
contexts = importlib.util.module_from_spec(spec)
spec.loader.exec_module(contexts)
class BuildPayloadTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
shutil.copytree(REPO / 'apps', self.root / 'apps')
shutil.copytree(REPO / 'docker', self.root / 'docker')
def test_complete_payload(self):
self.assertGreaterEqual(contexts.check(self.root), 6)
def test_iso_old_allowlist_rejected(self):
shutil.rmtree(self.root / 'docker/archipelago-source')
with self.assertRaisesRegex(ValueError, 'archipelago-source.*missing'):
contexts.check(self.root)
def test_missing_dockerfile_rejected(self):
(self.root / 'docker/archipelago-source/Dockerfile').unlink()
with self.assertRaisesRegex(ValueError, 'archipelago-source.*Dockerfile'):
contexts.check(self.root)
def test_context_symlink_cannot_escape_payload(self):
target = self.root / 'docker/archipelago-source'
shutil.rmtree(target)
target.symlink_to(REPO / 'docker/archipelago-source', target_is_directory=True)
with self.assertRaisesRegex(ValueError, 'out-of-payload'):
contexts.check(self.root)
def test_empty_payload_rejected(self):
shutil.rmtree(self.root / 'apps')
with self.assertRaisesRegex(ValueError, 'No app manifests'):
contexts.check(self.root)
if __name__ == '__main__':
unittest.main()
+1
View File
@@ -71,6 +71,7 @@ summary() {
# ── Stage 1: static ──────────────────────────────────────────────────
stage "git-diff-check" git diff --check
stage "cargo-fmt" timeout 240 cargo fmt --manifest-path core/Cargo.toml --all --check
stage "app-build-contexts" python3 tests/regression/app-build-contexts.py
stage "manifest-shell" python3 scripts/check-manifest-shell.py
stage "doctor-ports" bash tests/regression/container-doctor-ports.sh
stage "bitcoin-pruning" python3 tests/regression/bitcoin-prune-entrypoint.py