#!/usr/bin/env bash # check-image.sh: build the room service's image, run it beside the relay's, and hold it to its # checks; then remove the containers, the network, the volume (and the images built, unless # KEEP=1). Nothing is pushed anywhere. # # bash joule-code/joule-code-rs/crates/joule-moq-room/deploy/check-image.sh (from the repository's root) # # The checks: the image runs as the user wai (uid 10001), not as root, with wai-room as its entry # point; `wai-room --version --json` runs in it and names the toolchain of this checkout # (wai/wai-rs/Cargo.toml); `keygen` makes the service's keys in its volume and prints the lines # that make a relay trust them; and with those lines in a relay's configuration, the two images # on one network, each run with a read-only root, every capability dropped and no privilege to # gain, the service answers /health from a process of uid 10001, and its clock's session to the # relay is up (`"relay": true`). # # ENGINE picks the container engine (docker, or podman), JOBS bounds the builds' parallel jobs, # PORT is the host port the service is published on. TAG names the image built (default # wai-room-check:); IMAGE checks that image instead of building one; RELAY_IMAGE is the # relay's image to run beside it (default: built from the relay's Containerfile); KEEP=1 keeps the # images built. VERSION is the toolchain version the image must state (default this checkout's). set -euo pipefail ENGINE=${ENGINE:-docker} JOBS=${JOBS:-8} PORT=${PORT:-18745} here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) root=$(cd "$here/../../../../.." && pwd) relay_dir="$root/joule-code/joule-code-rs/crates/joule-moq-relay/deploy" VERSION=${VERSION:-$(sed -n 's/^version = "\(.*\)"$/\1/p' "$root/wai/wai-rs/Cargo.toml" | head -1)} id="wai-room-check-$$" work=$(mktemp -d) built=() cleanup() { "$ENGINE" rm -f "$id-room" "$id-relay" >/dev/null 2>&1 || true "$ENGINE" network rm "$id" >/dev/null 2>&1 || true "$ENGINE" volume rm -f "$id" >/dev/null 2>&1 || true if [ "${KEEP:-0}" != 1 ]; then for b in ${built[@]+"${built[@]}"}; do "$ENGINE" image rm -f "$b" >/dev/null 2>&1 || true; done fi rm -rf "$work" } trap cleanup EXIT die() { echo "check-image: $*" >&2; exit 1; } build() { # local ignore=() [ "$(basename "$ENGINE")" = podman ] && ignore=(--ignorefile "$2/Containerfile.dockerignore") "$ENGINE" build ${ignore[@]+"${ignore[@]}"} --build-arg JOBS="$JOBS" -f "$2/Containerfile" -t "$1" "$root" built+=("$1") } if [ -n "${IMAGE:-}" ]; then tag="$IMAGE" else tag="${TAG:-wai-room-check:$$}" build "$tag" "$here" fi if [ -n "${RELAY_IMAGE:-}" ]; then relay="$RELAY_IMAGE" else relay="wai-relay-for-room-check:$$" build "$relay" "$relay_dir" fi user=$("$ENGINE" image inspect --format '{{.Config.User}}' "$tag") [ "$user" = wai ] || die "the image runs as '${user:-root}', not as wai" entry=$("$ENGINE" image inspect --format '{{json .Config.Entrypoint}}' "$tag") [ "$entry" = '["/usr/local/bin/wai-room"]' ] || die "the image's entry point is $entry, not wai-room" uid=$("$ENGINE" run --rm --entrypoint /usr/bin/id "$tag" -u) || die "id does not run in the image" [ "$uid" = 10001 ] || die "the image's user has uid $uid, not 10001" out=$("$ENGINE" run --rm "$tag" --version --json) || die "wai-room --version does not run in the image" python3 -I -c ' import json, sys d = json.loads(sys.argv[1])["data"] assert d["program"] == "wai-room" and d["toolchain"] == sys.argv[2], d print("image: wai-room %s, of the WAI toolchain %s" % (d["version"], d["toolchain"])) ' "$out" "$VERSION" || die "wai-room --version --json says $out, not the toolchain $VERSION" "$ENGINE" volume create "$id" >/dev/null "$ENGINE" run --rm -v "$id:/var/lib/wai-room" "$tag" keygen /var/lib/wai-room/keys.toml > "$work/lines.toml" \ || die "keygen does not make the service's keys in its volume" grep -q '^\[\[auth.grants\]\]' "$work/lines.toml" || die "keygen printed no relay configuration lines" # The relay's configuration in its image, with the lines that trust the service's keys. cat "$relay_dir/relay.toml" "$work/lines.toml" > "$work/relay.toml" chmod 644 "$work/relay.toml" "$ENGINE" network create "$id" >/dev/null "$ENGINE" run -d --name "$id-relay" --network "$id" --network-alias relay \ -v "$work/relay.toml:/etc/wai-relay/relay.toml:ro" \ --read-only --tmpfs /tmp --cap-drop ALL --security-opt no-new-privileges "$relay" >/dev/null "$ENGINE" run -d --name "$id-room" --network "$id" -p "127.0.0.1:$PORT:9682" -v "$id:/var/lib/wai-room" \ --read-only --tmpfs /tmp --cap-drop ALL --security-opt no-new-privileges "$tag" >/dev/null health="" for _ in $(seq 1 150); do health=$(curl -fsS "http://127.0.0.1:$PORT/health" 2>/dev/null || true) python3 -I -c 'import json,sys; d=json.loads(sys.argv[1]); sys.exit(0 if d.get("ok") is True and d.get("relay") is True else 1)' "$health" 2>/dev/null && break sleep 0.2 done python3 -I -c ' import json, sys d = json.loads(sys.argv[1] or "{}") assert d.get("ok") is True and d.get("relay") is True, d print("image: /health ok, the clock connected to the relay, room %s" % d["room"]) ' "$health" || { "$ENGINE" logs "$id-room" 2>&1 | tail -20 >&2 "$ENGINE" logs "$id-relay" 2>&1 | tail -20 >&2 die "the service did not answer /health with its clock connected to the relay (last: ${health:-nothing})" } status=$("$ENGINE" exec "$id-room" cat /proc/1/status) || die "the service's process cannot be read" grep -qE '^Uid:[[:space:]]+10001[[:space:]]' <<<"$status" || die "the service runs as $(grep '^Uid:' <<<"$status"), not uid 10001" echo "image: built, ran read-only with no capabilities as uid 10001 beside the relay's, and held to its checks"