Skip to content

fix(docker): prevent case-duplicate user templates#2620

Merged
limetech merged 4 commits intomasterfrom
codex/fix-docker-template-case-duplicates
Apr 22, 2026
Merged

fix(docker): prevent case-duplicate user templates#2620
limetech merged 4 commits intomasterfrom
codex/fix-docker-template-case-duplicates

Conversation

@elibosley
Copy link
Copy Markdown
Member

@elibosley elibosley commented Apr 21, 2026

Summary

  • reuse existing Docker user-template filenames when the requested container name differs only by case
  • avoid creating a second XML on case-sensitive boot media during template saves
  • preserve the opened user-template basename for unchanged-name edits so saving cannot silently write to a different case-variant file
  • reject posted source-template values with path separators and rebuild accepted my-*.xml basenames under templates-user
  • resolve renamed-container old-template paths through the opened source template, or through DockerTemplates::getUserTemplatePath() when no source template was posted
  • replace startup find -iname template lookup with deterministic single-file lookup

Testing

  • php -l emhttp/plugins/dynamix.docker.manager/include/DockerClient.php
  • php -l emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php
  • php -l emhttp/plugins/dynamix.docker.manager/scripts/docker_init
  • php -d short_open_tag=1 -l emhttp/plugins/dynamix.docker.manager/include/DockerClient.php
  • php -d short_open_tag=1 -l emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php
  • php -d short_open_tag=1 -l emhttp/plugins/dynamix.docker.manager/scripts/docker_init
  • bash -n etc/rc.d/rc.docker
  • git diff --check
  • focused PHP regression for case-only Docker template lookup and save behavior
  • deployed to root@192.168.0.206 and verified edit/save updates an existing mixed-case template instead of creating a lowercase duplicate
  • restarted Docker on root@192.168.0.206 with a mismatched-case autostart template probe and verified no duplicate template was created
  • reproduced duplicate case-variant templates on root@192.168.0.206 (my-FIREfox2.xml plus my-Firefox2.xml) and verified unchanged-name save from the opened my-FIREfox2.xml basename updates only that opened file
  • verified sourceTemplate=../../my-FIREfox2.xml is rejected and falls back to normal canonical save behavior rather than traversing paths

Notes

This PR is intentionally separate from codex/docker-endpoint-mac-address and contains only the template case load/save/startup lookup fix plus existing rename-time old-template deletion path correction. It does not add a duplicate cleanup pass and does not remove duplicate template files.

Summary by CodeRabbit

  • New Features

    • Created support for specifying and preserving a source user template when creating or editing containers.
  • Bug Fixes

    • Improved user-template resolution (exact then case-insensitive) to select the correct template.
    • Fixed template cleanup on rename so the previous template is removed reliably.
    • Deferred autostart containers when their user template cannot be loaded.
  • Stability

    • Hardened template validation and file handling to reduce startup and editing errors.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Add helpers (PHP and shell) to deterministically resolve per-container user template XML paths (exact or case-insensitive), prefer resolved user templates when creating/editing/autostarting/networking containers, update rename/unlink to operate on resolved paths, and defer startup when template resolution or XML load fails.

Changes

Cohort / File(s) Summary
Create/update template (PHP)
emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php
Read optional POST sourceTemplate, validate/unscript it as a my-*.xml user template, prefer its resolved path when selecting template for create/edit/rename, and unlink the previously resolved template path during rename cleanup. Emit hidden sourceTemplate basename in edit flows; removed a trailing blank line in output.
Template helpers & lookup (PHP)
emhttp/plugins/dynamix.docker.manager/include/DockerClient.php
Added DockerTemplates::getUserTemplatePath($Container) to resolve user template paths under templates-user with exact and case-insensitive matching and deterministic fallback. Enables reuse of resolved path without parsing XML contents.
Autostart template resolution (shell)
emhttp/plugins/dynamix.docker.manager/scripts/docker_init
Added user_template($container) helper that returns an exact or case-insensitive my-*.xml match. Autostart logic now uses the resolved template, loads it via DOMDocument, and defers the container when no template is found or XML load fails.
Runtime network start (shell)
etc/rc.d/rc.docker
Added docker_user_template() to return a single matched user template path. docker_network_start() now uses the resolved path, verifies it with [[ -f $XMLFILE ]], and tightened quoting for downstream grep/sed/read_dom input handling.

Sequence Diagram(s)

