Skip to content

feat: automatic patch updates - #1

Merged
wandercone merged 6 commits into
mainfrom
dev
Jul 9, 2026
Merged

feat: automatic patch updates#1
wandercone merged 6 commits into
mainfrom
dev

Conversation

@wandercone

@wandercone wandercone commented Jul 9, 2026

Copy link
Copy Markdown
Owner

This PR introduces automatic Unraid OS patch updates.

Summary by CodeRabbit

  • New Features

    • Added a dedicated Updater page with clearer controls for manual installs, automatic patch updates, scheduling, and staged update actions.
    • Added support for installing updates from a direct release URL, with checksum verification and clearer update status details.
    • Added automatic update handling for patch releases within the current major.minor branch.
    • Added plugin-managed scheduled update checks that are reflected immediately in the system.
  • Bug Fixes

    • Improved update state handling so saved settings, staged downloads, and scheduled tasks stay in sync after changes or removal.

@wandercone wandercone self-assigned this Jul 9, 2026
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@wandercone, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 82d4cb7d-41ea-4a9f-9149-691416d7d300

📥 Commits

Reviewing files that changed from the base of the PR and between a4ab244 and 75e73b1.

📒 Files selected for processing (4)
  • src/usr/local/emhttp/plugins/updater/include/Settings.php
  • src/usr/local/emhttp/plugins/updater/include/Versions.php
  • src/usr/local/emhttp/plugins/updater/include/ajax.php
  • src/usr/local/emhttp/plugins/updater/include/cron.php
📝 Walkthrough

Walkthrough

This PR adds automatic update capability to the Unraid Updater plugin: a Versions utility for version parsing/comparison, a Settings class for persisted state and cron generation, a cron.php CLI script for checking/staging/installing patch updates, expanded AJAX endpoints, a reworked UI with scheduling controls, plugin cron wiring, and updated docs.

Changes

Automatic Update Feature

Layer / File(s) Summary
Version parsing and comparison
src/usr/local/emhttp/plugins/updater/include/Versions.php
New Versions class parses major.minor.patch, compares versions (treating stable as newer than prerelease), checks branch match, and selects the newest matching public release.
Settings persistence and cron generation
src/usr/local/emhttp/plugins/updater/include/Settings.php
New Settings class persists state as JSON, resolves installed version, and builds/installs/removes cron entries for check/install schedules with time and cron-expression validation.
Cron CLI check/install/stage workflow
src/usr/local/emhttp/plugins/updater/include/cron.php
New CLI script fetches release metadata, finds matching patches, stages downloads with checksum verification, installs staged updates, and optionally stops the array and reboots, guarded by a lock file.
AJAX endpoints for settings and staged apply
src/usr/local/emhttp/plugins/updater/include/ajax.php
Adds get_settings, save_settings, apply_staged, check_now actions with validation/sanitization helpers and a generated manual-install script; updates start_update error message and script formatting.
Updater page UI and client-side JavaScript
src/usr/local/emhttp/plugins/updater/Updater.page
Reworks the page with automatic-update controls and status panel, plus new JS for loading/saving settings, checking now, rendering status, and applying staged updates with polling.
Plugin cron wiring and documentation
plugin/plugin.j2, src/install/slack-desc, README.md
Generates and syncs a plugin-owned cron file during install/remove via update_cron, and updates README and slack-desc text to describe manual and automatic patch update behavior.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CronDaemon
  participant CronPHP
  participant ReleasesAPI
  participant Settings
  participant Filesystem
  CronDaemon->>CronPHP: run check
  CronPHP->>Settings: load()
  CronPHP->>ReleasesAPI: fetchReleases()
  CronPHP->>CronPHP: Versions.findNewestMatchingPatch()
  CronPHP->>Filesystem: stageUpdate() download + verify checksum
  CronPHP->>Settings: updateState(staged info)
  CronDaemon->>CronPHP: run install
  CronPHP->>Filesystem: installStagedFile()
  CronPHP->>CronPHP: stopArrayAndReboot() if auto_reboot enabled
Loading
sequenceDiagram
  participant User
  participant UpdaterPage
  participant AjaxPHP
  participant Settings
  User->>UpdaterPage: open Tools > Updater
  UpdaterPage->>AjaxPHP: get_settings
  AjaxPHP->>Settings: load()
  Settings-->>AjaxPHP: settings + state
  AjaxPHP-->>UpdaterPage: JSON settings/status
  User->>UpdaterPage: click Apply Staged Update
  UpdaterPage->>AjaxPHP: apply_staged
  AjaxPHP->>Filesystem: run install script via nohup
  UpdaterPage->>AjaxPHP: poll log
  AjaxPHP-->>UpdaterPage: progress/completion
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately highlights the primary change: automatic patch updates.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/usr/local/emhttp/plugins/updater/include/ajax.php (1)

345-416: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

$zipFile parameter is unused.

