#!/usr/bin/env bash # Actual Immich API fixture only. Never installed-node or Rust/browser acceptance. set -euo pipefail repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) server=${ARCHY_CLOUD_IMMICH_IMAGE:?Immutable Immich 2.7.4 image required} db=${ARCHY_CLOUD_IMMICH_DB_IMAGE:?Immutable compatible Postgres image required} redis=${ARCHY_CLOUD_IMMICH_REDIS_IMAGE:?Immutable Redis image required} if [[ ${ARCHY_CLOUD_BROWSER_QUALIFY:-0} == 1 ]]; then : "${ARCHY_TEST_REUSE_ARTIFACT:?Compile and bind the artifact before starting Immich}" : "${ARCHY_TEST_CLOUD_RUNTIME_IMAGE:?Exact browser runtime required}" fi for image in "$server" "$db" "$redis"; do [[ "$image" =~ ^(sha256:)?[a-f0-9]{64}$ ]] || { echo 'Full immutable image IDs required' >&2; exit 2; } podman image exists "$image" done work=$(mktemp -d /tmp/archy-immich.XXXXXXXX) name="archy-cloud-immich-${work##*.}" containers=() cleanup() { result=$? trap - EXIT for ((i=${#containers[@]}-1; i>=0; i--)); do c=${containers[$i]} if ! podman container exists "$c"; then continue; fi if [[ "$result" != 0 ]]; then timeout 5 podman inspect "$c" --format '{{json .State}}' || true timeout 5 podman exec "$c" /bin/sh -c 'grep . /sys/fs/cgroup/memory.events /sys/fs/cgroup/memory.peak /sys/fs/cgroup/memory.max /sys/fs/cgroup/memory.swap.max' || true timeout 5 podman logs --tail 200 "$c" 2>&1 || true fi timeout --kill-after=2s 20 podman rm -f --volumes --time 0 "$c" >/dev/null 2>&1 || true if podman container exists "$c"; then echo "Cleanup failed: $c" >&2; result=1; fi done rm -rf -- "$work" exit "$result" } trap cleanup EXIT trap 'exit 130' INT trap 'exit 143' TERM : > "$work/resolv.conf" # No host inventory; upstream workers still need standard loopback resolution. printf '127.0.0.1 localhost\n::1 localhost\n' > "$work/hosts" common=(--pull=never --image-volume=ignore --read-only --pid=private --ipc=private --http-proxy=false --no-hosts --cap-drop=all --security-opt=no-new-privileges --cpus=1 --pids-limit=128 --timeout=420 --log-driver=k8s-file --log-opt=max-size=4mb --label org.archipelago.cloud-fixture=immich274-v1 --volume "$work/resolv.conf:/etc/resolv.conf:ro" --volume "$work/hosts:/etc/hosts:ro" --tmpfs /tmp:rw,notmpcopyup,nosuid,nodev,size=64m) # The database owns a network-none namespace; no infra image or published ports. containers+=("$name-db") podman run -d --name "$name-db" "${common[@]}" --network=none \ --memory=1g --memory-swap=1g --cap-add=chown,dac_override,fowner,setuid,setgid \ --tmpfs /var/lib/postgresql/data:rw,nosuid,nodev,size=768m \ --tmpfs /run/postgresql:rw,nosuid,nodev,size=16m \ --tmpfs /etc/postgresql:rw,nosuid,nodev,size=8m \ --env POSTGRES_PASSWORD=Synthetic-Immich-DB-Only-2026! \ --env POSTGRES_USER=postgres --env POSTGRES_DB=immich "$db" >/dev/null common+=(--network="container:$name-db") containers+=("$name-redis") podman run -d --name "$name-redis" "${common[@]}" --memory=128m --memory-swap=128m \ --cap-add=chown,setuid,setgid --tmpfs /data:rw,nosuid,nodev,size=64m \ "$redis" redis-server --save '' --appendonly no >/dev/null ready=0 for _ in $(seq 1 60); do if timeout --kill-after=2s 3 podman exec "$name-db" pg_isready -U postgres -d immich >/dev/null 2>&1; then ready=1; break; fi sleep 1 done [[ "$ready" == 1 ]] || { echo 'Database readiness failed' >&2; exit 1; } containers+=("$name-server") podman run -d --name "$name-server" "${common[@]}" --cpus=2 \ --memory=2g --memory-swap=2g --pids-limit=256 \ --tmpfs /usr/src/app/upload:rw,nosuid,nodev,size=256m \ --env DB_HOSTNAME=127.0.0.1 --env DB_USERNAME=postgres --env DB_DATABASE_NAME=immich \ --env DB_PASSWORD=Synthetic-Immich-DB-Only-2026! --env REDIS_HOSTNAME=127.0.0.1 \ --env UPLOAD_LOCATION=/usr/src/app/upload --env IMMICH_MACHINE_LEARNING_ENABLED=false \ --volume "$repo/tests/cloud/immich-upstream-contract.mjs:/contract.mjs:ro" \ --volume "$repo/tests/cloud/immich-fixture.png:/fixture.png:ro" "$server" >/dev/null ready=0 for _ in $(seq 1 90); do if timeout --kill-after=2s 3 podman exec "$name-server" node -e \ 'fetch("http://127.0.0.1:2283/api/server/ping",{signal:AbortSignal.timeout(1500)}).then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))' >/dev/null 2>&1; then ready=1; break; fi sleep 2 done [[ "$ready" == 1 ]] || { echo 'Immich readiness failed' >&2; exit 1; } # /server/ping is ready before the separate worker finishes its fresh geodata # import. Do not spend the thumbnail assertion's budget waiting for bootstrap. ready=0 for _ in $(seq 1 90); do logs=$(timeout --kill-after=2s 10 podman logs --tail 80 "$name-server" 2>&1) || { echo 'Worker log readiness probe timed out' >&2; continue; } if [[ "$logs" == *'Immich Microservices is running'* ]]; then ready=1; break; fi sleep 2 done [[ "$ready" == 1 ]] || { echo 'Immich worker readiness failed' >&2; exit 1; } podman inspect "$name-db" "$name-redis" "$name-server" | python3 "$repo/scripts/validate-immich-test-fixture.py" "$name" "$db" "$redis" "$server" >/dev/null if [[ ${ARCHY_CLOUD_BROWSER_QUALIFY:-0} == 1 ]]; then : "${ARCHY_TEST_REUSE_ARTIFACT:?Compile and bind the artifact before starting Immich}" : "${ARCHY_TEST_CLOUD_RUNTIME_IMAGE:?Exact browser runtime required}" run_bound_tests() { ARCHY_TEST_ISOLATOR=podman ARCHY_TEST_CLOUD_UPSTREAM=immich274 \ ARCHY_TEST_CLOUD_FIXTURE_CONTAINER="$name-db" ARCHY_TEST_CLOUD_FIXTURE_IMAGE="$db" \ ARCHY_TEST_IMMICH_SERVER_IMAGE="$server" ARCHY_TEST_IMMICH_REDIS_IMAGE="$redis" \ bash "$repo/scripts/test-backend-isolated.sh" "$@" } # Matching focused source tests and actual browser share one retained binary. # Ignored real-Nextcloud tests are not selected by the normal unit filter. run_bound_tests cloud_sources:: --nocapture run_bound_tests --ignored --exact api::handler::cloud_browser_tests::immich_browser_rpc_without_filebrowser --nocapture else timeout --kill-after=5s 120 podman exec "$name-server" node /contract.mjs fi if [[ ${ARCHY_CLOUD_BROWSER_QUALIFY:-0} == 1 ]]; then echo 'PASS isolated Immich source and production browser/RPC batch; synthetic session/runtime binding, not installed lifecycle or native identity acceptance.' else echo 'PASS portable isolated Immich protocol; not Rust/browser, installed lifecycle or native identity acceptance.' fi