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.
|
||||
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.
|
||||
|
||||
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"
|
||||
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.
|
||||
# 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%.
|
||||
@@ -3230,6 +3235,18 @@ for test_script in run-e2e-tests.sh run-post-install-tests.sh; do
|
||||
fi
|
||||
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
|
||||
if [ -f "$BOOT_MEDIA/archipelago/scripts/self-update.sh" ]; then
|
||||
cp "$BOOT_MEDIA/archipelago/scripts/self-update.sh" /mnt/target/opt/archipelago/scripts/
|
||||
|
||||
@@ -71,6 +71,25 @@ else
|
||||
bad "incomplete app build payload"
|
||||
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 ───────────────────────────────────
|
||||
if grep -q "boot=live" "$MNT/boot/grub/grub.cfg" 2>/dev/null; then
|
||||
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 "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 "iso-doctor-overlay" python3 tests/regression/iso-doctor-overlay.py
|
||||
stage "doctor-egress" bash tests/regression/container-doctor-egress.sh
|
||||
stage "doctor-ports" bash tests/regression/container-doctor-ports.sh
|
||||
stage "bitcoin-pruning" python3 tests/regression/bitcoin-prune-entrypoint.py
|
||||
|
||||
Reference in New Issue
Block a user