The body is a nowdoc (<<<'BASH') that reads the ZIP path from the bash positional arg $1; the actual file is passed at exec time (bash {$scriptEsc} {$fileEsc} on Line 83). The $zipFile argument is therefore dead and misleading — drop it to match the caller's real data flow.

♻️ Proposed cleanup
-            file_put_contents(SCRIPT_FILE, buildManualInstallScript($file));
+            file_put_contents(SCRIPT_FILE, buildManualInstallScript());
-function buildManualInstallScript(string $zipFile): string
+function buildManualInstallScript(): string
🤖 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 `@src/usr/local/emhttp/plugins/updater/include/ajax.php` around lines 345 -
416, The buildManualInstallScript helper currently declares a $zipFile parameter
that is never used because the generated bash script reads the ZIP path from $1
at runtime. Remove the unused parameter from buildManualInstallScript and update
any related call sites or signatures so the function reflects the actual data
flow used by the updater flow that builds the script and executes it with the
file argument.

Source: Linters/SAST tools

🤖 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 `@src/usr/local/emhttp/plugins/updater/include/cron.php`:
- Around line 281-282: isVerified currently returns success when both checksum
values are empty, which lets unverified update files pass through; update the
checksum validation flow in isVerified (and the cached-file reuse path that
relies on it) so that a missing md5 and sha256 causes verification to fail
closed, only allowing files that have at least one valid checksum and a
successful digest match before staging or installation.
- Around line 479-480: `releaseLock()` is removing the lock file after closing
it, which can reopen a race where another process acquires the same inode and
then gets unlinked underneath it. Update `releaseLock()` in `cron.php` to only
close the lock handle and stop calling `@unlink(LOCK_FILE)`. Keep the lock
lifecycle centered on `acquireLock()`/`releaseLock()` so the existing
`fopen(..., 'c')` creation logic remains the single source of file creation.
- Around line 453-456: stopArrayAndReboot() is only returning the launch status
from exec(), so the caller never sees failures from the actual reboot script and
the later error check becomes ineffective. Add a pre-flight validation in
stopArrayAndReboot() before building the nohup/bash command to detect obvious
failures such as a missing emcmd or an unlaunchable script, and return a nonzero
code before backgrounding; keep the existing background execution path and
update the pending_reboot/state handling so it is only set when the pre-check
passes.

In `@src/usr/local/emhttp/plugins/updater/include/Settings.php`:
- Around line 174-186: The cron field validation in Settings::sanitizeCron is
using regex patterns with incorrect alternation precedence, so malformed values
can still pass. Update the $patterns entries to anchor the entire
allowed-expression branch by grouping each alternative in the regex, then keep
the foreach validation against $fields so only fully valid cron field formats
are accepted before ajax.php writes the cron file.

In `@src/usr/local/emhttp/plugins/updater/Updater.page`:
- Around line 196-222: loadSettings currently returns immediately on a failed
get_settings response, which leaves branchLabel and the status area
uninitialized and hides the failure from the user. Update loadSettings in
Updater.page to handle the !data.success path by surfacing the backend error
through the existing settingsMsg UI (or a similar visible message) and avoid
leaving the page in a silent loading state; keep the rest of the settings
population logic unchanged.

---

Nitpick comments:
In `@src/usr/local/emhttp/plugins/updater/include/ajax.php`:
- Around line 345-416: The buildManualInstallScript helper currently declares a
$zipFile parameter that is never used because the generated bash script reads
the ZIP path from $1 at runtime. Remove the unused parameter from
buildManualInstallScript and update any related call sites or signatures so the
function reflects the actual data flow used by the updater flow that builds the
script and executes it with the file argument.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: e527e700-8dfe-4f4d-9257-0900b80f41dd

📥 Commits

Reviewing files that changed from the base of the PR and between cf3abb2 and a4ab244.

📒 Files selected for processing (8)
  • README.md
  • plugin/plugin.j2
  • src/install/slack-desc
  • src/usr/local/emhttp/plugins/updater/Updater.page
  • src/usr/local/emhttp/plugins/updater/include/Settings.php
  • src/usr/local/emhttp/plugins/updater/include/Versions.php
  • src/usr/local/emhttp/plugins/updater/include/ajax.php
  • src/usr/local/emhttp/plugins/updater/include/cron.php

Comment thread src/usr/local/emhttp/plugins/updater/include/cron.php Outdated
Comment thread src/usr/local/emhttp/plugins/updater/include/cron.php Outdated
Comment thread src/usr/local/emhttp/plugins/updater/include/cron.php Outdated
Comment thread src/usr/local/emhttp/plugins/updater/include/Settings.php
Comment thread src/usr/local/emhttp/plugins/updater/Updater.page
wandercone and others added 4 commits July 9, 2026 07:42
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@wandercone
wandercone merged commit 5c36528 into main Jul 9, 2026
3 of 4 checks passed
@wandercone
wandercone deleted the dev branch July 9, 2026 12:55
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