diff --git a/.github/workflows/glpi-nightly.yml b/.github/workflows/glpi-nightly.yml deleted file mode 100644 index 4d946e9..0000000 --- a/.github/workflows/glpi-nightly.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: "GLPI nightly images" - -on: - push: - branches: - - "main" - paths: - - ".github/workflows/glpi-nightly.yml" - - "glpi/**" - pull_request: - paths: - - ".github/workflows/glpi-nightly.yml" - - "glpi/**" - schedule: - - cron: '0 0 * * *' - # Enable manual run - workflow_dispatch: - -jobs: - build: - name: "Build GLPI ${{ matrix.branch }}" - runs-on: "ubuntu-latest" - 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"} - steps: - - name: "Set variables" - id: "variables" - 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 - - # compute the marketplace dir - MARKETPLACE_DIR="/var/glpi/marketplace" - if [[ "${{ matrix.branch }}" = "10.0/bugfixes" ]]; then - MARKETPLACE_DIR="/var/www/glpi/marketplace" - fi - echo "marketplace_dir=$MARKETPLACE_DIR" >> $GITHUB_OUTPUT - - 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 - 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" - 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" - context: "glpi" - outputs: "${{ steps.variables.outputs.destinations }}" - platforms: "linux/amd64,linux/arm64" - pull: true - tags: "${{ steps.variables.outputs.tags }}" diff --git a/.github/workflows/glpi.yml b/.github/workflows/glpi.yml index 9984a01..625cf1a 100644 --- a/.github/workflows/glpi.yml +++ b/.github/workflows/glpi.yml @@ -1,13 +1,29 @@ -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 execution from another workflow + # 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 - image-suffix: + 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: "" @@ -15,6 +31,16 @@ on: required: false type: string default: "8.5" + image-tag: + description: "Override image tag. If not set, computed from version." + 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 @@ -25,109 +51,337 @@ on: GHCR_ACCESS_TOKEN: required: true - # Enable manual run - # - # It can be executed by a curl command: - # + # CI: Test build on workflow changes (images are not pushed to docker registries) + 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: " \ # 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 (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, e.g. 'nighlty'" + image-tag: + description: "Override image tag (glpi-version otherwise). i.e: '12.0.0-prebuild-20260615-1'" required: false type: string default: "" - php-version: - description: "PHP version to use for the build" - required: true + patch-url: + description: "Optional patch URL(s) to apply. Multiple patches: separate with spaces" + required: false type: string - default: "8.5" + 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: - build: - name: "Build GLPI ${{ inputs.glpi-version }}" + prepare: + name: "Prepare" runs-on: "ubuntu-latest" + outputs: + 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 }} + 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 }} + 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 }} + marketplace-dir: ${{ steps.set-vars.outputs.marketplace_dir }} steps: - name: "Set variables" - id: "variables" + id: "set-vars" 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) + # 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 + + # 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 - if [[ "${{ inputs.image-suffix }}" != '' ]]; then - IMAGE_VERSION="$IMAGE_VERSION-${{ 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 --quiet --extended-regexp '^[0-9]+\.[0-9]+\.[0-9]+(\-\w+)?$'; then + IS_SEMVER=true fi - # prepare the tags to push - TAGS="glpi/glpi:$IMAGE_VERSION,ghcr.io/glpi-project/glpi:$IMAGE_VERSION" + # 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 a random value to force rebuild, as content behind a URL can change between builds. + 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') + CACHE_KEY=$(curl -s "https://api.github.com/repos/glpi-project/glpi/commits/$RESOLVED_TAG" | jq -r '.sha // empty') + else + CACHE_KEY=$(curl -s "https://api.github.com/repos/glpi-project/glpi/commits/$GLPI_VERSION" | jq -r '.sha // empty') + fi + echo "cache_key=$CACHE_KEY" >> $GITHUB_OUTPUT - 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" + # 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 --regexp-extended 's|[/:]|-|g' | sed --regexp-extended 's|https?--||')" + fi + echo "artifact_prefix=$ARTIFACT_PREFIX" >> $GITHUB_OUTPUT - # 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 + # Compute image version for metadata-action + if [[ "$IMAGE_TAG" != '' ]]; then + IMAGE_VERSION="$IMAGE_TAG" + else + IMAGE_VERSION="$(echo "$GLPI_VERSION" | sed --regexp-extended 's|[/:]|-|g' | sed --regexp-extended 's|https?--||')" fi - echo "tags=$TAGS" >> $GITHUB_OUTPUT + echo "image_version=$IMAGE_VERSION" >> $GITHUB_OUTPUT - # compute the marketplace dir - MARKETPLACE_DIR="/var/glpi/marketplace" - if [[ "$IMAGE_VERSION_MAJOR" = "10" ]]; then - MARKETPLACE_DIR="/var/www/glpi/marketplace" + # 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 - echo "marketplace_dir=$MARKETPLACE_DIR" >> $GITHUB_OUTPUT + + # Detect prerelease (contains -rc, -beta, -alpha, etc.) + 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 + 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 --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 + 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" - with: - repository: glpi-project/docker-images - - name: "Get sources from glpi-project/glpi" + + - name: "Prepare environment" 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 - 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 + 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" + + - 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" + 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: | + 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.cache-key }}-${{ 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 }}" + 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 --almost-all ${{ 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 --replace={} 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 --replace={} 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/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b4a2a57 --- /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 --file docker-compose.test.yml up --build +``` + +### Verify + +Once the containers are up: +1. Check the logs to see the installation progress: + ```bash + 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). + - **User:** `glpi` + - **Password:** `glpi` + +### Cleanup + +To stop and remove the test containers: +```bash +docker compose --file docker-compose.test.yml down --volumes +``` diff --git a/README.md b/README.md index 144091b..cd5868d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # 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) + ![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. @@ -8,6 +10,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). @@ -26,8 +29,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" @@ -41,12 +43,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" ``` @@ -104,7 +100,11 @@ 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. +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`. ### Custom PHP configuration The following example sets the memory limit to 256M diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 0000000..508c12f --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,39 @@ +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 + + db: + image: mariadb:latest + restart: "no" + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: glpi + MYSQL_USER: glpi + MYSQL_PASSWORD: glpi + volumes: + - db_data:/var/lib/mysql + +volumes: + glpi_data: + db_data: diff --git a/glpi/Dockerfile b/glpi/Dockerfile index f8af832..742fa38 100644 --- a/glpi/Dockerfile +++ b/glpi/Dockerfile @@ -1,7 +1,52 @@ +# 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 +##### +# 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 +# Cache-busting key for glpi workflows +ARG GLPI_CACHE_KEY="" + +RUN apk add --no-cache curl jq + +# Resolve version and download source +RUN set -ex; \ + INPUT="${GLPI_VERSION}"; \ + # If input starts with https://, use it as-is \ + if echo "$INPUT" | grep --quiet '^https://'; then \ + URL="$INPUT"; \ + else \ + VERSION="$INPUT"; \ + # If "latest", resolve from GitHub API \ + if [ "$VERSION" = "latest" ]; then \ + 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 --location "$URL" --output /glpi.tar.gz + ##### # Builder image ##### @@ -27,8 +72,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 \ @@ -41,8 +86,24 @@ 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 tarball from downloader and extract (preserves permissions from archive) +COPY --from=downloader /glpi.tar.gz /tmp/glpi.tar.gz +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 --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 \ + cd /usr/src/glpi && \ + for PATCH in ${GLPI_PATCH_URL}; do \ + echo "Applying patch from ${PATCH}"; \ + curl --location "${PATCH}" | patch --strip=1; \ + done; \ + fi # Build GLPI app USER www-data @@ -54,18 +115,15 @@ 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" \ - 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" 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 \ @@ -138,7 +196,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 @@ -161,6 +219,10 @@ 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 ENV \ GLPI_INSTALL_MODE=DOCKER \ diff --git a/glpi/files/opt/glpi/entrypoint.sh b/glpi/files/opt/glpi/entrypoint.sh index b7170fc..52c6f7a 100644 --- a/glpi/files/opt/glpi/entrypoint.sh +++ b/glpi/files/opt/glpi/entrypoint.sh @@ -1,6 +1,7 @@ #!/bin/bash set -e -u -o pipefail + /opt/glpi/entrypoint/init-volumes-directories.sh /opt/glpi/entrypoint/forward-logs.sh /opt/glpi/entrypoint/wait-for-db.sh entrypoint