From 5e4ec25ce6766ef8f198dbe32aeac90c88b76acc Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Mon, 30 Mar 2026 01:12:30 -0600 Subject: [PATCH 1/2] Consolidate CI workflows --- .github/workflows/build.yml | 196 +++++++++++++++++++++++++++- .github/workflows/security.yml | 73 ----------- .github/workflows/sync-template.yml | 60 --------- 3 files changed, 190 insertions(+), 139 deletions(-) delete mode 100644 .github/workflows/security.yml delete mode 100644 .github/workflows/sync-template.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index be4d278..cf2ac06 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,5 @@ -name: Build Sure-AIO +name: CI / Sure-AIO + on: push: branches: [ main ] @@ -7,7 +8,9 @@ on: - 'rootfs/**' - 'scripts/**' - 'upstream.toml' - - '.github/workflows/build.yml' + - 'sure-aio.xml' + - 'renovate.json' + - '.github/workflows/**' pull_request: branches: [ main ] paths: @@ -15,7 +18,9 @@ on: - 'rootfs/**' - 'scripts/**' - 'upstream.toml' - - '.github/workflows/build.yml' + - 'sure-aio.xml' + - 'renovate.json' + - '.github/workflows/**' workflow_dispatch: inputs: run_smoke_test: @@ -39,7 +44,82 @@ concurrency: cancel-in-progress: true jobs: + detect-changes: + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + build_related: ${{ steps.filter.outputs.build_related }} + xml_related: ${{ steps.filter.outputs.xml_related }} + renovate_related: ${{ steps.filter.outputs.renovate_related }} + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + + - name: Classify changed files + id: filter + env: + EVENT_NAME: ${{ github.event_name }} + BEFORE_SHA: ${{ github.event.before || '' }} + BASE_SHA: ${{ github.event.pull_request.base.sha || '' }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + GITHUB_SHA_VALUE: ${{ github.sha }} + run: | + if [[ "${EVENT_NAME}" == "workflow_dispatch" ]]; then + echo "build_related=true" >> "${GITHUB_OUTPUT}" + echo "xml_related=true" >> "${GITHUB_OUTPUT}" + echo "renovate_related=true" >> "${GITHUB_OUTPUT}" + exit 0 + fi + + if [[ "${EVENT_NAME}" == "pull_request" ]]; then + base="${BASE_SHA}" + head="${HEAD_SHA}" + else + base="${BEFORE_SHA}" + head="${GITHUB_SHA_VALUE}" + fi + + if [[ -z "${base}" || "${base}" =~ ^0+$ ]]; then + changed_files="$(git show --pretty='' --name-only "${head}")" + else + changed_files="$(git diff --name-only "${base}" "${head}")" + fi + + printf 'Changed files:\n%s\n' "${changed_files}" + + build_related=false + xml_related=false + renovate_related=false + + while IFS= read -r path; do + [[ -z "${path}" ]] && continue + case "${path}" in + Dockerfile|upstream.toml|rootfs/*|scripts/*) + build_related=true + ;; + sure-aio.xml) + xml_related=true + ;; + renovate.json) + renovate_related=true + ;; + .github/workflows/*) + build_related=true + renovate_related=true + ;; + esac + done <<< "${changed_files}" + + echo "build_related=${build_related}" >> "${GITHUB_OUTPUT}" + echo "xml_related=${xml_related}" >> "${GITHUB_OUTPUT}" + echo "renovate_related=${renovate_related}" >> "${GITHUB_OUTPUT}" + validate-template: + needs: detect-changes + if: ${{ needs.detect-changes.outputs.build_related == 'true' || needs.detect-changes.outputs.xml_related == 'true' || needs.detect-changes.outputs.renovate_related == 'true' }} runs-on: ubuntu-latest permissions: contents: read @@ -48,6 +128,7 @@ jobs: uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Validate shell and python scripts + if: ${{ needs.detect-changes.outputs.build_related == 'true' }} run: | bash -n scripts/smoke-test.sh PYTHONPYCACHEPREFIX=/tmp/sure-aio-pyc python3 -m py_compile scripts/check-upstream.py @@ -56,6 +137,7 @@ jobs: find rootfs -type f -path '*/up' -print0 | xargs -0 -n1 sh -n - name: Validate XML + if: ${{ needs.detect-changes.outputs.build_related == 'true' || needs.detect-changes.outputs.xml_related == 'true' }} run: | python3 - <<'PY' import xml.etree.ElementTree as ET @@ -63,9 +145,63 @@ jobs: print('sure-aio.xml parsed successfully') PY + - name: Validate Renovate config + if: ${{ needs.detect-changes.outputs.renovate_related == 'true' }} + run: npx --yes --package renovate renovate-config-validator renovate.json + + pinned-actions: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Enforce pinned action SHAs + run: | + python3 - <<'PY' + import pathlib + import re + import sys + + workflow_dir = pathlib.Path(".github/workflows") + pattern = re.compile(r"^\s*uses:\s*([^@\s]+)@([^\s#]+)") + sha_pattern = re.compile(r"^[0-9a-f]{40}$") + failures = [] + + for path in sorted(workflow_dir.glob("*.yml")): + for lineno, line in enumerate(path.read_text().splitlines(), start=1): + match = pattern.match(line) + if not match: + continue + target, ref = match.groups() + if target.startswith("./"): + continue + if not sha_pattern.fullmatch(ref): + failures.append(f"{path}:{lineno}: action is not pinned to a full SHA -> {line.strip()}") + + if failures: + print("\n".join(failures), file=sys.stderr) + sys.exit(1) + print("All workflow actions are pinned to full commit SHAs.") + PY + + dependency-review: + if: ${{ github.event_name == 'pull_request' }} + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Dependency review + uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 + smoke-test: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_smoke_test == true }} - needs: validate-template + if: ${{ needs.detect-changes.outputs.build_related == 'true' && (github.event_name != 'workflow_dispatch' || inputs.run_smoke_test == true) }} + needs: + - detect-changes + - validate-template + - pinned-actions runs-on: ubuntu-latest permissions: contents: read @@ -104,9 +240,11 @@ jobs: docker logs sure-aio-smoke || true publish: - if: ${{ github.event_name != 'pull_request' && ((github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && inputs.publish_image == true)) }} + if: ${{ needs.detect-changes.outputs.build_related == 'true' && github.event_name != 'pull_request' && ((github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && inputs.publish_image == true)) }} needs: + - detect-changes - validate-template + - pinned-actions - smoke-test runs-on: ubuntu-latest permissions: @@ -172,3 +310,49 @@ jobs: org.opencontainers.image.version=${{ steps.prep.outputs.upstream_version || '' }} io.jsonbored.upstream.name=Sure load: false + + sync-awesome-unraid: + if: ${{ needs.detect-changes.outputs.xml_related == 'true' && github.event_name == 'push' && github.ref == 'refs/heads/main' }} + needs: + - detect-changes + - validate-template + - pinned-actions + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Check sync configuration + env: + SYNC_TOKEN: ${{ secrets.SYNC_TOKEN }} + run: | + if [[ -z "${SYNC_TOKEN}" ]]; then + echo "SYNC_ENABLED=false" >> "${GITHUB_ENV}" + echo "SYNC_TOKEN is not configured; skipping sync." + else + echo "SYNC_ENABLED=true" >> "${GITHUB_ENV}" + fi + + - name: Checkout Source Repository + if: ${{ env.SYNC_ENABLED == 'true' }} + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Checkout Target Repository + if: ${{ env.SYNC_ENABLED == 'true' }} + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + repository: JSONbored/awesome-unraid + token: ${{ secrets.SYNC_TOKEN }} + path: target-repo + + - name: Copy and Commit Template + if: ${{ env.SYNC_ENABLED == 'true' }} + run: | + cp sure-aio.xml target-repo/sure-aio.xml + cd target-repo + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + git add sure-aio.xml + + git diff --quiet && git diff --staged --quiet || (git commit -m "chore: auto-sync sure-aio.xml from upstream" && git push) diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml deleted file mode 100644 index a325f2b..0000000 --- a/.github/workflows/security.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: Workflow And Dependency Security - -on: - push: - branches: [ main ] - paths: - - '.github/workflows/**' - - 'Dockerfile' - - 'renovate.json' - - 'upstream.toml' - pull_request: - branches: [ main ] - paths: - - '.github/workflows/**' - - 'Dockerfile' - - 'renovate.json' - - 'upstream.toml' - workflow_dispatch: - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - pinned-actions: - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - name: Enforce pinned action SHAs - run: | - python3 - <<'PY' - import pathlib - import re - import sys - - workflow_dir = pathlib.Path(".github/workflows") - pattern = re.compile(r"^\s*uses:\s*([^@\s]+)@([^\s#]+)") - sha_pattern = re.compile(r"^[0-9a-f]{40}$") - failures = [] - - for path in sorted(workflow_dir.glob("*.yml")): - for lineno, line in enumerate(path.read_text().splitlines(), start=1): - match = pattern.match(line) - if not match: - continue - target, ref = match.groups() - if target.startswith("./"): - continue - if not sha_pattern.fullmatch(ref): - failures.append(f"{path}:{lineno}: action is not pinned to a full SHA -> {line.strip()}") - - if failures: - print("\n".join(failures), file=sys.stderr) - sys.exit(1) - print("All workflow actions are pinned to full commit SHAs.") - PY - - dependency-review: - if: ${{ github.event_name == 'pull_request' }} - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - name: Dependency review - uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 diff --git a/.github/workflows/sync-template.yml b/.github/workflows/sync-template.yml deleted file mode 100644 index 6ecec19..0000000 --- a/.github/workflows/sync-template.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Sync Template to Awesome-Unraid - -on: - push: - paths: - - 'sure-aio.xml' - - '.github/workflows/sync-template.yml' - branches: - - main - workflow_dispatch: - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - sync: - runs-on: ubuntu-latest - steps: - - name: Check sync configuration - env: - SYNC_TOKEN: ${{ secrets.SYNC_TOKEN }} - run: | - if [[ -z "${SYNC_TOKEN}" ]]; then - echo "SYNC_ENABLED=false" >> "${GITHUB_ENV}" - echo "SYNC_TOKEN is not configured; skipping sync." - else - echo "SYNC_ENABLED=true" >> "${GITHUB_ENV}" - fi - - - name: Checkout Source Repository - if: ${{ env.SYNC_ENABLED == 'true' }} - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - name: Checkout Target Repository - if: ${{ env.SYNC_ENABLED == 'true' }} - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - repository: JSONbored/awesome-unraid - token: ${{ secrets.SYNC_TOKEN }} - path: target-repo - - - name: Copy and Commit Template - if: ${{ env.SYNC_ENABLED == 'true' }} - run: | - cp sure-aio.xml target-repo/sure-aio.xml - cd target-repo - - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - git add sure-aio.xml - - git diff --quiet && git diff --staged --quiet || (git commit -m "chore: auto-sync sure-aio.xml from upstream" && git push) From d118175aaaa52659086a1538c8b0ea6fff81f651 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Mon, 30 Mar 2026 01:18:55 -0600 Subject: [PATCH 2/2] Harden CI change detection --- .github/workflows/build.yml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cf2ac06..8ffa15f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -82,10 +82,30 @@ jobs: head="${GITHUB_SHA_VALUE}" fi + if ! git rev-parse --git-dir >/dev/null 2>&1; then + echo "Git metadata is unavailable; treating the workflow as fully impacted." + echo "build_related=true" >> "${GITHUB_OUTPUT}" + echo "xml_related=true" >> "${GITHUB_OUTPUT}" + echo "renovate_related=true" >> "${GITHUB_OUTPUT}" + exit 0 + fi + if [[ -z "${base}" || "${base}" =~ ^0+$ ]]; then - changed_files="$(git show --pretty='' --name-only "${head}")" + if ! changed_files="$(git show --pretty='' --name-only "${head}" 2>/dev/null)"; then + echo "Unable to inspect commit range; treating the workflow as fully impacted." + echo "build_related=true" >> "${GITHUB_OUTPUT}" + echo "xml_related=true" >> "${GITHUB_OUTPUT}" + echo "renovate_related=true" >> "${GITHUB_OUTPUT}" + exit 0 + fi else - changed_files="$(git diff --name-only "${base}" "${head}")" + if ! changed_files="$(git diff --name-only "${base}" "${head}" 2>/dev/null)"; then + echo "Unable to diff commit range; treating the workflow as fully impacted." + echo "build_related=true" >> "${GITHUB_OUTPUT}" + echo "xml_related=true" >> "${GITHUB_OUTPUT}" + echo "renovate_related=true" >> "${GITHUB_OUTPUT}" + exit 0 + fi fi printf 'Changed files:\n%s\n' "${changed_files}"