<file_sep>#!/bin/bash

# Usage: `./this-script <image name>`
#
# The image name should be of the form 'namespace:tag', e.g.
# 'yourname/yourproject:latest', see also https://docs.docker.com/docker-hub/builds/advanced/.
#
# If $IMAGE_NAME is defined as an environment variable, it takes precedent and the
# command line argument image name will be ignored.
# If neither is given, this script fails.

# In 2021, Docker retired its free automated builds. They now require a Pro plan.
# (We can thank abusive miners for that).
# To cope, and to not have to pay, these images are now built locally and pushed
# manually. Hence, this script now works for both local use as well as DockerHub use
# (should a Pro plan be available).

set -u

# For more info, see:
# https://docs.docker.com/docker-hub/builds/advanced/
# https://web.archive.org/web/20201002130055/https://dev.to/samuelea/automate-your-builds-on-docker-hub-by-writing-a-build-hook-script-13fp

BUILD_DATE=$(date --utc +%Y-%m-%dT%H:%M:%S%Z)

cut_tag_field() { echo "$DOCKER_TAG" | cut --delimiter="-" --fields="$1"; }

# Assign some (dynamic) default values if variables undefined.
# This is compatible with the DockerHub infrastructure where these are defined
# (see https://docs.docker.com/docker-hub/builds/advanced/) but also allows easy
# local execution.
: "${SOURCE_COMMIT:=$(git rev-parse HEAD)}" # https://stackoverflow.com/a/28085062
: "${DOCKER_TAG:=latest}" # https://stackoverflow.com/a/28085062

# This will fail if compiling locally where this env var is (probably) not defined,
# and no command line argument is given:
: "${IMAGE_NAME:=$1}" # https://stackoverflow.com/a/28085062

echo "Using Docker tag '$DOCKER_TAG'."
echo "Version control reference is '$SOURCE_COMMIT'."
echo "Building with image name '$IMAGE_NAME'."

if [[ "$DOCKER_TAG" == "latest" ]]
then
    # Default OS:
    BASE_OS="debian"
    # `debian:latest` will get latest *stable*, which has between 1.5 to 2 years old,
    # well-tested software. While that is suitable for servers etc., this Docker image
    # does not need highest stability. More recent features are more important (e.g. for
    # pandoc). Debian's `testing` branch sounds cutting-edge but is roughly 6 months old
    # software, so should be stable enough here.
    # This image's main component, TeXLive, is largely unaffected by this anyway.
    OS_VERSION="testing"
    # Installation script takes care of finding the appropriate version of TeXLive
    # for the 'latest' tag:
    TL_VERSION="$DOCKER_TAG"
else
    # Otherwise, take versions as manually specified in build process.
    #
    # This assumes tags in the form: '<baseos>-<version>-texlive-<version>',
    # e.g. 'debian-10-texlive-2019'
    #
    # E.g. 'debian'
    BASE_OS=$(cut_tag_field 1)
    # Version e.g. '10' for Debian, but can also be 'buster', see:
    # https://wiki.debian.org/DebianReleases
    # These are the available versions:
    # https://hub.docker.com/_/debian?tab=tags
    OS_VERSION=$(cut_tag_field 2)
    # E.g. '2020':
    TL_VERSION=$(cut_tag_field 4)
fi

docker build \
    --build-arg VCS_REF="$SOURCE_COMMIT" \
    --build-arg BUILD_DATE="$BUILD_DATE" \
    --build-arg BASE_OS="$BASE_OS" \
    --build-arg OS_VERSION="$OS_VERSION" \
    --build-arg TL_VERSION="$TL_VERSION" \
    --tag "$IMAGE_NAME" \
    .
<file_sep># Docker image with custom, almost-full TeXLive distribution & various tools

