mirror of
https://github.com/phantomptr/ps5upload.git
synced 2026-10-21 19:33:08 -05:00
104 lines
5.1 KiB
Bash
Executable File
104 lines
5.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Start a built engine image and check what a self-hoster hits first.
|
|
#
|
|
# scripts/docker-smoke.sh <image> [--webui]
|
|
#
|
|
# CI used to build the images and never run them, so an image that answered
|
|
# every request with 403, had no writable /tmp for uploads and no home for its
|
|
# state shipped for several releases. Each check below is one of those.
|
|
set -euo pipefail
|
|
|
|
image="$1"
|
|
webui="${2:-}"
|
|
name="ps5upload-smoke-$$"
|
|
port="${SMOKE_PORT:-19313}"
|
|
base="http://127.0.0.1:${port}${SMOKE_BASE_PATH:-}"
|
|
|
|
cleanup() {
|
|
if [ "${ok:-0}" != 1 ]; then docker logs "$name" 2>&1 | tail -40 || true; fi
|
|
docker rm -f "$name" >/dev/null 2>&1 || true
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
fail() { echo "FAIL: $*" >&2; exit 1; }
|
|
|
|
# 0.0.0.0/0 because the runner reaches the container through Docker's NAT,
|
|
# not loopback — exactly the case PS5UPLOAD_ALLOW_IP exists for.
|
|
docker run -d --name "$name" -p "${port}:19113" \
|
|
-e PS5_ADDR=192.0.2.1 -e PS5UPLOAD_ALLOW_IP=0.0.0.0/0 "$image" >/dev/null
|
|
|
|
for _ in $(seq 1 30); do
|
|
curl -fsS -m 2 "$base/api/jobs" >/dev/null 2>&1 && break
|
|
sleep 1
|
|
done
|
|
curl -fsS -m 5 "$base/api/jobs" >/dev/null || fail "engine never answered GET /api/jobs"
|
|
echo "ok: engine answers through the published port"
|
|
|
|
# Any Host header is served (the engine has no hostname allowlist): reaching it by a name works.
|
|
curl -fsS -m 5 -H 'Host: nas.example' "$base/api/jobs" >/dev/null || fail "a hostname Host header was refused"
|
|
echo "ok: the engine answers when reached by a hostname"
|
|
|
|
tmp="$(mktemp -d)"
|
|
head -c 65536 /dev/urandom > "$tmp/smoke.pkg"
|
|
code="$(curl -sS -m 30 -o "$tmp/upload.json" -w '%{http_code}' -F "pkg=@$tmp/smoke.pkg" "$base/api/pkg/upload")"
|
|
[ "$code" = 200 ] || fail "package upload returned HTTP $code: $(cat "$tmp/upload.json")"
|
|
upload_id="$(sed -n 's/.*"upload_id":"\([^"]*\)".*/\1/p' "$tmp/upload.json")"
|
|
[ -n "$upload_id" ] || fail "upload response had no upload_id: $(cat "$tmp/upload.json")"
|
|
curl -fsS -m 10 -X DELETE "$base/api/pkg/upload/$upload_id" >/dev/null || fail "upload cleanup failed"
|
|
echo "ok: a package uploads to the engine's /tmp and cleans up"
|
|
|
|
# Generic browser intake and remote downloads share the same API prefix.
|
|
code="$(curl -sS -m 30 -o "$tmp/file.json" -w '%{http_code}' -F "file=@$tmp/smoke.pkg;filename=notes.txt" "$base/api/files/upload")"
|
|
[ "$code" = 200 ] || fail "browser file upload returned HTTP $code"
|
|
file_id="$(sed -n 's/.*"upload_id":"\([^"]*\)".*/\1/p' "$tmp/file.json")"
|
|
[ -n "$file_id" ] || fail "browser file response had no upload_id: $(cat "$tmp/file.json")"
|
|
curl -fsS -m 10 -X DELETE "$base/api/files/upload/$file_id" >/dev/null || fail "browser file cleanup failed"
|
|
code="$(curl -sS -m 5 -o "$tmp/download.json" -w '%{http_code}' "$base/api/pkg/remote/download/status?id=smoke-missing")"
|
|
[ "$code" = 404 ] && grep -q 'no such download' "$tmp/download.json" || fail "remote download API missed the configured prefix"
|
|
# Console URLs are generated at the root, independently of the browser prefix.
|
|
code="$(curl -sS -m 5 -o "$tmp/host.txt" -w '%{http_code}' "http://127.0.0.1:${port}/pkg-host/smoke-missing/file.pkg")"
|
|
[ "$code" = 404 ] && grep -q 'no such install session' "$tmp/host.txt" || fail "console package route is missing at the root"
|
|
echo "ok: browser intake and download APIs follow the prefix; console URLs remain reachable"
|
|
|
|
# The artwork cache is created under HOME at first use; a read-only home
|
|
# logs "Permission denied" and silently disables it.
|
|
curl -fsS -m 5 "$base/api/cache/artwork" >/dev/null || fail "artwork cache route failed"
|
|
if docker logs "$name" 2>&1 | grep -q "Permission denied"; then
|
|
fail "the engine could not write its state (Permission denied in the log)"
|
|
fi
|
|
echo "ok: the engine's home is writable"
|
|
|
|
if [ "$webui" = "--webui" ]; then
|
|
ctype="$(curl -fsS -m 5 -o /dev/null -w '%{content_type}' "$base/")" || fail "web UI index did not load"
|
|
case "$ctype" in text/html*) ;; *) fail "web UI index served $ctype, not HTML" ;; esac
|
|
echo "ok: the web UI loads"
|
|
fi
|
|
|
|
for _ in $(seq 1 40); do
|
|
status="$(docker inspect -f '{{.State.Health.Status}}' "$name")"
|
|
[ "$status" = healthy ] && break
|
|
sleep 2
|
|
done
|
|
[ "$status" = healthy ] || fail "container health is '$status', not healthy"
|
|
echo "ok: the built-in health check reports healthy"
|
|
|
|
# A Compose `user:` override (#346: `user: "1000:1000"`) must still be able to
|
|
# stage uploads and write the engine's state.
|
|
docker rm -f "$name" >/dev/null 2>&1
|
|
docker run -d --name "$name" --user 1000:1000 -p "${port}:19113" \
|
|
-e PS5_ADDR=192.0.2.1 -e PS5UPLOAD_ALLOW_IP=0.0.0.0/0 "$image" >/dev/null
|
|
for _ in $(seq 1 30); do
|
|
curl -fsS -m 2 "$base/api/jobs" >/dev/null 2>&1 && break
|
|
sleep 1
|
|
done
|
|
code="$(curl -sS -m 30 -o "$tmp/upload.json" -w '%{http_code}' -F "pkg=@$tmp/smoke.pkg" "$base/api/pkg/upload")"
|
|
[ "$code" = 200 ] || fail "as uid 1000, package upload returned HTTP $code: $(cat "$tmp/upload.json")"
|
|
curl -fsS -m 5 "$base/api/cache/artwork" >/dev/null || fail "as uid 1000, artwork cache route failed"
|
|
if docker logs "$name" 2>&1 | grep -q "Permission denied"; then
|
|
fail "as uid 1000, the engine could not write its state (Permission denied in the log)"
|
|
fi
|
|
echo "ok: a user: override (uid 1000) can upload and save state"
|
|
|
|
ok=1
|
|
echo "docker smoke test passed: $image"
|