@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/systemd.js CHANGED
@@ -1,186 +1,488 @@
1
- /**
2
- * Systemd service unit creation and management.
3
- * Works with both local and SSH contexts via ExecContext from sudo.ts.
4
- */
5
- // Re-export helpers
6
- async function exec(args, opts) {
7
- const { sudo } = await import("./sudo.js");
8
- return sudo(args, opts);
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
+ });
9
40
  }
10
41
  async function writeFile(path, content, opts) {
11
- const { writeFile: wf } = await import("./sudo.js");
12
- return wf(path, content, opts);
13
- }
14
- // ---------------------------------------------------------------------------
15
- // Service creation
16
- // ---------------------------------------------------------------------------
17
- /**
18
- * Generate a systemd unit file content from options.
19
- */
20
- export function generateServiceUnit(name, opts) {
21
- const lines = [];
22
- // [Unit] section
23
- lines.push("[Unit]");
24
- lines.push(`Description=${opts.description}`);
25
- if (opts.after?.length) {
26
- lines.push(`After=${opts.after.join(" ")}`);
27
- }
28
- if (opts.wants?.length) {
29
- lines.push(`Wants=${opts.wants.join(" ")}`);
30
- }
31
- lines.push("");
32
- // [Service] section
33
- lines.push("[Service]");
34
- if (opts.type)
35
- lines.push(`Type=${opts.type}`);
36
- lines.push(`User=${opts.user ?? "root"}`);
37
- if (opts.group)
38
- lines.push(`Group=${opts.group}`);
39
- lines.push(`WorkingDirectory=${opts.workingDirectory}`);
40
- lines.push(`ExecStart=${opts.execStart}`);
41
- if (opts.execStartPre?.length) {
42
- opts.execStartPre.forEach((cmd, i) => {
43
- lines.push(`ExecStartPre${i > 0 ? `=${i}` : ""}=${cmd}`);
44
- });
45
- }
46
- if (opts.execStopPost?.length) {
47
- opts.execStopPost.forEach((cmd, i) => {
48
- lines.push(`ExecStopPost${i > 0 ? `=${i}` : ""}=${cmd}`);
49
- });
50
- }
51
- if (opts.restart)
52
- lines.push(`Restart=${opts.restart}`);
53
- if (opts.restartSec)
54
- lines.push(`RestartSec=${opts.restartSec}`);
55
- if (opts.environment) {
56
- Object.entries(opts.environment).forEach(([k, v]) => {
57
- lines.push(`Environment="${k}=${v}"`);
58
- });
59
- }
60
- if (opts.environmentFile) {
61
- lines.push(`EnvironmentFile=${opts.environmentFile}`);
62
- }
63
- if (opts.standardOutput)
64
- lines.push(`StandardOutput=${opts.standardOutput}`);
65
- if (opts.standardError)
66
- lines.push(`StandardError=${opts.standardError}`);
67
- if (opts.nice !== undefined)
68
- lines.push(`Nice=${opts.nice}`);
69
- // Extras
70
- if (opts.extras) {
71
- lines.push("");
72
- lines.push(opts.extras);
73
- }
74
- return lines.join("\n");
75
- }
76
- /**
77
- * Create and install a systemd service unit file.
78
- */
79
- export async function createServiceUnit(name, opts) {
80
- const unitPath = `/etc/systemd/system/${name}.service`;
81
- const content = generateServiceUnit(name, opts);
82
- // Write unit file
83
- const writeResult = await writeFile(unitPath, content, {
84
- context: opts.context,
85
- mode: "644",
86
- });
87
- if (!writeResult.ok) {
88
- return writeResult;
89
- }
90
- // Reload systemd
91
- await exec(["systemctl", "daemon-reload"], opts);
92
- return {
93
- ...writeResult,
94
- unitPath,
95
- };
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;
96
69
  }
