unitup 0.0.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/LICENSE +21 -0
- package/README.md +387 -0
- package/bin/unitup.js +4 -0
- package/index.d.ts +378 -0
- package/package.json +28 -0
- package/src/cli.js +351 -0
- package/src/doctor.js +109 -0
- package/src/index.js +70 -0
- package/src/systemd.js +506 -0
- package/src/unit.js +207 -0
- package/src/utils.js +184 -0
package/src/cli.js
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { runDoctor } from './doctor.js';
|
|
4
|
+
import {
|
|
5
|
+
addService,
|
|
6
|
+
startService,
|
|
7
|
+
stopService,
|
|
8
|
+
restartService,
|
|
9
|
+
removeService,
|
|
10
|
+
getServiceStatus,
|
|
11
|
+
getServiceStatusRaw,
|
|
12
|
+
listServices,
|
|
13
|
+
runJournalctlLogs,
|
|
14
|
+
isLinux,
|
|
15
|
+
isSystemctlAvailable,
|
|
16
|
+
findNodeExecutable
|
|
17
|
+
} from './systemd.js';
|
|
18
|
+
import { sanitizeServiceName, formatTable } from './utils.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Custom light CLI argument parser without runtime dependencies.
|
|
22
|
+
*
|
|
23
|
+
* @param {string[]} argv
|
|
24
|
+
*/
|
|
25
|
+
export function parseArgs(argv) {
|
|
26
|
+
const result = {
|
|
27
|
+
command: '',
|
|
28
|
+
positionals: [],
|
|
29
|
+
flags: {
|
|
30
|
+
name: '',
|
|
31
|
+
node: '',
|
|
32
|
+
cwd: '',
|
|
33
|
+
env: [],
|
|
34
|
+
envFile: '',
|
|
35
|
+
restart: 'on-failure',
|
|
36
|
+
args: [],
|
|
37
|
+
start: false,
|
|
38
|
+
enable: false,
|
|
39
|
+
raw: false,
|
|
40
|
+
follow: false,
|
|
41
|
+
lines: 100,
|
|
42
|
+
help: false,
|
|
43
|
+
version: false
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
let i = 0;
|
|
48
|
+
while (i < argv.length) {
|
|
49
|
+
const arg = argv[i];
|
|
50
|
+
|
|
51
|
+
if (arg === '-h' || arg === '--help') {
|
|
52
|
+
result.flags.help = true;
|
|
53
|
+
i++;
|
|
54
|
+
} else if (arg === '-v' || arg === '--version') {
|
|
55
|
+
result.flags.version = true;
|
|
56
|
+
i++;
|
|
57
|
+
} else if (arg === '--node') {
|
|
58
|
+
result.flags.node = argv[i + 1] || '';
|
|
59
|
+
i += 2;
|
|
60
|
+
} else if (arg.startsWith('--node=')) {
|
|
61
|
+
result.flags.node = arg.slice(7);
|
|
62
|
+
i++;
|
|
63
|
+
} else if (arg === '--name') {
|
|
64
|
+
result.flags.name = argv[i + 1] || '';
|
|
65
|
+
i += 2;
|
|
66
|
+
} else if (arg.startsWith('--name=')) {
|
|
67
|
+
result.flags.name = arg.slice(7);
|
|
68
|
+
i++;
|
|
69
|
+
} else if (arg === '--cwd') {
|
|
70
|
+
result.flags.cwd = argv[i + 1] || '';
|
|
71
|
+
i += 2;
|
|
72
|
+
} else if (arg.startsWith('--cwd=')) {
|
|
73
|
+
result.flags.cwd = arg.slice(6);
|
|
74
|
+
i++;
|
|
75
|
+
} else if (arg === '--env') {
|
|
76
|
+
if (argv[i + 1]) result.flags.env.push(argv[i + 1]);
|
|
77
|
+
i += 2;
|
|
78
|
+
} else if (arg.startsWith('--env=')) {
|
|
79
|
+
result.flags.env.push(arg.slice(6));
|
|
80
|
+
i++;
|
|
81
|
+
} else if (arg === '--env-file') {
|
|
82
|
+
result.flags.envFile = argv[i + 1] || '';
|
|
83
|
+
i += 2;
|
|
84
|
+
} else if (arg.startsWith('--env-file=')) {
|
|
85
|
+
result.flags.envFile = arg.slice(11);
|
|
86
|
+
i++;
|
|
87
|
+
} else if (arg === '--restart') {
|
|
88
|
+
result.flags.restart = argv[i + 1] || 'on-failure';
|
|
89
|
+
i += 2;
|
|
90
|
+
} else if (arg.startsWith('--restart=')) {
|
|
91
|
+
result.flags.restart = arg.slice(10);
|
|
92
|
+
i++;
|
|
93
|
+
} else if (arg === '--arg') {
|
|
94
|
+
if (argv[i + 1]) result.flags.args.push(argv[i + 1]);
|
|
95
|
+
i += 2;
|
|
96
|
+
} else if (arg.startsWith('--arg=')) {
|
|
97
|
+
result.flags.args.push(arg.slice(6));
|
|
98
|
+
i++;
|
|
99
|
+
} else if (arg === '--lines' || arg === '-n') {
|
|
100
|
+
result.flags.lines = parseInt(argv[i + 1] || '100', 10);
|
|
101
|
+
i += 2;
|
|
102
|
+
} else if (arg.startsWith('--lines=')) {
|
|
103
|
+
result.flags.lines = parseInt(arg.slice(8), 10);
|
|
104
|
+
i++;
|
|
105
|
+
} else if (arg === '--start') {
|
|
106
|
+
result.flags.start = true;
|
|
107
|
+
i++;
|
|
108
|
+
} else if (arg === '--enable') {
|
|
109
|
+
result.flags.enable = true;
|
|
110
|
+
i++;
|
|
111
|
+
} else if (arg === '--raw') {
|
|
112
|
+
result.flags.raw = true;
|
|
113
|
+
i++;
|
|
114
|
+
} else if (arg === '-f' || arg === '--follow') {
|
|
115
|
+
result.flags.follow = true;
|
|
116
|
+
i++;
|
|
117
|
+
} else if (!arg.startsWith('-')) {
|
|
118
|
+
if (!result.command) {
|
|
119
|
+
result.command = arg;
|
|
120
|
+
} else {
|
|
121
|
+
result.positionals.push(arg);
|
|
122
|
+
}
|
|
123
|
+
i++;
|
|
124
|
+
} else {
|
|
125
|
+
// Unknown flag, treat as positional or ignore
|
|
126
|
+
i++;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function printHelp() {
|
|
134
|
+
console.log(`
|
|
135
|
+
unitup - Minimal systemd user service wrapper for Node.js scripts
|
|
136
|
+
|
|
137
|
+
Usage:
|
|
138
|
+
unitup doctor Run system readiness check
|
|
139
|
+
unitup add <script> [options] Add script as systemd user service
|
|
140
|
+
unitup start <name> Start a service (--enable to enable on boot)
|
|
141
|
+
unitup stop <name> Stop a service
|
|
142
|
+
unitup restart <name> Restart a service
|
|
143
|
+
unitup status <name> Show status summary (--raw for full status)
|
|
144
|
+
unitup logs <name> Show journalctl logs (--follow, --lines N)
|
|
145
|
+
unitup remove <name> Stop, disable and delete a service
|
|
146
|
+
unitup list List all unitup user services
|
|
147
|
+
|
|
148
|
+
Add Options:
|
|
149
|
+
--name <name> Service name (default: script name)
|
|
150
|
+
--cwd <path> Working directory
|
|
151
|
+
--restart <policy> Restart policy (on-failure, always, etc. Default: on-failure)
|
|
152
|
+
--env KEY=value Environment variable (can be specified multiple times)
|
|
153
|
+
--env-file <file> Path to environment file
|
|
154
|
+
--arg <value> Script argument (can be specified multiple times)
|
|
155
|
+
--start Enable and start service immediately after creation
|
|
156
|
+
|
|
157
|
+
Examples:
|
|
158
|
+
unitup add server.js --name api --env NODE_ENV=production --start
|
|
159
|
+
unitup status api
|
|
160
|
+
unitup logs api --follow
|
|
161
|
+
unitup remove api
|
|
162
|
+
`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Main CLI runner entrypoint.
|
|
167
|
+
*
|
|
168
|
+
* @param {string[]} argv
|
|
169
|
+
*/
|
|
170
|
+
export async function runCli(argv = process.argv.slice(2)) {
|
|
171
|
+
const parsed = parseArgs(argv);
|
|
172
|
+
|
|
173
|
+
if (parsed.flags.version) {
|
|
174
|
+
try {
|
|
175
|
+
const pkgPath = new URL('../package.json', import.meta.url);
|
|
176
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
177
|
+
console.log(`unitup v${pkg.version}`);
|
|
178
|
+
} catch {
|
|
179
|
+
console.log('unitup v1.0.0');
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (parsed.flags.help || !parsed.command) {
|
|
185
|
+
printHelp();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const { command, positionals, flags } = parsed;
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
switch (command) {
|
|
193
|
+
case 'doctor': {
|
|
194
|
+
await runDoctor();
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
case 'add': {
|
|
199
|
+
const scriptArg = positionals[0];
|
|
200
|
+
if (!scriptArg) {
|
|
201
|
+
throw new Error('Script file path is required.\nExample: unitup add app.js');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const resolvedNode = await findNodeExecutable(flags.node);
|
|
205
|
+
if (!resolvedNode) {
|
|
206
|
+
throw new Error('Node.js is required but not found.\nRun: unitup doctor');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const absScriptPath = path.resolve(process.cwd(), scriptArg);
|
|
210
|
+
if (!fs.existsSync(absScriptPath)) {
|
|
211
|
+
throw new Error(`Script file does not exist: ${absScriptPath}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let name = flags.name;
|
|
215
|
+
if (!name) {
|
|
216
|
+
const baseName = path.basename(scriptArg);
|
|
217
|
+
const ext = path.extname(baseName);
|
|
218
|
+
name = ext ? baseName.slice(0, -ext.length) : baseName;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const envObj = {};
|
|
222
|
+
for (const e of flags.env) {
|
|
223
|
+
const idx = e.indexOf('=');
|
|
224
|
+
if (idx !== -1) {
|
|
225
|
+
envObj[e.slice(0, idx)] = e.slice(idx + 1);
|
|
226
|
+
} else {
|
|
227
|
+
envObj[e] = '';
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const res = await addService({
|
|
232
|
+
name,
|
|
233
|
+
script: absScriptPath,
|
|
234
|
+
nodePath: resolvedNode,
|
|
235
|
+
cwd: flags.cwd ? path.resolve(process.cwd(), flags.cwd) : path.dirname(absScriptPath),
|
|
236
|
+
env: envObj,
|
|
237
|
+
envFile: flags.envFile ? path.resolve(process.cwd(), flags.envFile) : undefined,
|
|
238
|
+
restart: flags.restart,
|
|
239
|
+
args: flags.args,
|
|
240
|
+
start: flags.start
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
console.log(`✓ Service "${res.name}" created at ${res.unitPath}`);
|
|
244
|
+
if (flags.start) {
|
|
245
|
+
console.log(`✓ Service "${res.name}" enabled and started.`);
|
|
246
|
+
} else {
|
|
247
|
+
console.log(`Run "unitup start ${res.name}" to start it.`);
|
|
248
|
+
}
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
case 'start': {
|
|
253
|
+
const name = positionals[0];
|
|
254
|
+
if (!name) {
|
|
255
|
+
throw new Error('Service name is required.\nExample: unitup start api');
|
|
256
|
+
}
|
|
257
|
+
await startService(name, flags.enable);
|
|
258
|
+
console.log(`✓ Service "${sanitizeServiceName(name)}" ${flags.enable ? 'enabled & ' : ''}started.`);
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
case 'stop': {
|
|
263
|
+
const name = positionals[0];
|
|
264
|
+
if (!name) {
|
|
265
|
+
throw new Error('Service name is required.\nExample: unitup stop api');
|
|
266
|
+
}
|
|
267
|
+
await stopService(name);
|
|
268
|
+
console.log(`✓ Service "${sanitizeServiceName(name)}" stopped.`);
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
case 'restart': {
|
|
273
|
+
const name = positionals[0];
|
|
274
|
+
if (!name) {
|
|
275
|
+
throw new Error('Service name is required.\nExample: unitup restart api');
|
|
276
|
+
}
|
|
277
|
+
await restartService(name);
|
|
278
|
+
console.log(`✓ Service "${sanitizeServiceName(name)}" restarted.`);
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
case 'status': {
|
|
283
|
+
const name = positionals[0];
|
|
284
|
+
if (!name) {
|
|
285
|
+
throw new Error('Service name is required.\nExample: unitup status api');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (flags.raw) {
|
|
289
|
+
const raw = await getServiceStatusRaw(name);
|
|
290
|
+
console.log(raw);
|
|
291
|
+
} else {
|
|
292
|
+
const status = await getServiceStatus(name);
|
|
293
|
+
console.log(`${status.name}\n`);
|
|
294
|
+
console.log(`Status: ${status.status}`);
|
|
295
|
+
console.log(`PID: ${status.pid}`);
|
|
296
|
+
console.log(`Started: ${status.started}`);
|
|
297
|
+
console.log(`Restarts: ${status.restarts}`);
|
|
298
|
+
console.log(`Script: ${status.script}`);
|
|
299
|
+
console.log(`Working directory: ${status.cwd}`);
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
case 'logs': {
|
|
305
|
+
const name = positionals[0];
|
|
306
|
+
if (!name) {
|
|
307
|
+
throw new Error('Service name is required.\nExample: unitup logs api');
|
|
308
|
+
}
|
|
309
|
+
const output = await runJournalctlLogs(name, {
|
|
310
|
+
follow: flags.follow,
|
|
311
|
+
lines: flags.lines
|
|
312
|
+
});
|
|
313
|
+
if (typeof output === 'string') {
|
|
314
|
+
console.log(output);
|
|
315
|
+
}
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
case 'remove': {
|
|
320
|
+
const name = positionals[0];
|
|
321
|
+
if (!name) {
|
|
322
|
+
throw new Error('Service name is required.\nExample: unitup remove api');
|
|
323
|
+
}
|
|
324
|
+
await removeService(name);
|
|
325
|
+
console.log(`✓ Service "${sanitizeServiceName(name)}" removed.`);
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
case 'list': {
|
|
330
|
+
const services = await listServices();
|
|
331
|
+
const table = formatTable(services, [
|
|
332
|
+
{ key: 'name', label: 'NAME' },
|
|
333
|
+
{ key: 'status', label: 'STATUS' },
|
|
334
|
+
{ key: 'enabled', label: 'ENABLED' }
|
|
335
|
+
]);
|
|
336
|
+
console.log(table);
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
default: {
|
|
341
|
+
console.error(`Unknown command: "${command}"\n`);
|
|
342
|
+
printHelp();
|
|
343
|
+
process.exitCode = 1;
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} catch (err) {
|
|
348
|
+
console.error(`Error: ${err.message}`);
|
|
349
|
+
process.exitCode = 1;
|
|
350
|
+
}
|
|
351
|
+
}
|
package/src/doctor.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import {
|
|
3
|
+
isLinux,
|
|
4
|
+
isSystemctlAvailable,
|
|
5
|
+
isSystemdPID1,
|
|
6
|
+
isUserSystemdAvailable,
|
|
7
|
+
isUserUnitDirWritable,
|
|
8
|
+
checkUserLinger,
|
|
9
|
+
checkNodeDiagnostics
|
|
10
|
+
} from './systemd.js';
|
|
11
|
+
import { getUserUnitDir } from './unit.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Runs system diagnostic checks and returns structured data.
|
|
15
|
+
*
|
|
16
|
+
* @returns {Promise<Object>}
|
|
17
|
+
*/
|
|
18
|
+
export async function getDoctorInfo() {
|
|
19
|
+
const linux = isLinux();
|
|
20
|
+
const systemctl = await isSystemctlAvailable();
|
|
21
|
+
const pid1 = await isSystemdPID1();
|
|
22
|
+
const userSystemd = await isUserSystemdAvailable();
|
|
23
|
+
const unitDirWritable = await isUserUnitDirWritable();
|
|
24
|
+
const lingering = await checkUserLinger();
|
|
25
|
+
const nodeDiag = await checkNodeDiagnostics();
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
linux,
|
|
29
|
+
systemctl,
|
|
30
|
+
systemdRunning: pid1,
|
|
31
|
+
userSystemdAvailable: userSystemd,
|
|
32
|
+
nodeDiag,
|
|
33
|
+
nodePath: nodeDiag.execPath || process.execPath,
|
|
34
|
+
nodeVersion: nodeDiag.version,
|
|
35
|
+
unitDir: getUserUnitDir(),
|
|
36
|
+
unitDirWritable,
|
|
37
|
+
lingering,
|
|
38
|
+
username: os.userInfo().username
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Executes doctor check and prints human-readable output to terminal.
|
|
44
|
+
*/
|
|
45
|
+
export async function runDoctor() {
|
|
46
|
+
console.log('unitup doctor\n');
|
|
47
|
+
|
|
48
|
+
const info = await getDoctorInfo();
|
|
49
|
+
|
|
50
|
+
if (info.linux) {
|
|
51
|
+
console.log('✓ Linux detected');
|
|
52
|
+
} else {
|
|
53
|
+
console.log('✗ Non-Linux OS detected (systemd requires Linux)');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (info.systemctl) {
|
|
57
|
+
console.log('✓ systemctl available');
|
|
58
|
+
} else {
|
|
59
|
+
console.log('✗ systemctl command not found');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (info.systemdRunning) {
|
|
63
|
+
console.log('✓ systemd is running');
|
|
64
|
+
} else {
|
|
65
|
+
console.log('! systemd is not running as PID 1');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (info.userSystemdAvailable) {
|
|
69
|
+
console.log('✓ systemd user services available');
|
|
70
|
+
} else {
|
|
71
|
+
console.log('! systemd user services unavailable');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const n = info.nodeDiag;
|
|
75
|
+
if (n.found && n.executable) {
|
|
76
|
+
console.log(`✓ Node.js: ${n.execPath}`);
|
|
77
|
+
if (n.version) {
|
|
78
|
+
console.log(`✓ Node version: ${n.version}`);
|
|
79
|
+
}
|
|
80
|
+
if (n.inPath) {
|
|
81
|
+
console.log('✓ PATH includes node');
|
|
82
|
+
} else {
|
|
83
|
+
console.log('! node is not in standard PATH');
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
console.log(`✗ ${n.error || 'Node.js executable could not be resolved'}`);
|
|
87
|
+
if (n.solution) {
|
|
88
|
+
console.log(`\n${n.solution}\n`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (info.unitDirWritable) {
|
|
93
|
+
console.log('✓ Unit directory writable');
|
|
94
|
+
} else {
|
|
95
|
+
console.log(`! Unit directory not writable (${info.unitDir})`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (info.lingering) {
|
|
99
|
+
console.log('✓ User lingering is enabled');
|
|
100
|
+
} else {
|
|
101
|
+
console.log('! User lingering is disabled');
|
|
102
|
+
console.log('\nUser lingering is disabled.');
|
|
103
|
+
console.log('The service may stop after logout.');
|
|
104
|
+
console.log('\nEnable it manually:');
|
|
105
|
+
console.log(` loginctl enable-linger ${info.username}\n`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return info;
|
|
109
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addService,
|
|
3
|
+
startService,
|
|
4
|
+
stopService,
|
|
5
|
+
restartService,
|
|
6
|
+
removeService,
|
|
7
|
+
getServiceStatus,
|
|
8
|
+
getServiceStatusRaw,
|
|
9
|
+
listServices,
|
|
10
|
+
runJournalctlLogs,
|
|
11
|
+
isLinux,
|
|
12
|
+
isSystemctlAvailable,
|
|
13
|
+
isSystemdPID1,
|
|
14
|
+
isUserSystemdAvailable,
|
|
15
|
+
checkUserLinger,
|
|
16
|
+
checkNodeDiagnostics,
|
|
17
|
+
findNodeExecutable,
|
|
18
|
+
setCommandRunner,
|
|
19
|
+
resetCommandRunner
|
|
20
|
+
} from './systemd.js';
|
|
21
|
+
import { getDoctorInfo } from './doctor.js';
|
|
22
|
+
import {
|
|
23
|
+
getUserUnitDir,
|
|
24
|
+
getUnitPath,
|
|
25
|
+
unitFileExists,
|
|
26
|
+
parseUnitContent
|
|
27
|
+
} from './unit.js';
|
|
28
|
+
import {
|
|
29
|
+
sanitizeServiceName,
|
|
30
|
+
getUnitFilename,
|
|
31
|
+
getServiceNameFromUnit
|
|
32
|
+
} from './utils.js';
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
// Main programmatic service management API
|
|
36
|
+
addService as createService,
|
|
37
|
+
addService,
|
|
38
|
+
startService,
|
|
39
|
+
stopService,
|
|
40
|
+
restartService,
|
|
41
|
+
removeService,
|
|
42
|
+
getServiceStatus,
|
|
43
|
+
getServiceStatusRaw,
|
|
44
|
+
listServices,
|
|
45
|
+
runJournalctlLogs as getServiceLogs,
|
|
46
|
+
|
|
47
|
+
// System & Doctor checks
|
|
48
|
+
isSystemctlAvailable as isSystemdAvailable,
|
|
49
|
+
isSystemctlAvailable,
|
|
50
|
+
isLinux,
|
|
51
|
+
isSystemdPID1,
|
|
52
|
+
isUserSystemdAvailable,
|
|
53
|
+
checkUserLinger,
|
|
54
|
+
checkNodeDiagnostics,
|
|
55
|
+
findNodeExecutable,
|
|
56
|
+
getDoctorInfo,
|
|
57
|
+
|
|
58
|
+
// Unit file & path helpers
|
|
59
|
+
getUserUnitDir,
|
|
60
|
+
getUnitPath,
|
|
61
|
+
unitFileExists,
|
|
62
|
+
parseUnitContent,
|
|
63
|
+
sanitizeServiceName,
|
|
64
|
+
getUnitFilename,
|
|
65
|
+
getServiceNameFromUnit,
|
|
66
|
+
|
|
67
|
+
// Runner override for testing
|
|
68
|
+
setCommandRunner,
|
|
69
|
+
resetCommandRunner
|
|
70
|
+
};
|