@coworker-jp/aidr 0.1.0 → 0.1.2
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/scheduled.mjs +11 -68
- package/src/sentinel.mjs +10 -10
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
package/src/scheduled.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import { execFile
|
|
3
|
+
import { execFile } from "child_process";
|
|
4
4
|
import { promisify } from "util";
|
|
5
5
|
|
|
6
6
|
const execFileP = promisify(execFile);
|
|
@@ -177,82 +177,25 @@ export async function installScheduled(opts) {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
async function writeEnvFile(accessKey, dryRun) {
|
|
180
|
-
// Capture the
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
|
|
180
|
+
// Capture only the SUDO_USER (the unix username of whoever ran
|
|
181
|
+
// `sudo aidr install ...`) — the Rust binary will resolve the full
|
|
182
|
+
// identity (Apple ID / git email / username) at scan time using
|
|
183
|
+
// `sudo -u <invoker>` so it reads the invoker's plist / gitconfig
|
|
184
|
+
// instead of root's empty ones. Pre-resolving here is the wrong
|
|
185
|
+
// layer because the Apple ID needs to be re-read in the right user
|
|
186
|
+
// context at runtime anyway (root has no MobileMeAccounts.plist).
|
|
187
|
+
const invoker = process.env.SUDO_USER || null;
|
|
186
188
|
let envContent = `AI_SCANNER_ACCESS_KEY=${accessKey}\n`;
|
|
187
|
-
if (
|
|
189
|
+
if (invoker) envContent += `AI_SCANNER_INVOKER_USER=${invoker}\n`;
|
|
188
190
|
if (dryRun) {
|
|
189
191
|
console.log("[dry-run] Would create:", SYSTEM_ENV_FILE, "(mode 0600)");
|
|
190
|
-
if (
|
|
192
|
+
if (invoker) console.log("[dry-run] invoker captured:", invoker);
|
|
191
193
|
return;
|
|
192
194
|
}
|
|
193
195
|
await fs.mkdir(SYSTEM_ENV_DIR, { recursive: true, mode: 0o755 });
|
|
194
196
|
await fs.writeFile(SYSTEM_ENV_FILE, envContent, { mode: 0o600 });
|
|
195
197
|
}
|
|
196
198
|
|
|
197
|
-
/**
|
|
198
|
-
* Resolve the **pre-sudo user's** identity for embedding in
|
|
199
|
-
* AI_SCANNER_ACTOR_ID at install time. Order, per user spec:
|
|
200
|
-
* 1. SUDO_USER's git config user.email (run as that user — root has
|
|
201
|
-
* its own (usually empty) git config that we must not return)
|
|
202
|
-
* 2. macOS: SUDO_USER's Apple ID (`defaults read MobileMeAccounts`)
|
|
203
|
-
* 3. SUDO_USER itself (the Unix username)
|
|
204
|
-
*
|
|
205
|
-
* Returns `null` when none of the above succeed (e.g. install was run
|
|
206
|
-
* as bare root with no SUDO_USER). The caller writes nothing in that
|
|
207
|
-
* case; the Rust scanner then surfaces "unknown" instead of mislabeling
|
|
208
|
-
* the snapshot with the user-portal login email.
|
|
209
|
-
*
|
|
210
|
-
* Test override: AIDR_INSTALLER_ACTOR_ID env bypasses every detection
|
|
211
|
-
* step (used by Node unit tests so they don't have to mock spawnSync).
|
|
212
|
-
*/
|
|
213
|
-
export function resolveInstallerActorId() {
|
|
214
|
-
if (process.env.AIDR_INSTALLER_ACTOR_ID) {
|
|
215
|
-
return process.env.AIDR_INSTALLER_ACTOR_ID || null;
|
|
216
|
-
}
|
|
217
|
-
const sudoUser = process.env.SUDO_USER;
|
|
218
|
-
if (!sudoUser) return null;
|
|
219
|
-
|
|
220
|
-
// 1. git config user.email — run AS the SUDO_USER so we read their
|
|
221
|
-
// ~/.gitconfig, not /root/.gitconfig (which is normally empty).
|
|
222
|
-
// --global is intentional: per-repo configs aren't relevant for
|
|
223
|
-
// a host-level identity. timeout caps a hung git invocation.
|
|
224
|
-
const gitRes = spawnSync(
|
|
225
|
-
"sudo",
|
|
226
|
-
["-u", sudoUser, "git", "config", "--global", "--get", "user.email"],
|
|
227
|
-
{ encoding: "utf8", timeout: 5000 },
|
|
228
|
-
);
|
|
229
|
-
if (gitRes.status === 0) {
|
|
230
|
-
const email = (gitRes.stdout || "").trim();
|
|
231
|
-
if (email) return email;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// 2. macOS Apple ID. Mirrors the Rust binary's parse_apple_id: read
|
|
235
|
-
// the first AccountID line from MobileMeAccounts. Must run as the
|
|
236
|
-
// SUDO_USER — root has no MobileMeAccounts plist of its own.
|
|
237
|
-
if (process.platform === "darwin") {
|
|
238
|
-
const dRes = spawnSync(
|
|
239
|
-
"sudo",
|
|
240
|
-
["-u", sudoUser, "defaults", "read", "MobileMeAccounts"],
|
|
241
|
-
{ encoding: "utf8", timeout: 5000 },
|
|
242
|
-
);
|
|
243
|
-
if (dRes.status === 0) {
|
|
244
|
-
const m = (dRes.stdout || "").match(/AccountID\s*=\s*"([^"]+)"/);
|
|
245
|
-
const id = m && m[1] ? m[1].trim() : "";
|
|
246
|
-
if (id) return id;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// 3. Username fallback. Better than "unknown" because at least it
|
|
251
|
-
// ties the snapshot to a Unix account on the host, but it loses
|
|
252
|
-
// the cross-host identity that an email gives us.
|
|
253
|
-
return sudoUser;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
199
|
async function installSystemd({ binaryPath, dryRun, noActivate, interval = 4 }) {
|
|
257
200
|
// StandardOutput=append: requires systemd 240+ (released 2018). Modern
|
|
258
201
|
// distros (RHEL 9 / Ubuntu 20.04+ / Debian 11+) all support it. Older
|
package/src/sentinel.mjs
CHANGED
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
ensureSystemLogDir,
|
|
23
23
|
writeLogrotateConfig,
|
|
24
24
|
removeSystemLog,
|
|
25
|
-
resolveInstallerActorId,
|
|
26
25
|
} from "./scheduled.mjs";
|
|
27
26
|
|
|
28
27
|
const execFileP = promisify(execFile);
|
|
@@ -139,14 +138,15 @@ export async function installSentinel(opts = {}) {
|
|
|
139
138
|
await ensureSystemLogDir(SENTINEL_LOG_FILE, { dryRun });
|
|
140
139
|
await writeLogrotateConfig({ dryRun });
|
|
141
140
|
|
|
142
|
-
// 3. Write env file (append AI_SCANNER_ACCESS_KEY +
|
|
141
|
+
// 3. Write env file (append AI_SCANNER_ACCESS_KEY + AI_SCANNER_INVOKER_USER
|
|
143
142
|
// if missing). When --with-sentinel runs alongside --scheduled,
|
|
144
143
|
// scheduled.mjs's writeEnvFile has already written both lines and
|
|
145
144
|
// we no-op. When --with-sentinel runs standalone (no --scheduled),
|
|
146
|
-
// we are the only writer of aidr.env and must capture
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
145
|
+
// we are the only writer of aidr.env and must capture SUDO_USER
|
|
146
|
+
// ourselves; otherwise the daemon (running as root with no useful
|
|
147
|
+
// git/Apple/USER) reports `unknown`. Identity resolution itself
|
|
148
|
+
// (Apple ID / git email / username) happens at scan time inside
|
|
149
|
+
// the Rust binary using `sudo -u <invoker>`.
|
|
150
150
|
if (accessKey && !dryRun) {
|
|
151
151
|
let content = "";
|
|
152
152
|
try { content = await fs.readFile(SYSTEM_ENV_FILE, "utf8"); } catch { /* first run */ }
|
|
@@ -154,10 +154,10 @@ export async function installSentinel(opts = {}) {
|
|
|
154
154
|
if (!content.includes("AI_SCANNER_ACCESS_KEY")) {
|
|
155
155
|
appended += `\nAI_SCANNER_ACCESS_KEY=${accessKey}\n`;
|
|
156
156
|
}
|
|
157
|
-
if (!content.includes("
|
|
158
|
-
const
|
|
159
|
-
if (
|
|
160
|
-
appended += `
|
|
157
|
+
if (!content.includes("AI_SCANNER_INVOKER_USER")) {
|
|
158
|
+
const invoker = process.env.SUDO_USER;
|
|
159
|
+
if (invoker) {
|
|
160
|
+
appended += `AI_SCANNER_INVOKER_USER=${invoker}\n`;
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
if (appended) {
|