97
- // ---------------------------------------------------------------------------
98
- // Service control
99
- // ---------------------------------------------------------------------------
100
- /**
101
- * Enable a service to start on boot.
102
- */
103
- export async function enableService(name, opts) {
104
- return exec(["systemctl", "enable", name], opts);
105
- }
106
- /**
107
- * Disable a service from starting on boot.
108
- */
109
- export async function disableService(name, opts) {
110
- return exec(["systemctl", "disable", name], opts);
111
- }
112
- /**
113
- * Start a service.
114
- */
115
- export async function startService(name, opts) {
116
- return exec(["systemctl", "start", name], opts);
117
- }
118
- /**
119
- * Stop a service.
120
- */
121
- export async function stopService(name, opts) {
122
- return exec(["systemctl", "stop", name], opts);
123
- }
124
- /**
125
- * Restart a service.
126
- */
127
- export async function restartService(name, opts) {
128
- return exec(["systemctl", "restart", name], opts);
129
- }
130
- /**
131
- * Reload a service (sends SIGHUP).
132
- */
133
- export async function reloadService(name, opts) {
134
- return exec(["systemctl", "reload", name], opts);
135
- }
136
- /**
137
- * Get service status.
138
- */
139
- export async function getServiceStatus(name, opts) {
140
- const result = await exec(["systemctl", "show", name, "--property=LoadState,ActiveState,SubState,MainPID,Description"], {
141
- ...opts,
142
- quiet: true,
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
143
94
  });
144
- if (!result.ok) {
145
- return { loaded: false, active: false, subState: "unknown", mainPid: 0, description: "" };
146
- }
147
- const parse = (key) => {
148
- const match = result.stdout.match(new RegExp(`^${key}=(.+)$`, "m"));
149
- return match?.[1]?.trim() ?? "";
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
+ // systemd.ts
131
+ async function exec2(args, opts) {
132
+ const { sudo: sudo2 } = await Promise.resolve().then(() => (init_sudo(), exports_sudo));
133
+ return sudo2(args, opts);
134
+ }
135
+ async function writeFile2(path, content, opts) {
136
+ const { writeFile: wf } = await Promise.resolve().then(() => (init_sudo(), exports_sudo));
137
+ return wf(path, content, opts);
138
+ }
139
+ var systemdCheckCache = null;
140
+ async function hasSystemd(opts) {
141
+ if (systemdCheckCache) {
142
+ return systemdCheckCache;
143
+ }
144
+ const sudoOpts = opts ?? { context: { type: "local" } };
145
+ const whichResult = await exec2(["which", "systemctl"], {
146
+ ...sudoOpts,
147
+ quiet: true,
148
+ env: sudoOpts.env
149
+ });
150
+ if (!whichResult.ok || !whichResult.stdout.trim()) {
151
+ const result2 = {
152
+ available: false,
153
+ reason: "not_found",
154
+ message: "systemctl command not found - systemd is not installed or not in PATH"
155
+ };
156
+ systemdCheckCache = result2;
157
+ return result2;
158
+ }
159
+ const runningResult = await exec2(["systemctl", "is-system-running"], {
160
+ ...sudoOpts,
161
+ quiet: true,
162
+ env: sudoOpts.env
163
+ });
164
+ if (runningResult.ok && runningResult.stdout.trim() !== "") {
165
+ const result2 = {
166
+ available: true,
167
+ message: "systemd is available and running"
150
168
  };
151
- return {
152
- loaded: parse("LoadState") === "loaded",
153
- active: parse("ActiveState") === "active",
154
- subState: parse("SubState"),
155
- mainPid: parseInt(parse("MainPID") || "0", 10),
156
- description: parse("Description"),
169
+ systemdCheckCache = result2;
170
+ return result2;
171
+ }
172
+ const dirResult = await exec2(["test", "-d", "/run/systemd/system"], {
173
+ ...sudoOpts,
174
+ quiet: true,
175
+ env: sudoOpts.env
176
+ });
177
+ if (dirResult.ok) {
178
+ const result2 = {
179
+ available: true,
180
+ message: "systemd detected via /run/systemd/system (container environment)"
157
181
  };
182
+ systemdCheckCache = result2;
183
+ return result2;
184
+ }
185
+ const result = {
186
+ available: false,
187
+ reason: "not_running",
188
+ message: "systemd is not available on this system (container/initless environment)"
189
+ };
190
+ systemdCheckCache = result;
191
+ return result;
158
192
  }
159
- /**
160
- * Enable and start a service in one call.
161
- */
162
- export async function enableAndStartService(name, opts) {
163
- return exec(["systemctl", "enable", "--now", name], opts);
164
- }
165
- /**
166
- * Check if a service exists.
167
- */
168
- export async function serviceExists(name, opts) {
169
- const result = await exec(["systemctl", "list-unit-files", name + ".service"], {
170
- ...opts,
171
- quiet: true,
193
+ function clearSystemdCache() {
194
+ systemdCheckCache = null;
195
+ }
196
+ function generateServiceUnit(name, opts) {
197
+ const lines = [];
198
+ lines.push("[Unit]");
199
+ lines.push(`Description=${opts.description}`);
200
+ if (opts.after?.length) {
201
+ lines.push(`After=${opts.after.join(" ")}`);
202
+ }
203
+ if (opts.wants?.length) {
204
+ lines.push(`Wants=${opts.wants.join(" ")}`);
205
+ }
206
+ lines.push("");
207
+ lines.push("[Service]");
208
+ if (opts.type)
209
+ lines.push(`Type=${opts.type}`);
210
+ lines.push(`User=${opts.user ?? "root"}`);
211
+ if (opts.group)
212
+ lines.push(`Group=${opts.group}`);
213
+ lines.push(`WorkingDirectory=${opts.workingDirectory}`);
214
+ lines.push(`ExecStart=${opts.execStart}`);
215
+ if (opts.execStartPre?.length) {
216
+ opts.execStartPre.forEach((cmd, i) => {
217
+ lines.push(`ExecStartPre${i > 0 ? `=${i}` : ""}=${cmd}`);
218
+ });
219
+ }
220
+ if (opts.execStopPost?.length) {
221
+ opts.execStopPost.forEach((cmd, i) => {
222
+ lines.push(`ExecStopPost${i > 0 ? `=${i}` : ""}=${cmd}`);
223
+ });
224
+ }
225
+ if (opts.restart)
226
+ lines.push(`Restart=${opts.restart}`);
227
+ if (opts.restartSec)
228
+ lines.push(`RestartSec=${opts.restartSec}`);
229
+ if (opts.environment) {
230
+ Object.entries(opts.environment).forEach(([k, v]) => {
231
+ lines.push(`Environment="${k}=${v}"`);
172
232
  });
173
- return result.ok && result.stdout.includes(name);
174
- }
175
- /**
176
- * Get service logs via journalctl.
177
- */
178
- export async function getServiceLogs(name, opts) {
179
- const args = ["journalctl", "-u", name, "-n", String(opts.lines ?? 100)];
180
- if (opts.since)
181
- args.push("--since", opts.since);
182
- if (!opts.follow)
183
- args.push("-n", String(opts.lines ?? 100));
184
- const result = await exec(args, opts);
185
- return result.stdout;
233
+ }
234
+ if (opts.environmentFile) {
235
+ lines.push(`EnvironmentFile=${opts.environmentFile}`);
236
+ }
237
+ if (opts.standardOutput)
238
+ lines.push(`StandardOutput=${opts.standardOutput}`);
239
+ if (opts.standardError)
240
+ lines.push(`StandardError=${opts.standardError}`);
241
+ if (opts.nice !== undefined)
242
+ lines.push(`Nice=${opts.nice}`);
243
+ if (opts.noNewPrivileges)
244
+ lines.push("NoNewPrivileges=true");
245
+ if (opts.privateTmp)
246
+ lines.push("PrivateTmp=true");
247
+ if (opts.protectSystem)
248
+ lines.push(`ProtectSystem=${opts.protectSystem}`);
249
+ if (opts.protectHome === true)
250
+ lines.push("ProtectHome=true");
251
+ else if (opts.protectHome)
252
+ lines.push(`ProtectHome=${opts.protectHome}`);
253
+ if (opts.readOnlyPaths?.length)
254
+ lines.push(`ReadOnlyPaths=${opts.readOnlyPaths.join(" ")}`);
255
+ if (opts.readWritePaths?.length)
256
+ lines.push(`ReadWritePaths=${opts.readWritePaths.join(" ")}`);
257
+ if (opts.restrictRealtime)
258
+ lines.push("RestrictRealtime=true");
259
+ if (opts.memoryDenyWriteExecute)
260
+ lines.push("MemoryDenyWriteExecute=true");
261
+ if (opts.protectKernelTunables)
262
+ lines.push("ProtectKernelTunables=true");
263
+ if (opts.protectKernelModules)
264
+ lines.push("ProtectKernelModules=true");
265
+ if (opts.protectControlGroups)
266
+ lines.push("ProtectControlGroups=true");
267
+ if (opts.restrictAddressFamilies?.length)
268
+ lines.push(`RestrictAddressFamilies=${opts.restrictAddressFamilies.join(" ")}`);
269
+ if (opts.deviceAllow?.length)
270
+ lines.push(`DeviceAllow=${opts.deviceAllow.join(" ")}`);
271
+ if (opts.devicePolicy)
272
+ lines.push(`DevicePolicy=${opts.devicePolicy}`);
273
+ if (opts.systemCallFilter?.length)
274
+ lines.push(`SystemCallFilter=${opts.systemCallFilter.join(" ")}`);
275
+ if (opts.restrictNamespaces === true)
276
+ lines.push("RestrictNamespaces=true");
277
+ else if (Array.isArray(opts.restrictNamespaces) && opts.restrictNamespaces.length > 0)
278
+ lines.push(`RestrictNamespaces=${opts.restrictNamespaces.join(" ")}`);
279
+ if (opts.personality)
280
+ lines.push(`Personality=${opts.personality}`);
281
+ if (opts.lockPersonality)
282
+ lines.push("LockPersonality=true");
283
+ if (opts.removeCapability?.length)
284
+ opts.removeCapability.forEach((cap) => lines.push(`RemoveCapability=${cap}`));
285
+ if (opts.capabilityBoundingSet?.length)
286
+ lines.push(`CapabilityBoundingSet=${opts.capabilityBoundingSet.join(" ")}`);
287
+ if (opts.ambientCapabilities?.length)
288
+ lines.push(`AmbientCapabilities=${opts.ambientCapabilities.join(" ")}`);
289
+ if (opts.privateDevices)
290
+ lines.push("PrivateDevices=true");
291
+ if (opts.protectKernelLogs)
292
+ lines.push("ProtectKernelLogs=true");
293
+ if (opts.extras) {
294
+ lines.push("");
295
+ lines.push(opts.extras);
296
+ }
297
+ return lines.join(`
298
+ `);
299
+ }
300
+ async function createServiceUnit(name, opts) {
301
+ const unitPath = `/etc/systemd/system/${name}.service`;
302
+ const content = generateServiceUnit(name, opts);
303
+ const writeResult = await writeFile2(unitPath, content, {
304
+ context: opts.context,
305
+ mode: "644",
306
+ env: opts.env,
307
+ timeout: opts.timeout
308
+ });
309
+ if (!writeResult.ok) {
310
+ return writeResult;
311
+ }
312
+ await exec2(["systemctl", "daemon-reload"], opts);
313
+ return {
314
+ ...writeResult,
315
+ unitPath
316
+ };
317
+ }
318
+ async function enableService(name, opts) {
319
+ return exec2(["systemctl", "enable", name], opts);
320
+ }
321
+ async function disableService(name, opts) {
322
+ return exec2(["systemctl", "disable", name], opts);
323
+ }
324
+ async function startService(name, opts) {
325
+ return exec2(["systemctl", "start", name], opts);
326
+ }
327
+ async function stopService(name, opts) {
328
+ return exec2(["systemctl", "stop", name], opts);
329
+ }
330
+ async function restartService(name, opts) {
331
+ return exec2(["systemctl", "restart", name], opts);
332
+ }
333
+ async function reloadService(name, opts) {
334
+ return exec2(["systemctl", "reload", name], opts);
335
+ }
336
+ async function getServiceStatus(name, opts) {
337
+ const result = await exec2(["systemctl", "show", name, "--property=LoadState,ActiveState,SubState,MainPID,Description"], {
338
+ ...opts,
339
+ quiet: true
340
+ });
341
+ if (!result.ok) {
342
+ return { loaded: false, active: false, subState: "unknown", mainPid: 0, description: "" };
343
+ }
344
+ const parse = (key) => {
345
+ const match = result.stdout.match(new RegExp(`^${key}=(.+)$`, "m"));
346
+ return match?.[1]?.trim() ?? "";
347
+ };
348
+ return {
349
+ loaded: parse("LoadState") === "loaded",
350
+ active: parse("ActiveState") === "active",
351
+ subState: parse("SubState"),
352
+ mainPid: parseInt(parse("MainPID") || "0", 10),
353
+ description: parse("Description")
354
+ };
355
+ }
356
+ async function enableAndStartService(name, opts) {
357
+ return exec2(["systemctl", "enable", "--now", name], opts);
358
+ }
359
+ async function serviceExists(name, opts) {
360
+ const result = await exec2(["systemctl", "list-unit-files", name + ".service"], {
361
+ ...opts,
362
+ quiet: true
363
+ });
364
+ return result.ok && result.stdout.includes(name);
365
+ }
366
+ async function getServiceLogs(name, opts = {}) {
367
+ const args = ["journalctl", "-u", name, "-n", String(opts.lines ?? 100)];
368
+ if (opts.since)
369
+ args.push("--since", opts.since);
370
+ if (!opts.follow)
371
+ args.push("-n", String(opts.lines ?? 100));
372
+ const result = await exec2(args, opts);
373
+ return result.stdout;
374
+ }
375
+ var SECURITY_PRESETS = {
376
+ strict: {
377
+ noNewPrivileges: true,
378
+ privateTmp: true,
379
+ protectSystem: "strict",
380
+ protectHome: true,
381
+ readOnlyPaths: ["/"],
382
+ readWritePaths: [],
383
+ restrictRealtime: true,
384
+ memoryDenyWriteExecute: true,
385
+ protectKernelTunables: true,
386
+ protectKernelModules: true,
387
+ protectControlGroups: true,
388
+ restrictAddressFamilies: ["AF_UNIX", "AF_INET", "AF_INET6"],
389
+ devicePolicy: "closed",
390
+ deviceAllow: [],
391
+ restrictNamespaces: true,
392
+ privateDevices: true,
393
+ protectKernelLogs: true,
394
+ lockPersonality: true,
395
+ capabilityBoundingSet: []
396
+ },
397
+ standard: {
398
+ noNewPrivileges: true,
399
+ privateTmp: true,
400
+ protectSystem: "full",
401
+ protectHome: true,
402
+ restrictRealtime: true,
403
+ memoryDenyWriteExecute: true,
404
+ protectKernelTunables: true,
405
+ protectKernelModules: true,
406
+ protectControlGroups: true,
407
+ restrictAddressFamilies: ["AF_UNIX", "AF_INET", "AF_INET6"],
408
+ devicePolicy: "auto",
409
+ restrictNamespaces: true,
410
+ privateDevices: true,
411
+ capabilityBoundingSet: ["CAP_NET_BIND_SERVICE"]
412
+ },
413
+ network: {
414
+ noNewPrivileges: true,
415
+ privateTmp: true,
416
+ protectSystem: "full",
417
+ protectHome: true,
418
+ restrictRealtime: true,
419
+ protectKernelTunables: true,
420
+ protectKernelModules: true,
421
+ protectControlGroups: true,
422
+ restrictAddressFamilies: ["AF_UNIX", "AF_INET", "AF_INET6"],
423
+ devicePolicy: "auto",
424
+ privateDevices: true,
425
+ capabilityBoundingSet: ["CAP_NET_BIND_SERVICE"],
426
+ ambientCapabilities: ["CAP_NET_BIND_SERVICE"]
427
+ },
428
+ minimal: {
429
+ noNewPrivileges: true,
430
+ privateTmp: true,
431
+ protectSystem: "yes",
432
+ restrictRealtime: true
433
+ },
434
+ deviceAccess: {
435
+ noNewPrivileges: true,
436
+ privateTmp: true,
437
+ protectSystem: "full",
438
+ protectHome: true,
439
+ restrictRealtime: true,
440
+ memoryDenyWriteExecute: true,
441
+ protectKernelTunables: true,
442
+ protectKernelModules: true,
443
+ protectControlGroups: true,
444
+ devicePolicy: "auto",
445
+ restrictNamespaces: true,
446
+ privateDevices: false
447
+ }
448
+ };
449
+ function withSecurityPreset(preset, customOptions) {
450
+ return {
451
+ ...SECURITY_PRESETS[preset],
452
+ ...customOptions
453
+ };
454
+ }
455
+ async function safeSystemd(operation, options = {}) {
456
+ const { requireSystemd = true, onNoSystemd, context, env, timeout, quiet } = options;
457
+ const sudoOpts = context ? { context, env, timeout, quiet } : { context: { type: "local" }, env, timeout, quiet };
458
+ const check = await hasSystemd(sudoOpts);
459
+ if (!check.available) {
460
+ if (onNoSystemd) {
461
+ await onNoSystemd(check);
462
+ }
463
+ if (requireSystemd) {
464
+ throw new Error(`Systemd required but not available: ${check.message}`);
465
+ }
466
+ return;
467
+ }
468
+ return operation();
186
469
  }
470
+ export {
471
+ withSecurityPreset,
472
+ stopService,
473
+ startService,
474
+ serviceExists,
475
+ safeSystemd,
476
+ restartService,
477
+ reloadService,
478
+ hasSystemd,
479
+ getServiceStatus,
480
+ getServiceLogs,
481
+ generateServiceUnit,
482
+ enableService,
483
+ enableAndStartService,
484
+ disableService,
485
+ createServiceUnit,
486
+ clearSystemdCache,
487
+ SECURITY_PRESETS
488
+ };