From b1035da235504b4b318ce24c706610f2a54757ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 02:48:37 +0000 Subject: [PATCH 01/22] Initial plan From b043bced61aa18f409adf74edd0d6217cf83c2b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 02:52:04 +0000 Subject: [PATCH 02/22] Investigate persistent Debian mirror 404 Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- TODO.md | 12 ++++++++++++ base/Dockerfile | 8 ++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 883ca4c0..5710ac58 100644 --- a/TODO.md +++ b/TODO.md @@ -13,3 +13,15 @@ Use this file to track work that needs to be done. Lint scripts automatically ap - Existing validation passes and the changed files contain no secrets. - [ ] Remove shellcheck baseline comparison: delete tests/baselines/shellcheck-baseline.json and the baseline comparison logic from tests/lint-shell.sh, and update the script to fail when shellcheck reports any violations (fail when the generated JSON is non-empty). After that, any new shellcheck violation will be an immediate CI failure. + +# Task: Retry downloader apt package installation + +## Steps +- [x] Identify the 404 for the Debian security `unzip` package in the failing Actions logs. +- [x] Add bounded retries around `apt-get update` and the `unzip`/`zip` installation in `base/Dockerfile`. +- [ ] Run focused Dockerfile validation and review the final diff. +- [ ] Scan changed files for secrets and run parallel code validation. + +## Definition of Done +- The downloader retries the metadata refresh and package installation after transient Debian mirror failures. +- Focused validation passes, the diff stays limited to the fix and this checklist, and changed files contain no secrets. diff --git a/base/Dockerfile b/base/Dockerfile index 5f3d8bee..d82f4def 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -61,8 +61,12 @@ ARG COPILOT_CHAT_VSIX_SHA256=691e80194fdba0399879fd065a76b9b14b32bfd29818292e816 # Install unzip and zip early so they are available for VSIX validation and # repacking during the download step. -RUN apt-get update \ - && apt-get install -y --no-install-recommends unzip zip \ +RUN for i in $(seq 1 3); do \ + apt-get update \ + && apt-get install -y --no-install-recommends unzip zip \ + && break \ + || { [ "$i" -lt 3 ] && sleep 5; }; \ + done \ && rm -rf /var/lib/apt/lists/* # Download libsodium source (pinned release, identical for every PHP version) From 2b17b48e529d36bbe9eddc24eaf7c61748948a2d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 02:56:24 +0000 Subject: [PATCH 03/22] Use archived Bullseye repositories in PHP builds Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- TODO.md | 13 +++++++------ base/Dockerfile | 20 ++++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/TODO.md b/TODO.md index 5710ac58..3fa151af 100644 --- a/TODO.md +++ b/TODO.md @@ -14,14 +14,15 @@ Use this file to track work that needs to be done. Lint scripts automatically ap - [ ] Remove shellcheck baseline comparison: delete tests/baselines/shellcheck-baseline.json and the baseline comparison logic from tests/lint-shell.sh, and update the script to fail when shellcheck reports any violations (fail when the generated JSON is non-empty). After that, any new shellcheck violation will be an immediate CI failure. -# Task: Retry downloader apt package installation +# Task: Use archived Bullseye package sources ## Steps -- [x] Identify the 404 for the Debian security `unzip` package in the failing Actions logs. -- [x] Add bounded retries around `apt-get update` and the `unzip`/`zip` installation in `base/Dockerfile`. -- [ ] Run focused Dockerfile validation and review the final diff. -- [ ] Scan changed files for secrets and run parallel code validation. +- [x] Identify that PHP 7.4's Bullseye security repository returns 404 for the `unzip` package after Bullseye LTS ended. +- [x] Configure Bullseye stages to use Debian's archive repository and accept its expired metadata. +- [x] Run focused Dockerfile lint/configuration validation and review the final diff. +- [x] Scan changed files for secrets. +- [ ] Run parallel code validation. ## Definition of Done -- The downloader retries the metadata refresh and package installation after transient Debian mirror failures. +- Bullseye downloader and extension stages inherit Debian Archive sources with expired metadata checks disabled. - Focused validation passes, the diff stays limited to the fix and this checklist, and changed files contain no secrets. diff --git a/base/Dockerfile b/base/Dockerfile index d82f4def..69f9d5fb 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -21,7 +21,15 @@ ARG PHP_VERSION=8.3 FROM alpine:3 AS downloads RUN mkdir -p /pre-downloaded -FROM php:${PHP_VERSION}-apache AS downloader +# Debian Bullseye reached end of LTS in August 2026 and its repositories are +# archived. Keep both downloader and PHP extension stages on the same sources. +FROM php:${PHP_VERSION}-apache AS php-base +RUN if grep -q '^VERSION_CODENAME=bullseye$' /etc/os-release; then \ + sed -i 's|deb.debian.org|archive.debian.org|g' /etc/apt/sources.list \ + && echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99archive; \ + fi + +FROM php-base AS downloader ARG CODESERVER_VERSION=4.99.4 ARG CODESERVER_DEB_SHA256_AMD64=74c107b33643ed11223263b86431530fbc9c9f7c8202997579a6f0c41b4cb102 @@ -61,12 +69,8 @@ ARG COPILOT_CHAT_VSIX_SHA256=691e80194fdba0399879fd065a76b9b14b32bfd29818292e816 # Install unzip and zip early so they are available for VSIX validation and # repacking during the download step. -RUN for i in $(seq 1 3); do \ - apt-get update \ - && apt-get install -y --no-install-recommends unzip zip \ - && break \ - || { [ "$i" -lt 3 ] && sleep 5; }; \ - done \ +RUN apt-get update \ + && apt-get install -y --no-install-recommends unzip zip \ && rm -rf /var/lib/apt/lists/* # Download libsodium source (pinned release, identical for every PHP version) @@ -206,7 +210,7 @@ RUN if [ -d /tmp/_vsix_repack ]; then \ # # This stage is referenced as the 'common-php-ext' Bake named context. # It is cached in GitHub Actions (type=gha) but never pushed to Docker Hub. -FROM php:${PHP_VERSION}-apache AS php-ext-common +FROM php-base AS php-ext-common #== System Environments ARG DEBIAN_FRONTEND=noninteractive From 58edab8f65dfcd78973048ac3e1d2f7ab8f51b67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 02:56:46 +0000 Subject: [PATCH 04/22] Mark validation complete Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 3fa151af..f5e23572 100644 --- a/TODO.md +++ b/TODO.md @@ -21,7 +21,7 @@ Use this file to track work that needs to be done. Lint scripts automatically ap - [x] Configure Bullseye stages to use Debian's archive repository and accept its expired metadata. - [x] Run focused Dockerfile lint/configuration validation and review the final diff. - [x] Scan changed files for secrets. -- [ ] Run parallel code validation. +- [x] Run parallel code validation. ## Definition of Done - Bullseye downloader and extension stages inherit Debian Archive sources with expired metadata checks disabled. From f9b566c063d92d0ec2965d7ae42287afa5681c07 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 03:53:18 +0000 Subject: [PATCH 05/22] Use a pinned Bullseye security snapshot Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- TODO.md | 13 +++++++------ base/Dockerfile | 9 ++++++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/TODO.md b/TODO.md index f5e23572..f3ff6dd9 100644 --- a/TODO.md +++ b/TODO.md @@ -14,15 +14,16 @@ Use this file to track work that needs to be done. Lint scripts automatically ap - [ ] Remove shellcheck baseline comparison: delete tests/baselines/shellcheck-baseline.json and the baseline comparison logic from tests/lint-shell.sh, and update the script to fail when shellcheck reports any violations (fail when the generated JSON is non-empty). After that, any new shellcheck violation will be an immediate CI failure. -# Task: Use archived Bullseye package sources +# Task: Fix Bullseye security package source ## Steps -- [x] Identify that PHP 7.4's Bullseye security repository returns 404 for the `unzip` package after Bullseye LTS ended. -- [x] Configure Bullseye stages to use Debian's archive repository and accept its expired metadata. +- [x] Keep the Bullseye main repository on Debian's archive and accept expired metadata. +- [x] Pin the Bullseye security suite to the final available Debian security snapshot. +- [ ] Confirm package installation in the PHP 7.4 downloader build (blocked locally by unavailable DNS). - [x] Run focused Dockerfile lint/configuration validation and review the final diff. - [x] Scan changed files for secrets. -- [x] Run parallel code validation. +- [ ] Run parallel code validation. ## Definition of Done -- Bullseye downloader and extension stages inherit Debian Archive sources with expired metadata checks disabled. -- Focused validation passes, the diff stays limited to the fix and this checklist, and changed files contain no secrets. +- Bullseye downloader and extension stages inherit the archived main source and pinned security snapshot with expired metadata checks disabled. +- PHP 7.4 package installation and focused validation pass; changed files contain no secrets. diff --git a/base/Dockerfile b/base/Dockerfile index 69f9d5fb..94a06a4c 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -21,11 +21,14 @@ ARG PHP_VERSION=8.3 FROM alpine:3 AS downloads RUN mkdir -p /pre-downloaded -# Debian Bullseye reached end of LTS in August 2026 and its repositories are -# archived. Keep both downloader and PHP extension stages on the same sources. +# Debian Bullseye reached end of LTS in August 2026. Its main repository is +# archived, while the security suite is pinned to its final available snapshot. FROM php:${PHP_VERSION}-apache AS php-base RUN if grep -q '^VERSION_CODENAME=bullseye$' /etc/os-release; then \ - sed -i 's|deb.debian.org|archive.debian.org|g' /etc/apt/sources.list \ + sed -i \ + -e 's|deb.debian.org/debian-security|snapshot.debian.org/archive/debian-security/20260903T220410Z|g' \ + -e 's|deb.debian.org|archive.debian.org|g' \ + /etc/apt/sources.list \ && echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99archive; \ fi From 71ae6c89de5ca334240f2504edd08082141df09b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 04:35:34 +0000 Subject: [PATCH 06/22] Fix failing Test workflow builds Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- 7.4/base/Dockerfile | 2 +- 8.0/base/Dockerfile | 2 +- 8.1/base/Dockerfile | 3 +-- TODO.md | 20 +++++++++++++++++--- tests/run-dockerfile.sh | 2 +- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/7.4/base/Dockerfile b/7.4/base/Dockerfile index 0b9c17d2..99c35cd4 100644 --- a/7.4/base/Dockerfile +++ b/7.4/base/Dockerfile @@ -76,7 +76,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ done \ && wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc \ | gpg --dearmor -o /usr/share/keyrings/postgresql.gpg \ - && echo "deb [signed-by=/usr/share/keyrings/postgresql.gpg] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \ + && echo "deb [signed-by=/usr/share/keyrings/postgresql.gpg] https://apt-archive.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg-archive main" \ > /etc/apt/sources.list.d/pgdg.list \ && apt-get update \ && apt-get install -y postgresql-client-18 \ diff --git a/8.0/base/Dockerfile b/8.0/base/Dockerfile index d9e9187c..7097a835 100644 --- a/8.0/base/Dockerfile +++ b/8.0/base/Dockerfile @@ -70,7 +70,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ done \ && wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc \ | gpg --dearmor -o /usr/share/keyrings/postgresql.gpg \ - && echo "deb [signed-by=/usr/share/keyrings/postgresql.gpg] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \ + && echo "deb [signed-by=/usr/share/keyrings/postgresql.gpg] https://apt-archive.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg-archive main" \ > /etc/apt/sources.list.d/pgdg.list \ && apt-get update \ && apt-get install -y postgresql-client-18 \ diff --git a/8.1/base/Dockerfile b/8.1/base/Dockerfile index 726c841e..8192a3ac 100644 --- a/8.1/base/Dockerfile +++ b/8.1/base/Dockerfile @@ -52,7 +52,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ | while IFS= read -r lib; do dpkg-query --search "$(realpath "$lib" 2>/dev/null || echo "$lib")" 2>/dev/null; done \ | cut -d: -f1 \ | sort -u \ - | xargs -r apt-mark manual; } || true \ + | xargs -r apt-mark manual || true; } \ && apt-get purge -y --auto-remove \ -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* \ @@ -77,4 +77,3 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ && rm -f /etc/apt/apt.conf.d/01dpkg-no-pty USER ${USER} - diff --git a/TODO.md b/TODO.md index f3ff6dd9..8a167194 100644 --- a/TODO.md +++ b/TODO.md @@ -19,11 +19,25 @@ Use this file to track work that needs to be done. Lint scripts automatically ap ## Steps - [x] Keep the Bullseye main repository on Debian's archive and accept expired metadata. - [x] Pin the Bullseye security suite to the final available Debian security snapshot. -- [ ] Confirm package installation in the PHP 7.4 downloader build (blocked locally by unavailable DNS). +- [x] Update PHP 7.4 and 8.0 PostgreSQL sources to the PGDG archive. - [x] Run focused Dockerfile lint/configuration validation and review the final diff. - [x] Scan changed files for secrets. - [ ] Run parallel code validation. ## Definition of Done -- Bullseye downloader and extension stages inherit the archived main source and pinned security snapshot with expired metadata checks disabled. -- PHP 7.4 package installation and focused validation pass; changed files contain no secrets. +- Bullseye builds use the Debian archive and pinned security snapshot, and PHP 7.4/8.0 use the archived PGDG repository. +- Focused validation passes and changed files contain no secrets. + +# Task: Fix failing Test workflow + +## Steps +- [x] Identify matrix failures from GitHub Actions logs. +- [x] Use `--force` in the Copilot Chat functional test to handle a newer preinstalled extension. +- [x] Keep PHP 8.1 extension installation failures from being hidden by the optional dependency-marking fallback. +- [x] Point Bullseye PostgreSQL client installs at the PGDG archive. +- [x] Run Dockerfile/shell lint, shell syntax, diff, and secret checks; attempt a PHP 8.1 build (local Buildx driver lacks cache-export support). + +## Definition of Done +- PHP 7.4/8.0 build from valid PostgreSQL package sources. +- PHP 8.1 imagick setup failures are surfaced, and Copilot Chat functional tests handle preinstalled newer versions. +- Focused validation passes; changed files contain no secrets. diff --git a/tests/run-dockerfile.sh b/tests/run-dockerfile.sh index ba5139aa..eb98863c 100755 --- a/tests/run-dockerfile.sh +++ b/tests/run-dockerfile.sh @@ -197,7 +197,7 @@ test_base() { # -iname to locate the directory case-insensitively, normalize to lowercase, # and grep to confirm the expected publisher/name prefix is present. assert_contains "$image" "github\.copilot-chat" "GitHub Copilot Chat extension installs and is found" \ - sh -c "code-server --install-extension /usr/local/share/devpanel/copilot-chat.vsix --user-data-dir /var/www/html/.vscode && find /var/www/html/.vscode/extensions/ -maxdepth 1 -iname 'github.copilot-chat-*' -type d | tr '[:upper:]' '[:lower:]' | grep 'github.copilot-chat'" + sh -c "code-server --install-extension /usr/local/share/devpanel/copilot-chat.vsix --user-data-dir /var/www/html/.vscode --force && find /var/www/html/.vscode/extensions/ -maxdepth 1 -iname 'github.copilot-chat-*' -type d | tr '[:upper:]' '[:lower:]' | grep 'github.copilot-chat'" } test_secure() { From 07fcd65290331e3f12eb5ebd1a296b2a5f07a2fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 04:36:37 +0000 Subject: [PATCH 07/22] Track pending CI approval Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- TODO.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 8a167194..f3c88108 100644 --- a/TODO.md +++ b/TODO.md @@ -22,7 +22,7 @@ Use this file to track work that needs to be done. Lint scripts automatically ap - [x] Update PHP 7.4 and 8.0 PostgreSQL sources to the PGDG archive. - [x] Run focused Dockerfile lint/configuration validation and review the final diff. - [x] Scan changed files for secrets. -- [ ] Run parallel code validation. +- [x] Run parallel code validation. ## Definition of Done - Bullseye builds use the Debian archive and pinned security snapshot, and PHP 7.4/8.0 use the archived PGDG repository. @@ -36,8 +36,10 @@ Use this file to track work that needs to be done. Lint scripts automatically ap - [x] Keep PHP 8.1 extension installation failures from being hidden by the optional dependency-marking fallback. - [x] Point Bullseye PostgreSQL client installs at the PGDG archive. - [x] Run Dockerfile/shell lint, shell syntax, diff, and secret checks; attempt a PHP 8.1 build (local Buildx driver lacks cache-export support). +- [x] Run parallel code validation; no actionable review findings, and CodeQL found no analyzable code changes. +- [ ] Confirm the GitHub Actions rerun after workflow approval. ## Definition of Done - PHP 7.4/8.0 build from valid PostgreSQL package sources. - PHP 8.1 imagick setup failures are surfaced, and Copilot Chat functional tests handle preinstalled newer versions. -- Focused validation passes; changed files contain no secrets. +- The GitHub Actions test matrix passes; focused validation passes and changed files contain no secrets. From 3727206b622d6d523b70ff857ce744c6c52fb938 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 04:42:37 +0000 Subject: [PATCH 08/22] Fix failing Test workflow matrix builds Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- 7.4/base/Dockerfile | 3 ++- 8.0/base/Dockerfile | 3 ++- 8.1/base/Dockerfile | 1 + 8.2/base/Dockerfile | 4 ++-- 8.3/base/Dockerfile | 4 ++-- 8.4/base/Dockerfile | 4 ++-- 8.5/base/Dockerfile | 4 ++-- TODO.md | 13 +++++++------ base/Dockerfile | 3 ++- 9 files changed, 22 insertions(+), 17 deletions(-) diff --git a/7.4/base/Dockerfile b/7.4/base/Dockerfile index 99c35cd4..e06c1df2 100644 --- a/7.4/base/Dockerfile +++ b/7.4/base/Dockerfile @@ -42,6 +42,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ done \ && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd pspell \ + && pecl channel-update pecl.php.net \ && pecl install -o -f \ igbinary-3.2.12 \ imagick-3.8.1 \ @@ -57,7 +58,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ | while IFS= read -r lib; do dpkg-query --search "$(realpath "$lib" 2>/dev/null || echo "$lib")" 2>/dev/null; done \ | cut -d: -f1 \ | sort -u \ - | xargs -r apt-mark manual; } || true \ + | xargs -r apt-mark manual || true; } \ && apt-get purge -y --auto-remove \ -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* \ diff --git a/8.0/base/Dockerfile b/8.0/base/Dockerfile index 7097a835..2682ec4e 100644 --- a/8.0/base/Dockerfile +++ b/8.0/base/Dockerfile @@ -37,6 +37,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ done \ && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd pspell \ + && pecl channel-update pecl.php.net \ && pecl install -o -f \ igbinary-3.2.12 \ imagick-3.8.1 \ @@ -51,7 +52,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ | while IFS= read -r lib; do dpkg-query --search "$(realpath "$lib" 2>/dev/null || echo "$lib")" 2>/dev/null; done \ | cut -d: -f1 \ | sort -u \ - | xargs -r apt-mark manual; } || true \ + | xargs -r apt-mark manual || true; } \ && apt-get purge -y --auto-remove \ -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* \ diff --git a/8.1/base/Dockerfile b/8.1/base/Dockerfile index 8192a3ac..4434fc53 100644 --- a/8.1/base/Dockerfile +++ b/8.1/base/Dockerfile @@ -38,6 +38,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ done \ && docker-php-ext-configure gd --with-avif --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd pspell \ + && pecl channel-update pecl.php.net \ && pecl install -o -f \ igbinary-3.2.12 \ imagick-3.8.1 \ diff --git a/8.2/base/Dockerfile b/8.2/base/Dockerfile index 1cf32a1e..d69cea8d 100644 --- a/8.2/base/Dockerfile +++ b/8.2/base/Dockerfile @@ -38,6 +38,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ done \ && docker-php-ext-configure gd --with-avif --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd pspell \ + && pecl channel-update pecl.php.net \ && pecl install -o -f \ igbinary-3.2.12 \ imagick-3.8.1 \ @@ -52,7 +53,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ | while IFS= read -r lib; do dpkg-query --search "$(realpath "$lib" 2>/dev/null || echo "$lib")" 2>/dev/null; done \ | cut -d: -f1 \ | sort -u \ - | xargs -r apt-mark manual; } || true \ + | xargs -r apt-mark manual || true; } \ && apt-get purge -y --auto-remove \ -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* \ @@ -77,4 +78,3 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ && rm -f /etc/apt/apt.conf.d/01dpkg-no-pty USER ${USER} - diff --git a/8.3/base/Dockerfile b/8.3/base/Dockerfile index 3dac92ac..135d8a23 100644 --- a/8.3/base/Dockerfile +++ b/8.3/base/Dockerfile @@ -39,6 +39,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ done \ && docker-php-ext-configure gd --with-avif --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd pspell \ + && pecl channel-update pecl.php.net \ && pecl install -o -f \ igbinary \ imagick \ @@ -54,7 +55,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ | while IFS= read -r lib; do dpkg-query --search "$(realpath "$lib" 2>/dev/null || echo "$lib")" 2>/dev/null; done \ | cut -d: -f1 \ | sort -u \ - | xargs -r apt-mark manual; } || true \ + | xargs -r apt-mark manual || true; } \ && apt-get purge -y --auto-remove \ -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* \ @@ -82,4 +83,3 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ RUN composer self-update 2.7.7 USER ${USER} - diff --git a/8.4/base/Dockerfile b/8.4/base/Dockerfile index 68160e1d..db29688d 100644 --- a/8.4/base/Dockerfile +++ b/8.4/base/Dockerfile @@ -38,6 +38,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ done \ && docker-php-ext-configure gd --with-avif --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd \ + && pecl channel-update pecl.php.net \ && pecl install -o -f \ igbinary \ imagick \ @@ -53,7 +54,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ | while IFS= read -r lib; do dpkg-query --search "$(realpath "$lib" 2>/dev/null || echo "$lib")" 2>/dev/null; done \ | cut -d: -f1 \ | sort -u \ - | xargs -r apt-mark manual; } || true \ + | xargs -r apt-mark manual || true; } \ && apt-get purge -y --auto-remove \ -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* \ @@ -81,4 +82,3 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ RUN composer self-update USER ${USER} - diff --git a/8.5/base/Dockerfile b/8.5/base/Dockerfile index 82a60100..5cd903a7 100644 --- a/8.5/base/Dockerfile +++ b/8.5/base/Dockerfile @@ -38,6 +38,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ done \ && docker-php-ext-configure gd --with-avif --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd \ + && pecl channel-update pecl.php.net \ && pecl install -o -f \ igbinary \ imagick \ @@ -53,7 +54,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ | while IFS= read -r lib; do dpkg-query --search "$(realpath "$lib" 2>/dev/null || echo "$lib")" 2>/dev/null; done \ | cut -d: -f1 \ | sort -u \ - | xargs -r apt-mark manual; } || true \ + | xargs -r apt-mark manual || true; } \ && apt-get purge -y --auto-remove \ -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* \ @@ -81,4 +82,3 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ RUN composer self-update USER ${USER} - diff --git a/TODO.md b/TODO.md index f3c88108..9dea282a 100644 --- a/TODO.md +++ b/TODO.md @@ -35,11 +35,12 @@ Use this file to track work that needs to be done. Lint scripts automatically ap - [x] Use `--force` in the Copilot Chat functional test to handle a newer preinstalled extension. - [x] Keep PHP 8.1 extension installation failures from being hidden by the optional dependency-marking fallback. - [x] Point Bullseye PostgreSQL client installs at the PGDG archive. -- [x] Run Dockerfile/shell lint, shell syntax, diff, and secret checks; attempt a PHP 8.1 build (local Buildx driver lacks cache-export support). -- [x] Run parallel code validation; no actionable review findings, and CodeQL found no analyzable code changes. -- [ ] Confirm the GitHub Actions rerun after workflow approval. +- [x] Refresh PECL channel metadata before extension installs. +- [x] Scope the best-effort apt-mark fallback in all PHP version Dockerfiles. +- [x] Run Dockerfile/shell lint, shell syntax, Bake checks, and secret scanning; attempt a PHP 8.1 image build (sandbox DNS prevented GitHub download). +- [ ] Run parallel code/security validation on the final patch. +- [ ] Confirm the GitHub Actions rerun after approval. ## Definition of Done -- PHP 7.4/8.0 build from valid PostgreSQL package sources. -- PHP 8.1 imagick setup failures are surfaced, and Copilot Chat functional tests handle preinstalled newer versions. -- The GitHub Actions test matrix passes; focused validation passes and changed files contain no secrets. +- The CI failures from Test run 39719313157 have targeted fixes. +- The affected image builds and functional tests pass in CI; modified files contain no secrets. diff --git a/base/Dockerfile b/base/Dockerfile index 94a06a4c..a2a93527 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -343,6 +343,7 @@ RUN --mount=type=bind,from=common-downloader,source=/tmp/libsodium,target=/tmp/l bcmath bz2 calendar exif gettext gmp intl mysqli \ pdo_mysql pdo_pgsql shmop soap sockets sysvmsg sysvsem sysvshm \ tidy xsl zip pcntl \ + && pecl channel-update pecl.php.net \ && pecl install -o -f \ redis-5.3.7 \ yaml-2.2.2 \ @@ -366,7 +367,7 @@ RUN --mount=type=bind,from=common-downloader,source=/tmp/libsodium,target=/tmp/l | while IFS= read -r lib; do dpkg-query --search "$(realpath "$lib" 2>/dev/null || echo "$lib")" 2>/dev/null; done \ | cut -d: -f1 \ | sort -u \ - | xargs -r apt-mark manual; } || true \ + | xargs -r apt-mark manual || true; } \ && apt-get purge -y --auto-remove \ -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* \ From 5e89fd911f221253f1d051e58898b50e3cc477e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 04:48:28 +0000 Subject: [PATCH 09/22] Record pending CI approval Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- TODO.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 9dea282a..1cad2621 100644 --- a/TODO.md +++ b/TODO.md @@ -38,8 +38,8 @@ Use this file to track work that needs to be done. Lint scripts automatically ap - [x] Refresh PECL channel metadata before extension installs. - [x] Scope the best-effort apt-mark fallback in all PHP version Dockerfiles. - [x] Run Dockerfile/shell lint, shell syntax, Bake checks, and secret scanning; attempt a PHP 8.1 image build (sandbox DNS prevented GitHub download). -- [ ] Run parallel code/security validation on the final patch. -- [ ] Confirm the GitHub Actions rerun after approval. +- [x] Run parallel code/security validation; no review findings, and CodeQL had no analyzable code. +- [ ] Confirm the current Test run after approval (run 39724221886 is action-required and has not started jobs). ## Definition of Done - The CI failures from Test run 39719313157 have targeted fixes. From 84396aa14c35368e1782bb30db148fb3c3abfecf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 05:01:17 +0000 Subject: [PATCH 10/22] Fix PHP extension matrix build failures Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- 7.4/base/Dockerfile | 1 - 8.0/base/Dockerfile | 1 - 8.1/base/Dockerfile | 1 - 8.2/base/Dockerfile | 1 - 8.3/base/Dockerfile | 1 - 8.4/base/Dockerfile | 1 - 8.5/base/Dockerfile | 1 - TODO.md | 4 +++- base/Dockerfile | 8 ++++++-- 9 files changed, 9 insertions(+), 10 deletions(-) diff --git a/7.4/base/Dockerfile b/7.4/base/Dockerfile index e06c1df2..45d7907f 100644 --- a/7.4/base/Dockerfile +++ b/7.4/base/Dockerfile @@ -42,7 +42,6 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ done \ && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd pspell \ - && pecl channel-update pecl.php.net \ && pecl install -o -f \ igbinary-3.2.12 \ imagick-3.8.1 \ diff --git a/8.0/base/Dockerfile b/8.0/base/Dockerfile index 2682ec4e..90a47587 100644 --- a/8.0/base/Dockerfile +++ b/8.0/base/Dockerfile @@ -37,7 +37,6 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ done \ && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd pspell \ - && pecl channel-update pecl.php.net \ && pecl install -o -f \ igbinary-3.2.12 \ imagick-3.8.1 \ diff --git a/8.1/base/Dockerfile b/8.1/base/Dockerfile index 4434fc53..8192a3ac 100644 --- a/8.1/base/Dockerfile +++ b/8.1/base/Dockerfile @@ -38,7 +38,6 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ done \ && docker-php-ext-configure gd --with-avif --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd pspell \ - && pecl channel-update pecl.php.net \ && pecl install -o -f \ igbinary-3.2.12 \ imagick-3.8.1 \ diff --git a/8.2/base/Dockerfile b/8.2/base/Dockerfile index d69cea8d..29e79080 100644 --- a/8.2/base/Dockerfile +++ b/8.2/base/Dockerfile @@ -38,7 +38,6 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ done \ && docker-php-ext-configure gd --with-avif --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd pspell \ - && pecl channel-update pecl.php.net \ && pecl install -o -f \ igbinary-3.2.12 \ imagick-3.8.1 \ diff --git a/8.3/base/Dockerfile b/8.3/base/Dockerfile index 135d8a23..8b6afdec 100644 --- a/8.3/base/Dockerfile +++ b/8.3/base/Dockerfile @@ -39,7 +39,6 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ done \ && docker-php-ext-configure gd --with-avif --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd pspell \ - && pecl channel-update pecl.php.net \ && pecl install -o -f \ igbinary \ imagick \ diff --git a/8.4/base/Dockerfile b/8.4/base/Dockerfile index db29688d..d8e707bf 100644 --- a/8.4/base/Dockerfile +++ b/8.4/base/Dockerfile @@ -38,7 +38,6 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ done \ && docker-php-ext-configure gd --with-avif --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd \ - && pecl channel-update pecl.php.net \ && pecl install -o -f \ igbinary \ imagick \ diff --git a/8.5/base/Dockerfile b/8.5/base/Dockerfile index 5cd903a7..59a9fdbc 100644 --- a/8.5/base/Dockerfile +++ b/8.5/base/Dockerfile @@ -38,7 +38,6 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ done \ && docker-php-ext-configure gd --with-avif --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd \ - && pecl channel-update pecl.php.net \ && pecl install -o -f \ igbinary \ imagick \ diff --git a/TODO.md b/TODO.md index 1cad2621..236a3d66 100644 --- a/TODO.md +++ b/TODO.md @@ -39,7 +39,9 @@ Use this file to track work that needs to be done. Lint scripts automatically ap - [x] Scope the best-effort apt-mark fallback in all PHP version Dockerfiles. - [x] Run Dockerfile/shell lint, shell syntax, Bake checks, and secret scanning; attempt a PHP 8.1 image build (sandbox DNS prevented GitHub download). - [x] Run parallel code/security validation; no review findings, and CodeQL had no analyzable code. -- [ ] Confirm the current Test run after approval (run 39724221886 is action-required and has not started jobs). +- [x] Review Test run 39724432825; Redis 5.3.7 failed on PHP 8.4/8.5, and shared PECL channel refreshes returned malformed XML on multiple versions. +- [x] Upgrade the shared Redis extension to a PHP 8.4-compatible release, retry the shared PECL refresh, and remove redundant per-version refreshes. +- [ ] Validate the follow-up Test run and complete focused checks, secret scanning, and parallel validation. ## Definition of Done - The CI failures from Test run 39719313157 have targeted fixes. diff --git a/base/Dockerfile b/base/Dockerfile index a2a93527..b4923b9d 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -343,9 +343,13 @@ RUN --mount=type=bind,from=common-downloader,source=/tmp/libsodium,target=/tmp/l bcmath bz2 calendar exif gettext gmp intl mysqli \ pdo_mysql pdo_pgsql shmop soap sockets sysvmsg sysvsem sysvshm \ tidy xsl zip pcntl \ - && pecl channel-update pecl.php.net \ + && for i in $(seq 1 3); do \ + pecl channel-update pecl.php.net \ + && break \ + || { [ "$i" -lt 3 ] && sleep 5; }; \ + done \ && pecl install -o -f \ - redis-5.3.7 \ + redis-6.3.0 \ yaml-2.2.2 \ memcached-3.2.0 \ && rm -f /usr/local/etc/php/conf.d/*sodium.ini \ From 1287120de4c6593f2f48bada288fd8e3e71a5d51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 11:29:51 +0000 Subject: [PATCH 11/22] Fix PECL metadata transport and remove separate Copilot Chat installation Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- .github/actions/build-php-images/action.yml | 2 +- .github/actions/preseed-downloads/action.yml | 205 ++----------------- .github/workflows/docker-build.yml | 8 +- .github/workflows/test.yml | 59 +----- 7.4/base/Dockerfile | 4 +- 8.0/base/Dockerfile | 4 +- 8.1/base/Dockerfile | 4 +- 8.2/base/Dockerfile | 4 +- AGENTS.md | 2 +- TODO.md | 15 ++ base/Dockerfile | 138 +------------ base/scripts/apache-start.sh | 5 +- docker-bake.hcl | 8 +- tests/baselines/hadolint-baseline.json | 6 +- tests/build-dockerfile.sh | 2 +- tests/run-dockerfile.sh | 15 +- 16 files changed, 76 insertions(+), 405 deletions(-) diff --git a/.github/actions/build-php-images/action.yml b/.github/actions/build-php-images/action.yml index e20a2aa1..997a455c 100644 --- a/.github/actions/build-php-images/action.yml +++ b/.github/actions/build-php-images/action.yml @@ -83,7 +83,7 @@ inputs: bake_overrides_file: description: > Path to a pre-generated bake overrides JSON file that maps each - phpX_Y-downloader target to the correct code-server / Copilot Chat + phpX_Y-downloader target to the correct code-server versions and SHA256 digests for its Debian tier. Pass the path to the preseed-bake-overrides artifact downloaded from the detect job. required: true diff --git a/.github/actions/preseed-downloads/action.yml b/.github/actions/preseed-downloads/action.yml index d35f6fb9..dd1aeef1 100644 --- a/.github/actions/preseed-downloads/action.yml +++ b/.github/actions/preseed-downloads/action.yml @@ -1,7 +1,7 @@ name: Pre-seed build downloads description: > Detect which Debian codename each PHP version runs on, resolve the latest - compatible code-server and Copilot Chat versions for each detected codename, + compatible code-server version for each detected codename, restore previously downloaded files from the actions/cache, and download them on a cache miss. Produces a JSON bake-override file that maps each phpX_Y-downloader target to the correct versions and SHA256 digests for @@ -32,7 +32,7 @@ inputs: description: > When "true", always download fresh artifacts even when a cache entry already exists. Cache entries are keyed from the resolved Debian - codename and code-server / Copilot Chat versions, so a new cache entry + codename and code-server versions, so a new cache entry is created automatically when any of those resolved values changes. If the resolved codename and versions are unchanged, the cache key is also unchanged and the existing cache entry remains current. Use this @@ -49,7 +49,7 @@ outputs: bake_overrides_file: description: > Path to a JSON bake-override file that maps each phpX_Y-downloader target - to the code-server and Copilot Chat versions and SHA256 digests resolved + to the code-server versions and SHA256 digests resolved for its Debian tier. Pass to docker buildx bake as an additional --file. value: ${{ steps.assemble.outputs.bake_overrides_file }} @@ -174,35 +174,6 @@ runs: 'https://api.github.com/repos/coder/code-server/releases?per_page=100') printf '%s' "$CODESERVER_RELEASES_JSON" > "$RUNNER_TEMP/cs-releases.json" - MARKETPLACE_JSON=$(curl -fsSL --retry 3 --retry-all-errors --connect-timeout 30 \ - -X POST \ - -H 'Content-Type: application/json' \ - -H 'Accept: application/json;api-version=3.0-preview.1' \ - -d '{"filters":[{"criteria":[{"filterType":7,"value":"GitHub.copilot-chat"}],"pageSize":100}],"flags":17}' \ - 'https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery') - printf '%s' "$MARKETPLACE_JSON" > "$RUNNER_TEMP/marketplace.json" - - # Emits stable Copilot Chat versions compatible with the given VS Code - # version, sorted newest-first. - copilot_candidates_for_vscode() { - local vscode="$1" - printf '%s' "$MARKETPLACE_JSON" | jq -r \ - --arg vscode "$vscode" ' - def vparts: split(".") | map(tonumber? // 0); - [ - .results[0].extensions[0].versions[]? | - select(.version | test("^[0-9]+\\.[0-9]+\\.[0-9]{1,9}$")) | - (.properties // [] | - map(select(.key == "Microsoft.VisualStudio.Code.Engine")) | - .[0].value // "") as $engine | - select($engine != "") | - ($engine | gsub("^[^0-9]+"; "") | split("-")[0]) as $min_vscode | - select(($vscode | vparts) >= ($min_vscode | vparts)) | - .version - ] | sort_by(split(".") | map(tonumber? // 0)) | reverse | .[] - ' - } - # ── Step 3: Resolve code-server for the primary codename ────────────── CS_VERSION_PRIMARY=$(printf '%s' "$CODESERVER_RELEASES_JSON" | jq -r ' first( @@ -217,28 +188,18 @@ runs: } echo "Primary (${PRIMARY_CODENAME}) code-server: ${CS_VERSION_PRIMARY}" >&2 - # ── Step 4: Resolve Copilot Chat for the primary codename ───────────── - VSCODE_PRIMARY="$(printf '%s' "$CS_VERSION_PRIMARY" | sed 's/^[0-9][0-9]*\./1./')" - CC_VERSION_PRIMARY=$(copilot_candidates_for_vscode "$VSCODE_PRIMARY" | head -1) - [ -n "$CC_VERSION_PRIMARY" ] || { - echo "ERROR: failed to detect Copilot Chat version for ${PRIMARY_CODENAME}" >&2; exit 1 - } - echo "Primary (${PRIMARY_CODENAME}) Copilot Chat: ${CC_VERSION_PRIMARY}" >&2 - - # ── Step 5: Set tentative versions for all codenames ────────────────── + # ── Step 4: Set tentative versions for all codenames ────────────────── # Non-primary codenames are initially assigned the primary versions. # The actual compatibility testing (which requires downloading artifacts) # is deferred to the cache-miss download step so that cache hits skip # all artifact downloads. The cache is invalidated whenever the primary # version changes, which also triggers re-verification for all codenames. declare -A CS_VERSION_FOR # codename → resolved code-server version - declare -A CC_VERSION_FOR # codename → resolved Copilot Chat version for codename in "${!VERSIONS_FOR[@]}"; do CS_VERSION_FOR[$codename]="$CS_VERSION_PRIMARY" - CC_VERSION_FOR[$codename]="$CC_VERSION_PRIMARY" done - # ── Step 6: Write resolve.env and compute cache key ─────────────────── + # ── Step 5: Write resolve.env and compute cache key ─────────────────── # The env file is sourced by subsequent steps to access codename mappings # and resolved versions without re-running docker or API calls. # Variable names use the codename as a suffix (e.g. CS_VERSION_bookworm); @@ -257,7 +218,6 @@ runs: # Per-codename tentative resolved versions (may be updated by download step). for cn in "${!VERSIONS_FOR[@]}"; do printf 'CS_VERSION_%s=%s\n' "$cn" "${CS_VERSION_FOR[$cn]}" - printf 'CC_VERSION_%s=%s\n' "$cn" "${CC_VERSION_FOR[$cn]}" done # Per-codename representative image used by the download step for # compatibility testing of non-primary codenames. @@ -267,11 +227,11 @@ runs: } > "$RESOLVE_ENV" # Cache key: SHA256 of the primary-version-based tuples for all codenames. - # Invalidated whenever the primary code-server or Copilot Chat version + # Invalidated whenever the primary code-server version # changes, triggering a fresh download and compat re-test for all codenames. CACHE_KEY=$( for cn in $(echo "${!VERSIONS_FOR[@]}" | tr ' ' '\n' | sort); do - printf '%s=%s,%s;' "$cn" "${CS_VERSION_FOR[$cn]}" "${CC_VERSION_FOR[$cn]}" + printf '%s=%s;' "$cn" "${CS_VERSION_FOR[$cn]}" done | sha256sum | awk '{print $1}' ) @@ -280,7 +240,7 @@ runs: echo "Tentative versions per Debian codename (verified on cache miss):" for cn in $(echo "${!VERSIONS_FOR[@]}" | tr ' ' '\n' | sort); do - echo " ${cn}: code-server=${CS_VERSION_FOR[$cn]} copilot-chat=${CC_VERSION_FOR[$cn]}" + echo " ${cn}: code-server=${CS_VERSION_FOR[$cn]}" done # ── Save codenames cache ─────────────────────────────────────────────────── @@ -310,7 +270,7 @@ runs: # ── Cache step ──────────────────────────────────────────────────────────── # A single cache entry covers all detected Debian tiers. The key is a - # hash of every (codename, cs_version, cc_version) tuple, so the cache is + # hash of every (codename, cs_version) tuple, so the cache is # invalidated when any resolved version changes for any tier. The key is # stable across runs as long as the content hash does not change; there is # at most one cache entry per hash. @@ -319,7 +279,7 @@ runs: uses: actions/cache/restore@v5 with: path: ${{ runner.temp }}/preseed-artifacts - key: preseed-${{ steps.detect-and-resolve.outputs.cache_key }} + key: preseed-codeserver-${{ steps.detect-and-resolve.outputs.cache_key }} # ── Download step ───────────────────────────────────────────────────────── # Runs on cache miss (cache-matched-key is empty), or unconditionally when @@ -386,28 +346,6 @@ runs: return $result } - # Returns 0 if all .node files in the VSIX resolve all shared libs on - # the target image, 1 if extraction fails or any lib is missing. - check_vsix_on_image() { - local vsix="$1" image="$2" - local tmp_dir result=0 - tmp_dir=$(mktemp -d) - if ! unzip -q "$vsix" -d "$tmp_dir" 2>/dev/null; then - echo " WARNING: failed to extract VSIX; treating as incompatible" >&2 - rm -rf "$tmp_dir" - return 1 - fi - while IFS= read -r node_file; do - docker run --rm \ - -v "${node_file}:/tmp/check.node:ro" \ - --entrypoint sh \ - "$image" \ - -c 'missing=$(ldd /tmp/check.node 2>&1 | grep "not found"); [ -z "$missing" ] || { echo "$missing" >&2; exit 1; }' \ - || { result=1; break; } - done < <(find "$tmp_dir" -name "*.node" -type f 2>/dev/null) - rm -rf "$tmp_dir" - return $result - } # ────────────────────────────────────────────────────────────────────── # ── Load upstream release data saved by detect-and-resolve ──────────── @@ -416,36 +354,11 @@ runs: [ -f "$RUNNER_TEMP/cs-releases.json" ] || { echo "ERROR: cs-releases.json not found; detect-and-resolve may have failed" >&2; exit 1 } - [ -f "$RUNNER_TEMP/marketplace.json" ] || { - echo "ERROR: marketplace.json not found; detect-and-resolve may have failed" >&2; exit 1 - } CODESERVER_RELEASES_JSON=$(cat "$RUNNER_TEMP/cs-releases.json") - MARKETPLACE_JSON=$(cat "$RUNNER_TEMP/marketplace.json") - - copilot_candidates_for_vscode() { - local vscode="$1" - printf '%s' "$MARKETPLACE_JSON" | jq -r \ - --arg vscode "$vscode" ' - def vparts: split(".") | map(tonumber? // 0); - [ - .results[0].extensions[0].versions[]? | - select(.version | test("^[0-9]+\\.[0-9]+\\.[0-9]{1,9}$")) | - (.properties // [] | - map(select(.key == "Microsoft.VisualStudio.Code.Engine")) | - .[0].value // "") as $engine | - select($engine != "") | - ($engine | gsub("^[^0-9]+"; "") | split("-")[0]) as $min_vscode | - select(($vscode | vparts) >= ($min_vscode | vparts)) | - .version - ] | sort_by(split(".") | map(tonumber? // 0)) | reverse | .[] - ' - } # ── Determine primary versions from resolve.env ──────────────────────── CS_PRIMARY_VAR="CS_VERSION_${PRIMARY_CODENAME}" CS_VERSION_PRIMARY="${!CS_PRIMARY_VAR}" - CC_PRIMARY_VAR="CC_VERSION_${PRIMARY_CODENAME}" - CC_VERSION_PRIMARY="${!CC_PRIMARY_VAR}" # ── Download primary artifacts for compatibility testing ──────────────── echo "Downloading primary (${PRIMARY_CODENAME}) code-server ${CS_VERSION_PRIMARY} amd64 .deb..." >&2 @@ -456,19 +369,9 @@ runs: echo "ERROR: code-server_${CS_VERSION_PRIMARY}_amd64.deb failed format validation" >&2; exit 1 } - echo "Downloading primary (${PRIMARY_CODENAME}) Copilot Chat ${CC_VERSION_PRIMARY} VSIX..." >&2 - curl -fsSL --compressed --retry 3 --retry-all-errors --connect-timeout 30 \ - "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/GitHub/vsextensions/copilot-chat/${CC_VERSION_PRIMARY}/vspackage" \ - -o "$RUNNER_TEMP/cc-${PRIMARY_CODENAME}.vsix" - od -An -N4 -tx1 "$RUNNER_TEMP/cc-${PRIMARY_CODENAME}.vsix" | tr -d ' \n' | grep -q '^504b0304' || { - echo "ERROR: copilot-chat-${CC_VERSION_PRIMARY}.vsix failed format validation" >&2; exit 1 - } - # ── Resolve actual versions for non-primary codenames ────────────────── declare -A CS_VERSION_FOR - declare -A CC_VERSION_FOR CS_VERSION_FOR[$PRIMARY_CODENAME]="$CS_VERSION_PRIMARY" - CC_VERSION_FOR[$PRIMARY_CODENAME]="$CC_VERSION_PRIMARY" for codename in $UNIQUE_CODENAMES; do [ "$codename" = "$PRIMARY_CODENAME" ] && continue @@ -511,43 +414,6 @@ runs: echo "ERROR: no compatible code-server version found for ${test_image}" >&2; exit 1 } fi - - # ── Copilot Chat ───────────────────────────────────────────────────── - if [ "${CS_VERSION_FOR[$codename]}" = "$CS_VERSION_PRIMARY" ] \ - && check_vsix_on_image "$RUNNER_TEMP/cc-${PRIMARY_CODENAME}.vsix" "$test_image"; then - CC_VERSION_FOR[$codename]="$CC_VERSION_PRIMARY" - cp "$RUNNER_TEMP/cc-${PRIMARY_CODENAME}.vsix" \ - "$RUNNER_TEMP/cc-${codename}.vsix" - echo " ${codename} Copilot Chat = ${CC_VERSION_PRIMARY} (primary VSIX works)" >&2 - else - VSCODE_THIS="$(printf '%s' "${CS_VERSION_FOR[$codename]}" | sed 's/^[0-9][0-9]*\./1./')" - CC_VERSION_FOR[$codename]="" - while IFS= read -r candidate; do - echo " Checking Copilot Chat ${candidate} on ${test_image}..." >&2 - TMP_VSIX=$(mktemp --suffix=.vsix) - if [ "$candidate" = "$CC_VERSION_PRIMARY" ] \ - && [ -f "$RUNNER_TEMP/cc-${PRIMARY_CODENAME}.vsix" ]; then - cp "$RUNNER_TEMP/cc-${PRIMARY_CODENAME}.vsix" "$TMP_VSIX" - else - curl -fsSL --compressed --retry 3 --retry-all-errors --connect-timeout 30 \ - "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/GitHub/vsextensions/copilot-chat/${candidate}/vspackage" \ - -o "$TMP_VSIX" 2>/dev/null || { rm -f "$TMP_VSIX"; continue; } - od -An -N4 -tx1 "$TMP_VSIX" | tr -d ' \n' | grep -q '^504b0304' \ - || { rm -f "$TMP_VSIX"; continue; } - fi - if check_vsix_on_image "$TMP_VSIX" "$test_image"; then - CC_VERSION_FOR[$codename]="$candidate" - mv "$TMP_VSIX" "$RUNNER_TEMP/cc-${codename}.vsix" - echo " Selected ${codename} Copilot Chat: ${candidate}" >&2 - break - else - rm -f "$TMP_VSIX" - fi - done < <(copilot_candidates_for_vscode "$VSCODE_THIS") - [ -n "${CC_VERSION_FOR[$codename]}" ] || { - echo "ERROR: no compatible Copilot Chat version found for ${test_image}" >&2; exit 1 - } - fi done # ── Write final-versions.env into the preseed-artifacts cache dir ────── @@ -559,19 +425,17 @@ runs: { for cn in $(echo "${!CS_VERSION_FOR[@]}" | tr ' ' '\n' | sort); do printf 'CS_VERSION_%s=%s\n' "$cn" "${CS_VERSION_FOR[$cn]}" - printf 'CC_VERSION_%s=%s\n' "$cn" "${CC_VERSION_FOR[$cn]}" done } > "$FINAL_VERSIONS_ENV" echo "Final resolved versions per Debian codename:" for cn in $(echo "${!CS_VERSION_FOR[@]}" | tr ' ' '\n' | sort); do - echo " ${cn}: code-server=${CS_VERSION_FOR[$cn]} copilot-chat=${CC_VERSION_FOR[$cn]}" + echo " ${cn}: code-server=${CS_VERSION_FOR[$cn]}" done # ── Download all artifacts ───────────────────────────────────────────── for cn in $UNIQUE_CODENAMES; do cs_ver="${CS_VERSION_FOR[$cn]}" - cc_ver="${CC_VERSION_FOR[$cn]}" ARTIFACT_DIR="${{ runner.temp }}/preseed-artifacts/${cn}" mkdir -p "$ARTIFACT_DIR" @@ -597,26 +461,6 @@ runs: } fi done - - # ── Copilot Chat VSIX ───────────────────────────────────────────────── - VSIX="copilot-chat-${cc_ver}.vsix" - TARGET="${ARTIFACT_DIR}/${VSIX}" - COMPAT_VSIX="$RUNNER_TEMP/cc-${cn}.vsix" - if [ -f "$COMPAT_VSIX" ]; then - echo "Reusing ${VSIX} from compatibility testing phase (${cn})" - [ "$COMPAT_VSIX" != "$TARGET" ] && cp "$COMPAT_VSIX" "$TARGET" - od -An -N4 -tx1 "$TARGET" | tr -d ' \n' | grep -q '^504b0304' || { - echo "ERROR: ${VSIX} failed format validation" >&2; exit 1 - } - else - echo "Downloading ${VSIX} (${cn})" - curl -fsSL --compressed --retry 5 --retry-all-errors --connect-timeout 30 \ - "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/GitHub/vsextensions/copilot-chat/${cc_ver}/vspackage" \ - -o "$TARGET" - od -An -N4 -tx1 "$TARGET" | tr -d ' \n' | grep -q '^504b0304' || { - echo "ERROR: ${VSIX} failed format validation" >&2; exit 1 - } - fi done # ── Save preseed artifacts cache ────────────────────────────────────────── @@ -625,11 +469,11 @@ runs: # hash; when the restore already matched our key, the content is unchanged # and no new entry is needed. - name: Save preseed artifacts cache - if: steps.cache.outputs.cache-matched-key != format('preseed-{0}', steps.detect-and-resolve.outputs.cache_key) + if: steps.cache.outputs.cache-matched-key != format('preseed-codeserver-{0}', steps.detect-and-resolve.outputs.cache_key) uses: actions/cache/save@v5 with: path: ${{ runner.temp }}/preseed-artifacts - key: preseed-${{ steps.detect-and-resolve.outputs.cache_key }} + key: preseed-codeserver-${{ steps.detect-and-resolve.outputs.cache_key }} # ── Assemble step ───────────────────────────────────────────────────────── # Copies unique artifacts to pre-downloaded/, computes SHA256 digests, and @@ -657,13 +501,11 @@ runs: # ── Copy unique artifacts to pre-downloaded/ ─────────────────────────── # Track already-copied filenames to avoid duplicating files when two - # codenames resolve to the same code-server or Copilot Chat version. + # codenames resolve to the same code-server version. declare -A COPIED for cn in $UNIQUE_CODENAMES; do cs_ver_var="CS_VERSION_${cn}" - cc_ver_var="CC_VERSION_${cn}" cs_ver="${!cs_ver_var}" - cc_ver="${!cc_ver_var}" ARTIFACT_DIR="${{ runner.temp }}/preseed-artifacts/${cn}" for ARCH in amd64 arm64; do @@ -673,30 +515,20 @@ runs: COPIED[$DEB]=1 fi done - - VSIX="copilot-chat-${cc_ver}.vsix" - if [ -z "${COPIED[$VSIX]+x}" ]; then - cp "${ARTIFACT_DIR}/${VSIX}" "${PRE_DOWNLOADED_DIR}/" - COPIED[$VSIX]=1 - fi done # ── Compute SHA256 digests ───────────────────────────────────────────── declare -A CS_SHA256_AMD64_FOR declare -A CS_SHA256_ARM64_FOR - declare -A CC_SHA256_FOR for cn in $UNIQUE_CODENAMES; do cs_ver_var="CS_VERSION_${cn}" - cc_ver_var="CC_VERSION_${cn}" cs_ver="${!cs_ver_var}" - cc_ver="${!cc_ver_var}" CS_SHA256_AMD64_FOR[$cn]=$(sha256sum "${PRE_DOWNLOADED_DIR}/code-server_${cs_ver}_amd64.deb" | awk '{print $1}') CS_SHA256_ARM64_FOR[$cn]=$(sha256sum "${PRE_DOWNLOADED_DIR}/code-server_${cs_ver}_arm64.deb" | awk '{print $1}') - CC_SHA256_FOR[$cn]=$(sha256sum "${PRE_DOWNLOADED_DIR}/copilot-chat-${cc_ver}.vsix" | awk '{print $1}') - echo "${cn}: code-server-amd64=${CS_SHA256_AMD64_FOR[$cn]} arm64=${CS_SHA256_ARM64_FOR[$cn]} copilot-chat=${CC_SHA256_FOR[$cn]}" + echo "${cn}: code-server-amd64=${CS_SHA256_AMD64_FOR[$cn]} arm64=${CS_SHA256_ARM64_FOR[$cn]}" done # ── Generate JSON bake override file ─────────────────────────────────── @@ -710,20 +542,15 @@ runs: codename_var="CODENAME_${key}" cn="${!codename_var}" cs_ver_var="CS_VERSION_${cn}" - cc_ver_var="CC_VERSION_${cn}" cs_ver="${!cs_ver_var}" - cc_ver="${!cc_ver_var}" amd64_sha="${CS_SHA256_AMD64_FOR[$cn]}" arm64_sha="${CS_SHA256_ARM64_FOR[$cn]}" - vsix_sha="${CC_SHA256_FOR[$cn]}" $first || printf ',' first=false printf '"%s":{"args":{' "php${key}-downloader" printf '"CODESERVER_VERSION":"%s",' "$cs_ver" printf '"CODESERVER_DEB_SHA256_AMD64":"%s",' "$amd64_sha" - printf '"CODESERVER_DEB_SHA256_ARM64":"%s",' "$arm64_sha" - printf '"COPILOT_CHAT_VERSION":"%s",' "$cc_ver" - printf '"COPILOT_CHAT_VSIX_SHA256":"%s"' "$vsix_sha" + printf '"CODESERVER_DEB_SHA256_ARM64":"%s"' "$arm64_sha" printf '}}' done printf '}}' diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index bc09d4da..316ac98a 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -24,7 +24,7 @@ on: no_cache: description: > Disable Docker layer cache and force a fresh download of all build - artefacts (code-server, Copilot Chat). + artefacts (code-server). When false, the cache is used as normal. type: boolean default: true @@ -188,8 +188,8 @@ jobs: path: ${{ steps.preseed.outputs.bake_overrides_file }} retention-days: 1 - # Upload the flat pre-downloaded/ directory (code-server .deb files and - # Copilot Chat VSIX) as a workflow artifact so build jobs can download it + # Upload the flat pre-downloaded/ directory (code-server .deb files) + # as a workflow artifact so build jobs can download it # directly. Using an artifact (rather than the GHA preseed cache) is # more reliable because artifacts are guaranteed to persist for the # lifetime of the workflow run; the GHA cache can be evicted mid-run @@ -231,7 +231,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 - # Download the pre-seeded code-server .deb and Copilot Chat VSIX files + # Download the pre-seeded code-server .deb files # that the detect job assembled and uploaded. Using a workflow artifact # (not the GHA preseed cache) guarantees these files are present for the # lifetime of the run; a cache miss would silently produce an empty diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a9c072be..8a821d18 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -179,8 +179,8 @@ jobs: path: ${{ steps.preseed.outputs.bake_overrides_file }} retention-days: 1 - # Upload the flat pre-downloaded/ directory (code-server .deb files and - # Copilot Chat VSIX) as a workflow artifact so build jobs can download it + # Upload the flat pre-downloaded/ directory (code-server .deb files) + # as a workflow artifact so build jobs can download it # directly. Using an artifact (rather than the GHA preseed cache) is # more reliable because artifacts are guaranteed to persist for the # lifetime of the workflow run; the GHA cache can be evicted mid-run @@ -198,7 +198,7 @@ jobs: build: name: Build and test PHP ${{ matrix.version }} runs-on: ubuntu-latest - needs: [lint-yaml, lint-shell, lint-dockerfile, verify-extension, verify-code-server, detect] + needs: [lint-yaml, lint-shell, lint-dockerfile, verify-code-server, detect] if: needs.detect.outputs.has_versions == 'true' strategy: fail-fast: false @@ -221,7 +221,7 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - # Download the pre-seeded code-server .deb and Copilot Chat VSIX files + # Download the pre-seeded code-server .deb files # that the detect job assembled and uploaded. Using a workflow artifact # (not the GHA preseed cache) guarantees these files are present for the # lifetime of the run; a cache miss would silently produce an empty @@ -233,7 +233,7 @@ jobs: path: ${{ runner.temp }}/build-downloads/pre-downloaded # Download the bake-overrides JSON generated by the detect job's preseed - # run. It contains the correct code-server / Copilot Chat versions and + # run. It contains the correct code-server versions and # SHA256 digests for every detected Debian codename, including the # downgraded versions needed for older codenames (Buster / Bullseye). - name: Download preseed bake overrides @@ -254,54 +254,6 @@ jobs: - name: Run PHP functional tests run: bash tests/run-dockerfile.sh --version ${{ matrix.version }} - verify-extension: - name: Verify Copilot Chat VSIX - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - uses: actions/checkout@v6 - - - name: Get pinned Copilot Chat version - id: copilot-version - run: | - echo "version=$(grep -m1 '^ARG COPILOT_CHAT_VERSION=' base/Dockerfile | cut -d= -f2)" >> "$GITHUB_OUTPUT" - - - name: Cache Copilot Chat VSIX - id: cache-vsix - uses: actions/cache@v5 - with: - path: ${{ runner.temp }}/copilot-chat.vsix - key: copilot-chat-vsix-${{ steps.copilot-version.outputs.version }} - - - name: Download Copilot Chat VSIX (cache miss only) - if: steps.cache-vsix.outputs.cache-hit != 'true' - run: | - VERSION="${{ steps.copilot-version.outputs.version }}" - VSIX_URL="https://marketplace.visualstudio.com/_apis/public/gallery/publishers/GitHub/vsextensions/copilot-chat/${VERSION}/vspackage" - echo "Downloading GitHub.copilot-chat ${VERSION} from VS Marketplace" - curl -fsSL --compressed --retry 5 --retry-all-errors --connect-timeout 30 \ - "${VSIX_URL}" -o "$RUNNER_TEMP/copilot-chat.vsix" - - - name: Verify Copilot Chat VSIX - run: | - EXPECTED_SHA="$(grep -m1 '^ARG COPILOT_CHAT_VSIX_SHA256=' base/Dockerfile | cut -d= -f2)" - if ! od -An -tx1 -N4 "$RUNNER_TEMP/copilot-chat.vsix" | tr -d ' \n' | grep -qi '^504b0304'; then - ACTUAL_MAGIC="$(od -An -tx1 -N8 "$RUNNER_TEMP/copilot-chat.vsix" | tr -d ' \n')" - echo "Downloaded file is not a valid ZIP (expected magic 504b0304, got: ${ACTUAL_MAGIC})" - echo "The Marketplace may have returned a rate-limit/error page or gzip-encoded content." - exit 1 - fi - ACTUAL_SHA="$(sha256sum "$RUNNER_TEMP/copilot-chat.vsix" | cut -d' ' -f1)" - echo "Computed SHA256: ${ACTUAL_SHA}" - if [ -n "${EXPECTED_SHA}" ]; then - echo "${EXPECTED_SHA} $RUNNER_TEMP/copilot-chat.vsix" | sha256sum -c - - echo "SHA256 verified." - else - echo "::warning::COPILOT_CHAT_VSIX_SHA256 is not set in base/Dockerfile." - echo "::warning::Set ARG COPILOT_CHAT_VSIX_SHA256=${ACTUAL_SHA} in base/Dockerfile to enable integrity verification." - fi - verify-code-server: name: Verify code-server .deb hashes runs-on: ubuntu-latest @@ -371,7 +323,6 @@ jobs: - lint-yaml - lint-shell - lint-dockerfile - - verify-extension - verify-code-server - prepare - detect diff --git a/7.4/base/Dockerfile b/7.4/base/Dockerfile index 45d7907f..0705cf1e 100644 --- a/7.4/base/Dockerfile +++ b/7.4/base/Dockerfile @@ -43,8 +43,8 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd pspell \ && pecl install -o -f \ - igbinary-3.2.12 \ - imagick-3.8.1 \ + igbinary \ + imagick \ oauth-2.0.7 \ && docker-php-ext-enable gd igbinary imagick oauth pspell \ && apt-mark auto '.*' > /dev/null \ diff --git a/8.0/base/Dockerfile b/8.0/base/Dockerfile index 90a47587..6d56bcaf 100644 --- a/8.0/base/Dockerfile +++ b/8.0/base/Dockerfile @@ -38,8 +38,8 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd pspell \ && pecl install -o -f \ - igbinary-3.2.12 \ - imagick-3.8.1 \ + igbinary \ + imagick \ && docker-php-ext-enable gd igbinary imagick pspell \ && apt-mark auto '.*' > /dev/null \ && echo "$savedAptMark" | xargs -r apt-mark manual \ diff --git a/8.1/base/Dockerfile b/8.1/base/Dockerfile index 8192a3ac..8b89c9d8 100644 --- a/8.1/base/Dockerfile +++ b/8.1/base/Dockerfile @@ -39,8 +39,8 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ && docker-php-ext-configure gd --with-avif --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd pspell \ && pecl install -o -f \ - igbinary-3.2.12 \ - imagick-3.8.1 \ + igbinary \ + imagick \ && docker-php-ext-enable gd igbinary imagick pspell \ && apt-mark auto '.*' > /dev/null \ && echo "$savedAptMark" | xargs -r apt-mark manual \ diff --git a/8.2/base/Dockerfile b/8.2/base/Dockerfile index 29e79080..2500f43d 100644 --- a/8.2/base/Dockerfile +++ b/8.2/base/Dockerfile @@ -39,8 +39,8 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ && docker-php-ext-configure gd --with-avif --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j$(nproc) gd pspell \ && pecl install -o -f \ - igbinary-3.2.12 \ - imagick-3.8.1 \ + igbinary \ + imagick \ && docker-php-ext-enable gd igbinary imagick pspell \ && apt-mark auto '.*' > /dev/null \ && echo "$savedAptMark" | xargs -r apt-mark manual \ diff --git a/AGENTS.md b/AGENTS.md index 1855e570..abbe2022 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -125,7 +125,7 @@ Baseline JSON files in `tests/baselines/` track pre-existing lint violations per #### Composite Actions - **`.github/actions/build-php-images`** — detects changed PHP versions and runs `docker buildx bake` using `docker-bake.hcl`. Called directly by the trigger workflows below. -- **`.github/actions/preseed-downloads`** — resolves code-server and Copilot Chat versions, restores/downloads artifacts into `$RUNNER_TEMP/build-downloads/pre-downloaded/`, and exposes `DOWNLOADS_DIR` + SHA256 outputs for Docker builds. +- **`.github/actions/preseed-downloads`** — resolves code-server versions, restores/downloads artifacts into `$RUNNER_TEMP/build-downloads/pre-downloaded/`, and exposes `DOWNLOADS_DIR` + SHA256 outputs for Docker builds. #### Workflows - **`docker-build.yml`** — triggered on pushes to `main` or `develop` and by manual `workflow_dispatch`; detects changed versions, builds images per-platform in parallel, and merges platform-specific digests into multi-arch manifests. diff --git a/TODO.md b/TODO.md index 236a3d66..ac31c8b8 100644 --- a/TODO.md +++ b/TODO.md @@ -46,3 +46,18 @@ Use this file to track work that needs to be done. Lint scripts automatically ap ## Definition of Done - The CI failures from Test run 39719313157 have targeted fixes. - The affected image builds and functional tests pass in CI; modified files contain no secrets. + +# Task: Address build and Copilot Chat feedback + +## Steps +- [x] Inspect the latest CI failures and the ten PR commits. +- [x] Use HTTPS for PECL channel metadata and remove unnecessary extension pins. +- [x] Remove separate Copilot Chat downloads, installation, and verification. +- [x] Run focused lint/build checks and refresh affected baselines; PHP 7.4 Bake build is blocked by sandbox DNS resolution of Debian repositories. +- [x] Scan changed files for secrets; none detected. +- [ ] Complete code/security review and report remaining CI verification. + +## Definition of Done +- Builds no longer download or install a separate Copilot Chat extension. +- PECL uses HTTPS channel metadata and unpinned shared extensions. +- Focused checks pass; any unavailable build verification is explicitly reported. diff --git a/base/Dockerfile b/base/Dockerfile index b4923b9d..33516b8c 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -38,57 +38,18 @@ ARG CODESERVER_VERSION=4.99.4 ARG CODESERVER_DEB_SHA256_AMD64=74c107b33643ed11223263b86431530fbc9c9f7c8202997579a6f0c41b4cb102 ARG CODESERVER_DEB_SHA256_ARM64=ae12d6300736dd742de3f5aef1b80c996c4b164ed385992e5de9d55f1e2077c3 -# GitHub Copilot Chat is not available in the Open VSX marketplace used by -# code-server, so we download it directly from the VS Marketplace. -# -# Version channels -# ────────────────────────────────── -# The Copilot Chat extension publishes two channels to the VS Marketplace: -# • Stable releases use simple MAJOR.MINOR.PATCH version numbers -# (e.g. 0.26.7, visible as "0.26.0 … 0.26.7" in the VS Code desktop UI). -# • Pre-release builds use a build-date-stamped format: MAJOR.MINOR.YYYYMMDDNN -# (e.g. 0.26.2025040204 = series 0.26, released 2025-04-02, build 04). -# Only stable releases are passed by CI (patch ≤ 9 digits excludes date-stamps). -# code-server 4.X.Y bundles VS Code 1.X.Y (e.g. 4.99.4 → VS Code 1.99.4). -# -# CI resolves the latest compatible stable versions before the build and passes -# them explicitly via CODESERVER_VERSION and COPILOT_CHAT_VERSION build args. -# The defaults below are used for standalone `docker build` invocations. -# -# To update the defaults or SHA256 values: -# 1. Find the latest code-server release at github.com/coder/code-server/releases -# 2. Download the VSIX with --compressed so curl decompresses transfer encoding: -# curl -fsSL --compressed \ -# "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/GitHub/vsextensions/copilot-chat//vspackage" \ -# -o copilot-chat-.vsix -# 3. Run: sha256sum copilot-chat-.vsix (Linux) or -# shasum -a 256 copilot-chat-.vsix (macOS) -# 4. Update COPILOT_CHAT_VERSION and COPILOT_CHAT_VSIX_SHA256 below. -ARG COPILOT_CHAT_VERSION=0.26.7 -# SHA256 of the VSIX downloaded from the VS Marketplace vspackage endpoint -# (architecture-independent). CI passes the value computed at download time; -# standalone builds verify against this pinned value. -ARG COPILOT_CHAT_VSIX_SHA256=691e80194fdba0399879fd065a76b9b14b32bfd29818292e816a4522eb0330d1 - -# Install unzip and zip early so they are available for VSIX validation and -# repacking during the download step. -RUN apt-get update \ - && apt-get install -y --no-install-recommends unzip zip \ - && rm -rf /var/lib/apt/lists/* - # Download libsodium source (pinned release, identical for every PHP version) RUN mkdir -p /tmp/libsodium \ && curl -sL https://github.com/jedisct1/libsodium/archive/1.0.18-RELEASE.tar.gz \ | tar xzf - -C /tmp/libsodium -# Download code-server deb and GitHub Copilot Chat VSIX. -# Versions are resolved by CI and passed via CODESERVER_VERSION and -# COPILOT_CHAT_VERSION build args; standalone builds use the Dockerfile defaults. +# Download code-server deb. +# The version is resolved by CI and passed via CODESERVER_VERSION; +# standalone builds use the Dockerfile default. # /pre-downloaded (bind-mounted from the 'downloads' named context) holds files # pre-seeded by CI via actions/cache@v5 so they are never re-downloaded for the # same version even when Docker layers change. RUN --mount=type=cache,id=code-server-debs,target=/var/cache/code-server-debs,sharing=locked \ - --mount=type=cache,id=copilot-chat-vsix,target=/var/cache/copilot-chat-vsix,sharing=locked \ --mount=type=bind,from=downloads,source=/pre-downloaded,target=/pre-downloaded,readonly \ set -e; \ # code-server is required: fail build if version is empty @@ -126,89 +87,13 @@ RUN --mount=type=cache,id=code-server-debs,target=/var/cache/code-server-debs,sh echo " actual: ${ACTUAL_SHA}" >&2; \ echo "If you changed CODESERVER_VERSION, also update ${SHA_ARG} to match." >&2; \ exit 1; \ - fi; \ - \ - # Download GitHub Copilot Chat VSIX (not available in Open VSX marketplace). \ - # COPILOT_CHAT_VERSION is always provided — CI resolves it before the build and \ - # standalone builds fall back to the Dockerfile default. \ - [ -n "$COPILOT_CHAT_VERSION" ] || { echo "COPILOT_CHAT_VERSION is empty" >&2; exit 1; }; \ - # Validate the version matches semver major.minor.patch with optional pre-release/ - # build metadata using only URL-safe characters. This prevents a malformed URL - # from being constructed: an empty string, path separator, or other unexpected - # value is caught here with a clear message rather than silently producing - # .../copilot-chat//vspackage or similar. - printf '%s\n' "$COPILOT_CHAT_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z._-]*)?$' \ - || { echo "COPILOT_CHAT_VERSION has unexpected format: '${COPILOT_CHAT_VERSION}' (expected semver e.g. 1.2.3)" >&2; exit 1; }; \ - echo "Fetching Copilot Chat VSIX version: $COPILOT_CHAT_VERSION" >&2; \ - VSIX_URL="https://marketplace.visualstudio.com/_apis/public/gallery/publishers/GitHub/vsextensions/copilot-chat/${COPILOT_CHAT_VERSION}/vspackage"; \ - VSIX_CACHE="/var/cache/copilot-chat-vsix/copilot-chat-${COPILOT_CHAT_VERSION}.vsix"; \ - VSIX_CACHE_TMP="${VSIX_CACHE}.tmp"; \ - if [ ! -f "$VSIX_CACHE" ]; then \ - VSIX_PRELOADED="/pre-downloaded/copilot-chat-${COPILOT_CHAT_VERSION}.vsix"; \ - if [ -f "$VSIX_PRELOADED" ]; then \ - echo "Using pre-seeded Copilot Chat VSIX ${COPILOT_CHAT_VERSION} from downloads context" >&2; \ - cp "$VSIX_PRELOADED" "$VSIX_CACHE"; \ - else \ - echo "Downloading Copilot Chat VSIX ${COPILOT_CHAT_VERSION}" >&2; \ - curl -fsSL --compressed --retry 5 --retry-all-errors --connect-timeout 10 "$VSIX_URL" -o "$VSIX_CACHE_TMP" \ - && mv "$VSIX_CACHE_TMP" "$VSIX_CACHE" \ - || { rm -f "$VSIX_CACHE_TMP"; echo "Failed to download Copilot Chat VSIX ${COPILOT_CHAT_VERSION}" >&2; exit 1; }; \ - fi; \ - else \ - echo "Using cached Copilot Chat VSIX ${COPILOT_CHAT_VERSION}" >&2; \ - fi; \ - cp "$VSIX_CACHE" /tmp/copilot-chat.vsix; \ - [ -n "$COPILOT_CHAT_VSIX_SHA256" ] || { echo "COPILOT_CHAT_VSIX_SHA256 is empty — pass a matching hash when overriding COPILOT_CHAT_VERSION" >&2; exit 1; }; \ - if ! echo "$COPILOT_CHAT_VSIX_SHA256 /tmp/copilot-chat.vsix" | sha256sum -c -; then \ - ACTUAL_SHA="$(sha256sum /tmp/copilot-chat.vsix | awk '{print $1}')"; \ - rm -f "$VSIX_CACHE"; \ - echo "SHA256 mismatch for Copilot Chat VSIX ${COPILOT_CHAT_VERSION}:" >&2; \ - echo " expected: ${COPILOT_CHAT_VSIX_SHA256}" >&2; \ - echo " actual: ${ACTUAL_SHA}" >&2; \ - echo "If you changed COPILOT_CHAT_VERSION, also update COPILOT_CHAT_VSIX_SHA256 to match." >&2; \ - exit 1; \ - fi; \ - # Verify the download is a valid ZIP (VSIX is a ZIP archive). - # A ZIP file starts with the PK magic bytes: 50 4b 03 04 (hex). - # If the Marketplace returned an error page or rate-limit HTML instead of - # the binary, this guard gives a clear diagnostic rather than a cryptic - # "cannot find zipfile directory" message from unzip. - if ! od -An -tx1 -N4 /tmp/copilot-chat.vsix | tr -d ' \n' | grep -qi '^504b0304'; then \ - ACTUAL_MAGIC="$(od -An -tx1 -N8 /tmp/copilot-chat.vsix | tr -d ' \n')"; \ - echo "Copilot Chat VSIX is not a valid ZIP (expected magic 504b0304, got: ${ACTUAL_MAGIC})." >&2; \ - echo "The Marketplace may have returned a rate-limit/error page instead of the binary." >&2; \ - echo "If the first bytes are 1f8b (gzip), re-run with --compressed on the curl command." >&2; \ - rm -f "$VSIX_CACHE"; \ - echo "Cache cleared, please retry" >&2; \ - exit 1; \ - fi; \ - unzip -q /tmp/copilot-chat.vsix -d /tmp/_vsix_repack \ - || { echo "Failed to extract copilot-chat.vsix — file may be invalid or not a valid ZIP/VSIX (check marketplace response)" >&2; \ - exit 1; } - -# Repack the Copilot Chat VSIX to use only standard deflate (method 8) or stored -# (method 0) compression. Newer VS Marketplace packages may use Deflate64 -# (method 9) or other methods unsupported by the yauzl library bundled with -# code-server, causing a fatal "Extract" error at install time. Repacking with -# the system zip utility (which only emits methods 0 and 8) ensures the VSIX can -# be installed by any code-server version. -# TODO: Remove this repack step once code-server upgrades yauzl (or its bundled -# VS Code) to a version that supports Deflate64 (method 9) natively. -# The files were already unpacked into /tmp/_vsix_repack by the download step -# above. The repack is skipped only if that directory does not exist (e.g. a -# cache hit on a layer predating this code). The original file is kept until the -# repacked archive is fully written to avoid silently losing the VSIX on failure. -RUN if [ -d /tmp/_vsix_repack ]; then \ - ( cd /tmp/_vsix_repack && zip -r -9 /tmp/copilot-chat-repacked.vsix . ) \ - && mv /tmp/copilot-chat-repacked.vsix /tmp/copilot-chat.vsix \ - && rm -rf /tmp/_vsix_repack; \ - fi + fi # ─── Common PHP extensions stage ────────────────────────────────────────────── # Builds the shared intermediate that all per-version Dockerfiles extend. # Only packages and extensions that are IDENTICAL across all PHP versions are # here. Version-specific items (avif/pcre packages, gd configure flags, -# igbinary version pin, imagick build method, oauth, xdebug) live in each +# oauth, xdebug) live in each # {version}/base/Dockerfile. # # This stage is referenced as the 'common-php-ext' Bake named context. @@ -344,14 +229,14 @@ RUN --mount=type=bind,from=common-downloader,source=/tmp/libsodium,target=/tmp/l pdo_mysql pdo_pgsql shmop soap sockets sysvmsg sysvsem sysvshm \ tidy xsl zip pcntl \ && for i in $(seq 1 3); do \ - pecl channel-update pecl.php.net \ + pecl channel-update https://pecl.php.net/channel.xml \ && break \ || { [ "$i" -lt 3 ] && sleep 5; }; \ done \ && pecl install -o -f \ - redis-6.3.0 \ - yaml-2.2.2 \ - memcached-3.2.0 \ + redis \ + yaml \ + memcached \ && rm -f /usr/local/etc/php/conf.d/*sodium.ini \ && rm -f /usr/local/lib/php/extensions/*/*sodium.so \ && apt-get remove libsodium* -y \ @@ -413,11 +298,6 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ && rm /tmp/code-server.deb \ && rm -f /etc/apt/apt.conf.d/01dpkg-no-pty -# Store the GitHub Copilot Chat VSIX (repacked with standard Deflate above) at -# a persistent path outside APP_ROOT (which is a mounted volume) so it is -# available at container startup for apache-start.sh to install. -COPY --from=common-downloader /tmp/copilot-chat.vsix /usr/local/share/devpanel/copilot-chat.vsix - # Install Composer 2.x (default 2.3.10; PHP 8.3 overrides to 2.7.7 via self-update) # and legacy Composer 1.x for projects that require it. # 'composer2' bash alias is set after USER switch below. diff --git a/base/scripts/apache-start.sh b/base/scripts/apache-start.sh index 4988dd49..1ced8e9a 100644 --- a/base/scripts/apache-start.sh +++ b/base/scripts/apache-start.sh @@ -57,10 +57,7 @@ chown "${SUDO_USER:-$USER}:" "$CODES_USER_DATA_DIR" "$CODES_USER_DATA_DIR/extens set -m if [[ "$CODES_ENABLE" == "yes" ]]; then - # Install the GitHub Copilot Chat extension and any user-specified VSCode extensions. - if [ -z "$(find "$CODES_USER_DATA_DIR/extensions/" -maxdepth 1 -iname 'github.copilot-chat-*' -type d 2>/dev/null)" ]; then - sudo -u "${SUDO_USER:-$USER}" -E -- code-server --install-extension /usr/local/share/devpanel/copilot-chat.vsix --user-data-dir="$CODES_USER_DATA_DIR" - fi + # Install any user-specified VSCode extensions. if [ -n "${DP_VSCODE_EXTENSIONS:-}" ]; then IFS=',' read -ra _dp_extensions <<< "$DP_VSCODE_EXTENSIONS" for value in "${_dp_extensions[@]}"; do diff --git a/docker-bake.hcl b/docker-bake.hcl index d3003419..94bb2b28 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -19,7 +19,7 @@ # PLATFORMS Comma-separated target platforms ("linux/amd64,linux/arm64") # VERSIONS_NEEDING_MULTIPART_FIX Versions running Debian 11 / modsec 2.9.3 ("7.4 8.0") # -# Per-version code-server and Copilot Chat build args are NOT global bake +# Per-version code-server build args are NOT global bake # variables. In CI they are injected per-target via a JSON bake-override file # generated by the preseed-downloads composite action (one entry per # phpX_Y-downloader target). For standalone local builds the Dockerfile ARG @@ -70,7 +70,7 @@ variable "VERSIONS_SECURE" { default = "7.4 8.0 8.1 8.2 8.3" } variable "CORERULESET_VERSION" { default = "3.3.5" } # DOWNLOADS_DIR: path to a directory whose pre-downloaded/ subdirectory contains -# pre-seeded build artifacts (code-server .deb files and Copilot Chat VSIX). +# pre-seeded build artifacts (code-server .deb files). # When set by CI to a runner-local path pre-populated by actions/cache@v5, the # downloader stage bind-mounts /pre-downloaded from it instead of downloading. # When empty (default), no named context override is provided and the Dockerfile's @@ -199,8 +199,8 @@ function "cache_to_registry" { # ─── Per-version downloader targets (registry-cached in GHCR, NOT pushed to Docker Hub) ─ # Each PHP version has its own downloader that runs inside php:${PHP_VERSION}-apache, # which uses the correct Debian release for that PHP version. In CI the code-server -# and Copilot Chat build args (CODESERVER_VERSION, CODESERVER_DEB_SHA256_AMD64/ARM64, -# COPILOT_CHAT_VERSION, COPILOT_CHAT_VSIX_SHA256) are injected per-target via a JSON +# build args (CODESERVER_VERSION, CODESERVER_DEB_SHA256_AMD64/ARM64) +# are injected per-target via a JSON # bake-override file generated by the preseed-downloads composite action. That action # detects each version's Debian codename at runtime and resolves the newest compatible # binary for it — no hardcoded Debian version names anywhere in this file. diff --git a/tests/baselines/hadolint-baseline.json b/tests/baselines/hadolint-baseline.json index d79e18bd..672f097a 100644 --- a/tests/baselines/hadolint-baseline.json +++ b/tests/baselines/hadolint-baseline.json @@ -39,9 +39,9 @@ "./advance/Dockerfile:DL3006": 1, "./advance/Dockerfile:DL3008": 2, "./advance/Dockerfile:DL4006": 2, - "./base/Dockerfile:DL3003": 2, - "./base/Dockerfile:DL3008": 3, - "./base/Dockerfile:DL3022": 8, + "./base/Dockerfile:DL3003": 1, + "./base/Dockerfile:DL3008": 2, + "./base/Dockerfile:DL3022": 7, "./base/Dockerfile:DL3047": 1, "./base/Dockerfile:DL4001": 1, "./base/Dockerfile:DL4006": 4, diff --git a/tests/build-dockerfile.sh b/tests/build-dockerfile.sh index ec2800d9..54ac2d1a 100755 --- a/tests/build-dockerfile.sh +++ b/tests/build-dockerfile.sh @@ -137,7 +137,7 @@ PLATFORM_KEY="${NORMALIZED_PLATFORM//\//-}" # preseed-downloads composite action. When set, it is # passed as an additional --file argument so each # phpX_Y-downloader target receives the correct -# code-server/Copilot Chat version and SHA256 args for +# code-server version and SHA256 args for # its Debian tier. When unset, the Dockerfile ARG # defaults are used as fallback. # diff --git a/tests/run-dockerfile.sh b/tests/run-dockerfile.sh index eb98863c..dd2b2549 100755 --- a/tests/run-dockerfile.sh +++ b/tests/run-dockerfile.sh @@ -181,6 +181,11 @@ test_base() { assert_contains "$image" "imagick" "imagick extension loaded" \ php -m + for extension in redis yaml memcached igbinary; do + assert_contains "$image" "^${extension}$" "${extension} extension loaded" \ + php -m + done + # Verify no PHP extension .so has unresolved shared-library dependencies. # A "not found" line from ldd means a runtime library was removed along with # the dev packages — the failure mode caused by the usrmerge path mismatch @@ -191,13 +196,9 @@ test_base() { assert_empty "$image" "no unresolved .so deps in PHP extensions" \ sh -c 'ext=$(php -r "echo PHP_EXTENSION_DIR;") && find "$ext" -name "*.so" -exec ldd {} + 2>/dev/null | grep "not found" || true' - # GitHub Copilot Chat extension must be installed in the extensions directory. - # code-server --install-extension unpacks the VSIX into a subdirectory named - # after the extension (e.g. GitHub.copilot-chat-). Use find with - # -iname to locate the directory case-insensitively, normalize to lowercase, - # and grep to confirm the expected publisher/name prefix is present. - assert_contains "$image" "github\.copilot-chat" "GitHub Copilot Chat extension installs and is found" \ - sh -c "code-server --install-extension /usr/local/share/devpanel/copilot-chat.vsix --user-data-dir /var/www/html/.vscode --force && find /var/www/html/.vscode/extensions/ -maxdepth 1 -iname 'github.copilot-chat-*' -type d | tr '[:upper:]' '[:lower:]' | grep 'github.copilot-chat'" + # code-server must work without installing a separate Chat extension. + assert_contains "$image" "[0-9]+\.[0-9]+\.[0-9]+" "code-server available" \ + code-server --version } test_secure() { From 1288915ea5d719ea63f3d59213cc9f2dd0bd1db3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 11:31:30 +0000 Subject: [PATCH 12/22] Record validation results and pending CI approval Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index ac31c8b8..c0ce7475 100644 --- a/TODO.md +++ b/TODO.md @@ -55,7 +55,7 @@ Use this file to track work that needs to be done. Lint scripts automatically ap - [x] Remove separate Copilot Chat downloads, installation, and verification. - [x] Run focused lint/build checks and refresh affected baselines; PHP 7.4 Bake build is blocked by sandbox DNS resolution of Debian repositories. - [x] Scan changed files for secrets; none detected. -- [ ] Complete code/security review and report remaining CI verification. +- [x] Complete code/security review: independent review found no significant issues and CodeQL reported zero alerts. Test run 39771022231 requires approval before end-to-end builds can be verified. ## Definition of Done - Builds no longer download or install a separate Copilot Chat extension. From db2947264d84d75a4b510d9137fe5d9bfdc3ae80 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 12:49:20 +0000 Subject: [PATCH 13/22] Upgrade legacy PEAR transport and handle built-in PHP 8.5 OPcache Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- TODO.md | 14 ++++++++++++++ base/Dockerfile | 13 ++++++++++++- tests/run-dockerfile.sh | 3 +++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index c0ce7475..70c143c0 100644 --- a/TODO.md +++ b/TODO.md @@ -61,3 +61,17 @@ Use this file to track work that needs to be done. Lint scripts automatically ap - Builds no longer download or install a separate Copilot Chat extension. - PECL uses HTTPS channel metadata and unpinned shared extensions. - Focused checks pass; any unavailable build verification is explicitly reported. + +# Task: Fix remaining PEAR and OPcache build failures + +## Steps +- [x] Inspect Test run 39771238006 and the twelve PR commits. +- [x] Upgrade PEAR with curl-downloaded local metadata-independent installation. +- [x] Enable OPcache only when it is not already loaded, including built-in PHP 8.5. +- [x] Run Dockerfile/shell lint, shell syntax and Bake parsing; verify the OPcache guard on official PHP 7.4 and 8.5 images. Full PHP 7.4 Bake build is blocked by sandbox Debian DNS resolution. +- [ ] Scan for secrets and complete code/security review. + +## Definition of Done +- Legacy images use a PEAR release with HTTP chunked response decoding. +- OPcache remains loaded on every PHP version without enabling a nonexistent PHP 8.5 shared module. +- Focused checks pass; any remaining build or CI blockers are explicitly reported. diff --git a/base/Dockerfile b/base/Dockerfile index 33516b8c..6c857ead 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -179,6 +179,14 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ && rm -rf /var/lib/apt/lists/* \ && rm -f /etc/apt/apt.conf.d/01dpkg-no-pty +#== Update PEAR for HTTP chunked response support in legacy PHP images +ARG PEAR_VERSION=1.10.18 +RUN curl -fsSL --compressed --retry 5 --retry-all-errors --connect-timeout 10 \ + "https://pear.php.net/get/PEAR-${PEAR_VERSION}.tgz" -o /tmp/pear.tgz \ + && pear upgrade --offline /tmp/pear.tgz \ + && pear clear-cache \ + && rm -f /tmp/pear.tgz + # Install development libraries, build all common PHP and PECL extensions, # build libsodium from source, then remove the dev libraries and their # automatically-added dependencies that are not needed at runtime. @@ -243,9 +251,12 @@ RUN --mount=type=bind,from=common-downloader,source=/tmp/libsodium,target=/tmp/l && ( cd /tmp/libsodium/libsodium-1.0.18-RELEASE/ && ./configure && make && make check && make install ) \ && pecl install -o -f libsodium \ && docker-php-ext-enable \ - bcmath bz2 calendar exif gettext gmp intl mysqli opcache \ + bcmath bz2 calendar exif gettext gmp intl mysqli \ pdo_mysql pdo_pgsql redis shmop soap sockets sodium sysvmsg \ sysvsem sysvshm tidy xsl yaml zip pcntl memcached \ + && if ! php -r 'exit(extension_loaded("Zend OPcache") ? 0 : 1);'; then \ + docker-php-ext-enable opcache; \ + fi \ && apt-mark auto '.*' > /dev/null \ && echo "$savedAptMark" | xargs -r apt-mark manual \ && { find /usr/local/lib/php/extensions -name '*.so' \ diff --git a/tests/run-dockerfile.sh b/tests/run-dockerfile.sh index dd2b2549..f1b15875 100755 --- a/tests/run-dockerfile.sh +++ b/tests/run-dockerfile.sh @@ -186,6 +186,9 @@ test_base() { php -m done + assert_contains "$image" "^Zend OPcache$" "OPcache extension loaded" \ + php -m + # Verify no PHP extension .so has unresolved shared-library dependencies. # A "not found" line from ldd means a runtime library was removed along with # the dev packages — the failure mode caused by the usrmerge path mismatch From 012daca8890c7558981a5fba39066ff2294c2f3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 12:51:40 +0000 Subject: [PATCH 14/22] Make offline PEAR bootstrap safe on fresh images Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- TODO.md | 2 +- base/Dockerfile | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 70c143c0..f35f75c0 100644 --- a/TODO.md +++ b/TODO.md @@ -69,7 +69,7 @@ Use this file to track work that needs to be done. Lint scripts automatically ap - [x] Upgrade PEAR with curl-downloaded local metadata-independent installation. - [x] Enable OPcache only when it is not already loaded, including built-in PHP 8.5. - [x] Run Dockerfile/shell lint, shell syntax and Bake parsing; verify the OPcache guard on official PHP 7.4 and 8.5 images. Full PHP 7.4 Bake build is blocked by sandbox Debian DNS resolution. -- [ ] Scan for secrets and complete code/security review. +- [x] Scan for secrets; no secrets detected. Independent review found no significant issues; automated review was unavailable and CodeQL had no analyzable code. Fresh PHP 7.4/8.5 cache initialization checks pass; CI requires approval. ## Definition of Done - Legacy images use a PEAR release with HTTP chunked response decoding. diff --git a/base/Dockerfile b/base/Dockerfile index 6c857ead..8d196900 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -183,7 +183,8 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ ARG PEAR_VERSION=1.10.18 RUN curl -fsSL --compressed --retry 5 --retry-all-errors --connect-timeout 10 \ "https://pear.php.net/get/PEAR-${PEAR_VERSION}.tgz" -o /tmp/pear.tgz \ - && pear upgrade --offline /tmp/pear.tgz \ + && pear upgrade --offline --loose /tmp/pear.tgz \ + && mkdir -p "$(pear config-get cache_dir)" \ && pear clear-cache \ && rm -f /tmp/pear.tgz From e1dd7ae9828a198d04a905c35f9d1055b66efd4e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 14:54:15 +0000 Subject: [PATCH 15/22] Remove obsolete Copilot Chat README instructions Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- README.md | 92 ------------------------------------------------------- 1 file changed, 92 deletions(-) diff --git a/README.md b/README.md index 64df8625..b0cdf93b 100644 --- a/README.md +++ b/README.md @@ -312,95 +312,3 @@ done ``` Then update `CODESERVER_VERSION`, `CODESERVER_DEB_SHA256_AMD64`, and `CODESERVER_DEB_SHA256_ARM64` in `base/Dockerfile`. - -## GitHub Copilot Chat extension pinning - -GitHub Copilot Chat is not available in the Open VSX marketplace used by -code-server, so `base/Dockerfile` downloads it directly from the Visual Studio -Marketplace at image-build time. Only **stable** releases are used. - -### How the pinned version is chosen - -The Copilot Chat extension publishes to two channels on the VS Marketplace: - -| Channel | Version format | Example | Where visible | -|---|---|---|---| -| **Stable** (release) | `MAJOR.MINOR.PATCH` | `0.26.7` | VS Code desktop "Version History" | -| **Pre-release** | `MAJOR.MINOR.YYYYMMDDNN` | `0.26.2025040204` | Marketplace API (`flags=17`) only | - -The "Version History" panel in the VS Code desktop app shows stable patch releases -(e.g. `0.26.0` through `0.26.7`). Pre-release builds use a date-stamped third -component (`YYYYMMDDNN` = year + month + day + sequence), so `0.26.2025040204` -is build #04 on 2025-04-02. Because this date-derived number is much larger -than the stable patch counter, and the Marketplace API returns all versions -newest-first by publish date (regardless of channel), pre-release builds appear -before their stable equivalents in the API response. - -Each release declares a minimum VS Code engine version in its -`Microsoft.VisualStudio.Code.Engine` property (e.g. `^1.99.0`). -code-server `4.X.Y` bundles VS Code `1.X.Y` (e.g. `4.99.4` → VS Code -`1.99.4`), so the correct pinned version is **the most recent stable Copilot -Chat release whose engine minimum is ≤ the VS Code version bundled in the -target code-server release**. - -The current pin `0.26.7` is the latest stable release compatible with -code-server `4.99.4` (VS Code `1.99.4`, engine requirement `^1.99.0`). -Versions in the `0.27.x` series and later require VS Code `^1.100.0` or higher. - -### Finding the right version for a new code-server release - -1. Determine the VS Code version bundled with the target code-server release - (e.g. code-server `4.99.4` → VS Code `1.99.4`). -2. Query the VS Marketplace extensionquery API for `GitHub.copilot-chat`, - filtering for stable versions only (patch component 1–9 digits, excluding 10-digit pre-release date-stamps): - ```bash - curl -fsSL -X POST \ - -H 'Content-Type: application/json' \ - -H 'Accept: application/json;api-version=3.0-preview.1' \ - -d '{"filters":[{"criteria":[{"filterType":7,"value":"GitHub.copilot-chat"}],"pageSize":100}],"flags":17}' \ - 'https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery' \ - | python3 -c " - import json, sys, re - vscode = sys.argv[1] - data = json.load(sys.stdin) - for v in data['results'][0]['extensions'][0]['versions']: - ver = v.get('version','') - # Skip pre-release builds (10-digit date-stamped patch component). - if not re.match(r'^\d+\.\d+\.\d{1,9}$', ver): continue - engine = next((p['value'] for p in v.get('properties',[]) - if p['key']=='Microsoft.VisualStudio.Code.Engine'), '') - eng_min = engine.lstrip('^~>=').split('-')[0] - if eng_min and tuple(int(x) for x in vscode.split('.')) >= \ - tuple(int(x) for x in eng_min.split('.')): - print(ver); break - " 1.99.4 - ``` -3. For versions `v0.29.0` and later the engine requirement is also visible in - the extension's - [`package.json`](https://github.com/microsoft/vscode-copilot-chat) on GitHub. - For earlier versions (before the repository was open-sourced in June 2025) - use the [VsixHub version history](https://www.vsixhub.com/history/143611/). - -### Computing and updating the SHA256 - -Once you have the correct version string, download the VSIX with `--compressed` (the -Marketplace serves the VSIX with gzip transfer encoding) and compute its SHA256: - -```bash -VERSION=0.26.7 -curl -fsSL --compressed --retry 5 --retry-all-errors --connect-timeout 10 \ - "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/GitHub/vsextensions/copilot-chat/${VERSION}/vspackage" \ - -o "/tmp/copilot-chat-${VERSION}.vsix" -# Linux: -sha256sum "/tmp/copilot-chat-${VERSION}.vsix" -# macOS: -shasum -a 256 "/tmp/copilot-chat-${VERSION}.vsix" -``` - -Then update `COPILOT_CHAT_VERSION` and `COPILOT_CHAT_VSIX_SHA256` in -`base/Dockerfile`. The CI "Verify Copilot Chat VSIX" job independently downloads -the pinned VSIX from the same URL, validates it is a valid ZIP, and verifies the -SHA256 on every CI run. - -The VSIX is architecture-independent, so only one hash value is needed (unlike -code-server which ships separate `amd64`/`arm64` packages). From 0db0f2a704144861950486d29bc1eb99ff8dd05d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 15:39:39 +0000 Subject: [PATCH 16/22] Address review feedback on PEAR workaround and build tests Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- base/Dockerfile | 21 ++++++++++++--------- docker-bake.hcl | 19 +++++++++---------- tests/run-dockerfile.sh | 4 ---- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/base/Dockerfile b/base/Dockerfile index 8d196900..61b0d1b6 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -92,9 +92,8 @@ RUN --mount=type=cache,id=code-server-debs,target=/var/cache/code-server-debs,sh # ─── Common PHP extensions stage ────────────────────────────────────────────── # Builds the shared intermediate that all per-version Dockerfiles extend. # Only packages and extensions that are IDENTICAL across all PHP versions are -# here. Version-specific items (avif/pcre packages, gd configure flags, -# oauth, xdebug) live in each -# {version}/base/Dockerfile. +# here. Version-specific items (avif/pcre packages, gd configure flags, oauth, +# xdebug) live in each {version}/base/Dockerfile. # # This stage is referenced as the 'common-php-ext' Bake named context. # It is cached in GitHub Actions (type=gha) but never pushed to Docker Hub. @@ -181,12 +180,16 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ #== Update PEAR for HTTP chunked response support in legacy PHP images ARG PEAR_VERSION=1.10.18 -RUN curl -fsSL --compressed --retry 5 --retry-all-errors --connect-timeout 10 \ - "https://pear.php.net/get/PEAR-${PEAR_VERSION}.tgz" -o /tmp/pear.tgz \ - && pear upgrade --offline --loose /tmp/pear.tgz \ - && mkdir -p "$(pear config-get cache_dir)" \ - && pear clear-cache \ - && rm -f /tmp/pear.tgz +RUN installed_pear="$(pear version)" \ + && installed_pear="${installed_pear#PEAR Version: }" \ + && if dpkg --compare-versions "${installed_pear%%[[:space:]]*}" lt "$PEAR_VERSION"; then \ + curl -fsSL --compressed --retry 5 --retry-all-errors --connect-timeout 10 \ + "https://pear.php.net/get/PEAR-${PEAR_VERSION}.tgz" -o /tmp/pear.tgz \ + && pear upgrade --offline --loose /tmp/pear.tgz \ + && mkdir -p "$(pear config-get cache_dir)" \ + && pear clear-cache \ + && rm -f /tmp/pear.tgz; \ + fi # Install development libraries, build all common PHP and PECL extensions, # build libsodium from source, then remove the dev libraries and their diff --git a/docker-bake.hcl b/docker-bake.hcl index 94bb2b28..e9ca1bcd 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -19,11 +19,10 @@ # PLATFORMS Comma-separated target platforms ("linux/amd64,linux/arm64") # VERSIONS_NEEDING_MULTIPART_FIX Versions running Debian 11 / modsec 2.9.3 ("7.4 8.0") # -# Per-version code-server build args are NOT global bake -# variables. In CI they are injected per-target via a JSON bake-override file -# generated by the preseed-downloads composite action (one entry per -# phpX_Y-downloader target). For standalone local builds the Dockerfile ARG -# defaults are used as fallback. +# Per-version code-server build args are NOT global bake variables. In CI they +# are injected per-target via a JSON bake-override file generated by the +# preseed-downloads composite action (one entry per phpX_Y-downloader target). +# For standalone local builds the Dockerfile ARG defaults are used as fallback. # # Cache behavior depends on GHCR_WRITABLE. When GHCR_WRITABLE=true, all # targets use GHCR registry cache (type=registry, mode=max) as the sole @@ -199,11 +198,11 @@ function "cache_to_registry" { # ─── Per-version downloader targets (registry-cached in GHCR, NOT pushed to Docker Hub) ─ # Each PHP version has its own downloader that runs inside php:${PHP_VERSION}-apache, # which uses the correct Debian release for that PHP version. In CI the code-server -# build args (CODESERVER_VERSION, CODESERVER_DEB_SHA256_AMD64/ARM64) -# are injected per-target via a JSON -# bake-override file generated by the preseed-downloads composite action. That action -# detects each version's Debian codename at runtime and resolves the newest compatible -# binary for it — no hardcoded Debian version names anywhere in this file. +# build args (CODESERVER_VERSION, CODESERVER_DEB_SHA256_AMD64/ARM64) are injected +# per-target via a JSON bake-override file generated by the preseed-downloads +# composite action. That action detects each version's Debian codename at runtime +# and resolves the newest compatible binary for it — no hardcoded Debian version +# names anywhere in this file. # For standalone local builds the Dockerfile ARG defaults are used as fallback. # # Matrix generates one target per version: php7_4-downloader, php8_0-downloader, ... diff --git a/tests/run-dockerfile.sh b/tests/run-dockerfile.sh index f1b15875..7c18b487 100755 --- a/tests/run-dockerfile.sh +++ b/tests/run-dockerfile.sh @@ -198,10 +198,6 @@ test_base() { # shellcheck disable=SC2016 assert_empty "$image" "no unresolved .so deps in PHP extensions" \ sh -c 'ext=$(php -r "echo PHP_EXTENSION_DIR;") && find "$ext" -name "*.so" -exec ldd {} + 2>/dev/null | grep "not found" || true' - - # code-server must work without installing a separate Chat extension. - assert_contains "$image" "[0-9]+\.[0-9]+\.[0-9]+" "code-server available" \ - code-server --version } test_secure() { From 3c6552df95b9c78943f11049ea5cd3aa5e40b4b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 15:59:41 +0000 Subject: [PATCH 17/22] Select the latest PHP-compatible PEAR release for legacy upgrades Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- base/Dockerfile | 12 ++++-- base/resolve-pear-version.php | 77 ++++++++++++++++++++++++++++++++++ tests/resolve-pear-version.php | 69 ++++++++++++++++++++++++++++++ tests/run-dockerfile.sh | 7 ++++ 4 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 base/resolve-pear-version.php create mode 100644 tests/resolve-pear-version.php diff --git a/base/Dockerfile b/base/Dockerfile index 61b0d1b6..08d7e106 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -179,17 +179,21 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ && rm -f /etc/apt/apt.conf.d/01dpkg-no-pty #== Update PEAR for HTTP chunked response support in legacy PHP images -ARG PEAR_VERSION=1.10.18 +COPY resolve-pear-version.php /tmp/resolve-pear-version.php +ARG PEAR_MIN_VERSION=1.10.18 RUN installed_pear="$(pear version)" \ && installed_pear="${installed_pear#PEAR Version: }" \ - && if dpkg --compare-versions "${installed_pear%%[[:space:]]*}" lt "$PEAR_VERSION"; then \ - curl -fsSL --compressed --retry 5 --retry-all-errors --connect-timeout 10 \ + && if dpkg --compare-versions "${installed_pear%%[[:space:]]*}" lt "$PEAR_MIN_VERSION"; then \ + PEAR_VERSION="$(php /tmp/resolve-pear-version.php "$PEAR_MIN_VERSION")" \ + && echo "Updating PEAR to ${PEAR_VERSION}" \ + && curl -fsSL --compressed --retry 5 --retry-all-errors --connect-timeout 10 \ "https://pear.php.net/get/PEAR-${PEAR_VERSION}.tgz" -o /tmp/pear.tgz \ && pear upgrade --offline --loose /tmp/pear.tgz \ && mkdir -p "$(pear config-get cache_dir)" \ && pear clear-cache \ && rm -f /tmp/pear.tgz; \ - fi + fi \ + && rm -f /tmp/resolve-pear-version.php # Install development libraries, build all common PHP and PECL extensions, # build libsodium from source, then remove the dev libraries and their diff --git a/base/resolve-pear-version.php b/base/resolve-pear-version.php new file mode 100644 index 00000000..9211878d --- /dev/null +++ b/base/resolve-pear-version.php @@ -0,0 +1,77 @@ +r as $release) { + $version = (string) $release->v; + if ((string) $release->s === 'stable' + && preg_match('/^[0-9]+(?:\.[0-9]+)+$/D', $version) + && version_compare($version, $minimum, '>=') + ) { + $versions[] = $version; + } + } + usort($versions, function ($a, $b) { + return version_compare($b, $a); + }); + foreach ($versions as $version) { + $package = $metadata("https://pear.php.net/rest/r/pear/package.$version.xml"); + if (!isset($package->dependencies->required->php->min)) { + throw new RuntimeException("Missing PHP requirements for PEAR $version"); + } + $php = $package->dependencies->required->php; + if ((string) $php->min === '') { + throw new RuntimeException("Empty PHP minimum for PEAR $version"); + } + if (version_compare($phpVersion, (string) $php->min, '<') + || ((string) $php->max !== '' && version_compare($phpVersion, (string) $php->max, '>')) + ) { + continue; + } + foreach ($php->exclude as $excluded) { + if (version_compare($phpVersion, (string) $excluded, '==')) { + continue 2; + } + } + return $version; + } + throw new RuntimeException("No stable PEAR release >= $minimum supports PHP $phpVersion"); +} + +if (realpath($_SERVER['SCRIPT_FILENAME']) === __FILE__) { + try { + echo compatiblePearVersion( + pearMetadata('https://pear.php.net/rest/r/pear/allreleases.xml'), + PHP_VERSION, + $argv[1], + 'pearMetadata' + ), "\n"; + } catch (Throwable $error) { + fwrite(STDERR, $error->getMessage() . "\n"); + exit(1); + } +} diff --git a/tests/resolve-pear-version.php b/tests/resolve-pear-version.php new file mode 100644 index 00000000..b1256847 --- /dev/null +++ b/tests/resolve-pear-version.php @@ -0,0 +1,69 @@ + + 1.10.18stable + 1.11.0stable + 1.12.0beta + 1.10.20stable + ../../invalidstable +'); +$requirements = [ + '1.10.18' => '5.4.0', + '1.10.20' => '7.4.08.4.998.0.30', + '1.11.0' => '8.1.0', +]; +$metadata = function ($url) use ($requirements) { + $version = basename($url, '.xml'); + $version = substr($version, strlen('package.')); + if (!isset($requirements[$version])) { + throw new RuntimeException("Unexpected metadata request: $url"); + } + return simplexml_load_string( + '' + . '' . $requirements[$version] + . '' + ); +}; + +foreach (['7.4.33' => '1.10.20', '8.0.30' => '1.10.18', '8.5.12' => '1.11.0'] as $php => $expected) { + $actual = compatiblePearVersion($releases, $php, '1.10.18', $metadata); + if ($actual !== $expected) { + throw new RuntimeException("PHP $php: expected $expected, got $actual"); + } +} + +$olderReleases = simplexml_load_string(' + 1.10.20stable + 1.10.18stable +'); +if (compatiblePearVersion($olderReleases, '8.5.12', '1.10.18', $metadata) !== '1.10.18') { + throw new RuntimeException('PHP maximum was not respected'); +} + +foreach ([ + function () use ($releases, $metadata) { + compatiblePearVersion($releases, '5.3.0', '1.10.18', $metadata); + }, + function () use ($releases, $metadata) { + compatiblePearVersion($releases, '7.4.33', '1.11.0', $metadata); + }, + function () use ($releases) { + compatiblePearVersion($releases, '7.4.33', '1.10.18', function () { + return simplexml_load_string(''); + }); + }, + function () use ($metadata) { + compatiblePearVersion(simplexml_load_string(''), '7.4.33', '1.10.18', $metadata); + }, +] as $failure) { + try { + $failure(); + } catch (RuntimeException $expected) { + continue; + } + throw new RuntimeException('Expected resolution to fail'); +} + +echo "PEAR compatibility selection checks passed\n"; diff --git a/tests/run-dockerfile.sh b/tests/run-dockerfile.sh index 7c18b487..af054ed8 100755 --- a/tests/run-dockerfile.sh +++ b/tests/run-dockerfile.sh @@ -152,6 +152,13 @@ assert_file_exists() { test_base() { local image="$1" version="$2" + if docker run --rm --entrypoint php -v "$REPO_ROOT:/pear-tests:ro" \ + "$image" /pear-tests/tests/resolve-pear-version.php; then + ok "PEAR compatibility selection" + else + err "PEAR compatibility selection" + fi + # PHP CLI — version string must contain the expected major.minor assert_contains "$image" "PHP ${version}\." "PHP ${version} CLI available" \ php --version From 5514e4b53825cf2fd89d9870bab9beb8cd43ee8c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 16:03:20 +0000 Subject: [PATCH 18/22] Stop suppressing dependency-marking failures in all PHP images Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- 7.4/base/Dockerfile | 2 +- 8.0/base/Dockerfile | 2 +- 8.1/base/Dockerfile | 2 +- 8.2/base/Dockerfile | 2 +- 8.3/base/Dockerfile | 2 +- 8.4/base/Dockerfile | 2 +- 8.5/base/Dockerfile | 2 +- base/Dockerfile | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/7.4/base/Dockerfile b/7.4/base/Dockerfile index 0705cf1e..7bb075f8 100644 --- a/7.4/base/Dockerfile +++ b/7.4/base/Dockerfile @@ -57,7 +57,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ | while IFS= read -r lib; do dpkg-query --search "$(realpath "$lib" 2>/dev/null || echo "$lib")" 2>/dev/null; done \ | cut -d: -f1 \ | sort -u \ - | xargs -r apt-mark manual || true; } \ + | xargs -r apt-mark manual; } \ && apt-get purge -y --auto-remove \ -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* \ diff --git a/8.0/base/Dockerfile b/8.0/base/Dockerfile index 6d56bcaf..f00cb201 100644 --- a/8.0/base/Dockerfile +++ b/8.0/base/Dockerfile @@ -51,7 +51,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ | while IFS= read -r lib; do dpkg-query --search "$(realpath "$lib" 2>/dev/null || echo "$lib")" 2>/dev/null; done \ | cut -d: -f1 \ | sort -u \ - | xargs -r apt-mark manual || true; } \ + | xargs -r apt-mark manual; } \ && apt-get purge -y --auto-remove \ -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* \ diff --git a/8.1/base/Dockerfile b/8.1/base/Dockerfile index 8b89c9d8..23559360 100644 --- a/8.1/base/Dockerfile +++ b/8.1/base/Dockerfile @@ -52,7 +52,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ | while IFS= read -r lib; do dpkg-query --search "$(realpath "$lib" 2>/dev/null || echo "$lib")" 2>/dev/null; done \ | cut -d: -f1 \ | sort -u \ - | xargs -r apt-mark manual || true; } \ + | xargs -r apt-mark manual; } \ && apt-get purge -y --auto-remove \ -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* \ diff --git a/8.2/base/Dockerfile b/8.2/base/Dockerfile index 2500f43d..d79b6dc4 100644 --- a/8.2/base/Dockerfile +++ b/8.2/base/Dockerfile @@ -52,7 +52,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ | while IFS= read -r lib; do dpkg-query --search "$(realpath "$lib" 2>/dev/null || echo "$lib")" 2>/dev/null; done \ | cut -d: -f1 \ | sort -u \ - | xargs -r apt-mark manual || true; } \ + | xargs -r apt-mark manual; } \ && apt-get purge -y --auto-remove \ -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* \ diff --git a/8.3/base/Dockerfile b/8.3/base/Dockerfile index 8b6afdec..bd53e537 100644 --- a/8.3/base/Dockerfile +++ b/8.3/base/Dockerfile @@ -54,7 +54,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ | while IFS= read -r lib; do dpkg-query --search "$(realpath "$lib" 2>/dev/null || echo "$lib")" 2>/dev/null; done \ | cut -d: -f1 \ | sort -u \ - | xargs -r apt-mark manual || true; } \ + | xargs -r apt-mark manual; } \ && apt-get purge -y --auto-remove \ -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* \ diff --git a/8.4/base/Dockerfile b/8.4/base/Dockerfile index d8e707bf..1511aa43 100644 --- a/8.4/base/Dockerfile +++ b/8.4/base/Dockerfile @@ -53,7 +53,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ | while IFS= read -r lib; do dpkg-query --search "$(realpath "$lib" 2>/dev/null || echo "$lib")" 2>/dev/null; done \ | cut -d: -f1 \ | sort -u \ - | xargs -r apt-mark manual || true; } \ + | xargs -r apt-mark manual; } \ && apt-get purge -y --auto-remove \ -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* \ diff --git a/8.5/base/Dockerfile b/8.5/base/Dockerfile index 59a9fdbc..0188b2c8 100644 --- a/8.5/base/Dockerfile +++ b/8.5/base/Dockerfile @@ -53,7 +53,7 @@ RUN echo 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/01dpkg-no-pty \ | while IFS= read -r lib; do dpkg-query --search "$(realpath "$lib" 2>/dev/null || echo "$lib")" 2>/dev/null; done \ | cut -d: -f1 \ | sort -u \ - | xargs -r apt-mark manual || true; } \ + | xargs -r apt-mark manual; } \ && apt-get purge -y --auto-remove \ -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* \ diff --git a/base/Dockerfile b/base/Dockerfile index 08d7e106..f5a36be3 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -275,7 +275,7 @@ RUN --mount=type=bind,from=common-downloader,source=/tmp/libsodium,target=/tmp/l | while IFS= read -r lib; do dpkg-query --search "$(realpath "$lib" 2>/dev/null || echo "$lib")" 2>/dev/null; done \ | cut -d: -f1 \ | sort -u \ - | xargs -r apt-mark manual || true; } \ + | xargs -r apt-mark manual; } \ && apt-get purge -y --auto-remove \ -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* \ From 32e6eecfb1fa7a82ea7deabe2d28e3d3749b2e06 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 16:56:19 +0000 Subject: [PATCH 19/22] Reflow remaining paragraphs affected by Copilot Chat removal Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- .github/actions/preseed-downloads/action.yml | 12 ++++----- .github/workflows/docker-build.yml | 23 +++++++++-------- .github/workflows/test.yml | 26 ++++++++++---------- base/Dockerfile | 5 ++-- docker-bake.hcl | 12 ++++----- tests/build-dockerfile.sh | 6 ++--- 6 files changed, 41 insertions(+), 43 deletions(-) diff --git a/.github/actions/preseed-downloads/action.yml b/.github/actions/preseed-downloads/action.yml index dd1aeef1..4840bacb 100644 --- a/.github/actions/preseed-downloads/action.yml +++ b/.github/actions/preseed-downloads/action.yml @@ -227,8 +227,8 @@ runs: } > "$RESOLVE_ENV" # Cache key: SHA256 of the primary-version-based tuples for all codenames. - # Invalidated whenever the primary code-server version - # changes, triggering a fresh download and compat re-test for all codenames. + # Invalidated whenever the primary code-server version changes, triggering + # a fresh download and compat re-test for all codenames. CACHE_KEY=$( for cn in $(echo "${!VERSIONS_FOR[@]}" | tr ' ' '\n' | sort); do printf '%s=%s;' "$cn" "${CS_VERSION_FOR[$cn]}" @@ -270,10 +270,10 @@ runs: # ── Cache step ──────────────────────────────────────────────────────────── # A single cache entry covers all detected Debian tiers. The key is a - # hash of every (codename, cs_version) tuple, so the cache is - # invalidated when any resolved version changes for any tier. The key is - # stable across runs as long as the content hash does not change; there is - # at most one cache entry per hash. + # hash of every (codename, cs_version) tuple, so the cache is invalidated + # when any resolved version changes for any tier. The key is stable across + # runs as long as the content hash does not change; there is at most one + # cache entry per hash. - name: Restore preseed artifacts cache id: cache uses: actions/cache/restore@v5 diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 316ac98a..e9d30246 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -24,8 +24,7 @@ on: no_cache: description: > Disable Docker layer cache and force a fresh download of all build - artefacts (code-server). - When false, the cache is used as normal. + artefacts (code-server). When false, the cache is used as normal. type: boolean default: true @@ -189,11 +188,11 @@ jobs: retention-days: 1 # Upload the flat pre-downloaded/ directory (code-server .deb files) - # as a workflow artifact so build jobs can download it - # directly. Using an artifact (rather than the GHA preseed cache) is - # more reliable because artifacts are guaranteed to persist for the - # lifetime of the workflow run; the GHA cache can be evicted mid-run - # and a cache miss would silently produce an empty pre-downloaded/ dir. + # as a workflow artifact so build jobs can download it directly. Using + # an artifact (rather than the GHA preseed cache) is more reliable because + # artifacts are guaranteed to persist for the lifetime of the workflow + # run; the GHA cache can be evicted mid-run and a cache miss would silently + # produce an empty pre-downloaded/ dir. - name: Upload preseed pre-downloaded artifacts if: steps.detect.outputs.versions != '' uses: actions/upload-artifact@v7 @@ -231,11 +230,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 - # Download the pre-seeded code-server .deb files - # that the detect job assembled and uploaded. Using a workflow artifact - # (not the GHA preseed cache) guarantees these files are present for the - # lifetime of the run; a cache miss would silently produce an empty - # pre-downloaded/ directory and fail the Docker build. + # Download the pre-seeded code-server .deb files that the detect job + # assembled and uploaded. Using a workflow artifact (not the GHA preseed + # cache) guarantees these files are present for the lifetime of the run; + # a cache miss would silently produce an empty pre-downloaded/ directory + # and fail the Docker build. - name: Download preseed pre-downloaded artifacts uses: actions/download-artifact@v8 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8a821d18..e6e6e076 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -180,11 +180,11 @@ jobs: retention-days: 1 # Upload the flat pre-downloaded/ directory (code-server .deb files) - # as a workflow artifact so build jobs can download it - # directly. Using an artifact (rather than the GHA preseed cache) is - # more reliable because artifacts are guaranteed to persist for the - # lifetime of the workflow run; the GHA cache can be evicted mid-run - # and a cache miss would silently produce an empty pre-downloaded/ dir. + # as a workflow artifact so build jobs can download it directly. Using + # an artifact (rather than the GHA preseed cache) is more reliable because + # artifacts are guaranteed to persist for the lifetime of the workflow + # run; the GHA cache can be evicted mid-run and a cache miss would silently + # produce an empty pre-downloaded/ dir. - name: Upload preseed pre-downloaded artifacts if: steps.versions.outputs.has_versions == 'true' uses: actions/upload-artifact@v7 @@ -221,11 +221,11 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - # Download the pre-seeded code-server .deb files - # that the detect job assembled and uploaded. Using a workflow artifact - # (not the GHA preseed cache) guarantees these files are present for the - # lifetime of the run; a cache miss would silently produce an empty - # pre-downloaded/ directory and fail the Docker build. + # Download the pre-seeded code-server .deb files that the detect job + # assembled and uploaded. Using a workflow artifact (not the GHA preseed + # cache) guarantees these files are present for the lifetime of the run; + # a cache miss would silently produce an empty pre-downloaded/ directory + # and fail the Docker build. - name: Download preseed pre-downloaded artifacts uses: actions/download-artifact@v8 with: @@ -233,9 +233,9 @@ jobs: path: ${{ runner.temp }}/build-downloads/pre-downloaded # Download the bake-overrides JSON generated by the detect job's preseed - # run. It contains the correct code-server versions and - # SHA256 digests for every detected Debian codename, including the - # downgraded versions needed for older codenames (Buster / Bullseye). + # run. It contains the correct code-server versions and SHA256 digests + # for every detected Debian codename, including the downgraded versions + # needed for older codenames (Buster / Bullseye). - name: Download preseed bake overrides uses: actions/download-artifact@v8 with: diff --git a/base/Dockerfile b/base/Dockerfile index f5a36be3..6c243dcb 100644 --- a/base/Dockerfile +++ b/base/Dockerfile @@ -43,9 +43,8 @@ RUN mkdir -p /tmp/libsodium \ && curl -sL https://github.com/jedisct1/libsodium/archive/1.0.18-RELEASE.tar.gz \ | tar xzf - -C /tmp/libsodium -# Download code-server deb. -# The version is resolved by CI and passed via CODESERVER_VERSION; -# standalone builds use the Dockerfile default. +# Download code-server deb. The version is resolved by CI and passed via +# CODESERVER_VERSION; standalone builds use the Dockerfile default. # /pre-downloaded (bind-mounted from the 'downloads' named context) holds files # pre-seeded by CI via actions/cache@v5 so they are never re-downloaded for the # same version even when Docker layers change. diff --git a/docker-bake.hcl b/docker-bake.hcl index e9ca1bcd..233c112e 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -69,12 +69,12 @@ variable "VERSIONS_SECURE" { default = "7.4 8.0 8.1 8.2 8.3" } variable "CORERULESET_VERSION" { default = "3.3.5" } # DOWNLOADS_DIR: path to a directory whose pre-downloaded/ subdirectory contains -# pre-seeded build artifacts (code-server .deb files). -# When set by CI to a runner-local path pre-populated by actions/cache@v5, the -# downloader stage bind-mounts /pre-downloaded from it instead of downloading. -# When empty (default), no named context override is provided and the Dockerfile's -# 'downloads' stage (FROM alpine:3 with an empty /pre-downloaded dir) is used, -# causing all downloads to fall through to the normal network path. +# pre-seeded build artifacts (code-server .deb files). When set by CI to a +# runner-local path pre-populated by actions/cache@v5, the downloader stage +# bind-mounts /pre-downloaded from it instead of downloading. When empty (default), +# no named context override is provided and the Dockerfile's 'downloads' stage +# (FROM alpine:3 with an empty /pre-downloaded dir) is used, causing all downloads +# to fall through to the normal network path. variable "DOWNLOADS_DIR" { default = "" } variable "CACHE_FROM_ENABLED" { default = "true" } # GHCR_WRITABLE is set by the detect-versions action (runs once centrally diff --git a/tests/build-dockerfile.sh b/tests/build-dockerfile.sh index 54ac2d1a..2e08c6b4 100755 --- a/tests/build-dockerfile.sh +++ b/tests/build-dockerfile.sh @@ -137,9 +137,9 @@ PLATFORM_KEY="${NORMALIZED_PLATFORM//\//-}" # preseed-downloads composite action. When set, it is # passed as an additional --file argument so each # phpX_Y-downloader target receives the correct -# code-server version and SHA256 args for -# its Debian tier. When unset, the Dockerfile ARG -# defaults are used as fallback. +# code-server version and SHA256 args for its Debian +# tier. When unset, the Dockerfile ARG defaults are +# used as fallback. # # --load: # Import built images into the local Docker daemon so that run-dockerfile.sh From 6a90c5bf09b490a5fcf121b689d312142afff5d9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 17:26:43 +0000 Subject: [PATCH 20/22] Build ARM64 images on native ARM runners Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- .github/actions/build-php-images/action.yml | 1 + .github/workflows/docker-build.yml | 2 +- README.md | 7 +++++-- TODO.md | 13 +++++++++++++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/actions/build-php-images/action.yml b/.github/actions/build-php-images/action.yml index 997a455c..f192cee5 100644 --- a/.github/actions/build-php-images/action.yml +++ b/.github/actions/build-php-images/action.yml @@ -114,6 +114,7 @@ runs: password: ${{ inputs.ghcr_user && inputs.ghcr_token || inputs.github_token }} - name: Set up QEMU + if: ${{ !((runner.arch == 'ARM64' && startsWith(inputs.platform, 'linux/arm64')) || (runner.arch == 'X64' && startsWith(inputs.platform, 'linux/amd64'))) }} uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index e9d30246..abf93d15 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -221,7 +221,7 @@ jobs: fail-fast: false matrix: include: ${{ fromJSON(needs.detect.outputs.build_matrix) }} - runs-on: ubuntu-latest + runs-on: ${{ matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} permissions: contents: read packages: write diff --git a/README.md b/README.md index b0cdf93b..6812e82c 100644 --- a/README.md +++ b/README.md @@ -279,8 +279,11 @@ for setup instructions. without pushing. Functional tests (`tests/run-dockerfile.sh`) then start each built image and verify PHP, Apache, Composer, and extensions work. Run `./test.sh build run --version ` to test a single PHP version quickly. -- The build workflow is `docker-build.yml`, which handles both push-triggered and manual dispatch - builds. It runs a detect → build (per-platform, parallel) → merge pipeline. +- The build workflow is `docker-build.yml`, which handles both push-triggered + and manual dispatch builds. It runs a detect → build (per-platform, + parallel) → merge pipeline. ARM64 builds use native `ubuntu-24.04-arm` + runners; AMD64 builds use `ubuntu-latest`. QEMU is only set up when the + requested platform does not match the runner architecture. - The `advance` variant depends on `secure`, which depends on `base`. The build chain is enforced via the bake target dependency graph. diff --git a/TODO.md b/TODO.md index f35f75c0..3482f2ba 100644 --- a/TODO.md +++ b/TODO.md @@ -2,6 +2,19 @@ Use this file to track work that needs to be done. Lint scripts automatically append baseline-cleanup entries here when all violations for a linter reach zero. +# Task: Use native ARM runners for ARM64 builds + +## Steps +- [x] Inspect platform routing and composite build setup. +- [x] Route ARM64 builds to ARM runners and skip QEMU for native builds. +- [x] Document native runner selection and the cross-platform fallback. +- [ ] Run focused workflow checks, secret scanning, and code/security review. + +## Definition of Done +- ARM64 build matrix jobs use `ubuntu-24.04-arm`; AMD64 jobs retain `ubuntu-latest`. +- Native ARM64 and AMD64 builds skip QEMU; cross-platform builds retain it. +- Focused validation passes and changed files contain no secrets. + # Task: Run builds after an all-versions dispatch ## Steps From eecfb4bd30c894fce3fd60ade607677b4f33e17e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Oct 2026 17:28:05 +0000 Subject: [PATCH 21/22] Record native ARM workflow validation Co-authored-by: darrenoh <2293701+darrenoh@users.noreply.github.com> --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 3482f2ba..1adc41a8 100644 --- a/TODO.md +++ b/TODO.md @@ -8,7 +8,7 @@ Use this file to track work that needs to be done. Lint scripts automatically ap - [x] Inspect platform routing and composite build setup. - [x] Route ARM64 builds to ARM runners and skip QEMU for native builds. - [x] Document native runner selection and the cross-platform fallback. -- [ ] Run focused workflow checks, secret scanning, and code/security review. +- [x] Run focused workflow checks, secret scanning, and code/security review. ## Definition of Done - ARM64 build matrix jobs use `ubuntu-24.04-arm`; AMD64 jobs retain `ubuntu-latest`. From 3bfdd6d2448e05a45ac219de9d8a7b49557c1f78 Mon Sep 17 00:00:00 2001 From: Darren Oh <2293701+darrenoh@users.noreply.github.com> Date: Thu, 22 Oct 2026 14:37:14 -0400 Subject: [PATCH 22/22] Update Ubuntu version for ARM64 platform in workflow --- .github/workflows/docker-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index abf93d15..b2228dbc 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -221,7 +221,7 @@ jobs: fail-fast: false matrix: include: ${{ fromJSON(needs.detect.outputs.build_matrix) }} - runs-on: ${{ matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} + runs-on: ${{ matrix.platform == 'linux/arm64' && 'ubuntu-26.04-arm' || 'ubuntu-26.04' }} permissions: contents: read packages: write