Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 82 additions & 12 deletions pkgm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,25 +297,43 @@ async function query_pkgx(
set("PKGX_DIST_URL");
set("XDG_DATA_HOME");

const needs_sudo_backwards = install_prefix().string == "/usr/local";
let cmd = needs_sudo_backwards ? "/usr/bin/sudo" : pkgx;
if (needs_sudo_backwards) {
if (!Deno.env.get("SUDO_USER")) {
if (Deno.uid() == 0) {
const isRoot = Deno.uid() == 0;
const sudoUser = Deno.env.get("SUDO_USER");
const prefix = install_prefix().string;
const isSystemPrefix = prefix == "/usr/local";

let cmd = pkgx;
let cmd_args = args;

if (isSystemPrefix) {
if (isRoot && sudoUser) {
// Drop privileges so pkgx writes its cache as the invoking user, not root.
// But only if pkgx is reachable from sudoUser — otherwise the inner sudo
// aborts with "unable to execute …: Permission denied" (pkgxdev/pkgm#68).
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;
} else if (Deno.env.get("PKGM_DEBUG")) {
console.error(
"%cwarning",
"color:yellow",
"installing as root; installing via `sudo` is preferred",
`pkgm: \`pkgx\` at ${pkgx} is not reachable as ${sudoUser}; running it as root`,
);
}
cmd = pkgx;
} else {
args.unshift("-u", Deno.env.get("SUDO_USER")!, pkgx);
} else if (isRoot) {
console.error(
"%cwarning",
"color:yellow",
"installing as root; installing via `sudo` is preferred",
);
}
}

const proc = new Deno.Command(cmd, {
args: [...args, "--json=v1"],
args: [...cmd_args, "--json=v1"],
stdout: "piped",
env,
clearEnv: true,
Expand Down Expand Up @@ -766,6 +784,58 @@ function install_prefix() {
}
}

function user_home(user: string): string | undefined {
// getent is the portable lookup on Linux; on macOS getent is absent but the
// /root/.pkgx scenario this guards against doesn't arise there in practice.
try {
const out = new Deno.Command("/usr/bin/getent", {
args: ["passwd", user],
}).outputSync();
if (!out.success) return undefined;
const fields = new TextDecoder().decode(out.stdout).trim().split(":");
return fields[5] || undefined;
} catch {
return undefined;
}
}

function pkgx_reachable_as(current: string, user: string): string | undefined {
if (reachable_as(current, user)) return current;

const home = user_home(user);
if (home) {
// Versioned pkgx.sh layout: ~/.pkgx/pkgx.sh/v<x.y.z>/bin/pkgx — pick the highest.
const root = join(home, ".pkgx/pkgx.sh");
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;
}
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;
}

function dev_stub_text(selfpath: string, bin_prefix: string, name: string) {
if (selfpath.startsWith("/usr/local") && selfpath != "/usr/local/bin/dev") {
return `
Expand Down
Loading