Menu
AkurAI-Build
publicLatest change 0cf28150faedd8c150474a4f16be4c9922c04f56 - feat: add Ponytail UI design system by Olafur Bui
#!/usr/bin/env bash
# Opt-in black-box deployment test: deploy -> image -> container -> browser -> cleanup.
set -Eeuo pipefail
if [[ ${BUNFORK_DOCKER_E2E:-} != 1 ]]; then
printf '%s\n' 'set BUNFORK_DOCKER_E2E=1 to run the Docker deployment E2E test' >&2
exit 2
fi
for program in cargo docker curl python3; do
command -v "$program" >/dev/null || {
printf 'required program missing: %s\n' "$program" >&2
exit 2
}
done
browser=${BUNFORK_DOCKER_E2E_BROWSER:-chromium}
command -v "$browser" >/dev/null || {
printf 'required screenshot browser missing: %s (set BUNFORK_DOCKER_E2E_BROWSER)\n' "$browser" >&2
exit 2
}
docker info >/dev/null || {
printf '%s\n' 'Docker daemon is unavailable; start Docker and retry.' >&2
exit 2
}
root=$(git rev-parse --show-toplevel)
cd "$root"
run_id="$(date +%s)-$$-$(od -An -N4 -tx4 /dev/urandom | tr -d ' \n')"
deployment="bunfork-docker-e2e-$run_id"
context_dir="$(mktemp -d "${TMPDIR:-/tmp}/bunfork-docker-e2e-$run_id.XXXXXX")"
artifacts="target/docker-e2e/$run_id"
browser_profile="$artifacts/chromium-profile"
image="bunfork-e2e:$run_id"
container="bunfork-e2e-$run_id"
inspector="bunfork-e2e-inspector-$run_id"
status=0
cleanup() {
status=$?
if ((status != 0)) && [[ -n ${container:-} ]]; then
docker logs "$container" >&2 || true
fi
[[ -z ${inspector:-} ]] || docker rm -f "$inspector" >/dev/null 2>&1 || true
[[ -z ${container:-} ]] || docker rm -f "$container" >/dev/null 2>&1 || true
[[ -z ${image:-} ]] || docker image rm -f "$image" >/dev/null 2>&1 || true
[[ -z ${context_dir:-} ]] || rm -rf -- "$context_dir"
[[ -z ${browser_profile:-} ]] || rm -rf -- "$browser_profile"
[[ -z ${deployment:-} ]] || rm -rf -- "$deployment" "$deployment".previous-*
exit "$status"
}
trap cleanup EXIT INT TERM
mkdir -p "$artifacts"
cargo run --locked -- deploy --out "$deployment"
cp -a "$deployment"/. "$context_dir"/
cp tests/docker-e2e/Dockerfile "$context_dir"/Dockerfile
cp tests/docker-e2e/entrypoint.sh "$context_dir"/entrypoint.sh
chmod 0555 "$context_dir/entrypoint.sh"
docker build --tag "$image" "$context_dir"
docker run --detach --name "$container" --publish 127.0.0.1::3100 "$image" >/dev/null
port=$(docker port "$container" 3100/tcp | sed -n 's/.*:\([0-9][0-9]*\)$/\1/p' | head -n 1)
[[ $port =~ ^[0-9]+$ ]] || {
printf '%s\n' 'Docker did not publish a loopback port for 3100/tcp' >&2
exit 1
}
base_url="http://127.0.0.1:$port"
ready=0
for _ in {1..100}; do
if curl --fail --silent --show-error --max-time 1 "$base_url/_bunfork/ready" |
grep -qx '{"message":"ok"}'; then
ready=1
break
fi
sleep 0.1
done
((ready == 1)) || {
printf 'container did not become ready at %s\n' "$base_url" >&2
exit 1
}
curl --fail --silent --show-error --max-time 3 "$base_url/" >"$artifacts/index.html"
curl --fail --silent --show-error --max-time 3 "$base_url/assets/app.css" >"$artifacts/app.css"
[[ $(curl --silent --output /dev/null --write-out '%{http_code}' --max-time 3 "$base_url/does-not-exist") == 404 ]]
screenshot="$artifacts/home.png"
"$browser" --headless --disable-gpu --hide-scrollbars --user-data-dir="$browser_profile" \
--window-size=1440,900 --screenshot="$screenshot" "$base_url/"
"$browser" --headless --disable-gpu --user-data-dir="$browser_profile" --dump-dom "$base_url/" \
>"$artifacts/browser-dom.html"
grep -Fq 'Bunfork' "$artifacts/browser-dom.html"
python3 - "$screenshot" <<'PY'
import struct
import sys
path = sys.argv[1]
with open(path, "rb") as image:
header = image.read(24)
if header[:8] != b"\x89PNG\r\n\x1a\n" or header[12:16] != b"IHDR":
raise SystemExit("browser did not produce a PNG screenshot")
width, height = struct.unpack(">II", header[16:24])
if (width, height) != (1440, 900):
raise SystemExit(f"unexpected screenshot dimensions: {width}x{height}")
if len(open(path, "rb").read()) < 10_000:
raise SystemExit("screenshot is too small to show the expected rendered page")
print(f"screenshot analysis: valid non-blank PNG {width}x{height}")
PY
docker image inspect "$image" >"$artifacts/image-inspect.json"
python3 - "$artifacts/image-inspect.json" <<'PY'
import json
import sys
image = json.load(open(sys.argv[1], encoding="utf-8"))[0]
config = image["Config"]
assert config["User"] == "bunfork", "container image must run as the bunfork user"
assert config["WorkingDir"] == "/opt/bunfork", "unexpected image working directory"
assert "3100/tcp" in config.get("ExposedPorts", {}), "image does not expose 3100/tcp"
assert config["Entrypoint"] == ["/opt/bunfork/entrypoint.sh"], "unexpected entrypoint"
print("image analysis: non-root Bunfork entrypoint with 3100/tcp exposed")
PY
docker create --name "$inspector" "$image" >/dev/null
docker export "$inspector" >"$artifacts/runtime.tar"
python3 - "$artifacts/runtime.tar" <<'PY'
import tarfile
import sys
forbidden = {
"agent-browser", "bun", "chrome", "chromium", "chromium-browser",
"claude", "codex", "deno", "google-chrome", "google-chrome-stable",
"node", "nodejs", "npm", "npx", "playwright", "playwright-cli", "puppeteer",
}
with tarfile.open(sys.argv[1]) as archive:
found = [
member.name for member in archive.getmembers()
if member.isfile() and member.mode & 0o111
and member.name.rsplit("/", 1)[-1] in forbidden
]
if found:
raise SystemExit(f"runtime image contains browser or agent executables: {found}")
print("runtime analysis: no browser or agent executable payload")
PY
printf 'Docker E2E passed: %s; artifacts: %s\n' "$base_url" "$artifacts"