sequenceDiagram
  participant Shell as "docker_init / rc.docker"
  participant PHP as "DockerTemplates::getUserTemplatePath"
  participant FS as "Filesystem"
  participant Loader as "XML Loader"

  Shell->>PHP: request template path for CONTAINER
  PHP->>FS: ensure templates-user dir exists
  PHP->>FS: check exact `my-CONTAINER.xml`
  alt exact match
    FS-->>PHP: return exact path
  else case-insensitive match
    FS-->>PHP: return matched path
  else no match
    FS-->>PHP: return default target path
  end
  Shell->>Loader: load(resolved_path)
  alt load succeeds
    Loader-->>Shell: parsed -> continue start/network
  else load fails or no file
    Loader-->>Shell: error -> defer container
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through templates, sniffed each file and name,

exact then gentle match — I hunted without shame.
I nudged the old away when paths no longer fit,
held back shy containers when XML wouldn't sit.
Small paws, tidy files — I leave the treehouse prim and trim.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(docker): prevent case-duplicate user templates' directly and precisely summarizes the main objective of the changeset: preventing duplicate user template XML files when container names differ only by case.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-docker-template-case-duplicates

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

🔧 PR Test Plugin Available

A test plugin has been generated for this PR that includes the modified files.

Version: 2026.04.22.1446
Build: View Workflow Run

📥 Installation Instructions:

Install via Unraid Web UI:

  1. Go to Plugins → Install Plugin
  2. Copy and paste this URL:
https://preview.dl.unraid.net/pr-plugins/pr-2620/webgui-pr-2620.plg
  1. Click Install

Alternative: Direct Download

⚠️ Important Notes:

  • Testing only: This plugin is for testing PR changes
  • Backup included: Original files are automatically backed up
  • Easy removal: Files are restored when plugin is removed
  • Conflicts: Remove this plugin before installing production updates
  • Post-merge behavior: This preview stays available after merge until preview storage expires or it is manually cleaned up

📝 Modified Files:

Click to expand file list
emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php
emhttp/plugins/dynamix.docker.manager/include/DockerClient.php
emhttp/plugins/dynamix.docker.manager/scripts/docker_init
etc/rc.d/rc.docker

🔄 To Remove:

Navigate to Plugins → Installed Plugins and remove webgui-pr-2620, or run:

plugin remove webgui-pr-2620

🤖 This comment is automatically generated and will be updated with each new push to this PR.

@elibosley elibosley force-pushed the codex/fix-docker-template-case-duplicates branch from 57939a8 to 5865a7f Compare April 21, 2026 19:02
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php`:
- Line 84: When renaming/cleaning up templates use the resolved path from
getUserTemplatePath instead of constructing "my-$existing.xml"; replace the
deletion logic that builds "my-$existing.xml" (lines referencing $existing) so
it calls $DockerTemplates->getUserTemplatePath($existing) and deletes that
resolved $oldFilename (same approach used to set $filename from
getUserTemplatePath($Name)), ensuring case-sensitive files are correctly
targeted.
🪄 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: cfde94bf-8314-411c-a03c-434ee6592530

📥 Commits

Reviewing files that changed from the base of the PR and between 57939a8 and 5865a7f.

📒 Files selected for processing (4)
  • emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php
  • emhttp/plugins/dynamix.docker.manager/include/DockerClient.php
  • emhttp/plugins/dynamix.docker.manager/scripts/docker_init
  • etc/rc.d/rc.docker
🚧 Files skipped from review as they are similar to previous changes (1)
  • emhttp/plugins/dynamix.docker.manager/scripts/docker_init

Comment thread emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php Outdated
@elibosley elibosley marked this pull request as ready for review April 21, 2026 20:40
@elibosley elibosley added the 7.3 label Apr 21, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php`:
- Around line 83-85: The POSTed $sourceTemplate path is only checked as a user
template file but not validated against the container being edited; change the
logic that sets $sourceUserTemplate so after
unscript(urldecode($sourceTemplate)) and the existing file/dirname/preg_match
checks you also parse the XML at $sourceTemplate and verify its <Name> equals
the container name being edited (the target container identifier used by
CreateDocker.php); if it doesn’t match, treat it as invalid and instead call
getUserTemplatePath() to obtain the safe template path before continuing (so
subsequent code that overwrites or deletes the template uses a verified path).
🪄 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: 7bbdd4ee-2efa-4ca2-aa04-592fccbc15f3

📥 Commits

Reviewing files that changed from the base of the PR and between 868489b and 7bd3e2f.

📒 Files selected for processing (2)
  • emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php
  • emhttp/plugins/dynamix.docker.manager/include/DockerClient.php

Comment thread emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php (1)

83-91: ⚠️ Potential issue | 🟡 Minor

