@coworker-jp/aidr 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli.mjs +1 -1
- package/src/invoker-resolve.mjs +63 -0
- package/src/scheduled.mjs +9 -8
- package/src/sentinel.mjs +2 -1
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Best-effort invoker username resolution at install time.
|
|
2
|
+
//
|
|
3
|
+
// `aidr install --scheduled` / `--with-sentinel` writes the unix username
|
|
4
|
+
// into `/etc/aidr/aidr.env` as `AI_SCANNER_INVOKER_USER`. The Rust daemon
|
|
5
|
+
// reads it back to resolve git email / Apple ID / $HOME of the actual
|
|
6
|
+
// installer (root daemons have no useful identity of their own).
|
|
7
|
+
//
|
|
8
|
+
// In the auto-sudo path the CLI re-execs itself via `sudo -E`, which sets
|
|
9
|
+
// SUDO_USER. But several real-world contexts arrive here as bare root
|
|
10
|
+
// without SUDO_USER:
|
|
11
|
+
// - `ssh root@host` then `npx ... install --scheduled` (no sudo prefix)
|
|
12
|
+
// - container / CI where the user is already root
|
|
13
|
+
// - sudo policy that strips SUDO_USER
|
|
14
|
+
//
|
|
15
|
+
// We walk a fallback chain so the resulting daemon snapshot reports the
|
|
16
|
+
// real user instead of `actor_id=unknown`. Each step is best-effort and
|
|
17
|
+
// never throws — we return `null` when no signal is available, which is
|
|
18
|
+
// the honest outcome for genuinely identity-less contexts (CI runners).
|
|
19
|
+
|
|
20
|
+
import { execSync } from "node:child_process";
|
|
21
|
+
|
|
22
|
+
function tryExec(cmd) {
|
|
23
|
+
try {
|
|
24
|
+
return execSync(cmd, { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
25
|
+
} catch {
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isUsable(name) {
|
|
31
|
+
return name && name !== "root";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolveInvokerUser() {
|
|
35
|
+
if (process.env.SUDO_USER) return process.env.SUDO_USER;
|
|
36
|
+
|
|
37
|
+
// `who am i` reads utmp, so it returns the actual login user even after
|
|
38
|
+
// `sudo -i` / `su -`. Empty under non-tty / no utmp.
|
|
39
|
+
const whoUser = tryExec("who am i").split(/\s+/)[0];
|
|
40
|
+
if (isUsable(whoUser)) return whoUser;
|
|
41
|
+
|
|
42
|
+
// POSIX `logname` gives controlling-tty owner. Slightly different code
|
|
43
|
+
// path from `who am i` — kept as a separate fallback because they fail
|
|
44
|
+
// independently on minimal images.
|
|
45
|
+
const logname = tryExec("logname");
|
|
46
|
+
if (isUsable(logname)) return logname;
|
|
47
|
+
|
|
48
|
+
// systemd Linux: `loginctl list-sessions` columns are: SESSION UID USER ...
|
|
49
|
+
const loginctl = tryExec("loginctl list-sessions --no-legend");
|
|
50
|
+
for (const line of loginctl.split("\n")) {
|
|
51
|
+
const fields = line.trim().split(/\s+/);
|
|
52
|
+
if (fields.length >= 3 && isUsable(fields[2])) return fields[2];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Env fallbacks (root-filtered) so a user-shell install without sudo at
|
|
56
|
+
// all (rare for --scheduled, possible for plain --agent without root)
|
|
57
|
+
// still captures the obvious answer.
|
|
58
|
+
for (const env of ["LOGNAME", "USER"]) {
|
|
59
|
+
if (isUsable(process.env[env])) return process.env[env];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return null;
|
|
63
|
+
}
|
package/src/scheduled.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "fs/promises";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { execFile } from "child_process";
|
|
4
4
|
import { promisify } from "util";
|
|
5
|
+
import { resolveInvokerUser } from "./invoker-resolve.mjs";
|
|
5
6
|
|
|
6
7
|
const execFileP = promisify(execFile);
|
|
7
8
|
|
|
@@ -177,14 +178,14 @@ export async function installScheduled(opts) {
|
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
async function writeEnvFile(accessKey, dryRun) {
|
|
180
|
-
// Capture
|
|
181
|
-
// `
|
|
182
|
-
// identity (Apple ID / git email / username)
|
|
183
|
-
// `sudo -u <invoker>` so it reads the invoker's
|
|
184
|
-
// instead of root's empty ones. Pre-resolving here
|
|
185
|
-
// layer because the Apple ID needs to be re-read in the
|
|
186
|
-
// context at runtime anyway (root has no MobileMeAccounts
|
|
187
|
-
const invoker =
|
|
181
|
+
// Capture the invoker username (SUDO_USER first, then `who am i` /
|
|
182
|
+
// `logname` / `loginctl` / $LOGNAME / $USER fallbacks) — the Rust
|
|
183
|
+
// binary resolves the full identity (Apple ID / git email / username)
|
|
184
|
+
// at scan time using `sudo -u <invoker>` so it reads the invoker's
|
|
185
|
+
// plist / gitconfig instead of root's empty ones. Pre-resolving here
|
|
186
|
+
// is the wrong layer because the Apple ID needs to be re-read in the
|
|
187
|
+
// right user context at runtime anyway (root has no MobileMeAccounts).
|
|
188
|
+
const invoker = resolveInvokerUser();
|
|
188
189
|
let envContent = `AI_SCANNER_ACCESS_KEY=${accessKey}\n`;
|
|
189
190
|
if (invoker) envContent += `AI_SCANNER_INVOKER_USER=${invoker}\n`;
|
|
190
191
|
if (dryRun) {
|
package/src/sentinel.mjs
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import fs from "node:fs/promises";
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import { execFile, spawnSync } from "node:child_process";
|
|
16
|
+
import { resolveInvokerUser } from "./invoker-resolve.mjs";
|
|
16
17
|
import { promisify } from "node:util";
|
|
17
18
|
import { fetchSentinel, detectPlatform } from "./binary-fetcher.mjs";
|
|
18
19
|
import {
|
|
@@ -155,7 +156,7 @@ export async function installSentinel(opts = {}) {
|
|
|
155
156
|
appended += `\nAI_SCANNER_ACCESS_KEY=${accessKey}\n`;
|
|
156
157
|
}
|
|
157
158
|
if (!content.includes("AI_SCANNER_INVOKER_USER")) {
|
|
158
|
-
const invoker =
|
|
159
|
+
const invoker = resolveInvokerUser();
|
|
159
160
|
if (invoker) {
|
|
160
161
|
appended += `AI_SCANNER_INVOKER_USER=${invoker}\n`;
|
|
161
162
|
}
|