Skip to content

Keep seeded installer artifacts latest stable only#26

Merged
elibosley merged 1 commit into
mainfrom
codex/stable-latest-seeded-release
May 20, 2026
Merged

Keep seeded installer artifacts latest stable only#26
elibosley merged 1 commit into
mainfrom
codex/stable-latest-seeded-release

Conversation

@elibosley

@elibosley elibosley commented May 20, 2026

Copy link
Copy Markdown
Member

Summary

  • restrict the Unraid release-lock updater to the stable release channel
  • record channel: stable in the release lock and enforce it during seeded image builds
  • after publishing the newest stable release, delete bundled seeded IMG assets from older Installer-* releases
  • keep older release online installer assets intact
  • update docs to describe the latest-stable seeded image policy

Why

We only want to publish bundled Unraid OS installer images for the current stable release. Older releases should remain available through the online installer path, and non-stable metadata entries must not trigger public bundled image artifacts.

Validation

  • stable selector unit snippet skips a newer non-stable metadata candidate
  • python3 -m json.tool build/unraid-release-lock.json
  • python3 -m py_compile scripts/update-unraid-release-lock.py
  • Ruby YAML parse for .github/workflows/update-unraid-release-lock.yml
  • shellcheck scripts/build-usb-native.sh
  • git diff --check

Summary by CodeRabbit

  • New Features

    • Seeded builds now automatically use a pinned stable Unraid OS release.
  • Chores

    • Enhanced release management to automatically remove older bundled installer assets from previous releases when new versions are published.
  • Documentation

    • Updated documentation explaining how seeded builds utilize pinned releases and the automated asset cleanup process.

Review Change Stack

- Restrict the Unraid release-lock updater to the stable release channel.\n- Previously, release candidates or other metadata channels could be considered if they appeared in USB Creator metadata with a ZIP URL.\n- That risked publishing bundled installer artifacts for non-stable or unintended older release lines.\n- The lock now records its channel, the updater only selects stable releases, and seeded image builds validate that the lock and URL are stable.\n- After publishing the latest stable release, the automation removes bundled seeded IMG assets from older Installer-* releases while leaving their online installer assets available.\n- Documentation now states that only the latest stable release keeps a bundled seeded image.
@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR extends the Unraid release lock system to enforce stable channel pinning. The updater script now accepts --channel stable, validates release URLs contain the channel path segment, and stores channel metadata in the lock file. Build and workflow scripts validate that locked releases target the stable channel. The workflow adds cleanup logic to remove old bundled seeded assets from prior releases after publishing newer stable versions.

Changes

Channel-aware Stable Release Pinning

Layer / File(s) Summary
Channel-aware release updater script
scripts/update-unraid-release-lock.py
Introduces DEFAULT_CHANNEL = "stable", extends validate_release_url to accept and enforce channel in URL path, updates latest_release function to filter releases by channel and skip invalid candidates, extends lock payload with channel fields at source.channel and top level, and adds --channel CLI argument with default and constraint.
Workflow lock generation and validation
.github/workflows/update-unraid-release-lock.yml
Workflow invokes updater with --channel stable, strengthens release lock validation to require and enforce channel == "stable", and parses URL path to verify it targets the stable download endpoint (/dl/stable/...).
Build script lock validation
scripts/build-usb-native.sh
Embedded Python validation now requires lock JSON channel field, enforces channel == "stable", and validates URL path contains the stable download segment with explicit error messages on failure.
Cleanup of old bundled assets
.github/workflows/update-unraid-release-lock.yml
Adds workflow function that enumerates prior Installer-* releases, identifies older semantic versions, and deletes older bundled seeded assets (install-user.img.zip, install-user.img.zip.md5) from those releases while commenting on each deletion.
Manifest and documentation
build/unraid-release-lock.json, docs/USER_COMMANDS.md, docs/user-build.md
Lock manifest updated with minimum_version: "7.3.0" and new timestamp; documentation clarifies that seeded IMG builds use the pinned stable release lock, describe the update workflow, and explain removal of bundled assets from older releases after publishing new stable images.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • unraid/unraid-installer#21: Initial introduction of the update-unraid-release-lock.yml workflow and Python updater script infrastructure that this PR extends with channel-aware pinning.
  • unraid/unraid-installer#25: Concurrent modification of the same release-lock pipeline code (scripts/update-unraid-release-lock.py, scripts/build-usb-native.sh, .github/workflows/update-unraid-release-lock.yml), adding URL hash verification enforcement alongside channel enforcement.