Validate sourceTemplate belongs to the container being edited.

While the current validation (lines 85-89) confirms sourceTemplate is a valid user-template file, it doesn't verify that the template's <Name> element matches the container being edited ($existing). A crafted POST could set sourceTemplate=my-OtherContainer.xml, causing edits to overwrite a different container's template when $existing === $Name.

Given Unraid's admin-only context this is a data integrity concern rather than privilege escalation, but it could cause silent corruption of unrelated container configurations.

💡 Suggested validation
   $sourceUserTemplate = $sourceTemplate && basename($sourceTemplate)==$sourceTemplate && preg_match('/^my-[^\/\\\\]+\.xml$/', $sourceTemplate);
   if ($sourceUserTemplate) {
     $sourceTemplate = "$userTmplDir/$sourceTemplate";
-    $sourceUserTemplate = is_file($sourceTemplate);
+    if (is_file($sourceTemplate)) {
+      $sourceXml = `@simplexml_load_file`($sourceTemplate);
+      $sourceUserTemplate = $sourceXml && (string)($sourceXml->Name ?? '') === $existing;
+    } else {
+      $sourceUserTemplate = false;
+    }
   }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 15d9c424-feb7-4ccd-8256-02fd3da1b807

📥 Commits

Reviewing files that changed from the base of the PR and between 7bd3e2f and 7bbe2aa.

📒 Files selected for processing (2)
  • emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php
  • emhttp/plugins/dynamix.docker.manager/include/DockerClient.php

@elibosley
Copy link
Copy Markdown
Member Author

Not taking this one for now. The posted value is no longer trusted as a path: the form posts only the basename, the server requires it to match my-[^/\\]+.xml, rejects any slash/backslash including ../../, and reconstructs the path under templates-user before checking that the file exists.\n\nVerifying the XML <Name> would be extra authenticated-admin POST hardening, but it adds another XML parse/branch to the hot save path and is broader than this PR's scoped fix. For this change, basename-only source tracking is enough to keep edit/save pointed at the opened template without introducing path traversal risk.

- Purpose: keep Docker user template saves consistent when container names differ only by case.
- Before: saving a container template used `my-$Name.xml` directly, while other paths performed case-insensitive lookup for legacy FAT boot media.
- Problem: on case-sensitive internal/ZFS boot media, edits or case-only renames could create a new `my-Name.xml` instead of reusing an existing `my-name.xml`.
- New behavior: Docker template saves reuse an existing exact or case-insensitive user-template path before writing.
- Implementation: add deterministic user-template path lookup and use it from create/edit, docker init, and Docker startup network restore.
- Scope: this change does not add cleanup or delete duplicate templates.
- Purpose: address PR review feedback on Docker template rename handling.
- Before: the renamed-container cleanup path compared and deleted a manually constructed my-<name>.xml path.
- Problem: on case-sensitive boot storage, that constructed path can miss the actual existing template filename when only case differs.
- Now: the existing template path is resolved through DockerTemplates::getUserTemplatePath before comparison and deletion.
- How: reuse the same case-aware lookup helper used for saving the current container template, without adding any duplicate cleanup pass.
- Purpose: address duplicate case-variant template feedback from PR testing.
- Before: the Docker edit path could open one case-variant XML while saving preferred another exact my-<Name>.xml path.
- Problem: on case-sensitive boot storage, existing duplicate templates could silently split edits across two files.
- Now: edit forms carry the opened user-template path and unchanged-name saves write back to that source file.
- How: getUserTemplate and name-scoped getTemplateValue now prioritize the same case-aware resolver used by saves, while CreateDocker preserves the opened source path for normal edits and uses it as the old template during renames.
- Purpose: simplify the duplicate case-variant template fix after review.
- Before: the branch reordered template-value lookups and changed getUserTemplate selection rules to keep edit and save paths aligned.
- Problem: that was broader and harder to reason about than necessary for the reported split-state edit/save bug.
- Now: edit forms post only the opened user-template basename, and unchanged-name saves write back to that exact template file.
- How: CreateDocker reconstructs accepted my-*.xml basenames under templates-user, rejects path separators such as ../../, and falls back to getUserTemplatePath for new saves and renames while DockerClient sanitizes container-derived template path names.
@elibosley elibosley force-pushed the codex/fix-docker-template-case-duplicates branch from 7bbe2aa to 4bdaba8 Compare April 22, 2026 14:46
@limetech limetech merged commit ef1f4d6 into master Apr 22, 2026
5 checks passed
@limetech limetech deleted the codex/fix-docker-template-case-duplicates branch April 22, 2026 17:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants