feat(header): gate consolidated <unraid-header> on Unraid 7.3+ #971
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build PR Plugin | |
| on: | |
| pull_request: | |
| paths: | |
| - 'emhttp/**' | |
| - 'etc/**' | |
| - 'sbin/**' | |
| - 'share/**' | |
| - '.github/workflows/pr-plugin-build.yml' | |
| - '.github/scripts/**' | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| actions: read | |
| jobs: | |
| build-plugin: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Get changed files | |
| id: changed-files | |
| run: | | |
| set -euo pipefail | |
| # Get list of changed files in emhttp, etc, sbin, share using PR SHAs | |
| git diff --name-only "${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}" | grep -E '^(emhttp|etc|sbin|share)/' > changed_files.txt || true | |
| # Output for debugging | |
| echo "Changed files:" | |
| cat changed_files.txt | |
| # Check if we have any changes | |
| if [ ! -s "changed_files.txt" ]; then | |
| echo "No emhttp files changed" | |
| echo "has_changes=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "has_changes=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Generate plugin version | |
| if: steps.changed-files.outputs.has_changes == 'true' | |
| id: version | |
| run: | | |
| # Generate version with proper timestamp format: YYYY.MM.DD.HHMM | |
| VERSION=$(date -u +"%Y.%m.%d.%H%M") | |
| # Also keep PR info for reference | |
| PR_VERSION="pr.${{ github.event.pull_request.number }}.$(git rev-parse --short HEAD)" | |
| # Generate URLs with versioned TXZ to prevent SHA conflicts | |
| S3_BASE_URL="${{ secrets.CLOUDFLARE_PREVIEW_BUCKET_BASE_URL }}/pr-plugins/pr-${{ github.event.pull_request.number }}" | |
| # Define filenames | |
| LOCAL_TXZ_NAME="webgui-pr-${{ github.event.pull_request.number }}.tar.gz" | |
| REMOTE_TXZ_NAME="webgui-pr-${{ github.event.pull_request.number }}-${VERSION}.tar.gz" | |
| PLUGIN_NAME="webgui-pr-${{ github.event.pull_request.number }}.plg" | |
| # TXZ gets unique versioned path to prevent SHA conflicts on updates | |
| TXZ_URL="${S3_BASE_URL}/${REMOTE_TXZ_NAME}" | |
| PLUGIN_URL="${S3_BASE_URL}/${PLUGIN_NAME}" | |
| TXZ_KEY="pr-plugins/pr-${{ github.event.pull_request.number }}/${REMOTE_TXZ_NAME}" | |
| PLUGIN_KEY="pr-plugins/pr-${{ github.event.pull_request.number }}/${PLUGIN_NAME}" | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "pr_version=$PR_VERSION" >> $GITHUB_OUTPUT | |
| echo "local_txz=$LOCAL_TXZ_NAME" >> $GITHUB_OUTPUT | |
| echo "remote_txz=$REMOTE_TXZ_NAME" >> $GITHUB_OUTPUT | |
| echo "plugin_name=$PLUGIN_NAME" >> $GITHUB_OUTPUT | |
| echo "txz_url=$TXZ_URL" >> $GITHUB_OUTPUT | |
| echo "plugin_url=$PLUGIN_URL" >> $GITHUB_OUTPUT | |
| echo "txz_key=$TXZ_KEY" >> $GITHUB_OUTPUT | |
| echo "plugin_key=$PLUGIN_KEY" >> $GITHUB_OUTPUT | |
| echo "Generated version: $VERSION (PR: $PR_VERSION)" | |
| - name: Create plugin package | |
| if: steps.changed-files.outputs.has_changes == 'true' | |
| env: | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| run: | | |
| set -euo pipefail | |
| # Payload layout: | |
| # build/pr.patch unified diff of all changed TEXT files (system-relative paths) | |
| # build/binary/<path> whole copies of changed BINARY files (cannot be diffed) | |
| # build/binary_files.txt list of binary paths (system-relative) | |
| # The plugin applies pr.patch with `patch` so multiple PR plugins can | |
| # stack on the same file; binaries fall back to whole-file replace. | |
| mkdir -p build old new | |
| # Map a repo path to its system-relative install path (no leading slash), | |
| # matching the historical install layout. | |
| map_path() { | |
| case "$1" in | |
| etc/rc.d/*) echo "usr/local/etc/rc.d/${1#etc/rc.d/}" ;; | |
| etc/*) echo "$1" ;; | |
| sbin/*) echo "usr/local/sbin/${1#sbin/}" ;; | |
| share/*) echo "usr/local/share/${1#share/}" ;; | |
| emhttp/*) echo "usr/local/$1" ;; | |
| *) echo "" ;; | |
| esac | |
| } | |
| : > build/binary_files.txt | |
| : > build/text_files.txt | |
| has_text=0 | |
| while IFS= read -r file; do | |
| [ -n "$file" ] || continue | |
| sys=$(map_path "$file") | |
| if [ -z "$sys" ]; then echo "Skipping unsupported path: $file"; continue; fi | |
| # numstat reports "-\t-" for binary files (one line per single file) | |
| if git diff --numstat "$BASE_SHA" HEAD -- "$file" | grep -qP '^-\t-\t'; then | |
| if [ -f "$file" ]; then | |
| mkdir -p "build/binary/$(dirname "$sys")" | |
| cp "$file" "build/binary/$sys" | |
| echo "$sys" >> build/binary_files.txt | |
| echo "Binary: $file -> $sys" | |
| else | |
| echo "Skipping deleted binary (unsupported): $file" | |
| fi | |
| continue | |
| fi | |
| # Text: stage base version under old/, head version under new/ | |
| if git cat-file -e "$BASE_SHA:$file" 2>/dev/null; then | |
| mkdir -p "old/$(dirname "$sys")" | |
| git show "$BASE_SHA:$file" > "old/$sys" | |
| fi | |
| if [ -f "$file" ]; then | |
| mkdir -p "new/$(dirname "$sys")" | |
| cp "$file" "new/$sys" | |
| fi | |
| has_text=1 | |
| echo "$sys" >> build/text_files.txt | |
| echo "Text: $file -> $sys" | |
| done < changed_files.txt | |
| # Unified diff with old/<sys> and new/<sys> headers (apply with patch -p1 at /). | |
| # diff exits 1 when differences exist; only 2 is a real error. | |
| if [ "$has_text" -eq 1 ]; then | |
| diff -ruN old new > build/pr.patch || [ $? -eq 1 ] | |
| echo "pr.patch hunks: $(grep -c '^@@' build/pr.patch || echo 0)" | |
| # Ship the base (original) version of each text file so removal can | |
| # rebuild deterministically: restore originals + re-apply the other | |
| # still-installed PR plugins' patches. | |
| mkdir -p build/orig | |
| cp -a old/. build/orig/ 2>/dev/null || true | |
| fi | |
| # Record the intended mode of every changed file. Unified diffs carry | |
| # no permission bits, so `patch` creates new files with the umask | |
| # (0644) and drops the exec bit. The installer re-applies these modes | |
| # after patching/installing. | |
| : > build/modes.txt | |
| while IFS= read -r file; do | |
| [ -n "$file" ] || continue | |
| [ -f "$file" ] || continue | |
| sys=$(map_path "$file") | |
| [ -n "$sys" ] || continue | |
| printf '%s %s\n' "$(stat -c '%a' "$file")" "$sys" >> build/modes.txt | |
| done < changed_files.txt | |
| echo "modes.txt:"; cat build/modes.txt | |
| find build -type f | sed 's|^build/||' > file_list.txt | |
| echo "Payload contents:"; cat file_list.txt | |
| # Tarball the payload (extracted to a temp dir by the plugin, not to /) | |
| tar -czf "${{ steps.version.outputs.local_txz }}" -C build . | |
| echo "Tarball contents:" | |
| tar -tzf "${{ steps.version.outputs.local_txz }}" | |
| - name: Generate plugin file | |
| if: steps.changed-files.outputs.has_changes == 'true' | |
| run: | | |
| # Generate with placeholder URLs - will be updated by upload workflow | |
| bash .github/scripts/generate-pr-plugin.sh \ | |
| "${{ steps.version.outputs.version }}" \ | |
| "${{ github.event.pull_request.number }}" \ | |
| "$(git rev-parse --short HEAD)" \ | |
| "${{ steps.version.outputs.local_txz }}" \ | |
| "${{ steps.version.outputs.remote_txz }}" \ | |
| "PENDING_UPLOAD" \ | |
| "PENDING_UPLOAD" | |
| - name: Save metadata for upload workflow | |
| if: steps.changed-files.outputs.has_changes == 'true' | |
| run: | | |
| cat > pr-metadata.json << EOF | |
| { | |
| "pr_number": ${{ github.event.pull_request.number }}, | |
| "version": "${{ steps.version.outputs.version }}", | |
| "pr_version": "${{ steps.version.outputs.pr_version }}", | |
| "local_txz": "${{ steps.version.outputs.local_txz }}", | |
| "remote_txz": "${{ steps.version.outputs.remote_txz }}", | |
| "plugin_name": "${{ steps.version.outputs.plugin_name }}", | |
| "changed_files": $(cat changed_files.txt | jq -R -s -c 'split("\n") | map(select(length > 0))') | |
| } | |
| EOF | |
| echo "Metadata saved:" | |
| cat pr-metadata.json | |
| - name: Upload artifacts to GitHub | |
| if: steps.changed-files.outputs.has_changes == 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: webgui-pr-plugin-${{ github.event.pull_request.number }} | |
| path: | | |
| webgui-pr-*.plg | |
| webgui-pr-*.tar.gz | |
| pr-metadata.json | |
| changed_files.txt | |
| retention-days: 30 | |