Poem

🐰 A rabbit hops through channels stable,
Pinning releases, now it's able!
Old assets cleaned, the workflow's bright,
Seeded images locked just right! ✨


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Title check ❌ Error PR title does not follow conventional commit format (fix:/feat:/etc.); it uses descriptive prose instead of the required prefix. Rewrite title using conventional commit format, e.g., 'feat: keep seeded installer artifacts latest stable only' or 'feat(ci): restrict seeded artifacts to latest stable release'.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with 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.

Inline comments:
In @.github/workflows/update-unraid-release-lock.yml:
- Around line 273-276: The gh release list call that populates release_tags is
capped by "--limit 100", causing older releases to be missed; remove the
"--limit 100" and use gh's pagination (e.g. add "--paginate") so the command: gh
release list --paginate --json tagName,isDraft,isPrerelease --jq '... |
.tagName' returns all releases for the subsequent Installer-* filtering; update
the invocation that sets release_tags accordingly (referencing the release_tags
assignment and the gh release list invocation).

In `@scripts/update-unraid-release-lock.py`:
- Around line 96-99: The code currently swallows ValueError from
validate_release_url(url, version, channel) which allows malformed stable
candidates to be ignored; change the except handler so that if the candidate is
a stable entry (detect via channel == "stable" or url containing "/dl/stable/")
you do not continue silently but surface the error (re-raise the ValueError or
log and exit) including the url, version and channel in the message, otherwise
keep the existing continue behavior for non-stable channels; update the
try/except around validate_release_url to implement this logic so malformed
stable metadata fails fast instead of quietly keeping an older release.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro (Legacy)

Run ID: bb3b884b-8f1b-47e1-9b66-3f219f1481fa

📥 Commits

Reviewing files that changed from the base of the PR and between 765d15f and 3a97092.

📒 Files selected for processing (6)
  • .github/workflows/update-unraid-release-lock.yml
  • build/unraid-release-lock.json
  • docs/USER_COMMANDS.md
  • docs/user-build.md
  • scripts/build-usb-native.sh
  • scripts/update-unraid-release-lock.py

Comment on lines +273 to +276
release_tags="$(gh release list \
--limit 100 \
--json tagName,isDraft,isPrerelease \
--jq '.[] | select(.isDraft == false and .isPrerelease == false) | .tagName')"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/update-unraid-release-lock.yml | sed -n '260,290p'

Repository: unraid/unraid-installer

Length of output: 1499


🏁 Script executed:

# Get the full context around the cleanup function
wc -l .github/workflows/update-unraid-release-lock.yml

Repository: unraid/unraid-installer

Length of output: 120


🏁 Script executed:

# Find the cleanup function and understand what it does
grep -n "Installer-\|release_tags\|--limit" .github/workflows/update-unraid-release-lock.yml | head -20

Repository: unraid/unraid-installer

Length of output: 715


🏁 Script executed:

# Check if there are many releases in the repo
git ls-files | grep -i release

Repository: unraid/unraid-installer

Length of output: 268


🏁 Script executed:

# Look at the full cleanup section to understand context
sed -n '250,300p' .github/workflows/update-unraid-release-lock.yml | cat -n

Repository: unraid/unraid-installer

Length of output: 2629


🏁 Script executed:

# Count total releases in the repository
gh release list --limit 1000 | wc -l

Repository: unraid/unraid-installer

Length of output: 69


