diff --git a/.github/workflows/update-devpanel-cli.yml b/.github/workflows/update-devpanel-cli.yml new file mode 100644 index 00000000..d9c97495 --- /dev/null +++ b/.github/workflows/update-devpanel-cli.yml @@ -0,0 +1,212 @@ +name: Update DevPanel CLI binaries + +# Build linux/amd64 and linux/arm64 devpanel binaries from +# https://github.com/devpanel/cli/tree/HEAD/cmd/devpanel-client and commit +# base/bin/devpanel-amd64 and base/bin/devpanel-arm64. +# +# Run it manually. The Actions cache records the latest commit that changed +# cmd/devpanel-client. Delete that cache entry to rebuild the same commit. +# +# devpanel/cli is cloned under RUNNER_TEMP, outside this repository. +# GHCR_TOKEN must be able to read devpanel/cli. The workflow token commits +# the binaries. + +on: + workflow_dispatch: + +concurrency: + group: update-devpanel-cli-${{ github.ref }} + cancel-in-progress: true + +jobs: + plan: + name: Check cached devpanel-client revision + runs-on: ubuntu-26.04 + permissions: + actions: read + contents: read + outputs: + sha: ${{ steps.upstream.outputs.sha }} + changed: ${{ steps.decision.outputs.changed }} + steps: + - name: Require GHCR_TOKEN + env: + GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} + run: | + if [[ -z "${GHCR_TOKEN}" ]]; then + echo "::error::GHCR_TOKEN is required to read devpanel/cli." + exit 1 + fi + + - name: Resolve latest cmd/devpanel-client commit + id: upstream + env: + GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} + run: | + set -euo pipefail + body=$(mktemp) + code=$( + curl -sS \ + -H "Authorization: Bearer ${GHCR_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + -o "$body" \ + -w "%{http_code}" \ + "https://api.github.com/repos/devpanel/cli/commits?path=cmd/devpanel-client&per_page=1" \ + || true + ) + if [[ "$code" != "200" ]]; then + echo "::error::Unable to read cmd/devpanel-client history (HTTP ${code:-unknown})." + jq -r '.message // empty' "$body" >&2 || true + exit 1 + fi + sha=$(jq -r '.[0].sha // empty' "$body") + rm -f "$body" + if [[ ! "$sha" =~ ^[0-9a-f]{40}$ ]]; then + echo "::error::cmd/devpanel-client has no commits on the default branch." + exit 1 + fi + echo "sha=${sha}" >> "$GITHUB_OUTPUT" + echo "Latest cmd/devpanel-client commit is ${sha}" + + - name: Restore the built revision + id: revision-cache + uses: actions/cache/restore@v5 + with: + path: ${{ runner.temp }}/devpanel-cli-revision + key: devpanel-client-${{ steps.upstream.outputs.sha }} + + - name: Decide whether to rebuild + id: decision + env: + CACHE_HIT: ${{ steps.revision-cache.outputs.cache-hit }} + REF_TYPE: ${{ github.ref_type }} + UPSTREAM_SHA: ${{ steps.upstream.outputs.sha }} + run: | + if [[ "${REF_TYPE}" == "tag" ]]; then + echo "changed=false" >> "$GITHUB_OUTPUT" + echo "Tagged commits are left unchanged." + elif [[ "${CACHE_HIT}" == "true" ]]; then + echo "changed=false" >> "$GITHUB_OUTPUT" + echo "cmd/devpanel-client ${UPSTREAM_SHA} is already built." + else + echo "changed=true" >> "$GITHUB_OUTPUT" + echo "cmd/devpanel-client ${UPSTREAM_SHA} is not in the Actions cache." + fi + + build: + name: Build devpanel (${{ matrix.arch }}) + needs: plan + if: needs.plan.outputs.changed == 'true' + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + runner: ubuntu-26.04 + - arch: arm64 + runner: ubuntu-26.04-arm + runs-on: ${{ matrix.runner }} + permissions: + contents: read + steps: + - name: Clone devpanel/cli + env: + GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} + UPSTREAM_SHA: ${{ needs.plan.outputs.sha }} + run: | + set -euo pipefail + dest="${RUNNER_TEMP}/devpanel-cli" + mkdir "$dest" + git -C "$dest" init + git -C "$dest" remote add origin https://github.com/devpanel/cli.git + git -C "$dest" -c "http.extraheader=AUTHORIZATION: bearer ${GHCR_TOKEN}" \ + fetch --depth 1 origin "$UPSTREAM_SHA" + git -C "$dest" checkout --detach FETCH_HEAD + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: ${{ runner.temp }}/devpanel-cli/go.mod + cache: false + + - name: Build native binary + env: + GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} + run: | + set -euo pipefail + if [[ -n "${GHCR_TOKEN}" ]]; then + git config --global url."https://x-access-token:${GHCR_TOKEN}@github.com/".insteadOf "https://github.com/" + fi + dest="${RUNNER_TEMP}/devpanel-${{ matrix.arch }}" + ( + cd "${RUNNER_TEMP}/devpanel-cli" + env -u GOOS -u GOARCH CGO_ENABLED=0 \ + go build -trimpath -buildvcs=false -o "$dest" ./cmd/devpanel-client + ) + chmod 0755 "$dest" + "$dest" version + + - name: Upload binary + uses: actions/upload-artifact@v7 + with: + name: devpanel-binary-${{ matrix.arch }} + path: ${{ runner.temp }}/devpanel-${{ matrix.arch }} + if-no-files-found: error + retention-days: 1 + + commit: + name: Commit devpanel binaries + needs: [plan, build] + if: needs.plan.outputs.changed == 'true' && needs.build.result == 'success' + runs-on: ubuntu-26.04 + permissions: + actions: write + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Download amd64 binary + uses: actions/download-artifact@v8 + with: + name: devpanel-binary-amd64 + path: dist/amd64 + + - name: Download arm64 binary + uses: actions/download-artifact@v8 + with: + name: devpanel-binary-arm64 + path: dist/arm64 + + - name: Commit binaries + env: + UPSTREAM_SHA: ${{ needs.plan.outputs.sha }} + run: | + set -euo pipefail + install -m 0755 dist/amd64/devpanel-amd64 base/bin/devpanel-amd64 + install -m 0755 dist/arm64/devpanel-arm64 base/bin/devpanel-arm64 + git add -- base/bin/devpanel-amd64 base/bin/devpanel-arm64 + if git diff --cached --quiet; then + echo "Build produced no changes to commit." + else + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git commit \ + -m "Update devpanel CLI from devpanel/cli@${UPSTREAM_SHA}" \ + -m "Built linux/amd64 and linux/arm64 from cmd/devpanel-client at that commit." + git push origin "HEAD:${GITHUB_REF_NAME}" + fi + + - name: Record the built revision + env: + UPSTREAM_SHA: ${{ needs.plan.outputs.sha }} + run: | + mkdir -p "${RUNNER_TEMP}/devpanel-cli-revision" + printf '%s\n' "${UPSTREAM_SHA}" > "${RUNNER_TEMP}/devpanel-cli-revision/sha" + + - name: Save the built revision + uses: actions/cache/save@v5 + with: + path: ${{ runner.temp }}/devpanel-cli-revision + key: devpanel-client-${{ needs.plan.outputs.sha }} diff --git a/AGENTS.md b/AGENTS.md index abbe2022..292b2654 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ Images are published to Docker Hub as `devpanel/php:-` (e.g. ` ``` base/ # Shared base Dockerfile (PHP + Apache) Dockerfile - bin/ # DevPanel CLI binary + bin/ # DevPanel CLI binary; update-devpanel-cli.yml commits devpanel-$arch drush/ # Drush version directories (drush7–drush11) scripts/ # Apache startup scripts templates/ # Apache/PHP config templates @@ -129,9 +129,10 @@ Baseline JSON files in `tests/baselines/` track pre-existing lint violations per #### 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. +- **`update-devpanel-cli.yml`** — manual `workflow_dispatch`. Builds `base/bin/devpanel-amd64` and `base/bin/devpanel-arm64` from the latest `devpanel/cli` commit that changed `cmd/devpanel-client`, when the Actions cache does not already contain that commit. A new run cancels one already in progress. The workflow token commits the binaries. - **`test.yml`** — runs YAML, shell, and Dockerfile linting plus build/run tests on pushes and pull requests. - Production images are tagged without suffix (e.g. `devpanel/php:8.3-base`); `develop` branch builds use the `-rc` suffix. -- Required repository secrets: `DOCKERHUB_USERNAME`, `DOCKERHUB_TOKEN`. Optional: `GHCR_TOKEN` (falls back to `GITHUB_TOKEN` for GHCR pushes). +- Required repository secrets: `DOCKERHUB_USERNAME`, `DOCKERHUB_TOKEN`. Optional: `GHCR_TOKEN` (falls back to `GITHUB_TOKEN` for GHCR pushes). `update-devpanel-cli.yml` requires `GHCR_TOKEN` with access to `devpanel/cli`. ## Environment Variables (base images) diff --git a/README.md b/README.md index 72b29456..caedc07f 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,15 @@ for setup instructions. runners; AMD64 builds use `ubuntu-26.04`. QEMU is set up on ARM runners to execute the bundled AMD64 DevPanel CLI, and for cross-platform builds. Native AMD64 builds skip QEMU. +- `update-devpanel-cli.yml` rebuilds `base/bin/devpanel-amd64` and + `base/bin/devpanel-arm64` from the latest + [devpanel/cli](https://github.com/devpanel/cli) commit that changed + `cmd/devpanel-client`. Run it manually. It caches that commit and skips + the build when the cache entry exists. A new run cancels one already in + progress. The workflow token commits the binaries. Delete the cache + entry to rebuild the same commit. The `devpanel/cli` checkout is + placed outside this repository. AMD64 and ARM64 binaries build in + parallel on `ubuntu-26.04` and `ubuntu-26.04-arm`. - 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 a7fa5f1f..34171c0a 100644 --- a/TODO.md +++ b/TODO.md @@ -102,3 +102,14 @@ Use this file to track work that needs to be done. Lint scripts automatically ap - 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. + +# Task: Rebuild the DevPanel CLI from devpanel/cli + +## Steps +- [x] Add a manual workflow based on `main`. The workflow token commits the binaries. +- [x] Cache the latest commit that changed `cmd/devpanel-client` and skip the build when that entry exists. +- [x] Build linux/amd64 and linux/arm64 in parallel on native runners. +- [x] Clone `devpanel/cli` outside this repository and commit only the two binaries. + +## Definition of Done +- A cache miss builds both binaries on `ubuntu-26.04` and `ubuntu-26.04-arm` and commits `base/bin/devpanel-amd64` and `base/bin/devpanel-arm64`.