Skip to content
Merged
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
69 changes: 69 additions & 0 deletions .codex/coderabbit-fixes-wip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# CodeRabbit Fixes WIP

## Context

- Repo: unraid/api
- Branch: codex/fix-forked-plugin-publish
- PR: 2014
- PR URL: https://github.com/unraid/api/pull/2014
- Generated at: 2026-05-20T16:03:41Z

## Inputs Pulled

- [x] Unresolved CodeRabbit review threads pulled
- [x] Top-level CodeRabbit review notes pulled
- [x] Top-level actionable review-body comments extracted into queue

## Fix Queue

| Item ID | Type | File | Line | Summary | Status | Link | Evidence |
| --- | --- | --- | --- | --- | --- | --- | --- |
| CR-001 | thread | .github/workflows/upload-pr-plugin.yml | 31 | Pin mutable `uses:` action refs to immutable SHAs. | DONE | https://github.com/unraid/api/pull/2014#discussion_r3275010283 | `ruby -e 'require "yaml"; YAML.load_file(".github/workflows/upload-pr-plugin.yml")'`; `rg -nP '^\\s*uses:\\s*[^@]+@(?!(?:[a-f0-9]{40})$).+' .github/workflows/upload-pr-plugin.yml` returned no matches. |
| EXT-001 | thread | .github/workflows/upload-pr-plugin.yml | 15 | Make workflow_run concurrency fallback unique when PR number is unavailable. | DONE | https://github.com/unraid/api/pull/2014#discussion_r3275030567 | `ruby -e 'require "yaml"; YAML.load_file(".github/workflows/upload-pr-plugin.yml")'`. |
| CR-002 | thread | .github/workflows/push-staging-pr-on-close.yml | 50 | Avoid direct template interpolation of `inputs.pr_number` in shell. | DONE | https://github.com/unraid/api/pull/2014#discussion_r3275031746 | `ruby -e 'require "yaml"; YAML.load_file(".github/workflows/push-staging-pr-on-close.yml")'`; direct `inputs.pr_number` use is limited to step `env`. |
| RVW-001 | review-body | top-level | n/a | Top-level review repeats CR-001 action pinning request. | DONE | https://github.com/unraid/api/pull/2014#pullrequestreview-4329727578 | Covered by CR-001. |
| RVW-002 | review-body | top-level | n/a | Top-level review repeats CR-002 and notes misleading `sed` `.*?` regex. | DONE | https://github.com/unraid/api/pull/2014#pullrequestreview-4329754125 | `ruby -e 'require "yaml"; YAML.load_file(".github/workflows/push-staging-pr-on-close.yml")'`; `rg -n '\\.\\*\\?' .github/workflows/push-staging-pr-on-close.yml` returned no matches. |
| EXT-002 | follow-up | .github/workflows/push-staging-pr-on-close.yml | 197 | Pin close-out comment action because workflow now runs in privileged `pull_request_target` context. | DONE | n/a | `rg -nP '^\\s*uses:\\s*[^@]+@(?!(?:[a-f0-9]{40})$).+' .github/workflows/upload-pr-plugin.yml .github/workflows/push-staging-pr-on-close.yml` returned no matches. |

## Execution Log

### 1. Item: CR-001
- Action: Pin new upload workflow action refs to full SHAs.
- Validation: `ruby -e 'require "yaml"; YAML.load_file(".github/workflows/upload-pr-plugin.yml")'`; `rg -nP '^\s*uses:\s*[^@]+@(?!(?:[a-f0-9]{40})$).+' .github/workflows/upload-pr-plugin.yml`.
- Result: Passed; no mutable action refs remain in `.github/workflows/upload-pr-plugin.yml`.

### 2. Item: EXT-001
- Action: Use `github.event.workflow_run.id` as the no-PR-number concurrency fallback.
- Validation: `ruby -e 'require "yaml"; YAML.load_file(".github/workflows/upload-pr-plugin.yml")'`.
- Result: Passed; fallback is unique per workflow run.

