fix(plugin-manager): treat a dangling plugin symlink as not installed#2696
fix(plugin-manager): treat a dangling plugin symlink as not installed#2696elibosley wants to merge 1 commit into
Conversation
The plugin manager tracks installed plugins with a symlink under
/var/log/plugins/<name>.plg pointing at the real /boot/config/plugins/<name>.plg.
It used `@readlink()` alone to decide whether a plugin is installed, so a
*dangling* symlink — one whose target .plg was removed or renamed on the flash
out-of-band — was still read as "installed". The manager then tried to parse the
missing target and aborted with:
plugin: XML file doesn't exist or xml parse error
For `install` this made the plugin permanently un-installable until the stale
symlink was removed by hand; `check` and `update` failed the same way.
Add an `installed_plugin()` helper that resolves the tracking symlink to its
target, treats a dangling link as not installed, and removes the stale link so a
subsequent install/update/check starts clean. Use it in the install, check, and
update handlers in place of the bare `@readlink()`. (`remove` already unlinks the
stale symlink before acting, so it self-heals.)
Verified on a live box: with a dangling tracking symlink, `check` now reports
"plugin: not installed" and clears the stale link, and a fresh install proceeds
normally instead of aborting on the parse error.
WalkthroughThe plugin script adds validated symlink resolution and stale-link removal. Install, check, and update commands now use this helper, treating missing plugin targets as not installed. ChangesPlugin symlink cleanup
Estimated code review effort: 2 (Simple) | ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
🔧 PR Test Plugin AvailableA test plugin has been generated for this PR that includes the modified files. Version: 📥 Installation Instructions:Install via Unraid Web UI:
Alternative: Direct Download
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@emhttp/plugins/dynamix.plugin.manager/scripts/plugin`:
- Around line 521-535: Update the checkall, updateall, and checkos bulk loops to
call installed_plugin() when resolving tracking symlinks instead of bypassing it
with direct checks. Preserve each loop’s existing handling for valid installed
plugins while ensuring dangling symlinks are removed and treated as not
installed.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 615588bd-ed09-4084-9a2f-057611ff1eae
📒 Files selected for processing (1)
emhttp/plugins/dynamix.plugin.manager/scripts/plugin
| // Resolve the installed plugin file that a tracking symlink points at, or FALSE | ||
| // if the plugin is not installed. A dangling symlink - one whose target .plg has | ||
| // been removed or renamed on the flash out-of-band - is treated as not installed | ||
| // and the stale link is cleaned up, so a subsequent install/update/check starts | ||
| // from a clean state instead of failing to parse the missing target. | ||
| function installed_plugin($symlink) { | ||
| $target = @readlink($symlink); | ||
| if ($target === false) return false; | ||
| if (!is_file($target)) { | ||
| @unlink($symlink); | ||
| return false; | ||
| } | ||
| return $target; | ||
| } | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
sed -n '500,680p' emhttp/plugins/dynamix.plugin.manager/scripts/pluginRepository: unraid/webgui
Length of output: 6097
🏁 Script executed:
rg -n "^function plugin\\(|plugin\\('pluginURL'|plugin\\('version'" emhttp/plugins/dynamix.plugin.manager/scripts/pluginRepository: unraid/webgui
Length of output: 904
🏁 Script executed:
sed -n '1,220p' emhttp/plugins/dynamix.plugin.manager/scripts/pluginRepository: unraid/webgui
Length of output: 9648
🏁 Script executed:
sed -n '303,430p' emhttp/plugins/dynamix.plugin.manager/scripts/pluginRepository: unraid/webgui
Length of output: 4524
Use installed_plugin() in the bulk loops (emhttp/plugins/dynamix.plugin.manager/scripts/plugin:597,620,646)
checkall, updateall, and checkos still bypass the cleanup helper, so dangling tracking symlinks are silently skipped and left behind. Reusing installed_plugin() here keeps /var/log/plugins consistent.
🤖 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 `@emhttp/plugins/dynamix.plugin.manager/scripts/plugin` around lines 521 - 535,
Update the checkall, updateall, and checkos bulk loops to call
installed_plugin() when resolving tracking symlinks instead of bypassing it with
direct checks. Preserve each loop’s existing handling for valid installed
plugins while ensuring dangling symlinks are removed and treated as not
installed.
Summary
A stale plugin tracking symlink makes a plugin permanently un-installable via the plugin manager, failing with a misleading error:
This has been observed with legacy plugins whose
.plgwas removed or renamed on the flash outside the manager (leaving the/var/log/plugins/<name>.plgtracking symlink dangling). The only workaround today is deleting the stale symlink by hand.Root cause
The manager records an installed plugin as a symlink
/var/log/plugins/<name>.plg→/boot/config/plugins/<name>.plg, and uses@readlink($symlink) !== falseas "is this plugin installed?".readlink()still returns the target path for a dangling symlink (target file gone), so the manager takes the re-install path and then tries to read the version/pluginURL from the missing target:plugin()can't open the missing file, sets the generic "XML file doesn't exist or xml parse error", and the install aborts. (checkandupdatehit the same latent flaw.) The secondarysh: -c: ... matching 'noise is downstream: the error string itself contains an apostrophe ("doesn't"), which breaks the hook invocationrun("$hook plugin $method $plugin $error").Fix
Add a small
installed_plugin()helper that resolves the tracking symlink, treats a dangling link as not installed, and removes the stale link so the next operation starts from a clean state:Used in the
install,check, andupdatehandlers in place of the bare@readlink().removealready unlinks the stale symlink before acting, so it self-heals and is left unchanged.Verification
On a live system, with a dangling tracking symlink:
plugin check <name>XML file doesn't exist or xml parse error, stale link remainsplugin: not installed, stale link cleanedplugin install <url>php -lclean.Summary by CodeRabbit