Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions .github/workflows/build-images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
name: Build and push images

on:
push:
branches:
- main
paths:
- '*/*/Dockerfile'
- '*/*/variants.yaml'
- '*/*/rootfs/**'
- '*/*/.scripts/**'
- '*/*/otel/**'
- '*/latest'
workflow_dispatch:
inputs:
target:
description: 'Image/version to build (e.g. hyperf/8.3 or hyperf/latest).'
required: true

permissions:
contents: read

jobs:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
detect:
runs-on: ubuntu-latest
outputs:
builds: ${{ steps.list.outputs.builds }}
steps:
- name: Checkout
uses: actions/checkout@v4
Comment on lines +29 to +30
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Jun 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin every GitHub Action uses: to an immutable commit SHA (not a moving tag).

In .github/workflows/build-images.yml, these uses: entries reference mutable tags: actions/checkout@v4, tj-actions/changed-files@v44, docker/setup-buildx-action@v3, docker/login-action@v3, and docker/build-push-action@v6. Pin each to OWNER/REPO@<full_sha> (optionally keep the human-readable tag in a comment).

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 23-24: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 24-24: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-images.yml around lines 23 - 24, Update every GitHub
Action `uses:` in the workflow to pin to an immutable commit SHA instead of a
moving tag: replace actions/checkout@v4, tj-actions/changed-files@v44,
docker/setup-buildx-action@v3, docker/login-action@v3, and
docker/build-push-action@v6 with their corresponding OWNER/REPO@<full_sha>
values (you can look up each repo’s latest stable commit SHA) and optionally
retain the human-readable tag as a trailing comment; ensure the changes are made
where those `uses:` entries appear so the workflow references specific commit
SHAs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipping this for now to stay consistent with the rest of the repo — .github/workflows/copilot-sandbox.yml uses tag pins for the same docker/* actions and actions/checkout. Pinning to commit SHAs is reasonable but is a repo-wide policy decision that should be paired with Dependabot for action SHAs; otherwise the workflows drift apart in conventions. Happy to revisit as a separate PR.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wilcorrea, that's a fair point — consistency across workflows matters, and introducing SHA pins in isolation without Dependabot or a repo-wide policy would just create two different conventions. Would you like me to open a follow-up issue to track the "pin all action refs to commit SHAs + enable Dependabot for Actions" work so it doesn't get lost?

(ᵔᴥᵔ) 🐰 noted


- name: Get changed files
id: changed
if: github.event_name == 'push'
uses: tj-actions/changed-files@v44

- name: Build matrix
id: list
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_TARGET: ${{ inputs.target }}
CHANGED_FILES: ${{ steps.changed.outputs.all_changed_files }}
run: |
set -euo pipefail

declare -a versions=()

collect_version() {
local v="$1"
[ -z "$v" ] && return
case "$v" in
[A-Za-z0-9_./-]*) ;;
*) echo "::warning::ignoring suspicious path '$v'"; return ;;
esac
local depth
depth=$(awk -F/ '{print NF}' <<< "$v")
if [ "$depth" != "2" ]; then return; fi
local resolved="$v"
if [ -L "$v" ]; then
local link_target
link_target=$(readlink "$v")
resolved="${v%/*}/${link_target}"
fi
if [ ! -f "${resolved}/Dockerfile" ]; then return; fi
for existing in "${versions[@]+"${versions[@]}"}"; do
if [ "$existing" = "$v" ]; then return; fi
done
versions+=("$v")
}

if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
collect_version "$INPUT_TARGET"
else
declare -a changed_dirs=()
for f in $CHANGED_FILES; do
dir=$(awk -F/ 'NF>=2 {print $1"/"$2}' <<< "$f")
[ -z "$dir" ] && continue
for existing in "${changed_dirs[@]+"${changed_dirs[@]}"}"; do
if [ "$existing" = "$dir" ]; then dir=""; break; fi
done
[ -n "$dir" ] && changed_dirs+=("$dir")
done

for v in "${changed_dirs[@]+"${changed_dirs[@]}"}"; do
collect_version "$v"
done

for symlink in */latest; do
[ -L "$symlink" ] || continue
target=$(readlink "$symlink")
parent="${symlink%/*}"
resolved="${parent}/${target}"
for changed in "${changed_dirs[@]+"${changed_dirs[@]}"}"; do
if [ "$changed" = "$resolved" ]; then
collect_version "$symlink"
break
fi
done
done
fi

