From a8fef6ed69a3715168c0135be79f4a2dfd1e4434 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 7 Jan 2026 09:58:49 +0100 Subject: [PATCH 01/32] ci: swith to matrix build + update cache management to handle both platforms cache --- .github/workflows/glpi-nightly.yml | 138 +++++++++++++++++++---- .github/workflows/glpi.yml | 175 ++++++++++++++++++++++------- 2 files changed, 251 insertions(+), 62 deletions(-) diff --git a/.github/workflows/glpi-nightly.yml b/.github/workflows/glpi-nightly.yml index 4d946e9..5d94bec 100644 --- a/.github/workflows/glpi-nightly.yml +++ b/.github/workflows/glpi-nightly.yml @@ -1,5 +1,9 @@ name: "GLPI nightly images" +env: + DOCKERHUB_IMAGE: glpi/glpi + GHCR_IMAGE: ghcr.io/glpi-project/glpi + on: push: branches: @@ -17,38 +21,65 @@ on: workflow_dispatch: jobs: - build: - name: "Build GLPI ${{ matrix.branch }}" + prepare: + name: "Prepare build matrix" runs-on: "ubuntu-latest" + outputs: + branches: ${{ steps.set-matrix.outputs.branches }} + steps: + - name: "Set matrix" + id: "set-matrix" + run: | + echo 'branches<> $GITHUB_OUTPUT + echo '[ + {"name": "main", "tag": "dev-nightly"}, + {"name": "11.0/bugfixes", "tag": "11.0-nightly"}, + {"name": "10.0/bugfixes", "tag": "10.0-nightly"} + ]' >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT + + build: + name: "Build GLPI ${{ matrix.branch.name }} (${{ matrix.platform.suffix }})" + runs-on: "${{ matrix.platform.runner }}" + needs: [prepare] strategy: fail-fast: false matrix: - include: - - {branch: "main", tag: "dev-nightly"} - - {branch: "11.0/bugfixes", tag: "11.0-nightly"} - - {branch: "10.0/bugfixes", tag: "10.0-nightly"} + branch: ${{ fromJson(needs.prepare.outputs.branches) }} + platform: + - {arch: "linux/amd64", runner: "ubuntu-latest", suffix: "amd64"} + - {arch: "linux/arm64", runner: "ubuntu-24.04-arm", suffix: "arm64"} steps: - - name: "Set variables" - id: "variables" + - name: "Prepare" run: | - DESTINATIONS="type=image" - if [[ "${{ github.repository }}" = 'glpi-project/docker-images' && ( "${{ github.event_name }}" = "workflow_dispatch" || "${{ github.ref }}" = 'refs/heads/main' ) ]]; then - DESTINATIONS="type=registry" - fi - echo "destinations=$DESTINATIONS" >> $GITHUB_OUTPUT - echo "tags=glpi/glpi:${{ matrix.tag }},ghcr.io/glpi-project/glpi:${{ matrix.tag }}" >> $GITHUB_OUTPUT + platform=${{ matrix.platform.arch }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV # compute the marketplace dir MARKETPLACE_DIR="/var/glpi/marketplace" - if [[ "${{ matrix.branch }}" = "10.0/bugfixes" ]]; then + if [[ "${{ matrix.branch.name }}" = "10.0/bugfixes" ]]; then MARKETPLACE_DIR="/var/www/glpi/marketplace" fi - echo "marketplace_dir=$MARKETPLACE_DIR" >> $GITHUB_OUTPUT + echo "MARKETPLACE_DIR=$MARKETPLACE_DIR" >> $GITHUB_ENV + + # Determine if we should push to registry + PUSH_TO_REGISTRY="false" + if [[ "${{ github.repository }}" = 'glpi-project/docker-images' && ( "${{ github.event_name }}" = "workflow_dispatch" || "${{ github.ref }}" = 'refs/heads/main' ) ]]; then + PUSH_TO_REGISTRY="true" + fi + echo "PUSH_TO_REGISTRY=$PUSH_TO_REGISTRY" >> $GITHUB_ENV + - name: "Docker meta" + id: "meta" + uses: "docker/metadata-action@v5" + with: + images: | + ${{ env.DOCKERHUB_IMAGE }} + ${{ env.GHCR_IMAGE }} - name: "Checkout" uses: "actions/checkout@v6" - name: "Get sources from glpi-project/glpi" run: | - curl https://github.com/glpi-project/glpi/archive/${{ matrix.branch }}.tar.gz --location --output glpi.tar.gz + curl https://github.com/glpi-project/glpi/archive/${{ matrix.branch.name }}.tar.gz --location --output glpi.tar.gz mkdir glpi/sources tar --extract --ungzip --strip 1 --file glpi.tar.gz --directory glpi/sources - name: "Set up QEMU" @@ -66,17 +97,76 @@ jobs: registry: "ghcr.io" username: "${{ secrets.GHCR_USERNAME }}" password: "${{ secrets.GHCR_ACCESS_TOKEN }}" - - name: "Build and push" + - name: "Build and push by digest" + id: "build" uses: "docker/build-push-action@v6" with: build-args: | BUILDER_IMAGE=php:8.5-cli-alpine APP_IMAGE=php:8.5-apache - GLPI_MARKETPLACE_DIR=${{ steps.variables.outputs.marketplace_dir }} - cache-from: "type=gha" - cache-to: "type=gha,mode=max" + GLPI_MARKETPLACE_DIR=${{ env.MARKETPLACE_DIR }} + cache-from: | + type=gha,scope=${{ matrix.branch.tag }}-${{ matrix.platform.suffix }} + type=gha,scope=main-${{ matrix.platform.suffix }} + cache-to: "type=gha,mode=max,scope=${{ matrix.branch.tag }}-${{ matrix.platform.suffix }}" context: "glpi" - outputs: "${{ steps.variables.outputs.destinations }}" - platforms: "linux/amd64,linux/arm64" + labels: "${{ steps.meta.outputs.labels }}" + platforms: "${{ matrix.platform.arch }}" pull: true - tags: "${{ steps.variables.outputs.tags }}" + outputs: "type=image,\"name=${{ env.DOCKERHUB_IMAGE }},${{ env.GHCR_IMAGE }}\",push-by-digest=true,name-canonical=true,push=${{ env.PUSH_TO_REGISTRY }}" + - name: "Export digest" + if: ${{ env.PUSH_TO_REGISTRY == 'true' }} + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + - name: "Upload digest" + if: ${{ env.PUSH_TO_REGISTRY == 'true' }} + uses: "actions/upload-artifact@v4" + with: + name: "digests-${{ matrix.branch.tag }}-${{ env.PLATFORM_PAIR }}" + path: "${{ runner.temp }}/digests/*" + if-no-files-found: "error" + retention-days: 1 + + merge-manifests: + name: "Create multi-arch manifest for ${{ matrix.branch.tag }}" + runs-on: "ubuntu-latest" + needs: [prepare, build] + if: ${{ github.repository == 'glpi-project/docker-images' && (github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main') }} + strategy: + fail-fast: false + matrix: + branch: ${{ fromJson(needs.prepare.outputs.branches) }} + steps: + - name: "Download digests" + uses: "actions/download-artifact@v4" + with: + path: "${{ runner.temp }}/digests" + pattern: "digests-${{ matrix.branch.tag }}-*" + merge-multiple: true + - name: "Set up Docker Buildx" + uses: "docker/setup-buildx-action@v3" + - name: "Login to DockerHub" + uses: "docker/login-action@v3" + with: + username: "${{ secrets.DOCKER_HUB_USERNAME }}" + password: "${{ secrets.DOCKER_HUB_TOKEN }}" + - name: "Login to Github container registry" + uses: "docker/login-action@v3" + with: + registry: "ghcr.io" + username: "${{ secrets.GHCR_USERNAME }}" + password: "${{ secrets.GHCR_ACCESS_TOKEN }}" + - name: "Create and push manifest" + working-directory: "${{ runner.temp }}/digests" + run: | + TAG="${{ matrix.branch.tag }}" + + docker buildx imagetools create \ + -t ${{ env.DOCKERHUB_IMAGE }}:$TAG \ + -t ${{ env.GHCR_IMAGE }}:$TAG \ + $(printf '${{ env.DOCKERHUB_IMAGE }}@sha256:%s ' *) + - name: "Inspect image" + run: | + docker buildx imagetools inspect ${{ env.DOCKERHUB_IMAGE }}:${{ matrix.branch.tag }} diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index 9984a01..c1679f5 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -1,5 +1,9 @@ name: "GLPI images for official releases" +env: + DOCKERHUB_IMAGE: glpi/glpi + GHCR_IMAGE: ghcr.io/glpi-project/glpi + on: # Enable execution from another workflow workflow_call: @@ -52,45 +56,50 @@ on: default: "8.5" jobs: - build: - name: "Build GLPI ${{ inputs.glpi-version }}" + prepare: + name: "Prepare build matrix" runs-on: "ubuntu-latest" + outputs: + platforms: ${{ steps.set-matrix.outputs.platforms }} steps: - - name: "Set variables" - id: "variables" + - name: "Set matrix" + id: "set-matrix" run: | - IMAGE_VERSION="$(echo '${{ inputs.glpi-version }}' | sed -E 's|/|-|')" - IMAGE_VERSION_MAJOR=$(echo "${{ inputs.glpi-version }}" | cut -d. -f1) - IMAGE_VERSION_MINOR=$(echo "${{ inputs.glpi-version }}" | cut -d. -f1-2) - - if [[ "${{ inputs.image-suffix }}" != '' ]]; then - IMAGE_VERSION="$IMAGE_VERSION-${{ inputs.image-suffix }}" - fi + echo 'platforms<> $GITHUB_OUTPUT + echo '[ + {"arch": "linux/amd64", "runner": "ubuntu-latest", "suffix": "amd64"}, + {"arch": "linux/arm64", "runner": "ubuntu-24.04-arm", "suffix": "arm64"} + ]' >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT - # prepare the tags to push - TAGS="glpi/glpi:$IMAGE_VERSION,ghcr.io/glpi-project/glpi:$IMAGE_VERSION" - - PRERELEASE_FLAG="$( echo "${{ inputs.glpi-version }}" | grep -Po '(\-\w+)?$' )" - if [[ -z "$PRERELEASE_FLAG" ]]; then - # populate major version tags, ex 10.0.18 -> 10 - TAGS="$TAGS,glpi/glpi:$IMAGE_VERSION_MAJOR,ghcr.io/glpi-project/glpi:$IMAGE_VERSION_MAJOR" - - # populate minor version tags, ex 10.0.18 -> 10.0 - TAGS="$TAGS,glpi/glpi:$IMAGE_VERSION_MINOR,ghcr.io/glpi-project/glpi:$IMAGE_VERSION_MINOR" - - # find if the current version is the latest - if [[ "${{ inputs.glpi-version }}" = "$(curl -s https://api.github.com/repos/glpi-project/glpi/releases/latest | jq -r .tag_name)" ]]; then - TAGS="$TAGS,glpi/glpi:latest,ghcr.io/glpi-project/glpi:latest" - fi - fi - echo "tags=$TAGS" >> $GITHUB_OUTPUT + build: + name: "Build GLPI ${{ inputs.glpi-version }} (${{ matrix.platform.suffix }})" + runs-on: "${{ matrix.platform.runner }}" + needs: [prepare] + strategy: + fail-fast: false + matrix: + platform: ${{ fromJson(needs.prepare.outputs.platforms) }} + steps: + - name: "Prepare" + run: | + platform=${{ matrix.platform.arch }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV # compute the marketplace dir + IMAGE_VERSION_MAJOR=$(echo "${{ inputs.glpi-version }}" | cut -d. -f1) MARKETPLACE_DIR="/var/glpi/marketplace" if [[ "$IMAGE_VERSION_MAJOR" = "10" ]]; then MARKETPLACE_DIR="/var/www/glpi/marketplace" fi - echo "marketplace_dir=$MARKETPLACE_DIR" >> $GITHUB_OUTPUT + echo "MARKETPLACE_DIR=$MARKETPLACE_DIR" >> $GITHUB_ENV + - name: "Docker meta" + id: "meta" + uses: "docker/metadata-action@v5" + with: + images: | + ${{ env.DOCKERHUB_IMAGE }} + ${{ env.GHCR_IMAGE }} - name: "Checkout" uses: "actions/checkout@v6" with: @@ -117,17 +126,107 @@ jobs: registry: "ghcr.io" username: "${{ secrets.GHCR_USERNAME }}" password: "${{ secrets.GHCR_ACCESS_TOKEN }}" - - name: "Build and push" + - name: "Build and push by digest" + id: "build" uses: "docker/build-push-action@v6" with: build-args: | - BUILDER_IMAGE=php:${{inputs.php-version}}-cli-alpine - APP_IMAGE=php:${{inputs.php-version}}-apache - GLPI_MARKETPLACE_DIR=${{ steps.variables.outputs.marketplace_dir }} - cache-from: "type=gha" - cache-to: "type=gha,mode=max" + BUILDER_IMAGE=php:${{ inputs.php-version }}-cli-alpine + APP_IMAGE=php:${{ inputs.php-version }}-apache + GLPI_MARKETPLACE_DIR=${{ env.MARKETPLACE_DIR }} + cache-from: | + type=gha,scope=${{ github.ref_name }}-${{ matrix.platform.suffix }} + type=gha,scope=main-${{ matrix.platform.suffix }} + cache-to: "type=gha,mode=max,scope=${{ github.ref_name }}-${{ matrix.platform.suffix }}" context: "glpi" - outputs: "type=registry" - platforms: "linux/amd64,linux/arm64" + labels: "${{ steps.meta.outputs.labels }}" + platforms: "${{ matrix.platform.arch }}" pull: true - tags: "${{ steps.variables.outputs.tags }}" + outputs: "type=image,\"name=${{ env.DOCKERHUB_IMAGE }},${{ env.GHCR_IMAGE }}\",push-by-digest=true,name-canonical=true,push=true" + - name: "Export digest" + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + - name: "Upload digest" + uses: "actions/upload-artifact@v4" + with: + name: "digests-${{ env.PLATFORM_PAIR }}" + path: "${{ runner.temp }}/digests/*" + if-no-files-found: "error" + retention-days: 1 + + merge-manifests: + name: "Create multi-arch manifest for GLPI ${{ inputs.glpi-version }}" + runs-on: "ubuntu-latest" + needs: [prepare, build] + steps: + - name: "Download digests" + uses: "actions/download-artifact@v4" + with: + path: "${{ runner.temp }}/digests" + pattern: "digests-*" + merge-multiple: true + - name: "Set up Docker Buildx" + uses: "docker/setup-buildx-action@v3" + - name: "Login to DockerHub" + uses: "docker/login-action@v3" + with: + username: "${{ secrets.DOCKER_HUB_USERNAME }}" + password: "${{ secrets.DOCKER_HUB_TOKEN }}" + - name: "Login to Github container registry" + uses: "docker/login-action@v3" + with: + registry: "ghcr.io" + username: "${{ secrets.GHCR_USERNAME }}" + password: "${{ secrets.GHCR_ACCESS_TOKEN }}" + - name: "Compute tags" + id: "tags" + run: | + IMAGE_VERSION="$(echo '${{ inputs.glpi-version }}' | sed -E 's|/|-|')" + IMAGE_VERSION_MAJOR=$(echo "${{ inputs.glpi-version }}" | cut -d. -f1) + IMAGE_VERSION_MINOR=$(echo "${{ inputs.glpi-version }}" | cut -d. -f1-2) + + if [[ "${{ inputs.image-suffix }}" != '' ]]; then + IMAGE_VERSION="$IMAGE_VERSION-${{ inputs.image-suffix }}" + fi + + # Start with the version tag + TAGS="${{ env.DOCKERHUB_IMAGE }}:$IMAGE_VERSION,${{ env.GHCR_IMAGE }}:$IMAGE_VERSION" + + PRERELEASE_FLAG="$( echo "${{ inputs.glpi-version }}" | grep -Po '(\-\w+)?$' )" + if [[ -z "$PRERELEASE_FLAG" ]]; then + # Major version tags (e.g., 11) + TAGS="$TAGS,${{ env.DOCKERHUB_IMAGE }}:$IMAGE_VERSION_MAJOR,${{ env.GHCR_IMAGE }}:$IMAGE_VERSION_MAJOR" + + # Minor version tags (e.g., 11.0) + TAGS="$TAGS,${{ env.DOCKERHUB_IMAGE }}:$IMAGE_VERSION_MINOR,${{ env.GHCR_IMAGE }}:$IMAGE_VERSION_MINOR" + + # Check if this is the latest release + LATEST_TAG="$(curl -s https://api.github.com/repos/glpi-project/glpi/releases/latest | jq -r .tag_name)" + if [[ "${{ inputs.glpi-version }}" = "$LATEST_TAG" ]]; then + TAGS="$TAGS,${{ env.DOCKERHUB_IMAGE }}:latest,${{ env.GHCR_IMAGE }}:latest" + fi + fi + + echo "tags=$TAGS" >> $GITHUB_OUTPUT + - name: "Create and push manifest" + working-directory: "${{ runner.temp }}/digests" + run: | + # Build the tag arguments + TAG_ARGS="" + IFS=',' read -ra TAG_ARRAY <<< "${{ steps.tags.outputs.tags }}" + for tag in "${TAG_ARRAY[@]}"; do + TAG_ARGS="$TAG_ARGS -t $tag" + done + + # Create manifest for DockerHub + docker buildx imagetools create $TAG_ARGS \ + $(printf '${{ env.DOCKERHUB_IMAGE }}@sha256:%s ' *) + - name: "Inspect image" + run: | + IMAGE_VERSION="$(echo '${{ inputs.glpi-version }}' | sed -E 's|/|-|')" + if [[ "${{ inputs.image-suffix }}" != '' ]]; then + IMAGE_VERSION="$IMAGE_VERSION-${{ inputs.image-suffix }}" + fi + docker buildx imagetools inspect ${{ env.DOCKERHUB_IMAGE }}:$IMAGE_VERSION From 59c55bc59a3f36efe6d2ad0e3f78812ca28357be Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 7 Jan 2026 13:48:23 +0100 Subject: [PATCH 02/32] ci: switch to action to reduce number of duplicated code --- .github/actions/build-glpi/action.yml | 145 ++++++++++++++++ .github/workflows/_glpi-build.yml | 184 ++++++++++++++++++++ .github/workflows/glpi-nightly.yml | 189 ++++----------------- .github/workflows/glpi.yml | 232 +++----------------------- 4 files changed, 389 insertions(+), 361 deletions(-) create mode 100644 .github/actions/build-glpi/action.yml create mode 100644 .github/workflows/_glpi-build.yml diff --git a/.github/actions/build-glpi/action.yml b/.github/actions/build-glpi/action.yml new file mode 100644 index 0000000..5ada34f --- /dev/null +++ b/.github/actions/build-glpi/action.yml @@ -0,0 +1,145 @@ +name: "Build GLPI Image" +description: "Builds a GLPI Docker image for a single platform and uploads the digest" + +inputs: + glpi-version: + description: "GLPI version or branch to build" + required: true + platform-arch: + description: "Platform architecture (i.e: linux/amd64)" + required: true + platform-suffix: + description: "Platform suffix for artifact naming (i.e: amd64)" + required: true + php-version: + description: "PHP version to use" + required: false + default: "8.5" + marketplace-dir: + description: "GLPI marketplace directory path" + required: false + default: "/var/glpi/marketplace" + cache-scope: + description: "Cache scope prefix for GHA cache" + required: true + artifact-prefix: + description: "Prefix for digest artifact name" + required: false + default: "digests" + push: + description: "Whether to push to registry" + required: false + default: "true" + post-extract-commands: + description: "Custom bash commands to run after source extraction (i.e: patches)" + required: false + default: "" + dockerhub-image: + description: "DockerHub image name" + required: false + default: "glpi/glpi" + ghcr-image: + description: "GHCR image name" + required: false + default: "ghcr.io/glpi-project/glpi" + dockerhub-username: + description: "DockerHub username" + required: true + dockerhub-token: + description: "DockerHub token" + required: true + ghcr-username: + description: "GHCR username" + required: true + ghcr-token: + description: "GHCR token" + required: true + +outputs: + digest: + description: "Image digest" + value: ${{ steps.build.outputs.digest }} + +runs: + using: "composite" + steps: + - name: "Prepare environment" + shell: bash + run: | + platform="${{ inputs.platform-arch }}" + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: "Docker meta" + id: "meta" + uses: "docker/metadata-action@v5" + with: + images: | + ${{ inputs.dockerhub-image }} + ${{ inputs.ghcr-image }} + + - name: "Get sources from glpi-project/glpi" + shell: bash + run: | + curl https://github.com/glpi-project/glpi/archive/${{ inputs.glpi-version }}.tar.gz --location --output glpi.tar.gz + mkdir -p glpi/sources + tar --extract --ungzip --strip 1 --file glpi.tar.gz --directory glpi/sources + + - name: "Run post-extract commands" + if: ${{ inputs.post-extract-commands != '' }} + shell: bash + run: | + ${{ inputs.post-extract-commands }} + + - name: "Set up QEMU" + uses: "docker/setup-qemu-action@v3" + + - name: "Set up Docker Buildx" + uses: "docker/setup-buildx-action@v3" + + - name: "Login to DockerHub" + uses: "docker/login-action@v3" + with: + username: "${{ inputs.dockerhub-username }}" + password: "${{ inputs.dockerhub-token }}" + + - name: "Login to Github container registry" + uses: "docker/login-action@v3" + with: + registry: "ghcr.io" + username: "${{ inputs.ghcr-username }}" + password: "${{ inputs.ghcr-token }}" + + - name: "Build and push by digest" + id: "build" + uses: "docker/build-push-action@v6" + with: + build-args: | + BUILDER_IMAGE=php:${{ inputs.php-version }}-cli-alpine + APP_IMAGE=php:${{ inputs.php-version }}-apache + GLPI_MARKETPLACE_DIR=${{ inputs.marketplace-dir }} + cache-from: | + type=gha,scope=${{ inputs.cache-scope }}-${{ inputs.platform-suffix }} + type=gha,scope=main-${{ inputs.platform-suffix }} + cache-to: "type=gha,mode=max,scope=${{ inputs.cache-scope }}-${{ inputs.platform-suffix }}" + context: "glpi" + labels: "${{ steps.meta.outputs.labels }}" + platforms: "${{ inputs.platform-arch }}" + pull: true + outputs: "type=image,\"name=${{ inputs.dockerhub-image }},${{ inputs.ghcr-image }}\",push-by-digest=true,name-canonical=true,push=${{ inputs.push }}" + + - name: "Export digest" + if: ${{ inputs.push == 'true' }} + shell: bash + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: "Upload digest" + if: ${{ inputs.push == 'true' }} + uses: "actions/upload-artifact@v4" + with: + name: "${{ inputs.artifact-prefix }}-${{ env.PLATFORM_PAIR }}" + path: "${{ runner.temp }}/digests/*" + if-no-files-found: "error" + retention-days: 1 diff --git a/.github/workflows/_glpi-build.yml b/.github/workflows/_glpi-build.yml new file mode 100644 index 0000000..7b91b57 --- /dev/null +++ b/.github/workflows/_glpi-build.yml @@ -0,0 +1,184 @@ +name: "GLPI Docker Image Build (Template)" + +# This is a reusable workflow template - never run directly +# Called by: glpi.yml, glpi-nightly.yml + +env: + DOCKERHUB_IMAGE: glpi/glpi + GHCR_IMAGE: ghcr.io/glpi-project/glpi + +on: + workflow_call: + inputs: + push: + description: "Whether to push images to registries" + required: false + type: boolean + default: false + post-extract-commands: + description: "Custom bash commands to run after source extraction (i.e: patches)" + required: false + type: string + default: "" + php-version: + required: false + type: string + default: "8.5" + glpi-version: + description: "GLPI version or branch to build" + required: true + type: string + image-tag: + description: "Override image tag. If not set, computed from version." + required: false + type: string + default: "" + image-suffix: + description: "Suffix to add to computed tag (ignored if image-tag is set)" + required: false + type: string + default: "" + secrets: + DOCKER_HUB_USERNAME: + required: true + DOCKER_HUB_TOKEN: + required: true + GHCR_USERNAME: + required: true + GHCR_ACCESS_TOKEN: + required: true + +jobs: + prepare: + name: "Prepare build matrix" + runs-on: "ubuntu-latest" + outputs: + platforms: ${{ steps.set-matrix.outputs.platforms }} + marketplace-dir: ${{ steps.set-vars.outputs.marketplace_dir }} + tags: ${{ steps.set-vars.outputs.tags }} + steps: + - name: "Set matrix" + id: "set-matrix" + run: | + echo 'platforms<> $GITHUB_OUTPUT + echo '[ + {"arch": "linux/amd64", "runner": "ubuntu-24.04", "suffix": "amd64"}, + {"arch": "linux/arm64", "runner": "ubuntu-24.04-arm", "suffix": "arm64"} + ]' >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT + + - name: "Set variables" + id: "set-vars" + run: | + # Compute marketplace dir + IMAGE_VERSION_MAJOR=$(echo "${{ inputs.glpi-version }}" | cut -d. -f1) + MARKETPLACE_DIR="/var/glpi/marketplace" + if [[ "$IMAGE_VERSION_MAJOR" = "10" ]]; then + MARKETPLACE_DIR="/var/www/glpi/marketplace" + fi + echo "marketplace_dir=$MARKETPLACE_DIR" >> $GITHUB_OUTPUT + + # Compute tags + if [[ "${{ inputs.image-tag }}" != '' ]]; then + # Use provided tag directly + TAGS="${{ env.DOCKERHUB_IMAGE }}:${{ inputs.image-tag }},${{ env.GHCR_IMAGE }}:${{ inputs.image-tag }}" + else + # Compute from version + IMAGE_VERSION="$(echo '${{ inputs.glpi-version }}' | sed -E 's|/|-|')" + IMAGE_VERSION_MAJOR=$(echo "${{ inputs.glpi-version }}" | cut -d. -f1) + IMAGE_VERSION_MINOR=$(echo "${{ inputs.glpi-version }}" | cut -d. -f1-2) + + if [[ "${{ inputs.image-suffix }}" != '' ]]; then + IMAGE_VERSION="$IMAGE_VERSION-${{ inputs.image-suffix }}" + fi + + TAGS="${{ env.DOCKERHUB_IMAGE }}:$IMAGE_VERSION,${{ env.GHCR_IMAGE }}:$IMAGE_VERSION" + + PRERELEASE_FLAG="$( echo "${{ inputs.glpi-version }}" | grep -Po '(\-\w+)?$' )" + if [[ -z "$PRERELEASE_FLAG" ]]; then + TAGS="$TAGS,${{ env.DOCKERHUB_IMAGE }}:$IMAGE_VERSION_MAJOR,${{ env.GHCR_IMAGE }}:$IMAGE_VERSION_MAJOR" + TAGS="$TAGS,${{ env.DOCKERHUB_IMAGE }}:$IMAGE_VERSION_MINOR,${{ env.GHCR_IMAGE }}:$IMAGE_VERSION_MINOR" + + LATEST_TAG="$(curl -s https://api.github.com/repos/glpi-project/glpi/releases/latest | jq -r .tag_name)" + if [[ "${{ inputs.glpi-version }}" = "$LATEST_TAG" ]]; then + TAGS="$TAGS,${{ env.DOCKERHUB_IMAGE }}:latest,${{ env.GHCR_IMAGE }}:latest" + fi + fi + fi + echo "tags=$TAGS" >> $GITHUB_OUTPUT + + build: + name: "Build GLPI ${{ inputs.glpi-version }} (${{ matrix.platform.suffix }})" + runs-on: "${{ matrix.platform.runner }}" + needs: [prepare] + strategy: + fail-fast: false + matrix: + platform: ${{ fromJson(needs.prepare.outputs.platforms) }} + steps: + - name: "Checkout" + uses: "actions/checkout@v6" + - name: "Build GLPI image" + uses: "./.github/actions/build-glpi" + with: + push: "${{ inputs.push }}" + artifact-prefix: "digests" + cache-scope: "${{ github.ref_name }}" + platform-arch: "${{ matrix.platform.arch }}" + platform-suffix: "${{ matrix.platform.suffix }}" + post-extract-commands: "${{ inputs.post-extract-commands }}" + php-version: "${{ inputs.php-version }}" + + glpi-version: "${{ inputs.glpi-version }}" + marketplace-dir: "${{ needs.prepare.outputs.marketplace-dir }}" + dockerhub-image: "${{ env.DOCKERHUB_IMAGE }}" + + ghcr-image: "${{ env.GHCR_IMAGE }}" + dockerhub-username: "${{ secrets.DOCKER_HUB_USERNAME }}" + dockerhub-token: "${{ secrets.DOCKER_HUB_TOKEN }}" + ghcr-username: "${{ secrets.GHCR_USERNAME }}" + ghcr-token: "${{ secrets.GHCR_ACCESS_TOKEN }}" + + merge-manifests: + name: "Create multi-arch manifest" + runs-on: "ubuntu-latest" + needs: [prepare, build] + if: ${{ inputs.push }} + steps: + - name: "Download digests" + uses: "actions/download-artifact@v4" + with: + path: "${{ runner.temp }}/digests" + pattern: "digests-*" + merge-multiple: true + - name: "Set up Docker Buildx" + uses: "docker/setup-buildx-action@v3" + - name: "Login to DockerHub" + uses: "docker/login-action@v3" + with: + username: "${{ secrets.DOCKER_HUB_USERNAME }}" + password: "${{ secrets.DOCKER_HUB_TOKEN }}" + - name: "Login to Github container registry" + uses: "docker/login-action@v3" + with: + registry: "ghcr.io" + username: "${{ secrets.GHCR_USERNAME }}" + password: "${{ secrets.GHCR_ACCESS_TOKEN }}" + - name: "Create and push manifest" + working-directory: "${{ runner.temp }}/digests" + run: | + # Build the tag arguments + TAG_ARGS="" + IFS=',' read -ra TAG_ARRAY <<< "${{ needs.prepare.outputs.tags }}" + for tag in "${TAG_ARRAY[@]}"; do + TAG_ARGS="$TAG_ARGS -t $tag" + done + + # Create manifest + docker buildx imagetools create $TAG_ARGS \ + $(printf '${{ env.DOCKERHUB_IMAGE }}@sha256:%s ' *) + - name: "Inspect image" + run: | + # Get first tag for inspection + FIRST_TAG=$(echo "${{ needs.prepare.outputs.tags }}" | cut -d',' -f1) + docker buildx imagetools inspect $FIRST_TAG diff --git a/.github/workflows/glpi-nightly.yml b/.github/workflows/glpi-nightly.yml index 5d94bec..c8862a0 100644 --- a/.github/workflows/glpi-nightly.yml +++ b/.github/workflows/glpi-nightly.yml @@ -1,172 +1,53 @@ name: "GLPI nightly images" -env: - DOCKERHUB_IMAGE: glpi/glpi - GHCR_IMAGE: ghcr.io/glpi-project/glpi - on: push: branches: - "main" paths: - ".github/workflows/glpi-nightly.yml" + - ".github/workflows/_glpi-build.yml" + - ".github/actions/**" - "glpi/**" pull_request: paths: - ".github/workflows/glpi-nightly.yml" + - ".github/workflows/_glpi-build.yml" + - ".github/actions/**" - "glpi/**" schedule: - - cron: '0 0 * * *' + - cron: '0 0 * * *' # Enable manual run workflow_dispatch: jobs: - prepare: - name: "Prepare build matrix" - runs-on: "ubuntu-latest" - outputs: - branches: ${{ steps.set-matrix.outputs.branches }} - steps: - - name: "Set matrix" - id: "set-matrix" - run: | - echo 'branches<> $GITHUB_OUTPUT - echo '[ - {"name": "main", "tag": "dev-nightly"}, - {"name": "11.0/bugfixes", "tag": "11.0-nightly"}, - {"name": "10.0/bugfixes", "tag": "10.0-nightly"} - ]' >> $GITHUB_OUTPUT - echo 'EOF' >> $GITHUB_OUTPUT - - build: - name: "Build GLPI ${{ matrix.branch.name }} (${{ matrix.platform.suffix }})" - runs-on: "${{ matrix.platform.runner }}" - needs: [prepare] - strategy: - fail-fast: false - matrix: - branch: ${{ fromJson(needs.prepare.outputs.branches) }} - platform: - - {arch: "linux/amd64", runner: "ubuntu-latest", suffix: "amd64"} - - {arch: "linux/arm64", runner: "ubuntu-24.04-arm", suffix: "arm64"} - steps: - - name: "Prepare" - run: | - platform=${{ matrix.platform.arch }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - # compute the marketplace dir - MARKETPLACE_DIR="/var/glpi/marketplace" - if [[ "${{ matrix.branch.name }}" = "10.0/bugfixes" ]]; then - MARKETPLACE_DIR="/var/www/glpi/marketplace" - fi - echo "MARKETPLACE_DIR=$MARKETPLACE_DIR" >> $GITHUB_ENV - - # Determine if we should push to registry - PUSH_TO_REGISTRY="false" - if [[ "${{ github.repository }}" = 'glpi-project/docker-images' && ( "${{ github.event_name }}" = "workflow_dispatch" || "${{ github.ref }}" = 'refs/heads/main' ) ]]; then - PUSH_TO_REGISTRY="true" - fi - echo "PUSH_TO_REGISTRY=$PUSH_TO_REGISTRY" >> $GITHUB_ENV - - name: "Docker meta" - id: "meta" - uses: "docker/metadata-action@v5" - with: - images: | - ${{ env.DOCKERHUB_IMAGE }} - ${{ env.GHCR_IMAGE }} - - name: "Checkout" - uses: "actions/checkout@v6" - - name: "Get sources from glpi-project/glpi" - run: | - curl https://github.com/glpi-project/glpi/archive/${{ matrix.branch.name }}.tar.gz --location --output glpi.tar.gz - mkdir glpi/sources - tar --extract --ungzip --strip 1 --file glpi.tar.gz --directory glpi/sources - - name: "Set up QEMU" - uses: "docker/setup-qemu-action@v3" - - name: "Set up Docker Buildx" - uses: "docker/setup-buildx-action@v3" - - name: "Login to DockerHub" - uses: "docker/login-action@v3" - with: - username: "${{ secrets.DOCKER_HUB_USERNAME }}" - password: "${{ secrets.DOCKER_HUB_TOKEN }}" - - name: "Login to Github container registry" - uses: "docker/login-action@v3" - with: - registry: "ghcr.io" - username: "${{ secrets.GHCR_USERNAME }}" - password: "${{ secrets.GHCR_ACCESS_TOKEN }}" - - name: "Build and push by digest" - id: "build" - uses: "docker/build-push-action@v6" - with: - build-args: | - BUILDER_IMAGE=php:8.5-cli-alpine - APP_IMAGE=php:8.5-apache - GLPI_MARKETPLACE_DIR=${{ env.MARKETPLACE_DIR }} - cache-from: | - type=gha,scope=${{ matrix.branch.tag }}-${{ matrix.platform.suffix }} - type=gha,scope=main-${{ matrix.platform.suffix }} - cache-to: "type=gha,mode=max,scope=${{ matrix.branch.tag }}-${{ matrix.platform.suffix }}" - context: "glpi" - labels: "${{ steps.meta.outputs.labels }}" - platforms: "${{ matrix.platform.arch }}" - pull: true - outputs: "type=image,\"name=${{ env.DOCKERHUB_IMAGE }},${{ env.GHCR_IMAGE }}\",push-by-digest=true,name-canonical=true,push=${{ env.PUSH_TO_REGISTRY }}" - - name: "Export digest" - if: ${{ env.PUSH_TO_REGISTRY == 'true' }} - run: | - mkdir -p ${{ runner.temp }}/digests - digest="${{ steps.build.outputs.digest }}" - touch "${{ runner.temp }}/digests/${digest#sha256:}" - - name: "Upload digest" - if: ${{ env.PUSH_TO_REGISTRY == 'true' }} - uses: "actions/upload-artifact@v4" - with: - name: "digests-${{ matrix.branch.tag }}-${{ env.PLATFORM_PAIR }}" - path: "${{ runner.temp }}/digests/*" - if-no-files-found: "error" - retention-days: 1 - - merge-manifests: - name: "Create multi-arch manifest for ${{ matrix.branch.tag }}" - runs-on: "ubuntu-latest" - needs: [prepare, build] - if: ${{ github.repository == 'glpi-project/docker-images' && (github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main') }} - strategy: - fail-fast: false - matrix: - branch: ${{ fromJson(needs.prepare.outputs.branches) }} - steps: - - name: "Download digests" - uses: "actions/download-artifact@v4" - with: - path: "${{ runner.temp }}/digests" - pattern: "digests-${{ matrix.branch.tag }}-*" - merge-multiple: true - - name: "Set up Docker Buildx" - uses: "docker/setup-buildx-action@v3" - - name: "Login to DockerHub" - uses: "docker/login-action@v3" - with: - username: "${{ secrets.DOCKER_HUB_USERNAME }}" - password: "${{ secrets.DOCKER_HUB_TOKEN }}" - - name: "Login to Github container registry" - uses: "docker/login-action@v3" - with: - registry: "ghcr.io" - username: "${{ secrets.GHCR_USERNAME }}" - password: "${{ secrets.GHCR_ACCESS_TOKEN }}" - - name: "Create and push manifest" - working-directory: "${{ runner.temp }}/digests" - run: | - TAG="${{ matrix.branch.tag }}" - - docker buildx imagetools create \ - -t ${{ env.DOCKERHUB_IMAGE }}:$TAG \ - -t ${{ env.GHCR_IMAGE }}:$TAG \ - $(printf '${{ env.DOCKERHUB_IMAGE }}@sha256:%s ' *) - - name: "Inspect image" - run: | - docker buildx imagetools inspect ${{ env.DOCKERHUB_IMAGE }}:${{ matrix.branch.tag }} + build-main: + name: "Build main branch" + if: ${{ github.repository == 'glpi-project/docker-images' }} + uses: ./.github/workflows/_glpi-build.yml + with: + # Only push on main branch, schedule, or manual dispatch (not on PR), not a if, so we can test build on PR + push: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || github.ref == 'refs/heads/main' }} + glpi-version: "main" + image-tag: "dev-nightly" + secrets: inherit + + build-11: + name: "Build 11.0/bugfixes branch" + if: ${{ github.repository == 'glpi-project/docker-images' }} + uses: ./.github/workflows/_glpi-build.yml + with: + push: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || github.ref == 'refs/heads/main' }} + glpi-version: "11.0/bugfixes" + image-tag: "11.0-nightly" + secrets: inherit + + build-10: + name: "Build 10.0/bugfixes branch" + if: ${{ github.repository == 'glpi-project/docker-images' }} + uses: ./.github/workflows/_glpi-build.yml + with: + push: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || github.ref == 'refs/heads/main' }} + glpi-version: "10.0/bugfixes" + image-tag: "10.0-nightly" + secrets: inherit diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index c1679f5..8c39abe 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -1,34 +1,6 @@ name: "GLPI images for official releases" -env: - DOCKERHUB_IMAGE: glpi/glpi - GHCR_IMAGE: ghcr.io/glpi-project/glpi - on: - # Enable execution from another workflow - workflow_call: - inputs: - glpi-version: - required: true - type: string - image-suffix: - required: false - type: string - default: "" - php-version: - required: false - type: string - default: "8.5" - secrets: - DOCKER_HUB_USERNAME: - required: true - DOCKER_HUB_TOKEN: - required: true - GHCR_USERNAME: - required: true - GHCR_ACCESS_TOKEN: - required: true - # Enable manual run # # It can be executed by a curl command: @@ -37,196 +9,42 @@ on: # -H "Accept: application/vnd.github.v3+json" \ # -H "Authorization: " \ # https://api.github.com/repos/glpi-project/docker-images/actions/workflows//dispatches \ - # -d '{"ref":"main", "inputs": { "glpi-version":"10.0.18" }}' + # -d '{"ref":"main", "inputs": { "glpi-version":"11.0.5", "push": true }}' workflow_dispatch: inputs: + push: + description: "Whether to push images to registries" + required: false + type: boolean + default: false + php-version: + description: "PHP version to use for the build" + required: false + type: string + default: "8.5" glpi-version: - description: "GLPI version to build, e.g. 10.0.18" + description: "GLPI version to build, i.e: 11.0.5" required: true type: string image-suffix: - description: "Suffix to add to the image name, e.g. 'nighlty'" + description: "Suffix to add to the image name, i.e: 'nightly'" required: false type: string default: "" - php-version: - description: "PHP version to use for the build" - required: true - type: string - default: "8.5" jobs: - prepare: - name: "Prepare build matrix" - runs-on: "ubuntu-latest" - outputs: - platforms: ${{ steps.set-matrix.outputs.platforms }} - steps: - - name: "Set matrix" - id: "set-matrix" - run: | - echo 'platforms<> $GITHUB_OUTPUT - echo '[ - {"arch": "linux/amd64", "runner": "ubuntu-latest", "suffix": "amd64"}, - {"arch": "linux/arm64", "runner": "ubuntu-24.04-arm", "suffix": "arm64"} - ]' >> $GITHUB_OUTPUT - echo 'EOF' >> $GITHUB_OUTPUT - build: - name: "Build GLPI ${{ inputs.glpi-version }} (${{ matrix.platform.suffix }})" - runs-on: "${{ matrix.platform.runner }}" - needs: [prepare] - strategy: - fail-fast: false - matrix: - platform: ${{ fromJson(needs.prepare.outputs.platforms) }} - steps: - - name: "Prepare" - run: | - platform=${{ matrix.platform.arch }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - # compute the marketplace dir - IMAGE_VERSION_MAJOR=$(echo "${{ inputs.glpi-version }}" | cut -d. -f1) - MARKETPLACE_DIR="/var/glpi/marketplace" - if [[ "$IMAGE_VERSION_MAJOR" = "10" ]]; then - MARKETPLACE_DIR="/var/www/glpi/marketplace" - fi - echo "MARKETPLACE_DIR=$MARKETPLACE_DIR" >> $GITHUB_ENV - - name: "Docker meta" - id: "meta" - uses: "docker/metadata-action@v5" - with: - images: | - ${{ env.DOCKERHUB_IMAGE }} - ${{ env.GHCR_IMAGE }} - - name: "Checkout" - uses: "actions/checkout@v6" - with: - repository: glpi-project/docker-images - - name: "Get sources from glpi-project/glpi" - run: | - curl https://github.com/glpi-project/glpi/archive/${{ inputs.glpi-version }}.tar.gz --location --output glpi.tar.gz - mkdir glpi/sources - tar --extract --ungzip --strip 1 --file glpi.tar.gz --directory glpi/sources + name: "Build GLPI ${{ inputs.glpi-version }}" + uses: ./.github/workflows/_glpi-build.yml + with: + push: ${{ inputs.push }} + php-version: ${{ inputs.php-version }} + glpi-version: ${{ inputs.glpi-version }} + image-suffix: ${{ inputs.image-suffix }} + # Apply patch for GLPI 11.0.1-11.0.4 (PR #22381 will be included in 11.0.5+) + post-extract-commands: | + if [[ "${{ inputs.glpi-version }}" =~ ^11\.0\.[0-4]$ ]]; then curl https://patch-diff.githubusercontent.com/raw/glpi-project/glpi/pull/22381.diff --location --output composer-audit-config.diff patch --force --directory=glpi/sources < composer-audit-config.diff - - name: "Set up QEMU" - uses: "docker/setup-qemu-action@v3" - - name: "Set up Docker Buildx" - uses: "docker/setup-buildx-action@v3" - - name: "Login to DockerHub" - uses: "docker/login-action@v3" - with: - username: "${{ secrets.DOCKER_HUB_USERNAME }}" - password: "${{ secrets.DOCKER_HUB_TOKEN }}" - - name: "Login to Github container registry" - uses: "docker/login-action@v3" - with: - registry: "ghcr.io" - username: "${{ secrets.GHCR_USERNAME }}" - password: "${{ secrets.GHCR_ACCESS_TOKEN }}" - - name: "Build and push by digest" - id: "build" - uses: "docker/build-push-action@v6" - with: - build-args: | - BUILDER_IMAGE=php:${{ inputs.php-version }}-cli-alpine - APP_IMAGE=php:${{ inputs.php-version }}-apache - GLPI_MARKETPLACE_DIR=${{ env.MARKETPLACE_DIR }} - cache-from: | - type=gha,scope=${{ github.ref_name }}-${{ matrix.platform.suffix }} - type=gha,scope=main-${{ matrix.platform.suffix }} - cache-to: "type=gha,mode=max,scope=${{ github.ref_name }}-${{ matrix.platform.suffix }}" - context: "glpi" - labels: "${{ steps.meta.outputs.labels }}" - platforms: "${{ matrix.platform.arch }}" - pull: true - outputs: "type=image,\"name=${{ env.DOCKERHUB_IMAGE }},${{ env.GHCR_IMAGE }}\",push-by-digest=true,name-canonical=true,push=true" - - name: "Export digest" - run: | - mkdir -p ${{ runner.temp }}/digests - digest="${{ steps.build.outputs.digest }}" - touch "${{ runner.temp }}/digests/${digest#sha256:}" - - name: "Upload digest" - uses: "actions/upload-artifact@v4" - with: - name: "digests-${{ env.PLATFORM_PAIR }}" - path: "${{ runner.temp }}/digests/*" - if-no-files-found: "error" - retention-days: 1 - - merge-manifests: - name: "Create multi-arch manifest for GLPI ${{ inputs.glpi-version }}" - runs-on: "ubuntu-latest" - needs: [prepare, build] - steps: - - name: "Download digests" - uses: "actions/download-artifact@v4" - with: - path: "${{ runner.temp }}/digests" - pattern: "digests-*" - merge-multiple: true - - name: "Set up Docker Buildx" - uses: "docker/setup-buildx-action@v3" - - name: "Login to DockerHub" - uses: "docker/login-action@v3" - with: - username: "${{ secrets.DOCKER_HUB_USERNAME }}" - password: "${{ secrets.DOCKER_HUB_TOKEN }}" - - name: "Login to Github container registry" - uses: "docker/login-action@v3" - with: - registry: "ghcr.io" - username: "${{ secrets.GHCR_USERNAME }}" - password: "${{ secrets.GHCR_ACCESS_TOKEN }}" - - name: "Compute tags" - id: "tags" - run: | - IMAGE_VERSION="$(echo '${{ inputs.glpi-version }}' | sed -E 's|/|-|')" - IMAGE_VERSION_MAJOR=$(echo "${{ inputs.glpi-version }}" | cut -d. -f1) - IMAGE_VERSION_MINOR=$(echo "${{ inputs.glpi-version }}" | cut -d. -f1-2) - - if [[ "${{ inputs.image-suffix }}" != '' ]]; then - IMAGE_VERSION="$IMAGE_VERSION-${{ inputs.image-suffix }}" - fi - - # Start with the version tag - TAGS="${{ env.DOCKERHUB_IMAGE }}:$IMAGE_VERSION,${{ env.GHCR_IMAGE }}:$IMAGE_VERSION" - - PRERELEASE_FLAG="$( echo "${{ inputs.glpi-version }}" | grep -Po '(\-\w+)?$' )" - if [[ -z "$PRERELEASE_FLAG" ]]; then - # Major version tags (e.g., 11) - TAGS="$TAGS,${{ env.DOCKERHUB_IMAGE }}:$IMAGE_VERSION_MAJOR,${{ env.GHCR_IMAGE }}:$IMAGE_VERSION_MAJOR" - - # Minor version tags (e.g., 11.0) - TAGS="$TAGS,${{ env.DOCKERHUB_IMAGE }}:$IMAGE_VERSION_MINOR,${{ env.GHCR_IMAGE }}:$IMAGE_VERSION_MINOR" - - # Check if this is the latest release - LATEST_TAG="$(curl -s https://api.github.com/repos/glpi-project/glpi/releases/latest | jq -r .tag_name)" - if [[ "${{ inputs.glpi-version }}" = "$LATEST_TAG" ]]; then - TAGS="$TAGS,${{ env.DOCKERHUB_IMAGE }}:latest,${{ env.GHCR_IMAGE }}:latest" - fi - fi - - echo "tags=$TAGS" >> $GITHUB_OUTPUT - - name: "Create and push manifest" - working-directory: "${{ runner.temp }}/digests" - run: | - # Build the tag arguments - TAG_ARGS="" - IFS=',' read -ra TAG_ARRAY <<< "${{ steps.tags.outputs.tags }}" - for tag in "${TAG_ARRAY[@]}"; do - TAG_ARGS="$TAG_ARGS -t $tag" - done - - # Create manifest for DockerHub - docker buildx imagetools create $TAG_ARGS \ - $(printf '${{ env.DOCKERHUB_IMAGE }}@sha256:%s ' *) - - name: "Inspect image" - run: | - IMAGE_VERSION="$(echo '${{ inputs.glpi-version }}' | sed -E 's|/|-|')" - if [[ "${{ inputs.image-suffix }}" != '' ]]; then - IMAGE_VERSION="$IMAGE_VERSION-${{ inputs.image-suffix }}" - fi - docker buildx imagetools inspect ${{ env.DOCKERHUB_IMAGE }}:$IMAGE_VERSION + fi + secrets: inherit From 15e68fcff608c07d8e2a7db0ef7c9710519ccfcd Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 7 Jan 2026 14:44:46 +0100 Subject: [PATCH 03/32] ci: remove action not needed anymore --- .github/actions/build-glpi/action.yml | 145 -------------------------- .github/workflows/_glpi-build.yml | 101 ++++++++++++++---- .github/workflows/glpi-nightly.yml | 2 - .github/workflows/glpi.yml | 2 +- 4 files changed, 80 insertions(+), 170 deletions(-) delete mode 100644 .github/actions/build-glpi/action.yml diff --git a/.github/actions/build-glpi/action.yml b/.github/actions/build-glpi/action.yml deleted file mode 100644 index 5ada34f..0000000 --- a/.github/actions/build-glpi/action.yml +++ /dev/null @@ -1,145 +0,0 @@ -name: "Build GLPI Image" -description: "Builds a GLPI Docker image for a single platform and uploads the digest" - -inputs: - glpi-version: - description: "GLPI version or branch to build" - required: true - platform-arch: - description: "Platform architecture (i.e: linux/amd64)" - required: true - platform-suffix: - description: "Platform suffix for artifact naming (i.e: amd64)" - required: true - php-version: - description: "PHP version to use" - required: false - default: "8.5" - marketplace-dir: - description: "GLPI marketplace directory path" - required: false - default: "/var/glpi/marketplace" - cache-scope: - description: "Cache scope prefix for GHA cache" - required: true - artifact-prefix: - description: "Prefix for digest artifact name" - required: false - default: "digests" - push: - description: "Whether to push to registry" - required: false - default: "true" - post-extract-commands: - description: "Custom bash commands to run after source extraction (i.e: patches)" - required: false - default: "" - dockerhub-image: - description: "DockerHub image name" - required: false - default: "glpi/glpi" - ghcr-image: - description: "GHCR image name" - required: false - default: "ghcr.io/glpi-project/glpi" - dockerhub-username: - description: "DockerHub username" - required: true - dockerhub-token: - description: "DockerHub token" - required: true - ghcr-username: - description: "GHCR username" - required: true - ghcr-token: - description: "GHCR token" - required: true - -outputs: - digest: - description: "Image digest" - value: ${{ steps.build.outputs.digest }} - -runs: - using: "composite" - steps: - - name: "Prepare environment" - shell: bash - run: | - platform="${{ inputs.platform-arch }}" - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - - name: "Docker meta" - id: "meta" - uses: "docker/metadata-action@v5" - with: - images: | - ${{ inputs.dockerhub-image }} - ${{ inputs.ghcr-image }} - - - name: "Get sources from glpi-project/glpi" - shell: bash - run: | - curl https://github.com/glpi-project/glpi/archive/${{ inputs.glpi-version }}.tar.gz --location --output glpi.tar.gz - mkdir -p glpi/sources - tar --extract --ungzip --strip 1 --file glpi.tar.gz --directory glpi/sources - - - name: "Run post-extract commands" - if: ${{ inputs.post-extract-commands != '' }} - shell: bash - run: | - ${{ inputs.post-extract-commands }} - - - name: "Set up QEMU" - uses: "docker/setup-qemu-action@v3" - - - name: "Set up Docker Buildx" - uses: "docker/setup-buildx-action@v3" - - - name: "Login to DockerHub" - uses: "docker/login-action@v3" - with: - username: "${{ inputs.dockerhub-username }}" - password: "${{ inputs.dockerhub-token }}" - - - name: "Login to Github container registry" - uses: "docker/login-action@v3" - with: - registry: "ghcr.io" - username: "${{ inputs.ghcr-username }}" - password: "${{ inputs.ghcr-token }}" - - - name: "Build and push by digest" - id: "build" - uses: "docker/build-push-action@v6" - with: - build-args: | - BUILDER_IMAGE=php:${{ inputs.php-version }}-cli-alpine - APP_IMAGE=php:${{ inputs.php-version }}-apache - GLPI_MARKETPLACE_DIR=${{ inputs.marketplace-dir }} - cache-from: | - type=gha,scope=${{ inputs.cache-scope }}-${{ inputs.platform-suffix }} - type=gha,scope=main-${{ inputs.platform-suffix }} - cache-to: "type=gha,mode=max,scope=${{ inputs.cache-scope }}-${{ inputs.platform-suffix }}" - context: "glpi" - labels: "${{ steps.meta.outputs.labels }}" - platforms: "${{ inputs.platform-arch }}" - pull: true - outputs: "type=image,\"name=${{ inputs.dockerhub-image }},${{ inputs.ghcr-image }}\",push-by-digest=true,name-canonical=true,push=${{ inputs.push }}" - - - name: "Export digest" - if: ${{ inputs.push == 'true' }} - shell: bash - run: | - mkdir -p ${{ runner.temp }}/digests - digest="${{ steps.build.outputs.digest }}" - touch "${{ runner.temp }}/digests/${digest#sha256:}" - - - name: "Upload digest" - if: ${{ inputs.push == 'true' }} - uses: "actions/upload-artifact@v4" - with: - name: "${{ inputs.artifact-prefix }}-${{ env.PLATFORM_PAIR }}" - path: "${{ runner.temp }}/digests/*" - if-no-files-found: "error" - retention-days: 1 diff --git a/.github/workflows/_glpi-build.yml b/.github/workflows/_glpi-build.yml index 7b91b57..52c1d79 100644 --- a/.github/workflows/_glpi-build.yml +++ b/.github/workflows/_glpi-build.yml @@ -50,7 +50,7 @@ on: jobs: prepare: - name: "Prepare build matrix" + name: "Prepare" runs-on: "ubuntu-latest" outputs: platforms: ${{ steps.set-matrix.outputs.platforms }} @@ -108,7 +108,7 @@ jobs: echo "tags=$TAGS" >> $GITHUB_OUTPUT build: - name: "Build GLPI ${{ inputs.glpi-version }} (${{ matrix.platform.suffix }})" + name: "Build [${{ matrix.platform.suffix }}]" runs-on: "${{ matrix.platform.runner }}" needs: [prepare] strategy: @@ -118,29 +118,86 @@ jobs: steps: - name: "Checkout" uses: "actions/checkout@v6" - - name: "Build GLPI image" - uses: "./.github/actions/build-glpi" + + - name: "Prepare environment" + run: | + platform="${{ matrix.platform.arch }}" + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: "Docker meta" + id: "meta" + uses: "docker/metadata-action@v5" + with: + images: | + ${{ env.DOCKERHUB_IMAGE }} + ${{ env.GHCR_IMAGE }} + + - name: "Get sources from glpi-project/glpi" + run: | + curl https://github.com/glpi-project/glpi/archive/${{ inputs.glpi-version }}.tar.gz --location --output glpi.tar.gz + mkdir -p glpi/sources + tar --extract --ungzip --strip 1 --file glpi.tar.gz --directory glpi/sources + + - name: "Run post-extract commands" + if: ${{ inputs.post-extract-commands != '' }} + run: | + ${{ inputs.post-extract-commands }} + + - name: "Set up QEMU" + uses: "docker/setup-qemu-action@v3" + + - name: "Set up Docker Buildx" + uses: "docker/setup-buildx-action@v3" + + - name: "Login to DockerHub" + uses: "docker/login-action@v3" + with: + username: "${{ secrets.DOCKER_HUB_USERNAME }}" + password: "${{ secrets.DOCKER_HUB_TOKEN }}" + + - name: "Login to Github container registry" + uses: "docker/login-action@v3" + with: + registry: "ghcr.io" + username: "${{ secrets.GHCR_USERNAME }}" + password: "${{ secrets.GHCR_ACCESS_TOKEN }}" + + - name: "Build and push by digest" + id: "build" + uses: "docker/build-push-action@v6" + with: + build-args: | + BUILDER_IMAGE=php:${{ inputs.php-version }}-cli-alpine + APP_IMAGE=php:${{ inputs.php-version }}-apache + GLPI_MARKETPLACE_DIR=${{ needs.prepare.outputs.marketplace-dir }} + cache-from: | + type=gha,scope=${{ github.ref_name }}-${{ matrix.platform.suffix }} + type=gha,scope=main-${{ matrix.platform.suffix }} + cache-to: "type=gha,mode=max,scope=${{ github.ref_name }}-${{ matrix.platform.suffix }}" + context: "glpi" + labels: "${{ steps.meta.outputs.labels }}" + platforms: "${{ matrix.platform.arch }}" + pull: true + outputs: "type=image,\"name=${{ env.DOCKERHUB_IMAGE }},${{ env.GHCR_IMAGE }}\",push-by-digest=true,name-canonical=true,push=${{ inputs.push }}" + + - name: "Export digest" + if: ${{ inputs.push }} + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: "Upload digest" + if: ${{ inputs.push }} + uses: "actions/upload-artifact@v4" with: - push: "${{ inputs.push }}" - artifact-prefix: "digests" - cache-scope: "${{ github.ref_name }}" - platform-arch: "${{ matrix.platform.arch }}" - platform-suffix: "${{ matrix.platform.suffix }}" - post-extract-commands: "${{ inputs.post-extract-commands }}" - php-version: "${{ inputs.php-version }}" - - glpi-version: "${{ inputs.glpi-version }}" - marketplace-dir: "${{ needs.prepare.outputs.marketplace-dir }}" - dockerhub-image: "${{ env.DOCKERHUB_IMAGE }}" - - ghcr-image: "${{ env.GHCR_IMAGE }}" - dockerhub-username: "${{ secrets.DOCKER_HUB_USERNAME }}" - dockerhub-token: "${{ secrets.DOCKER_HUB_TOKEN }}" - ghcr-username: "${{ secrets.GHCR_USERNAME }}" - ghcr-token: "${{ secrets.GHCR_ACCESS_TOKEN }}" + name: "digests-${{ env.PLATFORM_PAIR }}" + path: "${{ runner.temp }}/digests/*" + if-no-files-found: "error" + retention-days: 1 merge-manifests: - name: "Create multi-arch manifest" + name: "Merge manifests" runs-on: "ubuntu-latest" needs: [prepare, build] if: ${{ inputs.push }} diff --git a/.github/workflows/glpi-nightly.yml b/.github/workflows/glpi-nightly.yml index c8862a0..51a79f0 100644 --- a/.github/workflows/glpi-nightly.yml +++ b/.github/workflows/glpi-nightly.yml @@ -7,13 +7,11 @@ on: paths: - ".github/workflows/glpi-nightly.yml" - ".github/workflows/_glpi-build.yml" - - ".github/actions/**" - "glpi/**" pull_request: paths: - ".github/workflows/glpi-nightly.yml" - ".github/workflows/_glpi-build.yml" - - ".github/actions/**" - "glpi/**" schedule: - cron: '0 0 * * *' diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index 8c39abe..ec899fa 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -41,7 +41,7 @@ jobs: php-version: ${{ inputs.php-version }} glpi-version: ${{ inputs.glpi-version }} image-suffix: ${{ inputs.image-suffix }} - # Apply patch for GLPI 11.0.1-11.0.4 (PR #22381 will be included in 11.0.5+) + # Apply patch for GLPI 11.0.0-11.0.4 (PR #22381 will be included in 11.0.5+) post-extract-commands: | if [[ "${{ inputs.glpi-version }}" =~ ^11\.0\.[0-4]$ ]]; then curl https://patch-diff.githubusercontent.com/raw/glpi-project/glpi/pull/22381.diff --location --output composer-audit-config.diff From 26dea97c8191e6fc991b387e270f362cd71b308c Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Thu, 8 Jan 2026 11:41:44 +0100 Subject: [PATCH 04/32] docs: add status badge --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 144091b..faf586a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # GLPI Docker Images +[![Release Build](https://github.com/glpi-project/docker-images/actions/workflows/glpi.yml/badge.svg)](https://github.com/glpi-project/docker-images/actions/workflows/glpi.yml) +[![Nightly Build](https://github.com/glpi-project/docker-images/actions/workflows/glpi-nightly.yml/badge.svg)](https://github.com/glpi-project/docker-images/actions/workflows/glpi-nightly.yml) + ![GLPI on docker illustration](https://raw.githubusercontent.com/glpi-project/docker-images/refs/heads/main/docs/illustration.png) [GLPI](https://glpi-project.org) is a free and open source Asset and IT Management Software package, Data center management, ITIL Service Desk, licenses tracking and software auditing. From 82d174527b68671df0ec4b9029e1fef08c9ea18c Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Thu, 8 Jan 2026 11:52:55 +0100 Subject: [PATCH 05/32] source downloading is now done in the prepare step --- .github/workflows/_glpi-build.yml | 71 ++++++++++++++++++++----------- glpi/Dockerfile | 4 ++ 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/.github/workflows/_glpi-build.yml b/.github/workflows/_glpi-build.yml index 52c1d79..4c7a8e7 100644 --- a/.github/workflows/_glpi-build.yml +++ b/.github/workflows/_glpi-build.yml @@ -53,20 +53,10 @@ jobs: name: "Prepare" runs-on: "ubuntu-latest" outputs: - platforms: ${{ steps.set-matrix.outputs.platforms }} marketplace-dir: ${{ steps.set-vars.outputs.marketplace_dir }} tags: ${{ steps.set-vars.outputs.tags }} + artifact-prefix: ${{ steps.set-vars.outputs.artifact_prefix }} steps: - - name: "Set matrix" - id: "set-matrix" - run: | - echo 'platforms<> $GITHUB_OUTPUT - echo '[ - {"arch": "linux/amd64", "runner": "ubuntu-24.04", "suffix": "amd64"}, - {"arch": "linux/arm64", "runner": "ubuntu-24.04-arm", "suffix": "arm64"} - ]' >> $GITHUB_OUTPUT - echo 'EOF' >> $GITHUB_OUTPUT - - name: "Set variables" id: "set-vars" run: | @@ -78,6 +68,14 @@ jobs: fi echo "marketplace_dir=$MARKETPLACE_DIR" >> $GITHUB_OUTPUT + # Compute artifact prefix (unique per workflow call) + if [[ "${{ inputs.image-tag }}" != '' ]]; then + ARTIFACT_PREFIX="${{ inputs.image-tag }}" + else + ARTIFACT_PREFIX="$(echo '${{ inputs.glpi-version }}' | sed -E 's|/|-|g')" + fi + echo "artifact_prefix=$ARTIFACT_PREFIX" >> $GITHUB_OUTPUT + # Compute tags if [[ "${{ inputs.image-tag }}" != '' ]]; then # Use provided tag directly @@ -107,6 +105,25 @@ jobs: fi echo "tags=$TAGS" >> $GITHUB_OUTPUT + - name: "Get sources from glpi-project/glpi" + run: | + curl https://github.com/glpi-project/glpi/archive/${{ inputs.glpi-version }}.tar.gz --location --output glpi.tar.gz + mkdir -p glpi/sources + tar --extract --ungzip --strip 1 --file glpi.tar.gz --directory glpi/sources + + - name: "Run post-extract commands" + if: ${{ inputs.post-extract-commands != '' }} + run: | + ${{ inputs.post-extract-commands }} + + - name: "Upload sources" + uses: "actions/upload-artifact@v4" + with: + name: "glpi-sources-${{ steps.set-vars.outputs.artifact_prefix }}" + path: "glpi/" + include-hidden-files: true + retention-days: 1 + build: name: "Build [${{ matrix.platform.suffix }}]" runs-on: "${{ matrix.platform.runner }}" @@ -114,7 +131,9 @@ jobs: strategy: fail-fast: false matrix: - platform: ${{ fromJson(needs.prepare.outputs.platforms) }} + platform: + - { arch: "linux/amd64", runner: "ubuntu-24.04", suffix: "amd64" } + - { arch: "linux/arm64", runner: "ubuntu-24.04-arm", suffix: "arm64" } steps: - name: "Checkout" uses: "actions/checkout@v6" @@ -124,6 +143,12 @@ jobs: platform="${{ matrix.platform.arch }}" echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + - name: "Download sources" + uses: "actions/download-artifact@v4" + with: + name: "glpi-sources-${{ needs.prepare.outputs.artifact-prefix }}" + path: "glpi" + - name: "Docker meta" id: "meta" uses: "docker/metadata-action@v5" @@ -132,17 +157,6 @@ jobs: ${{ env.DOCKERHUB_IMAGE }} ${{ env.GHCR_IMAGE }} - - name: "Get sources from glpi-project/glpi" - run: | - curl https://github.com/glpi-project/glpi/archive/${{ inputs.glpi-version }}.tar.gz --location --output glpi.tar.gz - mkdir -p glpi/sources - tar --extract --ungzip --strip 1 --file glpi.tar.gz --directory glpi/sources - - - name: "Run post-extract commands" - if: ${{ inputs.post-extract-commands != '' }} - run: | - ${{ inputs.post-extract-commands }} - - name: "Set up QEMU" uses: "docker/setup-qemu-action@v3" @@ -191,7 +205,7 @@ jobs: if: ${{ inputs.push }} uses: "actions/upload-artifact@v4" with: - name: "digests-${{ env.PLATFORM_PAIR }}" + name: "digests-${{ needs.prepare.outputs.artifact-prefix }}-${{ env.PLATFORM_PAIR }}" path: "${{ runner.temp }}/digests/*" if-no-files-found: "error" retention-days: 1 @@ -206,8 +220,15 @@ jobs: uses: "actions/download-artifact@v4" with: path: "${{ runner.temp }}/digests" - pattern: "digests-*" + pattern: "digests-${{ needs.prepare.outputs.artifact-prefix }}-*" merge-multiple: true + - name: "Verify digests" + run: | + mkdir -p "${{ runner.temp }}/digests" + if [ -z "$(ls -A ${{ runner.temp }}/digests)" ]; then + echo "::error::No digest files found! Build job may have failed or artifacts expired." + exit 1 + fi - name: "Set up Docker Buildx" uses: "docker/setup-buildx-action@v3" - name: "Login to DockerHub" diff --git a/glpi/Dockerfile b/glpi/Dockerfile index f8af832..9f5b571 100644 --- a/glpi/Dockerfile +++ b/glpi/Dockerfile @@ -44,6 +44,10 @@ RUN echo "memory_limit = 512M" >> /usr/local/etc/php/conf.d/docker-php-memory.in # Copy GLPI source. COPY --chown=www-data:www-data ./sources /usr/src/glpi +# Ensure scripts are executable (artifact upload strips permissions) +RUN find /usr/src/glpi/tools -name "*.sh" -exec chmod +x {} \; \ + && chmod +x /usr/src/glpi/bin/console + # Build GLPI app USER www-data RUN /usr/src/glpi/tools/build_glpi.sh From b2278fc63da57b7b48923fa592416feff35aa15f Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Thu, 8 Jan 2026 12:11:04 +0100 Subject: [PATCH 06/32] fix: Missing supply chain attestations for docker scout A rating --- .github/workflows/_glpi-build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/_glpi-build.yml b/.github/workflows/_glpi-build.yml index 4c7a8e7..59a4f4d 100644 --- a/.github/workflows/_glpi-build.yml +++ b/.github/workflows/_glpi-build.yml @@ -192,6 +192,8 @@ jobs: labels: "${{ steps.meta.outputs.labels }}" platforms: "${{ matrix.platform.arch }}" pull: true + sbom: ${{ inputs.push }} + provenance: ${{ inputs.push && 'mode=max' || 'false' }} outputs: "type=image,\"name=${{ env.DOCKERHUB_IMAGE }},${{ env.GHCR_IMAGE }}\",push-by-digest=true,name-canonical=true,push=${{ inputs.push }}" - name: "Export digest" From a7da028195ce59eb6298461dbd19be0f13062e3d Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Thu, 8 Jan 2026 15:47:16 +0100 Subject: [PATCH 07/32] refactor: Source download is now down whiting Dockerfile --- .github/workflows/_glpi-build.yml | 31 ++--------- .github/workflows/glpi.yml | 20 +++++-- CONTRIBUTING.md | 91 +++++++++++++++++++++++++++++++ README.md | 1 + docker-compose.test.yml | 46 ++++++++++++++++ glpi/Dockerfile | 51 ++++++++++++++++- 6 files changed, 205 insertions(+), 35 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 docker-compose.test.yml diff --git a/.github/workflows/_glpi-build.yml b/.github/workflows/_glpi-build.yml index 59a4f4d..eef2f79 100644 --- a/.github/workflows/_glpi-build.yml +++ b/.github/workflows/_glpi-build.yml @@ -15,8 +15,8 @@ on: required: false type: boolean default: false - post-extract-commands: - description: "Custom bash commands to run after source extraction (i.e: patches)" + patch-url: + description: "URL to a .diff/.patch file to apply after source download, multiple patches can be separated by space" required: false type: string default: "" @@ -105,25 +105,6 @@ jobs: fi echo "tags=$TAGS" >> $GITHUB_OUTPUT - - name: "Get sources from glpi-project/glpi" - run: | - curl https://github.com/glpi-project/glpi/archive/${{ inputs.glpi-version }}.tar.gz --location --output glpi.tar.gz - mkdir -p glpi/sources - tar --extract --ungzip --strip 1 --file glpi.tar.gz --directory glpi/sources - - - name: "Run post-extract commands" - if: ${{ inputs.post-extract-commands != '' }} - run: | - ${{ inputs.post-extract-commands }} - - - name: "Upload sources" - uses: "actions/upload-artifact@v4" - with: - name: "glpi-sources-${{ steps.set-vars.outputs.artifact_prefix }}" - path: "glpi/" - include-hidden-files: true - retention-days: 1 - build: name: "Build [${{ matrix.platform.suffix }}]" runs-on: "${{ matrix.platform.runner }}" @@ -143,12 +124,6 @@ jobs: platform="${{ matrix.platform.arch }}" echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - name: "Download sources" - uses: "actions/download-artifact@v4" - with: - name: "glpi-sources-${{ needs.prepare.outputs.artifact-prefix }}" - path: "glpi" - - name: "Docker meta" id: "meta" uses: "docker/metadata-action@v5" @@ -181,6 +156,8 @@ jobs: uses: "docker/build-push-action@v6" with: build-args: | + GLPI_VERSION=${{ inputs.glpi-version }} + GLPI_PATCH_URL=${{ inputs.patch-url }} BUILDER_IMAGE=php:${{ inputs.php-version }}-cli-alpine APP_IMAGE=php:${{ inputs.php-version }}-apache GLPI_MARKETPLACE_DIR=${{ needs.prepare.outputs.marketplace-dir }} diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index ec899fa..0d80334 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -41,10 +41,18 @@ jobs: php-version: ${{ inputs.php-version }} glpi-version: ${{ inputs.glpi-version }} image-suffix: ${{ inputs.image-suffix }} - # Apply patch for GLPI 11.0.0-11.0.4 (PR #22381 will be included in 11.0.5+) - post-extract-commands: | - if [[ "${{ inputs.glpi-version }}" =~ ^11\.0\.[0-4]$ ]]; then - curl https://patch-diff.githubusercontent.com/raw/glpi-project/glpi/pull/22381.diff --location --output composer-audit-config.diff - patch --force --directory=glpi/sources < composer-audit-config.diff - fi + # Patches for specific versions + # Format: Use startsWith() for ranges, exact match for single versions + # Multiple patches: separate URLs with spaces + # + # Examples: + # Single version: inputs.glpi-version == '11.0.5' + # Version range: startsWith(inputs.glpi-version, '11.0.') && inputs.glpi-version < '11.0.5' + # Multiple patches: 'url1 url2' + patch-url: >- + ${{ + (startsWith(inputs.glpi-version, '11.0.') && inputs.glpi-version < '11.0.5') + && 'https://patch-diff.githubusercontent.com/raw/glpi-project/glpi/pull/22381.diff' + || '' + }} secrets: inherit diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..d9faa73 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,91 @@ +# Contributing to GLPI Docker Images + +Thank you for your interest in contributing to the GLPI Docker images project! + +This guide will help you set up your local development environment to test changes. + +## Prerequisites + +- [Docker](https://docs.docker.com/get-docker/) installed. + +## Quick Start + +The Dockerfile supports automatic source downloading via the `GLPI_VERSION` build argument. +By default, it will download the latest stable version. + +### Build Options + +**Build with latest stable version:** +```bash +docker build --build-arg GLPI_VERSION=latest glpi/ +``` + +**Build with a specific version:** +```bash +docker build --build-arg GLPI_VERSION=10.0.18 glpi/ +``` + +**Build with a branch:** +```bash +docker build --build-arg GLPI_VERSION=11.0/bugfixes glpi/ +``` + +**Build with a specific commit:** +```bash +docker build --build-arg GLPI_VERSION=2186bc6bd410d8bcb048637b3c0fb86b7e320c0a glpi/ +``` + +**Build with a direct URL:** +```bash +docker build --build-arg GLPI_VERSION=https://github.com/glpi-project/glpi/archive/2186bc6.tar.gz glpi/ +``` + +### Applying Patches + +You can apply patches after the source download using the `GLPI_PATCH_URL` build argument. + +**Apply patches (space-separated):** +```bash +docker build \ + --build-arg GLPI_VERSION=11.0.4 \ + --build-arg "GLPI_PATCH_URL=https://example.com/patch1.diff https://example.com/patch2.diff" \ + glpi/ +``` + +**Using docker-compose with patches:** +```yaml +services: + glpi: + build: + context: ./glpi + args: + GLPI_VERSION: 11.0.4 + GLPI_PATCH_URL: https://patch-diff.githubusercontent.com/raw/glpi-project/glpi/pull/22381.diff +``` + +### Run the Test Environment + +We provide a `docker-compose.test.yml` file to test the `glpi` container: + +```bash +docker compose -f docker-compose.test.yml up --build +``` + +### Verify + +Once the containers are up: +1. Check the logs to see the installation progress: + ```bash + docker compose -f docker-compose.test.yml logs -f glpi + ``` +2. Wait for the "GLPI installation completed successfully!" message. +3. Access GLPI at [http://localhost:8080](http://localhost:8080). + - **User:** `glpi` + - **Password:** `glpi` + +### Cleanup + +To stop and remove the test containers: +```bash +docker compose -f docker-compose.test.yml down -v +``` diff --git a/README.md b/README.md index faf586a..6ea9a69 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ A few links: - [Report an issue](https://github.com/glpi-project/glpi/issues/new?template=bug_report.yml) - [Documentation](https://glpi-project.org/documentation/) +- [Contributing](CONTRIBUTING.md) This repository contains build files for docker images available in [Github Container Registry](https://github.com/orgs/glpi-project/packages?ecosystem=container) and [Docker hub](https://hub.docker.com/r/glpi/glpi). diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 0000000..0771c22 --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,46 @@ +name: glpi_docker_dev + +services: + glpi: + build: + context: ./glpi + args: + GLPI_VERSION: latest + GLPI_PATCH_URL: https://patch-diff.githubusercontent.com/raw/glpi-project/glpi/pull/22381.diff + restart: "no" + volumes: + # Using a named volume avoids permission issues on host (automatically managed by Docker) + - glpi_data:/var/glpi + environment: + - GLPI_DB_HOST=db + - GLPI_DB_NAME=glpi + - GLPI_DB_USER=glpi + - GLPI_DB_PASSWORD=glpi + - GLPI_DB_PORT=3306 + - GLPI_INSTALL_MODE=DOCKER + ports: + - "8080:80" + depends_on: + db: + condition: service_healthy + + db: + image: mariadb:10.11 + restart: "no" + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: glpi + MYSQL_USER: glpi + MYSQL_PASSWORD: glpi + volumes: + - db_data:/var/lib/mysql + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + start_period: 10s + interval: 10s + timeout: 5s + retries: 3 + +volumes: + glpi_data: + db_data: diff --git a/glpi/Dockerfile b/glpi/Dockerfile index 9f5b571..b6745a3 100644 --- a/glpi/Dockerfile +++ b/glpi/Dockerfile @@ -2,6 +2,53 @@ ARG BUILDER_IMAGE=php:cli-alpine ARG APP_IMAGE=php:apache +##### +# Downloader image - Resolve version and download source +##### +FROM alpine AS downloader + +# GLPI_VERSION can be: +# - "latest" (default): resolves to latest stable release +# - A tag version: e.g., "10.0.18", "11.0.0" +# - A branch name: e.g., "11.0/bugfixes", "main" +# - A commit hash: e.g., "2186bc6bd410d8bcb048637b3c0fb86b7e320c0a" +# - A direct URL: e.g., "https://github.com/glpi-project/glpi/archive/2186bc6.tar.gz" +ARG GLPI_VERSION=latest +# Optional patch URL to apply after download +ARG GLPI_PATCH_URL="" + +RUN apk add --no-cache curl jq patch + +WORKDIR /download + +# Resolve version and download source +RUN set -ex; \ + INPUT="${GLPI_VERSION}"; \ + # If input starts with https://, use it as-is \ + if echo "$INPUT" | grep -q '^https://'; then \ + URL="$INPUT"; \ + else \ + VERSION="$INPUT"; \ + # If "latest", resolve from GitHub API \ + if [ "$VERSION" = "latest" ]; then \ + VERSION=$(curl -s https://api.github.com/repos/glpi-project/glpi/releases/latest | jq -r .tag_name); \ + fi; \ + # Use GitHub's generic archive format (works for branches, tags, and commits) + URL="https://github.com/glpi-project/glpi/archive/${VERSION}.tar.gz"; \ + fi; \ + echo "Downloading GLPI from $URL"; \ + curl -L "$URL" | tar xz --strip-components=1 + +# Apply optional patches if GLPI_PATCH_URL is provided (space-separated URLs) +ARG GLPI_PATCH_URL +RUN set -ex; \ + if [ -n "${GLPI_PATCH_URL}" ]; then \ + for PATCH in ${GLPI_PATCH_URL}; do \ + echo "Applying patch from ${PATCH}"; \ + curl -L "${PATCH}" | patch -p1; \ + done; \ + fi + ##### # Builder image ##### @@ -41,8 +88,8 @@ RUN \ # Update PHP configuration. RUN echo "memory_limit = 512M" >> /usr/local/etc/php/conf.d/docker-php-memory.ini -# Copy GLPI source. -COPY --chown=www-data:www-data ./sources /usr/src/glpi +# Copy GLPI source from downloader stage. +COPY --from=downloader --chown=www-data:www-data /download /usr/src/glpi # Ensure scripts are executable (artifact upload strips permissions) RUN find /usr/src/glpi/tools -name "*.sh" -exec chmod +x {} \; \ From b9a15518081c63cc73e2ebab6547a0ea46d0c9f3 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Thu, 8 Jan 2026 15:55:38 +0100 Subject: [PATCH 08/32] update to use latest for mariadb --- docker-compose.test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 0771c22..d5ecefe 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -25,7 +25,7 @@ services: condition: service_healthy db: - image: mariadb:10.11 + image: mariadb:latest restart: "no" environment: MYSQL_ROOT_PASSWORD: root From 3ccf660637aaac15e359612e36986979924e1236 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Mon, 12 Jan 2026 11:02:29 +0100 Subject: [PATCH 09/32] Switch to really use docker/metadata + forgot about ghcr --- .github/workflows/_glpi-build.yml | 99 +++++++++++++++++++------------ 1 file changed, 62 insertions(+), 37 deletions(-) diff --git a/.github/workflows/_glpi-build.yml b/.github/workflows/_glpi-build.yml index eef2f79..003a120 100644 --- a/.github/workflows/_glpi-build.yml +++ b/.github/workflows/_glpi-build.yml @@ -54,8 +54,10 @@ jobs: runs-on: "ubuntu-latest" outputs: marketplace-dir: ${{ steps.set-vars.outputs.marketplace_dir }} - tags: ${{ steps.set-vars.outputs.tags }} artifact-prefix: ${{ steps.set-vars.outputs.artifact_prefix }} + is-latest: ${{ steps.set-vars.outputs.is_latest }} + is-prerelease: ${{ steps.set-vars.outputs.is_prerelease }} + image-version: ${{ steps.set-vars.outputs.image_version }} steps: - name: "Set variables" id: "set-vars" @@ -76,34 +78,30 @@ jobs: fi echo "artifact_prefix=$ARTIFACT_PREFIX" >> $GITHUB_OUTPUT - # Compute tags - if [[ "${{ inputs.image-tag }}" != '' ]]; then - # Use provided tag directly - TAGS="${{ env.DOCKERHUB_IMAGE }}:${{ inputs.image-tag }},${{ env.GHCR_IMAGE }}:${{ inputs.image-tag }}" - else - # Compute from version - IMAGE_VERSION="$(echo '${{ inputs.glpi-version }}' | sed -E 's|/|-|')" - IMAGE_VERSION_MAJOR=$(echo "${{ inputs.glpi-version }}" | cut -d. -f1) - IMAGE_VERSION_MINOR=$(echo "${{ inputs.glpi-version }}" | cut -d. -f1-2) - - if [[ "${{ inputs.image-suffix }}" != '' ]]; then - IMAGE_VERSION="$IMAGE_VERSION-${{ inputs.image-suffix }}" - fi - - TAGS="${{ env.DOCKERHUB_IMAGE }}:$IMAGE_VERSION,${{ env.GHCR_IMAGE }}:$IMAGE_VERSION" + # Compute image version for metadata-action + IMAGE_VERSION="$(echo '${{ inputs.glpi-version }}' | sed -E 's|/|-|')" + if [[ "${{ inputs.image-suffix }}" != '' ]]; then + IMAGE_VERSION="$IMAGE_VERSION-${{ inputs.image-suffix }}" + fi + echo "image_version=$IMAGE_VERSION" >> $GITHUB_OUTPUT - PRERELEASE_FLAG="$( echo "${{ inputs.glpi-version }}" | grep -Po '(\-\w+)?$' )" - if [[ -z "$PRERELEASE_FLAG" ]]; then - TAGS="$TAGS,${{ env.DOCKERHUB_IMAGE }}:$IMAGE_VERSION_MAJOR,${{ env.GHCR_IMAGE }}:$IMAGE_VERSION_MAJOR" - TAGS="$TAGS,${{ env.DOCKERHUB_IMAGE }}:$IMAGE_VERSION_MINOR,${{ env.GHCR_IMAGE }}:$IMAGE_VERSION_MINOR" + # Detect prerelease (contains -rc, -beta, -alpha, etc.) + PRERELEASE_FLAG="$( echo "${{ inputs.glpi-version }}" | grep -Po '(\-\w+)?$' )" + if [[ -n "$PRERELEASE_FLAG" ]]; then + echo "is_prerelease=true" >> $GITHUB_OUTPUT + else + echo "is_prerelease=false" >> $GITHUB_OUTPUT + fi - LATEST_TAG="$(curl -s https://api.github.com/repos/glpi-project/glpi/releases/latest | jq -r .tag_name)" - if [[ "${{ inputs.glpi-version }}" = "$LATEST_TAG" ]]; then - TAGS="$TAGS,${{ env.DOCKERHUB_IMAGE }}:latest,${{ env.GHCR_IMAGE }}:latest" - fi + # Check if this is the latest release (only for non-prereleases) + IS_LATEST="false" + if [[ -z "$PRERELEASE_FLAG" && "${{ inputs.image-tag }}" == '' ]]; then + LATEST_TAG="$(curl -s https://api.github.com/repos/glpi-project/glpi/releases/latest | jq -r .tag_name)" + if [[ "${{ inputs.glpi-version }}" = "$LATEST_TAG" ]]; then + IS_LATEST="true" fi fi - echo "tags=$TAGS" >> $GITHUB_OUTPUT + echo "is_latest=$IS_LATEST" >> $GITHUB_OUTPUT build: name: "Build [${{ matrix.platform.suffix }}]" @@ -221,21 +219,48 @@ jobs: registry: "ghcr.io" username: "${{ secrets.GHCR_USERNAME }}" password: "${{ secrets.GHCR_ACCESS_TOKEN }}" + - name: "Docker meta" + id: "meta" + uses: "docker/metadata-action@v5" + with: + images: | + ${{ env.DOCKERHUB_IMAGE }} + ${{ env.GHCR_IMAGE }} + # Disable automatic latest tag - we control it via is-latest + flavor: | + latest=false + tags: | + # If image-tag is explicitly provided, use it directly + type=raw,value=${{ inputs.image-tag }},enable=${{ inputs.image-tag != '' }} + # Base version tag (always, when no explicit image-tag) + type=raw,value=${{ needs.prepare.outputs.image-version }},enable=${{ inputs.image-tag == '' }} + # only for releases + type=semver,pattern={{major}},value=${{ inputs.glpi-version }},enable=${{ inputs.image-tag == '' && needs.prepare.outputs.is-prerelease == 'false' }} + type=semver,pattern={{major}}.{{minor}},value=${{ inputs.glpi-version }},enable=${{ inputs.image-tag == '' && needs.prepare.outputs.is-prerelease == 'false' }} + + # Latest + type=raw,value=latest,enable=${{ needs.prepare.outputs.is-latest == 'true' }} - name: "Create and push manifest" working-directory: "${{ runner.temp }}/digests" + env: + TAGS: ${{ steps.meta.outputs.tags }} run: | - # Build the tag arguments - TAG_ARGS="" - IFS=',' read -ra TAG_ARRAY <<< "${{ needs.prepare.outputs.tags }}" - for tag in "${TAG_ARRAY[@]}"; do - TAG_ARGS="$TAG_ARGS -t $tag" - done - - # Create manifest - docker buildx imagetools create $TAG_ARGS \ - $(printf '${{ env.DOCKERHUB_IMAGE }}@sha256:%s ' *) + # Create manifest for DockerHub + DOCKERHUB_TAGS=$(echo "$TAGS" | grep "^${{ env.DOCKERHUB_IMAGE }}" | xargs -I {} echo "-t {}" | tr '\n' ' ') + if [[ -n "$DOCKERHUB_TAGS" ]]; then + echo "Creating DockerHub manifest..." + docker buildx imagetools create $DOCKERHUB_TAGS \ + $(printf '${{ env.DOCKERHUB_IMAGE }}@sha256:%s ' *) + fi + + # Create manifest for GHCR + GHCR_TAGS=$(echo "$TAGS" | grep "^${{ env.GHCR_IMAGE }}" | xargs -I {} echo "-t {}" | tr '\n' ' ') + if [[ -n "$GHCR_TAGS" ]]; then + echo "Creating GHCR manifest..." + docker buildx imagetools create $GHCR_TAGS \ + $(printf '${{ env.GHCR_IMAGE }}@sha256:%s ' *) + fi - name: "Inspect image" run: | # Get first tag for inspection - FIRST_TAG=$(echo "${{ needs.prepare.outputs.tags }}" | cut -d',' -f1) - docker buildx imagetools inspect $FIRST_TAG + docker buildx imagetools inspect ${{ fromJSON(steps.meta.outputs.json).tags[0] }} From fb5f3fbe1e81a94744a69b62e6d16dc5383e0a70 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Mon, 12 Jan 2026 15:05:59 +0100 Subject: [PATCH 10/32] Support for non semver version --- .github/workflows/_glpi-build.yml | 94 ++++++++++++++++++++++--------- .github/workflows/glpi.yml | 21 ++++--- 2 files changed, 81 insertions(+), 34 deletions(-) diff --git a/.github/workflows/_glpi-build.yml b/.github/workflows/_glpi-build.yml index 003a120..1391c2d 100644 --- a/.github/workflows/_glpi-build.yml +++ b/.github/workflows/_glpi-build.yml @@ -56,52 +56,91 @@ jobs: marketplace-dir: ${{ steps.set-vars.outputs.marketplace_dir }} artifact-prefix: ${{ steps.set-vars.outputs.artifact_prefix }} is-latest: ${{ steps.set-vars.outputs.is_latest }} - is-prerelease: ${{ steps.set-vars.outputs.is_prerelease }} + is-latest-major: ${{ steps.set-vars.outputs.is_latest_major }} image-version: ${{ steps.set-vars.outputs.image_version }} steps: - name: "Set variables" id: "set-vars" run: | - # Compute marketplace dir - IMAGE_VERSION_MAJOR=$(echo "${{ inputs.glpi-version }}" | cut -d. -f1) + GLPI_VERSION="${{ inputs.glpi-version }}" + IMAGE_TAG="${{ inputs.image-tag }}" + IMAGE_SUFFIX="${{ inputs.image-suffix }}" + + # Determine if glpi-version is a semver tag (e.g., 10.0.18, 11.0.5-rc1) + # vs a branch, commit hash, or URL + IS_SEMVER=false + if echo "$GLPI_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then + IS_SEMVER=true + fi + + # Compute marketplace dir (only v10 uses old path) MARKETPLACE_DIR="/var/glpi/marketplace" - if [[ "$IMAGE_VERSION_MAJOR" = "10" ]]; then - MARKETPLACE_DIR="/var/www/glpi/marketplace" + if [[ "$IS_SEMVER" = "true" ]]; then + IMAGE_VERSION_MAJOR=$(echo "$GLPI_VERSION" | cut -d. -f1) + if [[ "$IMAGE_VERSION_MAJOR" = "10" ]]; then + MARKETPLACE_DIR="/var/www/glpi/marketplace" + fi fi echo "marketplace_dir=$MARKETPLACE_DIR" >> $GITHUB_OUTPUT # Compute artifact prefix (unique per workflow call) - if [[ "${{ inputs.image-tag }}" != '' ]]; then - ARTIFACT_PREFIX="${{ inputs.image-tag }}" + if [[ "$IMAGE_TAG" != '' ]]; then + ARTIFACT_PREFIX="$IMAGE_TAG" else - ARTIFACT_PREFIX="$(echo '${{ inputs.glpi-version }}' | sed -E 's|/|-|g')" + # Replace slashes and colons with dashes for branches like 11.0/bugfixes + ARTIFACT_PREFIX="$(echo "$GLPI_VERSION" | sed -E 's|[/:]|-|g' | sed -E 's|https?--||')" fi echo "artifact_prefix=$ARTIFACT_PREFIX" >> $GITHUB_OUTPUT # Compute image version for metadata-action - IMAGE_VERSION="$(echo '${{ inputs.glpi-version }}' | sed -E 's|/|-|')" - if [[ "${{ inputs.image-suffix }}" != '' ]]; then - IMAGE_VERSION="$IMAGE_VERSION-${{ inputs.image-suffix }}" + if [[ "$IMAGE_TAG" != '' ]]; then + IMAGE_VERSION="$IMAGE_TAG" + else + IMAGE_VERSION="$(echo "$GLPI_VERSION" | sed -E 's|[/:]|-|g' | sed -E 's|https?--||')" + if [[ "$IMAGE_SUFFIX" != '' ]]; then + IMAGE_VERSION="$IMAGE_VERSION-$IMAGE_SUFFIX" + fi fi echo "image_version=$IMAGE_VERSION" >> $GITHUB_OUTPUT + # Skip all version checks if: + # - image-tag is provided (explicit override) + # - glpi-version is not a semver (branch, commit, URL) + if [[ "$IMAGE_TAG" != '' ]] || [[ "$IS_SEMVER" != "true" ]]; then + echo "is_latest=false" >> $GITHUB_OUTPUT + echo "is_latest_major=false" >> $GITHUB_OUTPUT + exit 0 + fi + # Detect prerelease (contains -rc, -beta, -alpha, etc.) - PRERELEASE_FLAG="$( echo "${{ inputs.glpi-version }}" | grep -Po '(\-\w+)?$' )" + PRERELEASE_FLAG="$( echo "$GLPI_VERSION" | grep -Po '(\-\w+)?$' )" if [[ -n "$PRERELEASE_FLAG" ]]; then - echo "is_prerelease=true" >> $GITHUB_OUTPUT + echo "is_latest=false" >> $GITHUB_OUTPUT + echo "is_latest_major=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # Check if this is the latest release overall + LATEST_TAG=$(curl -s "https://api.github.com/repos/glpi-project/glpi/releases/latest" | jq -r '.tag_name') + if [[ "$GLPI_VERSION" = "$LATEST_TAG" ]]; then + echo "is_latest=true" >> $GITHUB_OUTPUT else - echo "is_prerelease=false" >> $GITHUB_OUTPUT + echo "is_latest=false" >> $GITHUB_OUTPUT fi - # Check if this is the latest release (only for non-prereleases) - IS_LATEST="false" - if [[ -z "$PRERELEASE_FLAG" && "${{ inputs.image-tag }}" == '' ]]; then - LATEST_TAG="$(curl -s https://api.github.com/repos/glpi-project/glpi/releases/latest | jq -r .tag_name)" - if [[ "${{ inputs.glpi-version }}" = "$LATEST_TAG" ]]; then - IS_LATEST="true" - fi + # Fetch all releases from GitHub API (non-prerelease only) for major series check + RELEASES=$(curl -s "https://api.github.com/repos/glpi-project/glpi/releases?per_page=100" | \ + jq -r '[.[] | select(.prerelease == false) | .tag_name]') + + # Check if this is the latest in its major series (e.g., latest 11.x.x) + IMAGE_VERSION_MAJOR=$(echo "$GLPI_VERSION" | cut -d. -f1) + LATEST_IN_MAJOR=$(echo "$RELEASES" | jq -r --arg major "$IMAGE_VERSION_MAJOR" \ + '[.[] | select(startswith($major + "."))] | .[0] // empty') + if [[ "$GLPI_VERSION" = "$LATEST_IN_MAJOR" ]]; then + echo "is_latest_major=true" >> $GITHUB_OUTPUT + else + echo "is_latest_major=false" >> $GITHUB_OUTPUT fi - echo "is_latest=$IS_LATEST" >> $GITHUB_OUTPUT build: name: "Build [${{ matrix.platform.suffix }}]" @@ -122,7 +161,7 @@ jobs: platform="${{ matrix.platform.arch }}" echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - name: "Docker meta" + - name: "Docker metadata" id: "meta" uses: "docker/metadata-action@v5" with: @@ -219,7 +258,7 @@ jobs: registry: "ghcr.io" username: "${{ secrets.GHCR_USERNAME }}" password: "${{ secrets.GHCR_ACCESS_TOKEN }}" - - name: "Docker meta" + - name: "Docker metadata" id: "meta" uses: "docker/metadata-action@v5" with: @@ -234,9 +273,10 @@ jobs: type=raw,value=${{ inputs.image-tag }},enable=${{ inputs.image-tag != '' }} # Base version tag (always, when no explicit image-tag) type=raw,value=${{ needs.prepare.outputs.image-version }},enable=${{ inputs.image-tag == '' }} - # only for releases - type=semver,pattern={{major}},value=${{ inputs.glpi-version }},enable=${{ inputs.image-tag == '' && needs.prepare.outputs.is-prerelease == 'false' }} - type=semver,pattern={{major}}.{{minor}},value=${{ inputs.glpi-version }},enable=${{ inputs.image-tag == '' && needs.prepare.outputs.is-prerelease == 'false' }} + + # Only for releases + type=semver,pattern={{major}},value=${{ inputs.glpi-version }},enable=${{ needs.prepare.outputs.is-latest-major == 'true' }} + type=semver,pattern={{major}}.{{minor}},value=${{ inputs.glpi-version }},enable=${{ needs.prepare.outputs.is-latest-major == 'true' }} # Latest type=raw,value=latest,enable=${{ needs.prepare.outputs.is-latest == 'true' }} diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index 0d80334..fe1f8a6 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -23,7 +23,7 @@ on: type: string default: "8.5" glpi-version: - description: "GLPI version to build, i.e: 11.0.5" + description: "GLPI version (tag: '11.0.5'), branch ('main', '11.0/bugfixes'), commit hash, or URL" required: true type: string image-suffix: @@ -31,6 +31,16 @@ on: required: false type: string default: "" + image-tag: + description: "Override image tag (ignores version computation). i.e: '11.0.5-beta1'" + required: false + type: string + default: "" + patch-url: + description: "Optional patch URL(s) to apply. Multiple patches: separate with spaces" + required: false + type: string + default: "" jobs: build: @@ -41,15 +51,12 @@ jobs: php-version: ${{ inputs.php-version }} glpi-version: ${{ inputs.glpi-version }} image-suffix: ${{ inputs.image-suffix }} - # Patches for specific versions + image-tag: ${{ inputs.image-tag }} + # Combine manual patch-url input with automatic version-based patches # Format: Use startsWith() for ranges, exact match for single versions # Multiple patches: separate URLs with spaces - # - # Examples: - # Single version: inputs.glpi-version == '11.0.5' - # Version range: startsWith(inputs.glpi-version, '11.0.') && inputs.glpi-version < '11.0.5' - # Multiple patches: 'url1 url2' patch-url: >- + ${{ inputs.patch-url }} ${{ (startsWith(inputs.glpi-version, '11.0.') && inputs.glpi-version < '11.0.5') && 'https://patch-diff.githubusercontent.com/raw/glpi-project/glpi/pull/22381.diff' From 0702775d91692a28b1c455e9e8bfd6e6b50831f9 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Mon, 12 Jan 2026 16:17:42 +0100 Subject: [PATCH 11/32] Add cache busting logic --- .github/workflows/_glpi-build.yml | 16 ++++++++++++++++ .github/workflows/glpi.yml | 2 +- glpi/Dockerfile | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/_glpi-build.yml b/.github/workflows/_glpi-build.yml index 1391c2d..5799e35 100644 --- a/.github/workflows/_glpi-build.yml +++ b/.github/workflows/_glpi-build.yml @@ -57,6 +57,7 @@ jobs: artifact-prefix: ${{ steps.set-vars.outputs.artifact_prefix }} is-latest: ${{ steps.set-vars.outputs.is_latest }} is-latest-major: ${{ steps.set-vars.outputs.is_latest_major }} + sha-source-commit: ${{ steps.set-vars.outputs.sha_source_commit }} image-version: ${{ steps.set-vars.outputs.image_version }} steps: - name: "Set variables" @@ -73,6 +74,20 @@ jobs: IS_SEMVER=true fi + # Fetch the commit SHA for this version/branch (for cache-busting) + # Same commit = cache hit, new commit = fresh build + if echo "$GLPI_VERSION" | grep -qE '^https://'; then + # For URLs, use empty (will always rebuild) + SOURCE_COMMIT="" + elif [[ "$GLPI_VERSION" = "latest" ]]; then + # Resolve "latest" to actual tag first, then get commit SHA + RESOLVED_TAG=$(curl -s "https://api.github.com/repos/glpi-project/glpi/releases/latest" | jq -r '.tag_name') + SOURCE_COMMIT=$(curl -s "https://api.github.com/repos/glpi-project/glpi/commits/$RESOLVED_TAG" | jq -r '.sha // empty') + else + SOURCE_COMMIT=$(curl -s "https://api.github.com/repos/glpi-project/glpi/commits/$GLPI_VERSION" | jq -r '.sha // empty') + fi + echo "sha_source_commit=$SOURCE_COMMIT" >> $GITHUB_OUTPUT + # Compute marketplace dir (only v10 uses old path) MARKETPLACE_DIR="/var/glpi/marketplace" if [[ "$IS_SEMVER" = "true" ]]; then @@ -195,6 +210,7 @@ jobs: build-args: | GLPI_VERSION=${{ inputs.glpi-version }} GLPI_PATCH_URL=${{ inputs.patch-url }} + GLPI_SHA_SOURCE_COMMIT=${{ needs.prepare.outputs.sha-source-commit }} BUILDER_IMAGE=php:${{ inputs.php-version }}-cli-alpine APP_IMAGE=php:${{ inputs.php-version }}-apache GLPI_MARKETPLACE_DIR=${{ needs.prepare.outputs.marketplace-dir }} diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index fe1f8a6..e8dbcf2 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -32,7 +32,7 @@ on: type: string default: "" image-tag: - description: "Override image tag (ignores version computation). i.e: '11.0.5-beta1'" + description: "Override image tag (glpi-version otherwise). i.e: '11.0.5-beta1'" required: false type: string default: "" diff --git a/glpi/Dockerfile b/glpi/Dockerfile index b6745a3..52c7d16 100644 --- a/glpi/Dockerfile +++ b/glpi/Dockerfile @@ -16,6 +16,8 @@ FROM alpine AS downloader ARG GLPI_VERSION=latest # Optional patch URL to apply after download ARG GLPI_PATCH_URL="" +# Cache-busting: SHA from GitHub API for _glpi-build.yaml workflows +ARG GLPI_SHA_SOURCE_COMMIT="" RUN apk add --no-cache curl jq patch From 11cbd2d91096bb6037b0293106c1345bea3824cb Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Tue, 13 Jan 2026 14:09:37 +0100 Subject: [PATCH 12/32] Remove image-suffix as it was never use --- .github/workflows/_glpi-build.yml | 9 --------- .github/workflows/glpi.yml | 6 ------ 2 files changed, 15 deletions(-) diff --git a/.github/workflows/_glpi-build.yml b/.github/workflows/_glpi-build.yml index 5799e35..4bffbd6 100644 --- a/.github/workflows/_glpi-build.yml +++ b/.github/workflows/_glpi-build.yml @@ -33,11 +33,6 @@ on: required: false type: string default: "" - image-suffix: - description: "Suffix to add to computed tag (ignored if image-tag is set)" - required: false - type: string - default: "" secrets: DOCKER_HUB_USERNAME: required: true @@ -65,7 +60,6 @@ jobs: run: | GLPI_VERSION="${{ inputs.glpi-version }}" IMAGE_TAG="${{ inputs.image-tag }}" - IMAGE_SUFFIX="${{ inputs.image-suffix }}" # Determine if glpi-version is a semver tag (e.g., 10.0.18, 11.0.5-rc1) # vs a branch, commit hash, or URL @@ -112,9 +106,6 @@ jobs: IMAGE_VERSION="$IMAGE_TAG" else IMAGE_VERSION="$(echo "$GLPI_VERSION" | sed -E 's|[/:]|-|g' | sed -E 's|https?--||')" - if [[ "$IMAGE_SUFFIX" != '' ]]; then - IMAGE_VERSION="$IMAGE_VERSION-$IMAGE_SUFFIX" - fi fi echo "image_version=$IMAGE_VERSION" >> $GITHUB_OUTPUT diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index e8dbcf2..9292779 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -26,11 +26,6 @@ on: description: "GLPI version (tag: '11.0.5'), branch ('main', '11.0/bugfixes'), commit hash, or URL" required: true type: string - image-suffix: - description: "Suffix to add to the image name, i.e: 'nightly'" - required: false - type: string - default: "" image-tag: description: "Override image tag (glpi-version otherwise). i.e: '11.0.5-beta1'" required: false @@ -50,7 +45,6 @@ jobs: push: ${{ inputs.push }} php-version: ${{ inputs.php-version }} glpi-version: ${{ inputs.glpi-version }} - image-suffix: ${{ inputs.image-suffix }} image-tag: ${{ inputs.image-tag }} # Combine manual patch-url input with automatic version-based patches # Format: Use startsWith() for ranges, exact match for single versions From 69ea5589677018310719638bd33953f12f0cec6e Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Tue, 13 Jan 2026 15:47:15 +0100 Subject: [PATCH 13/32] Move trigger logic to be from GLPI project itself Add a `on.push/pr` to trigger the ci build (without pushing images) when the workflow is updated --- .github/workflows/_glpi-build.yml | 313 ------------------------- .github/workflows/glpi-nightly.yml | 51 ---- .github/workflows/glpi.yml | 365 +++++++++++++++++++++++++++-- 3 files changed, 342 insertions(+), 387 deletions(-) delete mode 100644 .github/workflows/_glpi-build.yml delete mode 100644 .github/workflows/glpi-nightly.yml diff --git a/.github/workflows/_glpi-build.yml b/.github/workflows/_glpi-build.yml deleted file mode 100644 index 4bffbd6..0000000 --- a/.github/workflows/_glpi-build.yml +++ /dev/null @@ -1,313 +0,0 @@ -name: "GLPI Docker Image Build (Template)" - -# This is a reusable workflow template - never run directly -# Called by: glpi.yml, glpi-nightly.yml - -env: - DOCKERHUB_IMAGE: glpi/glpi - GHCR_IMAGE: ghcr.io/glpi-project/glpi - -on: - workflow_call: - inputs: - push: - description: "Whether to push images to registries" - required: false - type: boolean - default: false - patch-url: - description: "URL to a .diff/.patch file to apply after source download, multiple patches can be separated by space" - required: false - type: string - default: "" - php-version: - required: false - type: string - default: "8.5" - glpi-version: - description: "GLPI version or branch to build" - required: true - type: string - image-tag: - description: "Override image tag. If not set, computed from version." - required: false - type: string - default: "" - secrets: - DOCKER_HUB_USERNAME: - required: true - DOCKER_HUB_TOKEN: - required: true - GHCR_USERNAME: - required: true - GHCR_ACCESS_TOKEN: - required: true - -jobs: - prepare: - name: "Prepare" - runs-on: "ubuntu-latest" - outputs: - marketplace-dir: ${{ steps.set-vars.outputs.marketplace_dir }} - artifact-prefix: ${{ steps.set-vars.outputs.artifact_prefix }} - is-latest: ${{ steps.set-vars.outputs.is_latest }} - is-latest-major: ${{ steps.set-vars.outputs.is_latest_major }} - sha-source-commit: ${{ steps.set-vars.outputs.sha_source_commit }} - image-version: ${{ steps.set-vars.outputs.image_version }} - steps: - - name: "Set variables" - id: "set-vars" - run: | - GLPI_VERSION="${{ inputs.glpi-version }}" - IMAGE_TAG="${{ inputs.image-tag }}" - - # Determine if glpi-version is a semver tag (e.g., 10.0.18, 11.0.5-rc1) - # vs a branch, commit hash, or URL - IS_SEMVER=false - if echo "$GLPI_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then - IS_SEMVER=true - fi - - # Fetch the commit SHA for this version/branch (for cache-busting) - # Same commit = cache hit, new commit = fresh build - if echo "$GLPI_VERSION" | grep -qE '^https://'; then - # For URLs, use empty (will always rebuild) - SOURCE_COMMIT="" - elif [[ "$GLPI_VERSION" = "latest" ]]; then - # Resolve "latest" to actual tag first, then get commit SHA - RESOLVED_TAG=$(curl -s "https://api.github.com/repos/glpi-project/glpi/releases/latest" | jq -r '.tag_name') - SOURCE_COMMIT=$(curl -s "https://api.github.com/repos/glpi-project/glpi/commits/$RESOLVED_TAG" | jq -r '.sha // empty') - else - SOURCE_COMMIT=$(curl -s "https://api.github.com/repos/glpi-project/glpi/commits/$GLPI_VERSION" | jq -r '.sha // empty') - fi - echo "sha_source_commit=$SOURCE_COMMIT" >> $GITHUB_OUTPUT - - # Compute marketplace dir (only v10 uses old path) - MARKETPLACE_DIR="/var/glpi/marketplace" - if [[ "$IS_SEMVER" = "true" ]]; then - IMAGE_VERSION_MAJOR=$(echo "$GLPI_VERSION" | cut -d. -f1) - if [[ "$IMAGE_VERSION_MAJOR" = "10" ]]; then - MARKETPLACE_DIR="/var/www/glpi/marketplace" - fi - fi - echo "marketplace_dir=$MARKETPLACE_DIR" >> $GITHUB_OUTPUT - - # Compute artifact prefix (unique per workflow call) - if [[ "$IMAGE_TAG" != '' ]]; then - ARTIFACT_PREFIX="$IMAGE_TAG" - else - # Replace slashes and colons with dashes for branches like 11.0/bugfixes - ARTIFACT_PREFIX="$(echo "$GLPI_VERSION" | sed -E 's|[/:]|-|g' | sed -E 's|https?--||')" - fi - echo "artifact_prefix=$ARTIFACT_PREFIX" >> $GITHUB_OUTPUT - - # Compute image version for metadata-action - if [[ "$IMAGE_TAG" != '' ]]; then - IMAGE_VERSION="$IMAGE_TAG" - else - IMAGE_VERSION="$(echo "$GLPI_VERSION" | sed -E 's|[/:]|-|g' | sed -E 's|https?--||')" - fi - echo "image_version=$IMAGE_VERSION" >> $GITHUB_OUTPUT - - # Skip all version checks if: - # - image-tag is provided (explicit override) - # - glpi-version is not a semver (branch, commit, URL) - if [[ "$IMAGE_TAG" != '' ]] || [[ "$IS_SEMVER" != "true" ]]; then - echo "is_latest=false" >> $GITHUB_OUTPUT - echo "is_latest_major=false" >> $GITHUB_OUTPUT - exit 0 - fi - - # Detect prerelease (contains -rc, -beta, -alpha, etc.) - PRERELEASE_FLAG="$( echo "$GLPI_VERSION" | grep -Po '(\-\w+)?$' )" - if [[ -n "$PRERELEASE_FLAG" ]]; then - echo "is_latest=false" >> $GITHUB_OUTPUT - echo "is_latest_major=false" >> $GITHUB_OUTPUT - exit 0 - fi - - # Check if this is the latest release overall - LATEST_TAG=$(curl -s "https://api.github.com/repos/glpi-project/glpi/releases/latest" | jq -r '.tag_name') - if [[ "$GLPI_VERSION" = "$LATEST_TAG" ]]; then - echo "is_latest=true" >> $GITHUB_OUTPUT - else - echo "is_latest=false" >> $GITHUB_OUTPUT - fi - - # Fetch all releases from GitHub API (non-prerelease only) for major series check - RELEASES=$(curl -s "https://api.github.com/repos/glpi-project/glpi/releases?per_page=100" | \ - jq -r '[.[] | select(.prerelease == false) | .tag_name]') - - # Check if this is the latest in its major series (e.g., latest 11.x.x) - IMAGE_VERSION_MAJOR=$(echo "$GLPI_VERSION" | cut -d. -f1) - LATEST_IN_MAJOR=$(echo "$RELEASES" | jq -r --arg major "$IMAGE_VERSION_MAJOR" \ - '[.[] | select(startswith($major + "."))] | .[0] // empty') - if [[ "$GLPI_VERSION" = "$LATEST_IN_MAJOR" ]]; then - echo "is_latest_major=true" >> $GITHUB_OUTPUT - else - echo "is_latest_major=false" >> $GITHUB_OUTPUT - fi - - build: - name: "Build [${{ matrix.platform.suffix }}]" - runs-on: "${{ matrix.platform.runner }}" - needs: [prepare] - strategy: - fail-fast: false - matrix: - platform: - - { arch: "linux/amd64", runner: "ubuntu-24.04", suffix: "amd64" } - - { arch: "linux/arm64", runner: "ubuntu-24.04-arm", suffix: "arm64" } - steps: - - name: "Checkout" - uses: "actions/checkout@v6" - - - name: "Prepare environment" - run: | - platform="${{ matrix.platform.arch }}" - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - - name: "Docker metadata" - id: "meta" - uses: "docker/metadata-action@v5" - with: - images: | - ${{ env.DOCKERHUB_IMAGE }} - ${{ env.GHCR_IMAGE }} - - - name: "Set up QEMU" - uses: "docker/setup-qemu-action@v3" - - - name: "Set up Docker Buildx" - uses: "docker/setup-buildx-action@v3" - - - name: "Login to DockerHub" - uses: "docker/login-action@v3" - with: - username: "${{ secrets.DOCKER_HUB_USERNAME }}" - password: "${{ secrets.DOCKER_HUB_TOKEN }}" - - - name: "Login to Github container registry" - uses: "docker/login-action@v3" - with: - registry: "ghcr.io" - username: "${{ secrets.GHCR_USERNAME }}" - password: "${{ secrets.GHCR_ACCESS_TOKEN }}" - - - name: "Build and push by digest" - id: "build" - uses: "docker/build-push-action@v6" - with: - build-args: | - GLPI_VERSION=${{ inputs.glpi-version }} - GLPI_PATCH_URL=${{ inputs.patch-url }} - GLPI_SHA_SOURCE_COMMIT=${{ needs.prepare.outputs.sha-source-commit }} - BUILDER_IMAGE=php:${{ inputs.php-version }}-cli-alpine - APP_IMAGE=php:${{ inputs.php-version }}-apache - GLPI_MARKETPLACE_DIR=${{ needs.prepare.outputs.marketplace-dir }} - cache-from: | - type=gha,scope=${{ github.ref_name }}-${{ matrix.platform.suffix }} - type=gha,scope=main-${{ matrix.platform.suffix }} - cache-to: "type=gha,mode=max,scope=${{ github.ref_name }}-${{ matrix.platform.suffix }}" - context: "glpi" - labels: "${{ steps.meta.outputs.labels }}" - platforms: "${{ matrix.platform.arch }}" - pull: true - sbom: ${{ inputs.push }} - provenance: ${{ inputs.push && 'mode=max' || 'false' }} - outputs: "type=image,\"name=${{ env.DOCKERHUB_IMAGE }},${{ env.GHCR_IMAGE }}\",push-by-digest=true,name-canonical=true,push=${{ inputs.push }}" - - - name: "Export digest" - if: ${{ inputs.push }} - run: | - mkdir -p ${{ runner.temp }}/digests - digest="${{ steps.build.outputs.digest }}" - touch "${{ runner.temp }}/digests/${digest#sha256:}" - - - name: "Upload digest" - if: ${{ inputs.push }} - uses: "actions/upload-artifact@v4" - with: - name: "digests-${{ needs.prepare.outputs.artifact-prefix }}-${{ env.PLATFORM_PAIR }}" - path: "${{ runner.temp }}/digests/*" - if-no-files-found: "error" - retention-days: 1 - - merge-manifests: - name: "Merge manifests" - runs-on: "ubuntu-latest" - needs: [prepare, build] - if: ${{ inputs.push }} - steps: - - name: "Download digests" - uses: "actions/download-artifact@v4" - with: - path: "${{ runner.temp }}/digests" - pattern: "digests-${{ needs.prepare.outputs.artifact-prefix }}-*" - merge-multiple: true - - name: "Verify digests" - run: | - mkdir -p "${{ runner.temp }}/digests" - if [ -z "$(ls -A ${{ runner.temp }}/digests)" ]; then - echo "::error::No digest files found! Build job may have failed or artifacts expired." - exit 1 - fi - - name: "Set up Docker Buildx" - uses: "docker/setup-buildx-action@v3" - - name: "Login to DockerHub" - uses: "docker/login-action@v3" - with: - username: "${{ secrets.DOCKER_HUB_USERNAME }}" - password: "${{ secrets.DOCKER_HUB_TOKEN }}" - - name: "Login to Github container registry" - uses: "docker/login-action@v3" - with: - registry: "ghcr.io" - username: "${{ secrets.GHCR_USERNAME }}" - password: "${{ secrets.GHCR_ACCESS_TOKEN }}" - - name: "Docker metadata" - id: "meta" - uses: "docker/metadata-action@v5" - with: - images: | - ${{ env.DOCKERHUB_IMAGE }} - ${{ env.GHCR_IMAGE }} - # Disable automatic latest tag - we control it via is-latest - flavor: | - latest=false - tags: | - # If image-tag is explicitly provided, use it directly - type=raw,value=${{ inputs.image-tag }},enable=${{ inputs.image-tag != '' }} - # Base version tag (always, when no explicit image-tag) - type=raw,value=${{ needs.prepare.outputs.image-version }},enable=${{ inputs.image-tag == '' }} - - # Only for releases - type=semver,pattern={{major}},value=${{ inputs.glpi-version }},enable=${{ needs.prepare.outputs.is-latest-major == 'true' }} - type=semver,pattern={{major}}.{{minor}},value=${{ inputs.glpi-version }},enable=${{ needs.prepare.outputs.is-latest-major == 'true' }} - - # Latest - type=raw,value=latest,enable=${{ needs.prepare.outputs.is-latest == 'true' }} - - name: "Create and push manifest" - working-directory: "${{ runner.temp }}/digests" - env: - TAGS: ${{ steps.meta.outputs.tags }} - run: | - # Create manifest for DockerHub - DOCKERHUB_TAGS=$(echo "$TAGS" | grep "^${{ env.DOCKERHUB_IMAGE }}" | xargs -I {} echo "-t {}" | tr '\n' ' ') - if [[ -n "$DOCKERHUB_TAGS" ]]; then - echo "Creating DockerHub manifest..." - docker buildx imagetools create $DOCKERHUB_TAGS \ - $(printf '${{ env.DOCKERHUB_IMAGE }}@sha256:%s ' *) - fi - - # Create manifest for GHCR - GHCR_TAGS=$(echo "$TAGS" | grep "^${{ env.GHCR_IMAGE }}" | xargs -I {} echo "-t {}" | tr '\n' ' ') - if [[ -n "$GHCR_TAGS" ]]; then - echo "Creating GHCR manifest..." - docker buildx imagetools create $GHCR_TAGS \ - $(printf '${{ env.GHCR_IMAGE }}@sha256:%s ' *) - fi - - name: "Inspect image" - run: | - # Get first tag for inspection - docker buildx imagetools inspect ${{ fromJSON(steps.meta.outputs.json).tags[0] }} diff --git a/.github/workflows/glpi-nightly.yml b/.github/workflows/glpi-nightly.yml deleted file mode 100644 index 51a79f0..0000000 --- a/.github/workflows/glpi-nightly.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: "GLPI nightly images" - -on: - push: - branches: - - "main" - paths: - - ".github/workflows/glpi-nightly.yml" - - ".github/workflows/_glpi-build.yml" - - "glpi/**" - pull_request: - paths: - - ".github/workflows/glpi-nightly.yml" - - ".github/workflows/_glpi-build.yml" - - "glpi/**" - schedule: - - cron: '0 0 * * *' - # Enable manual run - workflow_dispatch: - -jobs: - build-main: - name: "Build main branch" - if: ${{ github.repository == 'glpi-project/docker-images' }} - uses: ./.github/workflows/_glpi-build.yml - with: - # Only push on main branch, schedule, or manual dispatch (not on PR), not a if, so we can test build on PR - push: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || github.ref == 'refs/heads/main' }} - glpi-version: "main" - image-tag: "dev-nightly" - secrets: inherit - - build-11: - name: "Build 11.0/bugfixes branch" - if: ${{ github.repository == 'glpi-project/docker-images' }} - uses: ./.github/workflows/_glpi-build.yml - with: - push: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || github.ref == 'refs/heads/main' }} - glpi-version: "11.0/bugfixes" - image-tag: "11.0-nightly" - secrets: inherit - - build-10: - name: "Build 10.0/bugfixes branch" - if: ${{ github.repository == 'glpi-project/docker-images' }} - uses: ./.github/workflows/_glpi-build.yml - with: - push: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || github.ref == 'refs/heads/main' }} - glpi-version: "10.0/bugfixes" - image-tag: "10.0-nightly" - secrets: inherit diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index 9292779..e10d11b 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -1,10 +1,65 @@ -name: "GLPI images for official releases" +name: "GLPI Docker Images" + +# Unified workflow for building GLPI Docker images +# - workflow_call: Called by GLPI repo for releases and nightly builds +# - workflow_dispatch: Manual builds from this repo +# - push/pull_request: CI test builds when workflow changes (no push) + +env: + DOCKERHUB_IMAGE: glpi/glpi + GHCR_IMAGE: ghcr.io/glpi-project/glpi on: - # Enable manual run - # - # It can be executed by a curl command: - # + # Reusable by other repos (e.g., glpi-project/glpi) + workflow_call: + inputs: + push: + description: "Whether to push images to registries" + required: false + type: boolean + default: false + patch-url: + description: "URL to a .diff/.patch file to apply after source download, multiple patches can be separated by space" + required: false + type: string + default: "" + php-version: + required: false + type: string + default: "8.5" + glpi-version: + description: "GLPI version or branch to build" + required: true + type: string + image-tag: + description: "Override image tag. If not set, computed from version." + required: false + type: string + default: "" + secrets: + DOCKER_HUB_USERNAME: + required: true + DOCKER_HUB_TOKEN: + required: true + GHCR_USERNAME: + required: true + GHCR_ACCESS_TOKEN: + required: true + + # CI: Test build on workflow changes (no push) + push: + branches: + - "main" + paths: + - ".github/workflows/glpi.yml" + - "glpi/**" + pull_request: + paths: + - ".github/workflows/glpi.yml" + - "glpi/**" + + # Manual builds + # Can be triggered by curl: # curl -X POST \ # -H "Accept: application/vnd.github.v3+json" \ # -H "Authorization: " \ @@ -38,22 +93,286 @@ on: default: "" jobs: + prepare: + name: "Prepare" + runs-on: "ubuntu-latest" + outputs: + marketplace-dir: ${{ steps.set-vars.outputs.marketplace_dir }} + artifact-prefix: ${{ steps.set-vars.outputs.artifact_prefix }} + is-latest: ${{ steps.set-vars.outputs.is_latest }} + is-latest-major: ${{ steps.set-vars.outputs.is_latest_major }} + sha-source-commit: ${{ steps.set-vars.outputs.sha_source_commit }} + image-version: ${{ steps.set-vars.outputs.image_version }} + # Unified inputs for all trigger types + glpi-version: ${{ steps.set-vars.outputs.glpi_version }} + php-version: ${{ steps.set-vars.outputs.php_version }} + patch-url: ${{ steps.set-vars.outputs.patch_url }} + image-tag: ${{ steps.set-vars.outputs.image_tag }} + push: ${{ steps.set-vars.outputs.push }} + steps: + - name: "Set variables" + id: "set-vars" + run: | + # Use inputs when provided, otherwise use defaults (for push/pull_request on this repo) + GLPI_VERSION="${{ inputs.glpi-version || 'main' }}" + PHP_VERSION="${{ inputs.php-version || '8.5' }}" + PUSH="${{ inputs.push || 'false' }}" + PATCH_URL="${{ inputs.patch-url || '' }}" + IMAGE_TAG="${{ inputs.image-tag || 'docker-image' }}" + + echo "glpi_version=$GLPI_VERSION" >> $GITHUB_OUTPUT + echo "php_version=$PHP_VERSION" >> $GITHUB_OUTPUT + echo "push=$PUSH" >> $GITHUB_OUTPUT + echo "patch_url=$PATCH_URL" >> $GITHUB_OUTPUT + echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT + + # Determine if glpi-version is a semver tag (e.g., 10.0.18, 11.0.5-rc1) + # vs a branch, commit hash, or URL + IS_SEMVER=false + if echo "$GLPI_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then + IS_SEMVER=true + fi + + # Fetch the commit SHA for this version/branch (for cache-busting) + # Same commit = cache hit, new commit = fresh build + if echo "$GLPI_VERSION" | grep -qE '^https://'; then + # For URLs, use empty (will always rebuild) + SOURCE_COMMIT="" + elif [[ "$GLPI_VERSION" = "latest" ]]; then + # Resolve "latest" to actual tag first, then get commit SHA + RESOLVED_TAG=$(curl -s "https://api.github.com/repos/glpi-project/glpi/releases/latest" | jq -r '.tag_name') + SOURCE_COMMIT=$(curl -s "https://api.github.com/repos/glpi-project/glpi/commits/$RESOLVED_TAG" | jq -r '.sha // empty') + else + SOURCE_COMMIT=$(curl -s "https://api.github.com/repos/glpi-project/glpi/commits/$GLPI_VERSION" | jq -r '.sha // empty') + fi + echo "sha_source_commit=$SOURCE_COMMIT" >> $GITHUB_OUTPUT + + # Compute marketplace dir (only v10 uses old path) + MARKETPLACE_DIR="/var/glpi/marketplace" + if [[ "$IS_SEMVER" = "true" ]]; then + IMAGE_VERSION_MAJOR=$(echo "$GLPI_VERSION" | cut -d. -f1) + if [[ "$IMAGE_VERSION_MAJOR" = "10" ]]; then + MARKETPLACE_DIR="/var/www/glpi/marketplace" + fi + fi + echo "marketplace_dir=$MARKETPLACE_DIR" >> $GITHUB_OUTPUT + + # Compute artifact prefix (unique per workflow call) + if [[ "$IMAGE_TAG" != '' ]]; then + ARTIFACT_PREFIX="$IMAGE_TAG" + else + # Replace slashes and colons with dashes for branches like 11.0/bugfixes + ARTIFACT_PREFIX="$(echo "$GLPI_VERSION" | sed -E 's|[/:]|-|g' | sed -E 's|https?--||')" + fi + echo "artifact_prefix=$ARTIFACT_PREFIX" >> $GITHUB_OUTPUT + + # Compute image version for metadata-action + if [[ "$IMAGE_TAG" != '' ]]; then + IMAGE_VERSION="$IMAGE_TAG" + else + IMAGE_VERSION="$(echo "$GLPI_VERSION" | sed -E 's|[/:]|-|g' | sed -E 's|https?--||')" + fi + echo "image_version=$IMAGE_VERSION" >> $GITHUB_OUTPUT + + # Skip all version checks if: + # - image-tag is provided (explicit override) + # - glpi-version is not a semver (branch, commit, URL) + if [[ "$IMAGE_TAG" != '' ]] || [[ "$IS_SEMVER" != "true" ]]; then + echo "is_latest=false" >> $GITHUB_OUTPUT + echo "is_latest_major=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # Detect prerelease (contains -rc, -beta, -alpha, etc.) + PRERELEASE_FLAG="$( echo "$GLPI_VERSION" | grep -Po '(\-\w+)?$' )" + if [[ -n "$PRERELEASE_FLAG" ]]; then + echo "is_latest=false" >> $GITHUB_OUTPUT + echo "is_latest_major=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # Check if this is the latest release overall + LATEST_TAG=$(curl -s "https://api.github.com/repos/glpi-project/glpi/releases/latest" | jq -r '.tag_name') + if [[ "$GLPI_VERSION" = "$LATEST_TAG" ]]; then + echo "is_latest=true" >> $GITHUB_OUTPUT + else + echo "is_latest=false" >> $GITHUB_OUTPUT + fi + + # Fetch all releases from GitHub API (non-prerelease only) for major series check + RELEASES=$(curl -s "https://api.github.com/repos/glpi-project/glpi/releases?per_page=100" | \ + jq -r '[.[] | select(.prerelease == false) | .tag_name]') + + # Check if this is the latest in its major series (e.g., latest 11.x.x) + IMAGE_VERSION_MAJOR=$(echo "$GLPI_VERSION" | cut -d. -f1) + LATEST_IN_MAJOR=$(echo "$RELEASES" | jq -r --arg major "$IMAGE_VERSION_MAJOR" \ + '[.[] | select(startswith($major + "."))] | .[0] // empty') + if [[ "$GLPI_VERSION" = "$LATEST_IN_MAJOR" ]]; then + echo "is_latest_major=true" >> $GITHUB_OUTPUT + else + echo "is_latest_major=false" >> $GITHUB_OUTPUT + fi + build: - name: "Build GLPI ${{ inputs.glpi-version }}" - uses: ./.github/workflows/_glpi-build.yml - with: - push: ${{ inputs.push }} - php-version: ${{ inputs.php-version }} - glpi-version: ${{ inputs.glpi-version }} - image-tag: ${{ inputs.image-tag }} - # Combine manual patch-url input with automatic version-based patches - # Format: Use startsWith() for ranges, exact match for single versions - # Multiple patches: separate URLs with spaces - patch-url: >- - ${{ inputs.patch-url }} - ${{ - (startsWith(inputs.glpi-version, '11.0.') && inputs.glpi-version < '11.0.5') - && 'https://patch-diff.githubusercontent.com/raw/glpi-project/glpi/pull/22381.diff' - || '' - }} - secrets: inherit + name: "Build [${{ matrix.platform.suffix }}]" + runs-on: "${{ matrix.platform.runner }}" + needs: [prepare] + strategy: + fail-fast: false + matrix: + platform: + - { arch: "linux/amd64", runner: "ubuntu-24.04", suffix: "amd64" } + - { arch: "linux/arm64", runner: "ubuntu-24.04-arm", suffix: "arm64" } + steps: + - name: "Checkout" + uses: "actions/checkout@v6" + + - name: "Prepare environment" + run: | + platform="${{ matrix.platform.arch }}" + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: "Docker metadata" + id: "meta" + uses: "docker/metadata-action@v5" + with: + images: | + ${{ env.DOCKERHUB_IMAGE }} + ${{ env.GHCR_IMAGE }} + + - name: "Set up QEMU" + uses: "docker/setup-qemu-action@v3" + + - name: "Set up Docker Buildx" + uses: "docker/setup-buildx-action@v3" + + - name: "Login to DockerHub" + uses: "docker/login-action@v3" + with: + username: "${{ secrets.DOCKER_HUB_USERNAME }}" + password: "${{ secrets.DOCKER_HUB_TOKEN }}" + + - name: "Login to Github container registry" + uses: "docker/login-action@v3" + with: + registry: "ghcr.io" + username: "${{ secrets.GHCR_USERNAME }}" + password: "${{ secrets.GHCR_ACCESS_TOKEN }}" + + - name: "Build and push by digest" + id: "build" + uses: "docker/build-push-action@v6" + with: + build-args: | + GLPI_VERSION=${{ needs.prepare.outputs.glpi-version }} + GLPI_PATCH_URL=${{ needs.prepare.outputs.patch-url }} + GLPI_SHA_SOURCE_COMMIT=${{ needs.prepare.outputs.sha-source-commit }} + BUILDER_IMAGE=php:${{ needs.prepare.outputs.php-version }}-cli-alpine + APP_IMAGE=php:${{ needs.prepare.outputs.php-version }}-apache + GLPI_MARKETPLACE_DIR=${{ needs.prepare.outputs.marketplace-dir }} + cache-from: | + type=gha,scope=${{ github.ref_name }}-${{ matrix.platform.suffix }} + type=gha,scope=main-${{ matrix.platform.suffix }} + cache-to: "type=gha,mode=max,scope=${{ github.ref_name }}-${{ matrix.platform.suffix }}" + context: "glpi" + labels: "${{ steps.meta.outputs.labels }}" + platforms: "${{ matrix.platform.arch }}" + pull: true + sbom: ${{ needs.prepare.outputs.push == 'true' }} + provenance: ${{ needs.prepare.outputs.push == 'true' && 'mode=max' || 'false' }} + outputs: "type=image,\"name=${{ env.DOCKERHUB_IMAGE }},${{ env.GHCR_IMAGE }}\",push-by-digest=true,name-canonical=true,push=${{ needs.prepare.outputs.push }}" + + - name: "Export digest" + if: ${{ needs.prepare.outputs.push == 'true' }} + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: "Upload digest" + if: ${{ needs.prepare.outputs.push == 'true' }} + uses: "actions/upload-artifact@v4" + with: + name: "digests-${{ needs.prepare.outputs.artifact-prefix }}-${{ env.PLATFORM_PAIR }}" + path: "${{ runner.temp }}/digests/*" + if-no-files-found: "error" + retention-days: 1 + + merge-manifests: + name: "Merge manifests" + runs-on: "ubuntu-latest" + needs: [prepare, build] + if: ${{ needs.prepare.outputs.push == 'true' }} + steps: + - name: "Download digests" + uses: "actions/download-artifact@v4" + with: + path: "${{ runner.temp }}/digests" + pattern: "digests-${{ needs.prepare.outputs.artifact-prefix }}-*" + merge-multiple: true + - name: "Verify digests" + run: | + mkdir -p "${{ runner.temp }}/digests" + if [ -z "$(ls -A ${{ runner.temp }}/digests)" ]; then + echo "::error::No digest files found! Build job may have failed or artifacts expired." + exit 1 + fi + - name: "Set up Docker Buildx" + uses: "docker/setup-buildx-action@v3" + - name: "Login to DockerHub" + uses: "docker/login-action@v3" + with: + username: "${{ secrets.DOCKER_HUB_USERNAME }}" + password: "${{ secrets.DOCKER_HUB_TOKEN }}" + - name: "Login to Github container registry" + uses: "docker/login-action@v3" + with: + registry: "ghcr.io" + username: "${{ secrets.GHCR_USERNAME }}" + password: "${{ secrets.GHCR_ACCESS_TOKEN }}" + - name: "Docker metadata" + id: "meta" + uses: "docker/metadata-action@v5" + with: + images: | + ${{ env.DOCKERHUB_IMAGE }} + ${{ env.GHCR_IMAGE }} + # Disable automatic latest tag - we control it via is-latest + flavor: | + latest=false + tags: | + # If image-tag is explicitly provided, use it directly + type=raw,value=${{ needs.prepare.outputs.image-tag }},enable=${{ needs.prepare.outputs.image-tag != '' }} + # Base version tag (always, when no explicit image-tag) + type=raw,value=${{ needs.prepare.outputs.image-version }},enable=${{ needs.prepare.outputs.image-tag == '' }} + + # Only for releases + type=semver,pattern={{major}},value=${{ needs.prepare.outputs.glpi-version }},enable=${{ needs.prepare.outputs.is-latest-major == 'true' }} + type=semver,pattern={{major}}.{{minor}},value=${{ needs.prepare.outputs.glpi-version }},enable=${{ needs.prepare.outputs.is-latest-major == 'true' }} + + # Latest + type=raw,value=latest,enable=${{ needs.prepare.outputs.is-latest == 'true' }} + - name: "Create and push manifest" + working-directory: "${{ runner.temp }}/digests" + env: + TAGS: ${{ steps.meta.outputs.tags }} + run: | + # Create manifest for DockerHub + DOCKERHUB_TAGS=$(echo "$TAGS" | grep "^${{ env.DOCKERHUB_IMAGE }}" | xargs -I {} echo "-t {}" | tr '\n' ' ') + if [[ -n "$DOCKERHUB_TAGS" ]]; then + echo "Creating DockerHub manifest..." + docker buildx imagetools create $DOCKERHUB_TAGS \ + $(printf '${{ env.DOCKERHUB_IMAGE }}@sha256:%s ' *) + fi + + # Create manifest for GHCR + GHCR_TAGS=$(echo "$TAGS" | grep "^${{ env.GHCR_IMAGE }}" | xargs -I {} echo "-t {}" | tr '\n' ' ') + if [[ -n "$GHCR_TAGS" ]]; then + echo "Creating GHCR manifest..." + docker buildx imagetools create $GHCR_TAGS \ + $(printf '${{ env.GHCR_IMAGE }}@sha256:%s ' *) + fi + - name: "Inspect image" + run: | + # Get first tag for inspection + docker buildx imagetools inspect ${{ fromJSON(steps.meta.outputs.json).tags[0] }} From 581b5dcca6644947229ec29726f4868d4abcf1f5 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Tue, 13 Jan 2026 15:48:39 +0100 Subject: [PATCH 14/32] Update doc --- .github/workflows/glpi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index e10d11b..8c7592c 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -82,7 +82,7 @@ on: required: true type: string image-tag: - description: "Override image tag (glpi-version otherwise). i.e: '11.0.5-beta1'" + description: "Override image tag (glpi-version otherwise). i.e: '12.0.0-prebuild-20260615-1'" required: false type: string default: "" From 4ea03d4bd0487d6752ae689d28191c965d41a5ac Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Tue, 13 Jan 2026 16:34:01 +0100 Subject: [PATCH 15/32] PATCH apply now move into builder not downloader --- glpi/Dockerfile | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/glpi/Dockerfile b/glpi/Dockerfile index 52c7d16..b2d06bf 100644 --- a/glpi/Dockerfile +++ b/glpi/Dockerfile @@ -14,14 +14,10 @@ FROM alpine AS downloader # - A commit hash: e.g., "2186bc6bd410d8bcb048637b3c0fb86b7e320c0a" # - A direct URL: e.g., "https://github.com/glpi-project/glpi/archive/2186bc6.tar.gz" ARG GLPI_VERSION=latest -# Optional patch URL to apply after download -ARG GLPI_PATCH_URL="" # Cache-busting: SHA from GitHub API for _glpi-build.yaml workflows ARG GLPI_SHA_SOURCE_COMMIT="" -RUN apk add --no-cache curl jq patch - -WORKDIR /download +RUN apk add --no-cache curl jq # Resolve version and download source RUN set -ex; \ @@ -39,23 +35,16 @@ RUN set -ex; \ URL="https://github.com/glpi-project/glpi/archive/${VERSION}.tar.gz"; \ fi; \ echo "Downloading GLPI from $URL"; \ - curl -L "$URL" | tar xz --strip-components=1 - -# Apply optional patches if GLPI_PATCH_URL is provided (space-separated URLs) -ARG GLPI_PATCH_URL -RUN set -ex; \ - if [ -n "${GLPI_PATCH_URL}" ]; then \ - for PATCH in ${GLPI_PATCH_URL}; do \ - echo "Applying patch from ${PATCH}"; \ - curl -L "${PATCH}" | patch -p1; \ - done; \ - fi + curl -L "$URL" -o /glpi.tar.gz ##### # Builder image ##### FROM $BUILDER_IMAGE AS builder +# Optional patch URL to apply after download +ARG GLPI_PATCH_URL="" + # Copy composer binary from latest composer image. COPY --from=composer:latest /usr/bin/composer /usr/bin/composer @@ -76,8 +65,8 @@ RUN \ # Install nodejs and npm. && apk add nodejs npm \ \ - # Install git and zip used by composer when fetching dependencies. - && apk add git unzip \ + # Install git, zip, and curl used by composer. + && apk add git unzip curl \ \ # Install intl PHP extension required to execute the GLPI console. && apk add icu-dev \ @@ -90,12 +79,22 @@ RUN \ # Update PHP configuration. RUN echo "memory_limit = 512M" >> /usr/local/etc/php/conf.d/docker-php-memory.ini -# Copy GLPI source from downloader stage. -COPY --from=downloader --chown=www-data:www-data /download /usr/src/glpi +# Copy tarball from downloader and extract (preserves permissions from archive) +COPY --from=downloader /glpi.tar.gz /tmp/glpi.tar.gz +RUN mkdir -p /usr/src/glpi \ + && tar xzf /tmp/glpi.tar.gz --strip-components=1 --directory=/usr/src/glpi \ + && rm /tmp/glpi.tar.gz \ + && chown -R www-data:www-data /usr/src/glpi -# Ensure scripts are executable (artifact upload strips permissions) -RUN find /usr/src/glpi/tools -name "*.sh" -exec chmod +x {} \; \ - && chmod +x /usr/src/glpi/bin/console +# Apply optional patches if GLPI_PATCH_URL is provided (space-separated URLs) +RUN set -ex; \ + if [ -n "${GLPI_PATCH_URL}" ]; then \ + cd /usr/src/glpi && \ + for PATCH in ${GLPI_PATCH_URL}; do \ + echo "Applying patch from ${PATCH}"; \ + curl -L "${PATCH}" | patch -p1; \ + done; \ + fi # Build GLPI app USER www-data From 9e354c6d0b4f027f72952b654a130122bb06e2f7 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 14 Jan 2026 09:04:42 +0100 Subject: [PATCH 16/32] Cache is now based on sha-source-commit --- .github/workflows/glpi.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index 8c7592c..ec91641 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -271,9 +271,9 @@ jobs: APP_IMAGE=php:${{ needs.prepare.outputs.php-version }}-apache GLPI_MARKETPLACE_DIR=${{ needs.prepare.outputs.marketplace-dir }} cache-from: | - type=gha,scope=${{ github.ref_name }}-${{ matrix.platform.suffix }} + type=gha,scope=${{ needs.prepare.outputs.sha-source-commit }}-${{ matrix.platform.suffix }} type=gha,scope=main-${{ matrix.platform.suffix }} - cache-to: "type=gha,mode=max,scope=${{ github.ref_name }}-${{ matrix.platform.suffix }}" + cache-to: "type=gha,mode=max,scope=${{ needs.prepare.outputs.sha-source-commit }}-${{ matrix.platform.suffix }}" context: "glpi" labels: "${{ steps.meta.outputs.labels }}" platforms: "${{ matrix.platform.arch }}" From 86086518c9518215dcf08ae55fa175d32eaebc1f Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 14 Jan 2026 09:44:25 +0100 Subject: [PATCH 17/32] Rename command short name to the long option name --- .github/workflows/glpi.yml | 20 ++++++++++---------- CONTRIBUTING.md | 6 +++--- glpi/Dockerfile | 20 ++++++++++---------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index ec91641..1c2fa2a 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -129,13 +129,13 @@ jobs: # Determine if glpi-version is a semver tag (e.g., 10.0.18, 11.0.5-rc1) # vs a branch, commit hash, or URL IS_SEMVER=false - if echo "$GLPI_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then + if echo "$GLPI_VERSION" | grep --quiet --extended-regexp '^[0-9]+\.[0-9]+\.[0-9]+(\-\w+)?$'; then IS_SEMVER=true fi # Fetch the commit SHA for this version/branch (for cache-busting) # Same commit = cache hit, new commit = fresh build - if echo "$GLPI_VERSION" | grep -qE '^https://'; then + if echo "$GLPI_VERSION" | grep --quiet --extended-regexp '^https://'; then # For URLs, use empty (will always rebuild) SOURCE_COMMIT="" elif [[ "$GLPI_VERSION" = "latest" ]]; then @@ -150,7 +150,7 @@ jobs: # Compute marketplace dir (only v10 uses old path) MARKETPLACE_DIR="/var/glpi/marketplace" if [[ "$IS_SEMVER" = "true" ]]; then - IMAGE_VERSION_MAJOR=$(echo "$GLPI_VERSION" | cut -d. -f1) + IMAGE_VERSION_MAJOR=$(echo "$GLPI_VERSION" | cut --delimiter=. --fields=1) if [[ "$IMAGE_VERSION_MAJOR" = "10" ]]; then MARKETPLACE_DIR="/var/www/glpi/marketplace" fi @@ -162,7 +162,7 @@ jobs: ARTIFACT_PREFIX="$IMAGE_TAG" else # Replace slashes and colons with dashes for branches like 11.0/bugfixes - ARTIFACT_PREFIX="$(echo "$GLPI_VERSION" | sed -E 's|[/:]|-|g' | sed -E 's|https?--||')" + ARTIFACT_PREFIX="$(echo "$GLPI_VERSION" | sed --regexp-extended 's|[/:]|-|g' | sed --regexp-extended 's|https?--||')" fi echo "artifact_prefix=$ARTIFACT_PREFIX" >> $GITHUB_OUTPUT @@ -170,7 +170,7 @@ jobs: if [[ "$IMAGE_TAG" != '' ]]; then IMAGE_VERSION="$IMAGE_TAG" else - IMAGE_VERSION="$(echo "$GLPI_VERSION" | sed -E 's|[/:]|-|g' | sed -E 's|https?--||')" + IMAGE_VERSION="$(echo "$GLPI_VERSION" | sed --regexp-extended 's|[/:]|-|g' | sed --regexp-extended 's|https?--||')" fi echo "image_version=$IMAGE_VERSION" >> $GITHUB_OUTPUT @@ -184,7 +184,7 @@ jobs: fi # Detect prerelease (contains -rc, -beta, -alpha, etc.) - PRERELEASE_FLAG="$( echo "$GLPI_VERSION" | grep -Po '(\-\w+)?$' )" + PRERELEASE_FLAG="$( echo "$GLPI_VERSION" | grep --perl-regexp --only-matching '(\-\w+)?$' )" if [[ -n "$PRERELEASE_FLAG" ]]; then echo "is_latest=false" >> $GITHUB_OUTPUT echo "is_latest_major=false" >> $GITHUB_OUTPUT @@ -204,7 +204,7 @@ jobs: jq -r '[.[] | select(.prerelease == false) | .tag_name]') # Check if this is the latest in its major series (e.g., latest 11.x.x) - IMAGE_VERSION_MAJOR=$(echo "$GLPI_VERSION" | cut -d. -f1) + IMAGE_VERSION_MAJOR=$(echo "$GLPI_VERSION" | cut --delimiter=. --fields=1) LATEST_IN_MAJOR=$(echo "$RELEASES" | jq -r --arg major "$IMAGE_VERSION_MAJOR" \ '[.[] | select(startswith($major + "."))] | .[0] // empty') if [[ "$GLPI_VERSION" = "$LATEST_IN_MAJOR" ]]; then @@ -313,7 +313,7 @@ jobs: - name: "Verify digests" run: | mkdir -p "${{ runner.temp }}/digests" - if [ -z "$(ls -A ${{ runner.temp }}/digests)" ]; then + if [ -z "$(ls --almost-all ${{ runner.temp }}/digests)" ]; then echo "::error::No digest files found! Build job may have failed or artifacts expired." exit 1 fi @@ -358,7 +358,7 @@ jobs: TAGS: ${{ steps.meta.outputs.tags }} run: | # Create manifest for DockerHub - DOCKERHUB_TAGS=$(echo "$TAGS" | grep "^${{ env.DOCKERHUB_IMAGE }}" | xargs -I {} echo "-t {}" | tr '\n' ' ') + DOCKERHUB_TAGS=$(echo "$TAGS" | grep "^${{ env.DOCKERHUB_IMAGE }}" | xargs --replace={} echo "-t {}" | tr '\n' ' ') if [[ -n "$DOCKERHUB_TAGS" ]]; then echo "Creating DockerHub manifest..." docker buildx imagetools create $DOCKERHUB_TAGS \ @@ -366,7 +366,7 @@ jobs: fi # Create manifest for GHCR - GHCR_TAGS=$(echo "$TAGS" | grep "^${{ env.GHCR_IMAGE }}" | xargs -I {} echo "-t {}" | tr '\n' ' ') + GHCR_TAGS=$(echo "$TAGS" | grep "^${{ env.GHCR_IMAGE }}" | xargs --replace={} echo "-t {}" | tr '\n' ' ') if [[ -n "$GHCR_TAGS" ]]; then echo "Creating GHCR manifest..." docker buildx imagetools create $GHCR_TAGS \ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9faa73..b4a2a57 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,7 +68,7 @@ services: We provide a `docker-compose.test.yml` file to test the `glpi` container: ```bash -docker compose -f docker-compose.test.yml up --build +docker compose --file docker-compose.test.yml up --build ``` ### Verify @@ -76,7 +76,7 @@ docker compose -f docker-compose.test.yml up --build Once the containers are up: 1. Check the logs to see the installation progress: ```bash - docker compose -f docker-compose.test.yml logs -f glpi + docker compose --file docker-compose.test.yml logs --follow glpi ``` 2. Wait for the "GLPI installation completed successfully!" message. 3. Access GLPI at [http://localhost:8080](http://localhost:8080). @@ -87,5 +87,5 @@ Once the containers are up: To stop and remove the test containers: ```bash -docker compose -f docker-compose.test.yml down -v +docker compose --file docker-compose.test.yml down --volumes ``` diff --git a/glpi/Dockerfile b/glpi/Dockerfile index b2d06bf..6dc1389 100644 --- a/glpi/Dockerfile +++ b/glpi/Dockerfile @@ -23,19 +23,19 @@ RUN apk add --no-cache curl jq RUN set -ex; \ INPUT="${GLPI_VERSION}"; \ # If input starts with https://, use it as-is \ - if echo "$INPUT" | grep -q '^https://'; then \ + if echo "$INPUT" | grep --quiet '^https://'; then \ URL="$INPUT"; \ else \ VERSION="$INPUT"; \ # If "latest", resolve from GitHub API \ if [ "$VERSION" = "latest" ]; then \ - VERSION=$(curl -s https://api.github.com/repos/glpi-project/glpi/releases/latest | jq -r .tag_name); \ + VERSION=$(curl --silent https://api.github.com/repos/glpi-project/glpi/releases/latest | jq --raw-output .tag_name); \ fi; \ # Use GitHub's generic archive format (works for branches, tags, and commits) URL="https://github.com/glpi-project/glpi/archive/${VERSION}.tar.gz"; \ fi; \ echo "Downloading GLPI from $URL"; \ - curl -L "$URL" -o /glpi.tar.gz + curl --location "$URL" --output /glpi.tar.gz ##### # Builder image @@ -81,10 +81,10 @@ RUN echo "memory_limit = 512M" >> /usr/local/etc/php/conf.d/docker-php-memory.in # Copy tarball from downloader and extract (preserves permissions from archive) COPY --from=downloader /glpi.tar.gz /tmp/glpi.tar.gz -RUN mkdir -p /usr/src/glpi \ - && tar xzf /tmp/glpi.tar.gz --strip-components=1 --directory=/usr/src/glpi \ +RUN mkdir --parents /usr/src/glpi \ + && tar --extract --gzip --file=/tmp/glpi.tar.gz --strip-components=1 --directory=/usr/src/glpi \ && rm /tmp/glpi.tar.gz \ - && chown -R www-data:www-data /usr/src/glpi + && chown --recursive www-data:www-data /usr/src/glpi # Apply optional patches if GLPI_PATCH_URL is provided (space-separated URLs) RUN set -ex; \ @@ -92,7 +92,7 @@ RUN set -ex; \ cd /usr/src/glpi && \ for PATCH in ${GLPI_PATCH_URL}; do \ echo "Applying patch from ${PATCH}"; \ - curl -L "${PATCH}" | patch -p1; \ + curl --location "${PATCH}" | patch --strip=1; \ done; \ fi @@ -116,8 +116,8 @@ It can be used to test latest features and bug fixes." \ org.opencontainers.image.source="git@github.com:glpi-project/docker-images" RUN apt-get update \ - && PHP_MAJOR_VERSION="$(echo $PHP_VERSION | cut -d '.' -f 1)" \ - && PHP_MINOR_VERSION="$(echo $PHP_VERSION | cut -d '.' -f 2)" \ + && PHP_MAJOR_VERSION="$(echo $PHP_VERSION | cut --delimiter='.' --fields=1)" \ + && PHP_MINOR_VERSION="$(echo $PHP_VERSION | cut --delimiter='.' --fields=2)" \ \ # Install APCU PHP extension. && pecl install apcu \ @@ -190,7 +190,7 @@ RUN apt-get update \ # Use the default production configuration # ref: https://github.com/docker-library/docs/tree/master/php#configuration -RUN ln -s $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini +RUN ln --symbolic $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini # Allow apache process to bind to port 80 as non-root user RUN setcap cap_net_bind_service=+ep /usr/sbin/apache2 From 5ffd314b0cdca85bb1ab88787040fbafe7791457 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 14 Jan 2026 09:51:51 +0100 Subject: [PATCH 18/32] Update SOURCE_COMMIT for url --- .github/workflows/glpi.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index 1c2fa2a..bc4c5be 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -136,8 +136,8 @@ jobs: # Fetch the commit SHA for this version/branch (for cache-busting) # Same commit = cache hit, new commit = fresh build if echo "$GLPI_VERSION" | grep --quiet --extended-regexp '^https://'; then - # For URLs, use empty (will always rebuild) - SOURCE_COMMIT="" + # For URLs, use a random value to force rebuild, as content behind a URL can change between builds. + SOURCE_COMMIT="$(head -200 /dev/urandom | sha1sum | cut --fields 1 --delimiter " ")" elif [[ "$GLPI_VERSION" = "latest" ]]; then # Resolve "latest" to actual tag first, then get commit SHA RESOLVED_TAG=$(curl -s "https://api.github.com/repos/glpi-project/glpi/releases/latest" | jq -r '.tag_name') From 1da2f1ea7879aaf2c7f481b2639dce165e8d205f Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 14 Jan 2026 09:58:02 +0100 Subject: [PATCH 19/32] Rename SOURCE_COMMIT to a more relevant CACHE_KEY --- .github/workflows/glpi.yml | 16 ++++++++-------- glpi/Dockerfile | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index bc4c5be..42ffa27 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -101,7 +101,7 @@ jobs: artifact-prefix: ${{ steps.set-vars.outputs.artifact_prefix }} is-latest: ${{ steps.set-vars.outputs.is_latest }} is-latest-major: ${{ steps.set-vars.outputs.is_latest_major }} - sha-source-commit: ${{ steps.set-vars.outputs.sha_source_commit }} + cache-key: ${{ steps.set-vars.outputs.cache_key }} image-version: ${{ steps.set-vars.outputs.image_version }} # Unified inputs for all trigger types glpi-version: ${{ steps.set-vars.outputs.glpi_version }} @@ -137,15 +137,15 @@ jobs: # Same commit = cache hit, new commit = fresh build if echo "$GLPI_VERSION" | grep --quiet --extended-regexp '^https://'; then # For URLs, use a random value to force rebuild, as content behind a URL can change between builds. - SOURCE_COMMIT="$(head -200 /dev/urandom | sha1sum | cut --fields 1 --delimiter " ")" + CACHE_KEY="$(head -200 /dev/urandom | sha1sum | cut --fields 1 --delimiter " ")" elif [[ "$GLPI_VERSION" = "latest" ]]; then # Resolve "latest" to actual tag first, then get commit SHA RESOLVED_TAG=$(curl -s "https://api.github.com/repos/glpi-project/glpi/releases/latest" | jq -r '.tag_name') - SOURCE_COMMIT=$(curl -s "https://api.github.com/repos/glpi-project/glpi/commits/$RESOLVED_TAG" | jq -r '.sha // empty') + CACHE_KEY=$(curl -s "https://api.github.com/repos/glpi-project/glpi/commits/$RESOLVED_TAG" | jq -r '.sha // empty') else - SOURCE_COMMIT=$(curl -s "https://api.github.com/repos/glpi-project/glpi/commits/$GLPI_VERSION" | jq -r '.sha // empty') + CACHE_KEY=$(curl -s "https://api.github.com/repos/glpi-project/glpi/commits/$GLPI_VERSION" | jq -r '.sha // empty') fi - echo "sha_source_commit=$SOURCE_COMMIT" >> $GITHUB_OUTPUT + echo "cache_key=$CACHE_KEY" >> $GITHUB_OUTPUT # Compute marketplace dir (only v10 uses old path) MARKETPLACE_DIR="/var/glpi/marketplace" @@ -266,14 +266,14 @@ jobs: build-args: | GLPI_VERSION=${{ needs.prepare.outputs.glpi-version }} GLPI_PATCH_URL=${{ needs.prepare.outputs.patch-url }} - GLPI_SHA_SOURCE_COMMIT=${{ needs.prepare.outputs.sha-source-commit }} + GLPI_CACHE_KEY=${{ needs.prepare.outputs.cache-key }} BUILDER_IMAGE=php:${{ needs.prepare.outputs.php-version }}-cli-alpine APP_IMAGE=php:${{ needs.prepare.outputs.php-version }}-apache GLPI_MARKETPLACE_DIR=${{ needs.prepare.outputs.marketplace-dir }} cache-from: | - type=gha,scope=${{ needs.prepare.outputs.sha-source-commit }}-${{ matrix.platform.suffix }} + type=gha,scope=${{ needs.prepare.outputs.cache-key }}-${{ matrix.platform.suffix }} type=gha,scope=main-${{ matrix.platform.suffix }} - cache-to: "type=gha,mode=max,scope=${{ needs.prepare.outputs.sha-source-commit }}-${{ matrix.platform.suffix }}" + cache-to: "type=gha,mode=max,scope=${{ needs.prepare.outputs.cache-key }}-${{ matrix.platform.suffix }}" context: "glpi" labels: "${{ steps.meta.outputs.labels }}" platforms: "${{ matrix.platform.arch }}" diff --git a/glpi/Dockerfile b/glpi/Dockerfile index 6dc1389..b5428b6 100644 --- a/glpi/Dockerfile +++ b/glpi/Dockerfile @@ -14,8 +14,8 @@ FROM alpine AS downloader # - A commit hash: e.g., "2186bc6bd410d8bcb048637b3c0fb86b7e320c0a" # - A direct URL: e.g., "https://github.com/glpi-project/glpi/archive/2186bc6.tar.gz" ARG GLPI_VERSION=latest -# Cache-busting: SHA from GitHub API for _glpi-build.yaml workflows -ARG GLPI_SHA_SOURCE_COMMIT="" +# Cache-busting key for glpi workflows +ARG GLPI_CACHE_KEY="" RUN apk add --no-cache curl jq From 3a871087057d3d4fd147001f3482ce4480c61acd Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 14 Jan 2026 10:18:46 +0100 Subject: [PATCH 20/32] Fix order of input to reduce diff message log --- .github/workflows/glpi.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index 42ffa27..7269356 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -13,6 +13,10 @@ on: # Reusable by other repos (e.g., glpi-project/glpi) workflow_call: inputs: + glpi-version: + description: "GLPI version or branch to build" + required: true + type: string push: description: "Whether to push images to registries" required: false @@ -27,10 +31,6 @@ on: required: false type: string default: "8.5" - glpi-version: - description: "GLPI version or branch to build" - required: true - type: string image-tag: description: "Override image tag. If not set, computed from version." required: false From 07ca706ce5b63c5bcf6a0d68de93f99324a05044 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 14 Jan 2026 12:29:10 +0100 Subject: [PATCH 21/32] Move MARKETPLACE_DIR into Dockerfile so 10.x is supported --- .github/workflows/glpi.yml | 11 ----------- glpi/Dockerfile | 15 +++++++++++++-- glpi/files/opt/glpi/entrypoint.sh | 5 +++++ 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index 7269356..9745641 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -97,7 +97,6 @@ jobs: name: "Prepare" runs-on: "ubuntu-latest" outputs: - marketplace-dir: ${{ steps.set-vars.outputs.marketplace_dir }} artifact-prefix: ${{ steps.set-vars.outputs.artifact_prefix }} is-latest: ${{ steps.set-vars.outputs.is_latest }} is-latest-major: ${{ steps.set-vars.outputs.is_latest_major }} @@ -147,15 +146,6 @@ jobs: fi echo "cache_key=$CACHE_KEY" >> $GITHUB_OUTPUT - # Compute marketplace dir (only v10 uses old path) - MARKETPLACE_DIR="/var/glpi/marketplace" - if [[ "$IS_SEMVER" = "true" ]]; then - IMAGE_VERSION_MAJOR=$(echo "$GLPI_VERSION" | cut --delimiter=. --fields=1) - if [[ "$IMAGE_VERSION_MAJOR" = "10" ]]; then - MARKETPLACE_DIR="/var/www/glpi/marketplace" - fi - fi - echo "marketplace_dir=$MARKETPLACE_DIR" >> $GITHUB_OUTPUT # Compute artifact prefix (unique per workflow call) if [[ "$IMAGE_TAG" != '' ]]; then @@ -269,7 +259,6 @@ jobs: GLPI_CACHE_KEY=${{ needs.prepare.outputs.cache-key }} BUILDER_IMAGE=php:${{ needs.prepare.outputs.php-version }}-cli-alpine APP_IMAGE=php:${{ needs.prepare.outputs.php-version }}-apache - GLPI_MARKETPLACE_DIR=${{ needs.prepare.outputs.marketplace-dir }} cache-from: | type=gha,scope=${{ needs.prepare.outputs.cache-key }}-${{ matrix.platform.suffix }} type=gha,scope=main-${{ matrix.platform.suffix }} diff --git a/glpi/Dockerfile b/glpi/Dockerfile index b5428b6..5b8d311 100644 --- a/glpi/Dockerfile +++ b/glpi/Dockerfile @@ -106,7 +106,6 @@ RUN /usr/src/glpi/tools/build_glpi.sh ##### FROM $APP_IMAGE -ARG GLPI_MARKETPLACE_DIR=/var/glpi/marketplace LABEL \ org.opencontainers.image.title="GLPI nightly build" \ @@ -209,15 +208,27 @@ RUN a2enconf zzz-glpi.conf # Copy GLPI application. COPY --from=builder --chown=www-data:www-data /usr/src/glpi /var/www/glpi +# Detect GLPI major version from version/ folder and set marketplace directory +# v10.x uses /var/www/glpi/marketplace, v11+ uses /var/glpi/marketplace +RUN GLPI_VERSION_FILE=$(ls /var/www/glpi/version/ | sort --version-sort | tail --lines=1); \ + GLPI_MAJOR=$(echo "$GLPI_VERSION_FILE" | cut --delimiter='.' --fields=1); \ + if [ "$GLPI_MAJOR" = "10" ]; then \ + MARKETPLACE_DIR="/var/www/glpi/marketplace"; \ + else \ + MARKETPLACE_DIR="/var/glpi/marketplace"; \ + fi; \ + echo "GLPI_MARKETPLACE_DIR=$MARKETPLACE_DIR" >> /etc/glpi_env + # Declare a volume for "config", "marketplace" and "files" directory RUN mkdir /var/glpi && chown www-data:www-data /var/glpi VOLUME /var/glpi # Define GLPI environment variables +# Note: GLPI_MARKETPLACE_DIR is sourced from /etc/glpi_env at container startup +# We cannot dynamically set an ENV value based on the output of a RUN command ENV \ GLPI_INSTALL_MODE=DOCKER \ GLPI_CONFIG_DIR=/var/glpi/config \ - GLPI_MARKETPLACE_DIR=${GLPI_MARKETPLACE_DIR} \ GLPI_VAR_DIR=/var/glpi/files \ GLPI_LOG_DIR=/var/glpi/logs \ GLPI_SKIP_AUTOINSTALL=false \ diff --git a/glpi/files/opt/glpi/entrypoint.sh b/glpi/files/opt/glpi/entrypoint.sh index b7170fc..f3be8a2 100644 --- a/glpi/files/opt/glpi/entrypoint.sh +++ b/glpi/files/opt/glpi/entrypoint.sh @@ -1,6 +1,11 @@ #!/bin/bash set -e -u -o pipefail +# Source GLPI environment variables (includes GLPI_MARKETPLACE_DIR detected at build time) +if [ -f /etc/glpi_env ]; then + export $(cat /etc/glpi_env | xargs) +fi + /opt/glpi/entrypoint/init-volumes-directories.sh /opt/glpi/entrypoint/forward-logs.sh /opt/glpi/entrypoint/wait-for-db.sh entrypoint From 113921e79ca34498ba31987e64a8236556129914 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 14 Jan 2026 14:34:15 +0100 Subject: [PATCH 22/32] docs: update docker-compose as now the database wait is in the entrypoint --- docker-compose.test.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docker-compose.test.yml b/docker-compose.test.yml index d5ecefe..508c12f 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -21,8 +21,7 @@ services: ports: - "8080:80" depends_on: - db: - condition: service_healthy + - db db: image: mariadb:latest @@ -34,12 +33,6 @@ services: MYSQL_PASSWORD: glpi volumes: - db_data:/var/lib/mysql - healthcheck: - test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] - start_period: 10s - interval: 10s - timeout: 5s - retries: 3 volumes: glpi_data: From fda6e72e0878a4cc4903b4c8084677ecb095a95d Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 14 Jan 2026 14:38:16 +0100 Subject: [PATCH 23/32] Update readme --- README.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/README.md b/README.md index 6ea9a69..30cbbe0 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,7 @@ services: - "./storage/glpi:/var/glpi:rw" env_file: .env # Pass environment variables from .env file to the container depends_on: - db: - condition: service_healthy + - db ports: - "80:80" @@ -45,12 +44,6 @@ services: MYSQL_DATABASE: ${GLPI_DB_NAME} MYSQL_USER: ${GLPI_DB_USER} MYSQL_PASSWORD: ${GLPI_DB_PASSWORD} - healthcheck: - test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD - start_period: 5s - interval: 5s - timeout: 5s - retries: 10 expose: - "3306" ``` From 0773cde62a8002fb87d020f0950120d2aad41722 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 14 Jan 2026 15:06:04 +0100 Subject: [PATCH 24/32] Marketplace path is check on image entrypoint --- glpi/Dockerfile | 11 ----------- glpi/files/opt/glpi/entrypoint.sh | 10 +++++++--- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/glpi/Dockerfile b/glpi/Dockerfile index 5b8d311..31bd7f6 100644 --- a/glpi/Dockerfile +++ b/glpi/Dockerfile @@ -208,17 +208,6 @@ RUN a2enconf zzz-glpi.conf # Copy GLPI application. COPY --from=builder --chown=www-data:www-data /usr/src/glpi /var/www/glpi -# Detect GLPI major version from version/ folder and set marketplace directory -# v10.x uses /var/www/glpi/marketplace, v11+ uses /var/glpi/marketplace -RUN GLPI_VERSION_FILE=$(ls /var/www/glpi/version/ | sort --version-sort | tail --lines=1); \ - GLPI_MAJOR=$(echo "$GLPI_VERSION_FILE" | cut --delimiter='.' --fields=1); \ - if [ "$GLPI_MAJOR" = "10" ]; then \ - MARKETPLACE_DIR="/var/www/glpi/marketplace"; \ - else \ - MARKETPLACE_DIR="/var/glpi/marketplace"; \ - fi; \ - echo "GLPI_MARKETPLACE_DIR=$MARKETPLACE_DIR" >> /etc/glpi_env - # Declare a volume for "config", "marketplace" and "files" directory RUN mkdir /var/glpi && chown www-data:www-data /var/glpi VOLUME /var/glpi diff --git a/glpi/files/opt/glpi/entrypoint.sh b/glpi/files/opt/glpi/entrypoint.sh index f3be8a2..6c1e701 100644 --- a/glpi/files/opt/glpi/entrypoint.sh +++ b/glpi/files/opt/glpi/entrypoint.sh @@ -1,9 +1,13 @@ #!/bin/bash set -e -u -o pipefail -# Source GLPI environment variables (includes GLPI_MARKETPLACE_DIR detected at build time) -if [ -f /etc/glpi_env ]; then - export $(cat /etc/glpi_env | xargs) +if [ -z "${GLPI_MARKETPLACE_DIR:-}" ]; then + GLPI_MAJOR=$(ls /var/www/glpi/version/ | sort --version-sort | tail --lines=1| cut --delimiter='.' --fields=1) + if [ "$GLPI_MAJOR" = "10" ]; then + export GLPI_MARKETPLACE_DIR="/var/www/glpi/marketplace" + else + export GLPI_MARKETPLACE_DIR="/var/glpi/marketplace" + fi fi /opt/glpi/entrypoint/init-volumes-directories.sh From 47e4dd876dea15a70792f748255238851022dfc2 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 14 Jan 2026 15:10:34 +0100 Subject: [PATCH 25/32] Update readme for plugins volume --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 30cbbe0..e8cab59 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,8 @@ docker exec -it /var/www/glpi/bin/console database:enable_ti ### Volumes By default, the `glpi/glpi` image provides a volume containing its `config`, `marketplace` and `files` directories. -For GLPI 10.0.x version the marketplace directory is not declared in the volume as the path differs. You may want to create a manual volume for the path `/var/www/glpi/marketplace` if you plan to use it. + +You can also mount a volume containing your own custom plugins in `/var/www/glpi/plugins`. ### Custom PHP configuration The following example sets the memory limit to 256M From 220288c1b5cd8354cf79a0d488971f40dd600f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20VIGNAL?= <2380113+froozeify@users.noreply.github.com> Date: Wed, 14 Jan 2026 15:11:05 +0100 Subject: [PATCH 26/32] Update glpi/files/opt/glpi/entrypoint.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cédric Anne --- glpi/files/opt/glpi/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glpi/files/opt/glpi/entrypoint.sh b/glpi/files/opt/glpi/entrypoint.sh index 6c1e701..85b7bfb 100644 --- a/glpi/files/opt/glpi/entrypoint.sh +++ b/glpi/files/opt/glpi/entrypoint.sh @@ -2,7 +2,7 @@ set -e -u -o pipefail if [ -z "${GLPI_MARKETPLACE_DIR:-}" ]; then - GLPI_MAJOR=$(ls /var/www/glpi/version/ | sort --version-sort | tail --lines=1| cut --delimiter='.' --fields=1) + GLPI_MAJOR=$(ls /var/www/glpi/version/ | sort --version-sort | tail --lines=1 | cut --delimiter='.' --fields=1) if [ "$GLPI_MAJOR" = "10" ]; then export GLPI_MARKETPLACE_DIR="/var/www/glpi/marketplace" else From 8958498a5c55dc503aba0d1056fbd90cfca697f7 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 14 Jan 2026 15:13:21 +0100 Subject: [PATCH 27/32] Marketplace path is check on image entrypoint --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e8cab59..e17a3ee 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ docker exec -it /var/www/glpi/bin/console database:enable_ti ### Volumes By default, the `glpi/glpi` image provides a volume containing its `config`, `marketplace` and `files` directories. +For GLPI 10.0.x version the marketplace directory is not declared in the volume as the path differs. You may want to create a manual volume for the path `/var/www/glpi/marketplace` if you plan to use it. You can also mount a volume containing your own custom plugins in `/var/www/glpi/plugins`. From fc6d1508a447a406f81c22767910b102492fe531 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 14 Jan 2026 15:18:24 +0100 Subject: [PATCH 28/32] Nightly is not present anymore this repo --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index e17a3ee..f8af5e0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # GLPI Docker Images [![Release Build](https://github.com/glpi-project/docker-images/actions/workflows/glpi.yml/badge.svg)](https://github.com/glpi-project/docker-images/actions/workflows/glpi.yml) -[![Nightly Build](https://github.com/glpi-project/docker-images/actions/workflows/glpi-nightly.yml/badge.svg)](https://github.com/glpi-project/docker-images/actions/workflows/glpi-nightly.yml) ![GLPI on docker illustration](https://raw.githubusercontent.com/glpi-project/docker-images/refs/heads/main/docs/illustration.png) From a3a42acc26f2b6fd6e05413ce0f39299617216e3 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Thu, 15 Jan 2026 09:35:46 +0100 Subject: [PATCH 29/32] GLPI_MARKETPLACE_DIR is now a build args, simplify that marketplace calculation --- .github/workflows/glpi.yml | 20 ++++++++++++++++++++ README.md | 4 +++- glpi/Dockerfile | 18 +++++++++--------- glpi/files/opt/glpi/entrypoint.sh | 8 -------- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index 9745641..0847f0c 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -36,6 +36,11 @@ on: required: false type: string default: "" + use-legacy-marketplace-path: + description: "Use legacy marketplace path (/var/www/glpi/marketplace) for v10.x builds" + required: false + type: boolean + default: false secrets: DOCKER_HUB_USERNAME: required: true @@ -91,6 +96,11 @@ on: required: false type: string default: "" + use-legacy-marketplace-path: + description: "Use legacy marketplace path (/var/www/glpi/marketplace) for v10.x builds" + required: false + type: boolean + default: false jobs: prepare: @@ -108,6 +118,7 @@ jobs: patch-url: ${{ steps.set-vars.outputs.patch_url }} image-tag: ${{ steps.set-vars.outputs.image_tag }} push: ${{ steps.set-vars.outputs.push }} + marketplace-dir: ${{ steps.set-vars.outputs.marketplace_dir }} steps: - name: "Set variables" id: "set-vars" @@ -125,6 +136,14 @@ jobs: echo "patch_url=$PATCH_URL" >> $GITHUB_OUTPUT echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT + # Compute marketplace directory based on boolean use-legacy-marketplace-path input + USE_LEGACY_MP_PATH="${{ inputs.use-legacy-marketplace-path || 'false' }}" + if [[ "$USE_LEGACY_MP_PATH" = "true" ]]; then + echo "marketplace_dir=/var/www/glpi/marketplace" >> $GITHUB_OUTPUT + else + echo "marketplace_dir=/var/glpi/marketplace" >> $GITHUB_OUTPUT + fi + # Determine if glpi-version is a semver tag (e.g., 10.0.18, 11.0.5-rc1) # vs a branch, commit hash, or URL IS_SEMVER=false @@ -257,6 +276,7 @@ jobs: GLPI_VERSION=${{ needs.prepare.outputs.glpi-version }} GLPI_PATCH_URL=${{ needs.prepare.outputs.patch-url }} GLPI_CACHE_KEY=${{ needs.prepare.outputs.cache-key }} + GLPI_MARKETPLACE_DIR=${{ needs.prepare.outputs.marketplace-dir }} BUILDER_IMAGE=php:${{ needs.prepare.outputs.php-version }}-cli-alpine APP_IMAGE=php:${{ needs.prepare.outputs.php-version }}-apache cache-from: | diff --git a/README.md b/README.md index f8af5e0..d53e7da 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,9 @@ docker exec -it /var/www/glpi/bin/console database:enable_ti ### Volumes By default, the `glpi/glpi` image provides a volume containing its `config`, `marketplace` and `files` directories. -For GLPI 10.0.x version the marketplace directory is not declared in the volume as the path differs. You may want to create a manual volume for the path `/var/www/glpi/marketplace` if you plan to use it. + +For GLPI 10.0.x version the marketplace directory is not declared in the volume as the path differs. You may want to create a manual volume for the path `/var/www/glpi/marketplace` if you plan to use it. +You must also set the `GLPI_MARKETPLACE_URL` environment variable to `/var/www/glpi/marketplace` or if you are building the image you can set the build arg `GLPI_MARKETPLACE_DIR`. You can also mount a volume containing your own custom plugins in `/var/www/glpi/plugins`. diff --git a/glpi/Dockerfile b/glpi/Dockerfile index 31bd7f6..5389b86 100644 --- a/glpi/Dockerfile +++ b/glpi/Dockerfile @@ -42,9 +42,6 @@ RUN set -ex; \ ##### FROM $BUILDER_IMAGE AS builder -# Optional patch URL to apply after download -ARG GLPI_PATCH_URL="" - # Copy composer binary from latest composer image. COPY --from=composer:latest /usr/bin/composer /usr/bin/composer @@ -86,6 +83,8 @@ RUN mkdir --parents /usr/src/glpi \ && rm /tmp/glpi.tar.gz \ && chown --recursive www-data:www-data /usr/src/glpi +# Optional patch URL to apply after download +ARG GLPI_PATCH_URL="" # Apply optional patches if GLPI_PATCH_URL is provided (space-separated URLs) RUN set -ex; \ if [ -n "${GLPI_PATCH_URL}" ]; then \ @@ -106,11 +105,9 @@ RUN /usr/src/glpi/tools/build_glpi.sh ##### FROM $APP_IMAGE - LABEL \ - org.opencontainers.image.title="GLPI nightly build" \ - org.opencontainers.image.description="This container contains Apache/PHP and a nightly build of GLPI. \ -It can be used to test latest features and bug fixes." \ + org.opencontainers.image.title="GLPI build" \ + org.opencontainers.image.description="This container contains Apache/PHP and a build of GLPI." \ org.opencontainers.image.url="https://github.com/glpi-project/docker-images" \ org.opencontainers.image.source="git@github.com:glpi-project/docker-images" @@ -212,12 +209,15 @@ COPY --from=builder --chown=www-data:www-data /usr/src/glpi /var/www/glpi RUN mkdir /var/glpi && chown www-data:www-data /var/glpi VOLUME /var/glpi +# Marketplace directory configuration +# v10.x uses /var/www/glpi/marketplace (legacy), v11+ uses /var/glpi/marketplace +ARG GLPI_MARKETPLACE_DIR=/var/glpi/marketplace + # Define GLPI environment variables -# Note: GLPI_MARKETPLACE_DIR is sourced from /etc/glpi_env at container startup -# We cannot dynamically set an ENV value based on the output of a RUN command ENV \ GLPI_INSTALL_MODE=DOCKER \ GLPI_CONFIG_DIR=/var/glpi/config \ + GLPI_MARKETPLACE_DIR=${GLPI_MARKETPLACE_DIR} \ GLPI_VAR_DIR=/var/glpi/files \ GLPI_LOG_DIR=/var/glpi/logs \ GLPI_SKIP_AUTOINSTALL=false \ diff --git a/glpi/files/opt/glpi/entrypoint.sh b/glpi/files/opt/glpi/entrypoint.sh index 85b7bfb..52c6f7a 100644 --- a/glpi/files/opt/glpi/entrypoint.sh +++ b/glpi/files/opt/glpi/entrypoint.sh @@ -1,14 +1,6 @@ #!/bin/bash set -e -u -o pipefail -if [ -z "${GLPI_MARKETPLACE_DIR:-}" ]; then - GLPI_MAJOR=$(ls /var/www/glpi/version/ | sort --version-sort | tail --lines=1 | cut --delimiter='.' --fields=1) - if [ "$GLPI_MAJOR" = "10" ]; then - export GLPI_MARKETPLACE_DIR="/var/www/glpi/marketplace" - else - export GLPI_MARKETPLACE_DIR="/var/glpi/marketplace" - fi -fi /opt/glpi/entrypoint/init-volumes-directories.sh /opt/glpi/entrypoint/forward-logs.sh From 925db0a3c9f5b4dd588511d80408d9a62a60672a Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Thu, 15 Jan 2026 09:55:34 +0100 Subject: [PATCH 30/32] Add dockerfile build args listing on the top --- glpi/Dockerfile | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/glpi/Dockerfile b/glpi/Dockerfile index 5389b86..742fa38 100644 --- a/glpi/Dockerfile +++ b/glpi/Dockerfile @@ -1,3 +1,13 @@ +# GLPI Docker Image +# +# Build Arguments: +# BUILDER_IMAGE - PHP CLI image for building (default: php:cli-alpine) +# APP_IMAGE - PHP Apache image for runtime (default: php:apache) +# GLPI_VERSION - Version to build: "latest" (default), tag (10.0.18), branch (main), commit, or URL +# GLPI_CACHE_KEY - Cache-busting key for CI workflows (internal use) +# GLPI_PATCH_URL - Space-separated URLs to .diff/.patch files to apply +# GLPI_MARKETPLACE_DIR - Marketplace directory path (default: /var/glpi/marketplace) +# Use /var/www/glpi/marketplace for v10.x builds ARG BUILDER_IMAGE=php:cli-alpine ARG APP_IMAGE=php:apache From 6d5c420af358d498c8ef61275ffb7f2083c51ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20VIGNAL?= <2380113+froozeify@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:56:22 +0100 Subject: [PATCH 31/32] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cédric Anne --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d53e7da..cd5868d 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ docker exec -it /var/www/glpi/bin/console database:enable_ti By default, the `glpi/glpi` image provides a volume containing its `config`, `marketplace` and `files` directories. For GLPI 10.0.x version the marketplace directory is not declared in the volume as the path differs. You may want to create a manual volume for the path `/var/www/glpi/marketplace` if you plan to use it. -You must also set the `GLPI_MARKETPLACE_URL` environment variable to `/var/www/glpi/marketplace` or if you are building the image you can set the build arg `GLPI_MARKETPLACE_DIR`. +If you are building your own GLPI 10.0.x image using the `glpi/Dockerfile` file, you have to specify the marketplace path using the `--build-arg GLPI_MARKETPLACE_DIR=/var/www/glpi/marketplace` option. You can also mount a volume containing your own custom plugins in `/var/www/glpi/plugins`. From 993dc52a7716e3aaadf353bfe1f6ac57b40e5867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20VIGNAL?= <2380113+froozeify@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:56:31 +0100 Subject: [PATCH 32/32] Update .github/workflows/glpi.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cédric Anne --- .github/workflows/glpi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index 0847f0c..625cf1a 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -51,7 +51,7 @@ on: GHCR_ACCESS_TOKEN: required: true - # CI: Test build on workflow changes (no push) + # CI: Test build on workflow changes (images are not pushed to docker registries) push: branches: - "main"