diff --git a/.github/actions/meta/action.yaml b/.github/actions/meta/action.yaml new file mode 100644 index 0000000..1644cd9 --- /dev/null +++ b/.github/actions/meta/action.yaml @@ -0,0 +1,52 @@ +name: 'Pandoc Docker Meta' +author: 'Albert Krewinkel' +description: 'Compute Docker image metadata values' +inputs: + images: + type: string + required: true + pandoc_version: + type: string + default: main + build_stack: + type: string + default: alpine +outputs: + base_image: + description: "name of the base image" + value: ${{ steps.config.outputs.base_image }} + base_image_version: + description: "version of the base image" + value: ${{ steps.config.outputs.base_image_version }} + labels: + description: "Docker image labels" + value: ${{ steps.meta.outputs.labels }} + tags: + description: "Docker image tags" + value: ${{ steps.config.outputs.tags }} + texlive_version: + description: "TeXLive version to be use for LaTeX images" + value: ${{ steps.config.outputs.texlive_version }} + +runs: + using: composite + steps: + - name: Config + id: config + shell: bash + run: | + ./scripts/config.sh \ + ${{ inputs.pandoc_version }} \ + ${{ inputs.build_stack }} \ + "${{ inputs.images }}" + ./scripts/config.sh \ + "${{ inputs.pandoc_version }}" \ + "${{ inputs.build_stack }}" \ + "${{ inputs.images }}" \ + >> $GITHUB_OUTPUT + + - name: Docker Meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ inputs.images }} diff --git a/.github/workflows/addon.yaml b/.github/workflows/addon.yaml new file mode 100644 index 0000000..c02e37e --- /dev/null +++ b/.github/workflows/addon.yaml @@ -0,0 +1,136 @@ +name: Addon images + +on: + workflow_call: + inputs: + addon: + type: string + default: typst + pandoc_version: + type: string + default: edge + build_stack: + type: string + default: alpine + secrets: + DOCKER_HUB_USERNAME: + required: true + DOCKER_HUB_TOKEN: + required: true + workflow_dispatch: + inputs: + addon: + type: string + default: typst + pandoc_version: + type: string + default: edge + build_stack: + type: string + default: alpine + +jobs: + conf: + name: Configure + runs-on: ubuntu-latest + outputs: + addon: ${{ steps.params.outputs.addon }} + build_stack: ${{ steps.params.outputs.build_stack }} + pandoc_version: ${{ steps.params.outputs.pandoc_version }} + steps: + - name: Parameters + id: params + run: | + pandoc_version=${{ inputs.pandoc_version }} + build_stack=${{ inputs.build_stack }} + addon=${{ inputs.addon }} + if [ -z "${pandoc_version}" ]; then + pandoc_version=edge + fi + if [ -z "${build_stack}" ]; then + build_stack=alpine + fi + if [ -z "${addon}" ]; then + addon=typst + fi + pandoc_version="['${pandoc_version}']" + build_stack="['${build_stack}']" + addon="['${addon}']" + printf 'addon=%s\n' "$addon" + printf 'build_stack=%s\n' "$build_stack" + printf 'pandoc_version=%s\n' "$pandoc_version" + + printf 'addon=%s\n' "$addon" >> $GITHUB_OUTPUT + printf 'build_stack=%s\n' "$build_stack" >> $GITHUB_OUTPUT + printf 'pandoc_version=%s\n' "$pandoc_version" >> $GITHUB_OUTPUT + + build: + name: Addon + runs-on: ubuntu-latest + needs: conf + + strategy: + fail-fast: false + matrix: + build_stack: ${{ fromJSON( needs.conf.outputs.build_stack ) }} + pandoc_version: ${{ fromJSON( needs.conf.outputs.pandoc_version ) }} + image_type: ${{ fromJSON( needs.conf.outputs.addon ) }} + + env: + PUSH_IMAGE: ${{ github.repository == 'pandoc/dockerfiles' && + (github.ref == 'refs/heads/main' || + github.ref_type == 'tag' || + github.event_name != 'pull_request') }} + PANDOC_VERSION: ${{ matrix.pandoc_version }} + FULL_TAG: >- + ${{ matrix.pandoc_version }}-${{ matrix.build_stack }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + if: ${{ env.PUSH_IMAGE }} + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + if: ${{ env.PUSH_IMAGE }} + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + # with: + # version: "lab:latest" + # driver: cloud + # endpoint: "pandoc/multiarch" + + - name: Meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + pandoc/${{ matrix.image_type }} + ghcr.io/pandoc/${{ matrix.image_type }} + + - name: pandoc/${{ matrix.image_type }}:${{ env.FULL_TAG }} + uses: docker/build-push-action@v6 + with: + build-args: | + base_image_tag=${{ env.FULL_TAG }} + context: '.' + file: ${{ matrix.build_stack }}/${{ matrix.image_type }}/Dockerfile + labels: ${{ steps.meta.outputs.labels }} + #platforms: linux/amd64,linux/arm64 + platforms: linux/arm64 + push: ${{ env.PUSH_IMAGE }} + tags: | + pandoc/${{ matrix.image_type }}:${{ env.FULL_TAG }} + ghcr.io/pandoc/${{ matrix.image_type }}:${{ env.FULL_TAG }} + diff --git a/.github/workflows/edge.yaml b/.github/workflows/edge.yaml new file mode 100644 index 0000000..9de4e7d --- /dev/null +++ b/.github/workflows/edge.yaml @@ -0,0 +1,102 @@ +name: edge images + +on: + push: + paths: + - edge/alpine/Dockerfile + - .github/workflows/edge.yaml + schedule: + # Rebuild on the 17th of each month + - cron: '19 1 17 * *' + + workflow_dispatch: + +jobs: + build: + name: Alpine + runs-on: ubuntu-latest + + env: + PUSH_IMAGE: ${{ github.repository == 'pandoc/dockerfiles' && + (github.ref == 'refs/heads/main' || + github.ref_type == 'tag' || + github.event_name != 'pull_request') }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Meta for minimal + id: meta-minimal + uses: ./.github/actions/meta + with: + images: | + pandoc/minimal + ghcr.io/pandoc/minimal + pandoc_version: main + build_stack: alpine + + - name: Login to Docker Hub + uses: docker/login-action@v3 + if: ${{ env.PUSH_IMAGE }} + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + if: ${{ env.PUSH_IMAGE }} + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + # with: + # version: "lab:latest" + # driver: cloud + # endpoint: "pandoc/multiarch" + + - name: Config minimal + id: config-minimal + run: | + ./scripts/config.sh main alpine pandoc/minimal >> $GITHUB_OUTPUT + + - name: minimal + uses: docker/build-push-action@v6 + with: + context: '.' + file: 'edge/alpine/Dockerfile' + labels: ${{ steps.meta-minimal.outputs.labels }} + # platforms: linux/amd64,linux/arm64 + push: ${{ steps.config-minimal.outputs.tags }} + target: minimal + tags: steps.config-minimal.outputs.tags + + - name: Meta for core + id: meta-core + uses: ./.github/actions/meta + with: + images: | + pandoc/core + ghcr.io/pandoc/core + pandoc_version: main + build_stack: alpine + + - name: Config core + id: config-core + run: | + ./scripts/config.sh main alpine pandoc/core \ + >> $GITHUB_OUTPUT + + - name: core + uses: docker/build-push-action@v6 + with: + file: 'edge/alpine/Dockerfile' + labels: ${{ steps.meta-core.outputs.labels }} + # platforms: linux/amd64,linux/arm64 + push: ${{ env.PUSH_IMAGE }} + target: core + tags: ${{ steps.config-core.outputs.tags }} + diff --git a/.github/workflows/latex.yaml b/.github/workflows/latex.yaml new file mode 100644 index 0000000..39e495f --- /dev/null +++ b/.github/workflows/latex.yaml @@ -0,0 +1,28 @@ +name: LaTeX images + +on: + push: + paths: + - '**/latex/Dockerfile' + - .github/workflows/latex.yaml + - .github/workflows/addon.yaml + workflow_dispatch: + inputs: + pandoc_version: + type: string + default: edge + build_stack: + type: string + default: alpine + +jobs: + build: + uses: ./.github/workflows/addon.yaml + with: + addon: latex + pandoc_version: ${{ inputs.pandoc_version }} + build_stack: ${{ inputs.build_stack }} + secrets: + DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }} + DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }} + diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 3b75361..fbc3255 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -12,7 +12,7 @@ jobs: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: ShellCheck run: | sudo apt-get install -y shellcheck diff --git a/alpine/latex/Dockerfile b/alpine/latex/Dockerfile new file mode 100644 index 0000000..270938f --- /dev/null +++ b/alpine/latex/Dockerfile @@ -0,0 +1,66 @@ +ARG base_image_tag=edge-alpine +FROM pandoc/core:$base_image_tag + +# NOTE: to maintainers, please keep this listing alphabetical. +RUN apk --no-cache add \ + curl \ + fontconfig \ + freetype \ + gnupg \ + gzip \ + perl \ + tar \ + wget \ + xz \ + $([ $(uname -m) = 'aarch64' ] && printf "libc6-compat" || true) + +# Installer scripts and config +COPY common/latex/texlive.profile /root/texlive.profile +COPY common/latex/install-texlive.sh /root/install-texlive.sh +COPY common/latex/packages.txt /root/packages.txt + +# TeXLive binaries location +ARG texlive_bin="/opt/texlive/texdir/bin" + +# TeXLive version to install (leave empty to use the latest version). +ARG texlive_version= + +# TeXLive mirror URL (leave empty to use the default mirror). +ARG texlive_mirror_url= + +# Modify PATH environment variable, prepending TexLive bin directory +ENV PATH="${texlive_bin}/default:${PATH}" + +# Ideally, the image would always install "linuxmusl" binaries. However, +# those are not available for aarch64, so we install binaries that have +# been built against libc and hope that the compatibility layer works +# well enough. +RUN ARCH="$(uname -m)" && \ + case "$ARCH" in \ + ('aarch64') \ + TEXLIVE_ARCH="aarch64-linux"; \ + ;; \ + ('x86_64') \ + TEXLIVE_ARCH="x86_64-linuxmusl"; \ + ;; \ + (*) echo >&2 "error: unsupported architecture '$ARCH'"; \ + exit 1 \ + ;; \ + esac && \ + mkdir -p ${texlive_bin} && \ + ln -sf "${texlive_bin}/${TEXLIVE_ARCH}" "${texlive_bin}/default" && \ +# Request musl precompiled binary access + echo "binary_${TEXLIVE_ARCH} 1" >> /root/texlive.profile && \ + ( \ + [ -z "$texlive_version" ] || printf '-t\n%s\n"' "$texlive_version"; \ + [ -z "$texlive_mirror_url" ] || printf '-m\n%s\n' "$texlive_mirror_url" \ + ) | xargs /root/install-texlive.sh && \ + sed -e 's/ *#.*$//' -e '/^ *$/d' /root/packages.txt | \ + xargs tlmgr install && \ + rm -f /root/texlive.profile \ + /root/install-texlive.sh \ + /root/packages.txt && \ + TERM=dumb luaotfload-tool --update && \ + chmod -R o+w /opt/texlive/texdir/texmf-var + +WORKDIR /data diff --git a/alpine/typst/Dockerfile b/alpine/typst/Dockerfile new file mode 100644 index 0000000..754833e --- /dev/null +++ b/alpine/typst/Dockerfile @@ -0,0 +1,3 @@ +ARG base_image_tag=edge-alpine +FROM pandoc/core:$base_image_tag +RUN apk --no-cache add typst diff --git a/common/latex/texlive.profile b/common/latex/texlive.profile index dd5364e..8f9dc7b 100644 --- a/common/latex/texlive.profile +++ b/common/latex/texlive.profile @@ -2,8 +2,8 @@ # It will NOT be updated and reflects only the # installation profile at installation time. # -# NOTE: see also alpine/latex.Dockerfile which appends -# `binary_x86_64-linuxmusl 1` to this file, use for non-glibc distributions. +# NOTE: see also alpine/latex/Dockerfile which appends +# `binary_${ARCH}-linuxmusl 1` to this file, use for non-glibc distributions. selected_scheme scheme-basic TEXDIR /opt/texlive/texdir TEXMFLOCAL /opt/texlive/texmf-local diff --git a/edge/alpine/Dockerfile b/edge/alpine/Dockerfile new file mode 100644 index 0000000..9972282 --- /dev/null +++ b/edge/alpine/Dockerfile @@ -0,0 +1,90 @@ +# Builder ############################################################### +ARG base_image_version=3.20 +FROM alpine:$base_image_version AS alpine-builder +RUN apk --no-cache add \ + alpine-sdk \ + bash \ + ca-certificates \ + cabal \ + fakeroot \ + ghc \ + git \ + gmp-dev \ + libffi \ + libffi-dev \ + lua5.4-dev \ + pkgconfig \ + yaml \ + zlib-dev + +RUN < cabal.project.local; + +# Build pandoc and pandoc-crossref. The `allow-newer` is required for +# when pandoc-crossref has not been updated yet, but we want to build +# anyway. +cabal update +cabal build \ + --jobs \ + --disable-tests \ + --disable-bench \ + --enable-split-sections \ + --enable-executable-stripping \ + --upgrade-dependencies \ + --ghc-options='-O -optc-Os -optl=-pthread -fPIC -j4 +RTS -M4G -A256m -RTS' \ + --constraint='aeson-pretty +lib-only' \ + --constraint='lua +pkg-config' \ + --constraint='lpeg -rely-on-shared-lpeg-library' \ + --constraint='pandoc +embed_data_files' \ + --constraint='pandoc-cli +lua +nightly +server' \ + . pandoc-cli pandoc-crossref + +# Cabal's exec stripping doesn't seem to work reliably, let's do it here. +find dist-newstyle \ + -name 'pandoc*' -type f -perm -u+x \ + -exec strip '{}' ';' \ + -exec cp '{}' /usr/local/bin/ ';' + +rm -rf /root/.cabal +EOF + +# Minimal ############################################################### +FROM alpine:$base_image_version AS minimal +ARG pandoc_version=edge +LABEL maintainer='Albert Krewinkel ' +LABEL org.pandoc.maintainer='Albert Krewinkel ' +LABEL org.pandoc.author="John MacFarlane" +LABEL org.pandoc.version="$pandoc_version" + +WORKDIR /data +ENTRYPOINT ["/usr/local/bin/pandoc"] + +COPY --from=alpine-builder \ + /usr/local/bin/pandoc \ + /usr/local/bin/ + +# Add pandoc symlinks +RUN ln -s /usr/local/bin/pandoc /usr/local/bin/pandoc-lua && \ + ln -s /usr/local/bin/pandoc /usr/local/bin/pandoc-server && \ +# Install runtime dependencies + apk --no-cache add \ + gmp \ + libffi \ + lua5.4 + +# Core ################################################################## +FROM minimal AS core +COPY --from=alpine-builder \ + /usr/local/bin/pandoc-crossref \ + /usr/local/bin/ + +# Additional packages frequently used during conversions +# NOTE: `libsrvg`, pandoc uses `rsvg-convert` for working with svg images. +RUN apk --no-cache add rsvg-convert diff --git a/scripts/config.sh b/scripts/config.sh new file mode 100755 index 0000000..14520d4 --- /dev/null +++ b/scripts/config.sh @@ -0,0 +1,72 @@ +#!/bin/sh + +usage () +{ + printf 'Generate parameters for GitHub Actions\n\n' + printf 'Usage:\n\t%s \n' "$0" +} + +if [ "$#" -ne 3 ]; then + usage + exit 1 +fi + +version="${1:-main}" +build_stack="${2:-alpine}" +images="${3}" + +versions_file=versions.md +row="$(grep "^| $version " "$versions_file")" + +if [ -z "$row" ]; then + printf 'Unknown or unsupported version "%s"\n' "$version" + exit 1 +fi + +field () +{ + col=$(($1 + 1)) + printf '%s\n' "$row" | \ + awk -F '|' "{ gsub(/^ *| *\$/,\"\",\$$col); print \$${col} }" | \ + sed -e 's/ */,/g' +} + +case "${build_stack}" in + (alpine|static) + base_image='alpine' + base_image_version="$(field 3)" + ;; + (ubuntu) + base_image='ubuntu' + base_image_version="$(field 4)" + ;; + (*) + printf 'Unsupported base image: "%s"\n' "$base_image" + ;; +esac + +version_tags="$(field 2)" +tags= +for name in $(printf '%s\n' "$images" | tr ',' ' '); do + image_tags_base="$( \ + printf '%s\n' "$version_tags" | sed -e "s#\([^,]*\)#${name}:\1#g" \ + )" + image_tags="$(printf '%s' "$image_tags_base" | \ + sed -e 's#\([^,]*\)#\1-'${base_image}'#g')" + image_name=$(printf '%s\n' "$name" | sed -e 's#.*\([^/]\+/[^/]\+\)$#\1#') + if { [ "$image_name" != 'pandoc/minimal' ] && \ + [ "$build_stack" = 'alpine' ]; } || + { [ "$image_name" = 'pandoc/minimal' ] && \ + [ "$build_stack" = 'static' ]; }; + then + tags="${tags},${image_tags_base},${image_tags}" + else + tags="${tags},${image_tags}" + fi +done +tags=$(printf '%s\n' "$tags" | sed -e 's/^,//') + +printf 'tags="%s"\n' "$tags" +printf 'base_image="%s"\n' "$base_image" +printf 'base_image_version="%s"\n' "$base_image_version" +printf 'texlive_version="%s"\n' "$(field 5)"