129 lines
6.4 KiB
Bash
Executable File
129 lines
6.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
repo=$(cd "$(dirname "$0")/../../.." && pwd)
|
|
image=${1:-localhost/archipelago-qortal:6.1.9-archy.1}
|
|
name="archy-qortal-fixture-$$"
|
|
volume="$name-data"
|
|
mode=${2:-fresh}
|
|
case "$mode" in fresh|initialized-fixture) ;; *) echo 'Expected fresh or initialized-fixture' >&2; exit 2 ;; esac
|
|
work=$(mktemp -d)
|
|
cleanup() {
|
|
podman rm -f "$name" "$name-probe" >/dev/null 2>&1 || true
|
|
podman volume rm -f "$volume" >/dev/null 2>&1 || true
|
|
rm -rf -- "$work"
|
|
}
|
|
trap cleanup EXIT
|
|
trap 'exit 130' INT
|
|
trap 'exit 143' TERM
|
|
common=(--pull=never --image-volume=ignore --network none --pid=private --ipc=private --http-proxy=false --cpus 2 --memory 2g --memory-swap 2g --pids-limit 256 --timeout=360
|
|
--cap-drop all --security-opt no-new-privileges --read-only
|
|
--tmpfs /tmp:rw,notmpcopyup,nosuid,nodev,size=512m)
|
|
# Optional unpublished test overlay. Never modifies the original image/JAR.
|
|
if [[ -n ${QORTAL_FIXTURE_JAR:-} ]]; then
|
|
[[ -f $QORTAL_FIXTURE_JAR && ! -L $QORTAL_FIXTURE_JAR ]]
|
|
[[ ${QORTAL_FIXTURE_JAR_SHA256:-} =~ ^[a-f0-9]{64}$ ]]
|
|
[[ $(sha256sum "$QORTAL_FIXTURE_JAR" | cut -d' ' -f1) == "$QORTAL_FIXTURE_JAR_SHA256" ]]
|
|
common+=(--volume "$QORTAL_FIXTURE_JAR:/usr/local/qortal/qortal.jar:ro")
|
|
fi
|
|
test "$(podman image inspect --format '{{.Config.User}}' "$image")" = '1000:100'
|
|
podman volume create "$volume" >/dev/null
|
|
podman run -i --rm --name "$name-probe" "${common[@]}" --volume "$volume:/qortal:U" \
|
|
--entrypoint python3 "$image" - <<'PY'
|
|
import json, os
|
|
from pathlib import Path
|
|
os.umask(0o077)
|
|
settings = {"bootstrap": False, "autoUpdateEnabled": False,
|
|
"localAuthBypassEnabled": False, "apiRestricted": True,
|
|
"apiWhitelistEnabled": False, "gatewayEnabled": True,
|
|
"gatewayLoopbackEnabled": True, "gatewayPort": 12380}
|
|
Path('/qortal/settings.json').write_text(json.dumps(settings))
|
|
Path('/qortal/operator-sentinel').write_bytes(b'preserve operator data\n')
|
|
PY
|
|
start() {
|
|
local log_offset
|
|
log_offset=$(podman run --rm --name "$name-probe" "${common[@]}" --volume "$volume:/qortal:U" \
|
|
--entrypoint python3 "$image" -c 'from pathlib import Path;p=Path("/qortal/qortal.log");print(p.stat().st_size if p.exists() else 0)')
|
|
podman run -d --name "$name" "${common[@]}" --volume "$volume:/qortal:U" \
|
|
--volume "$repo/tests/apps/qortal:/fixture:ro" \
|
|
--env QORTAL_API_KEY="$(printf '11%.0s' {1..32})" \
|
|
--env 'QORTAL_JVM_ARGS=-Xmx768m -XX:ActiveProcessorCount=2 -Xss512k' "$image" >/dev/null
|
|
for _ in $(seq 1 90); do
|
|
if podman exec "$name" curl -fsS -m 1 -o /dev/null http://127.0.0.1:12391/admin/info 2>/dev/null; then return; fi
|
|
if podman exec --env "FIXTURE_LOG_OFFSET=$log_offset" "$name" python3 -c 'import os;from pathlib import Path;p=Path("/qortal/qortal.log");offset=int(os.environ["FIXTURE_LOG_OFFSET"]);raise SystemExit(0 if p.exists() and b"statement is not in batch mode" in p.read_bytes()[offset:] else 1)' 2>/dev/null; then
|
|
echo 'REPRODUCED: upstream empty-genesis cache migration fails before API startup' >&2
|
|
return 2
|
|
fi
|
|
sleep 1
|
|
done
|
|
podman logs "$name" >&2 || true
|
|
podman exec "$name" python3 -c 'from pathlib import Path;print(Path("/qortal/qortal.log").read_text()[-6000:])' >&2 || true
|
|
return 1
|
|
}
|
|
check_and_wait() {
|
|
podman exec "$name" /usr/local/bin/qortal-health
|
|
podman exec "$name" python3 /fixture/protocol.py
|
|
for _ in $(seq 1 60); do
|
|
if test "$(podman inspect --format '{{.State.Running}}' "$name")" = false; then
|
|
test "$(podman inspect --format '{{.State.ExitCode}}' "$name")" = 0
|
|
return
|
|
fi
|
|
sleep 1
|
|
done
|
|
echo 'Core did not gracefully stop within the fixture deadline' >&2
|
|
return 1
|
|
}
|
|
if test "$mode" = initialized-fixture; then
|
|
# Compile only our standard-library helper, with annotation processors off.
|
|
# Never compile or execute proposal/application code on the host.
|
|
mkdir "$work/classes"
|
|
javac -proc:none --release 17 -d "$work/classes" "$repo/tests/apps/qortal/InitializedGenesis.java"
|
|
if start; then
|
|
echo 'Expected the documented empty-genesis failure; re-evaluate fixture rather than mutating a healthy DB' >&2
|
|
exit 1
|
|
else
|
|
test "$?" = 2
|
|
fi
|
|
podman stop -t 15 "$name" >/dev/null
|
|
podman rm "$name" >/dev/null
|
|
podman run --rm --name "$name-probe" "${common[@]}" --volume "$volume:/qortal:U" \
|
|
--volume "$work/classes:/fixture-classes:ro" --env ARCHY_QORTAL_SYNTHETIC_FIXTURE=1 \
|
|
--entrypoint java "$image" -Xmx256m -XX:ActiveProcessorCount=2 \
|
|
-cp /usr/local/qortal/qortal.jar:/fixture-classes InitializedGenesis
|
|
# Core also rebuilds every height<3 repository when bootstrap=false. Model
|
|
# an initialized, non-archival repository explicitly; network stays NONE.
|
|
# This is test setup, never an operator-settings repair or fresh-boot pass.
|
|
podman run --rm --name "$name-probe" "${common[@]}" --volume "$volume:/qortal:U" \
|
|
--entrypoint python3 "$image" -c 'import json;from pathlib import Path;p=Path("/qortal/settings.json");d=json.loads(p.read_text());d.update(bootstrap=True,archiveEnabled=False);p.write_text(json.dumps(d))'
|
|
fi
|
|
start
|
|
check_and_wait
|
|
podman rm "$name" >/dev/null
|
|
start
|
|
check_and_wait
|
|
podman rm "$name" >/dev/null
|
|
# A changed generated secret must refuse before either child starts, preserving
|
|
# the original key/settings rather than silently rotating or resetting them.
|
|
podman run -d --name "$name" "${common[@]}" --volume "$volume:/qortal:U" \
|
|
--env QORTAL_API_KEY="$(printf '22%.0s' {1..32})" "$image" >/dev/null
|
|
for _ in $(seq 1 20); do
|
|
test "$(podman inspect --format '{{.State.Running}}' "$name")" = false && break
|
|
sleep 0.2
|
|
done
|
|
test "$(podman inspect --format '{{.State.ExitCode}}' "$name")" = 78
|
|
podman logs "$name" > "$work/refusal.log" 2>&1
|
|
if grep -Fq "$(printf '22%.0s' {1..32})" "$work/refusal.log"; then
|
|
echo 'Refusal leaked the synthetic replacement key' >&2
|
|
exit 1
|
|
fi
|
|
podman run -i --rm --name "$name-probe" "${common[@]}" --volume "$volume:/qortal:U" \
|
|
--entrypoint python3 "$image" - <<'PY'
|
|
import hashlib
|
|
from pathlib import Path
|
|
key=Path('/qortal/apikey.txt')
|
|
assert key.read_text() == '11' * 32
|
|
assert key.stat().st_ino == int(Path('/qortal/fixture-key-inode').read_text())
|
|
assert hashlib.sha256(Path('/qortal/settings.json').read_bytes()).hexdigest() == Path('/qortal/fixture-settings-sha256').read_text()
|
|
print('PASS changed-secret refusal preserves original key inode/bytes and operator settings')
|
|
PY
|
|
printf 'PASS disposable Qortal (%s): actual Core auth, restricted UI proxy, private key and operator data across recreation\n' "$mode"
|