fix: preserve apps and diagnostics when first-boot setup retries
This commit is contained in:
@@ -784,3 +784,34 @@ by explicitly mounting the disposable resolver fixture.
|
|||||||
Qualified web image169e59da is built from157c9ec0; backend2722fa29 frome6e46a14.
|
Qualified web image169e59da is built from157c9ec0; backend2722fa29 frome6e46a14.
|
||||||
Public demo deployment remains scheduled after release with rollback. RC2 raw
|
Public demo deployment remains scheduled after release with rollback. RC2 raw
|
||||||
ISO assembly is running; no boot/install or final OTA pass is claimed yet.
|
ISO assembly is running; no boot/install or final OTA pass is claimed yet.
|
||||||
|
|
||||||
|
### RC2 actual installation and first-boot retry correction
|
||||||
|
|
||||||
|
RC2 passed mounted payload checks and completed a full UEFI installation to an
|
||||||
|
80GiB disposable NVMe disk with encrypted data. Installed backend560, both
|
||||||
|
security helpers, dashboardc18, AIUI415 and APK54 match qualified bytes. After
|
||||||
|
boot from the installed disk, SSH, dashboard200 and backend health/version pass.
|
||||||
|
Actual installed nginx passes32 IPv4/IPv6 public-source denial requests including
|
||||||
|
unknown Host/SNI and forged forwarding headers (`/tmp/archy-190-vm-guard-test.log`).
|
||||||
|
The VM's EDAC hardware initialization service reports unsupported virtual
|
||||||
|
hardware; this is not claimed as physical ECC/EDAC acceptance.
|
||||||
|
|
||||||
|
Actual first boot exposed `log: command not found` in the unbundled setup: the
|
||||||
|
logger was declared below that path's early exit. Moving it before first use
|
||||||
|
restores diagnostics. Retry testing also demonstrated that unconditional
|
||||||
|
`podman system migrate` stops existing apps and races manager reconciliation.
|
||||||
|
The unbundled path changes no ID mappings and no longer migrates; bundled setup
|
||||||
|
only migrates when it actually adds mappings. Podman documents this stop behavior
|
||||||
|
in its [migration reference](https://docs.podman.io/en/latest/markdown/podman-system-migrate.1.html).
|
||||||
|
|
||||||
|
Corrected actual-VM retry preserves container IDs/start times and produces clean
|
||||||
|
logs: `/tmp/archy-190-vm-firstboot-logging-fix-2.log`. Executable isolated retry
|
||||||
|
regression passes twice with stored-secret preservation and fails against the
|
||||||
|
prior script. Added to release harness. RC2 must not be published: rebuild the
|
||||||
|
ISO with this script and qualify its boot/install path before signing. The OTA
|
||||||
|
backend/frontend payloads are unchanged by this installer-only correction.
|
||||||
|
|
||||||
|
Demo archive33ec4111…6fae8 matches after private server transfer; both tested image
|
||||||
|
IDs loaded successfully without changing running demo containers. Clearing only
|
||||||
|
unused build cache recovered1.743GB; no additional historical ISO, image, volume
|
||||||
|
or application data was deleted. Final publication capacity must be rechecked.
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
# DO NOT split until tested on the build server — this is critical infrastructure.
|
# DO NOT split until tested on the build server — this is critical infrastructure.
|
||||||
#
|
#
|
||||||
LOG="/var/log/archipelago-first-boot.log"
|
LOG="/var/log/archipelago-first-boot.log"
|
||||||
|
# The unbundled path exits early; diagnostics must exist before either path.
|
||||||
|
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $*" | tee -a "$LOG"; }
|
||||||
|
|
||||||
# Source pinned image versions (single source of truth)
|
# Source pinned image versions (single source of truth)
|
||||||
# ISO copies to scripts/ subdir; also check the direct path for manual installs
|
# ISO copies to scripts/ subdir; also check the direct path for manual installs
|
||||||
@@ -111,7 +113,8 @@ if [ -f "$UNBUNDLED_MARKER" ]; then
|
|||||||
# Podman prerequisites
|
# Podman prerequisites
|
||||||
loginctl enable-linger archipelago 2>/dev/null || true
|
loginctl enable-linger archipelago 2>/dev/null || true
|
||||||
DOCKER="runuser -u archipelago -- env XDG_RUNTIME_DIR=/run/user/$(id -u archipelago) podman"
|
DOCKER="runuser -u archipelago -- env XDG_RUNTIME_DIR=/run/user/$(id -u archipelago) podman"
|
||||||
$DOCKER system migrate 2>/dev/null || true
|
# No ID mappings changed here. system migrate would stop existing apps
|
||||||
|
# on a retry and race the backend's reconciliation.
|
||||||
|
|
||||||
# Ensure archy-net exists
|
# Ensure archy-net exists
|
||||||
$DOCKER network create archy-net 2>/dev/null || true
|
$DOCKER network create archy-net 2>/dev/null || true
|
||||||
@@ -245,8 +248,6 @@ fi
|
|||||||
# trying to reach bitcoin-core's RPC on the host (LND, ElectrumX, etc).
|
# trying to reach bitcoin-core's RPC on the host (LND, ElectrumX, etc).
|
||||||
ADD_HOST_FLAG="--add-host=host.containers.internal:host-gateway"
|
ADD_HOST_FLAG="--add-host=host.containers.internal:host-gateway"
|
||||||
|
|
||||||
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $*" | tee -a "$LOG"; }
|
|
||||||
|
|
||||||
# Ensure Tor is running for hidden services (LND connect, Electrumx, etc.)
|
# Ensure Tor is running for hidden services (LND connect, Electrumx, etc.)
|
||||||
if ! systemctl is-active tor >/dev/null 2>&1; then
|
if ! systemctl is-active tor >/dev/null 2>&1; then
|
||||||
log "Starting Tor..."
|
log "Starting Tor..."
|
||||||
@@ -491,9 +492,9 @@ grep -q "^archipelago:" /etc/subuid 2>/dev/null || {
|
|||||||
echo "archipelago:100000:65536" >> /etc/subuid
|
echo "archipelago:100000:65536" >> /etc/subuid
|
||||||
echo "archipelago:100000:65536" >> /etc/subgid
|
echo "archipelago:100000:65536" >> /etc/subgid
|
||||||
log " subuid/subgid configured"
|
log " subuid/subgid configured"
|
||||||
|
# Only refresh the namespace when mappings actually changed.
|
||||||
|
$DOCKER system migrate 2>/dev/null || true
|
||||||
}
|
}
|
||||||
# Apply podman migrations after subuid/subgid changes (per official tutorial)
|
|
||||||
$DOCKER system migrate 2>/dev/null || true
|
|
||||||
# Ensure /etc/hosts is readable (rootless podman needs it)
|
# Ensure /etc/hosts is readable (rootless podman needs it)
|
||||||
chmod 644 /etc/hosts 2>/dev/null
|
chmod 644 /etc/hosts 2>/dev/null
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Execute unbundled first-boot retry with temporary paths and fake system commands."""
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
root = Path(__file__).resolve().parents[2]
|
||||||
|
source = (root / 'scripts/first-boot-containers.sh').read_text()
|
||||||
|
# Exercise the real early-exit path, excluding the unrelated bundled installer.
|
||||||
|
source = source.split('TARGET_IP=$(hostname -I', 1)[0]
|
||||||
|
with tempfile.TemporaryDirectory(prefix='archy-firstboot-retry-') as directory:
|
||||||
|
tmp = Path(directory)
|
||||||
|
for original, replacement in [('/var/lib/archipelago', tmp / 'data'),
|
||||||
|
('/opt/archipelago', tmp / 'opt'),
|
||||||
|
('/home/archipelago', tmp / 'home'),
|
||||||
|
('/var/log', tmp / 'logs')]:
|
||||||
|
source = source.replace(original, str(replacement))
|
||||||
|
for folder in ['data/secrets', 'data/wireguard', 'opt', 'logs']:
|
||||||
|
(tmp / folder).mkdir(parents=True, exist_ok=True)
|
||||||
|
(tmp / 'opt/.unbundled').touch()
|
||||||
|
for name in ['bitcoin-rpc-password', 'mempool-db-password',
|
||||||
|
'btcpay-db-password', 'mysql-root-db-password']:
|
||||||
|
(tmp / 'data/secrets' / name).write_text('fixture-preserved')
|
||||||
|
(tmp / 'data/wireguard/private.key').write_text('fixture-preserved')
|
||||||
|
candidate = tmp / 'firstboot.sh'
|
||||||
|
candidate.write_text(source)
|
||||||
|
# Functions take precedence over host commands. Unexpected privileged work
|
||||||
|
# fails, and no real Podman/systemd command can run through these stubs.
|
||||||
|
harness = r'''
|
||||||
|
id() { if [ "$*" = '-u' ]; then echo 0; else echo 1000; fi; }
|
||||||
|
chown() { :; }
|
||||||
|
loginctl() { :; }
|
||||||
|
modprobe() { :; }
|
||||||
|
systemctl() { :; }
|
||||||
|
ufw() { echo 'Status: inactive'; }
|
||||||
|
openssl() { echo 'unexpected credential rotation' >&2; return 99; }
|
||||||
|
runuser() {
|
||||||
|
printf '%s\n' "$*" >> "$TRACE"
|
||||||
|
case "$*" in
|
||||||
|
*'podman system migrate'*) return 99 ;;
|
||||||
|
*'podman container exists filebrowser'*) return 0 ;;
|
||||||
|
*'podman ps -a'*) echo fedimint-clientd; return 0 ;;
|
||||||
|
*'podman network create archy-net'*) return 0 ;;
|
||||||
|
*) echo 'unexpected system command' >&2; return 99 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
export -f id chown loginctl modprobe systemctl ufw openssl runuser
|
||||||
|
bash "$CANDIDATE"
|
||||||
|
'''
|
||||||
|
before = {str(p): p.read_bytes() for p in (tmp / 'data').rglob('*') if p.is_file()}
|
||||||
|
for attempt in range(2):
|
||||||
|
trace = tmp / f'trace-{attempt}'
|
||||||
|
result = subprocess.run(['bash', '-c', harness], capture_output=True,
|
||||||
|
text=True, timeout=15, env=os.environ | {
|
||||||
|
'CANDIDATE': str(candidate), 'TRACE': str(trace),
|
||||||
|
'ARCHY_REGISTRY': 'fixture.invalid',
|
||||||
|
'BITCOIN_KNOTS_IMAGE': 'fixture.invalid/bitcoin',
|
||||||
|
})
|
||||||
|
assert result.returncode == 0, result.stderr
|
||||||
|
assert not result.stderr, result.stderr
|
||||||
|
assert 'Unbundled first-boot complete' in result.stdout, result.stdout
|
||||||
|
assert 'system migrate' not in trace.read_text(), trace.read_text()
|
||||||
|
assert all(Path(p).read_bytes() == content for p, content in before.items())
|
||||||
|
print('PASS repeated unbundled setup logs correctly without stopping apps or rotating stored secrets')
|
||||||
@@ -73,6 +73,7 @@ stage "git-diff-check" git diff --check
|
|||||||
stage "mirror-gate-regression" python3 scripts/tests/test_git_mirrors.py
|
stage "mirror-gate-regression" python3 scripts/tests/test_git_mirrors.py
|
||||||
stage "iso-boot-runner-regression" python3 scripts/tests/test_iso_qemu_runner.py
|
stage "iso-boot-runner-regression" python3 scripts/tests/test_iso_qemu_runner.py
|
||||||
stage "iso-qualified-web-ui" python3 tests/regression/iso-qualified-web-ui.py
|
stage "iso-qualified-web-ui" python3 tests/regression/iso-qualified-web-ui.py
|
||||||
|
stage "first-boot-retry" python3 tests/regression/first-boot-retry.py
|
||||||
stage "demo-rpc-parity" timeout 120 node neode-ui/scripts/mock-rpc-parity.mjs
|
stage "demo-rpc-parity" timeout 120 node neode-ui/scripts/mock-rpc-parity.mjs
|
||||||
stage "demo-resumable-uploads" timeout 120 node --test neode-ui/scripts/demo-upload-test.mjs
|
stage "demo-resumable-uploads" timeout 120 node --test neode-ui/scripts/demo-upload-test.mjs
|
||||||
stage "companion-signature-regression" python3 scripts/tests/test_companion_apk_verification.py
|
stage "companion-signature-regression" python3 scripts/tests/test_companion_apk_verification.py
|
||||||
|
|||||||
Reference in New Issue
Block a user