### 3. Item: CR-002
- Action: Move workflow_dispatch PR number into a step environment variable and read it as shell data before validation.
- Validation: `ruby -e 'require "yaml"; YAML.load_file(".github/workflows/push-staging-pr-on-close.yml")'`; inspected direct `inputs.pr_number` occurrences.
- Result: Passed; the user-controlled input is no longer interpolated directly into the shell script body.

### 4. Item: RVW-001
- Action: Marked duplicate top-level review-body item as covered by CR-001.
- Validation: Same evidence as CR-001.
- Result: Done.

### 5. Item: RVW-002
- Action: Replace misleading `.*?` sed pattern with a quoted-string character class.
- Validation: `ruby -e 'require "yaml"; YAML.load_file(".github/workflows/push-staging-pr-on-close.yml")'`; `rg -n '\.\*\?' .github/workflows/push-staging-pr-on-close.yml`.
- Result: Passed; no misleading `.*?` remains.

### 6. Item: EXT-002
- Action: Pin `thollander/actions-comment-pull-request` to the current `v3` SHA.
- Validation: `ruby -e 'require "yaml"; ARGV.each { |f| YAML.load_file(f) }' .github/workflows/upload-pr-plugin.yml .github/workflows/push-staging-pr-on-close.yml`; targeted mutable-action scan.
- Result: Passed; no mutable action refs remain in the two changed workflows.

## Final Checks

- [x] Queue reviewed: no `TODO` left
- [x] Remaining `BLOCKED` items documented with reason
- [x] Re-pulled CodeRabbit threads and reviews
- [x] No unhandled top-level review-body comment remains

Final evidence:
- `coderabbit-review-data final 2014` returned `unresolved_coderabbit_threads 0`.
- Resolved fixed review threads `PRRT_kwDOC2VmQM6DhuxC`, `PRRT_kwDOC2VmQM6DhyZ8`, and `PRRT_kwDOC2VmQM6Dhym0`.
54 changes: 29 additions & 25 deletions .github/workflows/build-plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ on:
description: "Whether to automatically trigger the release-production workflow (default: false)"
secrets:
CF_ACCESS_KEY_ID:
required: true
required: false
CF_SECRET_ACCESS_KEY:
required: true
required: false
CF_BUCKET_PREVIEW:
required: true
required: false
CF_ENDPOINT:
required: true
required: false
UNRAID_BOT_GITHUB_ADMIN_TOKEN:
required: false
jobs:
Expand Down Expand Up @@ -144,7 +144,7 @@ jobs:
- name: Upload to GHA
uses: actions/upload-artifact@v6
with:
name: unraid-plugin-${{ github.run_id }}-${{ inputs.RELEASE_TAG }}
name: unraid-plugin-${{ github.run_id }}-${{ inputs.TAG || inputs.RELEASE_TAG || 'build' }}
path: plugin/deploy/

- name: Upload Release Assets
Expand All @@ -168,50 +168,54 @@ jobs:
token: ${{ secrets.UNRAID_BOT_GITHUB_ADMIN_TOKEN }}

- name: Upload to Cloudflare
if: inputs.RELEASE_CREATED == 'false'
if: inputs.RELEASE_CREATED == 'false' && github.event_name != 'pull_request'
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CF_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
AWS_EC2_METADATA_DISABLED: true
AWS_SHARED_CREDENTIALS_FILE: /dev/null
AWS_CONFIG_FILE: /dev/null
CF_BUCKET_PREVIEW: ${{ secrets.CF_BUCKET_PREVIEW }}
CF_ENDPOINT: ${{ secrets.CF_ENDPOINT }}
BUCKET_PATH: ${{ inputs.BUCKET_PATH }}
run: |
: "${CF_BUCKET_PREVIEW:?CF_BUCKET_PREVIEW secret is required}"
: "${CF_ENDPOINT:?CF_ENDPOINT secret is required}"
: "${BUCKET_PATH:?BUCKET_PATH input is required}"

