#!/usr/bin/env bash
set -Eeuo pipefail
source_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
tmp="$(mktemp -d)"
export COMPOSE_PROJECT_NAME
COMPOSE_PROJECT_NAME="it-tools-smoke-$(date +%s)-$$"
export FOSSARY_ENV_FILE="$tmp/recipe/.env"
export IT_TOOLS_BACKUP_DIR="$tmp/backups"
export IT_TOOLS_PORT
IT_TOOLS_PORT="$(python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1",0)); print(s.getsockname()[1]); s.close()')"
export IT_TOOLS_VERSION="${IT_TOOLS_VERSION:-2024.10.22-7ca5933}"
umask 077
mkdir "$tmp/recipe"
cp -R "$source_dir/config" "$tmp/recipe/"
chmod 644 "$tmp/recipe/config/nginx.conf"
cp "$source_dir/compose.yaml" "$source_dir/backup.sh" "$source_dir/restore.sh" "$tmp/recipe/"
printf 'IT_TOOLS_VERSION=%s\nIT_TOOLS_PORT=%s\n' "$IT_TOOLS_VERSION" "$IT_TOOLS_PORT" > "$FOSSARY_ENV_FILE"
compose=(docker compose --project-name "$COMPOSE_PROJECT_NAME" --env-file "$FOSSARY_ENV_FILE" --file "$tmp/recipe/compose.yaml")
cleanup() {
status=$?
trap - EXIT INT TERM
"${compose[@]}" logs --no-color --tail=40 || true
"${compose[@]}" down --volumes --remove-orphans || true
rm -rf "$tmp"
exit "$status"
}
trap cleanup EXIT
trap 'exit 130' INT TERM
"${compose[@]}" config --quiet
"${compose[@]}" up -d --wait --wait-timeout 120
base="http://127.0.0.1:$IT_TOOLS_PORT"
curl -fsS "$base/" > "$tmp/index.html"
grep -q '
IT Tools' "$tmp/index.html"
curl -fsS "$base/json-to-yaml-converter" | cmp -s - "$tmp/index.html"
[[ "$("${compose[@]}" exec -T it-tools id -u)" == 101 ]]
if "${compose[@]}" exec -T it-tools touch /usr/share/nginx/html/write-probe; then echo 'Static root was writable' >&2; exit 1; fi
# Isolated test dependency; the deployed recipe has no Node or browser requirement.
npm --prefix "$tmp/browser" install --no-audit --no-fund --no-package-lock --no-save playwright@1.62.1 >/dev/null
node "$tmp/browser/node_modules/playwright/cli.js" install chromium >/dev/null
node "$source_dir/smoke-browser.mjs" "$base" "$tmp/browser/node_modules/playwright/index.mjs"
python3 - "$tmp/recipe/config/nginx.conf" <<'PYCODE'
from pathlib import Path
import sys
p=Path(sys.argv[1]);s=p.read_text().replace('add_header Referrer-Policy', 'add_header X-Recipe-Recovery verified always;\n add_header Referrer-Policy');p.write_text(s)
PYCODE
"${compose[@]}" restart it-tools
curl --retry 20 --retry-delay 1 --retry-connrefused -fsSI "$base/" | grep -qi 'X-Recipe-Recovery: verified'
bash "$tmp/recipe/backup.sh"
archive="$(find "$tmp/backups" -name 'it-tools-*.tar.gz' | sort | tail -n 1)"
[[ "$(stat -c %a "$archive")" == 600 ]]
mkdir "$tmp/invalid"
printf invalid > "$tmp/invalid/sample"
tar -C "$tmp/invalid" -czf "$tmp/invalid.tar.gz" sample
if bash "$tmp/recipe/restore.sh" "$tmp/invalid.tar.gz"; then echo 'Incomplete archive accepted' >&2; exit 1; fi
curl -fsSI "$base/" | grep -qi 'X-Recipe-Recovery: verified'
mkdir "$tmp/invalid/config"
printf 'invalid nginx directive;\n' > "$tmp/invalid/config/nginx.conf"
cp "$FOSSARY_ENV_FILE" "$tmp/invalid/configuration.env"
cp "$tmp/recipe/compose.yaml" "$tmp/invalid/compose.yaml"
tar -C "$tmp/invalid" -czf "$tmp/invalid-nginx.tar.gz" config configuration.env compose.yaml
if bash "$tmp/recipe/restore.sh" "$tmp/invalid-nginx.tar.gz"; then echo 'Invalid Nginx accepted' >&2; exit 1; fi
curl -fsSI "$base/" | grep -qi 'X-Recipe-Recovery: verified'
"${compose[@]}" down --volumes
rm "$tmp/recipe/config/nginx.conf"
cp "$source_dir/config/nginx.conf" "$tmp/recipe/config/nginx.conf"
chmod 644 "$tmp/recipe/config/nginx.conf"
"${compose[@]}" up -d --wait --wait-timeout 120
if curl -fsSI "$base/" | grep -qi 'X-Recipe-Recovery: verified'; then echo 'Fresh configuration unexpectedly retained old marker' >&2; exit 1; fi
bash "$tmp/recipe/restore.sh" "$archive"
curl -fsSI "$base/" | grep -qi 'X-Recipe-Recovery: verified'
node "$source_dir/smoke-browser.mjs" "$base" "$tmp/browser/node_modules/playwright/index.mjs"
"${compose[@]}" stop it-tools
bash "$tmp/recipe/backup.sh"
[[ -z "$("${compose[@]}" ps --status running --quiet it-tools)" ]]
echo 'IT Tools smoke and fresh-configuration recovery passed; browser storage is not in server backups.'