# MLSysBook Quarto Build Container # Based on Ubuntu 22.04 with all dependencies pre-installed # This container eliminates the 30-45 minute setup time for Linux builds # RUN uses /bin/sh: inside double quotes, $$ is the shell PID, so $${VAR} and backticks, not $(). FROM ubuntu:22.04 # === PATH CONFIGURATION === # Paths are hardcoded since they're stable and ARG scope causes issues in multi-layer builds # Set environment variables ENV DEBIAN_FRONTEND=noninteractive ENV R_LIBS_USER=/usr/local/lib/R/library ENV QUARTO_LOG_LEVEL=INFO ENV PYTHONIOENCODING=utf-8 ENV LANG=en_US.UTF-8 ENV LC_ALL=en_US.UTF-8 # Prepend TeX; keep a full system PATH (BuildKit can expand $PATH in RUN to nothing). ENV PATH="/usr/local/texlive/bin/x86_64-linux:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" # === PHASE 0: COPY DEPENDENCY FILES EARLY (for better cache efficiency) === # Copy dependency files early for better cache efficiency COPY book/tools/dependencies/requirements.txt /tmp/requirements.txt COPY book/tools/dependencies/install_packages.R /tmp/install_packages.R COPY book/tools/dependencies/required_r_packages.R /tmp/required_r_packages.R COPY book/tools/dependencies/tl_packages /tmp/tl_packages COPY book/docker/linux/verify_r_packages.R /tmp/verify_r_packages.R COPY book/docker/linux/install-texlive-base.sh /opt/install-texlive-base.sh COPY book/docker/linux/install-tl-collections.sh /opt/install-tl-collections.sh # Note: Python packages are installed later in Phase 10 # Early cleanup RUN rm -rf /var/lib/apt/lists/* && \ apt-get clean # === PHASE 1: LOCALE CONFIGURATION === RUN echo "๐Ÿš€ === STARTING LOCALE CONFIGURATION ===" && \ echo "๐Ÿ” Checking system readiness..." && \ if [ -f /etc/os-release ]; then \ echo "โœ… OS release file found"; \ cat /etc/os-release | grep PRETTY_NAME; \ else \ echo "โŒ OS release file not found"; \ exit 1; \ fi && \ echo "๐Ÿ“ฆ Installing locales package..." && \ apt-get update && apt-get install -y locales && \ echo "๐Ÿ“ฆ Locales package installed" && \ echo "๐Ÿ”ง Generating en_US.UTF-8 locale..." && \ locale-gen en_US.UTF-8 && \ echo "๐Ÿ“„ Locale generated" && \ echo "๐Ÿ”ง Updating system locale..." && \ update-locale LANG=en_US.UTF-8 && \ echo "๐Ÿ”ง System locale updated" && \ echo "๐Ÿงน Cleaning package cache..." && \ rm -rf /var/lib/apt/lists/* && \ apt-get clean && \ echo "โœ… Locale configuration complete" # === PHASE 2: SYSTEM DEPENDENCIES === RUN echo "๐Ÿš€ === STARTING SYSTEM DEPENDENCIES INSTALLATION ===" && \ echo "โฐ Estimated time: 2-3 minutes" && \ echo "๐Ÿ”„ Updating package lists..." && \ apt-get update && \ \ echo "๐Ÿ“ฆ Installing core system packages (fonts, Cairo, RSVG, XML, image libs, build tools)..." && \ apt-get install -y \ fonts-dejavu \ fonts-freefont-ttf \ gdk-pixbuf2.0-bin \ libcairo2 \ libfontconfig1 \ libfontconfig1-dev \ libfreetype6 \ libfreetype6-dev \ libpango-1.0-0 \ libpangocairo-1.0-0 \ libpangoft2-1.0-0 \ libxml2-dev \ libcurl4-openssl-dev \ libjpeg-dev \ libtiff5-dev \ libpng-dev \ libharfbuzz-dev \ libfribidi-dev \ librsvg2-dev \ librsvg2-bin \ libgdal-dev \ libudunits2-dev \ wget \ curl \ git \ cmake \ pkg-config && \ echo "๐Ÿ“ฆ All system packages installed successfully" && \ \ echo "๐Ÿ” Verifying critical packages..." && \ if command -v wget >/dev/null 2>&1; then \ echo "๐Ÿ“ฆ wget available"; \ else \ echo "โŒ wget not found"; \ exit 1; \ fi && \ if command -v curl >/dev/null 2>&1; then \ echo "๐Ÿ“ฆ curl available"; \ else \ echo "โŒ curl not found"; \ exit 1; \ fi && \ if command -v git >/dev/null 2>&1; then \ echo "๐Ÿ“ฆ git available"; \ else \ echo "โŒ git not found"; \ exit 1; \ fi && \ if command -v rsvg-convert >/dev/null 2>&1; then \ echo "๐Ÿ“ฆ rsvg-convert available"; \ else \ echo "โŒ rsvg-convert not found"; \ exit 1; \ fi && \ \ echo "๐Ÿงน Cleaning package cache..." && \ rm -rf /var/lib/apt/lists/* && \ apt-get clean && \ echo "โœ… === SYSTEM DEPENDENCIES COMPLETE ===" # === PHASE 3: INKSCAPE INSTALLATION === RUN echo "๐Ÿš€ === STARTING INKSCAPE INSTALLATION ===" && \ echo "โฐ Estimated time: 1-2 minutes" && \ echo "๐Ÿ”„ Adding Inkscape PPA repository..." && \ apt-get update && \ echo "๐Ÿ“ฆ Installing software-properties-common..." && \ apt-get install -y software-properties-common && \ echo "๐Ÿ“ฆ software-properties-common installed" && \ echo "๐Ÿ”ง Adding Inkscape PPA..." && \ add-apt-repository ppa:inkscape.dev/stable -y && \ echo "๐Ÿ“ฆ Inkscape PPA added" && \ \ echo "๐Ÿ“ฆ Installing Inkscape..." && \ apt-get update && \ apt-get install -y inkscape && \ echo "๐Ÿ“ฆ Inkscape installed" && \ \ echo "๐Ÿงน Cleaning package cache..." && \ rm -rf /var/lib/apt/lists/* && \ apt-get clean && \ echo "โœ… === INKSCAPE INSTALLATION COMPLETE ===" # Install font dependencies (note: fonts-freefont-ttf already installed above) RUN echo "๐Ÿš€ === STARTING FONT INSTALLATION ===" && \ echo "๐Ÿ“ฆ Installing additional fonts..." && \ apt-get update && apt-get install -y \ fonts-liberation \ fontconfig && \ echo "๐Ÿ“ฆ Fonts installed" && \ echo "๐Ÿ”ง Updating font cache..." && \ fc-cache -fv && \ echo "๐Ÿ“„ Font cache updated" && \ echo "๐Ÿงน Cleaning package cache..." && \ rm -rf /var/lib/apt/lists/* && \ apt-get clean && \ echo "โœ… Font installation complete" # Test Inkscape SVG to PDF conversion (same as your workflow) RUN echo "๐Ÿš€ === STARTING INKSCAPE TEST ===" && \ echo "๐Ÿ“‹ Creating test SVG file..." && \ echo '' > test.svg && \ echo "๐Ÿ“„ Test SVG created" && \ echo "๐Ÿ”„ Converting SVG to PDF..." && \ inkscape --export-type=pdf --export-filename=test.pdf test.svg && \ echo "๐Ÿ“ฆ Conversion completed" && \ if [ -f test.pdf ]; then \ echo "โœ… Inkscape SVG to PDF conversion successful!"; \ echo "๐Ÿ“Š PDF file details:"; \ ls -lh test.pdf; \ else \ echo "โŒ Inkscape SVG to PDF conversion failed."; \ exit 1; \ fi && \ echo "๐Ÿงน Cleaning up test files..." && \ rm -f test.svg test.pdf && \ echo "โœ… Inkscape test complete" # === PHASE 4: GHOSTSCRIPT INSTALLATION === RUN echo "๐Ÿš€ === STARTING GHOSTSCRIPT INSTALLATION ===" && \ echo "โฐ Estimated time: 30 seconds" && \ echo "๐Ÿ“ฆ Installing Ghostscript..." && \ apt-get update && apt-get install -y ghostscript && \ echo "๐Ÿ“ฆ Ghostscript installed" && \ echo "๐Ÿ“Š Ghostscript version:" && \ gs --version && \ \ echo "๐Ÿงน Cleaning package cache..." && \ rm -rf /var/lib/apt/lists/* && \ apt-get clean && \ echo "โœ… === GHOSTSCRIPT INSTALLATION COMPLETE ===" # === PHASE 5: TEX LIVE INSTALLATION === # Step 5.1: Install TeX Live prerequisites RUN echo "๐Ÿš€ === STARTING TEX LIVE INSTALLATION ===" && \ echo "โฐ Estimated time: 8-12 minutes (largest phase)" && \ echo "๐Ÿ“Š Free disk space before: `df -h / | tail -1 | awk '{print $$4}'`" && \ \ echo "๐Ÿ“ฆ Installing TeX Live prerequisites..." && \ echo "๐Ÿ“‹ Prerequisites:" && \ echo " - perl" && \ echo " - wget" && \ echo " - xzdec" && \ apt-get update && apt-get install -y \ perl \ wget \ xzdec && \ echo "๐Ÿ“ฆ Prerequisites installed" && \ \ echo "๐Ÿ” Verifying prerequisites..." && \ if command -v perl >/dev/null 2>&1; then \ echo "๐Ÿ“ฆ perl available"; \ else \ echo "โŒ perl not found"; \ exit 1; \ fi && \ if command -v wget >/dev/null 2>&1; then \ echo "๐Ÿ“ฆ wget available"; \ else \ echo "โŒ wget not found"; \ exit 1; \ fi && \ if command -v xzdec >/dev/null 2>&1; then \ echo "๐Ÿ“ฆ xzdec available"; \ else \ echo "โŒ xzdec not found"; \ exit 1; \ fi && \ \ echo "๐Ÿงน Cleaning package cache..." && \ rm -rf /var/lib/apt/lists/* && \ apt-get clean && \ echo "โœ… TeX Live prerequisites installed" # Step 5.2+5.3: install-tl download + base install in ONE layer. # GHA buildx cache can otherwise reuse an old "download only" layer (TL year N) while # a later layer passes -repository for year N+1 โ€” install-tl fails in seconds. # Bump TEXLIVE_INSTALLER_REVISION when the annual CTAN rollover requires a fresh # install-tl-unx.tar.gz for this image. # install-tl -repository must point at a base URL whose tlpdb is at .../tlpkg/texlive.tlpdb. # As of 2026-06, mirror.ctan.org serves install-tl-2026* and the current tlnet repository. # TeX base install is a separate POSIX script (avoids long RUN + buildx $/() handling on /bin/sh). ARG TEXLIVE_INSTALLER_REVISION=7 RUN chmod 0755 /opt/install-texlive-base.sh && /opt/install-texlive-base.sh ${TEXLIVE_INSTALLER_REVISION} # Step 5.4: Configure TeX Live PATH RUN echo "๐Ÿ”ง Setting up TeX Live PATH..." && \ echo 'export PATH="/usr/local/texlive/bin/x86_64-linux:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$$PATH"' >> /etc/bash.bashrc && \ export PATH="/usr/local/texlive/bin/x86_64-linux:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" && \ echo "๐Ÿ”ง TeX Live PATH configured" && \ \ echo "๐Ÿ“ Checking if tlmgr is available..." && \ if command -v tlmgr >/dev/null 2>&1; then \ echo "๐Ÿ“ฆ tlmgr available"; \ tlmgr --version; \ else \ echo "โŒ tlmgr not found or not working"; \ exit 1; \ fi && \ echo "โœ… TeX Live PATH and tlmgr verified" # Step 5.5: Install TeX Live collection packages (POSIX script; long RUN + $$ breaks dash on CI) RUN chmod 0755 /opt/install-tl-collections.sh && /opt/install-tl-collections.sh # Step 5.6: TeX Live cleanup and completion RUN echo "๐Ÿงน Cleaning up TeX Live installer..." && \ rm -rf /tmp/install-tl-* /tmp/texlive.profile /tmp/install-tl-unx.tar.gz && \ echo "โœ… === TEX LIVE INSTALLATION COMPLETE ===" && \ echo "๐Ÿ“Š Free disk space after: `df -h / | tail -1 | awk '{print $$4}'`" && \ echo "๐Ÿ“Š TeX Live disk usage: `du -sh /usr/local/texlive 2>/dev/null || echo 'N/A'`" # Verify TeX Live installation (with error handling) RUN echo "๐Ÿ”„ Verifying TeX Live installation..." && \ export PATH="/usr/local/texlive/bin/x86_64-linux:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" && \ echo "๐Ÿ“ PATH: $${PATH}" && \ echo "๐Ÿ“ Checking TeX Live directory:" && \ ls -la /usr/local/texlive/ || echo "โŒ TeX Live directory not found" && \ ls -la /usr/local/texlive/bin/ || echo "โŒ TeX Live bin directory not found" && \ if [ -f /usr/local/texlive/bin/x86_64-linux/lualatex ]; then \ echo "โœ… lualatex found"; \ /usr/local/texlive/bin/x86_64-linux/lualatex --version; \ else \ echo "โŒ lualatex not found, checking for alternative locations"; \ find /usr/local/texlive -name "lualatex" -type f 2>/dev/null || echo "No lualatex found anywhere"; \ fi && \ echo "โœ… TeX Live verification complete (allowing partial failures)" # === PHASE 6: R INSTALLATION (R 4.5 from CRAN) === RUN echo "๐Ÿš€ === STARTING R INSTALLATION ===" && \ echo "โฐ Estimated time: 1-2 minutes" && \ echo "๐Ÿ“ฆ Adding CRAN repository for R 4.5..." && \ apt-get update && apt-get install -y --no-install-recommends software-properties-common dirmngr && \ wget -qO- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc | tee -a /etc/apt/trusted.gpg.d/cran_ubuntu_key.asc > /dev/null && \ add-apt-repository "deb https://cloud.r-project.org/bin/linux/ubuntu jammy-cran40/" && \ apt-get update && \ \ echo "๐Ÿ“ฆ Installing R and development packages..." && \ echo "๐Ÿ“‹ R packages (system) + build deps for CRAN (e.g. R package fs โ†’ libuv1-dev):" && \ echo " - r-base" && \ echo " - r-base-dev" && \ echo " - r-recommended" && \ echo " - libuv1-dev" && \ apt-get install -y --no-install-recommends \ r-base \ r-base-dev \ r-recommended \ libuv1-dev && \ if [ $$? -ne 0 ]; then \ echo "โŒ Failed to install R packages"; \ exit 1; \ fi && \ echo "๐Ÿ“ฆ R packages installed" && \ \ echo "๐Ÿงน Cleaning package cache..." && \ rm -rf /var/lib/apt/lists/* && \ apt-get clean && \ \ echo "๐Ÿ“Š R version: `R --version 2>&1 | head -1`" && \ if [ $$? -ne 0 ]; then \ echo "โŒ Failed to verify R installation"; \ exit 1; \ fi && \ echo "โœ… === R INSTALLATION COMPLETE ===" # R may omit R_LIBS_USER from .libPaths() if the directory is missing, so install and verify # can disagree on where packages were written. Create it before any Rscript package step. RUN mkdir -p $R_LIBS_USER # Fail fast: CRAN source build of rmarkdown deps (e.g. fs) needs libuv (libuv1-dev) + cmake. # See e12d7dc4a8; log 24964853614: fs "Package libuv was not found in the pkg-config search path". RUN echo "๐Ÿ”Ž Pre-flight: R build toolchain (cmake, pkg-config, libuv)โ€ฆ" && \ test -d "$R_LIBS_USER" || { echo "โŒ R_LIBS_USER dir missing: $R_LIBS_USER"; exit 1; } && \ command -v cmake >/dev/null 2>&1 || { echo "โŒ cmake not in PATH (required for R package fs, needed by rmarkdown/tidyverse)"; exit 1; } && \ command -v pkg-config >/dev/null 2>&1 || { echo "โŒ pkg-config not in PATH"; exit 1; } && \ pkg-config --print-errors --exists libuv || { echo "โŒ libuv not found (install libuv1-dev for R package fs)"; exit 1; } && \ echo "โœ… Pre-flight OK" # === PHASE 7: R PACKAGE INSTALLATION === RUN echo "๐Ÿš€ === STARTING R PACKAGE INSTALLATION ===" && \ echo "โฐ Cold build: this step compiles many CRAN packages; expect 20โ€“60+ min (cache shortens reruns)" && \ echo "Running R setup script with the following files:" && \ echo " - /tmp/install_packages.R (from book/tools/dependencies/install_packages.R)" && \ echo " - /tmp/required_r_packages.R (from book/tools/dependencies/required_r_packages.R)" && \ echo " - /tmp/verify_r_packages.R (from book/docker/linux/verify_r_packages.R)" && \ \ echo "๐Ÿ“ฆ Installing R packages..." && \ Rscript /tmp/install_packages.R && \ if [ $$? -ne 0 ]; then \ echo "โŒ Failed to install R packages"; \ exit 1; \ fi && \ echo "โœ… R packages installed successfully" && \ \ echo "๐Ÿ” Verifying R packages..." && \ Rscript /tmp/verify_r_packages.R && \ if [ $$? -ne 0 ]; then \ echo "โŒ Failed to verify R packages"; \ exit 1; \ fi && \ echo "โœ… R packages verified successfully" && \ \ echo "๐Ÿงน Cleaning up R installation files..." && \ rm -f /tmp/install_packages.R /tmp/required_r_packages.R /tmp/verify_r_packages.R && \ echo "โœ… === R PACKAGE INSTALLATION COMPLETE ===" # === PHASE 8: PYTHON INSTALLATION === RUN echo "๐Ÿš€ === STARTING PYTHON INSTALLATION ===" && \ echo "โฐ Estimated time: 1 minute" && \ echo "๐Ÿ“ฆ Installing Python 3 and development packages..." && \ echo "๐Ÿ“‹ Python packages:" && \ echo " - python3" && \ echo " - python3-pip" && \ echo " - python3-dev" && \ apt-get update && apt-get install -y \ python3 \ python3-pip \ python3-dev && \ if [ $$? -ne 0 ]; then \ echo "โŒ Failed to install Python packages"; \ exit 1; \ fi && \ echo "๐Ÿ“ฆ Python packages installed" && \ \ echo "๐Ÿงน Cleaning package cache..." && \ rm -rf /var/lib/apt/lists/* && \ apt-get clean && \ \ echo "๐Ÿ“Š Python version: `python3 --version 2>&1`" && \ if [ $$? -ne 0 ]; then \ echo "โŒ Failed to verify Python installation"; \ exit 1; \ fi && \ echo "๐Ÿ“Š Pip version: `pip3 --version 2>&1`" && \ if [ $$? -ne 0 ]; then \ echo "โŒ Failed to verify pip installation"; \ exit 1; \ fi && \ echo "โœ… === PYTHON INSTALLATION COMPLETE ===" # === PHASE 9: QUARTO INSTALLATION === RUN echo "๐Ÿš€ === STARTING QUARTO INSTALLATION ===" && \ echo "โฐ Estimated time: 1 minute" && \ echo "๐Ÿ“ฆ Downloading Quarto 1.9.27..." && \ wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.9.27/quarto-1.9.27-linux-amd64.deb && \ if [ $$? -ne 0 ]; then \ echo "โŒ Failed to download Quarto"; \ exit 1; \ fi && \ echo "๐Ÿ“ฅ Download completed" && \ echo "๐Ÿ“Š Downloaded file size:" && \ ls -lh quarto-1.9.27-linux-amd64.deb && \ \ echo "๐Ÿ“ฆ Installing Quarto..." && \ dpkg -i quarto-1.9.27-linux-amd64.deb && \ if [ $$? -ne 0 ]; then \ echo "โŒ Failed to install Quarto"; \ exit 1; \ fi && \ echo "๐Ÿ“ฆ Quarto installed" && \ \ echo "๐Ÿงน Cleaning up installer..." && \ rm quarto-1.9.27-linux-amd64.deb && \ echo "๐Ÿงน Installer cleaned up" && \ echo "โœ… === QUARTO INSTALLATION COMPLETE ===" # === PHASE 10: PYTHON PACKAGES === RUN echo "๐Ÿš€ === STARTING PYTHON PACKAGE INSTALLATION ===" && \ echo "โฐ Estimated time: 1-2 minutes" && \ echo "๐Ÿ”„ Upgrading pip..." && \ pip3 install --upgrade pip && \ \ echo "๐Ÿ“Š Analyzing requirements.txt..." && \ package_count=`grep -v '^#' /tmp/requirements.txt | grep -v '^$' | wc -l` && \ echo "๐Ÿ“ฆ Found $${package_count} Python packages to install" && \ \ echo "๐Ÿ”„ Installing Python packages with space optimization..." && \ pip3 install --no-cache-dir -r /tmp/requirements.txt && \ if [ $$? -ne 0 ]; then \ echo "โŒ Failed to install Python packages"; \ exit 1; \ fi && \ \ echo "๐Ÿงน Cleaning Python installation caches..." && \ pip3 cache purge && \ find /usr -name "*.pyc" -delete && \ find /usr -name "__pycache__" -type d -exec rm -rf {} + || true && \ \ echo "๐Ÿ“Š Installed Python packages:" && \ pip3 list | head -10 && \ echo "๐Ÿ“Š Total packages: `pip3 list | wc -l`" && \ echo "โœ… === PYTHON PACKAGES COMPLETE ===" # R CRAN packages: Phase 7 only (install_packages.R + verify_r_packages.R; /tmp copies removed after verify). # === PHASE 11: COMPREHENSIVE CLEANUP === RUN echo "๐Ÿš€ === STARTING COMPREHENSIVE CLEANUP ===" && \ echo "๐Ÿ“Š Disk space before cleanup: `df -h / | tail -1 | awk '{print $$4}'`" && \ echo "๐Ÿงน Removing temporary files..." && \ rm -rf /tmp/* && \ rm -rf /var/tmp/* && \ \ echo "๐Ÿงน Cleaning package caches..." && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ rm -rf /var/cache/apt/* && \ \ echo "๐Ÿงน Cleaning Python caches..." && \ find /usr -name "*.pyc" -delete && \ find /usr -name "__pycache__" -type d -exec rm -rf {} + || true && \ pip3 cache purge || true && \ \ echo "๐Ÿงน Cleaning R temporary files..." && \ rm -rf /tmp/Rtmp* || true && \ rm -rf /var/lib/R/site-library/*/help || true && \ \ echo "๐Ÿงน Cleaning TeX Live caches and docs..." && \ rm -rf /usr/local/texlive/*/texmf-var/luatex-cache/* || true && \ rm -rf /usr/local/texlive/*/texmf-var/web2c/* || true && \ \ echo "๐Ÿงน Removing unnecessary system files..." && \ rm -rf /usr/share/doc/* && \ rm -rf /usr/share/man/* && \ rm -rf /usr/share/info/* && \ rm -rf /var/log/* && \ echo "โœ… === COMPREHENSIVE CLEANUP COMPLETE ===" && \ echo "๐Ÿ“Š Final disk space: `df -h / | tail -1 | awk '{print $$4}'`" # Set working directory WORKDIR /workspace # === PHASE 12: FINAL VERIFICATION === RUN echo "๐Ÿš€ === STARTING FINAL VERIFICATION ===" && \ echo "๐Ÿ“Š Checking Quarto..." && \ if command -v quarto >/dev/null 2>&1; then \ quarto --version && \ echo "โœ… Quarto verified"; \ else \ echo "โŒ Quarto not found in PATH"; \ echo "๐Ÿ“ Checking for quarto in common locations:"; \ find /usr -name "quarto" -type f 2>/dev/null || echo "No quarto found"; \ exit 1; \ fi && \ echo "๐Ÿ“Š Checking Python..." && \ if command -v python3 >/dev/null 2>&1; then \ python3 --version && \ echo "โœ… Python verified"; \ else \ echo "โŒ Python3 not found"; \ exit 1; \ fi && \ echo "๐Ÿ“Š Checking R..." && \ if command -v R >/dev/null 2>&1; then \ R --version && \ echo "โœ… R verified"; \ else \ echo "โŒ R not found"; \ exit 1; \ fi && \ echo "๐Ÿ“Š Checking LaTeX..." && \ if command -v lualatex >/dev/null 2>&1; then \ lualatex --version && \ echo "โœ… LaTeX verified"; \ else \ echo "โŒ lualatex not found"; \ echo "๐Ÿ“ Checking for lualatex in TeX Live:"; \ find /usr/local/texlive -name "lualatex" -type f 2>/dev/null || echo "No lualatex found"; \ exit 1; \ fi && \ echo "โœ… Final verification complete" # Health check RUN echo "โœ… Container build completed successfully" && \ echo "๐Ÿ“Š Quarto version: `quarto --version 2>&1`" && \ echo "๐Ÿ“Š Python version: `python3 --version 2>&1`" && \ echo "๐Ÿ“Š R version: `R --version 2>&1 | head -1`" && \ echo "๐Ÿ“Š TeX Live: `lualatex --version 2>&1 | head -1`"