unitup 0.0.8 → 0.0.10
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 +398 -171
- package/docs/index.html +531 -0
- package/index.d.ts +204 -134
- package/package.json +4 -1
- package/src/cli.js +393 -90
- package/src/doctor.js +73 -14
- package/src/index.js +32 -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 +349 -25
- package/src/unit.js +72 -22
- package/src/utils.js +220 -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,
|
|
@@ -13,7 +14,12 @@ import {
|
|
|
13
14
|
runJournalctlLogs,
|
|
14
15
|
isLinux,
|
|
15
16
|
isSystemctlAvailable,
|
|
16
|
-
findNodeExecutable
|
|
17
|
+
findNodeExecutable,
|
|
18
|
+
getServicesByGroup,
|
|
19
|
+
inspectService,
|
|
20
|
+
getServiceFailures,
|
|
21
|
+
setServiceLimits,
|
|
22
|
+
executeJournalctlMaintenance
|
|
17
23
|
} from './systemd.js';
|
|
18
24
|
import { sanitizeServiceName, formatTable } from './utils.js';
|
|
19
25
|
|
|
@@ -28,6 +34,10 @@ export function parseArgs(argv) {
|
|
|
28
34
|
positionals: [],
|
|
29
35
|
flags: {
|
|
30
36
|
name: '',
|
|
37
|
+
group: '',
|
|
38
|
+
runtime: '',
|
|
39
|
+
runtimeArgs: [],
|
|
40
|
+
command: '',
|
|
31
41
|
node: '',
|
|
32
42
|
cwd: '',
|
|
33
43
|
env: [],
|
|
@@ -42,7 +52,23 @@ export function parseArgs(argv) {
|
|
|
42
52
|
output: '',
|
|
43
53
|
lines: 100,
|
|
44
54
|
help: false,
|
|
45
|
-
version: false
|
|
55
|
+
version: false,
|
|
56
|
+
memoryHigh: '',
|
|
57
|
+
memoryMax: '',
|
|
58
|
+
memorySwapMax: '',
|
|
59
|
+
resetMemory: false,
|
|
60
|
+
since: '',
|
|
61
|
+
until: '',
|
|
62
|
+
priority: '',
|
|
63
|
+
grep: '',
|
|
64
|
+
boot: false,
|
|
65
|
+
json: false,
|
|
66
|
+
diskUsage: false,
|
|
67
|
+
size: '',
|
|
68
|
+
time: '',
|
|
69
|
+
files: '',
|
|
70
|
+
yes: false,
|
|
71
|
+
dryRun: false
|
|
46
72
|
}
|
|
47
73
|
};
|
|
48
74
|
|
|
@@ -56,6 +82,24 @@ export function parseArgs(argv) {
|
|
|
56
82
|
} else if (arg === '-v' || arg === '--version') {
|
|
57
83
|
result.flags.version = true;
|
|
58
84
|
i++;
|
|
85
|
+
} else if (arg === '--runtime') {
|
|
86
|
+
result.flags.runtime = argv[i + 1] || '';
|
|
87
|
+
i += 2;
|
|
88
|
+
} else if (arg.startsWith('--runtime=')) {
|
|
89
|
+
result.flags.runtime = arg.slice(10);
|
|
90
|
+
i++;
|
|
91
|
+
} else if (arg === '--runtime-arg') {
|
|
92
|
+
if (argv[i + 1]) result.flags.runtimeArgs.push(argv[i + 1]);
|
|
93
|
+
i += 2;
|
|
94
|
+
} else if (arg.startsWith('--runtime-arg=')) {
|
|
95
|
+
result.flags.runtimeArgs.push(arg.slice(14));
|
|
96
|
+
i++;
|
|
97
|
+
} else if (arg === '--command') {
|
|
98
|
+
result.flags.command = argv[i + 1] || '';
|
|
99
|
+
i += 2;
|
|
100
|
+
} else if (arg.startsWith('--command=')) {
|
|
101
|
+
result.flags.command = arg.slice(10);
|
|
102
|
+
i++;
|
|
59
103
|
} else if (arg === '--node') {
|
|
60
104
|
result.flags.node = argv[i + 1] || '';
|
|
61
105
|
i += 2;
|
|
@@ -68,6 +112,12 @@ export function parseArgs(argv) {
|
|
|
68
112
|
} else if (arg.startsWith('--name=')) {
|
|
69
113
|
result.flags.name = arg.slice(7);
|
|
70
114
|
i++;
|
|
115
|
+
} else if (arg === '--group') {
|
|
116
|
+
result.flags.group = argv[i + 1] || '';
|
|
117
|
+
i += 2;
|
|
118
|
+
} else if (arg.startsWith('--group=')) {
|
|
119
|
+
result.flags.group = arg.slice(8);
|
|
120
|
+
i++;
|
|
71
121
|
} else if (arg === '--cwd') {
|
|
72
122
|
result.flags.cwd = argv[i + 1] || '';
|
|
73
123
|
i += 2;
|
|
@@ -125,6 +175,84 @@ export function parseArgs(argv) {
|
|
|
125
175
|
} else if (arg.startsWith('--output=')) {
|
|
126
176
|
result.flags.output = arg.slice(9);
|
|
127
177
|
i++;
|
|
178
|
+
} else if (arg === '--memory-high') {
|
|
179
|
+
result.flags.memoryHigh = argv[i + 1] || '';
|
|
180
|
+
i += 2;
|
|
181
|
+
} else if (arg.startsWith('--memory-high=')) {
|
|
182
|
+
result.flags.memoryHigh = arg.slice(14);
|
|
183
|
+
i++;
|
|
184
|
+
} else if (arg === '--memory-max') {
|
|
185
|
+
result.flags.memoryMax = argv[i + 1] || '';
|
|
186
|
+
i += 2;
|
|
187
|
+
} else if (arg.startsWith('--memory-max=')) {
|
|
188
|
+
result.flags.memoryMax = arg.slice(13);
|
|
189
|
+
i++;
|
|
190
|
+
} else if (arg === '--memory-swap-max') {
|
|
191
|
+
result.flags.memorySwapMax = argv[i + 1] || '';
|
|
192
|
+
i += 2;
|
|
193
|
+
} else if (arg.startsWith('--memory-swap-max=')) {
|
|
194
|
+
result.flags.memorySwapMax = arg.slice(18);
|
|
195
|
+
i++;
|
|
196
|
+
} else if (arg === '--reset-memory') {
|
|
197
|
+
result.flags.resetMemory = true;
|
|
198
|
+
i++;
|
|
199
|
+
} else if (arg === '--since') {
|
|
200
|
+
result.flags.since = argv[i + 1] || '';
|
|
201
|
+
i += 2;
|
|
202
|
+
} else if (arg.startsWith('--since=')) {
|
|
203
|
+
result.flags.since = arg.slice(8);
|
|
204
|
+
i++;
|
|
205
|
+
} else if (arg === '--until') {
|
|
206
|
+
result.flags.until = argv[i + 1] || '';
|
|
207
|
+
i += 2;
|
|
208
|
+
} else if (arg.startsWith('--until=')) {
|
|
209
|
+
result.flags.until = arg.slice(8);
|
|
210
|
+
i++;
|
|
211
|
+
} else if (arg === '--priority') {
|
|
212
|
+
result.flags.priority = argv[i + 1] || '';
|
|
213
|
+
i += 2;
|
|
214
|
+
} else if (arg.startsWith('--priority=')) {
|
|
215
|
+
result.flags.priority = arg.slice(11);
|
|
216
|
+
i++;
|
|
217
|
+
} else if (arg === '--grep') {
|
|
218
|
+
result.flags.grep = argv[i + 1] || '';
|
|
219
|
+
i += 2;
|
|
220
|
+
} else if (arg.startsWith('--grep=')) {
|
|
221
|
+
result.flags.grep = arg.slice(7);
|
|
222
|
+
i++;
|
|
223
|
+
} else if (arg === '--boot' || arg === '-b') {
|
|
224
|
+
result.flags.boot = true;
|
|
225
|
+
i++;
|
|
226
|
+
} else if (arg === '--json') {
|
|
227
|
+
result.flags.json = true;
|
|
228
|
+
i++;
|
|
229
|
+
} else if (arg === '--disk-usage') {
|
|
230
|
+
result.flags.diskUsage = true;
|
|
231
|
+
i++;
|
|
232
|
+
} else if (arg === '--size') {
|
|
233
|
+
result.flags.size = argv[i + 1] || '';
|
|
234
|
+
i += 2;
|
|
235
|
+
} else if (arg.startsWith('--size=')) {
|
|
236
|
+
result.flags.size = arg.slice(7);
|
|
237
|
+
i++;
|
|
238
|
+
} else if (arg === '--time') {
|
|
239
|
+
result.flags.time = argv[i + 1] || '';
|
|
240
|
+
i += 2;
|
|
241
|
+
} else if (arg.startsWith('--time=')) {
|
|
242
|
+
result.flags.time = arg.slice(7);
|
|
243
|
+
i++;
|
|
244
|
+
} else if (arg === '--files') {
|
|
245
|
+
result.flags.files = argv[i + 1] || '';
|
|
246
|
+
i += 2;
|
|
247
|
+
} else if (arg.startsWith('--files=')) {
|
|
248
|
+
result.flags.files = arg.slice(8);
|
|
249
|
+
i++;
|
|
250
|
+
} else if (arg === '--yes' || arg === '-y') {
|
|
251
|
+
result.flags.yes = true;
|
|
252
|
+
i++;
|
|
253
|
+
} else if (arg === '--dry-run') {
|
|
254
|
+
result.flags.dryRun = true;
|
|
255
|
+
i++;
|
|
128
256
|
} else if (!arg.startsWith('-')) {
|
|
129
257
|
if (!result.command) {
|
|
130
258
|
result.command = arg;
|
|
@@ -143,36 +271,53 @@ export function parseArgs(argv) {
|
|
|
143
271
|
|
|
144
272
|
export function printHelp() {
|
|
145
273
|
console.log(`
|
|
146
|
-
unitup - Minimal systemd user service wrapper for
|
|
274
|
+
unitup - Minimal systemd user service wrapper for any executable & runtime
|
|
147
275
|
|
|
148
276
|
Usage:
|
|
149
|
-
unitup doctor Run system readiness check
|
|
150
|
-
unitup add <script> [options] Add script as systemd user service
|
|
151
|
-
unitup start <name>
|
|
152
|
-
unitup stop <name>
|
|
153
|
-
unitup restart <name>
|
|
154
|
-
unitup status <name>
|
|
155
|
-
unitup logs <name>
|
|
156
|
-
unitup
|
|
157
|
-
unitup
|
|
277
|
+
unitup doctor Run system readiness and runtime check
|
|
278
|
+
unitup add <script> [options] Add script or executable as systemd user service
|
|
279
|
+
unitup start <name|@group> Start a service (--enable to enable on boot)
|
|
280
|
+
unitup stop <name|@group> Stop a service
|
|
281
|
+
unitup restart <name|@group> Restart a service
|
|
282
|
+
unitup status <name|@group> Show status summary (--raw for full status)
|
|
283
|
+
unitup logs <name|@group> Show journalctl logs (-f/--follow, -n/--lines N, -c/--cat)
|
|
284
|
+
unitup inspect <name> View detailed app configuration and status
|
|
285
|
+
unitup failures List all failed services with exit code & restarts
|
|
286
|
+
unitup remove <name|@group> Stop, disable and delete a service
|
|
287
|
+
unitup list / unitup ls List all services (--group <group>)
|
|
158
288
|
|
|
159
289
|
Add Options:
|
|
160
|
-
--name <name>
|
|
161
|
-
--
|
|
162
|
-
--
|
|
163
|
-
--
|
|
164
|
-
--
|
|
165
|
-
--
|
|
166
|
-
--
|
|
290
|
+
--name <name> Service name (default: script/executable name)
|
|
291
|
+
--runtime <name> Runtime (node, python, ruby, php, bun, deno, shell, go, elixir, native)
|
|
292
|
+
--runtime-arg <val> Runtime argument (can be specified multiple times)
|
|
293
|
+
--command <path> Custom executable command path (bypasses auto-detection)
|
|
294
|
+
--arg <value> Argument to pass to command (can be specified multiple times)
|
|
295
|
+
--group <group> Assign service to a group (default: default)
|
|
296
|
+
--cwd <path> Working directory
|
|
297
|
+
--restart <policy> Restart policy (on-failure, always, etc. Default: on-failure)
|
|
298
|
+
--env KEY=value Environment variable (can be specified multiple times)
|
|
299
|
+
--env-file <file> Path to environment file
|
|
300
|
+
--start Enable and start service immediately after creation
|
|
167
301
|
|
|
168
302
|
Examples:
|
|
169
|
-
unitup add server.js --
|
|
170
|
-
unitup
|
|
171
|
-
unitup
|
|
172
|
-
unitup
|
|
303
|
+
unitup add server.js --runtime node
|
|
304
|
+
unitup add worker.py --runtime python
|
|
305
|
+
unitup add ./server --runtime native
|
|
306
|
+
unitup add --name worker --command /usr/bin/python3 --arg worker.py --arg --port --arg 3000
|
|
173
307
|
`);
|
|
174
308
|
}
|
|
175
309
|
|
|
310
|
+
async function resolveTargetNames(target) {
|
|
311
|
+
if (target && target.startsWith('@')) {
|
|
312
|
+
const list = await getServicesByGroup(target);
|
|
313
|
+
if (list.length === 0) {
|
|
314
|
+
throw new Error(`No services found in group "${target}".`);
|
|
315
|
+
}
|
|
316
|
+
return list;
|
|
317
|
+
}
|
|
318
|
+
return [target];
|
|
319
|
+
}
|
|
320
|
+
|
|
176
321
|
/**
|
|
177
322
|
* Main CLI runner entrypoint.
|
|
178
323
|
*
|
|
@@ -208,25 +353,39 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
208
353
|
|
|
209
354
|
case 'add': {
|
|
210
355
|
const scriptArg = positionals[0];
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const resolvedNode = await findNodeExecutable(flags.node);
|
|
216
|
-
if (!resolvedNode) {
|
|
217
|
-
throw new Error('Node.js is required but not found.\nRun: unitup doctor');
|
|
218
|
-
}
|
|
356
|
+
let name = flags.name;
|
|
357
|
+
let absScriptPath = scriptArg ? path.resolve(process.cwd(), scriptArg) : undefined;
|
|
219
358
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
359
|
+
if (flags.node) {
|
|
360
|
+
const resolvedNode = await findNodeExecutable(flags.node);
|
|
361
|
+
if (!resolvedNode) {
|
|
362
|
+
throw new Error('Node.js is required but not found.\nRun: unitup doctor');
|
|
363
|
+
}
|
|
223
364
|
}
|
|
224
365
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
366
|
+
if (flags.command) {
|
|
367
|
+
if (!name) {
|
|
368
|
+
if (scriptArg) {
|
|
369
|
+
const baseName = path.basename(scriptArg);
|
|
370
|
+
const ext = path.extname(baseName);
|
|
371
|
+
name = ext ? baseName.slice(0, -ext.length) : baseName;
|
|
372
|
+
} else {
|
|
373
|
+
const baseCmd = path.basename(flags.command);
|
|
374
|
+
name = baseCmd;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
if (!scriptArg) {
|
|
379
|
+
throw new Error('Script file path or --command is required.\nExample: unitup add app.py --runtime python');
|
|
380
|
+
}
|
|
381
|
+
if (!fs.existsSync(absScriptPath)) {
|
|
382
|
+
throw new Error(`Script file does not exist: ${absScriptPath}`);
|
|
383
|
+
}
|
|
384
|
+
if (!name) {
|
|
385
|
+
const baseName = path.basename(scriptArg);
|
|
386
|
+
const ext = path.extname(baseName);
|
|
387
|
+
name = ext ? baseName.slice(0, -ext.length) : baseName;
|
|
388
|
+
}
|
|
230
389
|
}
|
|
231
390
|
|
|
232
391
|
const envObj = {};
|
|
@@ -241,14 +400,21 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
241
400
|
|
|
242
401
|
const res = await addService({
|
|
243
402
|
name,
|
|
403
|
+
group: flags.group || 'default',
|
|
244
404
|
script: absScriptPath,
|
|
245
|
-
|
|
246
|
-
|
|
405
|
+
command: flags.command,
|
|
406
|
+
runtime: flags.runtime,
|
|
407
|
+
runtimeArgs: flags.runtimeArgs,
|
|
408
|
+
nodePath: flags.node,
|
|
409
|
+
cwd: flags.cwd ? path.resolve(process.cwd(), flags.cwd) : (absScriptPath ? path.dirname(absScriptPath) : process.cwd()),
|
|
247
410
|
env: envObj,
|
|
248
411
|
envFile: flags.envFile ? path.resolve(process.cwd(), flags.envFile) : undefined,
|
|
249
412
|
restart: flags.restart,
|
|
250
413
|
args: flags.args,
|
|
251
|
-
start: flags.start
|
|
414
|
+
start: flags.start,
|
|
415
|
+
memoryHigh: flags.memoryHigh,
|
|
416
|
+
memoryMax: flags.memoryMax,
|
|
417
|
+
memorySwapMax: flags.memorySwapMax
|
|
252
418
|
});
|
|
253
419
|
|
|
254
420
|
console.log(`✓ Service "${res.name}" created at ${res.unitPath}`);
|
|
@@ -260,91 +426,228 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
260
426
|
break;
|
|
261
427
|
}
|
|
262
428
|
|
|
429
|
+
case 'limits': {
|
|
430
|
+
const nameArg = positionals[0];
|
|
431
|
+
if (!nameArg) {
|
|
432
|
+
throw new Error('Service name is required.\nExample: unitup limits api --memory-high 400M --memory-max 512M');
|
|
433
|
+
}
|
|
434
|
+
const info = await setServiceLimits(nameArg, {
|
|
435
|
+
memoryHigh: flags.memoryHigh,
|
|
436
|
+
memoryMax: flags.memoryMax,
|
|
437
|
+
memorySwapMax: flags.memorySwapMax,
|
|
438
|
+
resetMemory: flags.resetMemory
|
|
439
|
+
});
|
|
440
|
+
console.log(`✓ Service "${info.name}" limits updated.`);
|
|
441
|
+
console.log(`Memory High: ${info.memoryHigh}`);
|
|
442
|
+
console.log(`Memory Max: ${info.memoryMax}`);
|
|
443
|
+
console.log(`Swap Max: ${info.memorySwapMax}`);
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
|
|
263
447
|
case 'start': {
|
|
264
|
-
const
|
|
265
|
-
if (!
|
|
266
|
-
throw new Error('Service name is required.\nExample: unitup start api');
|
|
448
|
+
const nameArg = positionals[0];
|
|
449
|
+
if (!nameArg) {
|
|
450
|
+
throw new Error('Service name or @group is required.\nExample: unitup start api or unitup start @myproject');
|
|
451
|
+
}
|
|
452
|
+
const targetNames = await resolveTargetNames(nameArg);
|
|
453
|
+
for (const name of targetNames) {
|
|
454
|
+
await startService(name, flags.enable);
|
|
455
|
+
console.log(`✓ Service "${sanitizeServiceName(name)}" ${flags.enable ? 'enabled & ' : ''}started.`);
|
|
267
456
|
}
|
|
268
|
-
await startService(name, flags.enable);
|
|
269
|
-
console.log(`✓ Service "${sanitizeServiceName(name)}" ${flags.enable ? 'enabled & ' : ''}started.`);
|
|
270
457
|
break;
|
|
271
458
|
}
|
|
272
459
|
|
|
273
460
|
case 'stop': {
|
|
274
|
-
const
|
|
275
|
-
if (!
|
|
276
|
-
throw new Error('Service name is required.\nExample: unitup stop api');
|
|
461
|
+
const nameArg = positionals[0];
|
|
462
|
+
if (!nameArg) {
|
|
463
|
+
throw new Error('Service name or @group is required.\nExample: unitup stop api or unitup stop @myproject');
|
|
464
|
+
}
|
|
465
|
+
const targetNames = await resolveTargetNames(nameArg);
|
|
466
|
+
for (const name of targetNames) {
|
|
467
|
+
await stopService(name);
|
|
468
|
+
console.log(`✓ Service "${sanitizeServiceName(name)}" stopped.`);
|
|
277
469
|
}
|
|
278
|
-
await stopService(name);
|
|
279
|
-
console.log(`✓ Service "${sanitizeServiceName(name)}" stopped.`);
|
|
280
470
|
break;
|
|
281
471
|
}
|
|
282
472
|
|
|
283
473
|
case 'restart': {
|
|
284
|
-
const
|
|
285
|
-
if (!
|
|
286
|
-
throw new Error('Service name is required.\nExample: unitup restart api');
|
|
474
|
+
const nameArg = positionals[0];
|
|
475
|
+
if (!nameArg) {
|
|
476
|
+
throw new Error('Service name or @group is required.\nExample: unitup restart api or unitup restart @myproject');
|
|
477
|
+
}
|
|
478
|
+
const targetNames = await resolveTargetNames(nameArg);
|
|
479
|
+
for (const name of targetNames) {
|
|
480
|
+
await restartService(name);
|
|
481
|
+
console.log(`✓ Service "${sanitizeServiceName(name)}" restarted.`);
|
|
287
482
|
}
|
|
288
|
-
await restartService(name);
|
|
289
|
-
console.log(`✓ Service "${sanitizeServiceName(name)}" restarted.`);
|
|
290
483
|
break;
|
|
291
484
|
}
|
|
292
485
|
|
|
293
486
|
case 'status': {
|
|
294
|
-
const
|
|
295
|
-
if (!
|
|
296
|
-
throw new Error('Service name is required.\nExample: unitup status api');
|
|
487
|
+
const nameArg = positionals[0];
|
|
488
|
+
if (!nameArg) {
|
|
489
|
+
throw new Error('Service name or @group is required.\nExample: unitup status api or unitup status @myproject');
|
|
297
490
|
}
|
|
298
491
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
492
|
+
const targetNames = await resolveTargetNames(nameArg);
|
|
493
|
+
for (const name of targetNames) {
|
|
494
|
+
if (flags.raw) {
|
|
495
|
+
const raw = await getServiceStatusRaw(name);
|
|
496
|
+
console.log(raw);
|
|
497
|
+
} else {
|
|
498
|
+
const status = await getServiceStatus(name);
|
|
499
|
+
console.log(`${status.name}\n`);
|
|
500
|
+
console.log(`Status: ${status.status}`);
|
|
501
|
+
console.log(`PID: ${status.pid}`);
|
|
502
|
+
console.log(`Started: ${status.started}`);
|
|
503
|
+
console.log(`Restarts: ${status.restarts}`);
|
|
504
|
+
console.log(`Command: ${status.command}`);
|
|
505
|
+
console.log(`Arguments: ${status.arguments}`);
|
|
506
|
+
console.log(`Working directory: ${status.cwd}`);
|
|
507
|
+
console.log(`Memory: ${status.memory}`);
|
|
508
|
+
console.log(`Memory Peak: ${status.memoryPeak}`);
|
|
509
|
+
console.log(`Memory High: ${status.memoryHigh}`);
|
|
510
|
+
console.log(`Memory Max: ${status.memoryMax}`);
|
|
511
|
+
console.log(`Swap Max: ${status.memorySwapMax}`);
|
|
512
|
+
}
|
|
311
513
|
}
|
|
312
514
|
break;
|
|
313
515
|
}
|
|
314
516
|
|
|
315
517
|
case 'logs': {
|
|
316
|
-
const
|
|
317
|
-
if (!
|
|
318
|
-
throw new Error('Service name is required.\nExample: unitup logs api');
|
|
518
|
+
const nameArg = positionals[0];
|
|
519
|
+
if (!nameArg) {
|
|
520
|
+
throw new Error('Service name or @group is required.\nExample: unitup logs api or unitup logs @myproject');
|
|
319
521
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
522
|
+
|
|
523
|
+
const targetNames = await resolveTargetNames(nameArg);
|
|
524
|
+
for (const name of targetNames) {
|
|
525
|
+
if (targetNames.length > 1) {
|
|
526
|
+
console.log(`=== Logs for ${name} ===`);
|
|
527
|
+
}
|
|
528
|
+
const output = await runJournalctlLogs(name, {
|
|
529
|
+
follow: flags.follow,
|
|
530
|
+
lines: flags.lines,
|
|
531
|
+
cat: flags.cat,
|
|
532
|
+
output: flags.output,
|
|
533
|
+
since: flags.since,
|
|
534
|
+
until: flags.until,
|
|
535
|
+
priority: flags.priority,
|
|
536
|
+
grep: flags.grep,
|
|
537
|
+
boot: flags.boot,
|
|
538
|
+
json: flags.json,
|
|
539
|
+
diskUsage: flags.diskUsage
|
|
540
|
+
});
|
|
541
|
+
if (typeof output === 'string') {
|
|
542
|
+
console.log(output);
|
|
543
|
+
}
|
|
328
544
|
}
|
|
329
545
|
break;
|
|
330
546
|
}
|
|
331
547
|
|
|
548
|
+
case 'journal': {
|
|
549
|
+
const action = positionals[0];
|
|
550
|
+
if (!action) {
|
|
551
|
+
throw new Error('Journal action is required (disk-usage, rotate, vacuum).\nExample: unitup journal vacuum --size 500M');
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (action === 'vacuum' && !flags.yes && !flags.dryRun) {
|
|
555
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
556
|
+
const answer = await new Promise((resolve) => {
|
|
557
|
+
rl.question('This operation affects archived journal logs system-wide,\nnot only services managed by unitup.\n\nContinue? [y/N] ', (ans) => {
|
|
558
|
+
rl.close();
|
|
559
|
+
resolve(ans.trim());
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
563
|
+
console.log('Aborted.');
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const result = await executeJournalctlMaintenance(action, {
|
|
569
|
+
size: flags.size,
|
|
570
|
+
time: flags.time,
|
|
571
|
+
files: flags.files,
|
|
572
|
+
yes: flags.yes,
|
|
573
|
+
dryRun: flags.dryRun
|
|
574
|
+
});
|
|
575
|
+
console.log(result);
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
|
|
332
579
|
case 'remove': {
|
|
333
|
-
const
|
|
334
|
-
if (!
|
|
335
|
-
throw new Error('Service name is required.\nExample: unitup remove api');
|
|
580
|
+
const nameArg = positionals[0];
|
|
581
|
+
if (!nameArg) {
|
|
582
|
+
throw new Error('Service name or @group is required.\nExample: unitup remove api or unitup remove @myproject');
|
|
583
|
+
}
|
|
584
|
+
const targetNames = await resolveTargetNames(nameArg);
|
|
585
|
+
for (const name of targetNames) {
|
|
586
|
+
await removeService(name);
|
|
587
|
+
console.log(`✓ Service "${sanitizeServiceName(name)}" removed.`);
|
|
336
588
|
}
|
|
337
|
-
await removeService(name);
|
|
338
|
-
console.log(`✓ Service "${sanitizeServiceName(name)}" removed.`);
|
|
339
589
|
break;
|
|
340
590
|
}
|
|
341
591
|
|
|
592
|
+
case 'ls':
|
|
342
593
|
case 'list': {
|
|
343
|
-
const services = await listServices();
|
|
594
|
+
const services = await listServices({ group: flags.group });
|
|
595
|
+
if (services.length === 0) {
|
|
596
|
+
if (flags.group) {
|
|
597
|
+
console.log(`No services found in group "${flags.group}".`);
|
|
598
|
+
} else {
|
|
599
|
+
console.log('No unitup user services found.');
|
|
600
|
+
}
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
|
|
344
604
|
const table = formatTable(services, [
|
|
345
605
|
{ key: 'name', label: 'NAME' },
|
|
606
|
+
{ key: 'runtime', label: 'RUNTIME' },
|
|
607
|
+
{ key: 'status', label: 'STATUS' },
|
|
608
|
+
{ key: 'pid', label: 'PID' },
|
|
609
|
+
{ key: 'command', label: 'COMMAND' }
|
|
610
|
+
]);
|
|
611
|
+
console.log(table);
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
case 'inspect': {
|
|
616
|
+
const nameArg = positionals[0];
|
|
617
|
+
if (!nameArg) {
|
|
618
|
+
throw new Error('Service name is required.\nExample: unitup inspect api');
|
|
619
|
+
}
|
|
620
|
+
const info = await inspectService(nameArg);
|
|
621
|
+
console.log(`Name: ${info.name}`);
|
|
622
|
+
console.log(`Runtime: ${info.runtime}`);
|
|
623
|
+
console.log(`Group: ${info.group}`);
|
|
624
|
+
console.log(`Status: ${info.status}`);
|
|
625
|
+
console.log(`Command: ${info.command}`);
|
|
626
|
+
console.log(`Arguments: ${info.arguments}`);
|
|
627
|
+
console.log(`Working directory: ${info.cwd}`);
|
|
628
|
+
console.log(`Unit: ${info.unit}`);
|
|
629
|
+
console.log(`Memory: ${info.memory}`);
|
|
630
|
+
console.log(`Memory Peak: ${info.memoryPeak}`);
|
|
631
|
+
console.log(`Memory High: ${info.memoryHigh}`);
|
|
632
|
+
console.log(`Memory Max: ${info.memoryMax}`);
|
|
633
|
+
console.log(`Swap Max: ${info.memorySwapMax}`);
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
case 'failures': {
|
|
638
|
+
const failures = await getServiceFailures();
|
|
639
|
+
if (failures.length === 0) {
|
|
640
|
+
console.log('✓ No failed services.');
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const table = formatTable(failures, [
|
|
645
|
+
{ key: 'name', label: 'NAME' },
|
|
646
|
+
{ key: 'group', label: 'GROUP' },
|
|
346
647
|
{ key: 'status', label: 'STATUS' },
|
|
347
|
-
{ key: '
|
|
648
|
+
{ key: 'exitCode', label: 'EXIT_CODE' },
|
|
649
|
+
{ key: 'restarts', label: 'RESTARTS' },
|
|
650
|
+
{ key: 'uptime', label: 'UPTIME' }
|
|
348
651
|
]);
|
|
349
652
|
console.log(table);
|
|
350
653
|
break;
|