@ebowwa/seedinstallation 0.3.0 → 0.4.1

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/index.js CHANGED
@@ -1,10 +1,943 @@
1
- export { clone } from "./clone.js";
2
- export { sudo, pkgInstall, writeFile, service, serviceEnable } from "./sudo.js";
3
- // Runtime installation (Bun, Node, PATH, symlinks)
4
- export { installBun, addSystemPath, symlink, linkBinaries, getBunVersion, commandExists, } from "./runtime.js";
5
- // Systemd service management
6
- export { generateServiceUnit, createServiceUnit, enableService, disableService, startService, stopService, restartService, reloadService, getServiceStatus, enableAndStartService, serviceExists, getServiceLogs, } from "./systemd.js";
7
- // Device-code authentication flow (Doppler, GitHub, Tailscale)
8
- export { deviceAuth, isAuthed, cleanupDeviceAuth, stripAnsi, dopplerConfig, githubConfig, tailscaleConfig, } from "./device-auth.js";
9
- // Bootstrap status tracking and polling
10
- export { getBootstrapStatus, parseBootstrapStatus, initBootstrap, startPhase, completePhase, failPhase, completeBootstrap, failBootstrap, checkMarker, setMarker, removeMarker, waitForBootstrap, waitForMarker, } from "./bootstrap.js";
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, {
5
+ get: all[name],
6
+ enumerable: true,
7
+ configurable: true,
8
+ set: (newValue) => all[name] = () => newValue
9
+ });
10
+ };
11
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
12
+
13
+ // sudo.ts
14
+ var exports_sudo = {};
15
+ __export(exports_sudo, {
16
+ writeFile: () => writeFile,
17
+ sudo: () => sudo,
18
+ serviceEnable: () => serviceEnable,
19
+ service: () => service,
20
+ pkgInstall: () => pkgInstall,
21
+ exec: () => exec
22
+ });
23
+ async function sudo(cmd, opts) {
24
+ const parts = Array.isArray(cmd) ? cmd : cmd.split(/\s+/);
25
+ const envPrefix = opts.env ? Object.entries(opts.env).map(([k, v]) => `${k}=${shellEscape(v)}`) : [];
26
+ const sudoCmd = ["sudo", ...envPrefix, ...parts];
27
+ return exec(sudoCmd, opts);
28
+ }
29
+ async function pkgInstall(packages, opts) {
30
+ const pm = opts.pm ?? "apt";
31
+ const envOverride = {
32
+ ...opts.env,
33
+ ...opts.nonInteractive !== false ? { DEBIAN_FRONTEND: "noninteractive" } : {}
34
+ };
35
+ await sudo(updateCmd[pm], { ...opts, env: envOverride, quiet: true });
36
+ return sudo([...installCmd[pm], ...packages], {
37
+ ...opts,
38
+ env: envOverride
39
+ });
40
+ }
41
+ async function writeFile(path, content, opts) {
42
+ const op = opts.append ? "-a" : "";
43
+ const teeCmd = `tee ${op} ${shellEscape(path)}`.trim();
44
+ const result = await execPipe(content, ["sudo", teeCmd], opts);
45
+ if (result.ok && opts.mode) {
46
+ await sudo(["chmod", opts.mode, path], opts);
47
+ }
48
+ if (result.ok && opts.owner) {
49
+ await sudo(["chown", opts.owner, path], opts);
50
+ }
51
+ return result;
52
+ }
53
+ async function service(name, action, opts) {
54
+ return sudo(["systemctl", action, name], opts);
55
+ }
56
+ async function serviceEnable(name, opts) {
57
+ return sudo(["systemctl", "enable", "--now", name], opts);
58
+ }
59
+ function buildSshPrefix(ctx) {
60
+ const parts = ["ssh", "-F", "/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"];
61
+ if (ctx.keyPath)
62
+ parts.push("-i", ctx.keyPath);
63
+ else if (ctx.key)
64
+ parts.push("-i", ctx.key);
65
+ if (ctx.port)
66
+ parts.push("-p", String(ctx.port));
67
+ parts.push(`${ctx.user ?? "root"}@${ctx.host}`);
68
+ return parts;
69
+ }
70
+ async function exec(args, opts) {
71
+ const ctx = opts.context ?? { type: "local" };
72
+ const finalArgs = ctx.type === "ssh" ? [...buildSshPrefix(ctx), args.map(shellEscape).join(" ")] : args;
73
+ const proc = Bun.spawn(finalArgs, {
74
+ stdout: "pipe",
75
+ stderr: "pipe",
76
+ timeout: opts.timeout ?? 30000
77
+ });
78
+ const exitCode = await proc.exited;
79
+ const stdout = opts.quiet ? "" : await new Response(proc.stdout).text();
80
+ const stderr = await new Response(proc.stderr).text();
81
+ return { stdout, stderr, exitCode, ok: exitCode === 0 };
82
+ }
83
+ async function execPipe(input, args, opts) {
84
+ const ctx = opts.context ?? { type: "local" };
85
+ if (ctx.type === "ssh") {
86
+ const sshPrefix = buildSshPrefix(ctx);
87
+ const remoteCmd = args.join(" ");
88
+ const fullArgs = [...sshPrefix, remoteCmd];
89
+ const proc2 = Bun.spawn(fullArgs, {
90
+ stdin: new TextEncoder().encode(input),
91
+ stdout: "pipe",
92
+ stderr: "pipe",
93
+ timeout: opts.timeout ?? 30000
94
+ });
95
+ const exitCode2 = await proc2.exited;
96
+ const stdout2 = opts.quiet ? "" : await new Response(proc2.stdout).text();
97
+ const stderr2 = await new Response(proc2.stderr).text();
98
+ return { stdout: stdout2, stderr: stderr2, exitCode: exitCode2, ok: exitCode2 === 0 };
99
+ }
100
+ const proc = Bun.spawn(["sh", "-c", args.join(" ")], {
101
+ stdin: new TextEncoder().encode(input),
102
+ stdout: "pipe",
103
+ stderr: "pipe",
104
+ timeout: opts.timeout ?? 30000
105
+ });
106
+ const exitCode = await proc.exited;
107
+ const stdout = opts.quiet ? "" : await new Response(proc.stdout).text();
108
+ const stderr = await new Response(proc.stderr).text();
109
+ return { stdout, stderr, exitCode, ok: exitCode === 0 };
110
+ }
111
+ function shellEscape(s) {
112
+ if (/^[a-zA-Z0-9._\-\/=:@]+$/.test(s))
113
+ return s;
114
+ return `'${s.replace(/'/g, "'\\''")}'`;
115
+ }
116
+ var installCmd, updateCmd;
117
+ var init_sudo = __esm(() => {
118
+ installCmd = {
119
+ apt: ["apt-get", "install", "-y"],
120
+ dnf: ["dnf", "install", "-y"],
121
+ apk: ["apk", "add", "--no-cache"]
122
+ };
123
+ updateCmd = {
124
+ apt: ["apt-get", "update", "-qq"],
125
+ dnf: ["dnf", "check-update"],
126
+ apk: ["apk", "update"]
127
+ };
128
+ });
129
+
130
+ // clone.ts
131
+ async function clone(opts) {
132
+ const { repo, dest, ref, depth, sparse, cwd } = opts;
133
+ const repoName = dest ?? repoNameFrom(repo);
134
+ const targetDir = cwd ? `${cwd}/${repoName}` : repoName;
135
+ const args = ["git", "clone"];
136
+ if (depth)
137
+ args.push("--depth", String(depth));
138
+ if (ref && !sparse)
139
+ args.push("--branch", ref);
140
+ if (sparse)
141
+ args.push("--no-checkout", "--filter=blob:none");
142
+ args.push(repo, targetDir);
143
+ await run(args, cwd);
144
+ if (sparse) {
145
+ await run(["git", "sparse-checkout", "init", "--cone"], targetDir);
146
+ await run(["git", "sparse-checkout", "set", ...sparse], targetDir);
147
+ if (ref) {
148
+ await run(["git", "checkout", ref], targetDir);
149
+ } else {
150
+ await run(["git", "checkout"], targetDir);
151
+ }
152
+ }
153
+ const commit = (await run(["git", "rev-parse", "--short", "HEAD"], targetDir)).trim();
154
+ const checkedRef = (await run(["git", "rev-parse", "--abbrev-ref", "HEAD"], targetDir)).trim();
155
+ const absPath = (await run(["git", "rev-parse", "--show-toplevel"], targetDir)).trim();
156
+ return { path: absPath, ref: checkedRef, commit };
157
+ }
158
+ function repoNameFrom(url) {
159
+ return url.replace(/\.git$/, "").split("/").pop();
160
+ }
161
+ async function run(args, cwd) {
162
+ const proc = Bun.spawn(args, {
163
+ cwd,
164
+ stdout: "pipe",
165
+ stderr: "pipe"
166
+ });
167
+ const exitCode = await proc.exited;
168
+ const stdout = await new Response(proc.stdout).text();
169
+ if (exitCode !== 0) {
170
+ const stderr = await new Response(proc.stderr).text();
171
+ throw new Error(`${args.join(" ")} failed (exit ${exitCode}): ${stderr}`);
172
+ }
173
+ return stdout;
174
+ }
175
+
176
+ // runtime.ts
177
+ async function exec2(args, opts) {
178
+ const sudo2 = await Promise.resolve().then(() => (init_sudo(), exports_sudo)).then((m) => m.sudo);
179
+ return sudo2(args, opts);
180
+ }
181
+ async function installBun(opts = {}) {
182
+ const installCmd2 = opts.version ? `curl -fsSL https://bun.sh/install | bash -s "bun-${opts.version}"` : `curl -fsSL https://bun.sh/install | bash`;
183
+ await execShell(installCmd2, opts);
184
+ if (opts.systemWide) {
185
+ const bunPath = opts.installDir ?? "/root/.bun";
186
+ await addSystemPath(`${bunPath}/bin`, opts);
187
+ }
188
+ }
189
+ async function addSystemPath(dir, opts = {}) {
190
+ const { writeFile: writeFile2 } = await Promise.resolve().then(() => (init_sudo(), exports_sudo));
191
+ const target = opts.target ?? "system";
192
+ const position = opts.position ?? "prepend";
193
+ const existingPath = target === "system" ? await getSystemPath(opts) : await getUserPath(opts);
194
+ const paths = existingPath.split(":");
195
+ if (paths.includes(dir)) {
196
+ return;
197
+ }
198
+ const newPath = position === "prepend" ? `${dir}:${existingPath}` : `${existingPath}:${dir}`;
199
+ if (target === "system") {
200
+ const content = `PATH="${newPath}"
201
+ `;
202
+ await writeFile2("/etc/environment", content, { ...opts, append: false });
203
+ } else {
204
+ const exportLine = `
205
+ export PATH="${newPath}"
206
+ `;
207
+ await writeFile2("/root/.bashrc", exportLine, { ...opts, append: true });
208
+ }
209
+ }
210
+ async function getSystemPath(opts) {
211
+ const result = await exec2(["grep", "^PATH=", "/etc/environment"], { ...opts, quiet: true });
212
+ if (result.ok && result.stdout) {
213
+ const match = result.stdout.match(/^PATH="([^"]+)"/);
214
+ if (match?.[1])
215
+ return match[1];
216
+ }
217
+ return "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
218
+ }
219
+ async function getUserPath(opts) {
220
+ const result = await exec2(["bash", "-lc", "echo $PATH"], { ...opts, quiet: true });
221
+ return result.stdout.trim();
222
+ }
223
+ async function symlink(source, target, opts = {}) {
224
+ const args = ["ln", "-s"];
225
+ if (opts.force)
226
+ args.push("-f");
227
+ args.push(source, target);
228
+ await exec2(args, opts);
229
+ }
230
+ async function linkBinaries(sourceDir, opts = {}) {
231
+ const result = await exec2(["ls", "-1", sourceDir], { ...opts, quiet: true });
232
+ if (!result.ok)
233
+ return;
234
+ const binaries = result.stdout.trim().split(`
235
+ `).filter(Boolean);
236
+ for (const bin of binaries) {
237
+ const source = `${sourceDir}/${bin}`;
238
+ const target = `/usr/local/bin/${bin}`;
239
+ await symlink(source, target, { ...opts, force: true });
240
+ }
241
+ }
242
+ async function getBunVersion(opts) {
243
+ const result = await exec2(["bash", "-lc", "bun --version"], { ...opts, quiet: true });
244
+ if (result.ok) {
245
+ return result.stdout.trim();
246
+ }
247
+ return null;
248
+ }
249
+ async function commandExists(cmd, opts) {
250
+ const result = await exec2(["which", cmd], { ...opts, quiet: true });
251
+ return result.ok;
252
+ }
253
+ async function execShell(cmd, opts) {
254
+ const result = await exec2(["bash", "-lc", cmd], opts);
255
+ if (!result.ok) {
256
+ throw new Error(`Shell command failed: ${result.stderr}`);
257
+ }
258
+ }
259
+
260
+ // systemd.ts
261
+ async function exec3(args, opts) {
262
+ const { sudo: sudo2 } = await Promise.resolve().then(() => (init_sudo(), exports_sudo));
263
+ return sudo2(args, opts);
264
+ }
265
+ async function writeFile2(path, content, opts) {
266
+ const { writeFile: wf } = await Promise.resolve().then(() => (init_sudo(), exports_sudo));
267
+ return wf(path, content, opts);
268
+ }
269
+ var systemdCheckCache = null;
270
+ async function hasSystemd(opts) {
271
+ if (systemdCheckCache) {
272
+ return systemdCheckCache;
273
+ }
274
+ const sudoOpts = opts ?? { context: { type: "local" } };
275
+ const whichResult = await exec3(["which", "systemctl"], {
276
+ ...sudoOpts,
277
+ quiet: true,
278
+ env: sudoOpts.env
279
+ });
280
+ if (!whichResult.ok || !whichResult.stdout.trim()) {
281
+ const result2 = {
282
+ available: false,
283
+ reason: "not_found",
284
+ message: "systemctl command not found - systemd is not installed or not in PATH"
285
+ };
286
+ systemdCheckCache = result2;
287
+ return result2;
288
+ }
289
+ const runningResult = await exec3(["systemctl", "is-system-running"], {
290
+ ...sudoOpts,
291
+ quiet: true,
292
+ env: sudoOpts.env
293
+ });
294
+ if (runningResult.ok && runningResult.stdout.trim() !== "") {
295
+ const result2 = {
296
+ available: true,
297
+ message: "systemd is available and running"
298
+ };
299
+ systemdCheckCache = result2;
300
+ return result2;
301
+ }
302
+ const dirResult = await exec3(["test", "-d", "/run/systemd/system"], {
303
+ ...sudoOpts,
304
+ quiet: true,
305
+ env: sudoOpts.env
306
+ });
307
+ if (dirResult.ok) {
308
+ const result2 = {
309
+ available: true,
310
+ message: "systemd detected via /run/systemd/system (container environment)"
311
+ };
312
+ systemdCheckCache = result2;
313
+ return result2;
314
+ }
315
+ const result = {
316
+ available: false,
317
+ reason: "not_running",
318
+ message: "systemd is not available on this system (container/initless environment)"
319
+ };
320
+ systemdCheckCache = result;
321
+ return result;
322
+ }
323
+ function clearSystemdCache() {
324
+ systemdCheckCache = null;
325
+ }
326
+ function generateServiceUnit(name, opts) {
327
+ const lines = [];
328
+ lines.push("[Unit]");
329
+ lines.push(`Description=${opts.description}`);
330
+ if (opts.after?.length) {
331
+ lines.push(`After=${opts.after.join(" ")}`);
332
+ }
333
+ if (opts.wants?.length) {
334
+ lines.push(`Wants=${opts.wants.join(" ")}`);
335
+ }
336
+ lines.push("");
337
+ lines.push("[Service]");
338
+ if (opts.type)
339
+ lines.push(`Type=${opts.type}`);
340
+ lines.push(`User=${opts.user ?? "root"}`);
341
+ if (opts.group)
342
+ lines.push(`Group=${opts.group}`);
343
+ lines.push(`WorkingDirectory=${opts.workingDirectory}`);
344
+ lines.push(`ExecStart=${opts.execStart}`);
345
+ if (opts.execStartPre?.length) {
346
+ opts.execStartPre.forEach((cmd, i) => {
347
+ lines.push(`ExecStartPre${i > 0 ? `=${i}` : ""}=${cmd}`);
348
+ });
349
+ }
350
+ if (opts.execStopPost?.length) {
351
+ opts.execStopPost.forEach((cmd, i) => {
352
+ lines.push(`ExecStopPost${i > 0 ? `=${i}` : ""}=${cmd}`);
353
+ });
354
+ }
355
+ if (opts.restart)
356
+ lines.push(`Restart=${opts.restart}`);
357
+ if (opts.restartSec)
358
+ lines.push(`RestartSec=${opts.restartSec}`);
359
+ if (opts.environment) {
360
+ Object.entries(opts.environment).forEach(([k, v]) => {
361
+ lines.push(`Environment="${k}=${v}"`);
362
+ });
363
+ }
364
+ if (opts.environmentFile) {
365
+ lines.push(`EnvironmentFile=${opts.environmentFile}`);
366
+ }
367
+ if (opts.standardOutput)
368
+ lines.push(`StandardOutput=${opts.standardOutput}`);
369
+ if (opts.standardError)
370
+ lines.push(`StandardError=${opts.standardError}`);
371
+ if (opts.nice !== undefined)
372
+ lines.push(`Nice=${opts.nice}`);
373
+ if (opts.noNewPrivileges)
374
+ lines.push("NoNewPrivileges=true");
375
+ if (opts.privateTmp)
376
+ lines.push("PrivateTmp=true");
377
+ if (opts.protectSystem)
378
+ lines.push(`ProtectSystem=${opts.protectSystem}`);
379
+ if (opts.protectHome === true)
380
+ lines.push("ProtectHome=true");
381
+ else if (opts.protectHome)
382
+ lines.push(`ProtectHome=${opts.protectHome}`);
383
+ if (opts.readOnlyPaths?.length)
384
+ lines.push(`ReadOnlyPaths=${opts.readOnlyPaths.join(" ")}`);
385
+ if (opts.readWritePaths?.length)
386
+ lines.push(`ReadWritePaths=${opts.readWritePaths.join(" ")}`);
387
+ if (opts.restrictRealtime)
388
+ lines.push("RestrictRealtime=true");
389
+ if (opts.memoryDenyWriteExecute)
390
+ lines.push("MemoryDenyWriteExecute=true");
391
+ if (opts.protectKernelTunables)
392
+ lines.push("ProtectKernelTunables=true");
393
+ if (opts.protectKernelModules)
394
+ lines.push("ProtectKernelModules=true");
395
+ if (opts.protectControlGroups)
396
+ lines.push("ProtectControlGroups=true");
397
+ if (opts.restrictAddressFamilies?.length)
398
+ lines.push(`RestrictAddressFamilies=${opts.restrictAddressFamilies.join(" ")}`);
399
+ if (opts.deviceAllow?.length)
400
+ lines.push(`DeviceAllow=${opts.deviceAllow.join(" ")}`);
401
+ if (opts.devicePolicy)
402
+ lines.push(`DevicePolicy=${opts.devicePolicy}`);
403
+ if (opts.systemCallFilter?.length)
404
+ lines.push(`SystemCallFilter=${opts.systemCallFilter.join(" ")}`);
405
+ if (opts.restrictNamespaces === true)
406
+ lines.push("RestrictNamespaces=true");
407
+ else if (Array.isArray(opts.restrictNamespaces) && opts.restrictNamespaces.length > 0)
408
+ lines.push(`RestrictNamespaces=${opts.restrictNamespaces.join(" ")}`);
409
+ if (opts.personality)
410
+ lines.push(`Personality=${opts.personality}`);
411
+ if (opts.lockPersonality)
412
+ lines.push("LockPersonality=true");
413
+ if (opts.removeCapability?.length)
414
+ opts.removeCapability.forEach((cap) => lines.push(`RemoveCapability=${cap}`));
415
+ if (opts.capabilityBoundingSet?.length)
416
+ lines.push(`CapabilityBoundingSet=${opts.capabilityBoundingSet.join(" ")}`);
417
+ if (opts.ambientCapabilities?.length)
418
+ lines.push(`AmbientCapabilities=${opts.ambientCapabilities.join(" ")}`);
419
+ if (opts.privateDevices)
420
+ lines.push("PrivateDevices=true");
421
+ if (opts.protectKernelLogs)
422
+ lines.push("ProtectKernelLogs=true");
423
+ if (opts.extras) {
424
+ lines.push("");
425
+ lines.push(opts.extras);
426
+ }
427
+ return lines.join(`
428
+ `);
429
+ }
430
+ async function createServiceUnit(name, opts) {
431
+ const unitPath = `/etc/systemd/system/${name}.service`;
432
+ const content = generateServiceUnit(name, opts);
433
+ const writeResult = await writeFile2(unitPath, content, {
434
+ context: opts.context,
435
+ mode: "644",
436
+ env: opts.env,
437
+ timeout: opts.timeout
438
+ });
439
+ if (!writeResult.ok) {
440
+ return writeResult;
441
+ }
442
+ await exec3(["systemctl", "daemon-reload"], opts);
443
+ return {
444
+ ...writeResult,
445
+ unitPath
446
+ };
447
+ }
448
+ async function enableService(name, opts) {
449
+ return exec3(["systemctl", "enable", name], opts);
450
+ }
451
+ async function disableService(name, opts) {
452
+ return exec3(["systemctl", "disable", name], opts);
453
+ }
454
+ async function startService(name, opts) {
455
+ return exec3(["systemctl", "start", name], opts);
456
+ }
457
+ async function stopService(name, opts) {
458
+ return exec3(["systemctl", "stop", name], opts);
459
+ }
460
+ async function restartService(name, opts) {
461
+ return exec3(["systemctl", "restart", name], opts);
462
+ }
463
+ async function reloadService(name, opts) {
464
+ return exec3(["systemctl", "reload", name], opts);
465
+ }
466
+ async function getServiceStatus(name, opts) {
467
+ const result = await exec3(["systemctl", "show", name, "--property=LoadState,ActiveState,SubState,MainPID,Description"], {
468
+ ...opts,
469
+ quiet: true
470
+ });
471
+ if (!result.ok) {
472
+ return { loaded: false, active: false, subState: "unknown", mainPid: 0, description: "" };
473
+ }
474
+ const parse = (key) => {
475
+ const match = result.stdout.match(new RegExp(`^${key}=(.+)$`, "m"));
476
+ return match?.[1]?.trim() ?? "";
477
+ };
478
+ return {
479
+ loaded: parse("LoadState") === "loaded",
480
+ active: parse("ActiveState") === "active",
481
+ subState: parse("SubState"),
482
+ mainPid: parseInt(parse("MainPID") || "0", 10),
483
+ description: parse("Description")
484
+ };
485
+ }
486
+ async function enableAndStartService(name, opts) {
487
+ return exec3(["systemctl", "enable", "--now", name], opts);
488
+ }
489
+ async function serviceExists(name, opts) {
490
+ const result = await exec3(["systemctl", "list-unit-files", name + ".service"], {
491
+ ...opts,
492
+ quiet: true
493
+ });
494
+ return result.ok && result.stdout.includes(name);
495
+ }
496
+ async function getServiceLogs(name, opts = {}) {
497
+ const args = ["journalctl", "-u", name, "-n", String(opts.lines ?? 100)];
498
+ if (opts.since)
499
+ args.push("--since", opts.since);
500
+ if (!opts.follow)
501
+ args.push("-n", String(opts.lines ?? 100));
502
+ const result = await exec3(args, opts);
503
+ return result.stdout;
504
+ }
505
+ var SECURITY_PRESETS = {
506
+ strict: {
507
+ noNewPrivileges: true,
508
+ privateTmp: true,
509
+ protectSystem: "strict",
510
+ protectHome: true,
511
+ readOnlyPaths: ["/"],
512
+ readWritePaths: [],
513
+ restrictRealtime: true,
514
+ memoryDenyWriteExecute: true,
515
+ protectKernelTunables: true,
516
+ protectKernelModules: true,
517
+ protectControlGroups: true,
518
+ restrictAddressFamilies: ["AF_UNIX", "AF_INET", "AF_INET6"],
519
+ devicePolicy: "closed",
520
+ deviceAllow: [],
521
+ restrictNamespaces: true,
522
+ privateDevices: true,
523
+ protectKernelLogs: true,
524
+ lockPersonality: true,
525
+ capabilityBoundingSet: []
526
+ },
527
+ standard: {
528
+ noNewPrivileges: true,
529
+ privateTmp: true,
530
+ protectSystem: "full",
531
+ protectHome: true,
532
+ restrictRealtime: true,
533
+ memoryDenyWriteExecute: true,
534
+ protectKernelTunables: true,
535
+ protectKernelModules: true,
536
+ protectControlGroups: true,
537
+ restrictAddressFamilies: ["AF_UNIX", "AF_INET", "AF_INET6"],
538
+ devicePolicy: "auto",
539
+ restrictNamespaces: true,
540
+ privateDevices: true,
541
+ capabilityBoundingSet: ["CAP_NET_BIND_SERVICE"]
542
+ },
543
+ network: {
544
+ noNewPrivileges: true,
545
+ privateTmp: true,
546
+ protectSystem: "full",
547
+ protectHome: true,
548
+ restrictRealtime: true,
549
+ protectKernelTunables: true,
550
+ protectKernelModules: true,
551
+ protectControlGroups: true,
552
+ restrictAddressFamilies: ["AF_UNIX", "AF_INET", "AF_INET6"],
553
+ devicePolicy: "auto",
554
+ privateDevices: true,
555
+ capabilityBoundingSet: ["CAP_NET_BIND_SERVICE"],
556
+ ambientCapabilities: ["CAP_NET_BIND_SERVICE"]
557
+ },
558
+ minimal: {
559
+ noNewPrivileges: true,
560
+ privateTmp: true,
561
+ protectSystem: "yes",
562
+ restrictRealtime: true
563
+ },
564
+ deviceAccess: {
565
+ noNewPrivileges: true,
566
+ privateTmp: true,
567
+ protectSystem: "full",
568
+ protectHome: true,
569
+ restrictRealtime: true,
570
+ memoryDenyWriteExecute: true,
571
+ protectKernelTunables: true,
572
+ protectKernelModules: true,
573
+ protectControlGroups: true,
574
+ devicePolicy: "auto",
575
+ restrictNamespaces: true,
576
+ privateDevices: false
577
+ }
578
+ };
579
+ function withSecurityPreset(preset, customOptions) {
580
+ return {
581
+ ...SECURITY_PRESETS[preset],
582
+ ...customOptions
583
+ };
584
+ }
585
+ async function safeSystemd(operation, options = {}) {
586
+ const { requireSystemd = true, onNoSystemd, context, env, timeout, quiet } = options;
587
+ const sudoOpts = context ? { context, env, timeout, quiet } : { context: { type: "local" }, env, timeout, quiet };
588
+ const check = await hasSystemd(sudoOpts);
589
+ if (!check.available) {
590
+ if (onNoSystemd) {
591
+ await onNoSystemd(check);
592
+ }
593
+ if (requireSystemd) {
594
+ throw new Error(`Systemd required but not available: ${check.message}`);
595
+ }
596
+ return;
597
+ }
598
+ return operation();
599
+ }
600
+
601
+ // device-auth.ts
602
+ async function exec4(args, opts) {
603
+ const { sudo: sudo2 } = await Promise.resolve().then(() => (init_sudo(), exports_sudo));
604
+ return sudo2(args, opts);
605
+ }
606
+ function stripAnsi(text) {
607
+ return text.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
608
+ }
609
+ async function deviceAuth(config, opts = {}) {
610
+ const {
611
+ cli,
612
+ loginCmd,
613
+ statusCmd,
614
+ patterns,
615
+ logFile,
616
+ pidFile,
617
+ statusFlags
618
+ } = config;
619
+ const maxAttempts = opts.maxAttempts ?? 60;
620
+ const intervalMs = opts.intervalMs ?? 2000;
621
+ const loginCmdStr = loginCmd ?? `${cli} login`;
622
+ const startResult = await exec4([`bash`, `-lc`, `nohup ${loginCmdStr} > ${logFile} 2>&1 & echo $!`], opts);
623
+ if (!startResult.ok) {
624
+ return { success: false, error: `Failed to start login: ${startResult.stderr}` };
625
+ }
626
+ const pid = startResult.stdout.trim();
627
+ if (pidFile) {
628
+ await exec4(["bash", `-lc`, `echo ${pid} > ${pidFile}`], opts);
629
+ }
630
+ await sleep(1000);
631
+ const extractResult = await exec4(["cat", logFile], { ...opts, quiet: false });
632
+ const logContent = stripAnsi(extractResult.stdout);
633
+ const urlMatch = patterns.url.exec(logContent);
634
+ const codeMatch = patterns.code.exec(logContent);
635
+ const url = urlMatch?.[1];
636
+ const code = codeMatch?.[1];
637
+ if (!url || !code) {
638
+ return {
639
+ success: false,
640
+ error: `Could not extract auth URL/code from log output. Log content:
641
+ ${logContent}`
642
+ };
643
+ }
644
+ for (let attempt = 1;attempt <= maxAttempts; attempt++) {
645
+ const statusCmdStr = statusCmd ?? `${cli} status`;
646
+ const statusArgs = ["bash", "-lc", statusCmdStr];
647
+ if (statusFlags?.length)
648
+ statusArgs.push(...statusFlags);
649
+ const statusResult = await exec4(statusArgs, { ...opts, quiet: false });
650
+ const statusOutput = stripAnsi(statusResult.stdout);
651
+ if (patterns.success.test(statusOutput)) {
652
+ return {
653
+ success: true,
654
+ url,
655
+ code,
656
+ status: statusOutput
657
+ };
658
+ }
659
+ opts.onPoll?.(attempt, { success: false, url, code, status: statusOutput });
660
+ await sleep(intervalMs);
661
+ }
662
+ return {
663
+ success: false,
664
+ url,
665
+ code,
666
+ error: `Login did not complete after ${maxAttempts} attempts. Last status: ${await getStatusOutput(config, opts)}`
667
+ };
668
+ }
669
+ async function getStatusOutput(config, opts) {
670
+ const statusCmdStr = config.statusCmd ?? `${config.cli} status`;
671
+ const result = await exec4(["bash", "-lc", statusCmdStr], { ...opts, quiet: false });
672
+ return stripAnsi(result.stdout);
673
+ }
674
+ async function isAuthed(config, opts) {
675
+ const statusCmdStr = config.statusCmd ?? `${config.cli} status`;
676
+ const result = await exec4(["bash", "-lc", statusCmdStr], { ...opts, quiet: false });
677
+ const output = stripAnsi(result.stdout);
678
+ return config.patterns.success.test(output);
679
+ }
680
+ async function cleanupDeviceAuth(config, opts) {
681
+ if (config.pidFile) {
682
+ const pidResult = await exec4(["cat", config.pidFile], { ...opts, quiet: true });
683
+ if (pidResult.ok) {
684
+ const pid = pidResult.stdout.trim();
685
+ await exec4(["kill", pid], { ...opts, quiet: true });
686
+ }
687
+ await exec4(["rm", "-f", config.pidFile], { ...opts, quiet: true });
688
+ }
689
+ await exec4(["rm", "-f", config.logFile], { ...opts, quiet: true });
690
+ }
691
+ var dopplerConfig = {
692
+ cli: "doppler",
693
+ loginCmd: "doppler login -y",
694
+ statusCmd: "doppler configure get token --scope /",
695
+ patterns: {
696
+ url: /https:\/\/(?:cli|dashboard)\.doppler\.com\/[a-z\-\/]+/i,
697
+ code: /(?:Your authentication code is:|Your auth code is:)\s*([A-Z0-9_]+(?:-[A-Z0-9_]+)*)/i,
698
+ success: /.{10,}/
699
+ },
700
+ logFile: "/tmp/doppler-login.log",
701
+ pidFile: "/tmp/doppler-login.pid"
702
+ };
703
+ var githubConfig = {
704
+ cli: "gh",
705
+ loginCmd: "GH_BROWSER=echo sh -c 'gh auth login -p https -h github.com < /dev/null'",
706
+ statusCmd: "gh auth status",
707
+ patterns: {
708
+ url: /(https:\/\/github\.com\/login\/device)/i,
709
+ code: /one-time code:\s*([A-Z0-9]{4}-[A-Z0-9]{4})/i,
710
+ success: /Logged in (?:as|to)/i
711
+ },
712
+ logFile: "/tmp/gh-login.log",
713
+ pidFile: "/tmp/gh-login.pid"
714
+ };
715
+ var tailscaleConfig = {
716
+ cli: "tailscale",
717
+ loginCmd: "tailscale up --authkey",
718
+ patterns: {
719
+ url: /https:\/\/login\.tailscale\.com\/[a-z0-9\/]+/,
720
+ code: /https:\/\/login\.tailscale\.com\/[a-z0-9\/]+/,
721
+ success: /Tailscale is running/i
722
+ },
723
+ logFile: "/tmp/tailscale-login.log",
724
+ pidFile: "/tmp/tailscale-login.pid"
725
+ };
726
+ function sleep(ms) {
727
+ return new Promise((resolve) => setTimeout(resolve, ms));
728
+ }
729
+
730
+ // bootstrap.ts
731
+ async function exec5(args, opts) {
732
+ const { sudo: sudo2 } = await Promise.resolve().then(() => (init_sudo(), exports_sudo));
733
+ return sudo2(args, opts);
734
+ }
735
+ async function writeFile3(path, content, opts) {
736
+ const { writeFile: wf } = await Promise.resolve().then(() => (init_sudo(), exports_sudo));
737
+ return wf(path, content, opts);
738
+ }
739
+ async function getBootstrapStatus(statusFile, opts) {
740
+ const result = await exec5(["cat", statusFile], { ...opts, quiet: true });
741
+ if (!result.ok) {
742
+ return {
743
+ status: "pending",
744
+ phases: {},
745
+ raw: ""
746
+ };
747
+ }
748
+ return parseBootstrapStatus(result.stdout);
749
+ }
750
+ function parseBootstrapStatus(content) {
751
+ const phases = {};
752
+ const data = {};
753
+ for (const line of content.trim().split(`
754
+ `)) {
755
+ if (!line || !line.includes("="))
756
+ continue;
757
+ const [key, ...valueParts] = line.split("=");
758
+ const value = valueParts.join("=").trim();
759
+ data[key] = value;
760
+ }
761
+ const status = data.status || "started";
762
+ const startedAt = data.started_at;
763
+ const completedAt = data.completed_at;
764
+ const source = data.source;
765
+ for (const [key, value] of Object.entries(data)) {
766
+ if (!key.startsWith("phase."))
767
+ continue;
768
+ const parts = key.split(".");
769
+ if (parts.length < 3)
770
+ continue;
771
+ const [, phaseName, field] = parts;
772
+ if (!phases[phaseName]) {
773
+ phases[phaseName] = { name: phaseName, status: "pending" };
774
+ }
775
+ switch (field) {
776
+ case "status":
777
+ phases[phaseName].status = value;
778
+ break;
779
+ case "started_at":
780
+ phases[phaseName].startedAt = value;
781
+ break;
782
+ case "completed_at":
783
+ phases[phaseName].completedAt = value;
784
+ break;
785
+ case "error":
786
+ phases[phaseName].error = value;
787
+ break;
788
+ }
789
+ }
790
+ return {
791
+ status,
792
+ startedAt,
793
+ completedAt,
794
+ source,
795
+ phases,
796
+ raw: content
797
+ };
798
+ }
799
+ async function initBootstrap(statusFile, source, opts) {
800
+ const now = new Date().toISOString();
801
+ const content = `status=started
802
+ started_at=${now}
803
+ source=${source}
804
+ `;
805
+ return writeFile3(statusFile, content, opts);
806
+ }
807
+ async function startPhase(statusFile, phase, opts) {
808
+ const now = new Date().toISOString();
809
+ const line = `phase.${phase}.status=running
810
+ phase.${phase}.started_at=${now}
811
+ `;
812
+ return writeFile3(statusFile, line, { ...opts, append: true });
813
+ }
814
+ async function completePhase(statusFile, phase, opts) {
815
+ const now = new Date().toISOString();
816
+ const line = `phase.${phase}.status=complete
817
+ phase.${phase}.completed_at=${now}
818
+ `;
819
+ return writeFile3(statusFile, line, { ...opts, append: true });
820
+ }
821
+ async function failPhase(statusFile, phase, error, opts) {
822
+ const now = new Date().toISOString();
823
+ const safeError = error.replace(/\n/g, " ");
824
+ const line = `phase.${phase}.status=failed
825
+ phase.${phase}.completed_at=${now}
826
+ phase.${phase}.error=${safeError}
827
+ `;
828
+ return writeFile3(statusFile, line, { ...opts, append: true });
829
+ }
830
+ async function completeBootstrap(statusFile, opts) {
831
+ const now = new Date().toISOString();
832
+ const line = `status=complete
833
+ completed_at=${now}
834
+ `;
835
+ return writeFile3(statusFile, line, { ...opts, append: true });
836
+ }
837
+ async function failBootstrap(statusFile, error, opts) {
838
+ const now = new Date().toISOString();
839
+ const safeError = error.replace(/\n/g, " ");
840
+ const line = `status=failed
841
+ completed_at=${now}
842
+ error=${safeError}
843
+ `;
844
+ return writeFile3(statusFile, line, { ...opts, append: true });
845
+ }
846
+ async function checkMarker(markerPath, opts) {
847
+ const result = await exec5(["test", "-f", markerPath, "&&", "echo", "exists"], {
848
+ ...opts,
849
+ quiet: true
850
+ });
851
+ return result.ok && result.stdout.trim() === "exists";
852
+ }
853
+ async function setMarker(markerPath, opts) {
854
+ return exec5(["touch", markerPath], opts);
855
+ }
856
+ async function removeMarker(markerPath, opts) {
857
+ return exec5(["rm", "-f", markerPath], opts);
858
+ }
859
+ async function waitForBootstrap(statusFile, opts = {}) {
860
+ const maxAttempts = opts.maxAttempts ?? 30;
861
+ const intervalMs = opts.intervalMs ?? 2000;
862
+ for (let attempt = 1;attempt <= maxAttempts; attempt++) {
863
+ const status = await getBootstrapStatus(statusFile, opts);
864
+ if (status.status === "complete") {
865
+ return { completed: true, status };
866
+ }
867
+ if (status.status === "failed") {
868
+ return { completed: false, status };
869
+ }
870
+ opts.onProgress?.(attempt, status);
871
+ await sleep2(intervalMs);
872
+ }
873
+ const finalStatus = await getBootstrapStatus(statusFile, opts);
874
+ return { completed: false, status: finalStatus };
875
+ }
876
+ async function waitForMarker(markerPath, opts = {}) {
877
+ const maxAttempts = opts.maxAttempts ?? 30;
878
+ const intervalMs = opts.intervalMs ?? 2000;
879
+ for (let attempt = 1;attempt <= maxAttempts; attempt++) {
880
+ const exists = await checkMarker(markerPath, opts);
881
+ if (exists) {
882
+ return { exists: true, timedOut: false };
883
+ }
884
+ opts.onProgress?.(attempt, { status: "running" });
885
+ await sleep2(intervalMs);
886
+ }
887
+ return { exists: false, timedOut: true };
888
+ }
889
+ function sleep2(ms) {
890
+ return new Promise((resolve) => setTimeout(resolve, ms));
891
+ }
892
+
893
+ // index.ts
894
+ init_sudo();
895
+ export {
896
+ writeFile,
897
+ waitForMarker,
898
+ waitForBootstrap,
899
+ tailscaleConfig,
900
+ symlink,
901
+ sudo,
902
+ stripAnsi,
903
+ stopService,
904
+ startService,
905
+ startPhase,
906
+ setMarker,
907
+ serviceExists,
908
+ serviceEnable,
909
+ service,
910
+ safeSystemd,
911
+ restartService,
912
+ removeMarker,
913
+ reloadService,
914
+ pkgInstall,
915
+ parseBootstrapStatus,
916
+ linkBinaries,
917
+ isAuthed,
918
+ installBun,
919
+ initBootstrap,
920
+ hasSystemd,
921
+ githubConfig,
922
+ getServiceStatus,
923
+ getServiceLogs,
924
+ getBunVersion,
925
+ getBootstrapStatus,
926
+ generateServiceUnit,
927
+ failPhase,
928
+ failBootstrap,
929
+ enableService,
930
+ enableAndStartService,
931
+ dopplerConfig,
932
+ disableService,
933
+ deviceAuth,
934
+ createServiceUnit,
935
+ completePhase,
936
+ completeBootstrap,
937
+ commandExists,
938
+ clone,
939
+ clearSystemdCache,
940
+ cleanupDeviceAuth,
941
+ checkMarker,
942
+ addSystemPath
943
+ };