first cycle lab release
This commit is contained in:
Executable
+130
@@ -0,0 +1,130 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user