diff --git a/README.md b/README.md index 01edaf3..d321b16 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Filter a single relay: `make interop-remote RELAY=moxygen`. See [Getting Started | moqtransport | TUM | 13 | relay | (no persistent relay) | | moqtail | OzU | 14 | relay | `https://relay.moqtail.dev` | | libquicr | Cisco | 14 | relay | `https://us-west-2.relay.quicr.org:33437/relay` | -| imquic | Meetecho | 11-16 | relay | `https://lminiero.it:9000` | +| imquic | Meetecho | 16-17 | relay | `https://lminiero.it:9000` | This table is a snapshot — run `make interop-list` or see [`implementations.json`](./implementations.json) for the current state. See [IMPLEMENTATIONS.md](./IMPLEMENTATIONS.md) for how to add your implementation. diff --git a/builds/imquic/Dockerfile.client b/builds/imquic/Dockerfile.client new file mode 100644 index 0000000..1ca727a --- /dev/null +++ b/builds/imquic/Dockerfile.client @@ -0,0 +1,67 @@ +# syntax=docker/dockerfile:1 +# Dockerfile for imquic-moq-interop-test +# +# This Dockerfile is used by the moq-interop-runner builds framework. +# It expects to be run with the imquic repository root as the build context. +# +# Usage (via build.sh): +# ./builds/imquic/build.sh --target client +# ./builds/imquic/build.sh --local ~/git/imquic --target client + +FROM debian:bookworm-slim AS builder + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + build-essential git cmake make autoconf automake libtool \ + libglib2.0-dev libssl-dev libjansson-dev ca-certificates pkg-config && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /build + +# Copy source from build context (provided by build.sh) +COPY . /build/imquic + +# Build picoquic dependency, then imquic +RUN cd /build/imquic && \ + git clone https://github.com/private-octopus/picoquic /build/imquic/picoquic && \ + cd /build/imquic/picoquic && \ + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DPICOQUIC_FETCH_PTLS=Y . && \ + make -j$(nproc) && \ + cd /build/imquic && \ + ./autogen.sh && \ + ./configure --prefix=/build/imquic --enable-moq-examples --enable-qlog && \ + make -j$(nproc) install + +# Runtime image +FROM debian:bookworm-slim AS runtime + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + ca-certificates libglib2.0-0 libssl3 libjansson4 && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Copy binary and library +COPY --from=builder /build/imquic/bin/imquic-moq-interop-test /app/imquic-moq-interop-test +COPY --from=builder /build/imquic/lib/libimquic.so* /app/ + +RUN ldconfig + +# Copy entrypoint script (maintained separately for linting and testing) +# Note: This file is part of the moq-interop-runner builds, not the imquic source +COPY entrypoint-client.sh /app/entrypoint.sh +RUN chmod +x /app/entrypoint.sh + +# Create mlog directory +RUN mkdir -p /mlog + +# Environment variables with defaults +ENV RELAY_URL=https://relay:4443 +ENV TLS_DISABLE_VERIFY=0 +ENV LD_LIBRARY_PATH=/app:/usr/lib + +RUN useradd -r -u 1000 -g nogroup moq +USER moq + +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/builds/imquic/Dockerfile.relay b/builds/imquic/Dockerfile.relay new file mode 100644 index 0000000..261c358 --- /dev/null +++ b/builds/imquic/Dockerfile.relay @@ -0,0 +1,74 @@ +# syntax=docker/dockerfile:1 +# Dockerfile for imquic-moq-relay +# +# This Dockerfile is used by the moq-interop-runner builds framework. +# It expects to be run with the imquic repository root as the build context. +# +# Usage (via build.sh): +# ./builds/imquic/build.sh --target relay +# ./builds/imquic/build.sh --local ~/git/imquic --target relay + +FROM debian:bookworm-slim AS builder + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + build-essential git cmake make autoconf automake libtool \ + libglib2.0-dev libssl-dev libjansson-dev ca-certificates pkg-config && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /build + +# Copy source from build context (provided by build.sh) +COPY . /build/imquic + +# Build picoquic dependency, then imquic +RUN cd /build/imquic && \ + git clone https://github.com/private-octopus/picoquic /build/imquic/picoquic && \ + cd /build/imquic/picoquic && \ + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DPICOQUIC_FETCH_PTLS=Y . && \ + make -j$(nproc) && \ + cd /build/imquic && \ + ./autogen.sh && \ + ./configure --prefix=/build/imquic --enable-moq-examples --enable-qlog && \ + make -j$(nproc) install + +# Runtime image +FROM debian:bookworm-slim AS runtime + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + ca-certificates iproute2 libglib2.0-0 libssl3 libjansson4 && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Copy binary and library +COPY --from=builder /build/imquic/bin/imquic-moq-relay /app/imquic-moq-relay +COPY --from=builder /build/imquic/lib/libimquic.so* /app/ + +RUN ldconfig + +# Copy entrypoint script (maintained separately for linting and testing) +# Note: This file is part of the moq-interop-runner builds, not the imquic source +COPY entrypoint-relay.sh /app/run_endpoint.sh +RUN chmod +x /app/run_endpoint.sh + +# Create directories for mounts +RUN mkdir -p /mlog /certs + +# Environment variables (following QUIC interop runner conventions) +ENV MOQT_ROLE=relay +ENV MOQT_PORT=4443 +ENV LD_LIBRARY_PATH=/app:/usr/lib + +EXPOSE 4443/udp + +# Healthcheck verifies the relay is listening on the configured port +# Uses shell form to enable environment variable expansion at runtime +HEALTHCHECK --interval=5s --timeout=3s --start-period=5s \ + CMD sh -c 'ss -uln | grep -q ":${MOQT_PORT:-4443}" || exit 1' + +RUN useradd -r -u 1000 -g nogroup moq +USER moq + +ENTRYPOINT ["/app/run_endpoint.sh"] diff --git a/builds/imquic/build.sh b/builds/imquic/build.sh new file mode 100755 index 0000000..c3afa58 --- /dev/null +++ b/builds/imquic/build.sh @@ -0,0 +1,313 @@ +#!/bin/bash +# build.sh - Build imquic Docker images from source +# +# Usage: +# ./build.sh # Clone from default ref (main) +# ./build.sh --ref feature-branch # Clone specific branch/tag/commit +# ./build.sh --repo URL # Clone from a different repository (fork) +# ./build.sh --local ~/git/imquic # Use local checkout +# ./build.sh --target relay # Build only relay image +# ./build.sh --target client # Build only client image +# +# This script is designed to be easy to copy/paste for other implementations. +# Utility functions at the top can be extracted to a shared library. + +set -euo pipefail + +############################################################################# +# Configuration (implementation-specific) +############################################################################# + +IMPL_NAME="imquic" +REPO_URL="https://github.com/meetecho/imquic" +DEFAULT_REF="main" + +# Build directory (where this script lives) +BUILD_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SOURCES_DIR="${BUILD_DIR}/.sources" +RUNNER_ROOT="$(cd "${BUILD_DIR}/../.." && pwd)" + +############################################################################# +# Utility Functions (candidates for shared library) +############################################################################# + +log() { + echo "[build] $*" >&2 +} + +error() { + echo "[build] ERROR: $*" >&2 + exit 1 +} + +# Get git commit hash from a directory +get_git_commit() { + local dir="$1" + git -C "$dir" rev-parse HEAD 2>/dev/null || echo "unknown" +} + +# Check if git working directory is dirty +is_git_dirty() { + local dir="$1" + if git -C "$dir" diff --quiet HEAD 2>/dev/null && \ + git -C "$dir" diff --cached --quiet HEAD 2>/dev/null; then + echo "false" + else + echo "true" + fi +} + +# Get current timestamp in ISO 8601 format +get_timestamp() { + date -u +"%Y-%m-%dT%H:%M:%SZ" +} + +# Get the moq-interop-runner repo commit +get_runner_commit() { + git -C "$RUNNER_ROOT" rev-parse HEAD 2>/dev/null || echo "unknown" +} + +############################################################################# +# Argument Parsing +############################################################################# + +REF="" +LOCAL_PATH="" +TARGET="" # empty = build all targets +CUSTOM_REPO="" # override REPO_URL + +while [[ $# -gt 0 ]]; do + case $1 in + --ref) + if [[ -z "${2:-}" ]]; then + error "--ref requires a value" + fi + REF="$2" + shift 2 + ;; + --repo) + if [[ -z "${2:-}" ]]; then + error "--repo requires a value" + fi + CUSTOM_REPO="$2" + shift 2 + ;; + --local) + if [[ -z "${2:-}" ]]; then + error "--local requires a value" + fi + LOCAL_PATH="$2" + shift 2 + ;; + --target) + if [[ -z "${2:-}" ]]; then + error "--target requires a value" + fi + TARGET="$2" + shift 2 + ;; + --help|-h) + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --ref REF Git ref to checkout (branch/tag/commit)" + echo " --repo URL Clone from a different repository (fork)" + echo " --local PATH Use local checkout instead of cloning" + echo " --target NAME Build only specific target (relay|client)" + echo " --help Show this help" + echo "" + echo "Examples:" + echo " $0 # Clone main branch" + echo " $0 --ref v0.5.0 # Clone specific tag" + echo " $0 --repo https://github.com/user/imquic --ref branch" + echo " $0 --local ~/git/imquic # Use local checkout" + echo " $0 --local ~/git/imquic --target relay" + exit 0 + ;; + *) + error "Unknown option: $1" + ;; + esac +done + +# Apply --repo override if specified +if [[ -n "$CUSTOM_REPO" ]]; then + REPO_URL="$CUSTOM_REPO" +fi + +# Validate: can't specify both --ref and --local +if [[ -n "$REF" && -n "$LOCAL_PATH" ]]; then + error "Cannot specify both --ref and --local" +fi + +# Validate: --repo only makes sense with --ref (not --local) +if [[ -n "$CUSTOM_REPO" && -n "$LOCAL_PATH" ]]; then + error "Cannot specify both --repo and --local" +fi + +# Default to cloning if neither specified +if [[ -z "$REF" && -z "$LOCAL_PATH" ]]; then + REF="$DEFAULT_REF" +fi + +############################################################################# +# Source Preparation +############################################################################# + +if [[ -n "$LOCAL_PATH" ]]; then + # Using local checkout + if [[ ! -d "$LOCAL_PATH" ]]; then + error "Local path does not exist: $LOCAL_PATH" + fi + SOURCE_DIR="$(cd "$LOCAL_PATH" && pwd)" + SOURCE_TYPE="local" + log "Using local checkout: $SOURCE_DIR" +else + # Clone from remote + SOURCE_DIR="${SOURCES_DIR}/${IMPL_NAME}" + SOURCE_TYPE="git" + + mkdir -p "$SOURCES_DIR" + + if [[ -d "$SOURCE_DIR/.git" ]]; then + EXISTING_URL=$(git -C "$SOURCE_DIR" remote get-url origin 2>/dev/null || echo "") + if [[ "$EXISTING_URL" != "$REPO_URL" ]]; then + log "Repo URL changed ($EXISTING_URL -> $REPO_URL), re-cloning..." + rm -rf "$SOURCE_DIR" + git clone "$REPO_URL" "$SOURCE_DIR" + else + log "Updating existing clone..." + git -C "$SOURCE_DIR" fetch origin + fi + else + log "Cloning $REPO_URL..." + rm -rf "$SOURCE_DIR" + git clone "$REPO_URL" "$SOURCE_DIR" + fi + + log "Checking out ref: $REF" + git -C "$SOURCE_DIR" checkout "$REF" + git -C "$SOURCE_DIR" pull origin "$REF" 2>/dev/null || true +fi + +# Capture source provenance +SOURCE_COMMIT=$(get_git_commit "$SOURCE_DIR") +SOURCE_DIRTY=$(is_git_dirty "$SOURCE_DIR") + +############################################################################# +# Docker Builds +# +# We use docker build -f to keep Dockerfiles in the +# interop-runner repo while using the source repo as the build context. +# This avoids polluting local checkouts with extra files. +############################################################################# + +BUILT_IMAGES=() + +# Check for extra CA cert (useful for networks with TLS inspection) +CA_CERT_FILE="${BUILD_DIR}/extra-ca.pem" +SECRET_ARGS="" +if [[ -f "$CA_CERT_FILE" ]]; then + log "Found extra CA certificate: $CA_CERT_FILE" + SECRET_ARGS="--secret id=ca_cert,src=${CA_CERT_FILE}" +fi + +build_target() { + local target="$1" + local dockerfile="" + local image_name="" + local entrypoint_script="" + + case "$target" in + relay) + dockerfile="${BUILD_DIR}/Dockerfile.relay" + image_name="imquic-moq-relay" + entrypoint_script="${BUILD_DIR}/entrypoint-relay.sh" + ;; + client) + dockerfile="${BUILD_DIR}/Dockerfile.client" + image_name="imquic-moq-interop-test" + entrypoint_script="${BUILD_DIR}/entrypoint-client.sh" + ;; + *) + error "Unknown target: $target" + ;; + esac + + log "Building ${target} -> ${image_name}:latest" + log " Dockerfile: ${dockerfile}" + log " Context: ${SOURCE_DIR}" + + # Copy entrypoint script to build context (cleaned up after build) + local entrypoint_dest="${SOURCE_DIR}/$(basename "$entrypoint_script")" + cp "$entrypoint_script" "$entrypoint_dest" + + # shellcheck disable=SC2086 + if docker build \ + -f "${dockerfile}" \ + $SECRET_ARGS \ + -t "${image_name}:latest" \ + "$SOURCE_DIR"; then + rm -f "$entrypoint_dest" + else + rm -f "$entrypoint_dest" + error "Docker build failed for ${target}" + fi + + BUILT_IMAGES+=("{\"target\": \"${target}\", \"image\": \"${image_name}:latest\"}") +} + +if [[ -n "$TARGET" ]]; then + build_target "$TARGET" +else + build_target "relay" + build_target "client" +fi + +############################################################################# +# Provenance Output +############################################################################# + +TIMESTAMP=$(get_timestamp) +RUNNER_COMMIT=$(get_runner_commit) +IMAGES_JSON=$(IFS=,; echo "${BUILT_IMAGES[*]}") + +# Use jq for safe JSON generation to avoid injection issues +# shellcheck disable=SC2016 +PROVENANCE=$(jq -n \ + --arg impl "$IMPL_NAME" \ + --arg ts "$TIMESTAMP" \ + --arg runner_commit "$RUNNER_COMMIT" \ + --arg source_type "$SOURCE_TYPE" \ + --arg repo "$REPO_URL" \ + --arg ref "${REF:-}" \ + --arg local_path "${LOCAL_PATH:-}" \ + --arg commit "$SOURCE_COMMIT" \ + --argjson dirty "$SOURCE_DIRTY" \ + --argjson images "[$IMAGES_JSON]" \ + '{ + implementation: $impl, + timestamp: $ts, + runner_commit: $runner_commit, + source: { + type: $source_type, + repository: $repo, + ref: (if $ref == "" then "local" else $ref end), + local_path: (if $local_path == "" then null else $local_path end), + commit: $commit, + dirty: $dirty + }, + images: $images + }' +) + +# Save to file +echo "$PROVENANCE" > "${BUILD_DIR}/.last-build.json" +log "Provenance saved to ${BUILD_DIR}/.last-build.json" + +# Output to stdout for capture +echo "" +echo "=== Build Provenance ===" +echo "$PROVENANCE" + +log "Build complete!" diff --git a/builds/imquic/config.json b/builds/imquic/config.json new file mode 100644 index 0000000..ebf5d92 --- /dev/null +++ b/builds/imquic/config.json @@ -0,0 +1,18 @@ +{ + "name": "imquic", + "description": "Meetecho's imquic MoQT implementation", + "repository": "https://github.com/meetecho/imquic", + "default_ref": "main", + "targets": { + "relay": { + "dockerfile": "Dockerfile.relay", + "image_name": "imquic-moq-relay", + "context": ".sources/imquic" + }, + "client": { + "dockerfile": "Dockerfile.client", + "image_name": "imquic-moq-interop-test", + "context": ".sources/imquic" + } + } +} diff --git a/builds/imquic/entrypoint-client.sh b/builds/imquic/entrypoint-client.sh new file mode 100644 index 0000000..379e3c9 --- /dev/null +++ b/builds/imquic/entrypoint-client.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# entrypoint-client.sh - Wrapper script for imquic-moq-interop-test +# Translates standard MoQT interop environment variables to imquic-moq-interop-test CLI +# +# Expected environment: +# RELAY_URL - URL of relay to test against (required) +# TESTCASE - Specific test case to run (optional, runs all if not set) +# TLS_DISABLE_VERIFY - Set to 1 or true to disable TLS verification +# VERBOSE - Set to 1 or true for verbose output +# +# Exit codes: +# 0 - All tests passed +# 1 - One or more tests failed + +set -euo pipefail + +# Build command line arguments from environment variables +declare -a ARGS=() + +if [ -n "${RELAY_URL:-}" ]; then + ARGS+=("--relay" "$RELAY_URL") +fi + +if [ -n "${TESTCASE:-}" ]; then + ARGS+=("--test" "$TESTCASE") +fi + +if [ "${TLS_DISABLE_VERIFY:-}" = "1" ] || [ "${TLS_DISABLE_VERIFY:-}" = "true" ]; then + ARGS+=("--tls-disable-verify") +fi + +if [ "${VERBOSE:-}" = "1" ] || [ "${VERBOSE:-}" = "true" ]; then + ARGS+=("--verbose") +fi + +# Use ${ARGS[@]+"${ARGS[@]}"} for safe empty array handling +exec /app/imquic-moq-interop-test ${ARGS[@]+"${ARGS[@]}"} diff --git a/builds/imquic/entrypoint-relay.sh b/builds/imquic/entrypoint-relay.sh new file mode 100644 index 0000000..0006691 --- /dev/null +++ b/builds/imquic/entrypoint-relay.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# entrypoint-relay.sh - Wrapper script for imquic-moq-relay +# Translates standard MoQT interop environment variables to imquic-moq-relay CLI +# +# Expected environment: +# MOQT_ROLE - Role to run: relay (required, only relay supported) +# MOQT_PORT - Port to listen on (default: 4443) +# MOQT_CERT - Path to TLS certificate (default: /certs/cert.pem) +# MOQT_KEY - Path to TLS private key (default: /certs/priv.key) +# MOQT_MLOG_DIR - Directory for mlog files (default: /mlog) +# +# Expected mounts: +# /certs/cert.pem - TLS certificate +# /certs/priv.key - TLS private key +# +# Exit codes: +# 0 - Clean shutdown +# 1 - Configuration error +# 127 - Unsupported role + +set -euo pipefail + +ROLE="${MOQT_ROLE:-relay}" +PORT="${MOQT_PORT:-4443}" +CERT="${MOQT_CERT:-/certs/cert.pem}" +KEY="${MOQT_KEY:-/certs/priv.key}" +MLOG_DIR="${MOQT_MLOG_DIR:-/mlog}" + +case "$ROLE" in + relay) + echo "Starting imquic-moq-relay on port $PORT" + echo " Cert: $CERT" + echo " Key: $KEY" + echo " Mlog: $MLOG_DIR" + + if [ ! -f "$CERT" ]; then + echo "ERROR: Certificate not found at $CERT" >&2 + echo "Make sure /certs is mounted with cert.pem and priv.key" >&2 + exit 1 + fi + if [ ! -f "$KEY" ]; then + echo "ERROR: Private key not found at $KEY" >&2 + exit 1 + fi + + exec /app/imquic-moq-relay \ + -p "$PORT" -q -w \ + -c "$CERT" \ + -k "$KEY" \ + -Q "$MLOG_DIR" -J -l quic -l http3 -l moq + ;; + + *) + echo "Role '$ROLE' not supported by imquic adapter" >&2 + echo "Supported roles: relay" >&2 + exit 127 + ;; +esac