66 lines
3.2 KiB
Python
66 lines
3.2 KiB
Python
#!/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')
|