Files
minecraft-lab/scripts/verify-phase7.sh
T
2026-10-04 00:30:09 +07:00

131 lines
6.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# Real SSH/rsync + Docker on an isolated loopback endpoint and a disposable root.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.."
repository="$PWD"
node_binary=$(command -v node)
check=$(mktemp -d "$PWD/.tools/phase7-check.XXXXXX")
printf '%s\n' "$check" > .tools/phase7-check-path
mkdir -p "$check/evidence" "$check/ssh" "$check/bin" "$check/host" "$check/source"
git ls-files --cached --others --exclude-standard -z > "$check/files.list"
tar --null -T "$check/files.list" -cf "$check/source.tar"
tar --no-same-owner -xf "$check/source.tar" -C "$check/source"
git -C "$check/source" init -q
git -C "$check/source" add -A
git -C "$check/source" -c user.name='Phase 7 verification' -c user.email='phase7@localhost' commit -qm 'Temporary integration snapshot'
git clone -q "$check/source" "$check/clone"
clone="$check/clone"
mkdir "$clone/node_modules"
cp -a "$repository/node_modules/yaml" "$repository/node_modules/smol-toml" "$clone/node_modules/"
mkdir -p "$clone/.tools/packwiz"
cp "$repository/.tools/packwiz/packwiz" "$clone/.tools/packwiz/packwiz"
cp "$repository/.tools/docker-compose" "$clone/.tools/docker-compose"
(cd "$clone" && ./mcpack build --release) > "$check/evidence/build-first.log" 2>&1
sed -i 's/^version = ".*"$/version = "0.0.5"/' "$clone/pack/pack.toml"
printf 'server-override-phase7-v2\n' > "$clone/server/config/spike-marker.txt"
(cd "$clone/pack" && ../.tools/packwiz/packwiz refresh)
git -C "$clone" add pack server/config
git -C "$clone" -c user.name='Phase 7 verification' -c user.email='phase7@localhost' commit -qm 'Second test release'
(cd "$clone" && ./mcpack build --release) > "$check/evidence/build-second.log" 2>&1
ssh-keygen -q -t ed25519 -N '' -f "$check/ssh/host-key"
ssh-keygen -q -t ed25519 -N '' -f "$check/ssh/client-key"
cp "$check/ssh/client-key.pub" "$check/ssh/authorized_keys"
cat > "$check/ssh/sshd_config" <<CONFIG
Port 22227
ListenAddress 127.0.0.1
HostKey $check/ssh/host-key
PidFile $check/ssh/sshd.pid
AuthorizedKeysFile $check/ssh/authorized_keys
StrictModes no
PermitRootLogin prohibit-password
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
UsePAM yes
AllowUsers root
CONFIG
# Pin the generated host key directly; no trust-on-first-use.
printf '[127.0.0.1]:22227 ' > "$check/ssh/known_hosts"
cut -d ' ' -f 1-2 "$check/ssh/host-key.pub" >> "$check/ssh/known_hosts"
cat > "$check/ssh/client_config" <<CONFIG
Host phase7-fixture
HostName 127.0.0.1
Port 22227
User root
IdentityFile $check/ssh/client-key
IdentitiesOnly yes
UserKnownHostsFile $check/ssh/known_hosts
CONFIG
cat > "$check/bin/ssh" <<SCRIPT
#!/bin/sh
exec /usr/bin/ssh -F '$check/ssh/client_config' "\$@"
SCRIPT
chmod +x "$check/bin/ssh"
export PATH="$check/bin:$PATH"
/usr/sbin/sshd -D -e -f "$check/ssh/sshd_config" > "$check/evidence/sshd.log" 2>&1 &
sshd_pid=$!
cleanup(){
kill "$sshd_pid" 2>/dev/null || true
}
trap cleanup EXIT
sleep 0.3
ssh -o BatchMode=yes -o StrictHostKeyChecking=yes phase7-fixture "'$node_binary' --version; docker info --format "{{.ServerVersion}}"" > "$check/evidence/preflight.txt"
cat > "$check/host/.env" <<CONFIG
MC_EULA=TRUE
MC_MEMORY=2G
MC_PORT=25566
MC_UID=1000
MC_GID=1000
MC_PROJECT_NAME=minecraft-lab-phase7
CONFIG
chmod 600 "$check/host/.env"
REPOSITORY="$clone" TARGET_ROOT="$check/host" node --input-type=module <<'JS'
import fs from 'node:fs';
import {parse,stringify} from 'yaml';
const config=parse(fs.readFileSync(process.env.REPOSITORY+'/platform/config.yaml','utf8'));
config.server.host='phase7-fixture';config.server.directory=process.env.TARGET_ROOT;
fs.writeFileSync(process.env.REPOSITORY+'/deploy-fixture.yaml',stringify(config));
JS
first="$clone/dist/minecraft-lab-neoforge-spike-0.0.4"
second="$clone/dist/minecraft-lab-neoforge-spike-0.0.5"
deploy(){ (cd "$clone" && ./mcpack deploy --config deploy-fixture.yaml --artifact "$1" --remote-node "$node_binary"); }
deploy "$first" > "$check/evidence/deploy-first.log" 2>&1
before=$(docker inspect minecraft-lab-phase7-minecraft-1 --format '{{.Id}} {{.State.StartedAt}}')
deploy "$first" > "$check/evidence/deploy-noop.log" 2>&1
after=$(docker inspect minecraft-lab-phase7-minecraft-1 --format '{{.Id}} {{.State.StartedAt}}')
[[ "$before" == "$after" ]] || { echo 'No-op restarted server' >&2; exit 1; }
printf '%s\n' "$before" > "$check/evidence/noop-container.txt"
mkdir -p "$check/host/runtime/mod-state"
printf 'phase7-persistent-progress\n' > "$check/host/runtime/mod-state/phase7.txt"
# Test lock before updating. A rejected parallel operation must not overwrite state.
mkdir "$check/host/.mcpack-runtime.lock"
if deploy "$second" > "$check/evidence/locked.log" 2>&1; then echo 'Parallel deploy unexpectedly passed' >&2; exit 1; fi
rmdir "$check/host/.mcpack-runtime.lock"
deploy "$second" > "$check/evidence/deploy-second.log" 2>&1
HOST_ROOT="$check/host" node --input-type=module <<'JS' > "$check/evidence/update-assertions.txt"
import fs from 'node:fs';import assert from 'node:assert/strict';
const root=process.env.HOST_ROOT,state=JSON.parse(fs.readFileSync(root+'/deployment-state.json'));
assert.equal(state.phase,'complete');assert(state.backup_snapshot);
assert.equal(fs.readFileSync(root+'/active-release','utf8').trim(),'minecraft-lab-neoforge-spike-0.0.5');
assert.equal(fs.readFileSync(root+'/previous-release','utf8').trim(),'minecraft-lab-neoforge-spike-0.0.4');
assert.equal(fs.readFileSync(root+'/runtime/mod-state/phase7.txt','utf8'),'phase7-persistent-progress\n');
assert.equal(fs.readFileSync(root+'/runtime/config/spike-marker.txt','utf8'),'server-override-phase7-v2\n');
assert(fs.existsSync(root+'/runtime/world/level.dat'));
assert(fs.existsSync(state.backup_snapshot+'/runtime.tar.zst'));
console.log('First deploy, no-op, lock, backup-gated update, pointers, world and mod-state preserved.');
JS
# A manual config edit must be detected before stop/replacement, even for same release.
printf 'manual-phase7-edit\n' > "$check/host/runtime/config/spike-marker.txt"
if deploy "$second" > "$check/evidence/manual-conflict.log" 2>&1; then echo 'Manual conflict unexpectedly passed' >&2; exit 1; fi
HOST_ROOT="$check/host" node --input-type=module <<'JS' > "$check/evidence/conflict-assertions.txt"
import fs from 'node:fs';import assert from 'node:assert/strict';
const root=process.env.HOST_ROOT,state=JSON.parse(fs.readFileSync(root+'/deployment-state.json'));
assert.equal(state.phase,'failed');assert.equal(state.applied,false);
assert.equal(state.conflicts[0].path,'config/spike-marker.txt');
assert.equal(fs.readFileSync(root+'/runtime/config/spike-marker.txt','utf8'),'manual-phase7-edit\n');
console.log('Manual config conflict retained, rejected before applying/stopping.');
JS
MC_ROOT="$check/host" MC_RELEASE_DIR="$check/host/releases/minecraft-lab-neoforge-spike-0.0.5" scripts/server.sh down
scripts/spike-server.sh ps > "$check/evidence/primary-server-status.txt"
printf 'Phase 7 real SSH integration passed. Evidence: %s\n' "$check/evidence"