@ebowwa/seedinstallation 0.2.4 → 0.4.0
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/LICENSE +1 -1
- package/README.md +36 -144
- package/dist/bootstrap.d.ts +5 -6
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +294 -215
- package/dist/clone.d.ts +1 -0
- package/dist/clone.d.ts.map +1 -0
- package/dist/clone.js +54 -68
- package/dist/device-auth.d.ts +4 -5
- package/dist/device-auth.d.ts.map +1 -0
- package/dist/device-auth.js +252 -164
- package/dist/index.d.ts +13 -12
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +941 -10
- package/dist/runtime.d.ts +9 -14
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +211 -114
- package/dist/sudo.d.ts +1 -0
- package/dist/sudo.d.ts.map +1 -0
- package/dist/sudo.js +123 -222
- package/dist/systemd.d.ts +207 -3
- package/dist/systemd.d.ts.map +1 -0
- package/dist/systemd.js +477 -177
- package/package.json +40 -40
package/dist/sudo.js
CHANGED
|
@@ -1,235 +1,136 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
/** Run an arbitrary command with sudo. */
|
|
10
|
-
export async function sudo(cmd, opts) {
|
|
11
|
-
const parts = Array.isArray(cmd) ? cmd : cmd.split(/\s+/);
|
|
12
|
-
const envPrefix = opts.env
|
|
13
|
-
? Object.entries(opts.env).map(([k, v]) => `${k}=${shellEscape(v)}`)
|
|
14
|
-
: [];
|
|
15
|
-
const sudoCmd = ["sudo", ...envPrefix, ...parts];
|
|
16
|
-
return exec(sudoCmd, opts);
|
|
17
|
-
}
|
|
18
|
-
const installCmd = {
|
|
19
|
-
apt: ["apt-get", "install", "-y"],
|
|
20
|
-
dnf: ["dnf", "install", "-y"],
|
|
21
|
-
apk: ["apk", "add", "--no-cache"],
|
|
22
|
-
};
|
|
23
|
-
const updateCmd = {
|
|
24
|
-
apt: ["apt-get", "update", "-qq"],
|
|
25
|
-
dnf: ["dnf", "check-update"],
|
|
26
|
-
apk: ["apk", "update"],
|
|
27
|
-
};
|
|
28
|
-
/** Install one or more system packages. */
|
|
29
|
-
export 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
|
-
// Update package index first
|
|
36
|
-
await sudo(updateCmd[pm], { ...opts, env: envOverride, quiet: true });
|
|
37
|
-
return sudo([...installCmd[pm], ...packages], {
|
|
38
|
-
...opts,
|
|
39
|
-
env: envOverride,
|
|
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
|
|
40
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);
|
|
41
28
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
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
|
+
});
|
|
55
40
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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;
|
|
59
52
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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);
|
|
63
58
|
}
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
// Internals
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
59
|
function buildSshPrefix(ctx) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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;
|
|
77
69
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
90
|
-
});
|
|
91
|
-
let stdout = "";
|
|
92
|
-
let stderr = "";
|
|
93
|
-
proc.stdout?.on("data", (data) => {
|
|
94
|
-
stdout += data.toString();
|
|
95
|
-
});
|
|
96
|
-
proc.stderr?.on("data", (data) => {
|
|
97
|
-
stderr += data.toString();
|
|
98
|
-
});
|
|
99
|
-
if (timeout) {
|
|
100
|
-
timer = setTimeout(() => {
|
|
101
|
-
proc.kill();
|
|
102
|
-
resolve({
|
|
103
|
-
stdout: opts.quiet ? "" : stdout,
|
|
104
|
-
stderr: stderr + "\nCommand timed out",
|
|
105
|
-
exitCode: 124,
|
|
106
|
-
ok: false,
|
|
107
|
-
});
|
|
108
|
-
}, timeout);
|
|
109
|
-
}
|
|
110
|
-
proc.on("close", (code) => {
|
|
111
|
-
if (timer)
|
|
112
|
-
clearTimeout(timer);
|
|
113
|
-
resolve({
|
|
114
|
-
stdout: opts.quiet ? "" : stdout,
|
|
115
|
-
stderr,
|
|
116
|
-
exitCode: code ?? 0,
|
|
117
|
-
ok: code === 0,
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
proc.on("error", (err) => {
|
|
121
|
-
if (timer)
|
|
122
|
-
clearTimeout(timer);
|
|
123
|
-
reject(new Error(`Command failed: ${err.message}`));
|
|
124
|
-
});
|
|
125
|
-
});
|
|
70
|
+
async function exec(args, opts) {
|
|
71
|
+
const finalArgs = opts.context.type === "ssh" ? [...buildSshPrefix(opts.context), args.map(shellEscape).join(" ")] : args;
|
|
72
|
+
const proc = Bun.spawn(finalArgs, {
|
|
73
|
+
stdout: "pipe",
|
|
74
|
+
stderr: "pipe",
|
|
75
|
+
timeout: opts.timeout ?? 30000
|
|
76
|
+
});
|
|
77
|
+
const exitCode = await proc.exited;
|
|
78
|
+
const stdout = opts.quiet ? "" : await new Response(proc.stdout).text();
|
|
79
|
+
const stderr = await new Response(proc.stderr).text();
|
|
80
|
+
return { stdout, stderr, exitCode, ok: exitCode === 0 };
|
|
126
81
|
}
|
|
127
82
|
async function execPipe(input, args, opts) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
? ["sh", "-c", fullArgs.join(" ")]
|
|
138
|
-
: fullArgs;
|
|
139
|
-
const proc = spawn(spawnArgs[0], spawnArgs.slice(1), {
|
|
140
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
141
|
-
});
|
|
142
|
-
// Write input to stdin
|
|
143
|
-
if (proc.stdin) {
|
|
144
|
-
proc.stdin.write(input);
|
|
145
|
-
proc.stdin.end();
|
|
146
|
-
}
|
|
147
|
-
let stdout = "";
|
|
148
|
-
let stderr = "";
|
|
149
|
-
proc.stdout?.on("data", (data) => {
|
|
150
|
-
stdout += data.toString();
|
|
151
|
-
});
|
|
152
|
-
proc.stderr?.on("data", (data) => {
|
|
153
|
-
stderr += data.toString();
|
|
154
|
-
});
|
|
155
|
-
if (timeout) {
|
|
156
|
-
timer = setTimeout(() => {
|
|
157
|
-
proc.kill();
|
|
158
|
-
resolve({
|
|
159
|
-
stdout: opts.quiet ? "" : stdout,
|
|
160
|
-
stderr: stderr + "\nCommand timed out",
|
|
161
|
-
exitCode: 124,
|
|
162
|
-
ok: false,
|
|
163
|
-
});
|
|
164
|
-
}, timeout);
|
|
165
|
-
}
|
|
166
|
-
proc.on("close", (code) => {
|
|
167
|
-
if (timer)
|
|
168
|
-
clearTimeout(timer);
|
|
169
|
-
resolve({
|
|
170
|
-
stdout: opts.quiet ? "" : stdout,
|
|
171
|
-
stderr,
|
|
172
|
-
exitCode: code ?? 0,
|
|
173
|
-
ok: code === 0,
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
proc.on("error", (err) => {
|
|
177
|
-
if (timer)
|
|
178
|
-
clearTimeout(timer);
|
|
179
|
-
reject(new Error(`Command failed: ${err.message}`));
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
// Local: pipe through shell
|
|
184
|
-
return new Promise((resolve, reject) => {
|
|
185
|
-
const timeout = opts.timeout ?? 30_000;
|
|
186
|
-
let timer;
|
|
187
|
-
const proc = spawn("sh", ["-c", args.join(" ")], {
|
|
188
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
189
|
-
});
|
|
190
|
-
// Write input to stdin
|
|
191
|
-
if (proc.stdin) {
|
|
192
|
-
proc.stdin.write(input);
|
|
193
|
-
proc.stdin.end();
|
|
194
|
-
}
|
|
195
|
-
let stdout = "";
|
|
196
|
-
let stderr = "";
|
|
197
|
-
proc.stdout?.on("data", (data) => {
|
|
198
|
-
stdout += data.toString();
|
|
199
|
-
});
|
|
200
|
-
proc.stderr?.on("data", (data) => {
|
|
201
|
-
stderr += data.toString();
|
|
202
|
-
});
|
|
203
|
-
if (timeout) {
|
|
204
|
-
timer = setTimeout(() => {
|
|
205
|
-
proc.kill();
|
|
206
|
-
resolve({
|
|
207
|
-
stdout: opts.quiet ? "" : stdout,
|
|
208
|
-
stderr: stderr + "\nCommand timed out",
|
|
209
|
-
exitCode: 124,
|
|
210
|
-
ok: false,
|
|
211
|
-
});
|
|
212
|
-
}, timeout);
|
|
213
|
-
}
|
|
214
|
-
proc.on("close", (code) => {
|
|
215
|
-
if (timer)
|
|
216
|
-
clearTimeout(timer);
|
|
217
|
-
resolve({
|
|
218
|
-
stdout: opts.quiet ? "" : stdout,
|
|
219
|
-
stderr,
|
|
220
|
-
exitCode: code ?? 0,
|
|
221
|
-
ok: code === 0,
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
proc.on("error", (err) => {
|
|
225
|
-
if (timer)
|
|
226
|
-
clearTimeout(timer);
|
|
227
|
-
reject(new Error(`Command failed: ${err.message}`));
|
|
228
|
-
});
|
|
83
|
+
if (opts.context.type === "ssh") {
|
|
84
|
+
const sshPrefix = buildSshPrefix(opts.context);
|
|
85
|
+
const remoteCmd = args.join(" ");
|
|
86
|
+
const fullArgs = [...sshPrefix, remoteCmd];
|
|
87
|
+
const proc2 = Bun.spawn(fullArgs, {
|
|
88
|
+
stdin: new TextEncoder().encode(input),
|
|
89
|
+
stdout: "pipe",
|
|
90
|
+
stderr: "pipe",
|
|
91
|
+
timeout: opts.timeout ?? 30000
|
|
229
92
|
});
|
|
93
|
+
const exitCode2 = await proc2.exited;
|
|
94
|
+
const stdout2 = opts.quiet ? "" : await new Response(proc2.stdout).text();
|
|
95
|
+
const stderr2 = await new Response(proc2.stderr).text();
|
|
96
|
+
return { stdout: stdout2, stderr: stderr2, exitCode: exitCode2, ok: exitCode2 === 0 };
|
|
97
|
+
}
|
|
98
|
+
const proc = Bun.spawn(["sh", "-c", args.join(" ")], {
|
|
99
|
+
stdin: new TextEncoder().encode(input),
|
|
100
|
+
stdout: "pipe",
|
|
101
|
+
stderr: "pipe",
|
|
102
|
+
timeout: opts.timeout ?? 30000
|
|
103
|
+
});
|
|
104
|
+
const exitCode = await proc.exited;
|
|
105
|
+
const stdout = opts.quiet ? "" : await new Response(proc.stdout).text();
|
|
106
|
+
const stderr = await new Response(proc.stderr).text();
|
|
107
|
+
return { stdout, stderr, exitCode, ok: exitCode === 0 };
|
|
230
108
|
}
|
|
231
109
|
function shellEscape(s) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
110
|
+
if (/^[a-zA-Z0-9._\-\/=:@]+$/.test(s))
|
|
111
|
+
return s;
|
|
112
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
235
113
|
}
|
|
114
|
+
var installCmd, updateCmd;
|
|
115
|
+
var init_sudo = __esm(() => {
|
|
116
|
+
installCmd = {
|
|
117
|
+
apt: ["apt-get", "install", "-y"],
|
|
118
|
+
dnf: ["dnf", "install", "-y"],
|
|
119
|
+
apk: ["apk", "add", "--no-cache"]
|
|
120
|
+
};
|
|
121
|
+
updateCmd = {
|
|
122
|
+
apt: ["apt-get", "update", "-qq"],
|
|
123
|
+
dnf: ["dnf", "check-update"],
|
|
124
|
+
apk: ["apk", "update"]
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
init_sudo();
|
|
128
|
+
|
|
129
|
+
export {
|
|
130
|
+
writeFile,
|
|
131
|
+
sudo,
|
|
132
|
+
serviceEnable,
|
|
133
|
+
service,
|
|
134
|
+
pkgInstall,
|
|
135
|
+
exec
|
|
136
|
+
};
|
package/dist/systemd.d.ts
CHANGED
|
@@ -1,8 +1,105 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Systemd service unit creation and management.
|
|
2
|
+
* Systemd service unit creation and management with security hardening.
|
|
3
|
+
*
|
|
4
|
+
* @example Creating a secure web service
|
|
5
|
+
* ```typescript
|
|
6
|
+
* import { createServiceUnit, SECURITY_PRESETS, enableAndStartService } from "@ebowwa/seedinstallation/systemd";
|
|
7
|
+
*
|
|
8
|
+
* const webService = await createServiceUnit("my-api", {
|
|
9
|
+
* description: "My API Service",
|
|
10
|
+
* workingDirectory: "/opt/my-api",
|
|
11
|
+
* execStart: "/usr/bin/node /opt/my-api/index.js",
|
|
12
|
+
* user: "api-user",
|
|
13
|
+
* ...SECURITY_PRESETS.network,
|
|
14
|
+
* readWritePaths: ["/var/log/my-api", "/var/lib/my-api"],
|
|
15
|
+
* environment: {
|
|
16
|
+
* NODE_ENV: "production",
|
|
17
|
+
* PORT: "8911",
|
|
18
|
+
* },
|
|
19
|
+
* context: sudoContext,
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* await enableAndStartService("my-api", { context: sudoContext });
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example Creating a service with custom security
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const customService = await createServiceUnit("custom-agent", {
|
|
28
|
+
* description: "Custom Agent",
|
|
29
|
+
* workingDirectory: "/opt/agent",
|
|
30
|
+
* execStart: "/usr/bin/node /opt/agent/index.js",
|
|
31
|
+
* user: "agent",
|
|
32
|
+
* // Apply strict security but relax some options
|
|
33
|
+
* ...SECURITY_PRESETS.strict,
|
|
34
|
+
* // Allow specific directory for agent data
|
|
35
|
+
* readWritePaths: ["/var/lib/agent", "/var/log/agent"],
|
|
36
|
+
* // Allow network communication
|
|
37
|
+
* restrictAddressFamilies: ["AF_UNIX", "AF_INET", "AF_INET6"],
|
|
38
|
+
* // Allow binding to privileged ports
|
|
39
|
+
* capabilityBoundingSet: ["CAP_NET_BIND_SERVICE"],
|
|
40
|
+
* ambientCapabilities: ["CAP_NET_BIND_SERVICE"],
|
|
41
|
+
* context: sudoContext,
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example Creating a development service with minimal security
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const devService = await createServiceUnit("dev-service", {
|
|
48
|
+
* description: "Development Service",
|
|
49
|
+
* workingDirectory: "/home/dev/project",
|
|
50
|
+
* execStart: "npm run dev",
|
|
51
|
+
* user: "dev",
|
|
52
|
+
* ...SECURITY_PRESETS.minimal,
|
|
53
|
+
* // Allow full home directory access for development
|
|
54
|
+
* readWritePaths: ["/home/dev"],
|
|
55
|
+
* context: sudoContext,
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
3
59
|
* Works with both local and SSH contexts via ExecContext from sudo.ts.
|
|
4
60
|
*/
|
|
5
|
-
import type { SudoOptions, ExecResult } from "./sudo
|
|
61
|
+
import type { SudoOptions, ExecResult } from "./sudo";
|
|
62
|
+
/**
|
|
63
|
+
* Result of systemd availability check
|
|
64
|
+
*/
|
|
65
|
+
export interface SystemdCheckResult {
|
|
66
|
+
/** Whether systemd is available on this system */
|
|
67
|
+
available: boolean;
|
|
68
|
+
/** Reason why systemd is not available (undefined if available) */
|
|
69
|
+
reason?: "not_found" | "not_running" | "no_permission" | "unknown";
|
|
70
|
+
/** Human-readable message */
|
|
71
|
+
message: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if systemd is available on the system.
|
|
75
|
+
*
|
|
76
|
+
* Detection methods (tried in order):
|
|
77
|
+
* 1. Check if systemctl command exists
|
|
78
|
+
* 2. Check if /run/systemd/system directory exists
|
|
79
|
+
* 3. Try running systemctl --version
|
|
80
|
+
*
|
|
81
|
+
* Results are cached after first call.
|
|
82
|
+
*
|
|
83
|
+
* @param opts - Optional execution context (defaults to local if not provided)
|
|
84
|
+
*
|
|
85
|
+
* @example Skip systemd operations on non-systemd systems
|
|
86
|
+
* ```typescript
|
|
87
|
+
* import { hasSystemd, createServiceUnit } from "@ebowwa/seedinstallation/systemd";
|
|
88
|
+
*
|
|
89
|
+
* if (!(await hasSystemd()).available) {
|
|
90
|
+
* console.log("Running in container/without systemd - skipping service creation");
|
|
91
|
+
* // Run process directly instead
|
|
92
|
+
* } else {
|
|
93
|
+
* await createServiceUnit("my-service", { ... });
|
|
94
|
+
* }
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export declare function hasSystemd(opts?: SudoOptions): Promise<SystemdCheckResult>;
|
|
98
|
+
/**
|
|
99
|
+
* Clear the systemd detection cache.
|
|
100
|
+
* Useful for testing or when system state may have changed.
|
|
101
|
+
*/
|
|
102
|
+
export declare function clearSystemdCache(): void;
|
|
6
103
|
export interface ServiceUnitOptions {
|
|
7
104
|
/** Service description */
|
|
8
105
|
description: string;
|
|
@@ -40,6 +137,52 @@ export interface ServiceUnitOptions {
|
|
|
40
137
|
nice?: number;
|
|
41
138
|
/** Additional unit file sections (e.g. [Install], [Unit] extras) */
|
|
42
139
|
extras?: string;
|
|
140
|
+
/** Prevent the process and its children from gaining new privileges via execve */
|
|
141
|
+
noNewPrivileges?: boolean;
|
|
142
|
+
/** Mount /tmp and /var/tmp as private, empty directories */
|
|
143
|
+
privateTmp?: boolean;
|
|
144
|
+
/** Make the file system hierarchy read-only (strict = full read-only except /var, /etc, /home) */
|
|
145
|
+
protectSystem?: "strict" | "full" | "yes";
|
|
146
|
+
/** Mount /home and /root read-only or as tmpfs (true = read-only) */
|
|
147
|
+
protectHome?: boolean | "read-only" | "tmpfs";
|
|
148
|
+
/** Paths that should be mounted read-only (default: all paths except readWritePaths) */
|
|
149
|
+
readOnlyPaths?: string[];
|
|
150
|
+
/** Paths that should be writable (whitelist approach for security) */
|
|
151
|
+
readWritePaths?: string[];
|
|
152
|
+
/** Restrict real-time scheduling access */
|
|
153
|
+
restrictRealtime?: boolean;
|
|
154
|
+
/** Deny writing to memory/execute permissions (prevents JIT compilation, W^X) */
|
|
155
|
+
memoryDenyWriteExecute?: boolean;
|
|
156
|
+
/** Make kernel tunables (sysctl, /proc/sys, /sys) read-only */
|
|
157
|
+
protectKernelTunables?: boolean;
|
|
158
|
+
/** Prevent kernel module loading/unloading */
|
|
159
|
+
protectKernelModules?: boolean;
|
|
160
|
+
/** Prevent access to cgroups filesystem */
|
|
161
|
+
protectControlGroups?: boolean;
|
|
162
|
+
/** Restrict address families (e.g., ["AF_UNIX", "AF_INET", "AF_INET6"]) */
|
|
163
|
+
restrictAddressFamilies?: string[];
|
|
164
|
+
/** Create device node whitelist (empty = no device access) */
|
|
165
|
+
deviceAllow?: string[];
|
|
166
|
+
/** Make device nodes read-only */
|
|
167
|
+
devicePolicy?: "auto" | "closed" | "strict";
|
|
168
|
+
/** Restrict system call access (systemd 241+) */
|
|
169
|
+
systemCallFilter?: string[];
|
|
170
|
+
/** Restrict filesystem namespaces */
|
|
171
|
+
restrictNamespaces?: boolean | string[];
|
|
172
|
+
/** Set personality (e.g., "x86-64") */
|
|
173
|
+
personality?: string;
|
|
174
|
+
/** Lock personality with syscalls */
|
|
175
|
+
lockPersonality?: boolean;
|
|
176
|
+
/** Remove CAP_SYS_* capabilities */
|
|
177
|
+
removeCapability?: string[];
|
|
178
|
+
/** Bounding set to drop capabilities */
|
|
179
|
+
capabilityBoundingSet?: string[];
|
|
180
|
+
/** Ambient capabilities to keep */
|
|
181
|
+
ambientCapabilities?: string[];
|
|
182
|
+
/** Private devices namespace (/dev whitelist) */
|
|
183
|
+
privateDevices?: boolean;
|
|
184
|
+
/** Mount /usr read-only */
|
|
185
|
+
protectKernelLogs?: boolean;
|
|
43
186
|
}
|
|
44
187
|
export interface ServiceResult extends ExecResult {
|
|
45
188
|
/** Path to the unit file */
|
|
@@ -104,7 +247,7 @@ export declare function serviceExists(name: string, opts: SudoOptions): Promise<
|
|
|
104
247
|
/**
|
|
105
248
|
* Get service logs via journalctl.
|
|
106
249
|
*/
|
|
107
|
-
export declare function getServiceLogs(name: string, opts
|
|
250
|
+
export declare function getServiceLogs(name: string, opts?: SudoOptions & {
|
|
108
251
|
/** Number of lines to show (default: 100) */
|
|
109
252
|
lines?: number;
|
|
110
253
|
/** Follow logs */
|
|
@@ -112,3 +255,64 @@ export declare function getServiceLogs(name: string, opts: SudoOptions & {
|
|
|
112
255
|
/** Show logs since timestamp */
|
|
113
256
|
since?: string;
|
|
114
257
|
}): Promise<string>;
|
|
258
|
+
/**
|
|
259
|
+
* Security hardening presets for systemd services.
|
|
260
|
+
* Apply these using spread syntax: { ...SECURITY_PRESETS.strict, ...customOptions }
|
|
261
|
+
*/
|
|
262
|
+
export declare const SECURITY_PRESETS: Record<string, Partial<ServiceUnitOptions>>;
|
|
263
|
+
/**
|
|
264
|
+
* Helper to merge security preset with custom options.
|
|
265
|
+
* Custom options override preset values.
|
|
266
|
+
*/
|
|
267
|
+
export declare function withSecurityPreset(preset: keyof typeof SECURITY_PRESETS, customOptions: ServiceUnitOptions): ServiceUnitOptions;
|
|
268
|
+
/**
|
|
269
|
+
* Options for safe systemd operations
|
|
270
|
+
*/
|
|
271
|
+
export interface SafeSystemdOptions {
|
|
272
|
+
/** Whether to throw error if systemd is not available (default: true) */
|
|
273
|
+
requireSystemd?: boolean;
|
|
274
|
+
/** Callback to run when systemd is not available */
|
|
275
|
+
onNoSystemd?: (check: SystemdCheckResult) => void | Promise<void>;
|
|
276
|
+
/** Execution context (defaults to local if not provided) */
|
|
277
|
+
context?: SudoOptions["context"];
|
|
278
|
+
/** Environment variables */
|
|
279
|
+
env?: Record<string, string>;
|
|
280
|
+
/** Timeout in ms */
|
|
281
|
+
timeout?: number;
|
|
282
|
+
/** Suppress output */
|
|
283
|
+
quiet?: boolean;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Safely execute a systemd operation with automatic detection.
|
|
287
|
+
*
|
|
288
|
+
* If systemd is not available, either:
|
|
289
|
+
* - Throws an error (if requireSystemd = true, default)
|
|
290
|
+
* - Calls onNoSystemd callback (if provided)
|
|
291
|
+
* - Returns undefined gracefully
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```typescript
|
|
295
|
+
* import { safeSystemd, createServiceUnit } from "@ebowwa/seedinstallation/systemd";
|
|
296
|
+
*
|
|
297
|
+
* // Will throw if no systemd
|
|
298
|
+
* await safeSystemd(async () => {
|
|
299
|
+
* await createServiceUnit("my-service", { ... });
|
|
300
|
+
* });
|
|
301
|
+
*
|
|
302
|
+
* // Will skip gracefully if no systemd
|
|
303
|
+
* await safeSystemd(
|
|
304
|
+
* async () => {
|
|
305
|
+
* await createServiceUnit("my-service", { ... });
|
|
306
|
+
* },
|
|
307
|
+
* {
|
|
308
|
+
* requireSystemd: false,
|
|
309
|
+
* onNoSystemd: async (check) => {
|
|
310
|
+
* console.log(`No systemd: ${check.message}`);
|
|
311
|
+
* // Run process directly instead
|
|
312
|
+
* },
|
|
313
|
+
* }
|
|
314
|
+
* );
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
export declare function safeSystemd<T>(operation: () => Promise<T>, options?: SafeSystemdOptions): Promise<T | undefined>;
|
|
318
|
+
//# sourceMappingURL=systemd.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"systemd.d.ts","sourceRoot":"","sources":["../systemd.ts"],"names":[],"mappings":"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"}
|