ci: add pull_request_target image build/mirror workflow flow

This commit is contained in:
kevinveenbirkenbach committed 2026-03-17 23:43:53 +01:00
commit e2bc2ea9e2
16 files changed
+384 -60

No files matched your search

+51 -34
View File
@@ -7,13 +7,28 @@ on:
description: "Space-separated distro list"
required: true
type: string
checkout_ref:
description: "Optional git ref to checkout before build (default: event ref)"
required: false
type: string
default: ""
image_tag:
description: "Optional image tag override (default: ci-${github.sha})"
required: false
type: string
default: ""
concurrency_channel:
description: "Optional channel to separate concurrency groups between callers"
required: false
type: string
default: "default"
permissions:
contents: read
packages: write
concurrency:
group: infinito-build-${{ github.ref }}
group: infinito-build-${{ github.repository }}-${{ inputs.concurrency_channel }}-${{ inputs.checkout_ref || github.ref }}
cancel-in-progress: false
jobs:
@@ -22,7 +37,7 @@ jobs:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.out.outputs.matrix }}
image_tag: ci-${{ github.sha }}
image_tag: ${{ steps.out.outputs.image_tag }}
steps:
- name: Install jq
run: |
@@ -36,8 +51,14 @@ jobs:
set -euo pipefail
distros="${{ inputs.distros }}"
json="$(printf '%s' "$distros" | jq -Rc 'split(" ") | map(select(length>0))')"
image_tag="${{ inputs.image_tag }}"
if [ -z "${image_tag}" ]; then
image_tag="ci-${GITHUB_SHA}"
fi
echo "matrix=${json}" >> "$GITHUB_OUTPUT"
echo "image_tag=${image_tag}" >> "$GITHUB_OUTPUT"
echo "Using matrix: ${json}"
echo "Using image tag: ${image_tag}"
build:
name: 🏗️ Build image (${{ matrix.distro }})
@@ -50,8 +71,19 @@ jobs:
distro: ${{ fromJson(needs.prepare.outputs.matrix) }}
steps:
- name: Checkout
- name: Checkout trusted workflow repo (helpers)
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}
persist-credentials: false
- name: Checkout source to build
uses: actions/checkout@v6
with:
repository: ${{ github.repository }}
ref: ${{ inputs.checkout_ref || github.ref }}
path: _src
persist-credentials: false
- name: Disk pressure (start)
shell: bash
@@ -96,39 +128,24 @@ jobs:
run: echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"
- name: Build & push (retry 7x, 20s)
if: ${{ !(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) }}
shell: bash
run: |
set -euo pipefail
env:
BUILD_CONTEXT_DIR: _src
MATRIX_DISTRO: ${{ matrix.distro }}
IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }}
USE_NIX_TOKEN: ${{ !(github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository) }}
NIX_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAX_ATTEMPTS: "7"
RETRY_DELAY_SECONDS: "20"
run: ./scripts/ci/build_and_push_ci_image.sh
max_attempts=7
retry_delay_seconds=20
attempt=1
while true; do
echo "=== Build & push attempt ${attempt}/${max_attempts} ==="
if docker buildx build \
--file ./Dockerfile \
--push \
--tag "ghcr.io/${{ steps.lower_owner.outputs.owner }}/infinito-${{ matrix.distro }}:${{ needs.prepare.outputs.image_tag }}" \
--build-arg "PKGMGR_IMAGE=ghcr.io/kevinveenbirkenbach/pkgmgr-${{ matrix.distro }}:stable" \
--build-arg "NIX_CONFIG=access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}" \
--cache-from "type=gha,scope=infinito-${{ matrix.distro }}" \
--cache-to "type=gha,mode=max,scope=infinito-${{ matrix.distro }}" \
.; then
echo "Build & push succeeded on attempt ${attempt}/${max_attempts}."
break
fi
if [ "${attempt}" -ge "${max_attempts}" ]; then
echo "Build & push failed after ${max_attempts} attempts."
exit 1
fi
echo "Attempt ${attempt} failed. Retrying in ${retry_delay_seconds}s..."
sleep "${retry_delay_seconds}"
attempt=$((attempt + 1))
done
- name: Wait for prebuilt image (fork PR)
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }}
env:
MATRIX_DISTRO: ${{ matrix.distro }}
IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }}
run: ./scripts/ci/wait_prebuilt_ci_image.sh
- name: Disk pressure (post-build)
if: always()
+3
View File
@@ -48,6 +48,7 @@ jobs:
uses: ./.github/workflows/build-ci-images.yml
with:
distros: "arch debian ubuntu fedora centos"
concurrency_channel: ci-orchestrator-build-consumer
# Code Testing
test-code-integration:
@@ -85,6 +86,8 @@ jobs:
# Sync Missing Images
images-mirror-missing:
uses: ./.github/workflows/images-mirror-missing.yml
with:
concurrency_channel: ci-orchestrator-mirror-consumer
secrets: inherit
# Deploy tests
@@ -0,0 +1,42 @@
name: CI (On Pull-Request Target / Build CI Images For Forks)
on:
pull_request_target:
types:
- opened
- synchronize
- reopened
- ready_for_review
permissions:
contents: read
packages: write
jobs:
prepare-fork-merge-ref:
if: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
runs-on: ubuntu-latest
outputs:
checkout_ref: ${{ steps.out.outputs.checkout_ref }}
image_tag: ${{ steps.out.outputs.image_tag }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Resolve merge ref and image tag
id: out
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha || '' }}
run: ./scripts/ci/resolve_pr_merge_ref.sh
build-ci-images-fork:
if: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
needs: prepare-fork-merge-ref
uses: ./.github/workflows/build-ci-images.yml
with:
distros: "arch debian ubuntu fedora centos"
checkout_ref: ${{ needs.prepare-fork-merge-ref.outputs.checkout_ref }}
image_tag: ${{ needs.prepare-fork-merge-ref.outputs.image_tag }}
concurrency_channel: pr-target-build-producer
secrets: inherit
@@ -0,0 +1,43 @@
name: CI (On Pull-Request Target / Mirror Fork Images)
on:
pull_request_target:
types:
- opened
- synchronize
- reopened
- ready_for_review
permissions:
contents: read
packages: write
jobs:
prepare-fork-merge-ref:
if: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
runs-on: ubuntu-latest
outputs:
checkout_ref: ${{ steps.out.outputs.checkout_ref }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Resolve merge ref
id: out
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha || '' }}
run: ./scripts/ci/resolve_pr_merge_ref.sh
images-mirror-missing-fork:
if: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
needs: prepare-fork-merge-ref
uses: ./.github/workflows/images-mirror-missing.yml
with:
repo_root: _pr_source
source_repository: ${{ github.repository }}
source_ref: ${{ needs.prepare-fork-merge-ref.outputs.checkout_ref }}
source_path: _pr_source
concurrency_channel: pr-target-mirror-producer
concurrency_key: pr-${{ github.event.pull_request.number }}
secrets: inherit
+7 -8
View File
@@ -7,7 +7,6 @@ concurrency:
on:
push:
branches:
- master
- main
- "feature/**"
- "hotfix/**"
@@ -76,7 +75,7 @@ jobs:
distros: ${{ needs.pick-distro.outputs.distros }}
secrets: inherit
# Guard job: only allow version releases if the tag commit is contained in origin/master
# Guard job: only allow version releases if the tag commit is contained in origin/main
release-guard:
name: "🛡️ Guard release"
if: github.event.created == false && needs.pick-distro.outputs.is_version == 'true'
@@ -93,13 +92,13 @@ jobs:
fetch-depth: 0
fetch-tags: true
- name: Fetch master
- name: Fetch main
shell: bash
run: |
set -euo pipefail
git fetch origin master:refs/remotes/origin/master --force
git fetch origin main:refs/remotes/origin/main --force
- name: "Guard: tag commit must be contained in origin/master"
- name: "Guard: tag commit must be contained in origin/main"
id: guard
shell: bash
env:
@@ -117,12 +116,12 @@ jobs:
tag_sha="$(git rev-list -n 1 "${tag}")"
echo "Tag: ${tag} -> ${tag_sha}"
if git merge-base --is-ancestor "${tag_sha}" "origin/master"; then
if git merge-base --is-ancestor "${tag_sha}" "origin/main"; then
echo "allowed=true" >> "$GITHUB_OUTPUT"
echo "✅ Tag commit is on origin/master"
echo "✅ Tag commit is on origin/main"
else
echo "allowed=false" >> "$GITHUB_OUTPUT"
echo "⛔ Tag commit is NOT on origin/master"
echo "⛔ Tag commit is NOT on origin/main"
fi
release-version:
+46 -2
View File
@@ -17,11 +17,36 @@ on:
required: false
type: string
default: "."
source_repository:
description: "Optional source repository for image config scan (e.g. fork owner/repo)"
required: false
type: string
default: ""
source_ref:
description: "Optional source ref/SHA for image config scan"
required: false
type: string
default: ""
source_path:
description: "Optional checkout path for source_repository/source_ref"
required: false
type: string
default: "_pr_source"
images_per_hour:
description: "Optional throttle: max images mirrored per hour"
required: false
type: string
default: ""
concurrency_channel:
description: "Optional channel to separate concurrency groups between callers"
required: false
type: string
default: "default"
concurrency_key:
description: "Optional key (e.g. pr-123) to scope concurrency within a channel"
required: false
type: string
default: ""
secrets:
DOCKERHUB_USERNAME:
required: false
@@ -33,7 +58,7 @@ permissions:
packages: write
concurrency:
group: mirror-images-${{ github.repository }}-${{ github.ref_name }}
group: mirror-images-${{ github.repository }}-${{ inputs.concurrency_channel }}-${{ inputs.concurrency_key || github.ref_name }}
# Cancel scheduled Mirror sync, because may it works with outdatet mirror lists
cancel-in-progress: true
@@ -52,6 +77,16 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
- name: Checkout source repo for scan (optional)
if: ${{ inputs.source_repository != '' && inputs.source_ref != '' }}
uses: actions/checkout@v6
with:
repository: ${{ inputs.source_repository }}
ref: ${{ inputs.source_ref }}
path: ${{ inputs.source_path }}
fetch-depth: 1
persist-credentials: false
- name: Setup Python
uses: actions/setup-python@v6
with:
@@ -69,7 +104,7 @@ jobs:
# Optional: Docker Hub login to avoid rate limits while mirroring
- name: Login to Docker Hub (optional)
if: ${{ env.DOCKERHUB_USERNAME != '' && env.DOCKERHUB_TOKEN != '' }}
if: ${{ env.DOCKERHUB_USERNAME != '' && env.DOCKERHUB_TOKEN != '' && !(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) }}
uses: docker/login-action@v4
with:
username: ${{ env.DOCKERHUB_USERNAME }}
@@ -82,7 +117,16 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Wait for mirrored images (fork PR)
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }}
env:
GHCR_NAMESPACE: ${{ inputs.ghcr_namespace || github.repository_owner }}
GHCR_PREFIX: ${{ inputs.ghcr_prefix || 'mirror' }}
REPO_ROOT: ${{ inputs.repo_root || '.' }}
run: ./scripts/ci/wait_mirrored_images.sh
- name: Mirror images to GHCR (only missing, best-effort)
if: ${{ !(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) }}
shell: bash
run: |
set -euo pipefail
-1
View File
@@ -1,4 +1,3 @@
git@github.com:infinito-nexus/core.git
ssh://git@code.infinito.nexus:2201/infinito/nexus.git
ssh://git@git.veen.world:2201/kevinveenbirkenbach/infinito-nexus
git@github.com:kevinveenbirkenbach/infinito-nexus.git
+1 -1
View File
@@ -146,7 +146,7 @@ def build_single_graph(
"id": role,
**galaxy_info,
"doc_url": f"https://docs.infinito.nexus/roles/{role}/README.html",
"source_url": f"https://github.com/kevinveenbirkenbach/infinito-nexus/tree/master/roles/{role}",
"source_url": f"https://github.com/infinito-nexus/core/tree/main/roles/{role}",
}
nodes[role] = node
+1 -1
View File
@@ -25,7 +25,7 @@ Contributing to Infinito.Nexus
Want to contribute to the project or explore the source code? Check out our **GitHub repository**:
- `Infinito.Nexus GitHub Repository <https://s.infinito.nexus/code/tree/master/roles>`_
- `Infinito.Nexus GitHub Repository <https://github.com/infinito-nexus/core/tree/main/roles>`_
Contribution Guidelines
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+1 -1
View File
@@ -14,7 +14,7 @@
vars:
docker_git_repository_address: "https://github.com/akaunting/docker.git"
docker_git_repository_pull: true
docker_git_repository_version: "master"
docker_git_repository_version: "master"
docker_compose_flush_handlers: true
- name: "Akaunting | Create first-run marker to disable future setup"
+1 -1
View File
@@ -15,7 +15,7 @@ For further details on installation, configuration, and upgrades, please refer t
- [User_Administration.md](./User_Administration.md)
For more information about this role, visit the GitHub repositories:
- [Mailu](https://s.infinito.nexus/code/tree/master/roles/web-app-mailu)
- [Mailu](https://github.com/infinito-nexus/core/tree/main/roles/web-app-mailu)
- [Mailu-OIDC](https://github.com/heviat/Mailu-OIDC)
## Features
+11 -11
View File
@@ -108,9 +108,9 @@ compose:
onboard_enabled: true
standalone_enabled: false
network_mode: bridge
cpus: "1.0"
mem_reservation: "256m"
mem_limit: "1g"
cpus: "2.0"
mem_reservation: "1g"
mem_limit: "2g"
pids_limit: 1024
whiteboard:
name: "nextcloud-whiteboard"
@@ -126,15 +126,15 @@ compose:
enabled: true
shared: true
talk_recording:
name: "nextcloud-talk-recording"
image: "nextcloud/aio-talk-recording"
version: "latest"
name: "nextcloud-talk-recording"
image: "nextcloud/aio-talk-recording"
version: "latest"
backup:
no_stop_required: true
cpus: "2.0"
mem_reservation: "2g"
mem_limit: "4g"
pids_limit: 1024
no_stop_required: true
cpus: "2.0"
mem_reservation: "2g"
mem_limit: "6g"
pids_limit: 1024
css:
enabled: false
hcaptcha:
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
set -euo pipefail
: "${BUILD_CONTEXT_DIR:?Missing BUILD_CONTEXT_DIR}"
: "${MATRIX_DISTRO:?Missing MATRIX_DISTRO}"
: "${IMAGE_TAG:?Missing IMAGE_TAG}"
: "${GITHUB_REPOSITORY_OWNER:?Missing GITHUB_REPOSITORY_OWNER}"
: "${USE_NIX_TOKEN:?Missing USE_NIX_TOKEN}"
max_attempts="${MAX_ATTEMPTS:-7}"
retry_delay_seconds="${RETRY_DELAY_SECONDS:-20}"
attempt=1
nix_arg=()
if [[ "${USE_NIX_TOKEN}" == "true" ]]; then
: "${NIX_GITHUB_TOKEN:?Missing NIX_GITHUB_TOKEN}"
nix_arg=( --build-arg "NIX_CONFIG=access-tokens = github.com=${NIX_GITHUB_TOKEN}" )
else
echo "pull_request_target on fork detected: skipping NIX_CONFIG token build-arg."
fi
while true; do
echo "=== Build & push attempt ${attempt}/${max_attempts} ==="
if docker buildx build \
--file "${BUILD_CONTEXT_DIR}/Dockerfile" \
--push \
--tag "ghcr.io/${GITHUB_REPOSITORY_OWNER}/infinito-${MATRIX_DISTRO}:${IMAGE_TAG}" \
--build-arg "PKGMGR_IMAGE=ghcr.io/kevinveenbirkenbach/pkgmgr-${MATRIX_DISTRO}:stable" \
"${nix_arg[@]}" \
--cache-from "type=gha,scope=infinito-${MATRIX_DISTRO}" \
--cache-to "type=gha,mode=max,scope=infinito-${MATRIX_DISTRO}" \
"${BUILD_CONTEXT_DIR}"; then
echo "Build & push succeeded on attempt ${attempt}/${max_attempts}."
break
fi
if [[ "${attempt}" -ge "${max_attempts}" ]]; then
echo "Build & push failed after ${max_attempts} attempts."
exit 1
fi
echo "Attempt ${attempt} failed. Retrying in ${retry_delay_seconds}s..."
sleep "${retry_delay_seconds}"
attempt=$((attempt + 1))
done
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
set -euo pipefail
: "${PR_NUMBER:?Missing PR_NUMBER}"
: "${GITHUB_REPOSITORY:?Missing GITHUB_REPOSITORY}"
: "${GITHUB_OUTPUT:?Missing GITHUB_OUTPUT}"
ref="refs/pull/${PR_NUMBER}/merge"
repo_url="https://github.com/${GITHUB_REPOSITORY}.git"
sha="${PR_MERGE_SHA:-}"
checkout_ref="${ref}"
if [[ -n "${sha}" ]]; then
if [[ ! "${sha}" =~ ^[0-9a-f]{40}$ ]]; then
echo "Invalid PR_MERGE_SHA '${sha}', falling back to ${ref}" >&2
sha=""
else
checkout_ref="${sha}"
fi
fi
if [[ -z "${sha}" ]]; then
sha="$(git ls-remote "${repo_url}" "${ref}" | awk '{print $1}')"
if [[ -z "${sha}" ]]; then
echo "Failed to resolve merge SHA for ${ref}" >&2
exit 1
fi
checkout_ref="${sha}"
fi
{
echo "checkout_ref=${checkout_ref}"
echo "image_tag=ci-${sha}"
} >> "${GITHUB_OUTPUT}"
echo "Resolved fork PR merge ref ${ref} -> ${sha} (checkout_ref=${checkout_ref})"
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -euo pipefail
: "${GHCR_NAMESPACE:?Missing GHCR_NAMESPACE}"
: "${GHCR_PREFIX:?Missing GHCR_PREFIX}"
: "${REPO_ROOT:?Missing REPO_ROOT}"
max_attempts="${MAX_WAIT_ATTEMPTS:-180}"
sleep_seconds="${WAIT_SLEEP_SECONDS:-10}"
mapfile -t mirror_refs < <(
GHCR_NAMESPACE="${GHCR_NAMESPACE}" \
GHCR_PREFIX="${GHCR_PREFIX}" \
REPO_ROOT="${REPO_ROOT}" \
python - <<'PY'
import os
from pathlib import Path
from cli.mirror.providers import GHCRProvider
from cli.mirror.util import iter_role_images
provider = GHCRProvider(
os.environ["GHCR_NAMESPACE"],
os.environ["GHCR_PREFIX"],
)
repo_root = Path(os.environ["REPO_ROOT"]).resolve()
seen = set()
for img in iter_role_images(repo_root):
ref = f"{provider.image_base(img)}:{img.version}"
if ref in seen:
continue
seen.add(ref)
print(ref)
PY
)
total="${#mirror_refs[@]}"
if [[ "${total}" -eq 0 ]]; then
echo "No mirror refs found. Nothing to wait for."
exit 0
fi
echo "Fork pull_request detected; waiting for mirrored images from pull_request_target."
echo "Need ${total} mirror refs."
missing=()
for attempt in $(seq 1 "${max_attempts}"); do
missing=()
for ref in "${mirror_refs[@]}"; do
if ! skopeo inspect "docker://${ref}" >/dev/null 2>&1; then
missing+=("${ref}")
fi
done
if [[ "${#missing[@]}" -eq 0 ]]; then
echo "All mirrored images are available."
exit 0
fi
echo "[${attempt}/${max_attempts}] Missing ${#missing[@]}/${total} mirror refs. Waiting ${sleep_seconds}s..."
echo "Example missing: ${missing[0]}"
sleep "${sleep_seconds}"
done
echo "Timed out waiting for mirrored images." >&2
echo "Still missing (first 20):" >&2
for ref in "${missing[@]:0:20}"; do
echo " - ${ref}" >&2
done
exit 1
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
: "${MATRIX_DISTRO:?Missing MATRIX_DISTRO}"
: "${IMAGE_TAG:?Missing IMAGE_TAG}"
: "${GITHUB_REPOSITORY_OWNER:?Missing GITHUB_REPOSITORY_OWNER}"
image="ghcr.io/${GITHUB_REPOSITORY_OWNER}/infinito-${MATRIX_DISTRO}:${IMAGE_TAG}"
max_attempts="${MAX_WAIT_ATTEMPTS:-180}"
sleep_seconds="${WAIT_SLEEP_SECONDS:-10}"
echo "Fork pull_request detected; waiting for image built via pull_request_target:"
echo " ${image}"
for attempt in $(seq 1 "${max_attempts}"); do
if docker buildx imagetools inspect "${image}" >/dev/null 2>&1; then
echo "Image is available: ${image}"
exit 0
fi
echo "[${attempt}/${max_attempts}] Waiting for image..."
sleep "${sleep_seconds}"
done
echo "Timed out waiting for prebuilt image: ${image}" >&2
exit 1