Skip to content

Docker Image CI

Docker Image CI #163

Workflow file for this run

name: Docker Image CI
env:
SKIP_QEMU_SETUP: 'true'
DOCKER_BUILD_RECORD_UPLOAD: false
on:
workflow_dispatch:
pull_request:
types:
- closed
paths-ignore:
- '**/README.md'
- '.github/**'
- '.gitattributes'
- '.gitignore'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
setup:
runs-on: ubuntu-latest
outputs:
VERSION: ${{ steps.setup_env_vars.outputs.VERSION }}
REPO_OWNER_LOWER: ${{ steps.setup_env_vars.outputs.REPO_OWNER_LOWER }}
REPO_NAME: ${{ steps.setup_env_vars.outputs.REPO_NAME }}
BRANCH_NAME: ${{ steps.setup_env_vars.outputs.BRANCH_NAME }}
steps:
- uses: actions/checkout@v4
- name: Extract version and set environment variables
id: setup_env_vars
run: |
VERSION=$(grep -E '^version *= *' pyproject.toml | head -n 1 | cut -d '"' -f2)
REPO_OWNER_LOWER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
REPO_NAME=$(basename "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
BRANCH_NAME=$(echo "${{ github.ref_name }}" | tr '/' '-')
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "REPO_OWNER_LOWER=$REPO_OWNER_LOWER" >> $GITHUB_OUTPUT
echo "REPO_NAME=$REPO_NAME" >> $GITHUB_OUTPUT
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
fetch-latest-tags:
if: >
github.event_name == 'workflow_dispatch' ||
(github.event.pull_request.merged == true &&
startsWith(github.event.pull_request.title, 'chore(master): release '))
runs-on: ubuntu-latest
outputs:
PGAGENT_TAG: ${{ steps.fetch_pgagent_tag.outputs.PGAGENT_TAG }}
SYS_STATS_TAG: ${{ steps.fetch_sys_stats_tag.outputs.SYS_STATS_TAG }}
ZILEAN_TAG: ${{ steps.fetch_zilean_tag.outputs.ZILEAN_TAG }}
DUMB_FRONTEND_TAG: ${{ steps.fetch_dumb_frontend_tag.outputs.DUMB_FRONTEND_TAG }}
PLEX_DEBRID_TAG: ${{ steps.fetch_plex_debrid_tag.outputs.PLEX_DEBRID_TAG }}
CLI_DEBRID_TAG: ${{ steps.fetch_cli_debrid_tag.outputs.CLI_DEBRID_TAG }}
steps:
- name: Fetch latest pgAgent release tag
id: fetch_pgagent_tag
run: |
PGAGENT_TAG=$(curl -s https://api.github.com/repos/pgadmin-org/pgagent/releases/latest | jq -r .tag_name)
echo "PGAGENT_TAG=$PGAGENT_TAG" >> $GITHUB_OUTPUT
- name: Fetch latest system_stats release tag
id: fetch_sys_stats_tag
run: |
SYS_STATS_TAG=$(curl -s https://api.github.com/repos/EnterpriseDB/system_stats/releases/latest | jq -r .tag_name)
echo "SYS_STATS_TAG=$SYS_STATS_TAG" >> $GITHUB_OUTPUT
- name: Fetch latest zilean release tag
id: fetch_zilean_tag
run: |
ZILEAN_TAG=$(curl -s https://api.github.com/repos/iPromKnight/zilean/releases/latest | jq -r .tag_name)
echo "ZILEAN_TAG=$ZILEAN_TAG" >> $GITHUB_OUTPUT
- name: Fetch latest dmbdb release tag
id: fetch_dumb_frontend_tag
run: |
DUMB_FRONTEND_TAG=$(curl -s https://api.github.com/repos/nicocapalbo/dmbdb/releases/latest | jq -r .tag_name)
echo "DUMB_FRONTEND_TAG=$DUMB_FRONTEND_TAG" >> $GITHUB_OUTPUT
- name: Fetch latest plex_debrid version from source
id: fetch_plex_debrid_tag
run: |
PLEX_DEBRID_TAG=$(curl -s https://raw.githubusercontent.com/elfhosted/plex_debrid/main/ui/ui_settings.py | \
grep '^version\s*=' | \
sed -E "s/.*=\s*\[\s*'([^']+)'.*/\1/")
echo "PLEX_DEBRID_TAG=$PLEX_DEBRID_TAG" >> $GITHUB_OUTPUT
- name: Fetch latest cli_debrid release tag
id: fetch_cli_debrid_tag
run: |
CLI_DEBRID_TAG=$(curl -s https://api.github.com/repos/godver3/cli_debrid/releases/latest | jq -r .tag_name)
echo "CLI_DEBRID_TAG=$CLI_DEBRID_TAG" >> $GITHUB_OUTPUT
build-base-arch:
needs: setup
strategy:
matrix: { arch: [ X64, ARM64 ] }
runs-on: [ "self-hosted", "Linux", "${{ matrix.arch }}" ]
env:
BRANCH_NAME: ${{ needs.setup.outputs.BRANCH_NAME }}
PLATFORM: ${{ matrix.arch == 'X64' && 'linux/amd64' || 'linux/arm64' }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build base stage (native ${{ env.PLATFORM }})
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
target: base
platforms: ${{ env.PLATFORM }}
cache-from: type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/base
cache-to: type=local,dest=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/base,mode=max
push: false
build-zilean-arch:
needs: [setup, fetch-latest-tags, build-base-arch]
strategy:
matrix: { arch: [ X64, ARM64 ] }
runs-on: [ "self-hosted", "Linux", "${{ matrix.arch }}" ]
env:
BRANCH_NAME: ${{ needs.setup.outputs.BRANCH_NAME }}
PLATFORM: ${{ matrix.arch == 'X64' && 'linux/amd64' || 'linux/arm64' }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build zilean-builder (native ${{ env.PLATFORM }})
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
target: zilean-builder
platforms: ${{ env.PLATFORM }}
build-args: ZILEAN_TAG=${{ needs.fetch-latest-tags.outputs.ZILEAN_TAG }}
cache-from: |
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/base
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/zilean
cache-to: type=local,dest=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/zilean,mode=max
push: false
build-pgadmin-arch:
needs: [setup, fetch-latest-tags, build-base-arch]
strategy:
matrix: { arch: [ X64, ARM64 ] }
runs-on: [ "self-hosted", "Linux", "${{ matrix.arch }}" ]
env:
BRANCH_NAME: ${{ needs.setup.outputs.BRANCH_NAME }}
PLATFORM: ${{ matrix.arch == 'X64' && 'linux/amd64' || 'linux/arm64' }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build pgadmin-builder (native ${{ env.PLATFORM }})
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
target: pgadmin-builder
platforms: ${{ env.PLATFORM }}
cache-from: |
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/base
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/pgadmin
cache-to: type=local,dest=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/pgadmin,mode=max
push: false
build-systemstats-arch:
needs: [setup, fetch-latest-tags, build-base-arch]
strategy:
matrix: { arch: [ X64, ARM64 ] }
runs-on: [ "self-hosted", "Linux", "${{ matrix.arch }}" ]
env:
BRANCH_NAME: ${{ needs.setup.outputs.BRANCH_NAME }}
PLATFORM: ${{ matrix.arch == 'X64' && 'linux/amd64' || 'linux/arm64' }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build systemstats-builder (native ${{ env.PLATFORM }})
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
target: systemstats-builder
platforms: ${{ env.PLATFORM }}
build-args: SYS_STATS_TAG=${{ needs.fetch-latest-tags.outputs.SYS_STATS_TAG }}
cache-from: |
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/base
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/systemstats
cache-to: type=local,dest=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/systemstats,mode=max
push: false
build-dumb-frontend-arch:
needs: [setup, fetch-latest-tags, build-base-arch]
strategy:
matrix: { arch: [ X64, ARM64 ] }
runs-on: [ "self-hosted", "Linux", "${{ matrix.arch }}" ]
env:
BRANCH_NAME: ${{ needs.setup.outputs.BRANCH_NAME }}
PLATFORM: ${{ matrix.arch == 'X64' && 'linux/amd64' || 'linux/arm64' }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build dumb-frontend-builder (native ${{ env.PLATFORM }})
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
target: dumb-frontend-builder
platforms: ${{ env.PLATFORM }}
build-args: DUMB_FRONTEND_TAG=${{ needs.fetch-latest-tags.outputs.DUMB_FRONTEND_TAG }}
cache-from: |
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/base
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/dumb-frontend
cache-to: type=local,dest=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/dumb-frontend,mode=max
push: false
build-plex-debrid-arch:
needs: [setup, fetch-latest-tags, build-base-arch]
strategy:
matrix: { arch: [ X64, ARM64 ] }
runs-on: [ "self-hosted", "Linux", "${{ matrix.arch }}" ]
env:
BRANCH_NAME: ${{ needs.setup.outputs.BRANCH_NAME }}
PLATFORM: ${{ matrix.arch == 'X64' && 'linux/amd64' || 'linux/arm64' }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build plex_debrid-builder (native ${{ env.PLATFORM }})
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
target: plex_debrid-builder
platforms: ${{ env.PLATFORM }}
build-args: PLEX_DEBRID_TAG=${{ needs.fetch-latest-tags.outputs.PLEX_DEBRID_TAG }}
cache-from: |
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/base
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/plex-debrid
cache-to: type=local,dest=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/plex-debrid,mode=max
push: false
build-cli-debrid-arch:
needs: [setup, fetch-latest-tags, build-base-arch]
strategy:
matrix: { arch: [ X64, ARM64 ] }
runs-on: [ "self-hosted", "Linux", "${{ matrix.arch }}" ]
env:
BRANCH_NAME: ${{ needs.setup.outputs.BRANCH_NAME }}
PLATFORM: ${{ matrix.arch == 'X64' && 'linux/amd64' || 'linux/arm64' }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build cli_debrid-builder (native ${{ env.PLATFORM }})
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
target: cli_debrid-builder
platforms: ${{ env.PLATFORM }}
build-args: CLI_DEBRID_TAG=${{ needs.fetch-latest-tags.outputs.CLI_DEBRID_TAG }}
cache-from: |
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/base
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/cli-debrid
cache-to: type=local,dest=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/cli-debrid,mode=max
push: false
build-requirements-arch:
needs: [setup, fetch-latest-tags, build-base-arch]
strategy:
matrix: { arch: [ X64, ARM64 ] }
runs-on: [ "self-hosted", "Linux", "${{ matrix.arch }}" ]
env:
BRANCH_NAME: ${{ needs.setup.outputs.BRANCH_NAME }}
PLATFORM: ${{ matrix.arch == 'X64' && 'linux/amd64' || 'linux/arm64' }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build requirements-builder (native ${{ env.PLATFORM }})
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
target: requirements-builder
platforms: ${{ env.PLATFORM }}
cache-from: |
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/base
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/requirements
cache-to: type=local,dest=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/requirements,mode=max
push: false
build-amd64-digest:
name: Build linux/amd64 → Docker Hub (by digest)
needs: [setup, fetch-latest-tags, build-base-arch, build-zilean-arch, build-pgadmin-arch, build-systemstats-arch, build-dumb-frontend-arch, build-plex-debrid-arch, build-cli-debrid-arch, build-requirements-arch]
runs-on: [ self-hosted, Linux, X64 ]
permissions: { contents: read, packages: write }
env:
REPO_NAME: ${{ needs.setup.outputs.REPO_NAME }}
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASS: ${{ secrets.DOCKER_PASSWORD }}
BRANCH_NAME: ${{ needs.setup.outputs.BRANCH_NAME }}
outputs:
digest: ${{ steps.push.outputs.digest }}
steps:
- uses: actions/checkout@v4
- name: Login (Docker Hub)
uses: docker/login-action@v3
with:
username: ${{ env.DOCKER_USER }}
password: ${{ env.DOCKER_PASS }}
- uses: docker/setup-buildx-action@v3
- name: Build & push by digest (amd64) with cache
id: push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
target: final-stage
platforms: linux/amd64
push: true
outputs: type=registry,name=docker.io/${{ env.DOCKER_USER }}/${{ env.REPO_NAME }},push-by-digest=true,oci-mediatypes=true,compression=zstd,force-compression=true
provenance: true
sbom: true
cache-from: |
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/base
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/final
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/zilean
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/pgadmin
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/systemstats
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/dumb-frontend
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/plex-debrid
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/cli-debrid
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/requirements
cache-to: type=local,dest=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/final,mode=max
build-args: |
PGAGENT_TAG=${{ needs.fetch-latest-tags.outputs.PGAGENT_TAG }}
SYS_STATS_TAG=${{ needs.fetch-latest-tags.outputs.SYS_STATS_TAG }}
ZILEAN_TAG=${{ needs.fetch-latest-tags.outputs.ZILEAN_TAG }}
DUMB_FRONTEND_TAG=${{ needs.fetch-latest-tags.outputs.DUMB_FRONTEND_TAG }}
PLEX_DEBRID_TAG=${{ needs.fetch-latest-tags.outputs.PLEX_DEBRID_TAG }}
CLI_DEBRID_TAG=${{ needs.fetch-latest-tags.outputs.CLI_DEBRID_TAG }}
build-arm64-digest:
name: Build linux/arm64 → Docker Hub (by digest)
needs: [setup, fetch-latest-tags, build-base-arch, build-zilean-arch, build-pgadmin-arch, build-systemstats-arch, build-dumb-frontend-arch, build-plex-debrid-arch, build-cli-debrid-arch, build-requirements-arch]
runs-on: [ self-hosted, Linux, ARM64 ]
permissions: { contents: read, packages: write }
env:
REPO_NAME: ${{ needs.setup.outputs.REPO_NAME }}
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASS: ${{ secrets.DOCKER_PASSWORD }}
BRANCH_NAME: ${{ needs.setup.outputs.BRANCH_NAME }}
outputs:
digest: ${{ steps.push.outputs.digest }}
steps:
- uses: actions/checkout@v4
- name: Login (Docker Hub)
uses: docker/login-action@v3
with:
username: ${{ env.DOCKER_USER }}
password: ${{ env.DOCKER_PASS }}
- uses: docker/setup-buildx-action@v3
- name: Build & push by digest (arm64) with cache
id: push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
target: final-stage
platforms: linux/arm64
push: true
outputs: type=registry,name=docker.io/${{ env.DOCKER_USER }}/${{ env.REPO_NAME }},push-by-digest=true,oci-mediatypes=true,compression=zstd,force-compression=true
provenance: true
sbom: true
cache-from: |
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/base
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/final
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/zilean
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/pgadmin
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/systemstats
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/dumb-frontend
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/plex-debrid
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/cli-debrid
type=local,src=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/requirements
cache-to: type=local,dest=/home/docker/buildx-cache/${{ github.repository }}/${{ env.BRANCH_NAME }}/final,mode=max
build-args: |
PGAGENT_TAG=${{ needs.fetch-latest-tags.outputs.PGAGENT_TAG }}
SYS_STATS_TAG=${{ needs.fetch-latest-tags.outputs.SYS_STATS_TAG }}
ZILEAN_TAG=${{ needs.fetch-latest-tags.outputs.ZILEAN_TAG }}
DUMB_FRONTEND_TAG=${{ needs.fetch-latest-tags.outputs.DUMB_FRONTEND_TAG }}
PLEX_DEBRID_TAG=${{ needs.fetch-latest-tags.outputs.PLEX_DEBRID_TAG }}
CLI_DEBRID_TAG=${{ needs.fetch-latest-tags.outputs.CLI_DEBRID_TAG }}
build-and-push:
needs: [setup, fetch-latest-tags, build-amd64-digest, build-arm64-digest]
runs-on: ubuntu-latest
env:
REPO_NAME: ${{ needs.setup.outputs.REPO_NAME }}
REPO_OWNER_LOWER: ${{ needs.setup.outputs.REPO_OWNER_LOWER }}
VERSION: ${{ needs.setup.outputs.VERSION }}
BRANCH_NAME: ${{ needs.setup.outputs.BRANCH_NAME }}
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASS: ${{ secrets.DOCKER_PASSWORD }}
steps:
- uses: actions/checkout@v4
- name: Determine Docker tags
id: determine_tags
run: |
if [ "${{ github.ref_name }}" = "master" ]; then
echo "DH_VERSION=docker.io/${{ env.DOCKER_USER }}/${{ env.REPO_NAME }}:${{ env.VERSION }}" >> $GITHUB_ENV
echo "DH_LATEST=docker.io/${{ env.DOCKER_USER }}/${{ env.REPO_NAME }}:latest" >> $GITHUB_ENV
echo "GH_VERSION=ghcr.io/${{ env.REPO_OWNER_LOWER }}/${{ env.REPO_NAME }}:${{ env.VERSION }}" >> $GITHUB_ENV
echo "GH_LATEST=ghcr.io/${{ env.REPO_OWNER_LOWER }}/${{ env.REPO_NAME }}:latest" >> $GITHUB_ENV
else
echo "DH_VERSION=docker.io/${{ env.DOCKER_USER }}/${{ env.REPO_NAME }}:${{ env.BRANCH_NAME }}" >> $GITHUB_ENV
echo "DH_LATEST=" >> $GITHUB_ENV
echo "GH_VERSION=ghcr.io/${{ env.REPO_OWNER_LOWER }}/${{ env.REPO_NAME }}:${{ env.BRANCH_NAME }}" >> $GITHUB_ENV
echo "GH_LATEST=" >> $GITHUB_ENV
fi
- name: Login (Docker Hub)
uses: docker/login-action@v3
with:
username: ${{ env.DOCKER_USER }}
password: ${{ env.DOCKER_PASS }}
- uses: docker/setup-buildx-action@v3
- name: Create multi-arch manifest on Docker Hub
run: |
AMD="docker.io/${{ env.DOCKER_USER }}/${{ env.REPO_NAME }}@${{ needs.build-amd64-digest.outputs.digest }}"
ARM="docker.io/${{ env.DOCKER_USER }}/${{ env.REPO_NAME }}@${{ needs.build-arm64-digest.outputs.digest }}"
# Version tag
docker buildx imagetools create \
--tag "$DH_VERSION" \
"$AMD" "$ARM"
# Optional latest on master
if [ -n "$DH_LATEST" ]; then
docker buildx imagetools create \
--tag "$DH_LATEST" \
"$AMD" "$ARM"
fi
- name: Login (GHCR)
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Mirror tags to GHCR (from Docker Hub manifest)
run: |
docker buildx imagetools create --tag "$GH_VERSION" "$DH_VERSION"
if [ -n "$GH_LATEST" ]; then
docker buildx imagetools create --tag "$GH_LATEST" "$DH_LATEST"
fi
- name: Select tag for IMAGE_TAG (for inspection)
run: |
echo "IMAGE_TAG=$DH_VERSION" >> "$GITHUB_ENV"
- name: Pull image for version extraction
run: |
docker pull --platform=linux/amd64 "$IMAGE_TAG"
docker tag "$IMAGE_TAG" temp-local-image
- name: Extract Versions from Image
env:
IMAGE_TAG: temp-local-image
run: |
CONTAINER_ID=$(docker run -d --rm --entrypoint /bin/sh $IMAGE_TAG -c "sleep 60")
PSQL_VERSION=$(docker exec $CONTAINER_ID psql --version | awk '{print $3}')
PGADMIN_VERSION=$(docker exec $CONTAINER_ID /pgadmin/venv/bin/python -c "import importlib.metadata; print(importlib.metadata.version('pgadmin4'))" 2>/dev/null || echo "Not Installed")
NODE_VERSION=$(docker exec $CONTAINER_ID node -v 2>/dev/null || echo "Not Installed")
PNPM_VERSION=$(docker exec $CONTAINER_ID pnpm -v 2>/dev/null || echo "Not Installed")
RCLONE_VERSION=$(docker exec $CONTAINER_ID rclone version | head -n 1 | grep -oP 'v[0-9]+\.[0-9]+\.[0-9]+' 2>/dev/null || echo "Not Installed")
echo "PSQL_VERSION=$PSQL_VERSION" >> $GITHUB_ENV
echo "PGADMIN_VERSION=$PGADMIN_VERSION" >> $GITHUB_ENV
echo "NODE_VERSION=$NODE_VERSION" >> $GITHUB_ENV
echo "PNPM_VERSION=$PNPM_VERSION" >> $GITHUB_ENV
echo "RCLONE_VERSION=$RCLONE_VERSION" >> $GITHUB_ENV
docker stop $CONTAINER_ID
docker rmi -f temp-local-image
docker image prune -f
- name: Add Job Summary for Build
env:
DOCKER_TAGS_ALL: ${{ env.DH_VERSION }}${{ env.DH_LATEST && ', ' || '' }}${{ env.DH_LATEST }}${{ env.GH_VERSION && ', ' || '' }}${{ env.GH_VERSION }}${{ env.GH_LATEST && ', ' || '' }}${{ env.GH_LATEST }}
VERSION: ${{ env.VERSION }}
run: |
echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
echo "**Build Version:** \`${{ env.VERSION }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Git Branch:** \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Docker Tags:**" >> $GITHUB_STEP_SUMMARY
echo "\`${DOCKER_TAGS_ALL}\`" >> $GITHUB_STEP_SUMMARY
cleanup-cache:
name: Prune local buildx cache
needs: [ setup, build-amd64-digest, build-arm64-digest ]
if: always()
strategy:
matrix:
arch: [ X64, ARM64 ]
runs-on: [ "self-hosted", "Linux", "${{ matrix.arch }}" ]
steps:
- name: Ensure tools are present
shell: bash
run: |
if ! command -v jq >/dev/null 2>&1; then
sudo apt-get update -y
sudo apt-get install -y jq
fi
- name: Show cache size → prune → show again
shell: bash
env:
BRANCH_NAME: ${{ needs.setup.outputs.BRANCH_NAME }}
KEEP_MANIFESTS: "1"
MAX_DELETE_RATIO: "0.60"
DRY_RUN: "false"
run: |
set -Eeuo pipefail
shopt -s nullglob
set -x
trap '{
>&2 echo "❌ Failed at line $LINENO: $BASH_COMMAND";
if [[ -n "${STAGE_DIR:-}" ]]; then
>&2 echo "📂 Dump of $STAGE_DIR"
ls -la "${STAGE_DIR}" || true
fi
}' ERR
ROOT_BASE="${RUNNER_CACHE_ROOT:-/home/docker/buildx-cache}"
ROOT="${ROOT_BASE}/${GITHUB_REPOSITORY}/${BRANCH_NAME}"
LOCK="${ROOT_BASE}/.prune.lock"
echo "🖥️ Host: $(hostname) | Label: ${{ matrix.arch }}"
echo "📁 Cache root: $ROOT"
[[ -d "$ROOT" ]] || { echo "No cache dir for this branch — nothing to prune."; exit 0; }
echo "📊 Cache *before* prune:"
du -x -h --max-depth=1 "$ROOT" | sort -hr | head -n 20 || true
exec 9>"$LOCK"
flock -x 9
JQ_SLICE='def clamp0(x): if x < 0 then 0 else x end;
( .manifests // [] ) as $m
| ($m|length) as $L
| (if $L==0 then [] else $m[(clamp0($L-($n))):$L] end)
| .[]? | (.digest // empty) | sub("^sha256:";"")'
for STAGE_DIR in "$ROOT"/*/ ; do
[[ -f "${STAGE_DIR}index.json" ]] || continue
echo "::group::⏳ Pruning $(basename "$STAGE_DIR")"
BLOBS_DIR="${STAGE_DIR}blobs/sha256"
if [[ ! -d "$BLOBS_DIR" ]]; then
echo "ℹ️ No blobs dir — skip."
echo "::endgroup::"
continue
fi
KEEP="$(mktemp)"; REACH="$(mktemp)"
if ! mapfile -t MANIFESTS < <(jq -r --argjson n "${KEEP_MANIFESTS}" "$JQ_SLICE" "${STAGE_DIR}index.json"); then
echo "⚠️ jq failed parsing index.json — skip stage."
rm -f "$KEEP" "$REACH"
echo "::endgroup::"
continue
fi
if (( ${#MANIFESTS[@]} == 0 )); then
echo "⚠️ No manifests found — skip stage."
rm -f "$KEEP" "$REACH"
echo "::endgroup::"
continue
fi
printf "%s\n" "${MANIFESTS[@]}" > "$KEEP"
for M in "${MANIFESTS[@]}"; do
M_BLOB="${BLOBS_DIR}/${M}"
if [[ ! -f "$M_BLOB" ]]; then
echo "⚠️ Missing manifest blob: $M_BLOB"
continue
fi
jq -r '.. | .digest? | select(type=="string") | sub("^sha256:";"")' "$M_BLOB"
done | sort -u > "$REACH"
cat "$REACH" >> "$KEEP"
sort -u -o "$KEEP" "$KEEP"
grep -E '^[0-9a-f]{64}$' -x "$KEEP" > "${KEEP}.hex" || true
mv -f "${KEEP}.hex" "$KEEP"
if [[ ! -s "$KEEP" ]]; then
echo "⚠️ Empty KEEP set — skip stage."
rm -f "$KEEP" "$REACH"
echo "::endgroup::"
continue
fi
ALL_LIST="$(mktemp)"
find "$BLOBS_DIR" -type f -printf '%f\n' | sort -u > "$ALL_LIST"
if [[ ! -s "$ALL_LIST" ]]; then
echo "ℹ️ No blobs present."
rm -f "$KEEP" "$REACH" "$ALL_LIST"
echo "::endgroup::"
continue
fi
DEL_LIST="$(mktemp)"
comm -23 "$ALL_LIST" "$KEEP" > "$DEL_LIST" || true
TOTAL=$(wc -l < "$ALL_LIST" || echo 0)
TO_DEL=$(wc -l < "$DEL_LIST" || echo 0)
MAX_DELETE_RATIO="${MAX_DELETE_RATIO}"
RATIO=$(awk -v a="$TO_DEL" -v b="$TOTAL" 'BEGIN{ if(b==0) print 0; else printf "%.2f", a/b }')
awk -v r="$RATIO" -v m="$MAX_DELETE_RATIO" 'BEGIN{ exit !(r>m) }' && {
echo "⚠️ Delete ratio $RATIO exceeds max $MAX_DELETE_RATIO — skipping to avoid wipe."
rm -f "$KEEP" "$REACH" "$ALL_LIST" "$DEL_LIST"
echo "::endgroup::"
continue
}
TOTAL_BEFORE=$(du -sBK "$STAGE_DIR" | awk '{print $1}')
if [[ "${DRY_RUN}" == "true" ]]; then
echo "🔎 DRY-RUN: would delete $TO_DEL / $TOTAL blobs (ratio $RATIO)."
else
while IFS= read -r f; do
[[ -n "$f" ]] || continue
rm -f -- "$BLOBS_DIR/$f" 2>/dev/null || true
done < "$DEL_LIST"
fi
rm -f "$KEEP" "$REACH" "$ALL_LIST" "$DEL_LIST"
TOTAL_AFTER=$(du -sBK "$STAGE_DIR" | awk '{print $1}')
echo "🧹 $(basename "$STAGE_DIR"): ${TOTAL_BEFORE} → ${TOTAL_AFTER}"
echo "::endgroup::"
done
echo "📊 Cache *after* prune:"
du -x -h --max-depth=1 "$ROOT" | sort -hr | head -n 20 || true
flock -u 9 || true
release:
needs: [setup, build-and-push]
if: github.ref_name == 'master'
runs-on: ubuntu-latest
outputs:
release_exists: ${{ steps.check_release.outputs.release_exists }}
env:
VERSION: ${{ needs.setup.outputs.VERSION }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check if Release Exists
id: check_release
run: |
if gh release view "${{ env.VERSION }}" --repo ${{ github.repository }}; then
echo "Release already exists for version ${{ env.VERSION }}"
echo "release_exists=true" >> $GITHUB_ENV
echo "release_exists=true" >> $GITHUB_OUTPUT
else
echo "Release does not exist for version ${{ env.VERSION }}"
echo "release_exists=false" >> $GITHUB_ENV
echo "release_exists=false" >> $GITHUB_OUTPUT
fi
- name: Create Release with CHANGELOG Notes
if: steps.check_release.outputs.release_exists == 'false'
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
RELEASE_NOTES=$(sed -n '/^## \[[0-9]\+\.[0-9]\+\.[0-9]\+\](/,$p' CHANGELOG.md | sed -n '1!{/^## \[/q;p}')
gh release create ${{ env.VERSION }} \
--repo ${{ github.repository }} \
--title "Release ${{ env.VERSION }}" \
--notes "$RELEASE_NOTES" \
--draft=false \
--prerelease=false
- name: Add Job Summary for Release
run: |
echo "## Release Summary" >> $GITHUB_STEP_SUMMARY
echo "**Release Version:** \`${{ env.VERSION }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Release Status:** ${{ steps.check_release.outputs.release_exists == 'false' && '✅ Created' || '⚠️ Skipped (Already Exists)' }}" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.check_release.outputs.release_exists }}" == "false" ]; then
echo "**Release Notes:**" >> $GITHUB_STEP_SUMMARY
sed -n '/^## \[[0-9]\+\.[0-9]\+\.[0-9]\+\](/,$p' CHANGELOG.md | sed -n '1!{/^## \[/q;p}' >> $GITHUB_STEP_SUMMARY
fi
announce:
needs: [release, build-and-push, setup]
if: needs.release.outputs.release_exists == 'false' && github.ref_name == 'master'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Post announcement to Discord
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
VERSION: ${{ needs.setup.outputs.VERSION }}
run: |
RELEASE_NOTES=$(sed -n '/^## \[[0-9]\+\.[0-9]\+\.[0-9]\+\](/,$p' CHANGELOG.md | sed -n '1!{/^## \[/q;p}')
ANNOUNCEMENT_BODY="<@&1360241608649605240> 🚀 **New Release: Version [${{ env.VERSION }}]**${RELEASE_NOTES}"
ESCAPED_BODY=$(echo "$ANNOUNCEMENT_BODY" | jq -Rsa .)
curl -H "Content-Type: application/json" \
-d "{\"content\": $ESCAPED_BODY, \"flags\": 4}" \
$DISCORD_WEBHOOK_URL
update-pr-label:
needs: release
if: needs.release.outputs.release_exists == 'false' && github.ref_name == 'master'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: "Remove 'autorelease: pending' label from all merged PRs"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBERS=$(gh pr list --state merged --base master --json number,labels --jq '[.[] | select(.labels[].name == "autorelease: pending") | .number] | @sh')
if [[ -n "$PR_NUMBERS" ]]; then
for PR_NUMBER in $PR_NUMBERS; do
PR_NUMBER=$(echo $PR_NUMBER | tr -d "'") # Remove quotes from jq output
echo "Updating PR #$PR_NUMBER..."
gh pr edit $PR_NUMBER --remove-label "autorelease: pending"
gh pr edit $PR_NUMBER --add-label "autorelease: tagged"
echo "Updated PR #$PR_NUMBER with 'autorelease: tagged'"
done
else
echo "No merged PRs found with 'autorelease: pending' label."
fi