[![Docker Pulls](https://img.shields.io/docker/pulls/alexpovel/latex)](https://hub.docker.com/r/alexpovel/latex)

Serves a lot of needs surrounding LaTeX file generation and handling.
For the rationale behind the installed Debian packages, see [below](#custom-tools).
To see how to build the image, also see [below](#building).

## Quick Intro

To use the image, you can use the [example](tests/minimal.tex) provided in this repository:

- Bash:

  ```bash
  docker run \
    --rm \
    --volume $(pwd)/tests:/tex \
    alexpovel/latex
  ```

- PowerShell:

  ```powershell
  docker run `
    --rm `
    --volume ${PWD}/tests:/tex `
    alexpovel/latex
  ```

The parts making up the command are:

- The last line is the location on [DockerHub](https://hub.docker.com/repository/docker/alexpovel/latex).
  Without specifying a [*tag*](https://hub.docker.com/repository/docker/alexpovel/latex/tags?page=1),
  the default `latest` is implied.
  See [below](#historic-builds) for more options.
- The `--rm` option removes the container after a successful run.
  This is generally desired since containers are not supposed to be stateful:
  once their process terminates, the container terminates, and it can be considered junk.
- Providing a `--volume`, in this case [`tests/`](./tests/) in the current working directory,
  is required for the container to find files to work on.
  It has to be *mounted* to a location *inside* the container.
  This has to be whatever the last `WORKDIR` instruction in the [Dockerfile](Dockerfile) is,
  e.g. `/tex`.

  Otherwise, you can always override the `WORKDIR` using the `--workdir` option.
  This is the directory in which the Docker container's process works in and expects to
  find files.
- Note that there is no command given, e.g. there is nothing *after* `alexpovel/latex`.
  In this form, the container runs as an executable (just as if you ran `lualatex` or
  similar commands), where the program to be executed is determined by the `ENTRYPOINT`
  instruction in the [Dockerfile](Dockerfile).

  For example, if the `ENTRYPOINT` is set to `latexmk`, running the above command will
  execute `latexmk` in the container's context, without you having to specify it.

  (`latexmk` is a recipe-like tool that automates LaTeX document compilation by running
  `lualatex`, `biber` and whatever else required for compilation as many times as
  needed for proper PDF output (so references like `??` in the PDF are resolved).
  It does this by detecting that auxiliary files no longer change (steady-state).
  The tool is best configured using its config file, `.latexmkrc`.)

  Any options to the `ENTRYPOINT` executable are given at the end of the command, e.g.:

  ```bash
  docker run \
    --rm \
    --volume $(pwd)/tests:/tex \
    alexpovel/latex \
    -c
  ```

  to run, if `latexmk` is the `ENTRYPOINT`, the equivalent of `latexmk -c`
  ([cleaning auxiliary files](https://mg.readthedocs.io/latexmk.html#cleaning-up)).

  To **overwrite** the `ENTRYPOINT`, e.g. because you want to run only `lualatex`,
  use the `--entrypoint` option, e.g. `--entrypoint="lualatex"`.
  Similarly, you can work inside of the container directly, e.g. for debugging, using
  `--entrypoint="bash"`.

## Approach

This Dockerfile is based on a custom TeXLive installer using their
[`install-tl` tool](https://www.tug.org/texlive/doc/install-tl.html),
instead of
[Debian's `texlive-full`](https://packages.debian.org/search?searchon=names&keywords=texlive-full).
Other, smaller `texlive-` collections would be
[available](https://packages.debian.org/search?suite=default&section=all&arch=any&searchon=names&keywords=texlive),
but TeXLive cannot install missing packages on-the-fly,
[unlike MiKTeX](https://miktex.org/howto/miktex-console/).
Therefore, images should come with all desirable packages in place; installing them after
the fact in running containers using [`tlmgr`](https://www.tug.org/texlive/tlmgr.html)
is the wrong approach (containers are only meant to be run; if they are incomplete,
modify the *image*, ordinarily by modifying the `Dockerfile`).

This approach has the following advantages:

- Using ["vanilla" TeXLive](https://www.tug.org/texlive/debian.html)
  affords us the latest packages directly from the source, while the official
  Debian package might lag behind a bit.
  This is often not relevant, but has bitten me several times while working with the
  latest packages.
- The installation can be adjusted better.
  For example, *multiple GBs* of storage/image size are saved by omitting unneeded PDF
  documentation files.
- We can install arbitrary TeXLive versions, as long as they are the
  [current](https://tug.org/texlive/acquire-netinstall.html)
  or an
  archived (<ftp://tug.org/historic/systems/texlive/>)
  version.
  Otherwise, to obtain [older or even obsolete TeXLive installations](#historic-builds),
  one would have to also obtain a corresponding Debian version.
  This way, we can choose a somewhat recent Debian version and simply install an old
  TeXLive into it.

  Eligible archive versions are those year directories (`2019`, `2020`, ...) present
  at the above FTP link that have a `tlnet-final` subdirectory.
  This is (speculating here) a frozen, aka final, version, put online the day the *next*
  major release goes live.
  For example, version `2021` released on 2021-04-01 and `2020` received its `tlnet-final`
  subdirectory that same day.

The `install-tl` tool is configured via a [*Profile* file](config/texlive.profile), see also
the [documentation](https://www.tug.org/texlive/doc/install-tl.html#PROFILES).
This enables unattended, pre-configured installs, as required for a Docker installation.

---

The (official?) [`texlive/texlive` image](https://hub.docker.com/r/texlive/texlive) follows
[the same approach](https://hub.docker.com/layers/texlive/texlive/latest/images/sha256-70fdbc1d9596c8eeb4a80c71a8eb3a5aeb63bed784112cbdb87f740e28de7a80?context=explore).
However, there are a bunch of things this Dockerfile does differently that warrant not
building `FROM` that image:

- a user-editable, thus more easily configurable [profile](config/texlive.profile).
  This is mainly concerning the picked package *collections*. Unchecking (putting a `0`)
  unused ones saves around 500MB at the time of writing.
- more elaborate support for [historic versions](#historic-builds)
- an installation procedure incorporating a proper `USER`

Things they do that do not happen here:

- verify download integrity using `gpg`

### Historic Builds

LaTeX is a slow world, and many documents/templates in circulation still rely on
outdated practices or packages.
This can be a huge hassle.
Maintaining an old LaTeX distribution next to a current one on the same host is
not fun.
This is complicated by the fact that (La)TeX seems to do things differently than pretty much
everything else.
For this, Docker is the perfect tool.

This image can be built (`docker build`) with different build `ARG`s, and the build
process will figure out the proper way to handle installation.
There is a [script](texlive.sh) to handle getting and installing TeXLive from the
proper location ([current](https://www.tug.org/texlive/acquire-netinstall.html) or
[archive](ftp://tug.org/historic/systems/texlive/)).
Refer to the [Dockerfile](Dockerfile) for the available `ARG`s (all `ARG` have a default).
These are handed to the build process via the
[`--build-arg` option](https://docs.docker.com/engine/reference/commandline/build/#options).

Note that for a *specific* TeXLive version to be picked, it needs to be present in their
[archives](ftp://tug.org/historic/systems/texlive/).
The *current* TeXLive is not present there (it's not historic), but is available under
the `latest` Docker tag.
As such, if for example `2020` is the *current* TeXLive, and the image is to be based on
Debian 10, there is *no* `debian-10-texlive-2020` tag.
You would obtain this using the `latest` tag.
As soon as TeXLive 2020 is superseded and consequently moved to the archives,
the former tag can become available.

To build an array of different versions automatically, DockerHub provides
[advanced options](https://docs.docker.com/docker-hub/builds/advanced/) in the form of
hooks, e.g. a [build hook](hooks/build).
These are bash scripts that override the default DockerHub build process.
At build time, DockerHub provides
[environment variables](https://docs.docker.com/docker-hub/builds/advanced/#environment-variables-for-building-and-testing)
which can be used in the build hook to forward these into the Dockerfile build process.
As such, by just specifying the image *tags* on DockerHub, we can build corresponding
images automatically (see also
[here](https://web.archive.org/web/20201005132636/https://dev.to/samuelea/automate-your-builds-on-docker-hub-by-writing-a-build-hook-script-13fp)).
For more info on this, see [below](#on-dockerhub).

The approximate [matching of Debian to TeXLive versions](https://www.tug.org/texlive/debian.html)
is (see also [here](https://www.debian.org/releases/) and [here](https://www.debian.org/distrib/archive).):

| Debian Codename | Debian Version | TeXLive Version |
| --------------- | :------------: | :-------------: |
| bullseye        |       11       |      2020       |
| buster          |       10       |      2018       |
| stretch         |       9        |      2016       |
| jessie          |       8        |      2014       |
| wheezy          |       7        |      2012       |
| squeeze         |      6.0       |      2009       |
| lenny           |      5.0       |     unknown     |
| etch            |      4.0       |     unknown     |
| sarge           |      3.1       |     unknown     |
| woody           |      3.0       |     unknown     |
| potato          |      2.2       |     unknown     |
| slink           |      2.1       |     unknown     |
| hamm            |      2.0       |     unknown     |

This is only how the official Debian package is shipped.
These versions can be, to a narrow extend, freely mixed.
Using `install-tl`, older versions of TeXLive can be installed on modern Debian versions.

#### Issues

Using [*obsolete* Debian releases](https://www.debian.org/releases/) comes with a long
list of headaches.
As such, Debian versions do not reach too far back.
It does not seem worth the effort.
Instead, it seems much easier to install older TeXLive versions onto reasonably recent
Debians.

Issues I ran into are:

- `apt-get update` will fail if the original Debian repository is dead (Debian 6/TeXLive 2014):

  ```plaintext
  W: Failed to fetch http://httpredir.debian.org/debian/dists/squeeze/main/binary-amd64/Packages.gz  404  Not Found
  W: Failed to fetch http://httpredir.debian.org/debian/dists/squeeze-updates/main/binary-amd64/Packages.gz  404  Not Found
  W: Failed to fetch http://httpredir.debian.org/debian/dists/squeeze-lts/main/binary-amd64/Packages.gz  404  Not Found
  E: Some index files failed to download, they have been ignored, or old ones used instead.
  ```

  As such, there needs to be a [dynamic way to update `/etc/apt/sources.list`](https://github.com/alexpovel/latex-extras-docker/blob/fa9452c236079a65563daff22767b2b637dd80c6/adjust_sources_list.sh)
  if the Debian version to be used in an archived one, see also
  [here](https://web.archive.org/web/20201007095943/https://www.prado.lt/using-old-debian-versions-in-your-sources-list).
- `RUN wget` (or `curl` etc.) via `HTTPS` will fail for older releases, e.g. GitHub
  rejected the connection due to the outdated TLS version of the old release (Debian 6/TeXLive 2015):

  ```text
  Connecting to github.com|172.16.31.10|:443... connected.
  OpenSSL: error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version
  ```

- Downloading older releases requires using the [Debian archives](http://archive.debian.org/debian/).
  This works fine, however a warning is issued (Debian 6/TeXLive 2014):

  ```plaintext
  W: GPG error: http://archive.debian.org squeeze Release: The following signatures were invalid: KEYEXPIRED 1520281423 KEYEXPIRED 1501892461
  ```

  Probably related to this, the installation then fails:

  ```plaintext
  WARNING: The following packages cannot be authenticated!
    libgdbm3 libssl0.9.8 wget libdb4.7 perl-modules perl openssl ca-certificates
  E: There are problems and -y was used without --force-yes
  ```

  According to `man apt-get`, `--force-yes` is both deprecated and absolutely not
  recommended.
  The correct course here is to `--allow-unauthenticated`, however this would also
  affect the build process for modern versions, where authentication *did not* fail.
  The official Debian archives are probably trustworthy, but this is still an issue.
- A more obscure issue is (Debian 7/TeXLive 2011):

  ```plaintext
  The following packages have unmet dependencies:
    perl : Depends: perl-base (= 5.14.2-21+deb7u3) but 5.14.2-21+deb7u6 is to be installed
  E: Unable to correct problems, you have held broken packages.
  ```

  While the error message itself is crystal-clear, debugging this is probably a nightmare.
- Tools like `pandoc`, which was released in [2006](https://pandoc.org/releases.html),
  limit the earliest possible Debian version as long as the tool's installation is part
  of the Dockerfile.
  In this example, 2006 should in any case be early enough (if not, update your LaTeX
  file to work with more recent versions, that is probably decidedly less work).

## Custom Tools

The auxiliary tools are (for the actual, up-to-date list, see the [Dockerfile](Dockerfile)):

- A *Java Runtime Environment* for [`bib2gls`](https://ctan.org/pkg/bib2gls) from the
  [`glossaries-extra` package](https://www.ctan.org/pkg/glossaries-extra).

  `bib2gls` takes in `*.bib` files with glossary, symbol, index and other definitions
  and applies sorting, filtering etc.
  For this, it requires Java.
- [`inkscape`](https://inkscape.org/) because the [`svg`](https://ctan.org/pkg/svg)
  package needs it.
  We only require the CLI, however there is no "no-GUI" version available.
  Headless Inkscape is [currently in the making](https://web.archive.org/web/20201007100140/https://wiki.inkscape.org/wiki/index.php/Future_Architecture).

  Using that package, required PDFs and PDF_TEXs are only generated at build-time
  (on the server or locally) and treated as a cache.
  As such, they can be discarded freely and are regenerated in the next compilation run,
  using `svg`, which calls `inkscape`.
  Therefore, the `svg` package gets rid of all the PDF/PDF_TEX intermediate junk and lets us
  deal with the true source -- `*.svg` files -- directly.
  These files are really [XML](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics),
  ergo text-based, ergo suitable for VCS like `git` (as opposed to binary PDFs).

  Being an external tool, `svg`/`inkscape` also requires the `--shell-escape` option to
  `lualatex` etc. for writing outside files.
- [`gnuplot`](http://www.gnuplot.info/) for `contour gnuplot` commands for `addplot3` in `pgfplots`.
  So essentially, an external add-on for the magnificent `pgfplots` package.
  For `gnuplot` to write computed results to a file for `pgfplots` to read,
  `--shell-escape` is also required.
- [`pandoc`](https://pandoc.org/) as a very flexible, convenient markup conversion tool.
  For example, it can convert Markdown (like this very [README](README.md)) to PDF
  via LaTeX:

  ```bash
  pandoc README.md --output=README.pdf  # pandoc infers what to do from suffixes
  ```

  The default output is usable, but not very pretty.
  This is where *templates* come into play.
  A very tidy and well-made such template is
  [*Eisvogel*](https://github.com/Wandmalfarbe/pandoc-latex-template).
  Its installation is not via a (Debian) package, so it has to be downloaded specifically.
  For this, additional requirements are:

  - `wget` to download the archive,
  - `librsvg2-bin` for the `rsvg-convert` tool.
    This is used by `pandoc` to convert SVGs when embedding them into the new PDF.

  Note that `pandoc` and its *Eisvogel* template can draw
  [metadata from a YAML header](https://pandoc.org/MANUAL.html#metadata-variables),
  for example:

  ```yaml
  ---
  title: "Title"
  author: [Author]
  date: "YYYY-MM-DD"
  subject: "Subject"
  keywords: [Keyword1, Keyword2]
  lang: "en"
  ...
  ```

  among other metadata variables.
  *Eisvogel* uses it to fill the document with info, *e.g.* the PDF header and
  footer.

  `pandoc` is not required for LaTeX work, but is convenient to have at the ready for
  various conversion jobs.

## Building

The proper way to access these images is via DockerHub.

### On DockerHub

This repository and its [Dockerfile](Dockerfile) are built into the corresponding image
continuously and made available on [DockerHub](https://hub.docker.com/repository/docker/alexpovel/latex).

There, the automated build process looks somewhat like:

![DockerHub Build Rules](images/dockerhub_build_rules.png)

The DockerHub build process combines these build rules with the [build hook](hooks/build)
to automatically build and tag images as specified by those rules.

For the currently available tags, see [here](https://hub.docker.com/repository/docker/alexpovel/latex/tags?page=1).

### Locally

The Dockerfile can of course also be built locally.
If you desire to use non-default `ARG`s, the build might look like:

```bash
export TL_VERSION="<version>"
export BASE_OS="<Linux distro name>"
export OS_VERSION="<version corresponding to that name, e.g. '8' or 'buster' (Debian)>"

docker build . \
  --build-arg TL_VERSION=${TL_VERSION} \
  --build-arg BASE_OS=${BASE_OS} \
  --build-arg OS_VERSION=${OS_VERSION} \
  --tag <some>/<name>:${BASE_OS}-${OS_VERSION}-texlive-${TL_VERSION}
```

Essentially, you would want to emulate what the [build hook](hooks/build) does.

This process can take a very long time, especially when downloading from the TeXLive/TUG
archives.
For developing/debugging, it is advisable to download the archive files once.
E.g. for TexLive 2014, you would want this directory: <ftp://tug.org/historic/systems/texlive/2014/tlnet-final/>.
Download in one go using:

```bash
wget --recursive --no-clobber ftp://tug.org/historic/systems/texlive/2014/tlnet-final
```

The `--no-clobber` option allows you to restart the download at will.
Then, work with the `--repository=/some/local/path` option of `install-tl`,
after [copying the archive directory into the image at build time](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#understand-build-context)
(see also [here](https://tex.stackexchange.com/a/374651/120853)).
Having a local copy circumvents [unstable connections](https://tex.stackexchange.com/q/370686/120853)
and minimizes unnecessary load onto TUG servers.
<file_sep># ARGs before the first FROM are global and usable in all stages
ARG BASE_OS=debian
ARG OS_VERSION=testing

# Image with layers as used by all succeeding steps
FROM ${BASE_OS}:${OS_VERSION} as base

# Use `apt-get` over just `apt`, see https://askubuntu.com/a/990838/978477.
# Also run `apt-get update` on every `RUN`, see:
# https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run
RUN apt-get update && \
    apt-get install --yes --no-install-recommends \
        # wget for `install-tl` script to download TeXLive, and other downloads.
        wget \
        # In a similar vein, `curl` is required by various tools, or is just very
        # nice to have for various scripting tasks.
        curl \
        # wget/install-tl requires capability to check certificate validity.
        # Without this, executing `install-tl` fails with:
        #
        # install-tl: TLPDB::from_file could not initialize from: https://<mirror>/pub/ctan/systems/texlive/tlnet/tlpkg/texlive.tlpdb
        # install-tl: Maybe the repository setting should be changed.
        # install-tl: More info: https://tug.org/texlive/acquire.html
        #
        # Using `install-tl -v`, found out that mirrors use HTTPS, for which the
        # underlying `wget` (as used by `install-tl`) returns:
        #
        # ERROR: The certificate of '<mirror>' is not trusted.
        # ERROR: The certificate of '<mirror>' doesn't have a known issuer.
        #
        # This is resolved by installing:
        ca-certificates \
        # Update Perl, otherwise: "Can't locate Pod/Usage.pm in @INC" in install-tl
        # script; Perl is already installed, but do not use `upgrade`, see
        # https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run
        perl \
        # Install `latexindent` Perl dependencies.
        # Found these using this method: https://unix.stackexchange.com/a/506964/374985
        # List of `latexindent` dependencies is here:
        # https://latexindentpl.readthedocs.io/en/latest/appendices.html#linux,
        # see also the helper script at
        # https://github.com/cmhughes/latexindent.pl/blob/master/helper-scripts/latexindent-module-installer.pl
        #
        # Installing via Debian system packages because installing the modules via
        # `cpanm` requires `gcc` and I wanted to avoid installing that (~200MB).
        #
        # YAML::Tiny:
        libyaml-tiny-perl \
        # File::HomeDir:
        libfile-homedir-perl \
        # Unicode:GCString:
        libunicode-linebreak-perl \
        # Log::Log4perl:
        liblog-log4perl-perl \
        # Log::Dispatch:
        liblog-dispatch-perl \
        # Usually, `latexmk` is THE tool to use to automate, in a `make`-like style,
        # LaTeX (PDF) file generation. However, if that is not enough, the following
        # will fill the gaps and cover all other use cases:
        make \
        # Get `envsubst` to replace environment variables in files with their actual
        # values.
        gettext-base \
        # Using the LaTeX package `minted` for syntax highlighting of source code
        # snippets. It's much more powerful than the alternative `listings` (which is
        # pure TeX: no outside dependencies but limited functionality) but requires
        # Python's `pygments` package:
        python3 \
        python3-pygments \
        # Required to embed git metadata into PDF from within Docker container:
        git

# The `minted` LaTeX package provides syntax highlighting using the Python `pygmentize`
# package. That package also installs a callable script, which `minted` uses, see
# https://tex.stackexchange.com/a/281152/120853.
# Therefore, `minted` primarily works by invoking `pygmentize` (or whatever it was
# overridden by using `\MintedPygmentize`). However, it requires `python` for other
# jobs, e.g. to remove leading whitespace for the `autogobble` function, see the
# "\minted@autogobble Remove common leading whitespace." line in the docs.
# It is invoked with `python -c`, but Debian only has `python3`. Therefore, alias
# `python` to invoke `python3`. Use `update-alternatives` because it's cooler than
# symbolic linking, and made for this purpose.
# If `python` is not available but `pygmentize` is, stuff like `autogobble` and
# `\inputminted`won't work but syntax highlighting will.
# See also https://stackoverflow.com/a/55351449/11477374
# Last argument is `priority`, whose value shouldn't matter since there's nothing else.
RUN update-alternatives --install /usr/bin/python python /usr/bin/python3 1


FROM base as downloads

# Cannot share ARGs over multiple stages, see also:
# https://github.com/moby/moby/issues/37345.
# Therefore, work in root (no so `WORKDIR`), so in later stages, the location of
# files copied from this stage does not have to be guessed/WET.

# Using an ARG with 'TEX' in the name, TeXLive will warn:
#
#  ----------------------------------------------------------------------
#  The following environment variables contain the string "tex"
#  (case-independent).  If you're doing anything but adding personal
#  directories to the system paths, they may well cause trouble somewhere
#  while running TeX. If you encounter problems, try unsetting them.
#  Please ignore spurious matches unrelated to TeX.

#     TEXPROFILE_FILE=texlive.profile
#  ----------------------------------------------------------------------
#
# This also happens when the *value* contains 'TEX'.
# `ARG`s are only set during Docker image build-time, so this warning should be void.

ARG TL_VERSION=latest
ARG TL_INSTALL_ARCHIVE="install-tl-unx.tar.gz"
ARG EISVOGEL_ARCHIVE="Eisvogel.tar.gz"
ARG INSTALL_TL_DIR="install-tl"

COPY texlive.sh .

RUN \
    # Get appropriate installer for the TeXLive version to be installed:
    ./texlive.sh get_installer ${TL_VERSION} && \
    # Get Eisvogel LaTeX template for pandoc,
    # see also #175 in that repo.
    wget https://github.com/Wandmalfarbe/pandoc-latex-template/releases/latest/download/${EISVOGEL_ARCHIVE}

RUN \
    mkdir ${INSTALL_TL_DIR} && \
    # Save archive to predictable directory, in case its name ever changes; see
    # https://unix.stackexchange.com/a/11019/374985.
    # The archive comes with a name in the form of 'install-tl-YYYYMMDD' from the source,
    # which is of course unpredictable.
    tar --extract --file=${TL_INSTALL_ARCHIVE} --directory=${INSTALL_TL_DIR} --strip-components 1 && \
    \
    # Prepare Eisvogel pandoc template (yields `eisvogel.latex` among other things):
    tar --extract --file=${EISVOGEL_ARCHIVE}


FROM base as main

# Metadata
ARG BUILD_DATE="n/a"
ARG VCS_REF="n/a"

ARG TL_VERSION="latest"
ARG TL_PROFILE="texlive.profile"

# Label according to http://label-schema.org/rc1/ to have some metadata in the image.
# This is important e.g. to know *when* an image was built. Depending on that, it can
# contain different software versions (even if the base image is specified as a fixed
# version).
LABEL \
    maintainer="<NAME> <<EMAIL>>" \
    org.label-schema.build-date=${BUILD_DATE} \
    org.label-schema.description="TeXLive with most packages, JavaRE, Inkscape, pandoc and more" \
    org.label-schema.url="https://collaborating.tuhh.de/alex/latex-git-cookbook" \
    org.label-schema.vcs-url="https://github.com/alexpovel/latex-extras-docker" \
    org.label-schema.vcs-ref=${VCS_REF} \
    org.label-schema.schema-version="1.0"

# User to install and run LaTeX as.
# This is a security and convenience measure: by default, containers run as root.
# To work and compile PDFs using this container, you will need to map volumes into it
# from your host machine. Those bind-mounts will then be accessed as root from this
# container, and any generated files will also be owned by root. This is inconvenient at
# best and dangerous at worst.
# The generated user here will have IDs of 1000:1000. If your local user also has those
# (the case for single-user Debians etc.), your local user will already have correct
# ownership of all files generated by the user we create here.
ARG USER="tex"
RUN useradd --create-home ${USER}

ARG INSTALL_DIR="/install"
WORKDIR ${INSTALL_DIR}

# Copy custom file containing TeXLive installation instructions
COPY config/${TL_PROFILE} .
COPY --from=downloads /install-tl/ /texlive.sh ./

# Global wget config file, see the comments in that file for more info and the rationale.
# Location of that file depends on system, e.g.: https://askubuntu.com/a/368050
COPY config/.wgetrc /etc/wgetrc

# Move to where pandoc looks for templates, see https://pandoc.org/MANUAL.html#option--data-dir
COPY --from=downloads /eisvogel.latex /home/${USER}/.pandoc/templates/

# "In-place" `envsubst` run is a bit more involved, see also:
# https://stackoverflow.com/q/35078753/11477374.
RUN tmpfile=$(mktemp) && \
    cp ${TL_PROFILE} tmpfile && \
    echo "Using profile file (indent purely visual):" && \
    cat ${TL_PROFILE} | envsubst | tee tmpfile | sed "s/^/\t/" && \
    mv tmpfile ${TL_PROFILE}

# (Large) LaTeX layer
RUN ./texlive.sh install ${TL_VERSION}

# Remove no longer needed installation workdir.
# Cannot run this earlier because it would be recreated for any succeeding `RUN`
# instructions.
# Therefore, change `WORKDIR` first, then delete the old one.
WORKDIR /${USER}
RUN rm --recursive ${INSTALL_DIR}

# Layer with graphical and auxiliary tools
RUN apt-get update && \
    apt-get install --yes --no-install-recommends \
    # headless, 25% of normal size:
    default-jre-headless \
    # No headless inkscape available currently:
    inkscape \
    # nox (no X Window System): CLI version, 10% of normal size:
    gnuplot-nox \
    # For various conversion tasks, e.g. EPS -> PDF (for legacy support):
    ghostscript

# Pandoc layer; not required for LaTeX compilation, but useful for document conversions
RUN apt-get update && \
    apt-get install --yes --no-install-recommends \
    # librsvg2 for 'rsvg-convert' used by pandoc to convert SVGs when embedding
    # into PDF
    librsvg2-bin \
    pandoc

USER ${USER}

# Load font cache, has to be done on each compilation otherwise
# ("luaotfload | db : Font names database not found, generating new one.").
# If not found, e.g. TeXLive 2012 and earlier, simply skip it. Will return exit code
# 0 and allow the build to continue.
# Warning: This is USER-specific. If the current `USER` for which we run this is not
# the container user, the font will be regenerated for that new user.
RUN luaotfload-tool --update || echo "luaotfload-tool did not succeed, skipping."

# The default parameters to the entrypoint; overridden if any arguments are given to
# `docker run`.
# `lualatex` usage for `latexmk` implies PDF generation, otherwise DVI is generated.
CMD [ "--lualatex" ]

# Allow container to run as an executable; override with `--entrypoint`.
# Allows to simply `run` the image without specifying any executable.
# If `latexmk` is called without a file argument, it will run on all *.tex files found.
ENTRYPOINT [ "latexmk" ]
<file_sep>#!/bin/bash

# Script to fetch `install-tl` script from different sources, depending on argument
# given.

set -ueo pipefail

usage() {
    echo "Usage: $0 get_installer|install latest|version (YYYY)"
}

check_path() {
    # The following test assumes the most basic program, `tex`, is present, see also
    # https://www.tug.org/texlive/doc/texlive-en/texlive-en.html#x1-380003.5
    echo "Checking PATH and installation..."
    if tex --version
    then
        echo "PATH and installation seem OK, exiting with success."
        exit 0
    else
        echoerr "PATH or installation unhealthy, further action required..."
    fi
}

if [[ $# != 2 ]]; then
    echoerr "Unsuitable number of arguments given."
    usage
    # From /usr/include/sysexits.h
    exit 64
fi

# From: https://stackoverflow.com/a/2990533/11477374
echoerr() { echo "$@" 1>&2; }

# Bind CLI arguments to explicit names:
ACTION=${1}
VERSION=${2}

# Download the `install-tl` script from the `tlnet-final` subdirectory, NOT
# from the parent directory. The latter contains an outdated, non-final `install-tl`
# script, causing this exact problem:
# https://tug.org/pipermail/tex-live/2017-June/040376.html
HISTORIC_URL="ftp://tug.org/historic/systems/texlive/${VERSION}/tlnet-final"
REGULAR_URL="http://mirror.ctan.org/systems/texlive/tlnet"

case ${ACTION} in
    "get_installer")
        if [[ ${VERSION} == "latest" ]]
        then
            # Get from default, current repository
            GET_URL="${REGULAR_URL}/${TL_INSTALL_ARCHIVE}"
        else
            # Get from historic repository
            GET_URL="${HISTORIC_URL}/${TL_INSTALL_ARCHIVE}"
        fi
        wget "$GET_URL"
    ;;
    "install")
        if [[ ${VERSION} == "latest" ]]
        then
            # Install using default, current repository.
            # Install process/script documentation is here:
            # https://www.tug.org/texlive/doc/texlive-en/texlive-en.html
            perl install-tl \
                --profile="$TL_PROFILE"
        else
            # Install using historic repository (`install-tl` script and repository
            # versions need to match)
            perl install-tl \
                --profile="$TL_PROFILE" \
                --repository="$HISTORIC_URL"
        fi

        # If automatic `install-tl` process has already adjusted PATH, we are happy.
        check_path
        echo "install-tl procedure did not adjust PATH automatically, trying other options..."

        # `\d` class doesn't exist for basic `grep`, use `0-9`, which is much more
        # portable. Finding the initial dir is very fast, but looking through everything
        # else afterwards might take a while. Therefore, print and quit after first result.
        # Path example: `/usr/local/texlive/2018/bin/x86_64-linux`
        TEXLIVE_BIN_DIR=$(find / -type d -regextype grep -regex '.*/texlive/[0-9]\{4\}/bin/.*' -print -quit)

        # -z test: string zero length?
        if [ -z "$TEXLIVE_BIN_DIR" ]
        then
            echoerr "Expected TeXLive installation dir not found and TeXLive installation did not modify PATH automatically."
            echoerr "Exiting."
            exit 1
        fi

        echo "Found TeXLive binaries at $TEXLIVE_BIN_DIR"
        echo "Trying native TeXLive symlinking using tlmgr..."

        # To my amazement, `tlmgr path add` can fail but still link successfully. So
        # check if PATH is OK despite that command failing.
        "$TEXLIVE_BIN_DIR"/tlmgr path add || \
            echoerr "Command borked, checking if it worked regardless..."
        check_path
        echoerr "Symlinking using tlmgr did not succeed, trying manual linking..."

        SYMLINK_DESTINATION="/usr/local/bin"

        # "String contains", see: https://stackoverflow.com/a/229606/11477374
        if [[ ! ${PATH} == *${SYMLINK_DESTINATION}* ]]
        then
            # Should never get here, but make sure.
            echoerr "Symlink destination ${SYMLINK_DESTINATION} not in PATH (${PATH}), exiting."
            exit 1
        fi

        echo "Symlinking TeXLive binaries in ${TEXLIVE_BIN_DIR}"
        echo "to a directory (${SYMLINK_DESTINATION}) found on PATH (${PATH})"

        # Notice the slash and wildcard.
        ln \
            --symbolic \
            --verbose \
            --target-directory="$SYMLINK_DESTINATION" \
            "$TEXLIVE_BIN_DIR"/*

        check_path

        echoerr "All attempts failed, exiting."
        exit 1
    ;;
    *)
        echoerr "Input not understood."
        usage
        # From /usr/include/sysexits.h
        exit 64
esac
