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

123 lines
8.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# Rollback an isolated copy over real SSH. Never selects the user's live root.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.."
repository="$PWD"
node_binary=$(command -v node)
check=$(mktemp -d "$PWD/.tools/phase8-check.XXXXXX")
printf '%s\n' "$check" > .tools/phase8-check-path
mkdir -p "$check/evidence" "$check/ssh" "$check/bin"
snapshot="$repository/.tools/phase7-check.6dbBak/host/backups/20261003T153844610Z-732a1d62-9bee-4a9c-a095-68bde6c8406f"
./mcpack restore --backup "$snapshot" --destination "$check/host" > "$check/evidence/initial-restore.log"
HOST_ROOT="$check/host" node --input-type=module <<'JS'
import fs from 'node:fs';import {releaseName,deployableRelease} from './platform/deployment.mjs';
const root=process.env.HOST_ROOT,release=deployableRelease(root+'/release'),name=releaseName(release.manifest);
fs.mkdirSync(root+'/releases');fs.renameSync(root+'/release',root+'/releases/'+name);
fs.writeFileSync(root+'/active-release',name+'\n');
fs.writeFileSync(root+'/deployment-state.json',JSON.stringify({schema_version:1,id:'restored-phase8-fixture',phase:'complete',target_release:name}));
fs.writeFileSync(root+'/.env','MC_EULA=TRUE\nMC_MEMORY=2G\nMC_PORT=25566\nMC_BIND_ADDRESS=0.0.0.0\nMC_UID=1000\nMC_GID=1000\nMC_PROJECT_NAME=minecraft-lab-phase7\n',{mode:0o600});
fs.writeFileSync(root+'/runtime/world/phase8-progress.txt','snapshot-progress\n');
fs.mkdirSync(root+'/runtime/mod-state',{recursive:true});fs.writeFileSync(root+'/runtime/mod-state/phase8-progress.txt','snapshot-progress\n');
JS
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 22228
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
printf '[127.0.0.1]:22228 ' > "$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 phase8-fixture
HostName 127.0.0.1
Port 22228
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(){
if [[ -f "$check/host/.mcpack-runtime.json" ]]; then
for release in "$check/host/releases/"*; do
if MC_ROOT="$check/host" MC_RELEASE_DIR="$release" scripts/server.sh down > "$check/evidence/cleanup.log" 2>&1; then break; fi
done
fi
kill "$sshd_pid" 2>/dev/null || true
}
trap cleanup EXIT
sleep 0.3
FIXTURE_ROOT="$check/host" FIXTURE_CONFIG="$check/config.yaml" node --input-type=module <<'JS'
import fs from 'node:fs';import {parse,stringify} from 'yaml';
const config=parse(fs.readFileSync('platform/config.yaml','utf8'));config.server.host='phase8-fixture';config.server.directory=process.env.FIXTURE_ROOT;
fs.writeFileSync(process.env.FIXTURE_CONFIG,stringify(config));
JS
./mcpack deploy --config "$check/config.yaml" --artifact dist/minecraft-lab-neoforge-spike-0.0.5 --remote-node "$node_binary" > "$check/evidence/update-to-five.log" 2>&1
rollback_snapshot=$(HOST_ROOT="$check/host" node --input-type=module <<'JS'
import fs from 'node:fs';import path from 'node:path';
console.log(path.basename(JSON.parse(fs.readFileSync(process.env.HOST_ROOT+'/deployment-state.json')).backup_snapshot));
JS
)
printf '%s\n' "$rollback_snapshot" > "$check/evidence/requested-snapshot-id.txt"
printf 'current-progress\n' > "$check/host/runtime/world/phase8-progress.txt"
printf 'current-progress\n' > "$check/host/runtime/mod-state/phase8-progress.txt"
printf 'only-after-snapshot\n' > "$check/host/runtime/phase8-after-only.txt"
ls -A "$check/host" > "$check/evidence/root-before-plan.txt"
before=$(docker inspect minecraft-lab-phase7-minecraft-1 --format '{{.Id}} {{.State.StartedAt}}')
./mcpack rollback --config "$check/config.yaml" --remote-node "$node_binary" > "$check/evidence/plan.txt"
./mcpack rollback-pack 0.0.4 --config "$check/config.yaml" --remote-node "$node_binary" > "$check/evidence/pack-plan.txt"
ls -A "$check/host" > "$check/evidence/root-after-plan.txt"
[[ "$before" == "$(docker inspect minecraft-lab-phase7-minecraft-1 --format '{{.Id}} {{.State.StartedAt}}')" ]]
node -e 'const fs=require("fs"),assert=require("assert/strict");assert.equal(fs.readFileSync(process.argv[1],"utf8"),fs.readFileSync(process.argv[2],"utf8"))' "$check/evidence/root-before-plan.txt" "$check/evidence/root-after-plan.txt"
./mcpack rollback-pack 0.0.4 --confirm-compatible --config "$check/config.yaml" --remote-node "$node_binary" > "$check/evidence/pack-rollback.log" 2>&1
HOST_ROOT="$check/host" node --input-type=module <<'JS' > "$check/evidence/pack-assertions.txt"
import fs from 'node:fs';import assert from 'node:assert/strict';import {execFileSync} from 'node:child_process';
const root=process.env.HOST_ROOT,state=JSON.parse(fs.readFileSync(root+'/rollback-state.json'));
assert.equal(state.phase,'complete');assert.equal(state.mode,'pack');assert(state.backup_snapshot);
assert.equal(fs.readFileSync(root+'/runtime/world/phase8-progress.txt','utf8'),'current-progress\n');
assert.equal(fs.readFileSync(root+'/runtime/mod-state/phase8-progress.txt','utf8'),'current-progress\n');
assert(fs.existsSync(root+'/runtime/phase8-after-only.txt'));
const container=JSON.parse(execFileSync('docker',['inspect','minecraft-lab-phase7-minecraft-1']))[0];
assert.equal(container.State.Health.Status,'healthy');assert.equal(container.NetworkSettings.Ports['25565/tcp'][0].HostIp,'0.0.0.0');
console.log('Pack-only rollback: retained 0.0.4, current progress preserved, backup verified, LAN bind and health retained.');
JS
./mcpack rollback --backup "$rollback_snapshot" --accept-progress-loss --config "$check/config.yaml" --remote-node "$node_binary" > "$check/evidence/snapshot-rollback.log" 2>&1
HOST_ROOT="$check/host" node --input-type=module <<'JS' > "$check/evidence/snapshot-assertions.txt"
import fs from 'node:fs';import assert from 'node:assert/strict';import {execFileSync} from 'node:child_process';import {verifySnapshot} from './platform/backups.mjs';
const root=process.env.HOST_ROOT,state=JSON.parse(fs.readFileSync(root+'/rollback-state.json'));
assert.equal(state.phase,'complete');assert.equal(state.mode,'snapshot');assert.equal(state.runtime_replaced,true);
assert.equal(fs.readFileSync(root+'/runtime/world/phase8-progress.txt','utf8'),'snapshot-progress\n');
assert.equal(fs.readFileSync(root+'/runtime/mod-state/phase8-progress.txt','utf8'),'snapshot-progress\n');
assert(!fs.existsSync(root+'/runtime/phase8-after-only.txt'));
assert.equal(fs.readFileSync(state.retired_runtime+'/world/phase8-progress.txt','utf8'),'current-progress\n');
assert(fs.existsSync(state.retired_runtime+'/phase8-after-only.txt'));
assert.equal(verifySnapshot(state.backup_snapshot).source_release.name,'minecraft-lab-neoforge-spike-0.0.4');
assert.equal(fs.readFileSync(root+'/.env','utf8').includes('MC_BIND_ADDRESS=0.0.0.0'),true);
const container=JSON.parse(execFileSync('docker',['inspect','minecraft-lab-phase7-minecraft-1']))[0];
assert.equal(container.State.Health.Status,'healthy');assert.equal(container.NetworkSettings.Ports['25565/tcp'][0].HostIp,'0.0.0.0');
assert(!fs.existsSync(root+'/.mcpack-runtime.lock'));
console.log('Snapshot rollback: all runtime restored, subsequent progress removed only from active runtime, retired progress and current backup retained; source release, host settings, LAN bind and health verified.');
JS
node scripts/query-minecraft.mjs 127.0.0.1 25566 > "$check/evidence/restored-status.json"
MC_ROOT="$check/host" MC_RELEASE_DIR="$check/host/releases/minecraft-lab-neoforge-spike-0.0.4" scripts/server.sh down
scripts/spike-server.sh ps > "$check/evidence/primary-server-status.txt"
printf 'Phase 8 real SSH/Docker rollback passed. Evidence: %s\n' "$check/evidence"