unitup 0.0.9 → 0.0.11
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/README.md +401 -181
- package/docs/index.html +544 -0
- package/index.d.ts +140 -153
- package/package.json +18 -4
- package/src/cli.js +259 -55
- package/src/doctor.js +73 -14
- package/src/index.js +16 -1
- package/src/runtimes/bun.js +46 -0
- package/src/runtimes/common.js +73 -0
- package/src/runtimes/deno.js +46 -0
- package/src/runtimes/elixir.js +46 -0
- package/src/runtimes/go.js +46 -0
- package/src/runtimes/index.js +155 -0
- package/src/runtimes/native.js +38 -0
- package/src/runtimes/node.js +57 -0
- package/src/runtimes/php.js +46 -0
- package/src/runtimes/python.js +46 -0
- package/src/runtimes/ruby.js +46 -0
- package/src/runtimes/shell.js +46 -0
- package/src/systemd.js +262 -30
- package/src/unit.js +59 -21
- package/src/utils.js +139 -4
- package/tsconfig.json +13 -0
package/src/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import readline from 'node:readline';
|
|
3
4
|
import { runDoctor } from './doctor.js';
|
|
4
5
|
import {
|
|
5
6
|
addService,
|
|
@@ -16,7 +17,9 @@ import {
|
|
|
16
17
|
findNodeExecutable,
|
|
17
18
|
getServicesByGroup,
|
|
18
19
|
inspectService,
|
|
19
|
-
getServiceFailures
|
|
20
|
+
getServiceFailures,
|
|
21
|
+
setServiceLimits,
|
|
22
|
+
executeJournalctlMaintenance
|
|
20
23
|
} from './systemd.js';
|
|
21
24
|
import { sanitizeServiceName, formatTable } from './utils.js';
|
|
22
25
|
|
|
@@ -32,6 +35,9 @@ export function parseArgs(argv) {
|
|
|
32
35
|
flags: {
|
|
33
36
|
name: '',
|
|
34
37
|
group: '',
|
|
38
|
+
runtime: '',
|
|
39
|
+
runtimeArgs: [],
|
|
40
|
+
command: '',
|
|
35
41
|
node: '',
|
|
36
42
|
cwd: '',
|
|
37
43
|
env: [],
|
|
@@ -46,7 +52,24 @@ export function parseArgs(argv) {
|
|
|
46
52
|
output: '',
|
|
47
53
|
lines: 100,
|
|
48
54
|
help: false,
|
|
49
|
-
version: false
|
|
55
|
+
version: false,
|
|
56
|
+
force: false,
|
|
57
|
+
memoryHigh: '',
|
|
58
|
+
memoryMax: '',
|
|
59
|
+
memorySwapMax: '',
|
|
60
|
+
resetMemory: false,
|
|
61
|
+
since: '',
|
|
62
|
+
until: '',
|
|
63
|
+
priority: '',
|
|
64
|
+
grep: '',
|
|
65
|
+
boot: false,
|
|
66
|
+
json: false,
|
|
67
|
+
diskUsage: false,
|
|
68
|
+
size: '',
|
|
69
|
+
time: '',
|
|
70
|
+
files: '',
|
|
71
|
+
yes: false,
|
|
72
|
+
dryRun: false
|
|
50
73
|
}
|
|
51
74
|
};
|
|
52
75
|
|
|
@@ -60,6 +83,24 @@ export function parseArgs(argv) {
|
|
|
60
83
|
} else if (arg === '-v' || arg === '--version') {
|
|
61
84
|
result.flags.version = true;
|
|
62
85
|
i++;
|
|
86
|
+
} else if (arg === '--runtime') {
|
|
87
|
+
result.flags.runtime = argv[i + 1] || '';
|
|
88
|
+
i += 2;
|
|
89
|
+
} else if (arg.startsWith('--runtime=')) {
|
|
90
|
+
result.flags.runtime = arg.slice(10);
|
|
91
|
+
i++;
|
|
92
|
+
} else if (arg === '--runtime-arg') {
|
|
93
|
+
if (argv[i + 1]) result.flags.runtimeArgs.push(argv[i + 1]);
|
|
94
|
+
i += 2;
|
|
95
|
+
} else if (arg.startsWith('--runtime-arg=')) {
|
|
96
|
+
result.flags.runtimeArgs.push(arg.slice(14));
|
|
97
|
+
i++;
|
|
98
|
+
} else if (arg === '--command') {
|
|
99
|
+
result.flags.command = argv[i + 1] || '';
|
|
100
|
+
i += 2;
|
|
101
|
+
} else if (arg.startsWith('--command=')) {
|
|
102
|
+
result.flags.command = arg.slice(10);
|
|
103
|
+
i++;
|
|
63
104
|
} else if (arg === '--node') {
|
|
64
105
|
result.flags.node = argv[i + 1] || '';
|
|
65
106
|
i += 2;
|
|
@@ -123,8 +164,9 @@ export function parseArgs(argv) {
|
|
|
123
164
|
} else if (arg === '--raw') {
|
|
124
165
|
result.flags.raw = true;
|
|
125
166
|
i++;
|
|
126
|
-
} else if (arg === '-f' || arg === '--follow') {
|
|
167
|
+
} else if (arg === '-f' || arg === '--follow' || arg === '--force') {
|
|
127
168
|
result.flags.follow = true;
|
|
169
|
+
result.flags.force = true;
|
|
128
170
|
i++;
|
|
129
171
|
} else if (arg === '-c' || arg === '--cat') {
|
|
130
172
|
result.flags.cat = true;
|
|
@@ -135,6 +177,84 @@ export function parseArgs(argv) {
|
|
|
135
177
|
} else if (arg.startsWith('--output=')) {
|
|
136
178
|
result.flags.output = arg.slice(9);
|
|
137
179
|
i++;
|
|
180
|
+
} else if (arg === '--memory-high') {
|
|
181
|
+
result.flags.memoryHigh = argv[i + 1] || '';
|
|
182
|
+
i += 2;
|
|
183
|
+
} else if (arg.startsWith('--memory-high=')) {
|
|
184
|
+
result.flags.memoryHigh = arg.slice(14);
|
|
185
|
+
i++;
|
|
186
|
+
} else if (arg === '--memory-max') {
|
|
187
|
+
result.flags.memoryMax = argv[i + 1] || '';
|
|
188
|
+
i += 2;
|
|
189
|
+
} else if (arg.startsWith('--memory-max=')) {
|
|
190
|
+
result.flags.memoryMax = arg.slice(13);
|
|
191
|
+
i++;
|
|
192
|
+
} else if (arg === '--memory-swap-max') {
|
|
193
|
+
result.flags.memorySwapMax = argv[i + 1] || '';
|
|
194
|
+
i += 2;
|
|
195
|
+
} else if (arg.startsWith('--memory-swap-max=')) {
|
|
196
|
+
result.flags.memorySwapMax = arg.slice(18);
|
|
197
|
+
i++;
|
|
198
|
+
} else if (arg === '--reset-memory') {
|
|
199
|
+
result.flags.resetMemory = true;
|
|
200
|
+
i++;
|
|
201
|
+
} else if (arg === '--since') {
|
|
202
|
+
result.flags.since = argv[i + 1] || '';
|
|
203
|
+
i += 2;
|
|
204
|
+
} else if (arg.startsWith('--since=')) {
|
|
205
|
+
result.flags.since = arg.slice(8);
|
|
206
|
+
i++;
|
|
207
|
+
} else if (arg === '--until') {
|
|
208
|
+
result.flags.until = argv[i + 1] || '';
|
|
209
|
+
i += 2;
|
|
210
|
+
} else if (arg.startsWith('--until=')) {
|
|
211
|
+
result.flags.until = arg.slice(8);
|
|
212
|
+
i++;
|
|
213
|
+
} else if (arg === '--priority') {
|
|
214
|
+
result.flags.priority = argv[i + 1] || '';
|
|
215
|
+
i += 2;
|
|
216
|
+
} else if (arg.startsWith('--priority=')) {
|
|
217
|
+
result.flags.priority = arg.slice(11);
|
|
218
|
+
i++;
|
|
219
|
+
} else if (arg === '--grep') {
|
|
220
|
+
result.flags.grep = argv[i + 1] || '';
|
|
221
|
+
i += 2;
|
|
222
|
+
} else if (arg.startsWith('--grep=')) {
|
|
223
|
+
result.flags.grep = arg.slice(7);
|
|
224
|
+
i++;
|
|
225
|
+
} else if (arg === '--boot' || arg === '-b') {
|
|
226
|
+
result.flags.boot = true;
|
|
227
|
+
i++;
|
|
228
|
+
} else if (arg === '--json') {
|
|
229
|
+
result.flags.json = true;
|
|
230
|
+
i++;
|
|
231
|
+
} else if (arg === '--disk-usage') {
|
|
232
|
+
result.flags.diskUsage = true;
|
|
233
|
+
i++;
|
|
234
|
+
} else if (arg === '--size') {
|
|
235
|
+
result.flags.size = argv[i + 1] || '';
|
|
236
|
+
i += 2;
|
|
237
|
+
} else if (arg.startsWith('--size=')) {
|
|
238
|
+
result.flags.size = arg.slice(7);
|
|
239
|
+
i++;
|
|
240
|
+
} else if (arg === '--time') {
|
|
241
|
+
result.flags.time = argv[i + 1] || '';
|
|
242
|
+
i += 2;
|
|
243
|
+
} else if (arg.startsWith('--time=')) {
|
|
244
|
+
result.flags.time = arg.slice(7);
|
|
245
|
+
i++;
|
|
246
|
+
} else if (arg === '--files') {
|
|
247
|
+
result.flags.files = argv[i + 1] || '';
|
|
248
|
+
i += 2;
|
|
249
|
+
} else if (arg.startsWith('--files=')) {
|
|
250
|
+
result.flags.files = arg.slice(8);
|
|
251
|
+
i++;
|
|
252
|
+
} else if (arg === '--yes' || arg === '-y') {
|
|
253
|
+
result.flags.yes = true;
|
|
254
|
+
i++;
|
|
255
|
+
} else if (arg === '--dry-run') {
|
|
256
|
+
result.flags.dryRun = true;
|
|
257
|
+
i++;
|
|
138
258
|
} else if (!arg.startsWith('-')) {
|
|
139
259
|
if (!result.command) {
|
|
140
260
|
result.command = arg;
|
|
@@ -153,11 +273,11 @@ export function parseArgs(argv) {
|
|
|
153
273
|
|
|
154
274
|
export function printHelp() {
|
|
155
275
|
console.log(`
|
|
156
|
-
unitup - Minimal systemd user service wrapper for
|
|
276
|
+
unitup - Minimal systemd user service wrapper for any executable & runtime
|
|
157
277
|
|
|
158
278
|
Usage:
|
|
159
|
-
unitup doctor Run system readiness check
|
|
160
|
-
unitup add <script> [options] Add script as systemd user service
|
|
279
|
+
unitup doctor Run system readiness and runtime check
|
|
280
|
+
unitup add <script> [options] Add script or executable as systemd user service
|
|
161
281
|
unitup start <name|@group> Start a service (--enable to enable on boot)
|
|
162
282
|
unitup stop <name|@group> Stop a service
|
|
163
283
|
unitup restart <name|@group> Restart a service
|
|
@@ -169,23 +289,23 @@ Usage:
|
|
|
169
289
|
unitup list / unitup ls List all services (--group <group>)
|
|
170
290
|
|
|
171
291
|
Add Options:
|
|
172
|
-
--name <name>
|
|
173
|
-
--
|
|
174
|
-
--
|
|
175
|
-
--
|
|
176
|
-
--
|
|
177
|
-
--
|
|
178
|
-
--
|
|
179
|
-
--
|
|
180
|
-
--
|
|
292
|
+
--name <name> Service name (default: script/executable name)
|
|
293
|
+
--runtime <name> Runtime (node, python, ruby, php, bun, deno, shell, go, elixir, native)
|
|
294
|
+
--runtime-arg <val> Runtime argument (can be specified multiple times)
|
|
295
|
+
--command <path> Custom executable command path (bypasses auto-detection)
|
|
296
|
+
--arg <value> Argument to pass to command (can be specified multiple times)
|
|
297
|
+
--group <group> Assign service to a group (default: default)
|
|
298
|
+
--cwd <path> Working directory
|
|
299
|
+
--restart <policy> Restart policy (on-failure, always, etc. Default: on-failure)
|
|
300
|
+
--env KEY=value Environment variable (can be specified multiple times)
|
|
301
|
+
--env-file <file> Path to environment file
|
|
302
|
+
--start Enable and start service immediately after creation
|
|
181
303
|
|
|
182
304
|
Examples:
|
|
183
|
-
unitup add server.js --
|
|
184
|
-
unitup
|
|
185
|
-
unitup
|
|
186
|
-
unitup
|
|
187
|
-
unitup failures
|
|
188
|
-
unitup remove @myproject
|
|
305
|
+
unitup add server.js --runtime node
|
|
306
|
+
unitup add worker.py --runtime python
|
|
307
|
+
unitup add ./server --runtime native
|
|
308
|
+
unitup add --name worker --command /usr/bin/python3 --arg worker.py --arg --port --arg 3000
|
|
189
309
|
`);
|
|
190
310
|
}
|
|
191
311
|
|
|
@@ -235,25 +355,39 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
235
355
|
|
|
236
356
|
case 'add': {
|
|
237
357
|
const scriptArg = positionals[0];
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const resolvedNode = await findNodeExecutable(flags.node);
|
|
243
|
-
if (!resolvedNode) {
|
|
244
|
-
throw new Error('Node.js is required but not found.\nRun: unitup doctor');
|
|
245
|
-
}
|
|
358
|
+
let name = flags.name;
|
|
359
|
+
let absScriptPath = scriptArg ? path.resolve(process.cwd(), scriptArg) : undefined;
|
|
246
360
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
361
|
+
if (flags.node) {
|
|
362
|
+
const resolvedNode = await findNodeExecutable(flags.node);
|
|
363
|
+
if (!resolvedNode) {
|
|
364
|
+
throw new Error('Node.js is required but not found.\nRun: unitup doctor');
|
|
365
|
+
}
|
|
250
366
|
}
|
|
251
367
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
368
|
+
if (flags.command) {
|
|
369
|
+
if (!name) {
|
|
370
|
+
if (scriptArg) {
|
|
371
|
+
const baseName = path.basename(scriptArg);
|
|
372
|
+
const ext = path.extname(baseName);
|
|
373
|
+
name = ext ? baseName.slice(0, -ext.length) : baseName;
|
|
374
|
+
} else {
|
|
375
|
+
const baseCmd = path.basename(flags.command);
|
|
376
|
+
name = baseCmd;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
if (!scriptArg) {
|
|
381
|
+
throw new Error('Script file path or --command is required.\nExample: unitup add app.py --runtime python');
|
|
382
|
+
}
|
|
383
|
+
if (!fs.existsSync(absScriptPath)) {
|
|
384
|
+
throw new Error(`Script file does not exist: ${absScriptPath}`);
|
|
385
|
+
}
|
|
386
|
+
if (!name) {
|
|
387
|
+
const baseName = path.basename(scriptArg);
|
|
388
|
+
const ext = path.extname(baseName);
|
|
389
|
+
name = ext ? baseName.slice(0, -ext.length) : baseName;
|
|
390
|
+
}
|
|
257
391
|
}
|
|
258
392
|
|
|
259
393
|
const envObj = {};
|
|
@@ -270,13 +404,20 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
270
404
|
name,
|
|
271
405
|
group: flags.group || 'default',
|
|
272
406
|
script: absScriptPath,
|
|
273
|
-
|
|
274
|
-
|
|
407
|
+
command: flags.command,
|
|
408
|
+
runtime: flags.runtime,
|
|
409
|
+
runtimeArgs: flags.runtimeArgs,
|
|
410
|
+
nodePath: flags.node,
|
|
411
|
+
cwd: flags.cwd ? path.resolve(process.cwd(), flags.cwd) : (absScriptPath ? path.dirname(absScriptPath) : process.cwd()),
|
|
275
412
|
env: envObj,
|
|
276
413
|
envFile: flags.envFile ? path.resolve(process.cwd(), flags.envFile) : undefined,
|
|
277
414
|
restart: flags.restart,
|
|
278
415
|
args: flags.args,
|
|
279
|
-
start: flags.start
|
|
416
|
+
start: flags.start,
|
|
417
|
+
memoryHigh: flags.memoryHigh,
|
|
418
|
+
memoryMax: flags.memoryMax,
|
|
419
|
+
memorySwapMax: flags.memorySwapMax,
|
|
420
|
+
force: flags.force
|
|
280
421
|
});
|
|
281
422
|
|
|
282
423
|
console.log(`✓ Service "${res.name}" created at ${res.unitPath}`);
|
|
@@ -288,6 +429,24 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
288
429
|
break;
|
|
289
430
|
}
|
|
290
431
|
|
|
432
|
+
case 'limits': {
|
|
433
|
+
const nameArg = positionals[0];
|
|
434
|
+
if (!nameArg) {
|
|
435
|
+
throw new Error('Service name is required.\nExample: unitup limits api --memory-high 400M --memory-max 512M');
|
|
436
|
+
}
|
|
437
|
+
const info = await setServiceLimits(nameArg, {
|
|
438
|
+
memoryHigh: flags.memoryHigh,
|
|
439
|
+
memoryMax: flags.memoryMax,
|
|
440
|
+
memorySwapMax: flags.memorySwapMax,
|
|
441
|
+
resetMemory: flags.resetMemory
|
|
442
|
+
});
|
|
443
|
+
console.log(`✓ Service "${info.name}" limits updated.`);
|
|
444
|
+
console.log(`Memory High: ${info.memoryHigh}`);
|
|
445
|
+
console.log(`Memory Max: ${info.memoryMax}`);
|
|
446
|
+
console.log(`Swap Max: ${info.memorySwapMax}`);
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
|
|
291
450
|
case 'start': {
|
|
292
451
|
const nameArg = positionals[0];
|
|
293
452
|
if (!nameArg) {
|
|
@@ -345,8 +504,14 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
345
504
|
console.log(`PID: ${status.pid}`);
|
|
346
505
|
console.log(`Started: ${status.started}`);
|
|
347
506
|
console.log(`Restarts: ${status.restarts}`);
|
|
348
|
-
console.log(`
|
|
507
|
+
console.log(`Command: ${status.command}`);
|
|
508
|
+
console.log(`Arguments: ${status.arguments}`);
|
|
349
509
|
console.log(`Working directory: ${status.cwd}`);
|
|
510
|
+
console.log(`Memory: ${status.memory}`);
|
|
511
|
+
console.log(`Memory Peak: ${status.memoryPeak}`);
|
|
512
|
+
console.log(`Memory High: ${status.memoryHigh}`);
|
|
513
|
+
console.log(`Memory Max: ${status.memoryMax}`);
|
|
514
|
+
console.log(`Swap Max: ${status.memorySwapMax}`);
|
|
350
515
|
}
|
|
351
516
|
}
|
|
352
517
|
break;
|
|
@@ -367,7 +532,14 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
367
532
|
follow: flags.follow,
|
|
368
533
|
lines: flags.lines,
|
|
369
534
|
cat: flags.cat,
|
|
370
|
-
output: flags.output
|
|
535
|
+
output: flags.output,
|
|
536
|
+
since: flags.since,
|
|
537
|
+
until: flags.until,
|
|
538
|
+
priority: flags.priority,
|
|
539
|
+
grep: flags.grep,
|
|
540
|
+
boot: flags.boot,
|
|
541
|
+
json: flags.json,
|
|
542
|
+
diskUsage: flags.diskUsage
|
|
371
543
|
});
|
|
372
544
|
if (typeof output === 'string') {
|
|
373
545
|
console.log(output);
|
|
@@ -376,6 +548,37 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
376
548
|
break;
|
|
377
549
|
}
|
|
378
550
|
|
|
551
|
+
case 'journal': {
|
|
552
|
+
const action = positionals[0];
|
|
553
|
+
if (!action) {
|
|
554
|
+
throw new Error('Journal action is required (disk-usage, rotate, vacuum).\nExample: unitup journal vacuum --size 500M');
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (action === 'vacuum' && !flags.yes && !flags.dryRun) {
|
|
558
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
559
|
+
const answer = await new Promise((resolve) => {
|
|
560
|
+
rl.question('This operation affects archived journal logs system-wide,\nnot only services managed by unitup.\n\nContinue? [y/N] ', (ans) => {
|
|
561
|
+
rl.close();
|
|
562
|
+
resolve(ans.trim());
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
566
|
+
console.log('Aborted.');
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const result = await executeJournalctlMaintenance(action, {
|
|
572
|
+
size: flags.size,
|
|
573
|
+
time: flags.time,
|
|
574
|
+
files: flags.files,
|
|
575
|
+
yes: flags.yes,
|
|
576
|
+
dryRun: flags.dryRun
|
|
577
|
+
});
|
|
578
|
+
console.log(result);
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
|
|
379
582
|
case 'remove': {
|
|
380
583
|
const nameArg = positionals[0];
|
|
381
584
|
if (!nameArg) {
|
|
@@ -383,7 +586,7 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
383
586
|
}
|
|
384
587
|
const targetNames = await resolveTargetNames(nameArg);
|
|
385
588
|
for (const name of targetNames) {
|
|
386
|
-
await removeService(name);
|
|
589
|
+
await removeService(name, { force: flags.force });
|
|
387
590
|
console.log(`✓ Service "${sanitizeServiceName(name)}" removed.`);
|
|
388
591
|
}
|
|
389
592
|
break;
|
|
@@ -403,11 +606,10 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
403
606
|
|
|
404
607
|
const table = formatTable(services, [
|
|
405
608
|
{ key: 'name', label: 'NAME' },
|
|
406
|
-
{ key: '
|
|
609
|
+
{ key: 'runtime', label: 'RUNTIME' },
|
|
407
610
|
{ key: 'status', label: 'STATUS' },
|
|
408
611
|
{ key: 'pid', label: 'PID' },
|
|
409
|
-
{ key: '
|
|
410
|
-
{ key: 'restarts', label: 'RESTARTS' }
|
|
612
|
+
{ key: 'command', label: 'COMMAND' }
|
|
411
613
|
]);
|
|
412
614
|
console.log(table);
|
|
413
615
|
break;
|
|
@@ -419,17 +621,19 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
419
621
|
throw new Error('Service name is required.\nExample: unitup inspect api');
|
|
420
622
|
}
|
|
421
623
|
const info = await inspectService(nameArg);
|
|
422
|
-
console.log(`
|
|
624
|
+
console.log(`Name: ${info.name}`);
|
|
625
|
+
console.log(`Runtime: ${info.runtime}`);
|
|
423
626
|
console.log(`Group: ${info.group}`);
|
|
424
|
-
console.log(`Status: ${info.status}
|
|
425
|
-
console.log(`
|
|
426
|
-
console.log(`
|
|
427
|
-
console.log(`Uptime: ${info.started}`);
|
|
428
|
-
console.log(`Script: ${info.script}`);
|
|
627
|
+
console.log(`Status: ${info.status}`);
|
|
628
|
+
console.log(`Command: ${info.command}`);
|
|
629
|
+
console.log(`Arguments: ${info.arguments}`);
|
|
429
630
|
console.log(`Working directory: ${info.cwd}`);
|
|
430
|
-
console.log(`
|
|
431
|
-
console.log(`
|
|
432
|
-
console.log(`
|
|
631
|
+
console.log(`Unit: ${info.unit}`);
|
|
632
|
+
console.log(`Memory: ${info.memory}`);
|
|
633
|
+
console.log(`Memory Peak: ${info.memoryPeak}`);
|
|
634
|
+
console.log(`Memory High: ${info.memoryHigh}`);
|
|
635
|
+
console.log(`Memory Max: ${info.memoryMax}`);
|
|
636
|
+
console.log(`Swap Max: ${info.memorySwapMax}`);
|
|
433
637
|
break;
|
|
434
638
|
}
|
|
435
639
|
|
package/src/doctor.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
checkNodeDiagnostics
|
|
10
10
|
} from './systemd.js';
|
|
11
11
|
import { getUserUnitDir } from './unit.js';
|
|
12
|
+
import { findRuntimeExecutable } from './runtimes/common.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Runs system diagnostic checks and returns structured data.
|
|
@@ -24,14 +25,54 @@ export async function getDoctorInfo() {
|
|
|
24
25
|
const lingering = await checkUserLinger();
|
|
25
26
|
const nodeDiag = await checkNodeDiagnostics();
|
|
26
27
|
|
|
28
|
+
const nodePath = nodeDiag.execPath || (await findRuntimeExecutable(['node']));
|
|
29
|
+
const pythonPath = await findRuntimeExecutable(['python3', 'python']);
|
|
30
|
+
const rubyPath = await findRuntimeExecutable(['ruby']);
|
|
31
|
+
const phpPath = await findRuntimeExecutable(['php']);
|
|
32
|
+
const bunPath = await findRuntimeExecutable(['bun']);
|
|
33
|
+
const denoPath = await findRuntimeExecutable(['deno']);
|
|
34
|
+
const goPath = await findRuntimeExecutable(['go']);
|
|
35
|
+
const elixirPath = await findRuntimeExecutable(['elixir']);
|
|
36
|
+
|
|
37
|
+
const runtimes = {
|
|
38
|
+
'Node.js': nodePath,
|
|
39
|
+
'Python': pythonPath,
|
|
40
|
+
'Ruby': rubyPath,
|
|
41
|
+
'PHP': phpPath,
|
|
42
|
+
'Bun': bunPath,
|
|
43
|
+
'Deno': denoPath,
|
|
44
|
+
'Go': goPath,
|
|
45
|
+
'Elixir': elixirPath
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
let cgroupV2 = false;
|
|
49
|
+
let memoryController = false;
|
|
50
|
+
try {
|
|
51
|
+
if (fs.existsSync('/sys/fs/cgroup/cgroup.controllers')) {
|
|
52
|
+
cgroupV2 = true;
|
|
53
|
+
const controllers = fs.readFileSync('/sys/fs/cgroup/cgroup.controllers', 'utf8');
|
|
54
|
+
memoryController = controllers.includes('memory');
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// ignore
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const memoryMaxSupported = linux && (cgroupV2 || userSystemd);
|
|
61
|
+
const memorySwapMaxSupported = linux && cgroupV2 && memoryController;
|
|
62
|
+
|
|
27
63
|
return {
|
|
28
64
|
linux,
|
|
29
65
|
systemctl,
|
|
30
66
|
systemdRunning: pid1,
|
|
31
67
|
userSystemdAvailable: userSystemd,
|
|
68
|
+
cgroupV2,
|
|
69
|
+
memoryController,
|
|
70
|
+
memoryMaxSupported,
|
|
71
|
+
memorySwapMaxSupported,
|
|
32
72
|
nodeDiag,
|
|
33
|
-
nodePath:
|
|
73
|
+
nodePath: nodePath || process.execPath,
|
|
34
74
|
nodeVersion: nodeDiag.version,
|
|
75
|
+
runtimes,
|
|
35
76
|
unitDir: getUserUnitDir(),
|
|
36
77
|
unitDirWritable,
|
|
37
78
|
lingering,
|
|
@@ -71,24 +112,42 @@ export async function runDoctor() {
|
|
|
71
112
|
console.log('! systemd user services unavailable');
|
|
72
113
|
}
|
|
73
114
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
console.log(`✓ Node version: ${n.version}`);
|
|
79
|
-
}
|
|
80
|
-
if (n.inPath) {
|
|
81
|
-
console.log('✓ PATH includes node');
|
|
115
|
+
console.log('\nDetected runtimes:');
|
|
116
|
+
for (const [name, path] of Object.entries(info.runtimes)) {
|
|
117
|
+
if (path) {
|
|
118
|
+
console.log(`✓ ${name}: ${path}`);
|
|
82
119
|
} else {
|
|
83
|
-
console.log(
|
|
120
|
+
console.log(`- ${name}: not found`);
|
|
84
121
|
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log('\nMemory Controller Diagnostics:');
|
|
125
|
+
if (info.cgroupV2) {
|
|
126
|
+
console.log('✓ cgroup v2 detected');
|
|
85
127
|
} else {
|
|
86
|
-
console.log(
|
|
87
|
-
if (n.solution) {
|
|
88
|
-
console.log(`\n${n.solution}\n`);
|
|
89
|
-
}
|
|
128
|
+
console.log('- cgroup v2 unavailable');
|
|
90
129
|
}
|
|
91
130
|
|
|
131
|
+
if (info.memoryController) {
|
|
132
|
+
console.log('✓ Memory controller available');
|
|
133
|
+
} else {
|
|
134
|
+
console.log('- Memory controller unavailable');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (info.memoryMaxSupported) {
|
|
138
|
+
console.log('✓ MemoryMax supported');
|
|
139
|
+
} else {
|
|
140
|
+
console.log('- MemoryMax unavailable');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (info.memorySwapMaxSupported) {
|
|
144
|
+
console.log('✓ MemorySwapMax supported');
|
|
145
|
+
} else {
|
|
146
|
+
console.log('- MemorySwapMax unavailable');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log('');
|
|
150
|
+
|
|
92
151
|
if (info.unitDirWritable) {
|
|
93
152
|
console.log('✓ Unit directory writable');
|
|
94
153
|
} else {
|
package/src/index.js
CHANGED
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
getServiceFailures,
|
|
12
12
|
getServicesByGroup,
|
|
13
13
|
runJournalctlLogs,
|
|
14
|
+
setServiceLimits,
|
|
15
|
+
executeJournalctlMaintenance,
|
|
14
16
|
isLinux,
|
|
15
17
|
isSystemctlAvailable,
|
|
16
18
|
isSystemdPID1,
|
|
@@ -35,8 +37,11 @@ import {
|
|
|
35
37
|
readAppMetadata,
|
|
36
38
|
getAppMetadataPath,
|
|
37
39
|
getAppsDir,
|
|
38
|
-
getUnitupDir
|
|
40
|
+
getUnitupDir,
|
|
41
|
+
validateMemorySize,
|
|
42
|
+
formatMemoryBytes
|
|
39
43
|
} from './utils.js';
|
|
44
|
+
import { detectRuntime, resolveRuntimeConfig } from './runtimes/index.js';
|
|
40
45
|
|
|
41
46
|
export {
|
|
42
47
|
// Main programmatic service management API
|
|
@@ -52,8 +57,18 @@ export {
|
|
|
52
57
|
inspectService,
|
|
53
58
|
getServiceFailures,
|
|
54
59
|
getServicesByGroup,
|
|
60
|
+
setServiceLimits,
|
|
61
|
+
executeJournalctlMaintenance as executeJournalctlMaintenance,
|
|
55
62
|
runJournalctlLogs as getServiceLogs,
|
|
56
63
|
|
|
64
|
+
// Memory & Validation helpers
|
|
65
|
+
validateMemorySize,
|
|
66
|
+
formatMemoryBytes,
|
|
67
|
+
|
|
68
|
+
// Runtime detection & resolution
|
|
69
|
+
detectRuntime,
|
|
70
|
+
resolveRuntimeConfig,
|
|
71
|
+
|
|
57
72
|
// Metadata helpers
|
|
58
73
|
readAppMetadata,
|
|
59
74
|
getAppMetadataPath,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { findRuntimeExecutable, getBinaryVersion } from './common.js';
|
|
4
|
+
|
|
5
|
+
export async function createBunAdapter(opts = {}) {
|
|
6
|
+
const customPath = opts.command;
|
|
7
|
+
let execPath = null;
|
|
8
|
+
|
|
9
|
+
if (customPath) {
|
|
10
|
+
const absPath = path.resolve(process.cwd(), customPath);
|
|
11
|
+
if (fs.existsSync(absPath)) {
|
|
12
|
+
try {
|
|
13
|
+
fs.accessSync(absPath, fs.constants.X_OK);
|
|
14
|
+
execPath = absPath;
|
|
15
|
+
} catch {}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!execPath) {
|
|
20
|
+
execPath = await findRuntimeExecutable(['bun']);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!execPath) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
'Bun runtime could not be found.\n\nInstall Bun or specify its path:\n unitup add server.ts --command /usr/bin/bun'
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const version = await getBinaryVersion(execPath, '-v');
|
|
30
|
+
const scriptPath = opts.script ? path.resolve(process.cwd(), opts.script) : null;
|
|
31
|
+
const runtimeArgs = Array.isArray(opts.runtimeArgs) ? opts.runtimeArgs : [];
|
|
32
|
+
const scriptArgs = Array.isArray(opts.args) ? opts.args : [];
|
|
33
|
+
|
|
34
|
+
const args = [...runtimeArgs];
|
|
35
|
+
if (scriptPath) {
|
|
36
|
+
args.push(scriptPath);
|
|
37
|
+
}
|
|
38
|
+
args.push(...scriptArgs);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
command: execPath,
|
|
42
|
+
args,
|
|
43
|
+
runtime: 'bun',
|
|
44
|
+
version
|
|
45
|
+
};
|
|
46
|
+
}
|