# Sync the deploy directory to the Cloudflare bucket with explicit content encoding and public-read ACL
aws s3 sync deploy/ s3://${{ secrets.CF_BUCKET_PREVIEW }}/${{ inputs.BUCKET_PATH }} \
--endpoint-url ${{ secrets.CF_ENDPOINT }} \
aws s3 sync deploy/ "s3://${CF_BUCKET_PREVIEW}/${BUCKET_PATH}" \
--endpoint-url "${CF_ENDPOINT}" \
--checksum-algorithm CRC32 \
--no-guess-mime-type \
--content-encoding none \
--acl public-read

- name: Comment URL
if: github.event_name == 'pull_request'
uses: thollander/actions-comment-pull-request@v3
with:
comment-tag: prlink
mode: recreate
message: |
This plugin has been deployed to Cloudflare R2 and is available for testing.
Download it at this URL:
```
${{ inputs.BASE_URL }}/tag/${{ inputs.TAG }}/dynamix.unraid.net.plg
```

- name: Clean up old preview builds
if: inputs.RELEASE_CREATED == 'false' && github.event_name == 'push'
continue-on-error: true
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CF_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
AWS_EC2_METADATA_DISABLED: true
AWS_SHARED_CREDENTIALS_FILE: /dev/null
AWS_CONFIG_FILE: /dev/null
CF_BUCKET_PREVIEW: ${{ secrets.CF_BUCKET_PREVIEW }}
CF_ENDPOINT: ${{ secrets.CF_ENDPOINT }}
run: |
echo "🧹 Cleaning up old preview builds (keeping last 7 days)..."
: "${CF_BUCKET_PREVIEW:?CF_BUCKET_PREVIEW secret is required}"
: "${CF_ENDPOINT:?CF_ENDPOINT secret is required}"

# Calculate cutoff date (7 days ago)
CUTOFF_DATE=$(date -d "7 days ago" +"%Y.%m.%d")
echo "Deleting builds older than: ${CUTOFF_DATE}"

