Skip to content

feat(openemr-cmd): HOST_UID in non-worktree mode; remove obsolete auto-chown shim#842

Merged
bradymiller merged 3 commits into
openemr:masterfrom
bradymiller:feat/nonwt-host-uid-export
Jun 26, 2026
Merged

feat(openemr-cmd): HOST_UID in non-worktree mode; remove obsolete auto-chown shim#842
bradymiller merged 3 commits into
openemr:masterfrom
bradymiller:feat/nonwt-host-uid-export

Conversation

@bradymiller

@bradymiller bradymiller commented Jun 26, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features
    • openemr-cmd up now forwards HOST_UID/HOST_GID into Docker (when not set) to preserve host ownership for non-worktree runs.
    • Non-worktree startup pre-creates required host bind-mount directories before bringing containers up.
  • Bug Fixes
    • Worktree removal now uses a safer permission probe and no longer relies on the in-container auto-chown step, with updated messaging and tolerance for leftover empty mount dirs.
  • Tests
    • Added/updated BATS and CI workflow coverage to validate HOST_UID/HOST_GID behavior and the revised removal flows.

Cross-repo dependency

Depends on openemr/openemr#12647 (now landed). The flex image already pins HOST_UID entrypoint support (from openemr/openemr#12642 via the #12644 dependabot bump); #12647 added the compose-side env declaration so the exported HOST_UID actually reaches the openemr service. No additional image release needed — all moving pieces in place once both PRs land.

What's tested end-to-end in CI

  • Worktree mode: worktree-lifecycle-e2e asserts sites/default/sqlconf.php is owned by the runner uid after install — proves apache adopted HOST_UID via wt_write_override's env block + entrypoint adoption.
  • Non-worktree mode: nonworktree-lifecycle-e2e asserts the same against openemr/sites/default/sqlconf.php — proves run_docker_compose's export reaches the openemr service via #12647's env block + entrypoint adoption.

…o-chown shim

Three related changes that complete the HOST_UID story:

