fix sudo handling and pkgx-under-/root reachability#84
Conversation
Restore the privilege drop when running as root via sudo (so pkgx caches stay user-owned), but only when the resolved pkgx binary is actually reachable from $SUDO_USER. Falls back to running pkgx as root rather than crashing the inner sudo with "Permission denied" when pkgx lives under /root/.pkgx/ (pkgxdev#68). - Restore drop-privilege behaviour for `sudo pkgm` (fixes the regression flagged by jhheider on pkgxdev#83). - Resolve an alternative pkgx under $SUDO_USER's home / /usr/local when the current path is unreachable to $SUDO_USER. - Override HOME so pkgx caches under the invoking user's tree. - Stop mutating `args` so the args[0] lookup at line 341 keeps working. - Avoid the `Deno.env.get("USER")!` non-null assertion crash. - Call install_prefix() once (it has filesystem side effects). - Keep the visible log surface unchanged: only the pre-existing "installing as root" warning fires by default; the new "pkgx unreachable" diagnostic is gated behind PKGM_DEBUG. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates pkgm’s sudo/root execution flow when installing into the system prefix (/usr/local) to restore dropping privileges back to the invoking user (so caches remain user-owned), while avoiding failures when the resolved pkgx binary isn’t actually reachable by $SUDO_USER (eg when pkgx lives under root-owned private directories).
Changes:
- Reworks
query_pkgx()command selection sosudo -u $SUDO_USERis only used when running as root viasudoandpkgxis deemed reachable by that user. - Overrides
HOME(when dropping privileges) to keeppkgxcaches under the invoking user’s home. - Adds helper functions to discover a reachable
pkgxpath and to look up a user’s home directory.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Conservative heuristic: private home dirs are typically mode 700, so a | ||
| // path under another user's home is unreachable. System paths and the | ||
| // user's own home are assumed reachable. | ||
| if (p.startsWith("/root/")) return user === "root"; | ||
| const m = p.match(/^\/home\/([^/]+)\//); | ||
| if (m) return m[1] === user; |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| if (existsSync(root)) { | ||
| let best: { v: SemVer; path: string } | undefined; | ||
| for (const entry of Deno.readDirSync(root)) { | ||
| if (!entry.isDirectory || !entry.name.startsWith("v")) continue; | ||
| try { | ||
| const v = new SemVer(entry.name.slice(1)); | ||
| const path = join(root, entry.name, "bin/pkgx"); | ||
| if (!existsSync(path)) continue; | ||
| if (!best || v.gt(best.v)) best = { v, path }; | ||
| } catch { /* skip malformed version dir */ } | ||
| } | ||
| if (best) return best.path; |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@copilot apply changes based on the comments in this thread |
| for (const entry of Deno.readDirSync(root)) { | ||
| if (!entry.isDirectory || !entry.name.startsWith("v")) continue; | ||
| try { | ||
| const v = new SemVer(entry.name.slice(1)); | ||
| const path = join(root, entry.name, "bin/pkgx"); | ||
| if (!existsSync(path)) continue; | ||
| if (!best || v.gt(best.v)) best = { v, path }; | ||
| } catch { /* skip malformed version dir */ } |
|
@copilot apply changes based on the comments in this thread |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@copilot apply changes based on the comments in this thread |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| const local = join(home, ".local/bin/pkgx"); | ||
| if (existsSync(local)) return local; | ||
| } | ||
| if (existsSync("/usr/local/bin/pkgx")) return "/usr/local/bin/pkgx"; | ||
| return undefined; |
| function reachable_as(p: string, user: string): boolean { | ||
| // Conservative heuristic: private home dirs are typically mode 700, so a | ||
| // path under another user's home is unreachable. System paths and the | ||
| // user's own home are assumed reachable. | ||
| if (p.startsWith("/root/")) return user === "root"; | ||
| const m = p.match(/^\/home\/([^/]+)\//); | ||
| if (m) return m[1] === user; | ||
| return true; |
| if (p.startsWith("/root/")) return user === "root"; | ||
| const m = p.match(/^\/home\/([^/]+)\//); |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| const reachable = pkgx_reachable_as(pkgx, sudoUser); | ||
| if (reachable) { | ||
| cmd = "/usr/bin/sudo"; | ||
| cmd_args = ["-u", sudoUser, "--", reachable, ...args]; | ||
| // Override HOME, or pkgx will cache back under /root/ where sudoUser | ||
| // can't reach it on the next invocation. | ||
| const home = user_home(sudoUser); | ||
| if (home) env.HOME = home; |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@jhheider can you take a look please? |
|
it looks good, @tannevaled ; codex review flagged two areas for concern:
(1) is likely rare-but-not-impossible. i know we have one user trying to do that kind of shared-install system, but i don't know if this would trigger his issues. @magnusviri : any thoughts here? even better would be if we could add a CI test-suite to run through your code paths and check that everything is working and permissions are what's intended (i.e. no catastrophic changes to uid:0). |
Restore the privilege drop when running as root via sudo (so pkgx caches stay user-owned), but only when the resolved pkgx binary is actually reachable from $SUDO_USER. Falls back to running pkgx as root rather than crashing the inner sudo with "Permission denied" when pkgx lives under /root/.pkgx/ (#68).
sudo pkgm(fixes the regression flagged by jhheider on Refactor sudo handling for package installation #83).argsso the args[0] lookup at line 341 keeps working.Deno.env.get("USER")!non-null assertion crash.