fix(installer): ship all app build contexts and refresh GitWorkshop dependencies
This commit is contained in:
@@ -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.
|
GRASP, repository browser, issue, pull-request, or review interfaces.
|
||||||
|
|
||||||
The separate dependency patch refreshes the npm lockfile and moves `fflate` to
|
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
|
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`
|
||||||
install reports zero npm advisories; its type-check, 152 unit tests, and
|
1.1.21/5.0.12, `fast-uri` 3.1.8 and `ip-address` 10.7.2 after fresh node
|
||||||
Archipelago subpath production build pass. Keeping this mechanical security
|
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
|
update separate makes both the upstream integration and future dependency
|
||||||
refreshes auditable.
|
refreshes auditable.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
diff --git a/package-lock.json b/package-lock.json
|
diff --git a/package-lock.json b/package-lock.json
|
||||||
index 20631bb..0933917 100644
|
index 20631bb..86b6f86 100644
|
||||||
--- a/package-lock.json
|
--- a/package-lock.json
|
||||||
+++ b/package-lock.json
|
+++ b/package-lock.json
|
||||||
@@ -63,7 +63,7 @@
|
@@ -63,7 +63,7 @@
|
||||||
@@ -495,9 +495,9 @@ index 20631bb..0933917 100644
|
|||||||
- "version": "5.0.7",
|
- "version": "5.0.7",
|
||||||
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
||||||
- "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
|
- "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
|
||||||
+ "version": "5.0.9",
|
+ "version": "5.0.12",
|
||||||
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
|
||||||
+ "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -678,9 +678,9 @@ index 20631bb..0933917 100644
|
|||||||
- "version": "1.1.15",
|
- "version": "1.1.15",
|
||||||
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||||
- "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
- "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||||
+ "version": "1.1.18",
|
+ "version": "1.1.21",
|
||||||
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz",
|
||||||
+ "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
+ "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -833,9 +833,9 @@ index 20631bb..0933917 100644
|
|||||||
- "version": "3.1.3",
|
- "version": "3.1.3",
|
||||||
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
|
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
|
||||||
- "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==",
|
- "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==",
|
||||||
+ "version": "3.1.7",
|
+ "version": "3.1.8",
|
||||||
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
|
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
|
||||||
+ "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
|
+ "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -859,9 +859,9 @@ index 20631bb..0933917 100644
|
|||||||
- "version": "5.0.7",
|
- "version": "5.0.7",
|
||||||
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
||||||
- "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
|
- "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
|
||||||
+ "version": "5.0.9",
|
+ "version": "5.0.12",
|
||||||
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
|
||||||
+ "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -893,9 +893,9 @@ index 20631bb..0933917 100644
|
|||||||
- "version": "10.2.0",
|
- "version": "10.2.0",
|
||||||
- "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
|
- "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
|
||||||
- "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
|
- "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
|
||||||
+ "version": "10.7.0",
|
+ "version": "10.7.2",
|
||||||
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz",
|
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz",
|
||||||
+ "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==",
|
+ "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
@@ -1445,4 +1445,3 @@ index bd7190c..6aa5a6f 100644
|
|||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
|
|
||||||
// Mock window.matchMedia
|
// Mock window.matchMedia
|
||||||
|
|
||||||
|
|||||||
@@ -2607,21 +2607,14 @@ if [ -f "$SCRIPT_DIR/../../scripts/image-versions.sh" ]; then
|
|||||||
echo " ✅ Bundled image-versions.sh"
|
echo " ✅ Bundled image-versions.sh"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Bundle docker UI source files for building custom UIs on first boot
|
# Build-source apps need their complete contexts even on unbundled ISOs.
|
||||||
# Always bundle — these are tiny HTML/CSS files, not container images
|
# Keep this identical to the OTA runtime payload; a per-app allowlist silently
|
||||||
if true; then
|
# omitted GitWorkshop, FIPS and Cuprate and made fresh installs fail at 70%.
|
||||||
DOCKER_UI_DIR="$SCRIPT_DIR/../../docker"
|
DOCKER_UI_DIR="$SCRIPT_DIR/../../docker"
|
||||||
if [ -d "$DOCKER_UI_DIR" ]; then
|
[ -d "$DOCKER_UI_DIR" ] || { echo "Missing docker build sources" >&2; exit 1; }
|
||||||
echo " Bundling docker UI source files..."
|
|
||||||
mkdir -p "$ARCH_DIR/docker"
|
mkdir -p "$ARCH_DIR/docker"
|
||||||
for ui_dir in bitcoin-ui lnd-ui electrs-ui; do
|
cp -a "$DOCKER_UI_DIR/." "$ARCH_DIR/docker/"
|
||||||
if [ -d "$DOCKER_UI_DIR/$ui_dir" ]; then
|
python3 "$SCRIPT_DIR/../../scripts/check-app-build-contexts.py" "$ARCH_DIR"
|
||||||
cp -r "$DOCKER_UI_DIR/$ui_dir" "$ARCH_DIR/docker/"
|
|
||||||
echo " ✅ Bundled $ui_dir source"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$UNBUNDLED" = "1" ]; then
|
if [ "$UNBUNDLED" = "1" ]; then
|
||||||
echo " ✅ Unbundled build ready (Tor setup included, no container images)"
|
echo " ✅ Unbundled build ready (Tor setup included, no container images)"
|
||||||
|
|||||||
@@ -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.')
|
||||||
@@ -101,6 +101,7 @@ if [ -z "$FRONTEND_ARCHIVE" ]; then
|
|||||||
cp -r "$PROJECT_ROOT/$runtime_path" "$RUNTIME_DIR/$runtime_path"
|
cp -r "$PROJECT_ROOT/$runtime_path" "$RUNTIME_DIR/$runtime_path"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
python3 "$PROJECT_ROOT/scripts/check-app-build-contexts.py" "$RUNTIME_DIR"
|
||||||
# KEEP IN SYNC with the `for unit in [...]` array in
|
# KEEP IN SYNC with the `for unit in [...]` array in
|
||||||
# core/archipelago/src/bootstrap.rs (run_runtime_assets). A unit that
|
# core/archipelago/src/bootstrap.rs (run_runtime_assets). A unit that
|
||||||
# bootstrap installs but this list does not ship simply never reaches a
|
# bootstrap installs but this list does not ship simply never reaches a
|
||||||
|
|||||||
@@ -64,6 +64,13 @@ for f in live/vmlinuz live/initrd.img live/filesystem.squashfs \
|
|||||||
fi
|
fi
|
||||||
done
|
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 ───────────────────────────────────
|
# ── GRUB must boot the live system ───────────────────────────────────
|
||||||
if grep -q "boot=live" "$MNT/boot/grub/grub.cfg" 2>/dev/null; then
|
if grep -q "boot=live" "$MNT/boot/grub/grub.cfg" 2>/dev/null; then
|
||||||
ok "grub.cfg has boot=live"
|
ok "grub.cfg has boot=live"
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -71,6 +71,7 @@ summary() {
|
|||||||
# ── Stage 1: static ──────────────────────────────────────────────────
|
# ── Stage 1: static ──────────────────────────────────────────────────
|
||||||
stage "git-diff-check" git diff --check
|
stage "git-diff-check" git diff --check
|
||||||
stage "cargo-fmt" timeout 240 cargo fmt --manifest-path core/Cargo.toml --all --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 "manifest-shell" python3 scripts/check-manifest-shell.py
|
||||||
stage "doctor-ports" bash tests/regression/container-doctor-ports.sh
|
stage "doctor-ports" bash tests/regression/container-doctor-ports.sh
|
||||||
stage "bitcoin-pruning" python3 tests/regression/bitcoin-prune-entrypoint.py
|
stage "bitcoin-pruning" python3 tests/regression/bitcoin-prune-entrypoint.py
|
||||||
|
|||||||
Reference in New Issue
Block a user