# List and delete old timestamped .txz files
OLD_FILES=$(aws s3 ls "s3://${{ secrets.CF_BUCKET_PREVIEW }}/unraid-api/" \
--endpoint-url ${{ secrets.CF_ENDPOINT }} --recursive | \
OLD_FILES=$(aws s3 ls "s3://${CF_BUCKET_PREVIEW}/unraid-api/" \
--endpoint-url "${CF_ENDPOINT}" --recursive | \
grep -E "dynamix\.unraid\.net-[0-9]{4}\.[0-9]{2}\.[0-9]{2}\.[0-9]{4}\.txz" | \
awk '{print $4}' || true)

Expand All @@ -222,8 +226,8 @@ jobs:
FILE_DATE="${BASH_REMATCH[1]}"
if [[ "$FILE_DATE" < "$CUTOFF_DATE" ]]; then
echo "Deleting old build: $(basename "$file")"
aws s3 rm "s3://${{ secrets.CF_BUCKET_PREVIEW }}/${file}" \
--endpoint-url ${{ secrets.CF_ENDPOINT }} || true
aws s3 rm "s3://${CF_BUCKET_PREVIEW}/${file}" \
--endpoint-url "${CF_ENDPOINT}" || true
((DELETED_COUNT++))
fi
fi
Expand Down
132 changes: 103 additions & 29 deletions .github/workflows/push-staging-pr-on-close.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name: Replace PR Plugin with Staging Redirect on Merge
# update to the staging version on their next update check.

on:
pull_request:
pull_request_target:
types:
- closed
workflow_dispatch:
Expand All @@ -23,40 +23,88 @@ on:

jobs:
push-staging-redirect:
if: (github.event_name == 'pull_request' && github.event.pull_request.merged == true) || (github.event_name == 'workflow_dispatch' && inputs.pr_merged == true)
if: (github.event_name == 'pull_request_target' && github.event.pull_request.merged == true) || (github.event_name == 'workflow_dispatch' && inputs.pr_merged == true)
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
pull-requests: write
steps:
- name: Set PR number
id: pr_number
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_PR_NUMBER: ${{ inputs.pr_number }}
PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
run: |
if [ "${{ github.event_name }}" == "pull_request" ]; then
echo "pr_number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
set -Eeuo pipefail
IFS=$'\n\t'

if [ "$EVENT_NAME" = "pull_request_target" ]; then
PR_NUMBER="$PULL_REQUEST_NUMBER"
else
echo "pr_number=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT
PR_NUMBER="$INPUT_PR_NUMBER"
fi

- name: Download artifact
uses: dawidd6/action-download-artifact@v11
with:
name_is_regexp: true
name: unraid-plugin-.*
path: connect-files
pr: ${{ steps.pr_number.outputs.pr_number }}
workflow: main.yml
workflow_conclusion: success
search_artifacts: true
if_no_artifact_found: fail
if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
echo "Error: PR number '$PR_NUMBER' is not numeric" >&2
exit 1
fi

echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"

- name: Download PR plugin from Cloudflare
id: download_pr_plugin
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CF_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
AWS_EC2_METADATA_DISABLED: true
AWS_SHARED_CREDENTIALS_FILE: /dev/null
AWS_CONFIG_FILE: /dev/null
CF_BUCKET_PREVIEW: ${{ secrets.CF_BUCKET_PREVIEW }}
CF_ENDPOINT: ${{ secrets.CF_ENDPOINT }}
PR_NUMBER: ${{ steps.pr_number.outputs.pr_number }}
run: |
set -Eeuo pipefail
IFS=$'\n\t'

: "${CF_BUCKET_PREVIEW:?CF_BUCKET_PREVIEW secret is required}"
: "${CF_ENDPOINT:?CF_ENDPOINT secret is required}"
: "${PR_NUMBER:?PR_NUMBER is required}"

PR_PREFIX="unraid-api/tag/PR${PR_NUMBER}"
PLUGIN_KEY="${PR_PREFIX}/dynamix.unraid.net.plg"

if ! [[ "$PLUGIN_KEY" =~ ^unraid-api/tag/PR[0-9]+/dynamix\.unraid\.net\.plg$ ]]; then
echo "Error: Invalid plugin key '$PLUGIN_KEY'" >&2
exit 1
fi

mkdir -p pr-release

if ! aws s3api head-object \
--bucket "$CF_BUCKET_PREVIEW" \
--key "$PLUGIN_KEY" \
--endpoint-url "$CF_ENDPOINT" > /dev/null 2>&1; then
echo "No PR plugin found at s3://${CF_BUCKET_PREVIEW}/${PLUGIN_KEY}; nothing to close out."
echo "found=false" >> "$GITHUB_OUTPUT"
exit 0
fi

aws s3 cp "s3://${CF_BUCKET_PREVIEW}/${PLUGIN_KEY}" pr-release/dynamix.unraid.net.plg \
--endpoint-url "$CF_ENDPOINT"

echo "found=true" >> "$GITHUB_OUTPUT"

- name: Update Downloaded Plugin to Redirect to Staging
if: steps.download_pr_plugin.outputs.found == 'true'
run: |
# Find the .plg file in the downloaded artifact
plgfile=$(find connect-files -name "*.plg" -type f | head -1)
plgfile="pr-release/dynamix.unraid.net.plg"
if [ ! -f "$plgfile" ]; then
echo "ERROR: .plg file not found in connect-files/"
ls -la connect-files/
echo "ERROR: .plg file not found in pr-release/"
ls -la pr-release/
exit 1
fi

Expand All @@ -75,52 +123,78 @@ jobs:

# Change the plugin url to point to staging - users will switch to staging on next update
url="https://preview.dl.unraid.net/unraid-api/dynamix.unraid.net.plg"
sed -i -E "s#(<!ENTITY plugin_url \").*?(\">)#\1${url}\2#g" "${plgfile}" || exit 1
sed -i -E "s#(<!ENTITY plugin_url \")[^\"]*(\">)#\1${url}\2#g" "${plgfile}" || exit 1

echo "Modified plugin to redirect to: ${url}"
echo "Version bumped from ${current_version} to ${new_version}"

mkdir -p pr-release
mv "${plgfile}" pr-release/dynamix.unraid.net.plg

- name: Clean up old PR artifacts from Cloudflare
if: steps.download_pr_plugin.outputs.found == 'true'
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CF_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
AWS_EC2_METADATA_DISABLED: true
AWS_SHARED_CREDENTIALS_FILE: /dev/null
AWS_CONFIG_FILE: /dev/null
CF_BUCKET_PREVIEW: ${{ secrets.CF_BUCKET_PREVIEW }}
CF_ENDPOINT: ${{ secrets.CF_ENDPOINT }}
PR_NUMBER: ${{ steps.pr_number.outputs.pr_number }}
run: |
set -Eeuo pipefail
IFS=$'\n\t'

: "${CF_BUCKET_PREVIEW:?CF_BUCKET_PREVIEW secret is required}"
: "${CF_ENDPOINT:?CF_ENDPOINT secret is required}"
: "${PR_NUMBER:?PR_NUMBER is required}"

# Delete all existing files in the PR directory first (txz, plg, etc.)
aws s3 rm s3://${{ secrets.CF_BUCKET_PREVIEW }}/unraid-api/tag/PR${{ steps.pr_number.outputs.pr_number }}/ \
aws s3 rm "s3://${CF_BUCKET_PREVIEW}/unraid-api/tag/PR${PR_NUMBER}/" \
--recursive \
--endpoint-url ${{ secrets.CF_ENDPOINT }}
--endpoint-url "$CF_ENDPOINT"

echo "✅ Cleaned up old PR artifacts"

- name: Upload PR Redirect Plugin to Cloudflare
if: steps.download_pr_plugin.outputs.found == 'true'
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CF_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
AWS_EC2_METADATA_DISABLED: true
AWS_SHARED_CREDENTIALS_FILE: /dev/null
AWS_CONFIG_FILE: /dev/null
CF_BUCKET_PREVIEW: ${{ secrets.CF_BUCKET_PREVIEW }}
CF_ENDPOINT: ${{ secrets.CF_ENDPOINT }}
PR_NUMBER: ${{ steps.pr_number.outputs.pr_number }}
run: |
set -Eeuo pipefail
IFS=$'\n\t'

: "${CF_BUCKET_PREVIEW:?CF_BUCKET_PREVIEW secret is required}"
: "${CF_ENDPOINT:?CF_ENDPOINT secret is required}"
: "${PR_NUMBER:?PR_NUMBER is required}"

# Upload only the redirect plugin file
aws s3 cp pr-release/dynamix.unraid.net.plg \
s3://${{ secrets.CF_BUCKET_PREVIEW }}/unraid-api/tag/PR${{ steps.pr_number.outputs.pr_number }}/dynamix.unraid.net.plg \
--endpoint-url ${{ secrets.CF_ENDPOINT }} \
"s3://${CF_BUCKET_PREVIEW}/unraid-api/tag/PR${PR_NUMBER}/dynamix.unraid.net.plg" \
--endpoint-url "$CF_ENDPOINT" \
--content-encoding none \
--acl public-read

echo "✅ Uploaded redirect plugin"

- name: Output redirect information
if: steps.download_pr_plugin.outputs.found == 'true'
run: |
echo "✅ PR plugin replaced with staging redirect version"
echo "PR URL remains: https://preview.dl.unraid.net/unraid-api/tag/PR${{ steps.pr_number.outputs.pr_number }}/dynamix.unraid.net.plg"
echo "Redirects users to staging: https://preview.dl.unraid.net/unraid-api/dynamix.unraid.net.plg"
echo "Users updating from this PR version will automatically switch to staging"

- name: Comment on PR about staging redirect
if: github.event_name == 'pull_request'
uses: thollander/actions-comment-pull-request@v3
if: github.event_name == 'pull_request_target' && steps.download_pr_plugin.outputs.found == 'true'
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b
with:
comment-tag: pr-closed-staging
mode: recreate
Expand Down
Loading
Loading