🏁 Script executed:

# Check if there are Installer-* releases beyond what gh release list shows
gh api repos/{GITHUB_REPOSITORY}/releases --paginate --jq '.[] | .tag_name' 2>/dev/null | grep -c "^Installer-" || echo "Query check"

Repository: unraid/unraid-installer

Length of output: 81


🏁 Script executed:

# Verify the field names for gh release list vs gh api
gh release list --limit 1 --json tagName,isDraft,isPrerelease | head -5

Repository: unraid/unraid-installer

Length of output: 136


Eliminate the arbitrary 100-release limit to ensure complete cleanup.

The --limit 100 cap applies before the Installer-* filter, so older installer tags will eventually fall out of scope as the repository grows beyond 100 total releases, leaving their bundled seeded assets permanently. Use pagination to scan all releases.

Suggested fix
-            release_tags="$(gh release list \
-              --limit 100 \
-              --json tagName,isDraft,isPrerelease \
-              --jq '.[] | select(.isDraft == false and .isPrerelease == false) | .tagName')"
+            release_tags="$(gh api "repos/${GITHUB_REPOSITORY}/releases" --paginate \
+              --jq '.[] | select(.draft == false and .prerelease == false) | .tag_name')"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
release_tags="$(gh release list \
--limit 100 \
--json tagName,isDraft,isPrerelease \
--jq '.[] | select(.isDraft == false and .isPrerelease == false) | .tagName')"
release_tags="$(gh api "repos/${GITHUB_REPOSITORY}/releases" --paginate \
--jq '.[] | select(.draft == false and .prerelease == false) | .tag_name')"
🤖 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/update-unraid-release-lock.yml around lines 273 - 276, The
gh release list call that populates release_tags is capped by "--limit 100",
causing older releases to be missed; remove the "--limit 100" and use gh's
pagination (e.g. add "--paginate") so the command: gh release list --paginate
--json tagName,isDraft,isPrerelease --jq '... | .tagName' returns all releases
for the subsequent Installer-* filtering; update the invocation that sets
release_tags accordingly (referencing the release_tags assignment and the gh
release list invocation).

Comment on lines +96 to +99
try:
filename, sha256 = validate_release_url(url, version, channel)
except ValueError:
continue

Copy link
Copy Markdown

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

Don't silently fall back past a malformed stable candidate.

Once an entry already targets /dl/stable/..., swallowing validate_release_url(...) failures lets the updater quietly keep an older stable release instead of surfacing that the newest stable metadata changed shape. That breaks the "latest stable" guarantee and can stall lock updates without any signal.

💡 Suggested direction
         version = find_version(name)
         if not version or version_tuple(version) < minimum or ".zip" not in url:
             continue
-        try:
-            filename, sha256 = validate_release_url(url, version, channel)
-        except ValueError:
+        parsed = urllib.parse.urlsplit(url)
+        path_parts = [part for part in parsed.path.split("/") if part]
+        if len(path_parts) < 2 or path_parts[:2] != ["dl", channel]:
             continue
+        filename, sha256 = validate_release_url(url, version, channel)
         candidates.append((version_tuple(version), entry, version, filename, sha256))
🤖 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 `@scripts/update-unraid-release-lock.py` around lines 96 - 99, The code
currently swallows ValueError from validate_release_url(url, version, channel)
which allows malformed stable candidates to be ignored; change the except
handler so that if the candidate is a stable entry (detect via channel ==
"stable" or url containing "/dl/stable/") you do not continue silently but
surface the error (re-raise the ValueError or log and exit) including the url,
version and channel in the message, otherwise keep the existing continue
behavior for non-stable channels; update the try/except around
validate_release_url to implement this logic so malformed stable metadata fails
fast instead of quietly keeping an older release.

@elibosley elibosley merged commit f1ee910 into main May 20, 2026
2 checks passed
@elibosley elibosley deleted the codex/stable-latest-seeded-release branch May 20, 2026 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant