@coworker-jp/aidr 0.0.7 → 0.0.9
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 +69 -2
- package/src/sentinel.mjs +20 -2
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 } from "child_process";
|
|
3
|
+
import { execFile, spawnSync } from "child_process";
|
|
4
4
|
import { promisify } from "util";
|
|
5
5
|
|
|
6
6
|
const execFileP = promisify(execFile);
|
|
@@ -177,15 +177,82 @@ export async function installScheduled(opts) {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
async function writeEnvFile(accessKey, dryRun) {
|
|
180
|
-
|
|
180
|
+
// Capture the install-time actor identity here (NOT at scheduled
|
|
181
|
+
// run-time) because the scheduler runs as root with no Apple ID, no
|
|
182
|
+
// git config, and $USER="root". Without this, the Rust scanner would
|
|
183
|
+
// emit snapshots labelled "unknown" (or worse, fall through to
|
|
184
|
+
// customer_email — the bug this whole helper exists to fix).
|
|
185
|
+
const actorId = resolveInstallerActorId();
|
|
186
|
+
let envContent = `AI_SCANNER_ACCESS_KEY=${accessKey}\n`;
|
|
187
|
+
if (actorId) envContent += `AI_SCANNER_ACTOR_ID=${actorId}\n`;
|
|
181
188
|
if (dryRun) {
|
|
182
189
|
console.log("[dry-run] Would create:", SYSTEM_ENV_FILE, "(mode 0600)");
|
|
190
|
+
if (actorId) console.log("[dry-run] actor_id captured:", actorId);
|
|
183
191
|
return;
|
|
184
192
|
}
|
|
185
193
|
await fs.mkdir(SYSTEM_ENV_DIR, { recursive: true, mode: 0o755 });
|
|
186
194
|
await fs.writeFile(SYSTEM_ENV_FILE, envContent, { mode: 0o600 });
|
|
187
195
|
}
|
|
188
196
|
|
|
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
|
+
|
|
189
256
|
async function installSystemd({ binaryPath, dryRun, noActivate, interval = 4 }) {
|
|
190
257
|
// StandardOutput=append: requires systemd 240+ (released 2018). Modern
|
|
191
258
|
// distros (RHEL 9 / Ubuntu 20.04+ / Debian 11+) all support it. Older
|
package/src/sentinel.mjs
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
ensureSystemLogDir,
|
|
23
23
|
writeLogrotateConfig,
|
|
24
24
|
removeSystemLog,
|
|
25
|
+
resolveInstallerActorId,
|
|
25
26
|
} from "./scheduled.mjs";
|
|
26
27
|
|
|
27
28
|
const execFileP = promisify(execFile);
|
|
@@ -138,13 +139,30 @@ export async function installSentinel(opts = {}) {
|
|
|
138
139
|
await ensureSystemLogDir(SENTINEL_LOG_FILE, { dryRun });
|
|
139
140
|
await writeLogrotateConfig({ dryRun });
|
|
140
141
|
|
|
141
|
-
// 3. Write env file (append AI_SCANNER_ACCESS_KEY
|
|
142
|
+
// 3. Write env file (append AI_SCANNER_ACCESS_KEY + AI_SCANNER_ACTOR_ID
|
|
143
|
+
// if missing). When --with-sentinel runs alongside --scheduled,
|
|
144
|
+
// scheduled.mjs's writeEnvFile has already written both lines and
|
|
145
|
+
// we no-op. When --with-sentinel runs standalone (no --scheduled),
|
|
146
|
+
// we are the only writer of aidr.env and must capture the
|
|
147
|
+
// pre-sudo SUDO_USER identity ourselves; otherwise the daemon
|
|
148
|
+
// would report `unknown` actor (or worse, the customer_email
|
|
149
|
+
// fallback before that branch was removed).
|
|
142
150
|
if (accessKey && !dryRun) {
|
|
143
151
|
let content = "";
|
|
144
152
|
try { content = await fs.readFile(SYSTEM_ENV_FILE, "utf8"); } catch { /* first run */ }
|
|
153
|
+
let appended = "";
|
|
145
154
|
if (!content.includes("AI_SCANNER_ACCESS_KEY")) {
|
|
155
|
+
appended += `\nAI_SCANNER_ACCESS_KEY=${accessKey}\n`;
|
|
156
|
+
}
|
|
157
|
+
if (!content.includes("AI_SCANNER_ACTOR_ID")) {
|
|
158
|
+
const actorId = resolveInstallerActorId();
|
|
159
|
+
if (actorId) {
|
|
160
|
+
appended += `AI_SCANNER_ACTOR_ID=${actorId}\n`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (appended) {
|
|
146
164
|
await fs.mkdir(path.dirname(SYSTEM_ENV_FILE), { recursive: true });
|
|
147
|
-
await fs.appendFile(SYSTEM_ENV_FILE,
|
|
165
|
+
await fs.appendFile(SYSTEM_ENV_FILE, appended);
|
|
148
166
|
await fs.chmod(SYSTEM_ENV_FILE, 0o600);
|
|
149
167
|
}
|
|
150
168
|
}
|