131 lines
6.8 KiB
Bash
Executable File
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"
|