diff --git a/checksums.json b/checksums.json index 9222a6f..df59297 100644 --- a/checksums.json +++ b/checksums.json @@ -121,7 +121,7 @@ } }, "quarto": { - "pinned_version": "1.8.24", + "pinned_version": "1.10.18", "archive_format": "tar.gz / deb / pkg / msi", "archive_url_template": "https://github.com/quarto-dev/quarto-cli/releases/download/v{version}/quarto-{version}-linux-{arch}.tar.gz", "checksum_asset": "quarto-{version}-checksums.txt", @@ -131,6 +131,10 @@ "1.8.24": { "amd64": "6b83c1c9b6f2ce6454798b42260bd2ee184551d74debe817b8aaf28b09ac22d0", "arm64": "89a97a65a242a5b9b010a9f9978928c1d8e4ac02a558c9cd91a110c3f2611fdd" + }, + "1.10.18": { + "amd64": "afad071b5bd22c02f2d300695743189d3650e0537a53073e654b630cff2b0c73", + "arm64": "f6a07df68e25330b5df34f65d3df66bca605acce3b830c593a58e91884d4cf6c" } } }, diff --git a/features/quarto-chromium/feature.json b/features/quarto-chromium/feature.json index dc771aa..cf85f69 100644 --- a/features/quarto-chromium/feature.json +++ b/features/quarto-chromium/feature.json @@ -19,8 +19,8 @@ "email": "emmanuel.bruno@univ-tln.fr" }, "postInstallCheck": { - "command": "sh -c 'command -v chromium >/dev/null 2>&1 || command -v chromium-browser >/dev/null 2>&1 || find ${HOME}/.local/share/quarto/chromium ${HOME}/.quarto ${HOME}/.local/share/quarto-chromium \\( -type f -o -type l \\) \\( -name chrome -o -name chromium -o -name chromium-browser -o -name headless_shell \\) -print -quit 2>/dev/null | grep -q .'", - "description": "Verify Chromium is available for Quarto rendering" + "command": "chromium_bin=\"$(command -v chromium || command -v chromium-browser || command -v chrome-headless-shell || command -v headless_shell || true)\"\nif [ -z \"${chromium_bin}\" ]; then\n echo \"quarto-chromium: CHECK FAIL - no chromium/chromium-browser/chrome-headless-shell on PATH\" >&2\n exit 1\nfi\nerr_file=\"$(mktemp)\"\nbin_base=\"$(basename \"${chromium_bin}\")\"\ncase \"${bin_base}\" in\n chrome-headless-shell|headless_shell)\n out=\"$(timeout 60 \"${chromium_bin}\" --disable-gpu --dump-dom about:blank 2>\"${err_file}\")\" && rc=0 || rc=$?\n ;;\n *)\n out=\"$(timeout 60 \"${chromium_bin}\" --headless --disable-gpu --dump-dom about:blank 2>\"${err_file}\")\" && rc=0 || rc=$?\n ;;\nesac\nif [ \"${rc}\" -ne 0 ]; then\n echo \"quarto-chromium: CHECK FAIL - headless chromium exited ${rc}\" >&2\n cat \"${err_file}\" >&2\n rm -f \"${err_file}\"\n exit 1\nfi\nrm -f \"${err_file}\"\nif [ -z \"${out}\" ]; then\n echo \"quarto-chromium: CHECK FAIL - headless chromium produced no output\" >&2\n exit 1\nfi\necho \"quarto-chromium: check OK (headless chromium rendered via ${chromium_bin})\"", + "description": "Functionally verify headless Chromium starts and renders (CI-safe shim + shared libraries), not just that the binary exists" }, "documentationURL": "./features/quarto-chromium/README.md" } diff --git a/features/quarto-chromium/install.sh b/features/quarto-chromium/install.sh index 29aea20..4a6876a 100755 --- a/features/quarto-chromium/install.sh +++ b/features/quarto-chromium/install.sh @@ -17,6 +17,38 @@ HOME_DIR="/home/${NB_USER}" echo "quarto-chromium: installing Chromium runtime" +# Shared libraries required by the Chromium binary that 'quarto install +# chromium' downloads (verified via ldd against the bundled build: +# libnss3, libnspr4, libatk*, libatk-bridge*, libcups, libdrm, +# libxkbcommon, libXcomposite, libXdamage, libXfixes, libXrandr, libgbm, +# libpango, libcairo, libgtk-3, libasound, libatspi, libxshmfence). +# Without these the binary dies at startup with +# "error while loading shared libraries: libnss3.so", which Quarto's +# server-side render reports only as a bare "ERROR: AssertionError:". +# (noble 24.04 t64 package names; verified with apt-get install --dry-run.) +export DEBIAN_FRONTEND=noninteractive +apt-get update -qq +apt-get install -y --no-install-recommends \ + libnss3 \ + libnspr4 \ + libatk1.0-0t64 \ + libatk-bridge2.0-0t64 \ + libcups2t64 \ + libdrm2 \ + libxkbcommon0 \ + libxcomposite1 \ + libxdamage1 \ + libxfixes3 \ + libxrandr2 \ + libgbm1 \ + libpango-1.0-0 \ + libcairo2 \ + libasound2t64 \ + libatspi2.0-0t64 \ + libgtk-3-0t64 \ + libxshmfence1 +rm -rf /var/lib/apt/lists/* + # Use the available Quarto binary (search PATH or common /opt/quarto/* location) TMP_SCRIPT="/tmp/quarto-chromium-install-${NB_USER}.sh" cat > "${TMP_SCRIPT}" <<'BASH' @@ -33,7 +65,9 @@ if [ -z "${QUARTO_BIN}" ]; then done fi if [ -n "${QUARTO_BIN}" ] && [ -x "${QUARTO_BIN}" ]; then - CI=true "${QUARTO_BIN}" install --no-prompt --log "${HOME}/.quarto-chromium-install.log" --log-level debug chromium \ + CI=true "${QUARTO_BIN}" install --no-prompt --log "${HOME}/.quarto-chromium-install.log" --log-level debug chrome-headless-shell \ + || CI=true "${QUARTO_BIN}" install chrome-headless-shell --no-prompt \ + || CI=true "${QUARTO_BIN}" install --no-prompt --log "${HOME}/.quarto-chromium-install.log" --log-level debug chromium \ || CI=true "${QUARTO_BIN}" install chromium --no-prompt \ || true else @@ -51,7 +85,10 @@ find_chromium_bin() { "${HOME_DIR}/.quarto/bin/chromium" \ "${HOME_DIR}/.quarto/bin/chromium-browser" \ "${HOME_DIR}/.quarto/bin/chrome" \ + "${HOME_DIR}/.quarto/bin/chrome-headless-shell" \ + "${HOME_DIR}/.quarto/bin/headless_shell" \ "${HOME_DIR}/.local/share/quarto/chromium/linux-"*/chrome-linux/chrome \ + "${HOME_DIR}/.local/share/quarto/chrome-headless-shell/linux-"*/chrome-headless-shell \ "${HOME_DIR}/.local/share/quarto-chromium/chromium" \ "${HOME_DIR}/.local/share/quarto-chromium/chromium-browser" \ "${HOME_DIR}/.local/share/quarto-chromium/chrome" \ @@ -84,7 +121,7 @@ find_chromium_bin() { do [ -d "${search_dir}" ] || continue found=$( - find "${search_dir}" \( -type f -o -type l \) \( -name chrome -o -name chromium -o -name chromium-browser -o -name headless_shell \) 2>/dev/null | + find "${search_dir}" \( -type f -o -type l \) \( -name chrome -o -name chrome-headless-shell -o -name chromium -o -name chromium-browser -o -name headless_shell \) 2>/dev/null | while IFS= read -r candidate; do if [ -x "${candidate}" ]; then printf '%s\n' "${candidate}" @@ -101,6 +138,63 @@ find_chromium_bin() { return 1 } +# Wrap the real Chromium binary IN PLACE with a CI-safe shim. +# +# Why: courseware render CI runs the container as root in a restricted +# docker context. Quarto's own browser spawn (criClient in quarto.js) +# passes --no-sandbox --disable-gpu but NOT --disable-dev-shm-usage, and +# the default 64 MB /dev/shm is too small for headless Chrome, so the +# Chromium child dies at startup and Quarto's error path reports a bare +# "ERROR: AssertionError:" instead of the real cause. The shim bakes in +# the CI-safe flags for every invocation. +# +# In-place wrapping keeps all of Quarto's discovery routes on the shim: +# the /usr/local/bin + ~/.local/bin symlinks created below, and Quarto's +# own install dir (~/.local/share/quarto/chromium/...), all resolve to +# this same file. +wrap_chromium_shim() { + local bin="$1" + local real bin_name resolved + resolved="$(readlink -f "${bin}" 2>/dev/null || true)" + if [ -n "${resolved}" ]; then + bin="${resolved}" + fi + real="${bin}.real" + bin_name="$(basename "${bin}")" + + # Idempotent: a shim is already in place. + if [ -e "${bin}" ] && head -n 5 "${bin}" 2>/dev/null | grep -q 'solen-chromium-shim'; then + echo "quarto-chromium: shim already in place at ${bin}" + return 0 + fi + + if [ -e "${bin}" ]; then + # Fresh binary at the public path (first run, or 'quarto install + # chromium' replaced the shim on a re-run): move it aside, overwriting + # any stale .real. + mv -f "${bin}" "${real}" + elif [ ! -e "${real}" ]; then + echo "quarto-chromium: ERROR: no Chromium binary at ${bin} to wrap" >&2 + return 1 + fi + # else: public path gone but .real remains — fall through and re-write + # the shim pointing at it. + + cat > "${bin}" </dev/null || echo "\$0")" +exec "\$(dirname "\$self")/${bin_name}.real" --no-sandbox --disable-gpu --disable-dev-shm-usage "\$@" +SHIM + chmod 0755 "${bin}" + chown "${NB_UID}":"${NB_GID}" "${bin}" "${real}" || true + echo "quarto-chromium: installed CI-safe shim at ${bin} (original: ${real})" +} + CHROMIUM_BIN="$(find_chromium_bin || true)" if [ -n "${CHROMIUM_BIN}" ]; then echo "quarto-chromium: found Chromium binary at ${CHROMIUM_BIN}" @@ -146,11 +240,18 @@ if [ -n "${CHROMIUM_BIN}" ]; then mkdir -p "${HOME_DIR}/.local/bin" || true ln -sf "${CHROMIUM_BIN}" /usr/local/bin/chromium || true ln -sf "${CHROMIUM_BIN}" /usr/local/bin/chromium-browser || true + ln -sf "${CHROMIUM_BIN}" /usr/local/bin/chrome-headless-shell || true + ln -sf "${CHROMIUM_BIN}" /usr/local/bin/headless_shell || true ln -sf "${CHROMIUM_BIN}" "${HOME_DIR}/.local/bin/chromium" || true ln -sf "${CHROMIUM_BIN}" "${HOME_DIR}/.local/bin/chromium-browser" || true - chown "${NB_UID}":"${NB_GID}" "${HOME_DIR}/.local/bin/chromium" "${HOME_DIR}/.local/bin/chromium-browser" || true + ln -sf "${CHROMIUM_BIN}" "${HOME_DIR}/.local/bin/chrome-headless-shell" || true + ln -sf "${CHROMIUM_BIN}" "${HOME_DIR}/.local/bin/headless_shell" || true + chown "${NB_UID}":"${NB_GID}" "${HOME_DIR}/.local/bin/chromium" "${HOME_DIR}/.local/bin/chromium-browser" "${HOME_DIR}/.local/bin/chrome-headless-shell" "${HOME_DIR}/.local/bin/headless_shell" || true + + # Bake the CI-safe flags into the binary itself (idempotent). + wrap_chromium_shim "${CHROMIUM_BIN}" else - echo "quarto-chromium: WARNING: Chromium binary not found after installation" >&2 + echo "quarto-chromium: ERROR: Chromium/Chrome Headless Shell binary not found after installation" >&2 for search_dir in \ "${HOME_DIR}/.local/share/quarto/chromium" \ "${HOME_DIR}/.local/share/quarto" \ @@ -162,12 +263,13 @@ else /var/cache do [ -d "${search_dir}" ] || continue - find "${search_dir}" -maxdepth 6 \( -type f -o -type l \) \( -name chrome -o -name chromium -o -name chromium-browser -o -name headless_shell \) 2>/dev/null | head -n 20 || true + find "${search_dir}" -maxdepth 6 \( -type f -o -type l \) \( -name chrome -o -name chrome-headless-shell -o -name chromium -o -name chromium-browser -o -name headless_shell \) 2>/dev/null | head -n 20 || true done if [ -f "${HOME_DIR}/.quarto-chromium-install.log" ]; then echo "quarto-chromium: Quarto install log tail:" >&2 tail -n 40 "${HOME_DIR}/.quarto-chromium-install.log" >&2 || true fi + exit 1 fi echo "quarto-chromium: installation complete" diff --git a/features/quarto-cli/feature.json b/features/quarto-cli/feature.json index ba6de83..17f7812 100644 --- a/features/quarto-cli/feature.json +++ b/features/quarto-cli/feature.json @@ -10,7 +10,7 @@ "options": { "QUARTO_VERSION": { "type": "string", - "default": "1.8.24", + "default": "1.10.18", "description": "Specific Quarto version to install" } }, diff --git a/features/quarto-cli/install.sh b/features/quarto-cli/install.sh index 1f9f1e1..d040d1e 100755 --- a/features/quarto-cli/install.sh +++ b/features/quarto-cli/install.sh @@ -149,11 +149,32 @@ fi echo "quarto-cli: found runtime at ${INSTDIR} -> ${QUARTO_EXE}" +# The tarball ships bundled tools (typst, pandoc, deno, ...) under +# /bin/tools// (arch = x86_64 | aarch64). Quarto +# itself locates them relative to its own binary (QUARTO_BIN_PATH), so +# renders work without PATH; expose the dir on PATH so that direct +# `typst` invocations (user notebook cells, scripts) resolve too. +QUARTO_TOOLS_DIR="" +for cand in "$(dirname "$QUARTO_EXE")"/tools/*/typst; do + if [ -x "$cand" ]; then + QUARTO_TOOLS_DIR="$(dirname "$cand")" + break + fi +done +if [ -n "${QUARTO_TOOLS_DIR}" ]; then + echo "quarto-cli: bundled tools dir at ${QUARTO_TOOLS_DIR}" +fi + +QUARTO_PATH_ADD="${CONDA_DIR}/bin:$(dirname "$QUARTO_EXE")" +if [ -n "${QUARTO_TOOLS_DIR}" ]; then + QUARTO_PATH_ADD="${QUARTO_PATH_ADD}:${QUARTO_TOOLS_DIR}" +fi + # Create wrapper in /usr/local/bin mkdir -p /usr/local/bin cat > /usr/local/bin/quarto < /etc/profile.d/quarto.sh <" } } } +# { "quarto": { "1.10.18": { "amd64": "" } } } REPO_ROOT=$(cd "$(dirname "$0")/.." && pwd) # Single source of truth: repo-root checksums.json and versions.json diff --git a/versions.json b/versions.json index 8b7470a..48e5e31 100644 --- a/versions.json +++ b/versions.json @@ -6,7 +6,7 @@ "buildx": "v0.30.1", "compose": "v5.0.1", "docker": "27-cli", - "quarto": "v1.8.26", + "quarto": "v1.10.18", "node": "22.12.0", "docker-ce": "27.5.1", "docker-compose": "v5.0.1", diff --git a/versions/versions.yaml b/versions/versions.yaml index feb6cf4..e415350 100644 --- a/versions/versions.yaml +++ b/versions/versions.yaml @@ -21,7 +21,7 @@ tools: - type: github repo: "docker/cli" quarto: - version: "v1.8.26" + version: "v1.10.18" sources: - type: github repo: "quarto-dev/quarto-cli"