1. Non-worktree `up` now exports HOST_UID/HOST_GID before invoking
   docker compose, so the openemr service's compose-side env block
   (declared in openemr/openemr#12647) gets the runner's uid for
   apache to adopt. Worktree mode already did this via wt_write_
   override's environment block (openemr#840); this closes the gap for the
   non-worktree path (`openemr-cmd up` from openemr/docker/development-*).
   Respects user pre-exports — only auto-derives from `id -u` when
   unset.

2. Non-worktree `up` also pre-creates host-side dirs for named-volume
   mount points under the webroot bind mount, mirroring the worktree
   path's wt_precreate_volume_mountpoints. Without this, docker daemon
   creates the missing dirs on the host as root:root 0755, accumulating
   cruft in the base repo over time. Refactored the existing pre-create
   logic into precreate_volume_mountpoints_at(compose_file, bind_root)
   so both paths share the implementation. Idempotent — accepts
   existing host-owned dirs AND existing root-owned legacy cruft
   (mkdir-then-accept; we don't sudo-chown legacy dirs because that
   would require host root and surprise the user).

3. Removed the openemr#833 in-container auto-chown shim from cmd_worktree_
   remove. With HOST_UID adoption now shipping universally (worktree
   AND non-worktree), apache writes bind-mount files as the host uid
   throughout — the shim's chown(host-uid → host-uid) was a permanent
   no-op wasting docker exec time on every remove. The probe + the
   pre-create + the probe-loosening (openemr#838) are the safety net for any
   drift cases.

E2e updates:
- worktree-lifecycle-e2e: removed the "auto-chown banner present"
  assertion (banner is gone); added an explicit bind-mount-ownership
  assertion (verify sites/default/sqlconf.php is owned by runner uid,
  proving apache adopted host uid via HOST_UID)
- nonworktree-lifecycle-e2e: added the same bind-mount-ownership
  assertion against openemr/sites/default/sqlconf.php — proves the
  non-worktree path's HOST_UID export reaches the container
- multi-3 + prek: updated step names + comments to reflect the new
  reality (no shim, no chown dance)

Bats:
- New host_uid_nonworktree.bats: 4 cases pinning the non-worktree
  HOST_UID export + pre-create behavior (export with default uid;
  respects pre-exports; pre-creates expected dirs; idempotent on
  existing dirs)

VERSION bumped 1.0.49 → 1.0.50.

Upgrade path for existing base-repo users (with legacy root-owned
mount-point dirs): one-shot `sudo chown -R "$(id -u):$(id -g)" <openemr-checkout>`
if they care about the cruft. Otherwise harmless — non-worktree mode
has no rm-based operation that would trip the writability probe.

Assisted-by: Claude Code
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 81178699-1530-4f55-9c96-19db9c7e895c

📥 Commits

Reviewing files that changed from the base of the PR and between 64e2704 and 439e78d.

📒 Files selected for processing (2)
  • tests/bats/openemr-cmd/remove_graceful.bats
  • utilities/openemr-cmd/openemr-cmd
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/bats/openemr-cmd/remove_graceful.bats
  • utilities/openemr-cmd/openemr-cmd

📝 Walkthrough

Walkthrough

openemr-cmd now exports HOST_UID/HOST_GID, precreates bind-mount directories for non-worktree up, and changes worktree removal to a probe-only flow without the in-container auto-chown step. The workflow and BATS coverage now assert host-owned bind mounts and preserved mountpoint state.

Changes

Host UID bind-mount flow

Layer / File(s) Summary
Compose setup and mountpoint precreation
utilities/openemr-cmd/openemr-cmd
openemr-cmd now exports HOST_UID/HOST_GID, precreates named-volume mountpoints through precreate_volume_mountpoints_at(), and calls that path before non-worktree up -d.
Worktree remove probe
utilities/openemr-cmd/openemr-cmd, tests/bats/openemr-cmd/remove_graceful.bats
cmd_worktree_remove drops the container chown -R step and relaxes the probe for empty root-owned leftovers; the remove tests now assert prompt gating, no docker exec, and the simplified failure guidance.
Workflow ownership checks
.github/workflows/test-bats-openemr-cmd-real-docker.yml
The real-docker job comments, names, and probes now describe HOST_UID-based host ownership for the worktree, multi-worktree, non-worktree, and prek steps.
Non-worktree BATS coverage
tests/bats/openemr-cmd/host_uid_nonworktree.bats
A new BATS suite stubs docker, records HOST_UID/HOST_GID, and checks non-worktree up preserves explicit env values, precreates only the expected mountpoint directories, and leaves existing contents intact.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • openemr/openemr-devops#840: Adds the HOST_UID/HOST_GID compose propagation that this PR extends with non-worktree mountpoint precreation and ownership checks.
  • openemr/openemr-devops#833: Modifies the same worktree remove path by introducing the auto-chown step that this PR removes.
  • openemr/openemr-devops#834: Updates the same workflow removal assertions around the auto-chown banner that this PR changes to host-owned probes.

Poem

I hopped through up with HOST_UID bright,
And sniffed the paths in morning light.
No chown-shim trick, no muddy burrow,
Just tidy dirs and a happy furrow.
🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 Title clearly summarizes the main changes: HOST_UID support in non-worktree mode and removal of the obsolete auto-chown shim.
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

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

@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

🧹 Nitpick comments (1)
.github/workflows/test-bats-openemr-cmd-real-docker.yml (1)

444-445: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert GID adoption too.

These checks pin HOST_UID, but the PR also exports HOST_GID; adding a %g / id -g assertion catches a broken group-adoption path in the real Docker flow.

Proposed check to add in both ownership probes
           owner_uid=$(stat -c '%u' "${probe_file}")
+          owner_gid=$(stat -c '%g' "${probe_file}")
           runner_uid=$(id -u)
+          runner_gid=$(id -g)
           if [[ "${owner_uid}" != "${runner_uid}" ]]; then
               echo "FAIL: bind-mount file owned by uid=${owner_uid} (expected runner uid=${runner_uid})"
               echo "HOST_UID adoption is NOT working — apache may have stayed at uid=1000"
               ls -la "${probe_file}"
               exit 1
           fi
+          if [[ "${owner_gid}" != "${runner_gid}" ]]; then
+              echo "FAIL: bind-mount file owned by gid=${owner_gid} (expected runner gid=${runner_gid})"
+              echo "HOST_GID adoption is NOT working"
+              ls -la "${probe_file}"
+              exit 1
+          fi

Also applies to: 909-910

🤖 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/test-bats-openemr-cmd-real-docker.yml around lines 444 -
445, The ownership probes currently verify only HOST_UID adoption, so extend the
checks in the Docker workflow’s probe logic to also validate HOST_GID. Update
the ownership assertions alongside the existing stat/id comparisons in the probe
sections so they capture the file’s group ID with stat %g and compare it against
id -g from the runner. Apply the same GID assertion in both ownership probe
locations referenced by the workflow to ensure the real Docker path confirms
group adoption too.
🤖 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 `@utilities/openemr-cmd/openemr-cmd`:
- Around line 954-956: The stale auto-chown recovery hint still references a
removed in-container permissions fix, so update the failure message to direct
users to perform a manual host-side chown instead of starting the stack for an
auto-chown step. Make this change in the openemr-cmd permission recovery/error
path that emits the hint, and also review any related BATS expectations or
assertions that still look for the old auto-chown banner or docker exec chown
behavior.
- Around line 2206-2213: The non-worktree bind-root resolution in the `up` flow
ignores `OPENEMR_DIR` and always precreates mountpoints under `${PWD}/../..`,
which can target the wrong checkout. Update the `bind_root_resolved` /
`precreate_volume_mountpoints_at` path handling to resolve the bind root from
the same `${OPENEMR_DIR:-../..}` value used by the compose file, while
preserving the existing cwd-based worktree check. Ensure the logic in
`openemr-cmd` uses the resolved environment-aware bind root before calling
`precreate_volume_mountpoints_at`.

---

Nitpick comments:
In @.github/workflows/test-bats-openemr-cmd-real-docker.yml:
- Around line 444-445: The ownership probes currently verify only HOST_UID
adoption, so extend the checks in the Docker workflow’s probe logic to also
validate HOST_GID. Update the ownership assertions alongside the existing
stat/id comparisons in the probe sections so they capture the file’s group ID
with stat %g and compare it against id -g from the runner. Apply the same GID
assertion in both ownership probe locations referenced by the workflow to ensure
the real Docker path confirms group adoption too.
🪄 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 Plus

Run ID: e971af20-41f7-43cf-b9a9-3cccb5a05878

📥 Commits

Reviewing files that changed from the base of the PR and between f4e73bf and 7395dc5.

📒 Files selected for processing (3)
  • .github/workflows/test-bats-openemr-cmd-real-docker.yml
  • tests/bats/openemr-cmd/host_uid_nonworktree.bats
  • utilities/openemr-cmd/openemr-cmd

Comment thread utilities/openemr-cmd/openemr-cmd
Comment thread utilities/openemr-cmd/openemr-cmd Outdated
The auto-chown shim removal in the previous commit broke three bats
tests in remove_graceful.bats that asserted the shim's specific
behavior (banner present, docker exec invoked, failure non-fatal).
Replace with one regression-guard test pinning the "no shim" contract:
"remove never invokes docker exec for chown (shim removed)" — fires
loudly if anyone re-introduces it.

Also:
- Update the "aborted at prompt" test to drop the auto-chown banner
  check (banner is gone regardless of abort path)
- Update the probe error message in cmd_worktree_remove to remove
  the obsolete "start the stack first, auto-chown handles it" option;
  the only recovery is sudo chown now
- Update the corresponding probe-test assertion to expect the
  simplified single-option error
- Tidy stale "auto-chown" mentions in workflow job names + top comment

Assisted-by: Claude Code

@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: 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 `@tests/bats/openemr-cmd/remove_graceful.bats`:
- Around line 232-238: The regression check in the remove_graceful bats tests is
too narrow because it only searches for the exact "exec -u root" fragment.
Update the check in the test logic around the docker log assertion to fail on
any docker exec invocation by matching the exec subcommand itself, so future
variants with different flags or ordering are still caught; apply the same
change in both affected test blocks, including the one referenced by the
remove_graceful test helpers.
🪄 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 Plus

Run ID: 6ac353f0-e666-4831-a79d-9d1ba2392b0c

📥 Commits

Reviewing files that changed from the base of the PR and between 7395dc5 and 64e2704.

📒 Files selected for processing (3)
  • .github/workflows/test-bats-openemr-cmd-real-docker.yml
  • tests/bats/openemr-cmd/remove_graceful.bats
  • utilities/openemr-cmd/openemr-cmd
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/test-bats-openemr-cmd-real-docker.yml
  • utilities/openemr-cmd/openemr-cmd

Comment thread tests/bats/openemr-cmd/remove_graceful.bats
…aden bats exec-check

CodeRabbit found two issues on openemr#842:

(1) Non-worktree pre-create hardcoded `${PWD}/../..` as the bind
mount root, ignoring the `${OPENEMR_DIR:-../..}` substitution that
the compose file actually honors. If a user pointed their openemr
checkout elsewhere via OPENEMR_DIR, pre-create wrote dirs to a
phantom location at cwd/../.. while docker created the real mount-
point dirs (under OPENEMR_DIR) as root — same bug we were trying
to fix, just one level up. Fix: resolve the bind root from
OPENEMR_DIR with the same default the compose uses.

(2) Two bats regression-guard checks used `grep -F "exec -u root"`
to detect a re-introduced shim. Too narrow — a future variant like
`docker exec --tty <id> chown ...` (without `-u root`) would slip
through. Broadened to match the `exec` subcommand at line start,
catching any flag/user/order shape.

Assisted-by: Claude Code
@bradymiller bradymiller merged commit b3829d6 into openemr:master Jun 26, 2026
12 checks passed
@bradymiller bradymiller deleted the feat/nonwt-host-uid-export branch June 26, 2026 07:12
bradymiller added a commit to bradymiller/openemr that referenced this pull request Jun 26, 2026
Brief mention of the openemr-cmd HOST_UID/HOST_GID auto-export in
two places where contributors and agents will look:

- CONTRIBUTING.md: added a sub-bullet under the openemr-cmd intro
  explaining that bind-mounted files apache writes are host-owned
  for any host uid (transparent for new checkouts; one-shot sudo
  chown documented for legacy checkouts with root-owned cruft).
- CLAUDE.md: added a Common Gotchas entry so future agents debugging
  EACCES errors on bind-mount edits go straight to "did you bypass
  openemr-cmd?" instead of tracing it themselves. Also notes the
  insane env hasn't been wired up yet (older image tags don't
  honor HOST_UID).

The auto-export landed in openemr/openemr-devops#840 (worktree side)
and openemr/openemr-devops#842 (non-worktree side); the entrypoint
adoption it depends on landed in openemr#12642 (image side)
and openemr#12647 (compose env declaration).

Assisted-by: Claude Code
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