#!/usr/bin/env bash set -euo pipefail ROOT="$(mktemp -d /tmp/luci-manual-XXXXXX)" LUCI="${LUCI_BIN:-${ROOT}/luci}" HTTP_ADDR="${LUCI_HTTP_ADDR:-127.0.0.1:18080}" CHROME="${CHROME:-}" PODMAN_SOCKET="${LUCI_PODMAN_SOCKET:-unix://${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/podman/podman.sock}" PODMAN_PID="" LUCI_PID="" cleanup() { if [[ -n "${LUCI_PID}" ]]; then kill "${LUCI_PID}" 2>/dev/null || true fi if [[ -n "${PODMAN_PID}" ]]; then kill "${PODMAN_PID}" 2>/dev/null || true fi echo "manual e2e root: ${ROOT}" } trap cleanup EXIT find_chrome() { if [[ -n "${CHROME}" ]]; then printf '%s\n' "${CHROME}" return fi for candidate in google-chrome-stable google-chrome chromium chromium-browser /opt/google/chrome/chrome; do if command -v "${candidate}" >/dev/null 2>&1 || [[ -x "${candidate}" ]]; then printf '%s\n' "${candidate}" return fi done echo "Chrome/Chromium not found" >&2 exit 1 } ensure_podman_socket() { local socket_path="${PODMAN_SOCKET#unix://}" if [[ -S "${socket_path}" ]]; then return fi mkdir -p "$(dirname "${socket_path}")" podman system service --time=0 "${PODMAN_SOCKET}" >"${ROOT}/podman-service.log" 2>&1 & PODMAN_PID="$!" for _ in {1..50}; do [[ -S "${socket_path}" ]] && return sleep 0.1 done cat "${ROOT}/podman-service.log" >&2 || true echo "Podman socket did not start: ${socket_path}" >&2 exit 1 } wait_http() { for _ in {1..80}; do curl -fsS "http://${HTTP_ADDR}/" >"${ROOT}/dashboard.html" && return sleep 0.1 done echo "Luci HTTP did not start" >&2 exit 1 } wait_log() { local wanted="$1" for _ in {1..200}; do if find "${ROOT}/data/logs" -type f -name '*.zst' -print0 | xargs -0 -r zstdcat 2>/dev/null | grep -q "${wanted}"; then return fi sleep 0.1 done echo "Luci log did not contain ${wanted}" >&2 exit 1 } build_luci() { if [[ -x "${LUCI}" ]]; then return fi (cd services/luci && go build -o "${LUCI}" ./cmd/luci) } write_repo() { local work="${ROOT}/work-repo" git init "${work}" >/dev/null git -C "${work}" config user.email test@example.invalid git -C "${work}" config user.name Test mkdir -p "${work}/.ci" cat >"${work}/.ci/ci.kdl" <<'KDL' job "push-main" { image "docker.io/library/golang:1.26" run "git rev-parse --is-inside-work-tree | grep -q true" run "git ls-files | grep -qx README.md" run "echo PUSH_OK GIT_OK" trigger { push { branch "main" } } } job "push-path" { image "docker.io/library/alpine:latest" run "echo PATH_OK" trigger { push { branch "main" include "README.md" } } } job "push-ignore" { image "docker.io/library/alpine:latest" run "echo SHOULD_NOT_RUN" trigger { push { include "docs/**" } } } job "matrix" { image "docker.io/library/alpine:latest" run "echo MATRIX_${axis}" trigger { push { branch "main" } } matrix { axis "one" "two" } } job "tag-release" { image "docker.io/library/alpine:latest" run "echo TAG_OK" trigger { push { tag "v*" } } } job "scheduled" { image "docker.io/library/alpine:latest" run "echo SCHEDULE_OK" trigger { schedule "* * * * *" } } job "manual-rev" { image "docker.io/library/alpine:latest" run "grep -q second README.md && echo MANUAL_REV_OK" } job "manual-secret" { image "docker.io/library/alpine:latest" run "test $TOKEN = s3cr3t && test -f .secrets/key && echo MANUAL_SECRET_OK" secret "TOKEN" env="TOKEN" secret "KEY" file=".secrets/key" } KDL echo first >"${work}/README.md" git -C "${work}" add . >/dev/null git -C "${work}" commit -m initial >/dev/null git -C "${work}" branch -M main git clone --bare "${work}" "${ROOT}/repos/repo.git" >/dev/null 2>&1 git -C "${work}" remote add origin "${ROOT}/repos/repo.git" } main() { local chrome chrome="$(find_chrome)" ensure_podman_socket build_luci mkdir -p "${ROOT}/repos" write_repo export LUCI_DATA_DIR="${ROOT}/data" export LUCI_REPO_ROOTS="${ROOT}/repos" export LUCI_HTTP_ADDR="${HTTP_ADDR}" export LUCI_BASE_URL="http://${HTTP_ADDR}" export LUCI_INBOX_DIR="${ROOT}/inbox" export LUCI_PODMAN_SOCKET="${PODMAN_SOCKET}" export LUCI_DEFAULT_MEMORY="512m" export LUCI_DEFAULT_CPUS="1" "${LUCI}" env-check >"${ROOT}/env-check.out" "${LUCI}" serve --once >"${ROOT}/baseline.out" echo second >"${ROOT}/work-repo/README.md" git -C "${ROOT}/work-repo" add README.md >/dev/null git -C "${ROOT}/work-repo" commit -m update >/dev/null local update_rev update_rev="$(git -C "${ROOT}/work-repo" rev-parse HEAD)" git -C "${ROOT}/work-repo" push origin main >/dev/null 2>&1 "${LUCI}" serve --once >"${ROOT}/push.out" git -C "${ROOT}/work-repo" -c tag.gpgSign=false tag v1.0.0 git -C "${ROOT}/work-repo" push origin v1.0.0 >/dev/null 2>&1 "${LUCI}" serve --once >"${ROOT}/tag.out" USER=manual "${LUCI}" run repo manual-rev --rev "${update_rev}" >"${ROOT}/manual-rev-id.out" "${LUCI}" serve --once >"${ROOT}/manual-rev.out" mkdir -p "${ROOT}/data/secrets/repo/manual-secret" printf 's3cr3t\n' >"${ROOT}/data/secrets/repo/manual-secret/TOKEN" printf 'keydata\n' >"${ROOT}/data/secrets/repo/manual-secret/KEY" USER=manual "${LUCI}" run repo manual-secret --ref main >"${ROOT}/manual-id.out" "${LUCI}" serve --once >"${ROOT}/manual.out" SSH_ORIGINAL_COMMAND='ci env-check' "${LUCI}" ssh >"${ROOT}/ssh.out" "${LUCI}" serve >"${ROOT}/http.out" 2>"${ROOT}/http.err" & LUCI_PID="$!" wait_http wait_log SCHEDULE_OK curl -fsS "http://${HTTP_ADDR}/llms.txt" >"${ROOT}/llms.txt" local chrome_args=("${chrome}" --headless=new --no-sandbox --disable-gpu --user-data-dir="${ROOT}/chrome") "${chrome_args[@]}" --dump-dom "http://${HTTP_ADDR}/" >"${ROOT}/chrome-dashboard.html" "${chrome_args[@]}" --dump-dom "http://${HTTP_ADDR}/repos/repo" >"${ROOT}/chrome-repo.html" local run_id run_id="$(grep -o '/runs/[^"<]*' "${ROOT}/chrome-dashboard.html" | head -1 | cut -d/ -f3)" "${chrome_args[@]}" --dump-dom "http://${HTTP_ADDR}/runs/${run_id}" >"${ROOT}/chrome-run.html" "${chrome_args[@]}" --dump-dom "http://${HTTP_ADDR}/docs" >"${ROOT}/chrome-docs.html" "${chrome_args[@]}" --screenshot="${ROOT}/chrome-dashboard.png" "http://${HTTP_ADDR}/" >/dev/null : >"${ROOT}/combined.log" while IFS= read -r -d '' log; do zstdcat "${log}" >>"${ROOT}/combined.log" done < <(find "${ROOT}/data/logs" -type f -name '*.zst' -print0) for want in PUSH_OK GIT_OK PATH_OK MATRIX_one MATRIX_two TAG_OK MANUAL_REV_OK MANUAL_SECRET_OK SCHEDULE_OK; do grep -q "${want}" "${ROOT}/combined.log" done if grep -q SHOULD_NOT_RUN "${ROOT}/combined.log"; then echo 'excluded path job ran' >&2 exit 1 fi grep -q '

repo

' "${ROOT}/chrome-repo.html" grep -q 'Run ' "${ROOT}/chrome-run.html" grep -q 'Luci docs' "${ROOT}/chrome-docs.html" grep -q '# Luci CI' "${ROOT}/llms.txt" if grep -Eiq '<(form|button)\b' "${ROOT}"/chrome-{dashboard,repo,run,docs}.html; then echo 'mutating UI controls found' >&2 exit 1 fi local logs logs="$(find "${ROOT}/data/logs" -type f | wc -l)" if [[ "${logs}" != 8 ]]; then echo "unexpected log count: ${logs}, want 8" >&2 exit 1 fi printf 'ok: root=%s run=%s logs=%s\n' "${ROOT}" "${run_id}" "${logs}" } main "$@"