fix: replace cached container doctor before ISO first boot
This commit is contained in:
@@ -385,3 +385,16 @@ are restored with the safe script. Dev's native Bitcoin/LND stayed running;
|
|||||||
all-container dev acceptance remains pending the companion-loop backend fix.
|
all-container dev acceptance remains pending the companion-loop backend fix.
|
||||||
Final-source UI suite: 1,133 passed. Heavy backend compilation is serialized with
|
Final-source UI suite: 1,133 passed. Heavy backend compilation is serialized with
|
||||||
remaining build steps to reduce memory/IO pressure on the syncing dev node.
|
remaining build steps to reduce memory/IO pressure on the syncing dev node.
|
||||||
|
|
||||||
|
Final source release gates at `96fb5a4f`: 1,613 backend tests passed, zero failed,
|
||||||
|
four explicitly ignored; 1,133 UI tests passed; type-check, production UI build,
|
||||||
|
catalog/trust, shell, pruning, LND readiness, NPM migration and doctor regressions
|
||||||
|
passed. The isolated companion-loop regression passed independently as well.
|
||||||
|
|
||||||
|
ISO cache hardening: the installer now carries the current doctor script and
|
||||||
|
service/timer separately from rootfs.tar and overwrites both historical and active
|
||||||
|
script locations before first boot. This prevents a cached base image restoring
|
||||||
|
the old recovery code. A regression executes the actual installer block against
|
||||||
|
stale disposable files twice and confirms a missing safety payload fails closed.
|
||||||
|
The mounted-ISO smoke test also compares all three overlay files to source.
|
||||||
|
The final ISO build captures the exact newly deployed OTA UI/runtime payload.
|
||||||
|
|||||||
@@ -2607,6 +2607,11 @@ if [ -f "$SCRIPT_DIR/../../scripts/image-versions.sh" ]; then
|
|||||||
echo " ✅ Bundled image-versions.sh"
|
echo " ✅ Bundled image-versions.sh"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Always overlay the current doctor, including when rootfs.tar is cached.
|
||||||
|
cp "$SCRIPT_DIR/../../scripts/container-doctor.sh" "$ARCH_DIR/scripts/"
|
||||||
|
cp "$SCRIPT_DIR/../configs/archipelago-doctor.service" "$ARCH_DIR/scripts/"
|
||||||
|
cp "$SCRIPT_DIR/../configs/archipelago-doctor.timer" "$ARCH_DIR/scripts/"
|
||||||
|
|
||||||
# Build-source apps need their complete contexts even on unbundled ISOs.
|
# Build-source apps need their complete contexts even on unbundled ISOs.
|
||||||
# Keep this identical to the OTA runtime payload; a per-app allowlist silently
|
# 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%.
|
# omitted GitWorkshop, FIPS and Cuprate and made fresh installs fail at 70%.
|
||||||
@@ -3230,6 +3235,18 @@ for test_script in run-e2e-tests.sh run-post-install-tests.sh; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# BEGIN DOCTOR OVERLAY
|
||||||
|
# Replace both the active and historical script locations before first boot.
|
||||||
|
# A cached rootfs can contain the unsafe network recovery implementation.
|
||||||
|
mkdir -p /mnt/target/opt/archipelago/scripts /mnt/target/home/archipelago/archy/scripts
|
||||||
|
for doctor_dir in /mnt/target/opt/archipelago/scripts /mnt/target/home/archipelago/archy/scripts; do
|
||||||
|
install -m 755 "$BOOT_MEDIA/archipelago/scripts/container-doctor.sh" "$doctor_dir/container-doctor.sh" || exit 1
|
||||||
|
done
|
||||||
|
for doctor_unit in archipelago-doctor.service archipelago-doctor.timer; do
|
||||||
|
install -m 644 "$BOOT_MEDIA/archipelago/scripts/$doctor_unit" "/mnt/target/etc/systemd/system/$doctor_unit" || exit 1
|
||||||
|
done
|
||||||
|
# END DOCTOR OVERLAY
|
||||||
|
|
||||||
# Copy self-update script
|
# Copy self-update script
|
||||||
if [ -f "$BOOT_MEDIA/archipelago/scripts/self-update.sh" ]; then
|
if [ -f "$BOOT_MEDIA/archipelago/scripts/self-update.sh" ]; then
|
||||||
cp "$BOOT_MEDIA/archipelago/scripts/self-update.sh" /mnt/target/opt/archipelago/scripts/
|
cp "$BOOT_MEDIA/archipelago/scripts/self-update.sh" /mnt/target/opt/archipelago/scripts/
|
||||||
|
|||||||
@@ -71,6 +71,25 @@ else
|
|||||||
bad "incomplete app build payload"
|
bad "incomplete app build payload"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# The cached rootfs must never restore the unsafe historical doctor on boot.
|
||||||
|
for doctor_file in container-doctor.sh archipelago-doctor.service archipelago-doctor.timer; do
|
||||||
|
if [[ "$doctor_file" == container-doctor.sh ]]; then
|
||||||
|
doctor_source="$REPO/scripts/$doctor_file"
|
||||||
|
else
|
||||||
|
doctor_source="$REPO/image-recipe/configs/$doctor_file"
|
||||||
|
fi
|
||||||
|
if cmp -s "$doctor_source" "$MNT/archipelago/scripts/$doctor_file"; then
|
||||||
|
ok "current doctor payload: $doctor_file"
|
||||||
|
else
|
||||||
|
bad "missing/stale doctor overlay: $doctor_file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if grep -Fq '# BEGIN DOCTOR OVERLAY' "$MNT/archipelago/auto-install.sh"; then
|
||||||
|
ok "installer replaces cached doctor before first boot"
|
||||||
|
else
|
||||||
|
bad "installer lacks cached doctor replacement"
|
||||||
|
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,39 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Execute the installer's actual overlay against a stale disposable rootfs."""
|
||||||
|
import pathlib, subprocess, tempfile, shutil, unittest
|
||||||
|
ROOT = pathlib.Path(__file__).resolve().parents[2]
|
||||||
|
class DoctorOverlayTests(unittest.TestCase):
|
||||||
|
def test_cached_rootfs_and_missing_payload(self):
|
||||||
|
source = (ROOT / 'image-recipe/_archived/build-auto-installer-iso.sh').read_text()
|
||||||
|
block = source.split('# BEGIN DOCTOR OVERLAY\n', 1)[1].split('# END DOCTOR OVERLAY', 1)[0]
|
||||||
|
with tempfile.TemporaryDirectory() as temp:
|
||||||
|
base = pathlib.Path(temp)
|
||||||
|
target = base / 'target'
|
||||||
|
media = base / 'media'
|
||||||
|
payload = media / 'archipelago/scripts'
|
||||||
|
payload.mkdir(parents=True)
|
||||||
|
units = target / 'etc/systemd/system'
|
||||||
|
units.mkdir(parents=True)
|
||||||
|
for directory in ['opt/archipelago/scripts', 'home/archipelago/archy/scripts']:
|
||||||
|
dest = target / directory
|
||||||
|
dest.mkdir(parents=True)
|
||||||
|
(dest / 'container-doctor.sh').write_text('UNSAFE OLD SCRIPT')
|
||||||
|
files = [ROOT / 'scripts/container-doctor.sh',
|
||||||
|
ROOT / 'image-recipe/configs/archipelago-doctor.service',
|
||||||
|
ROOT / 'image-recipe/configs/archipelago-doctor.timer']
|
||||||
|
for path in files:
|
||||||
|
shutil.copyfile(path, payload / path.name)
|
||||||
|
script = block.replace('/mnt/target', str(target))
|
||||||
|
for _ in range(2):
|
||||||
|
subprocess.run(['bash', '-c', script], env={'BOOT_MEDIA': str(media), 'PATH': '/usr/bin:/bin'}, check=True)
|
||||||
|
for directory in ['opt/archipelago/scripts', 'home/archipelago/archy/scripts']:
|
||||||
|
dest = target / directory / 'container-doctor.sh'
|
||||||
|
self.assertEqual(dest.read_bytes(), files[0].read_bytes())
|
||||||
|
self.assertEqual(dest.stat().st_mode & 0o777, 0o755)
|
||||||
|
for path in files[1:]:
|
||||||
|
self.assertEqual((units / path.name).read_bytes(), path.read_bytes())
|
||||||
|
(payload / 'container-doctor.sh').unlink()
|
||||||
|
failed = subprocess.run(['bash', '-c', script], env={'BOOT_MEDIA': str(media), 'PATH': '/usr/bin:/bin'}, capture_output=True)
|
||||||
|
self.assertNotEqual(failed.returncode, 0, 'Missing safety overlay must fail installation')
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -74,6 +74,7 @@ stage "cargo-fmt" timeout 240 cargo fmt --manifest-path core/Cargo.toml -
|
|||||||
stage "app-build-contexts" python3 tests/regression/app-build-contexts.py
|
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 "npm-tunnel-migration" python3 -m unittest discover -s scripts/tests -p test_repair_npm_tunnel.py
|
stage "npm-tunnel-migration" python3 -m unittest discover -s scripts/tests -p test_repair_npm_tunnel.py
|
||||||
|
stage "iso-doctor-overlay" python3 tests/regression/iso-doctor-overlay.py
|
||||||
stage "doctor-egress" bash tests/regression/container-doctor-egress.sh
|
stage "doctor-egress" bash tests/regression/container-doctor-egress.sh
|
||||||
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