@appforge-ci/admin-cli 0.3.1 → 0.3.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/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +21 -1
- package/dist/commands/agents.js.map +1 -1
- package/dist/lib/build-user.d.ts +97 -0
- package/dist/lib/build-user.d.ts.map +1 -0
- package/dist/lib/build-user.js +243 -0
- package/dist/lib/build-user.js.map +1 -0
- package/dist/lib/build-user.test.d.ts +2 -0
- package/dist/lib/build-user.test.d.ts.map +1 -0
- package/dist/lib/build-user.test.js +161 -0
- package/dist/lib/build-user.test.js.map +1 -0
- package/dist/lib/runner-user.d.ts +93 -0
- package/dist/lib/runner-user.d.ts.map +1 -0
- package/dist/lib/runner-user.js +191 -0
- package/dist/lib/runner-user.js.map +1 -0
- package/dist/lib/runner-user.test.d.ts +2 -0
- package/dist/lib/runner-user.test.d.ts.map +1 -0
- package/dist/lib/runner-user.test.js +143 -0
- package/dist/lib/runner-user.test.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import "../test-support/home-sandbox.js";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import { BUILD_HELPER_PATH, BUILD_USER_NAME } from "@appforge-ci/agent/dist/lib/sandbox/build-user.js";
|
|
5
|
+
import { BUILD_USER_PF_ANCHOR, pfRulesSha256, renderBuildUserPfRules } from "@appforge-ci/agent/dist/lib/sandbox/pf-rules.js";
|
|
6
|
+
import { APPFORGE_ETC_DIR, PF_RULES_FILE, SUDOERS_FILE, ensureSudoersLines, installBuildUser, nextFreeSystemId, renderBuildHelperScript, requiredSudoersLines, } from "./build-user.js";
|
|
7
|
+
function fakeDeps(over = {}) {
|
|
8
|
+
const calls = [];
|
|
9
|
+
const writes = [];
|
|
10
|
+
const logs = [];
|
|
11
|
+
const deps = {
|
|
12
|
+
userExists: () => false,
|
|
13
|
+
groupExists: () => false,
|
|
14
|
+
isGroupMember: () => false,
|
|
15
|
+
listUserUids: () => "a 501\nb 502\n",
|
|
16
|
+
listGroupGids: () => "",
|
|
17
|
+
etcDirExists: () => false,
|
|
18
|
+
readSudoers: () => "",
|
|
19
|
+
validateSudoers: () => { },
|
|
20
|
+
writeRootOwnedFile: (path, content, mode) => writes.push({ path, content, mode }),
|
|
21
|
+
run: (cmd, args) => calls.push({ cmd, args }),
|
|
22
|
+
whoami: () => "tooming",
|
|
23
|
+
log: (l) => logs.push(l),
|
|
24
|
+
...over,
|
|
25
|
+
};
|
|
26
|
+
return { deps, calls, writes, logs };
|
|
27
|
+
}
|
|
28
|
+
test("nextFreeSystemId picks one past the highest used id in the 300-399 band, and never picks one already in use", () => {
|
|
29
|
+
assert.equal(nextFreeSystemId("a 399\nb 398\n"), 397);
|
|
30
|
+
assert.equal(nextFreeSystemId(""), 399);
|
|
31
|
+
assert.equal(nextFreeSystemId("unrelated 501\n"), 399, "a regular-user uid outside the band is irrelevant");
|
|
32
|
+
assert.equal(nextFreeSystemId("x 398\ny 397\n"), 399, "picks the TOP of the band when it is itself free, not just one above the highest used id");
|
|
33
|
+
});
|
|
34
|
+
test("nextFreeSystemId throws rather than guessing when the entire band is already taken", () => {
|
|
35
|
+
const all = Array.from({ length: 100 }, (_, i) => `u${i} ${300 + i}`).join("\n");
|
|
36
|
+
assert.throws(() => nextFreeSystemId(all, 399, 300), /no free id/);
|
|
37
|
+
});
|
|
38
|
+
test("requiredSudoersLines: one line per grant, neither one grants blanket root or blanket sandbox-exec to anyone but the build user", () => {
|
|
39
|
+
const { helper, becomeUser } = requiredSudoersLines("tooming", "_appforgebuild");
|
|
40
|
+
assert.equal(helper, `tooming ALL=(root) NOPASSWD: ${BUILD_HELPER_PATH} *`);
|
|
41
|
+
assert.equal(becomeUser, "tooming ALL=(_appforgebuild) NOPASSWD: /usr/bin/sandbox-exec *");
|
|
42
|
+
assert.ok(!helper.includes("ALL=(ALL)"), "the root grant must be scoped to one fixed path, not every command");
|
|
43
|
+
assert.ok(becomeUser.includes("(_appforgebuild)"), "the second grant must become the build user specifically, not root");
|
|
44
|
+
});
|
|
45
|
+
test("renderBuildHelperScript substitutes every token and leaves none behind", () => {
|
|
46
|
+
const script = renderBuildHelperScript("_appforgebuild", "/Users/tooming/appforge/jobs.noindex", "/Users/tooming/appforge/cache.noindex");
|
|
47
|
+
assert.ok(script.includes('BUILD_USER="_appforgebuild"'));
|
|
48
|
+
assert.ok(script.includes('JOBS_ROOT="/Users/tooming/appforge/jobs.noindex"'));
|
|
49
|
+
assert.ok(script.includes('CACHE_ROOT="/Users/tooming/appforge/cache.noindex"'));
|
|
50
|
+
assert.ok(script.includes(`PF_ANCHOR="${BUILD_USER_PF_ANCHOR}"`));
|
|
51
|
+
assert.doesNotMatch(script, /@@[A-Z_]+@@/);
|
|
52
|
+
});
|
|
53
|
+
// ---- ensureSudoersLines: the single most security-relevant piece — exercised directly ----
|
|
54
|
+
test("ensureSudoersLines appends only the missing lines, validates with visudo BEFORE writing, and never touches an existing unrelated line", () => {
|
|
55
|
+
const { deps, writes } = fakeDeps({ readSudoers: () => "tooming ALL=(root) NOPASSWD: /sbin/shutdown -r now\n" });
|
|
56
|
+
let validated;
|
|
57
|
+
deps.validateSudoers = (content) => {
|
|
58
|
+
validated = content;
|
|
59
|
+
};
|
|
60
|
+
ensureSudoersLines(["tooming ALL=(root) NOPASSWD: /etc/appforge/build-helper.sh *", "tooming ALL=(_appforgebuild) NOPASSWD: /usr/bin/sandbox-exec *"], deps);
|
|
61
|
+
assert.equal(writes.length, 1);
|
|
62
|
+
assert.equal(writes[0].path, SUDOERS_FILE);
|
|
63
|
+
assert.equal(writes[0].mode, "0440");
|
|
64
|
+
assert.match(writes[0].content, /^tooming ALL=\(root\) NOPASSWD: \/sbin\/shutdown -r now\n/, "the REBOOT_HOST line from bootstrap.sh is kept, not replaced");
|
|
65
|
+
assert.match(writes[0].content, /build-helper\.sh \*\n/);
|
|
66
|
+
assert.match(writes[0].content, /sandbox-exec \*\n$/);
|
|
67
|
+
assert.equal(validated, writes[0].content, "the EXACT content that gets written is what was validated — never a different string");
|
|
68
|
+
});
|
|
69
|
+
test("ensureSudoersLines is a no-op when every line is already present — never re-validates, never re-writes", () => {
|
|
70
|
+
const helper = "tooming ALL=(root) NOPASSWD: /etc/appforge/build-helper.sh *";
|
|
71
|
+
const becomeUser = "tooming ALL=(_appforgebuild) NOPASSWD: /usr/bin/sandbox-exec *";
|
|
72
|
+
const { deps, writes } = fakeDeps({ readSudoers: () => `${helper}\n${becomeUser}\n` });
|
|
73
|
+
let validateCalls = 0;
|
|
74
|
+
deps.validateSudoers = () => {
|
|
75
|
+
validateCalls++;
|
|
76
|
+
};
|
|
77
|
+
ensureSudoersLines([helper, becomeUser], deps);
|
|
78
|
+
assert.equal(writes.length, 0);
|
|
79
|
+
assert.equal(validateCalls, 0);
|
|
80
|
+
});
|
|
81
|
+
test("ensureSudoersLines adds only the ONE line that's actually missing when the other already exists", () => {
|
|
82
|
+
const helper = "tooming ALL=(root) NOPASSWD: /etc/appforge/build-helper.sh *";
|
|
83
|
+
const becomeUser = "tooming ALL=(_appforgebuild) NOPASSWD: /usr/bin/sandbox-exec *";
|
|
84
|
+
const { deps, writes } = fakeDeps({ readSudoers: () => `${helper}\n` });
|
|
85
|
+
ensureSudoersLines([helper, becomeUser], deps);
|
|
86
|
+
assert.equal(writes.length, 1);
|
|
87
|
+
const added = writes[0].content.split("\n").filter(Boolean);
|
|
88
|
+
assert.deepEqual(added, [helper, becomeUser]);
|
|
89
|
+
});
|
|
90
|
+
test("ensureSudoersLines refuses (never writes) when visudo rejects the candidate content", () => {
|
|
91
|
+
const { deps, writes } = fakeDeps();
|
|
92
|
+
deps.validateSudoers = () => {
|
|
93
|
+
throw new Error("visudo: syntax error");
|
|
94
|
+
};
|
|
95
|
+
assert.throws(() => ensureSudoersLines(["x ALL=(root) NOPASSWD: /bin/true"], deps), /syntax error/);
|
|
96
|
+
assert.equal(writes.length, 0, "a rejected candidate must never reach the real sudoers file");
|
|
97
|
+
});
|
|
98
|
+
// ---- installBuildUser: the full orchestration, each step's idempotency in isolation ----
|
|
99
|
+
test("installBuildUser creates the account, adds the agent user to its group, installs the helper + pf rules, and writes both sudoers lines — on a clean host", () => {
|
|
100
|
+
const { deps, calls, writes, logs } = fakeDeps();
|
|
101
|
+
installBuildUser({ user: "_appforgebuild", agentUser: "tooming", jobsRoot: "/j", cacheRoot: "/c" }, deps);
|
|
102
|
+
assert.ok(calls.some((c) => c.cmd === "sudo" && c.args.join(" ") === "dseditgroup -o create -i 399 _appforgebuild"), "the group is created atomically, WITH its gid, in one call");
|
|
103
|
+
assert.ok(calls.some((c) => c.cmd === "sudo" && c.args.join(" ") === "dscl . -create /Users/_appforgebuild UniqueID 399"));
|
|
104
|
+
assert.ok(calls.some((c) => c.cmd === "sudo" && c.args.join(" ") === "dscl . -create /Users/_appforgebuild IsHidden 1"));
|
|
105
|
+
assert.ok(!calls.some((c) => c.args.includes("Password")), "no password entry is ever created for this account");
|
|
106
|
+
assert.ok(calls.some((c) => c.cmd === "sudo" && c.args.join(" ") === "dseditgroup -o edit -a tooming -t user _appforgebuild"));
|
|
107
|
+
assert.ok(calls.some((c) => c.cmd === "sudo" && c.args.join(" ") === "mkdir -m 0755 /etc/appforge"));
|
|
108
|
+
assert.ok(writes.some((w) => w.path === BUILD_HELPER_PATH && w.mode === "0500" && w.content.includes('JOBS_ROOT="/j"')));
|
|
109
|
+
assert.ok(writes.some((w) => w.path === PF_RULES_FILE && w.mode === "0600"));
|
|
110
|
+
assert.ok(calls.some((c) => c.cmd === "sudo" && c.args.join(" ") === `${BUILD_HELPER_PATH} pf-reload`));
|
|
111
|
+
assert.ok(writes.some((w) => w.path === SUDOERS_FILE));
|
|
112
|
+
assert.match(logs.join("\n"), /Done\./);
|
|
113
|
+
});
|
|
114
|
+
test("installBuildUser picks an id free in BOTH namespaces — reproduces the real failure on a stock Mac, where com.apple.access_ssh/access_screensharing/access_sessionkey/access_disabled/access_ftp already occupy gids 395-399", () => {
|
|
115
|
+
const { deps, calls } = fakeDeps({
|
|
116
|
+
listUserUids: () => "", // no user anywhere near the band: a check of this listing ALONE would (wrongly) pick 399
|
|
117
|
+
listGroupGids: () => ["com.apple.access_ftp 395", "com.apple.access_disabled 396", "com.apple.access_sessionkey 397", "com.apple.access_screensharing 398", "com.apple.access_ssh 399"].join("\n"),
|
|
118
|
+
});
|
|
119
|
+
installBuildUser({ user: "_appforgebuild", agentUser: "tooming", jobsRoot: "/j", cacheRoot: "/c" }, deps);
|
|
120
|
+
assert.ok(calls.some((c) => c.cmd === "sudo" && c.args.join(" ") === "dseditgroup -o create -i 394 _appforgebuild"), "394 is the first id free in BOTH listings, not 399");
|
|
121
|
+
assert.ok(!calls.some((c) => c.args.includes("399")), "399 (taken by com.apple.access_ssh) must never be assigned to anything here");
|
|
122
|
+
});
|
|
123
|
+
test("installBuildUser is fully idempotent: nothing is created, written, or run again when everything already exists", () => {
|
|
124
|
+
const helper = requiredSudoersLines("tooming", "_appforgebuild").helper;
|
|
125
|
+
const becomeUser = requiredSudoersLines("tooming", "_appforgebuild").becomeUser;
|
|
126
|
+
const { deps, calls, writes } = fakeDeps({
|
|
127
|
+
userExists: () => true,
|
|
128
|
+
groupExists: () => true,
|
|
129
|
+
isGroupMember: () => true,
|
|
130
|
+
etcDirExists: () => true,
|
|
131
|
+
readSudoers: () => `${helper}\n${becomeUser}\n`,
|
|
132
|
+
});
|
|
133
|
+
installBuildUser({ user: "_appforgebuild", agentUser: "tooming", jobsRoot: "/j", cacheRoot: "/c" }, deps);
|
|
134
|
+
// The account/group/membership/etc-dir/sudoers steps all skip; only the helper script and pf rules are
|
|
135
|
+
// UNCONDITIONALLY rewritten each run (so a template or rules change always takes effect on the next
|
|
136
|
+
// install), plus the one pf-reload `run` call that goes with them.
|
|
137
|
+
assert.ok(!calls.some((c) => c.args[0] === "dscl"), "no dscl calls at all: nothing about the account needed creating");
|
|
138
|
+
assert.ok(!calls.some((c) => c.cmd === "sudo" && c.args[0] === "dseditgroup"));
|
|
139
|
+
assert.ok(!calls.some((c) => c.cmd === "sudo" && c.args[0] === "mkdir"));
|
|
140
|
+
assert.equal(writes.find((w) => w.path === SUDOERS_FILE), undefined, "sudoers is untouched when both lines are already present");
|
|
141
|
+
assert.equal(calls.filter((c) => c.cmd === "sudo" && c.args.join(" ") === `${BUILD_HELPER_PATH} pf-reload`).length, 1);
|
|
142
|
+
});
|
|
143
|
+
test("installBuildUser defaults user to BUILD_USER_NAME and agentUser to whoami()", () => {
|
|
144
|
+
const { deps, writes } = fakeDeps({ whoami: () => "martin" });
|
|
145
|
+
installBuildUser({ jobsRoot: "/j", cacheRoot: "/c" }, deps);
|
|
146
|
+
assert.ok(writes.some((w) => w.content.includes(`BUILD_USER="${BUILD_USER_NAME}"`)));
|
|
147
|
+
assert.ok(writes.some((w) => w.path === SUDOERS_FILE && w.content.includes("martin ALL=")));
|
|
148
|
+
});
|
|
149
|
+
test("the pf rules actually written match pf-rules.ts's own renderer exactly (no drift between the two modules)", () => {
|
|
150
|
+
const { deps, writes } = fakeDeps();
|
|
151
|
+
installBuildUser({ user: "_appforgebuild", agentUser: "tooming", jobsRoot: "/j", cacheRoot: "/c" }, deps);
|
|
152
|
+
const pfWrite = writes.find((w) => w.path === PF_RULES_FILE);
|
|
153
|
+
assert.equal(pfWrite.content, renderBuildUserPfRules("_appforgebuild"));
|
|
154
|
+
assert.equal(pfRulesSha256(pfWrite.content), pfRulesSha256(renderBuildUserPfRules("_appforgebuild")));
|
|
155
|
+
});
|
|
156
|
+
test("APPFORGE_ETC_DIR / PF_RULES_FILE / SUDOERS_FILE are the exact paths the agent-side detector and the helper template expect", () => {
|
|
157
|
+
assert.equal(APPFORGE_ETC_DIR, "/etc/appforge");
|
|
158
|
+
assert.equal(PF_RULES_FILE, "/etc/appforge/build-user.pf.conf");
|
|
159
|
+
assert.equal(SUDOERS_FILE, "/etc/sudoers.d/appforge-agent", "the SAME file bootstrap.ts's REBOOT_HOST line already lives in — never a second sudoers file");
|
|
160
|
+
});
|
|
161
|
+
//# sourceMappingURL=build-user.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-user.test.js","sourceRoot":"","sources":["../../src/lib/build-user.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gives a GitHub Actions self-hosted runner its OWN, separate macOS user — run ON the target Mac, by the
|
|
3
|
+
* operator, in their own terminal (`appforge-admin agents install-runner-user`).
|
|
4
|
+
*
|
|
5
|
+
* Why. A runner installed as the agent's OWN user exposes every job's environment (CI secrets included) to
|
|
6
|
+
* anything else that runs as that same user, including a regulated build: `sysctl kern.procargs2` lets any
|
|
7
|
+
* same-uid process read another's argv and env, and Seatbelt cannot close that (docs/security.md's "Known
|
|
8
|
+
* gaps"). packages/agent's own `detectSameUidCoHostedRunner` (co-hosted-runner.ts) now refuses a regulated
|
|
9
|
+
* build while that condition holds, but refusing is a workaround, not a fix: giving the runner a DIFFERENT
|
|
10
|
+
* uid removes the condition outright, with no ongoing check needed. The Mac mini in this fleet already has
|
|
11
|
+
* exactly this (its runner runs as `ci-runner`, a separate account) — this command automates making a Mac
|
|
12
|
+
* that doesn't look like that one.
|
|
13
|
+
*
|
|
14
|
+
* What it does, idempotently (safe to re-run):
|
|
15
|
+
* 1. Creates a plain, ordinary (not hidden, not admin) macOS user with its own home directory — a real
|
|
16
|
+
* account, the same kind the operator would create by hand in System Settings, not a system/service
|
|
17
|
+
* account: GitHub's runner sometimes needs a real login session for GUI frameworks (Xcode, the
|
|
18
|
+
* Simulator), which a hidden service account cannot provide. Skips account creation entirely if the
|
|
19
|
+
* user already exists, so re-running never fights with (or resets) an account the operator customised.
|
|
20
|
+
* 2. Downloads and extracts the official GitHub Actions runner release into that account's home, owned
|
|
21
|
+
* by it.
|
|
22
|
+
*
|
|
23
|
+
* What it deliberately does NOT do, and why:
|
|
24
|
+
* - Register the runner with GitHub (`config.sh --url --token`) or install/start its service (`svc.sh`).
|
|
25
|
+
* That needs a registration token the operator copies from GitHub's own UI (Settings → Actions →
|
|
26
|
+
* Runners → "New self-hosted runner") — short-lived (about an hour) and single-use by design, so this
|
|
27
|
+
* command takes it only as an explicit, one-time `--url`/--token pair the operator pastes in, and never
|
|
28
|
+
* stores or re-derives it. Given both, it runs the two commands GitHub's own instructions show, as that
|
|
29
|
+
* user; given neither, it prints those same two commands for the operator to run themselves.
|
|
30
|
+
* - Enable automatic login or any other change to how this Mac's console session works. A runner that
|
|
31
|
+
* needs GUI access (Simulator, keychain unlock) needs a real logged-in session for this user, which is a
|
|
32
|
+
* call about THIS Mac's physical security the operator makes themselves (Lock Screen settings), not
|
|
33
|
+
* something this command decides on their behalf.
|
|
34
|
+
*/
|
|
35
|
+
export declare const DEFAULT_RUNNER_USER = "ci-runner";
|
|
36
|
+
/** Pinned, update deliberately (same reasoning as MIN_EGRESS_HELPER_VERSION elsewhere): the exact release
|
|
37
|
+
* this command downloads, checked against GitHub's own release list when bumped. */
|
|
38
|
+
export declare const DEFAULT_RUNNER_VERSION = "2.321.0";
|
|
39
|
+
export declare function runnerDownloadUrl(version: string, arch: "arm64" | "x64"): string;
|
|
40
|
+
/** `process.arch` as the runner release asset names it (`x64`, not Node's own `x64`... they happen to agree;
|
|
41
|
+
* arm64 likewise — kept as its own function so a future Node arch string that does NOT agree has one place
|
|
42
|
+
* to fix, and so this is mockable in a test without faking process.arch globally). */
|
|
43
|
+
export declare function runnerArch(nodeArch?: string): "arm64" | "x64";
|
|
44
|
+
export interface InstallRunnerUserDeps {
|
|
45
|
+
userExists(user: string): boolean;
|
|
46
|
+
/** Raw `dscl . -list /Users UniqueID` output: one `<name> <uid>` line per user. Its own function (rather
|
|
47
|
+
* than folding into `run`/`tryRun`) so `nextFreeUid`'s parsing is testable without a real `dscl`. */
|
|
48
|
+
listUserUids(): string;
|
|
49
|
+
run(cmd: string, args: string[]): void;
|
|
50
|
+
tryRun(cmd: string, args: string[]): boolean;
|
|
51
|
+
exists(path: string): boolean;
|
|
52
|
+
mkdirp(path: string): void;
|
|
53
|
+
log(line: string): void;
|
|
54
|
+
arch: string;
|
|
55
|
+
}
|
|
56
|
+
export declare const realInstallRunnerUserDeps: InstallRunnerUserDeps;
|
|
57
|
+
export interface InstallRunnerUserOptions {
|
|
58
|
+
user?: string;
|
|
59
|
+
version?: string;
|
|
60
|
+
/** From GitHub's "New self-hosted runner" page. Both or neither — see the module docblock. */
|
|
61
|
+
githubUrl?: string;
|
|
62
|
+
githubToken?: string;
|
|
63
|
+
/** Shown to GitHub as this runner's name. Default: this Mac's hostname. */
|
|
64
|
+
runnerName?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The exact commands GitHub's own instructions show for registering and starting a downloaded runner, as
|
|
68
|
+
* absolute-path argv arrays (never a shell string: nothing here needs `bash -c` or its quoting) — one
|
|
69
|
+
* function so the "run them now" and "print them for later" paths can never drift apart.
|
|
70
|
+
*/
|
|
71
|
+
export declare function registrationCommands(opts: {
|
|
72
|
+
dir: string;
|
|
73
|
+
user: string;
|
|
74
|
+
githubUrl: string;
|
|
75
|
+
githubToken: string;
|
|
76
|
+
runnerName: string;
|
|
77
|
+
}): {
|
|
78
|
+
config: string[];
|
|
79
|
+
svcInstall: string[];
|
|
80
|
+
svcStart: string[];
|
|
81
|
+
};
|
|
82
|
+
/** The standard scripted-macOS-user-creation idiom: one past the highest uid already in the regular (500+)
|
|
83
|
+
* range. dscl has no "pick one for me" primitive the way System Settings' own UI does. A listing with
|
|
84
|
+
* nothing parseable (including an empty string, `listUserUids`'s own failure value) falls back to 501,
|
|
85
|
+
* macOS's own first regular uid. */
|
|
86
|
+
export declare function nextFreeUid(listing: string): number;
|
|
87
|
+
/** Creates the dedicated account, or leaves it alone if it already exists. Returns its home directory. */
|
|
88
|
+
export declare function ensureRunnerUser(user: string, deps?: InstallRunnerUserDeps): string;
|
|
89
|
+
/** Downloads and extracts the runner release into `<home>/actions-runner`, owned by `user`. A no-op (besides
|
|
90
|
+
* logging) if that directory already looks like a runner install. */
|
|
91
|
+
export declare function ensureRunnerDownloaded(user: string, home: string, version: string, deps?: InstallRunnerUserDeps): string;
|
|
92
|
+
export declare function installRunnerUser(opts?: InstallRunnerUserOptions, deps?: InstallRunnerUserDeps): void;
|
|
93
|
+
//# sourceMappingURL=runner-user.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner-user.d.ts","sourceRoot":"","sources":["../../src/lib/runner-user.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAC/C;qFACqF;AACrF,eAAO,MAAM,sBAAsB,YAAY,CAAC;AAEhD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,KAAK,GAAG,MAAM,CAEhF;AAED;;uFAEuF;AACvF,wBAAgB,UAAU,CAAC,QAAQ,GAAE,MAAqB,GAAG,OAAO,GAAG,KAAK,CAE3E;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC;0GACsG;IACtG,YAAY,IAAI,MAAM,CAAC;IACvB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACvC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC7C,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,yBAAyB,EAAE,qBAkCvC,CAAC;AAEF,MAAM,WAAW,wBAAwB;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8FAA8F;IAC9F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CASjE;AAED;;;qCAGqC;AACrC,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAOnD;AAED,0GAA0G;AAC1G,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,qBAAiD,GAAG,MAAM,CAwB9G;AAED;sEACsE;AACtE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,qBAAiD,GAAG,MAAM,CAgBnJ;AAED,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,wBAA6B,EAAE,IAAI,GAAE,qBAAiD,GAAG,IAAI,CA0BpI"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
// (existsSync/mkdirSync are used only inside realInstallRunnerUserDeps below — every function in this file
|
|
5
|
+
// that needs to touch the filesystem goes through the injectable `deps.exists`/`deps.mkdirp`, the same
|
|
6
|
+
// seam `run`/`tryRun`/`userExists`/`listUserUids` already are, so the whole module is testable without a
|
|
7
|
+
// real `/Users/ci-runner`.)
|
|
8
|
+
/**
|
|
9
|
+
* Gives a GitHub Actions self-hosted runner its OWN, separate macOS user — run ON the target Mac, by the
|
|
10
|
+
* operator, in their own terminal (`appforge-admin agents install-runner-user`).
|
|
11
|
+
*
|
|
12
|
+
* Why. A runner installed as the agent's OWN user exposes every job's environment (CI secrets included) to
|
|
13
|
+
* anything else that runs as that same user, including a regulated build: `sysctl kern.procargs2` lets any
|
|
14
|
+
* same-uid process read another's argv and env, and Seatbelt cannot close that (docs/security.md's "Known
|
|
15
|
+
* gaps"). packages/agent's own `detectSameUidCoHostedRunner` (co-hosted-runner.ts) now refuses a regulated
|
|
16
|
+
* build while that condition holds, but refusing is a workaround, not a fix: giving the runner a DIFFERENT
|
|
17
|
+
* uid removes the condition outright, with no ongoing check needed. The Mac mini in this fleet already has
|
|
18
|
+
* exactly this (its runner runs as `ci-runner`, a separate account) — this command automates making a Mac
|
|
19
|
+
* that doesn't look like that one.
|
|
20
|
+
*
|
|
21
|
+
* What it does, idempotently (safe to re-run):
|
|
22
|
+
* 1. Creates a plain, ordinary (not hidden, not admin) macOS user with its own home directory — a real
|
|
23
|
+
* account, the same kind the operator would create by hand in System Settings, not a system/service
|
|
24
|
+
* account: GitHub's runner sometimes needs a real login session for GUI frameworks (Xcode, the
|
|
25
|
+
* Simulator), which a hidden service account cannot provide. Skips account creation entirely if the
|
|
26
|
+
* user already exists, so re-running never fights with (or resets) an account the operator customised.
|
|
27
|
+
* 2. Downloads and extracts the official GitHub Actions runner release into that account's home, owned
|
|
28
|
+
* by it.
|
|
29
|
+
*
|
|
30
|
+
* What it deliberately does NOT do, and why:
|
|
31
|
+
* - Register the runner with GitHub (`config.sh --url --token`) or install/start its service (`svc.sh`).
|
|
32
|
+
* That needs a registration token the operator copies from GitHub's own UI (Settings → Actions →
|
|
33
|
+
* Runners → "New self-hosted runner") — short-lived (about an hour) and single-use by design, so this
|
|
34
|
+
* command takes it only as an explicit, one-time `--url`/--token pair the operator pastes in, and never
|
|
35
|
+
* stores or re-derives it. Given both, it runs the two commands GitHub's own instructions show, as that
|
|
36
|
+
* user; given neither, it prints those same two commands for the operator to run themselves.
|
|
37
|
+
* - Enable automatic login or any other change to how this Mac's console session works. A runner that
|
|
38
|
+
* needs GUI access (Simulator, keychain unlock) needs a real logged-in session for this user, which is a
|
|
39
|
+
* call about THIS Mac's physical security the operator makes themselves (Lock Screen settings), not
|
|
40
|
+
* something this command decides on their behalf.
|
|
41
|
+
*/
|
|
42
|
+
export const DEFAULT_RUNNER_USER = "ci-runner";
|
|
43
|
+
/** Pinned, update deliberately (same reasoning as MIN_EGRESS_HELPER_VERSION elsewhere): the exact release
|
|
44
|
+
* this command downloads, checked against GitHub's own release list when bumped. */
|
|
45
|
+
export const DEFAULT_RUNNER_VERSION = "2.321.0";
|
|
46
|
+
export function runnerDownloadUrl(version, arch) {
|
|
47
|
+
return `https://github.com/actions/runner/releases/download/v${version}/actions-runner-osx-${arch}-${version}.tar.gz`;
|
|
48
|
+
}
|
|
49
|
+
/** `process.arch` as the runner release asset names it (`x64`, not Node's own `x64`... they happen to agree;
|
|
50
|
+
* arm64 likewise — kept as its own function so a future Node arch string that does NOT agree has one place
|
|
51
|
+
* to fix, and so this is mockable in a test without faking process.arch globally). */
|
|
52
|
+
export function runnerArch(nodeArch = process.arch) {
|
|
53
|
+
return nodeArch === "arm64" ? "arm64" : "x64";
|
|
54
|
+
}
|
|
55
|
+
export const realInstallRunnerUserDeps = {
|
|
56
|
+
userExists(user) {
|
|
57
|
+
try {
|
|
58
|
+
execFileSync("dscl", [".", "-read", `/Users/${user}`, "UniqueID"], { stdio: "ignore" });
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
listUserUids() {
|
|
66
|
+
try {
|
|
67
|
+
return execFileSync("dscl", [".", "-list", "/Users", "UniqueID"], { encoding: "utf8" });
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return "";
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
run(cmd, args) {
|
|
74
|
+
// eslint-disable-next-line no-console
|
|
75
|
+
console.log(` $ ${cmd} ${args.join(" ")}`);
|
|
76
|
+
execFileSync(cmd, args, { stdio: "inherit" });
|
|
77
|
+
},
|
|
78
|
+
tryRun(cmd, args) {
|
|
79
|
+
try {
|
|
80
|
+
execFileSync(cmd, args, { stdio: "ignore" });
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
exists: (path) => existsSync(path),
|
|
88
|
+
mkdirp: (path) => mkdirSync(path, { recursive: true }),
|
|
89
|
+
// eslint-disable-next-line no-console
|
|
90
|
+
log: (l) => console.log(l),
|
|
91
|
+
arch: runnerArch(),
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* The exact commands GitHub's own instructions show for registering and starting a downloaded runner, as
|
|
95
|
+
* absolute-path argv arrays (never a shell string: nothing here needs `bash -c` or its quoting) — one
|
|
96
|
+
* function so the "run them now" and "print them for later" paths can never drift apart.
|
|
97
|
+
*/
|
|
98
|
+
export function registrationCommands(opts) {
|
|
99
|
+
const svc = join(opts.dir, "svc.sh");
|
|
100
|
+
return {
|
|
101
|
+
config: [join(opts.dir, "config.sh"), "--url", opts.githubUrl, "--token", opts.githubToken, "--unattended", "--name", opts.runnerName],
|
|
102
|
+
// `svc.sh install [username]` must be given the target user explicitly: run under `sudo` with none, it
|
|
103
|
+
// would install the service to run as root, which is exactly what this command exists to avoid.
|
|
104
|
+
svcInstall: [svc, "install", opts.user],
|
|
105
|
+
svcStart: [svc, "start"],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/** The standard scripted-macOS-user-creation idiom: one past the highest uid already in the regular (500+)
|
|
109
|
+
* range. dscl has no "pick one for me" primitive the way System Settings' own UI does. A listing with
|
|
110
|
+
* nothing parseable (including an empty string, `listUserUids`'s own failure value) falls back to 501,
|
|
111
|
+
* macOS's own first regular uid. */
|
|
112
|
+
export function nextFreeUid(listing) {
|
|
113
|
+
let max = 500;
|
|
114
|
+
for (const line of listing.split("\n")) {
|
|
115
|
+
const m = /\s(\d+)\s*$/.exec(line);
|
|
116
|
+
if (m)
|
|
117
|
+
max = Math.max(max, Number(m[1]));
|
|
118
|
+
}
|
|
119
|
+
return max + 1;
|
|
120
|
+
}
|
|
121
|
+
/** Creates the dedicated account, or leaves it alone if it already exists. Returns its home directory. */
|
|
122
|
+
export function ensureRunnerUser(user, deps = realInstallRunnerUserDeps) {
|
|
123
|
+
const home = `/Users/${user}`;
|
|
124
|
+
if (deps.userExists(user)) {
|
|
125
|
+
deps.log(` user '${user}' already exists, leaving it alone`);
|
|
126
|
+
return home;
|
|
127
|
+
}
|
|
128
|
+
deps.log(`\n==> Creating the dedicated '${user}' account (so this Mac's CI runner no longer shares a uid with the build agent)`);
|
|
129
|
+
const uid = nextFreeUid(deps.listUserUids());
|
|
130
|
+
const gid = uid;
|
|
131
|
+
deps.run("sudo", ["dscl", ".", "-create", `/Groups/${user}`]);
|
|
132
|
+
deps.run("sudo", ["dscl", ".", "-create", `/Groups/${user}`, "PrimaryGroupID", String(gid)]);
|
|
133
|
+
deps.run("sudo", ["dscl", ".", "-create", `/Users/${user}`]);
|
|
134
|
+
deps.run("sudo", ["dscl", ".", "-create", `/Users/${user}`, "UserShell", "/bin/zsh"]);
|
|
135
|
+
deps.run("sudo", ["dscl", ".", "-create", `/Users/${user}`, "RealName", "AppForge CI runner (dedicated)"]);
|
|
136
|
+
deps.run("sudo", ["dscl", ".", "-create", `/Users/${user}`, "UniqueID", String(uid)]);
|
|
137
|
+
deps.run("sudo", ["dscl", ".", "-create", `/Users/${user}`, "PrimaryGroupID", String(gid)]);
|
|
138
|
+
deps.run("sudo", ["dscl", ".", "-create", `/Users/${user}`, "NFSHomeDirectory", home]);
|
|
139
|
+
// No -create .../Password at all: an account with no AuthenticationAuthority entry cannot authenticate by
|
|
140
|
+
// password until one is set. `createhomedir` below needs no password to run (root does it on the
|
|
141
|
+
// account's behalf). Deliberately NOT marking IsHidden — see the module docblock on why this is an
|
|
142
|
+
// ordinary account, not a service account.
|
|
143
|
+
deps.run("sudo", ["createhomedir", "-c", "-u", user]);
|
|
144
|
+
deps.log(` created ${home} (uid ${uid})`);
|
|
145
|
+
return home;
|
|
146
|
+
}
|
|
147
|
+
/** Downloads and extracts the runner release into `<home>/actions-runner`, owned by `user`. A no-op (besides
|
|
148
|
+
* logging) if that directory already looks like a runner install. */
|
|
149
|
+
export function ensureRunnerDownloaded(user, home, version, deps = realInstallRunnerUserDeps) {
|
|
150
|
+
const dir = join(home, "actions-runner");
|
|
151
|
+
if (deps.exists(join(dir, "config.sh"))) {
|
|
152
|
+
deps.log(` ${dir} already has a runner install, leaving it alone`);
|
|
153
|
+
return dir;
|
|
154
|
+
}
|
|
155
|
+
deps.log(`\n==> Downloading the GitHub Actions runner v${version} (${deps.arch})`);
|
|
156
|
+
deps.mkdirp(dir);
|
|
157
|
+
const url = runnerDownloadUrl(version, deps.arch);
|
|
158
|
+
const tarball = join(dir, `actions-runner.tar.gz`);
|
|
159
|
+
deps.run("curl", ["-fsSL", "-o", tarball, url]);
|
|
160
|
+
deps.run("tar", ["-xzf", tarball, "-C", dir]);
|
|
161
|
+
deps.run("rm", [tarball]);
|
|
162
|
+
deps.run("sudo", ["chown", "-R", `${user}:${user}`, dir]);
|
|
163
|
+
deps.log(` extracted to ${dir}`);
|
|
164
|
+
return dir;
|
|
165
|
+
}
|
|
166
|
+
export function installRunnerUser(opts = {}, deps = realInstallRunnerUserDeps) {
|
|
167
|
+
const user = opts.user ?? DEFAULT_RUNNER_USER;
|
|
168
|
+
const version = opts.version ?? DEFAULT_RUNNER_VERSION;
|
|
169
|
+
const home = ensureRunnerUser(user, deps);
|
|
170
|
+
const dir = ensureRunnerDownloaded(user, home, version, deps);
|
|
171
|
+
const runnerName = opts.runnerName ?? (deps.tryRun("hostname", []) ? execFileSync("hostname", { encoding: "utf8" }).trim() : "mac-runner");
|
|
172
|
+
if (opts.githubUrl && opts.githubToken) {
|
|
173
|
+
const { config, svcInstall, svcStart } = registrationCommands({ dir, user, githubUrl: opts.githubUrl, githubToken: opts.githubToken, runnerName });
|
|
174
|
+
deps.log(`\n==> Registering the runner with GitHub as '${runnerName}'`);
|
|
175
|
+
deps.run("sudo", ["-u", user, "--", ...config]);
|
|
176
|
+
deps.log("\n==> Installing and starting the runner service");
|
|
177
|
+
deps.run("sudo", svcInstall);
|
|
178
|
+
deps.run("sudo", svcStart);
|
|
179
|
+
deps.log(`\nDone. '${user}' now runs its own GitHub Actions jobs, out of reach of anything that runs as this Mac's agent user.`);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
const { config, svcInstall, svcStart } = registrationCommands({ dir, user, githubUrl: "<url>", githubToken: "<token>", runnerName });
|
|
183
|
+
deps.log(`\nNot registered yet — both --github-url and --github-token are needed for that (from this repo's Settings → Actions →\n` +
|
|
184
|
+
`Runners → "New self-hosted runner"; the token is short-lived, so copy it right before running this). Either re-run this\n` +
|
|
185
|
+
`command with both flags, or finish by hand as the '${user}' user:\n\n` +
|
|
186
|
+
` sudo -u ${user} -- ${config.join(" ")}\n` +
|
|
187
|
+
` sudo ${svcInstall.join(" ")}\n` +
|
|
188
|
+
` sudo ${svcStart.join(" ")}\n`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=runner-user.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner-user.js","sourceRoot":"","sources":["../../src/lib/runner-user.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner-user.test.d.ts","sourceRoot":"","sources":["../../src/lib/runner-user.test.ts"],"names":[],"mappings":"AAAA,OAAO,iCAAiC,CAAC"}
|