diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 45c8c79..9c55a02 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,5 +1,7 @@ name: Docker +# Many ports copyied from https://docs.docker.com/build/ci/github-actions/manage-tags-labels/ + # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support @@ -18,23 +20,31 @@ env: # Use docker.io for Docker Hub if empty REGISTRY: ghcr.io # github.repository as / - IMAGE_NAME: ${{ github.repository }} + IMAGE_NAME: pflum/docker-postfix jobs: build: - runs-on: ubuntu-latest permissions: contents: read packages: write + strategy: + matrix: + include: + - variant: latest + build_arg: "INCLUDE_DEV_TOOLS=false" + tag_suffix: "latest" + + - variant: latest-dev + build_arg: "INCLUDE_DEV_TOOLS=true" + tag_suffix: "latest-dev" + steps: - name: Checkout repository uses: actions/checkout@v3 - # Login against a Docker registry except on PR - # https://github.com/docker/login-action - name: Log into registry ${{ env.REGISTRY }} if: github.event_name != 'pull_request' uses: docker/login-action@v4 @@ -43,25 +53,16 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - # Extract metadata (tags, labels) for Docker - # https://github.com/docker/metadata-action - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@v6 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - name: Setup Docker buildx uses: docker/setup-buildx-action@v4 - # Build and push Docker image with Buildx (don't push on PR) - # https://github.com/docker/build-push-action - - name: Build and push Docker image + - name: Build and push Docker image - ${{ matrix.variant }} id: build-and-push uses: docker/build-push-action@v7 with: context: . push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - + build-args: ${{ matrix.build_arg }} + tags: | + ${{ github.ref == 'refs/heads/master' && format('{0}/{1}:{2}', env.REGISTRY, env.IMAGE_NAME, matrix.tag_suffix) || format('{0}/{1}:{2}-{3}', env.REGISTRY, env.IMAGE_NAME, github.ref_name, matrix.tag_suffix) }} + ${{ github.ref == 'refs/heads/master' && format('{0}/{1}:{2}-{3}', env.REGISTRY, env.IMAGE_NAME, matrix.tag_suffix, github.sha) || format('{0}/{1}:{2}-{3}-{4}', env.REGISTRY, env.IMAGE_NAME, github.ref_name, matrix.tag_suffix, github.sha) }} diff --git a/Dockerfile b/Dockerfile index ef737b5..9302af5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,24 @@ +ARG INCLUDE_DEV_TOOLS=false + FROM alpine:3.22 RUN apk add --no-cache --update \ - bash \ ca-certificates \ postfix \ postfix-doc \ postfix-ldap \ tzdata +RUN if [ "$INCLUDE_DEV_TOOLS" = "true" ]; then \ + apk add --no-cache bash bash-doc; \ + fi + EXPOSE 25 465 587 VOLUME [ "/var/spool/postfix" ] COPY VERSION / +COPY entrypoint.sh / -ENTRYPOINT ["/usr/sbin/postfix", "start-fg"] +ENTRYPOINT ["/entrypoint.sh"] diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..9fe9553 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,87 @@ +#!/bin/sh + +logger () { + if [ "$(echo "$ENTRYPOINT_DEBUG" | tr '[:upper:]' '[:lower:]')" = "true" ]; then + echo "$1" + else + if [ -n "$2" ]; then + echo "$2" + fi + fi +} + +postfix_copy_replace_env () { + # copy file and replace env vars (syntax: https://doc.dovecot.org/main/core/settings/syntax.html#environment-variables) + rm -f "$2" + LINENR=1 + while IFS= read -r line; do + # shellcheck disable=SC2016 + MATCHES="$(echo "$line" | grep -oE '(=|\s)(%\{env:\w+\}|\$ENV:\w+)(\s|$)')" + if [ -n "$MATCHES" ]; then + while IFS= read -r MATCH; do + ENVNAME="$(echo "$MATCH" | cut -d':' -f2 | cut -d'}' -f1)" + # shellcheck disable=SC2086 + ENVVALUE="$(eval echo \"\$$ENVNAME\")" + FIRSTCHAR="$(echo "$MATCH" | cut -c1)" + LASTCHAR="$(echo "$MATCH" | rev | cut -c1)" + if [ ! "$LASTCHAR" = " " ]; then + LASTCHAR="" + fi + line="$(echo "$line" | sed "s#${MATCH}#${FIRSTCHAR}${ENVVALUE}${LASTCHAR}#")" + logger "Found $ENVNAME in ${1}:$LINENR and replaced with \"$ENVVALUE\"" + done <> "$2" + LINENR=$((LINENR+1)) + done < "$1" +} + +postfix_compile_maps () { + MAPS=$(grep -vE "^\s*#.*$" "$1" | grep -oE "lmdb:/\S+") + if [ -n "$MAPS" ]; then + echo "$MAPS" | while IFS= read -r MAP; do + FILE="$(echo "$MAP" | rev | cut -d':' -f1 | rev)" + if [ -e "$FILE" ]; then + logger "Compile postfix-map $MAP" + postmap "$MAP" || logger "postmap for $MAP failed with exitcode $?" "postmap for $MAP failed" + else + logger "postmap $MAP, file not found!" + fi + done + fi +} + +if [ -d "/etc/postfix.template/" ]; then + echo "Copy config from /etc/postfix.template/ to /etc/postfix/" + find "/etc/postfix.template/" ! -path "*/..*" ! -type d | while read -r file; do + targetfile="$(echo "$file" | sed 's#^/etc/postfix.template/#/etc/postfix/#')" + rm -f "$targetfile" + install -D -m 0640 "$file" "$targetfile" + done + find "/etc/postfix.template/" ! -path "*/..*" -iname "*.cf" ! -type d | while read -r sourcefile; do + targetfile="$(echo "$sourcefile" | sed 's#^/etc/postfix.template/#/etc/postfix/#')" + if [ ! "$(echo "$DISABLE_ENV_REPLACE" | tr '[:upper:]' '[:lower:]')" = true ]; then + postfix_copy_replace_env "$sourcefile" "$targetfile" + fi + if [ ! "$(echo "$DISABLE_AUTO_COMPILE_MAPS" | tr '[:upper:]' '[:lower:]')" = true ]; then + postfix_compile_maps "$targetfile" + fi + done +fi + +if [ ! "$(echo "$DISABLE_POSTCONF_OVERWRITE" | tr '[:upper:]' '[:lower:]')" = true ]; then + postconf -e maillog_file=/dev/stdout +fi + +if [ -d "/entrypoint.d/" ]; then + for script in /entrypoint.d/*.sh; do + if [ -x "$script" ]; then + echo "Run script $script" + "$script" + fi + done +fi + +exec /usr/sbin/postfix start-fg