declare -a builds=()
for v in "${versions[@]+"${versions[@]}"}"; do
image="${v%%/*}"
version="${v##*/}"
ctx="$v"
if [ -L "$ctx" ]; then
link_target=$(readlink "$ctx")
ctx="${ctx%/*}/${link_target}"
fi

manifest="${ctx}/variants.yaml"
if [ -f "$manifest" ]; then
if ! yq eval 'all_c(.target != null and .target != "" and has("suffix"))' "$manifest" | grep -qx true; then
echo "::error file=${manifest}::each entry must declare 'target' (non-empty) and 'suffix' (key required, value may be empty)"
exit 1
fi
entries=$(yq eval -o=json "$manifest")
else
entries='[{"target":"'"$image"'","suffix":""}]'
fi

while IFS= read -r line; do
[ -z "$line" ] && continue
builds+=("$line")
done < <(jq -c --arg img "$image" --arg ver "$version" --arg ctx "$ctx" '
.[] | {
image: $img,
tag: ($ver + (if (.suffix // "") == "" then "" else "-" + .suffix end)),
context: $ctx,
target: .target,
build_args: ((.args // {}) | to_entries | map(.key + "=" + (.value | tostring)) | join("\n"))
}
' <<< "$entries")
done

if [ ${#builds[@]} -eq 0 ]; then
echo "builds=[]" >> "$GITHUB_OUTPUT"
else
joined=$(IFS=,; echo "${builds[*]}")
echo "builds=[${joined}]" >> "$GITHUB_OUTPUT"
fi

build:
needs: detect
if: needs.detect.outputs.builds != '[]'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
build: ${{ fromJson(needs.detect.outputs.builds) }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v6
with:
context: ./${{ matrix.build.context }}
target: ${{ matrix.build.target }}
build-args: ${{ matrix.build.build_args }}
platforms: linux/amd64
push: true
tags: devitools/${{ matrix.build.image }}:${{ matrix.build.tag }}
cache-from: type=gha,scope=${{ matrix.build.image }}-${{ matrix.build.tag }}
cache-to: type=gha,mode=max,scope=${{ matrix.build.image }}-${{ matrix.build.tag }}
102 changes: 102 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Validate variants.yaml

on:
pull_request:
branches:
- main

permissions:
contents: read

jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Get changed variants.yaml files
id: changed
uses: tj-actions/changed-files@v44
with:
files: '*/*/variants.yaml'

- name: Validate each manifest
if: steps.changed.outputs.any_changed == 'true'
shell: bash
env:
CHANGED_MANIFESTS: ${{ steps.changed.outputs.all_changed_files }}
run: |
set -uo pipefail
errors=0

for manifest in $CHANGED_MANIFESTS; do
case "$manifest" in
[A-Za-z0-9_./-]*) ;;
*) echo "::warning::skipping suspicious path '$manifest'"; continue ;;
esac
echo "::group::${manifest}"
dir="$(dirname "$manifest")"
dockerfile="${dir}/Dockerfile"

if ! yq eval '.' "$manifest" > /dev/null 2>&1; then
echo "::error file=${manifest}::invalid YAML syntax"
errors=$((errors+1))
echo "::endgroup::"
continue
fi

kind=$(yq eval 'type' "$manifest")
if [ "$kind" != "!!seq" ]; then
echo "::error file=${manifest}::root must be a YAML list (got ${kind})"
errors=$((errors+1))
echo "::endgroup::"
continue
fi

entries=$(yq eval -o=json "$manifest")

bad=$(jq -c '[.[] | select((.target | type) != "string" or .target == "" or (has("suffix") | not))]' <<< "$entries")
if [ "$(jq 'length' <<< "$bad")" != "0" ]; then
echo "::error file=${manifest}::entries missing required 'target' (non-empty string) or 'suffix' key:"
jq -r '.[] | " - " + (. | tostring)' <<< "$bad"
errors=$((errors+1))
fi

bad_args=$(jq -c '[.[] | select(has("args") and (.args | type) != "object")]' <<< "$entries")
if [ "$(jq 'length' <<< "$bad_args")" != "0" ]; then
echo "::error file=${manifest}::'args' must be a mapping when present:"
jq -r '.[] | " - " + (. | tostring)' <<< "$bad_args"
errors=$((errors+1))
fi

if [ ! -f "$dockerfile" ]; then
echo "::error file=${manifest}::sibling Dockerfile not found at ${dockerfile}"
errors=$((errors+1))
else
for target in $(jq -r '.[].target' <<< "$entries" | sort -u); do
if ! grep -qE "^FROM[[:space:]]+.*[[:space:]]+AS[[:space:]]+${target}([[:space:]]|$)" "$dockerfile"; then
echo "::error file=${manifest}::target '${target}' is not declared as a stage in ${dockerfile} (expected: FROM <base> AS ${target})"
errors=$((errors+1))
fi
done
fi

duplicates=$(jq -r '
[.[] | (if (.suffix // "") == "" then "<empty>" else .suffix end)] |
group_by(.) | map(select(length > 1)) | map(.[0]) | .[]
' <<< "$entries" | sort -u)
if [ -n "$duplicates" ]; then
echo "::error file=${manifest}::duplicate suffix(es) detected (would produce colliding tags):"
echo "$duplicates" | sed 's/^/ - /'
errors=$((errors+1))
fi

echo "::endgroup::"
done

if [ $errors -gt 0 ]; then
echo "Found ${errors} error(s) across the changed manifests."
exit 1
fi
echo "All changed variants.yaml are valid."
File renamed without changes.
File renamed without changes.
35 changes: 28 additions & 7 deletions hyperf/Dockerfile → hyperf/8.3/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.20 AS hyperf

ARG CONTEXT
ARG TIMEZONE
Expand Down Expand Up @@ -57,18 +57,39 @@ COPY --from=composer/composer:2.8.5-bin /composer /usr/local/bin/composer

COPY .scripts /devitools/.scripts

# update
RUN set -ex \
# ---------- apply settings -------\
&& bash /devitools/.scripts/setup.sh "$TIMEZONE" \
&& bash /devitools/.scripts/setup-dev.sh "$APP_TARGET" \
# ---------- clear works ----------\
&& rm -rf /var/cache/apk/* /tmp/* /usr/share/man

WORKDIR /opt/www

EXPOSE 9501
ENTRYPOINT ["php", "/opt/www/bin/hyperf.php"]
CMD ["start"]

# --- Variant: with OTEL Collector + pgbouncer + supervisor ---
FROM hyperf AS hyperf-otel

ARG COLLECTOR=debug
ARG OTEL_COLLECTOR_VERSION=0.121.0

RUN apk add --no-cache --upgrade expat \
&& apk add --no-cache supervisor wget pgbouncer \
&& OTEL_BASE_URL="https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v${OTEL_COLLECTOR_VERSION}" \
&& OTEL_ASSET="otelcol-contrib_${OTEL_COLLECTOR_VERSION}_linux_amd64.tar.gz" \
&& cd /tmp \
&& wget -q "${OTEL_BASE_URL}/${OTEL_ASSET}" -O "${OTEL_ASSET}" \
&& wget -q "${OTEL_BASE_URL}/opentelemetry-collector-releases_otelcol-contrib_checksums.txt" -O checksums.txt \
&& grep " ${OTEL_ASSET}$" checksums.txt | sha256sum -c - \
&& tar -xzf "${OTEL_ASSET}" -C /usr/local/bin otelcol-contrib \
&& rm "${OTEL_ASSET}" checksums.txt \
&& chmod +x /usr/local/bin/otelcol-contrib \
&& mkdir -p /etc/pgbouncer /var/log/pgbouncer /var/run/pgbouncer /etc/supervisor.d /var/run/supervisor

ENTRYPOINT [ "php", "/opt/www/bin/hyperf.php" ]
COPY otel/collectors/${COLLECTOR}.yaml /etc/otel-collector-config.yaml
COPY otel/supervisord.conf /etc/supervisord.conf
COPY otel/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

CMD [ "start" ]
ENTRYPOINT ["/entrypoint.sh"]
CMD []
File renamed without changes.
File renamed without changes.
22 changes: 22 additions & 0 deletions hyperf/8.3/otel/collectors/debug.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
receivers:
zipkin:
endpoint: "0.0.0.0:9411"

processors:
batch:
send_batch_size: 200
timeout: 5s
memory_limiter:
check_interval: 1s
limit_mib: 256

exporters:
debug:
verbosity: normal

service:
pipelines:
traces:
receivers: [zipkin]
processors: [memory_limiter, batch]
exporters: [debug]
22 changes: 22 additions & 0 deletions hyperf/8.3/otel/collectors/google.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
receivers:
zipkin:
endpoint: "0.0.0.0:9411"

processors:
batch:
send_batch_size: 200
timeout: 5s
memory_limiter:
check_interval: 1s
limit_mib: 256

exporters:
googlecloud:
project: ${GOOGLE_CLOUD_PROJECT}

service:
pipelines:
traces:
receivers: [zipkin]
processors: [memory_limiter, batch]
exporters: [googlecloud]
Loading
Loading