From e50dbdcaae96b1626a45457d03514171a77ccf28 Mon Sep 17 00:00:00 2001 From: pablojmarti Date: Tue, 29 Sep 2026 12:31:28 -0400 Subject: [PATCH 01/11] Updates to only use the latest 3 versions of node in our build --- Dockerfile | 5 +++-- build/install-node.sh | 29 ++++++++++++++++++++--------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2b84b68..92b3fee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -110,10 +110,11 @@ RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh | b ENV CYPRESS_CACHE_FOLDER="/tmp/cypress/cache" RUN mkdir -p ${CYPRESS_CACHE_FOLDER} && chmod 777 ${CYPRESS_CACHE_FOLDER} -ARG NODE_VERSION=24 +# Install the latest N LTS lines of node; the newest is the default +ARG NODE_LTS_COUNT=3 COPY build/install-node.sh /tmp/install-node.sh RUN chmod +x /tmp/install-node.sh && \ - /tmp/install-node.sh "--lts" && \ + /tmp/install-node.sh "${NODE_LTS_COUNT}" && \ . "$NVM_DIR/nvm.sh" && \ nvm alias default 'lts/*' diff --git a/build/install-node.sh b/build/install-node.sh index f3cc688..0b48ea3 100644 --- a/build/install-node.sh +++ b/build/install-node.sh @@ -1,26 +1,37 @@ #!/bin/bash -# Install only the current LTS version and npm packages -# This avoids extra GB of build container image size and maintains security +# Install the latest N LTS lines of node (default 3), oldest first +# Uses nvm's relative LTS aliases (lts/-2, lts/-1, lts/*), so the weekly rebuild +# rolls the set forward automatically when a new line enters LTS # nvm can be used in individual pipelines to install other versions # LTS calendar: https://nodejs.org/en/about/releases/ -# TODO: NODE_VERSION variable could be centralized in the Dockerfile for quicker management - # catch Errors set -euo pipefail -#Get node version from Docker build argument -NODE_VERSION="$1" +# Get number of LTS lines from Docker build argument +LTS_COUNT="${1:-3}" + +if [[ ! "${LTS_COUNT}" =~ ^[1-9][0-9]*$ ]]; then + >&2 echo "ERROR: LTS count must be a positive integer, got '${LTS_COUNT}'" + exit 1 +fi # set up nvm in this script . "$NVM_DIR/nvm.sh" -echo "Building node environment for version ${NODE_VERSION}" +for (( offset = LTS_COUNT - 1; offset >= 0; offset-- )); do + if [[ "${offset}" -eq 0 ]]; then + NODE_VERSION="lts/*" + else + NODE_VERSION="lts/-${offset}" + fi -nvm install "${NODE_VERSION}" + echo "Building node environment for version ${NODE_VERSION}" + nvm install "${NODE_VERSION}" +done npm cache clean --force -echo "node ${NODE_VERSION} build completed..." +echo "node build completed for the latest ${LTS_COUNT} LTS versions..." From d6f2031140f543ea4963b3c74d51274f130b605c Mon Sep 17 00:00:00 2001 From: pablojmarti Date: Tue, 29 Sep 2026 12:31:43 -0400 Subject: [PATCH 02/11] Adds info about update to readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4788e7d..b7b2900 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ - terminus - yamllint -Node and npm are managed by the `build/install-node.sh` script. The only version installed by default will be the `lts` version. +Node and npm are managed by the `build/install-node.sh` script. The three latest LTS versions of Node are installed (resolved at build time via nvm's `lts/-2`, `lts/-1` and `lts/*` aliases), and the newest LTS is the default. Because images are rebuilt weekly, the set rolls forward automatically when a new Node line enters LTS. The number of LTS versions can be changed with the `NODE_LTS_COUNT` build argument. ## Customize Package and Tool Configurations @@ -111,7 +111,7 @@ This will enable detailed logs in php-syntax checking for debugging, trading off ## Node Version -For convenience, `nvm` is installed to easily manage the node version in the CI container. To install a different node version from CI just add a step to execute the command: `nvm install `; you can also execute the command from within a build script. +For convenience, `nvm` is installed to easily manage the node version in the CI container. The three latest LTS versions are preinstalled, so switching between them is instant with `nvm use ` (e.g. `nvm use 22`). To install any other node version from CI just add a step to execute the command: `nvm install `; you can also execute the command from within a build script. [![Support Level](https://img.shields.io/badge/support-stable-blue.svg)](#support-level) From 94857ab0ab8e21424874eecb472fc5dace7ca3ef Mon Sep 17 00:00:00 2001 From: pablojmarti Date: Tue, 29 Sep 2026 12:31:55 -0400 Subject: [PATCH 03/11] Adds helper script to test installation --- tests/test-install-node.sh | 113 +++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 tests/test-install-node.sh diff --git a/tests/test-install-node.sh b/tests/test-install-node.sh new file mode 100644 index 0000000..dccef98 --- /dev/null +++ b/tests/test-install-node.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash + +set -uo pipefail + +TEST_DIR="$(mktemp -d "${TMPDIR:-/tmp}/install-node-tests.XXXXXX")" +readonly TEST_DIR +REPOSITORY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +readonly REPOSITORY_DIR +readonly SCRIPT_PATH="${REPOSITORY_DIR}/build/install-node.sh" + +failures=0 + +cleanup() { + if [[ -d "${TEST_DIR}" ]]; then + rm -r -- "${TEST_DIR}" + fi +} +trap cleanup EXIT + +fail() { + printf 'not ok - %s\n' "$1" >&2 + failures=$((failures + 1)) +} + +assert_status() { + local expected="$1" + local actual="$2" + local description="$3" + + if [[ "${actual}" -ne "${expected}" ]]; then + fail "${description}: expected exit ${expected}, got ${actual}" + fi +} + +assert_file_equals() { + local file="$1" + local expected="$2" + local description="$3" + + if [[ ! -f "${file}" ]] || [[ "$(cat "${file}")" != "${expected}" ]]; then + fail "${description}" + fi +} + +# Fake nvm.sh: defines nvm and npm functions that record their calls +create_fake_nvm() { + local nvm_dir="$1" + + mkdir -p "${nvm_dir}" + + cat > "${nvm_dir}/nvm.sh" <<'EOF' +nvm() { + printf '%s\n' "$*" >> "${TEST_STATE}/nvm.calls" + if [[ "$1" == "install" && "$2" == "${FAKE_FAIL_VERSION:-}" ]]; then + return 3 + fi +} + +npm() { + printf '%s\n' "$*" >> "${TEST_STATE}/npm.calls" +} +EOF +} + +run_install_case() { + local name="$1" + local expected_exit="$2" + shift 2 + local state_dir="${TEST_DIR}/${name}" + local actual_exit + + mkdir -p "${state_dir}" + create_fake_nvm "${state_dir}/nvm" + + ( + NVM_DIR="${state_dir}/nvm" \ + TEST_STATE="${state_dir}" \ + bash "${SCRIPT_PATH}" "$@" + ) > "${state_dir}/output" 2>&1 + actual_exit=$? + + assert_status "${expected_exit}" "${actual_exit}" "${name}" +} + +run_install_case default 0 +assert_file_equals "${TEST_DIR}/default/nvm.calls" "$(printf '%s\n' 'install lts/-2' 'install lts/-1' 'install lts/*')" \ + 'default installs the three latest LTS lines, oldest first' +assert_file_equals "${TEST_DIR}/default/npm.calls" 'cache clean --force' 'npm cache is cleaned once' + +run_install_case single 0 1 +assert_file_equals "${TEST_DIR}/single/nvm.calls" 'install lts/*' 'a count of 1 installs only the latest LTS' + +run_install_case five 0 5 +assert_file_equals "${TEST_DIR}/five/nvm.calls" \ + "$(printf '%s\n' 'install lts/-4' 'install lts/-3' 'install lts/-2' 'install lts/-1' 'install lts/*')" \ + 'the LTS count is configurable' + +run_install_case zero 1 0 +[[ -f "${TEST_DIR}/zero/nvm.calls" ]] && fail 'a count of 0 installs nothing' + +run_install_case not_a_number 1 abc +[[ -f "${TEST_DIR}/not_a_number/nvm.calls" ]] && fail 'a non-numeric count installs nothing' + +FAKE_FAIL_VERSION='lts/-1' run_install_case install_failure 3 +assert_file_equals "${TEST_DIR}/install_failure/nvm.calls" "$(printf '%s\n' 'install lts/-2' 'install lts/-1')" \ + 'a failed install stops the build' + +if [[ "${failures}" -gt 0 ]]; then + printf '%s test assertion(s) failed\n' "${failures}" >&2 + exit 1 +fi + +printf 'ok - install-node behavior\n' From 6b89d9915e24b664f25ca48d769a024bdd25792e Mon Sep 17 00:00:00 2001 From: pablojmarti Date: Tue, 29 Sep 2026 12:32:31 -0400 Subject: [PATCH 04/11] Adds Claude.md to repo --- CLAUDE.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9d772e1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,58 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +**Keep this file current.** Any change to the Dockerfile, build/CI scripts, workflow matrix, env-var contract, or tests must be reflected here in the same change. + +## What this repo is + +Source for the `10up/wordpress-ci` Docker images: one Debian-based PHP image per PHP version, bundling the tooling WordPress CI/CD pipelines need (composer, nvm/node, wp-cli, ClamAV, kubectl, docker-cli, terminus, gh, glab, awscli/ansible via pip, etc.). There is no application code. The deliverables are the image and the helper scripts it ships in `/custom-scripts`. + +## Commands + +```bash +# Build locally (PHP_IMG is required, there is no default) +docker build --build-arg PHP_IMG=php:8.3-bookworm --build-arg COMPOSER_VERSION=2 -t wordpress-ci:local . + +# Try a script inside the image against the current dir +docker run --rm -v "$PWD":/work -w /work wordpress-ci:local virus-scan + +# Tests (host-side, no Docker; fake binaries/functions on PATH or NVM_DIR) +bash tests/test-virus-scan.sh +bash tests/test-install-node.sh + +# Lint shell (all current findings are info-level) +shellcheck scripts/* build/*.sh entrypoint.sh tests/*.sh +``` + +`tests/test-virus-scan.sh` hardcodes `mktemp -d /private/tmp/...`, so it only runs on macOS as written. `tests/test-install-node.sh` uses `${TMPDIR:-/tmp}` and is portable. CI does not run the tests or shellcheck. The only CI job is the image build. + +## Architecture + +**Image build (`Dockerfile`)**, in this order: apt packages (incl. `clamav-daemon`), then `freshclam` early so the virus DB is baked in (running it later in the file hit bugs), locale, PHP ini (`memory_limit=-1`) and extensions (`opentelemetry` only on PHP ≥ 8.1), nvm in `/tmp/.nvm` + the latest `NODE_LTS_COUNT` (default 3) Node LTS lines via `build/install-node.sh`, pip `requirements.txt` (uses `PIP_BREAK_SYSTEM_PACKAGES=1` for bookworm), composer, wp-cli, then docker-cli/kubectl/terminus/gh/glab, which are always the latest release and unpinned. Finally `scripts/*` go to `/custom-scripts` (on `PATH`), plus `BASH_ENV=/root/.bashrc` so non-interactive CI shells load nvm and the `wp` alias. + +- `build/install-composer.sh` installs `composer1-bin`, `composer2-bin`, and a default `composer` chosen by the `COMPOSER_VERSION` build arg. +- `build/install-wpcli.sh` installs `/usr/local/bin/wp-cli.phar`. `wp` exists only as a `.bashrc` alias (`php wp-cli.phar --allow-root`), not as a binary. Bash doesn't expand aliases in non-interactive shells, so `bash -c 'wp ...'` fails with "command not found" even though `BASH_ENV` loads `.bashrc`. +- `build/install-node.sh [count]` installs nvm's relative aliases oldest first: `lts/-2`, `lts/-1`, then `lts/*`. It resolves them at build time, so the weekly no-cache rebuild rolls the set forward automatically when a new line goes LTS (versions get dropped the same way). It rejects a count that isn't a positive integer. Keeping 3 is a deliberate choice, even when the oldest line is past EOL (e.g. Node 20 until 26 goes LTS). The Dockerfile then runs `nvm alias default 'lts/*'`. Pipelines switch with `nvm use ` or run `nvm install ` for anything else. + +**Runtime (`entrypoint.sh`, POSIX sh)**: sources nvm, then applies optional env vars: `PRIVATE_KEY`/`PUBLIC_KEY` go to `/root/.ssh/id_rsa{,.pub}`, `TERMINUS_TOKEN` runs a terminus login, `GIT_USER_NAME`/`GIT_USER_EMAIL` set git global config, `COMPOSER_CONFIG` runs `composer config -g`, and `BUILD_CACHE_DIR` points the composer and npm caches at `$BUILD_CACHE_DIR/.composer-cache` and `$BUILD_CACHE_DIR/node_modules_cache` (for GitLab: `${CI_PROJECT_DIR}`). It then prints tool versions and `exec "$@"`. The version printout still calls grunt/gulp/bower/yarn, which were removed from the image, so those lines print "not found". This is harmless because the script has no `set -e`. + +**CI scripts (`scripts/`, extensionless, copied to `/custom-scripts`)**: +- `all-scripts` *sources* (does not execute) every top-level `/custom-scripts/*.sh` (user-added), then `php-syntax`, then `virus-scan`, all under `set -euo pipefail`. Because they are sourced, an `exit` in any of them ends the whole run, and `virus-scan` always exits. It must stay last. +- `php-syntax` runs `php -l` in parallel (`xargs -P10`) on every `*.php` outside `vendor/`. Output goes to stdout only when `IS_DEBUG_ENABLED=true`. +- `virus-scan` starts a private, temporary `clamd` (generated config in a `mktemp` dir, `LogClean yes`, excludes `.composer-cache` and `node_modules_cache`), waits up to 30s for `--ping`, runs `clamdscan --multiscan "$PWD"`, then kills the daemon and removes the temp dir via an EXIT trap. The scanned-file count comes from the **daemon log** (`: OK` / ` FOUND` lines), because clamdscan's report on a directory doesn't give a per-file count. `: OK` lines are filtered out of the printed report. **Exit contract:** 1 only when an infection is found. Every scanner or daemon failure exits 0 (fail-open, so a broken scanner never blocks a deploy). `CLAMAV_DB_DIR` and `TMPDIR` can be overridden; the tests rely on both. +- `slack-message` posts a Slack attachment via webhook. `-u` and `-m` are required (see the README for all flags). + +**Tests** follow one pattern: fake the external tool, run the real script, then assert exit codes and the calls or output that were recorded. +- `test-virus-scan.sh` stubs `clamd`/`clamdscan` as bash scripts driven by `FAKE_SCAN_EXIT` (0 clean, 1 infected, 2 scan error, 3 no summary) and `FAKE_START_MODE`. It checks the generated `clamd.conf`, the output, and that the daemon was stopped. Changes to virus-scan output or config need matching test updates. +- `test-install-node.sh` points `NVM_DIR` at a fake `nvm.sh` that defines recording `nvm`/`npm` functions (`FAKE_FAIL_VERSION` makes one install fail). It checks install order, the count handling, and that the script stops at the first failed install. + +## Release / CI (`.github/workflows/build.yaml`) + +- Runs on every push to any branch, plus a weekly rebuild (Sun 04:00 UTC) to pick up security updates. Builds are always `no-cache: true`, wrapped in a 3-attempt retry. +- The matrix is `base_container`. Each entry needs a matching "Set PHP X settings" step that sets `BUILD_TAGS` and `COMPOSER_VERSION`. PHP 7.4 uses Composer 1 and also owns the `latest` tag. All others use Composer 2. 7.4–8.1 use `-bullseye` images and 8.2–8.4 use `-bookworm`. +- Images push to Docker Hub only from `trunk`. Branch builds only validate. Adding a PHP version means adding a matrix entry plus a settings step (the README has the template). + +## Known doc drift + +The README's matrix example shows all versions on bullseye, lists `azure-cli` (not installed), and spells glab as "glap". `.bowerrc` is a leftover and isn't used by the build. From 4cc153b10901e5b75cfd38f85edb18c099fd7ec9 Mon Sep 17 00:00:00 2001 From: pablojmarti Date: Tue, 29 Sep 2026 12:55:10 -0400 Subject: [PATCH 05/11] moves 8.1 to bookworm as well to help with build issues --- .github/workflows/build.yaml | 4 ++-- CLAUDE.md | 5 +++-- README.md | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 118baed..037217e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -12,7 +12,7 @@ jobs: build_push_to_dockerhub: strategy: matrix: - base_container: ["php:7.4-bullseye", "php:8.0-bullseye", "php:8.1-bullseye", "php:8.2-bookworm", "php:8.3-bookworm", "php:8.4-bookworm"] + base_container: ["php:7.4-bullseye", "php:8.0-bullseye", "php:8.1-bookworm", "php:8.2-bookworm", "php:8.3-bookworm", "php:8.4-bookworm"] runs-on: ubuntu-latest steps: @@ -37,7 +37,7 @@ jobs: echo "COMPOSER_VERSION=2" >> $GITHUB_ENV - name: Set PHP 8.1 settings - if: ${{ matrix.base_container == 'php:8.1-bullseye' }} + if: ${{ matrix.base_container == 'php:8.1-bookworm' }} run: | echo "BUILD_TAGS=10up/wordpress-ci:php-8.1" >> $GITHUB_ENV echo "COMPOSER_VERSION=2" >> $GITHUB_ENV diff --git a/CLAUDE.md b/CLAUDE.md index 9d772e1..c1d6629 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,9 +50,10 @@ shellcheck scripts/* build/*.sh entrypoint.sh tests/*.sh ## Release / CI (`.github/workflows/build.yaml`) - Runs on every push to any branch, plus a weekly rebuild (Sun 04:00 UTC) to pick up security updates. Builds are always `no-cache: true`, wrapped in a 3-attempt retry. -- The matrix is `base_container`. Each entry needs a matching "Set PHP X settings" step that sets `BUILD_TAGS` and `COMPOSER_VERSION`. PHP 7.4 uses Composer 1 and also owns the `latest` tag. All others use Composer 2. 7.4–8.1 use `-bullseye` images and 8.2–8.4 use `-bookworm`. +- The matrix is `base_container`. Each entry needs a matching "Set PHP X settings" step that sets `BUILD_TAGS` and `COMPOSER_VERSION`. PHP 7.4 uses Composer 1 and also owns the `latest` tag. All others use Composer 2. 7.4 and 8.0 use `-bullseye` images, because `php:7.4-bookworm` and `php:8.0-bookworm` don't exist. 8.1–8.4 use `-bookworm`. +- **7.4 and 8.0 builds currently fail.** Debian bullseye support ended on Aug 31, 2026. `deb.debian.org` still lists `bullseye-security` packages, but the `.deb` files return 404, so the first `apt-get install` exits with code 100. Because the matrix uses fail-fast, that failure cancels every other job, which makes the bookworm jobs look broken too. Dropping 7.4 and 8.0 is pending a team decision. - Images push to Docker Hub only from `trunk`. Branch builds only validate. Adding a PHP version means adding a matrix entry plus a settings step (the README has the template). ## Known doc drift -The README's matrix example shows all versions on bullseye, lists `azure-cli` (not installed), and spells glab as "glap". `.bowerrc` is a leftover and isn't used by the build. +The README lists `azure-cli` (not installed), and spells glab as "glap". `.bowerrc` is a leftover and isn't used by the build. diff --git a/README.md b/README.md index b7b2900..8f6f74f 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ jobs: build_push_to_dockerhub: strategy: matrix: - base_container: ["php:7.4-bullseye", "php:8.0-bullseye", "php:8.1-bullseye", "php:8.2-bullseye", "php:8.3-bullseye", "php:8.4-bullseye"] + base_container: ["php:7.4-bullseye", "php:8.0-bullseye", "php:8.1-bookworm", "php:8.2-bookworm", "php:8.3-bookworm", "php:8.4-bookworm"] ``` Update steps: From 98b1a56e965365124ff78b390bdb1e4a1f04a261 Mon Sep 17 00:00:00 2001 From: pablojmarti Date: Tue, 29 Sep 2026 13:00:14 -0400 Subject: [PATCH 06/11] Adds fail fast to test pipeline --- .github/workflows/build.yaml | 2 ++ CLAUDE.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 037217e..aacd64b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -11,6 +11,8 @@ on: jobs: build_push_to_dockerhub: strategy: + # TEMPORARY: let each PHP version finish while 7.4/8.0 (bullseye) are broken + fail-fast: false matrix: base_container: ["php:7.4-bullseye", "php:8.0-bullseye", "php:8.1-bookworm", "php:8.2-bookworm", "php:8.3-bookworm", "php:8.4-bookworm"] runs-on: ubuntu-latest diff --git a/CLAUDE.md b/CLAUDE.md index c1d6629..8cff1d7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,7 +51,7 @@ shellcheck scripts/* build/*.sh entrypoint.sh tests/*.sh - Runs on every push to any branch, plus a weekly rebuild (Sun 04:00 UTC) to pick up security updates. Builds are always `no-cache: true`, wrapped in a 3-attempt retry. - The matrix is `base_container`. Each entry needs a matching "Set PHP X settings" step that sets `BUILD_TAGS` and `COMPOSER_VERSION`. PHP 7.4 uses Composer 1 and also owns the `latest` tag. All others use Composer 2. 7.4 and 8.0 use `-bullseye` images, because `php:7.4-bookworm` and `php:8.0-bookworm` don't exist. 8.1–8.4 use `-bookworm`. -- **7.4 and 8.0 builds currently fail.** Debian bullseye support ended on Aug 31, 2026. `deb.debian.org` still lists `bullseye-security` packages, but the `.deb` files return 404, so the first `apt-get install` exits with code 100. Because the matrix uses fail-fast, that failure cancels every other job, which makes the bookworm jobs look broken too. Dropping 7.4 and 8.0 is pending a team decision. +- **7.4 and 8.0 builds currently fail.** Debian bullseye support ended on Aug 31, 2026. `deb.debian.org` still lists `bullseye-security` packages, but the `.deb` files return 404, so the first `apt-get install` exits with code 100. The matrix has `fail-fast: false` set **temporarily**, so 8.1–8.4 finish and report their own results instead of being cancelled. Expect 7.4 and 8.0 to show red until they're dropped (pending a team decision). Remove `fail-fast: false` again once testing is done. - Images push to Docker Hub only from `trunk`. Branch builds only validate. Adding a PHP version means adding a matrix entry plus a settings step (the README has the template). ## Known doc drift From 4bbad1d60d3240d0f7087e680cfee6294aa8578b Mon Sep 17 00:00:00 2001 From: pablojmarti Date: Tue, 29 Sep 2026 14:40:00 -0400 Subject: [PATCH 07/11] Updates for swapping claude to agents and updated readme --- .bowerrc | 1 - CLAUDE.md => AGENTS.md | 10 +++------- README.md | 3 +-- entrypoint.sh | 4 ---- 4 files changed, 4 insertions(+), 14 deletions(-) delete mode 100644 .bowerrc rename CLAUDE.md => AGENTS.md (93%) diff --git a/.bowerrc b/.bowerrc deleted file mode 100644 index 784ee65..0000000 --- a/.bowerrc +++ /dev/null @@ -1 +0,0 @@ -{ "allow_root": true } diff --git a/CLAUDE.md b/AGENTS.md similarity index 93% rename from CLAUDE.md rename to AGENTS.md index 8cff1d7..389d0a0 100644 --- a/CLAUDE.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ -# CLAUDE.md +# AGENTS.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +This file provides guidance to AI coding agents working with code in this repository. **Keep this file current.** Any change to the Dockerfile, build/CI scripts, workflow matrix, env-var contract, or tests must be reflected here in the same change. @@ -35,7 +35,7 @@ shellcheck scripts/* build/*.sh entrypoint.sh tests/*.sh - `build/install-wpcli.sh` installs `/usr/local/bin/wp-cli.phar`. `wp` exists only as a `.bashrc` alias (`php wp-cli.phar --allow-root`), not as a binary. Bash doesn't expand aliases in non-interactive shells, so `bash -c 'wp ...'` fails with "command not found" even though `BASH_ENV` loads `.bashrc`. - `build/install-node.sh [count]` installs nvm's relative aliases oldest first: `lts/-2`, `lts/-1`, then `lts/*`. It resolves them at build time, so the weekly no-cache rebuild rolls the set forward automatically when a new line goes LTS (versions get dropped the same way). It rejects a count that isn't a positive integer. Keeping 3 is a deliberate choice, even when the oldest line is past EOL (e.g. Node 20 until 26 goes LTS). The Dockerfile then runs `nvm alias default 'lts/*'`. Pipelines switch with `nvm use ` or run `nvm install ` for anything else. -**Runtime (`entrypoint.sh`, POSIX sh)**: sources nvm, then applies optional env vars: `PRIVATE_KEY`/`PUBLIC_KEY` go to `/root/.ssh/id_rsa{,.pub}`, `TERMINUS_TOKEN` runs a terminus login, `GIT_USER_NAME`/`GIT_USER_EMAIL` set git global config, `COMPOSER_CONFIG` runs `composer config -g`, and `BUILD_CACHE_DIR` points the composer and npm caches at `$BUILD_CACHE_DIR/.composer-cache` and `$BUILD_CACHE_DIR/node_modules_cache` (for GitLab: `${CI_PROJECT_DIR}`). It then prints tool versions and `exec "$@"`. The version printout still calls grunt/gulp/bower/yarn, which were removed from the image, so those lines print "not found". This is harmless because the script has no `set -e`. +**Runtime (`entrypoint.sh`, POSIX sh)**: sources nvm, then applies optional env vars: `PRIVATE_KEY`/`PUBLIC_KEY` go to `/root/.ssh/id_rsa{,.pub}`, `TERMINUS_TOKEN` runs a terminus login, `GIT_USER_NAME`/`GIT_USER_EMAIL` set git global config, `COMPOSER_CONFIG` runs `composer config -g`, and `BUILD_CACHE_DIR` points the composer and npm caches at `$BUILD_CACHE_DIR/.composer-cache` and `$BUILD_CACHE_DIR/node_modules_cache` (for GitLab: `${CI_PROJECT_DIR}`). It then prints the versions of php, composer, node, npm and wp-cli, and runs `exec "$@"`. The script has no `set -e`, so a missing tool in that printout only logs "not found". When removing a tool from the image, also remove its version line here. **CI scripts (`scripts/`, extensionless, copied to `/custom-scripts`)**: - `all-scripts` *sources* (does not execute) every top-level `/custom-scripts/*.sh` (user-added), then `php-syntax`, then `virus-scan`, all under `set -euo pipefail`. Because they are sourced, an `exit` in any of them ends the whole run, and `virus-scan` always exits. It must stay last. @@ -53,7 +53,3 @@ shellcheck scripts/* build/*.sh entrypoint.sh tests/*.sh - The matrix is `base_container`. Each entry needs a matching "Set PHP X settings" step that sets `BUILD_TAGS` and `COMPOSER_VERSION`. PHP 7.4 uses Composer 1 and also owns the `latest` tag. All others use Composer 2. 7.4 and 8.0 use `-bullseye` images, because `php:7.4-bookworm` and `php:8.0-bookworm` don't exist. 8.1–8.4 use `-bookworm`. - **7.4 and 8.0 builds currently fail.** Debian bullseye support ended on Aug 31, 2026. `deb.debian.org` still lists `bullseye-security` packages, but the `.deb` files return 404, so the first `apt-get install` exits with code 100. The matrix has `fail-fast: false` set **temporarily**, so 8.1–8.4 finish and report their own results instead of being cancelled. Expect 7.4 and 8.0 to show red until they're dropped (pending a team decision). Remove `fail-fast: false` again once testing is done. - Images push to Docker Hub only from `trunk`. Branch builds only validate. Adding a PHP version means adding a matrix entry plus a settings step (the README has the template). - -## Known doc drift - -The README lists `azure-cli` (not installed), and spells glab as "glap". `.bowerrc` is a leftover and isn't used by the build. diff --git a/README.md b/README.md index 8f6f74f..1894d0b 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ - curl - gh (Github CLI) - git -- glap (Gitlab CLI) +- glab (Gitlab CLI) - mysql-client - nodejs / npm, nvm for management - php @@ -19,7 +19,6 @@ - clamdscan - kubectl - aws-cli -- azure-cli - docker-cli - wp-cli - terminus diff --git a/entrypoint.sh b/entrypoint.sh index 29b3c97..30c5c83 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -58,10 +58,6 @@ php --version composer --version node --version npm --version -grunt --version -gulp --version -bower --version -yarn --version php /usr/local/bin/wp-cli.phar --allow-root --version set +x From 45de87efe867c996b98749d3adc256a57b6b1ba5 Mon Sep 17 00:00:00 2001 From: pablojmarti Date: Tue, 29 Sep 2026 14:54:43 -0400 Subject: [PATCH 08/11] Removes old versions of PHP from build syncs latest with 8.4; keep fail fast --- .github/workflows/build.yaml | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index aacd64b..ad028e5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -11,10 +11,10 @@ on: jobs: build_push_to_dockerhub: strategy: - # TEMPORARY: let each PHP version finish while 7.4/8.0 (bullseye) are broken + # TEMPORARY: report each PHP version's result separately while testing; remove before merging fail-fast: false matrix: - base_container: ["php:7.4-bullseye", "php:8.0-bullseye", "php:8.1-bookworm", "php:8.2-bookworm", "php:8.3-bookworm", "php:8.4-bookworm"] + base_container: ["php:8.2-bookworm", "php:8.3-bookworm", "php:8.4-bookworm"] runs-on: ubuntu-latest steps: @@ -23,27 +23,6 @@ jobs: ## Set environment variables for the build container by matrix - # 7.4 is the current "latest" default version and gets this extra tag - # upon EOL, this should move to 8.0 - # 7.4 has already reached EoL, we can consider 8.0 as default version now - - name: Set PHP 7.4 settings - if: ${{ matrix.base_container == 'php:7.4-bullseye' }} - run: | - echo "BUILD_TAGS=10up/wordpress-ci:latest,10up/wordpress-ci:php-7.4" >> $GITHUB_ENV - echo "COMPOSER_VERSION=1" >> $GITHUB_ENV - - - name: Set PHP 8.0 settings - if: ${{ matrix.base_container == 'php:8.0-bullseye' }} - run: | - echo "BUILD_TAGS=10up/wordpress-ci:php-8.0" >> $GITHUB_ENV - echo "COMPOSER_VERSION=2" >> $GITHUB_ENV - - - name: Set PHP 8.1 settings - if: ${{ matrix.base_container == 'php:8.1-bookworm' }} - run: | - echo "BUILD_TAGS=10up/wordpress-ci:php-8.1" >> $GITHUB_ENV - echo "COMPOSER_VERSION=2" >> $GITHUB_ENV - - name: Set PHP 8.2 settings if: ${{ matrix.base_container == 'php:8.2-bookworm' }} run: | @@ -56,10 +35,11 @@ jobs: echo "BUILD_TAGS=10up/wordpress-ci:php-8.3" >> $GITHUB_ENV echo "COMPOSER_VERSION=2" >> $GITHUB_ENV + # The newest PHP version also gets the "latest" tag; move it when adding a newer version - name: Set PHP 8.4 settings if: ${{ matrix.base_container == 'php:8.4-bookworm' }} run: | - echo "BUILD_TAGS=10up/wordpress-ci:php-8.4" >> $GITHUB_ENV + echo "BUILD_TAGS=10up/wordpress-ci:latest,10up/wordpress-ci:php-8.4" >> $GITHUB_ENV echo "COMPOSER_VERSION=2" >> $GITHUB_ENV ## GitHub Action validation testing before starting workflow ## From c431b16a529ed083e8db5e5d2bc109f037935d73 Mon Sep 17 00:00:00 2001 From: pablojmarti Date: Tue, 29 Sep 2026 14:55:01 -0400 Subject: [PATCH 09/11] Installs opentelemtry on all builds now --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 92b3fee..9b1c673 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# base image should be a PHP Debian container, such as php:7.4-buster +# base image should be a PHP Debian container, such as php:8.4-bookworm ARG PHP_IMG FROM $PHP_IMG @@ -96,7 +96,7 @@ RUN echo "memory_limit=-1" > "$PHP_INI_DIR/conf.d/memory-limit.ini" && \ ## PHP extensions ## RUN docker-php-ext-install zip pdo pdo_mysql gd bcmath intl sockets mysqli exif soap -RUN if [ ! -z "$(php --version | grep ^PHP | awk '{print $2}' | grep -v ^7 | grep -v "8.0")" ]; then pecl install opentelemetry && docker-php-ext-enable opentelemetry; fi +RUN pecl install opentelemetry && docker-php-ext-enable opentelemetry #### Specific to building / deploying #### @@ -126,7 +126,7 @@ RUN PIP_BREAK_SYSTEM_PACKAGES=1 python3 -m pip -V && \ PIP_BREAK_SYSTEM_PACKAGES=1 python3 -m pip install -r /tmp/requirements.txt ## Composer ## -ARG COMPOSER_VERSION 1 +ARG COMPOSER_VERSION=2 ENV COMPOSER_ALLOW_SUPERUSER="1" ENV COMPOSER_HOME="/tmp" From 86dac95aad107e56dabecfc4dbc0d08c5bab47a1 Mon Sep 17 00:00:00 2001 From: pablojmarti Date: Tue, 29 Sep 2026 14:55:13 -0400 Subject: [PATCH 10/11] updates for new supported version of php --- README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1894d0b..5c7be27 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,12 @@ The variables `PRIVATE_KEY` and `PUBLIC_KEY` can be used to create a custom publ The Terminus token can be set via the `TERMINUS_TOKEN` variable, for use with the Pantheon managed hosting platform. +## Supported PHP versions + +Images are built for PHP 8.2, 8.3 and 8.4 (tags `php-8.2`, `php-8.3`, `php-8.4`). The `latest` tag points to the newest version, currently PHP 8.4. + +PHP 7.4, 8.0 and 8.1 are **deprecated** and no longer built. Their existing tags (`php-7.4`, `php-8.0`, `php-8.1`) remain on Docker Hub, but they will not receive any further updates, including security updates. Projects using them should move to PHP 8.2 or newer. + ## Updating PHP versions The versions of PHP in use by the container are defined in `.github/workflows/build.yaml`: @@ -64,7 +70,7 @@ jobs: build_push_to_dockerhub: strategy: matrix: - base_container: ["php:7.4-bullseye", "php:8.0-bullseye", "php:8.1-bookworm", "php:8.2-bookworm", "php:8.3-bookworm", "php:8.4-bookworm"] + base_container: ["php:8.2-bookworm", "php:8.3-bookworm", "php:8.4-bookworm"] ``` Update steps: @@ -78,8 +84,9 @@ Update steps: echo "BUILD_TAGS=10up/wordpress-ci:php-" >> $GITHUB_ENV echo "COMPOSER_VERSION=2" >> $GITHUB_ENV ``` -3. Push changes to working branch to trigger the build of the new image. *NOTE:* This will not push it to the registry. -4. Create a PR and tag one of the maintainers. The image will become available once it is merged into `trunk` and the actions complete. +3. If the new version is the newest one, move `10up/wordpress-ci:latest` from the previous newest version's `BUILD_TAGS` to the new version's. +4. Push changes to working branch to trigger the build of the new image. *NOTE:* This will not push it to the registry. +5. Create a PR and tag one of the maintainers. The image will become available once it is merged into `trunk` and the actions complete. From b4eba1eaecedc30b0e5ce239a1c3ce1d20da8760 Mon Sep 17 00:00:00 2001 From: pablojmarti Date: Tue, 29 Sep 2026 14:55:19 -0400 Subject: [PATCH 11/11] Adds latest rule --- AGENTS.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 389d0a0..df15da5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,9 +29,9 @@ shellcheck scripts/* build/*.sh entrypoint.sh tests/*.sh ## Architecture -**Image build (`Dockerfile`)**, in this order: apt packages (incl. `clamav-daemon`), then `freshclam` early so the virus DB is baked in (running it later in the file hit bugs), locale, PHP ini (`memory_limit=-1`) and extensions (`opentelemetry` only on PHP ≥ 8.1), nvm in `/tmp/.nvm` + the latest `NODE_LTS_COUNT` (default 3) Node LTS lines via `build/install-node.sh`, pip `requirements.txt` (uses `PIP_BREAK_SYSTEM_PACKAGES=1` for bookworm), composer, wp-cli, then docker-cli/kubectl/terminus/gh/glab, which are always the latest release and unpinned. Finally `scripts/*` go to `/custom-scripts` (on `PATH`), plus `BASH_ENV=/root/.bashrc` so non-interactive CI shells load nvm and the `wp` alias. +**Image build (`Dockerfile`)**, in this order: apt packages (incl. `clamav-daemon`), then `freshclam` early so the virus DB is baked in (running it later in the file hit bugs), locale, PHP ini (`memory_limit=-1`) and extensions (including `opentelemetry` from pecl), nvm in `/tmp/.nvm` + the latest `NODE_LTS_COUNT` (default 3) Node LTS lines via `build/install-node.sh`, pip `requirements.txt` (uses `PIP_BREAK_SYSTEM_PACKAGES=1` for bookworm), composer, wp-cli, then docker-cli/kubectl/terminus/gh/glab, which are always the latest release and unpinned. Finally `scripts/*` go to `/custom-scripts` (on `PATH`), plus `BASH_ENV=/root/.bashrc` so non-interactive CI shells load nvm and the `wp` alias. -- `build/install-composer.sh` installs `composer1-bin`, `composer2-bin`, and a default `composer` chosen by the `COMPOSER_VERSION` build arg. +- `build/install-composer.sh` installs `composer1-bin`, `composer2-bin`, and a default `composer` chosen by the `COMPOSER_VERSION` build arg (default 2, and every image in CI uses 2). - `build/install-wpcli.sh` installs `/usr/local/bin/wp-cli.phar`. `wp` exists only as a `.bashrc` alias (`php wp-cli.phar --allow-root`), not as a binary. Bash doesn't expand aliases in non-interactive shells, so `bash -c 'wp ...'` fails with "command not found" even though `BASH_ENV` loads `.bashrc`. - `build/install-node.sh [count]` installs nvm's relative aliases oldest first: `lts/-2`, `lts/-1`, then `lts/*`. It resolves them at build time, so the weekly no-cache rebuild rolls the set forward automatically when a new line goes LTS (versions get dropped the same way). It rejects a count that isn't a positive integer. Keeping 3 is a deliberate choice, even when the oldest line is past EOL (e.g. Node 20 until 26 goes LTS). The Dockerfile then runs `nvm alias default 'lts/*'`. Pipelines switch with `nvm use ` or run `nvm install ` for anything else. @@ -50,6 +50,7 @@ shellcheck scripts/* build/*.sh entrypoint.sh tests/*.sh ## Release / CI (`.github/workflows/build.yaml`) - Runs on every push to any branch, plus a weekly rebuild (Sun 04:00 UTC) to pick up security updates. Builds are always `no-cache: true`, wrapped in a 3-attempt retry. -- The matrix is `base_container`. Each entry needs a matching "Set PHP X settings" step that sets `BUILD_TAGS` and `COMPOSER_VERSION`. PHP 7.4 uses Composer 1 and also owns the `latest` tag. All others use Composer 2. 7.4 and 8.0 use `-bullseye` images, because `php:7.4-bookworm` and `php:8.0-bookworm` don't exist. 8.1–8.4 use `-bookworm`. -- **7.4 and 8.0 builds currently fail.** Debian bullseye support ended on Aug 31, 2026. `deb.debian.org` still lists `bullseye-security` packages, but the `.deb` files return 404, so the first `apt-get install` exits with code 100. The matrix has `fail-fast: false` set **temporarily**, so 8.1–8.4 finish and report their own results instead of being cancelled. Expect 7.4 and 8.0 to show red until they're dropped (pending a team decision). Remove `fail-fast: false` again once testing is done. +- The matrix is `base_container`, which currently builds PHP 8.2, 8.3 and 8.4, all on `-bookworm` images. Each entry needs a matching "Set PHP X settings" step that sets `BUILD_TAGS` and `COMPOSER_VERSION` (always 2). The newest version also carries the `latest` tag, which is currently 8.4, so adding a newer PHP means moving `latest` to it. +- PHP 7.4, 8.0 and 8.1 are deprecated and removed from the matrix. Their Docker Hub tags still exist but are no longer rebuilt. Don't reintroduce bullseye-based images: Debian bullseye support ended on Aug 31, 2026, and its security package files return 404, so any bullseye build fails at the first `apt-get install`. +- The matrix has `fail-fast: false` set **temporarily** so each PHP version reports its own result while this branch is being tested. Remove it before merging. - Images push to Docker Hub only from `trunk`. Branch builds only validate. Adding a PHP version means adding a matrix entry plus a settings step (the README has the template).