Files
2026-10-04 00:30:09 +07:00

85 lines
4.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Disposable runtime restored from Phase 5; primary server is never selected.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.."
evidence="$PWD/.tools/phase6-evidence"
root="$PWD/.tools/phase6-server"
loop_root="$PWD/.tools/phase6-loop-server"
mkdir -p "$evidence"
[[ ! -e "$root" && ! -e "$loop_root" ]] || { echo 'Phase 6 fixtures already exist; use new roots to repeat.' >&2; exit 1; }
snapshot=$(cat .tools/phase5-evidence/snapshot-path.txt)
./mcpack restore --backup "$snapshot" --destination "$root"
export MC_ROOT="$root" MC_RELEASE_DIR="$root/release"
cp .tools/phase4-server/.env "$root/.env"
chmod 600 "$root/.env"
# A real cold JVM start cannot finish within two seconds.
if scripts/server.sh up --wait --wait-timeout 2 --stable-seconds 1 > "$evidence/timeout.txt" 2>&1; then
echo 'Short startup timeout unexpectedly passed' >&2; exit 1
fi
scripts/server.sh health --wait-timeout 120 --stable-seconds 3 > "$evidence/healthy.txt" 2>&1
scripts/server.sh logs --tail 100 > "$evidence/healthy-start.log"
# Abrupt termination only affects this disposable copied world.
docker kill --signal KILL minecraft-lab-phase4-minecraft-1 > "$evidence/kill.txt"
if scripts/server.sh health --wait-timeout 30 --stable-seconds 2 > "$evidence/crash.txt" 2>&1; then
echo 'Crashed container unexpectedly healthy' >&2; exit 1
fi
scripts/server.sh up --wait --wait-timeout 120 --stable-seconds 3 > "$evidence/recovered.txt" 2>&1
scripts/server.sh down
# Deliberately broken Compose fixture, separate from immutable public release.
mkdir -p "$loop_root/release"
cp -a "$MC_RELEASE_DIR/." "$loop_root/release/"
cp "$root/.env" "$loop_root/.env"
LOOP_ROOT="$loop_root" node --input-type=module <<'JS'
import fs from 'node:fs';
import crypto from 'node:crypto';
import {parse,stringify} from 'yaml';
const release=process.env.LOOP_ROOT+'/release';
const composePath=release+'/server/compose.yaml';
const compose=parse(fs.readFileSync(composePath,'utf8'));
compose.services.minecraft.restart='on-failure:5';
compose.services.minecraft.entrypoint=['/bin/sh','-c','echo phase6-intentional-restart-loop; sleep 1; exit 42'];
fs.writeFileSync(composePath,stringify(compose));
const hash=file=>crypto.createHash('sha256').update(fs.readFileSync(release+'/'+file)).digest('hex');
const manifest=JSON.parse(fs.readFileSync(release+'/manifest.json'));
manifest.pack.version='phase6-failure-fixture';
for(const file of manifest.release_files)file.sha256=hash(file.path);
fs.writeFileSync(release+'/manifest.json',JSON.stringify(manifest,null,2)+'\n');
const files=[...manifest.release_files.map(file=>file.path),'manifest.json'].sort();
fs.writeFileSync(release+'/SHA256SUMS',files.map(file=>`${hash(file)} ${file}\n`).join(''));
JS
export MC_ROOT="$loop_root" MC_RELEASE_DIR="$loop_root/release"
scripts/server.sh up -d
# Wait until the real Docker container has restarted at least once.
for attempt in {1..20}; do
count=$(docker inspect minecraft-lab-phase4-minecraft-1 --format '{{.RestartCount}}')
if [[ "$count" -ge 1 ]]; then break; fi
sleep 0.2
done
docker inspect minecraft-lab-phase4-minecraft-1 --format '{{json .State}} {{.RestartCount}}' > "$evidence/loop-docker-state.txt"
if scripts/server.sh health --wait-timeout 15 --stable-seconds 2 > "$evidence/restart-loop.txt" 2>&1; then
echo 'Restart loop unexpectedly healthy' >&2; exit 1
fi
scripts/server.sh down
ROOT="$root" LOOP_ROOT="$loop_root" node --input-type=module <<'JS' > "$evidence/assertions.txt"
import fs from 'node:fs';
import assert from 'node:assert/strict';
const reports=root=>fs.readdirSync(root+'/diagnostics').filter(name=>name.endsWith('.json')).map(name=>JSON.parse(fs.readFileSync(root+'/diagnostics/'+name)));
const normal=reports(process.env.ROOT),loop=reports(process.env.LOOP_ROOT);
assert(normal.some(report=>report.reason==='timeout'));
assert(normal.some(report=>report.reason==='crash'&&report.state.exit_code===137));
assert(loop.some(report=>report.reason==='restart-loop'));
for(const root of [process.env.ROOT,process.env.LOOP_ROOT]){
assert(!fs.existsSync(root+'/.mcpack-runtime.lock'));
assert(fs.existsSync(root+'/.mcpack-runtime.json'));
for(const report of reports(root)){
assert(fs.existsSync(report.logs));
assert.equal(report.recovery.automatic_rollback,false);
assert.equal(report.recovery.runtime_preserved,true);
}
}
assert(fs.existsSync(process.env.ROOT+'/runtime/mod-state/phase5.json'));
console.log('Real success, timeout, crash (exit 137), restart-loop, diagnostics and runtime/binding preservation passed.');
JS
scripts/spike-server.sh ps > "$evidence/primary-server-status.txt"
printf 'Phase 6 integration passed.\n'