unitup 0.1.5 → 0.3.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/.agents/AGENTS.md +35 -0
- package/.agents/skills/multi-runtime-management/SKILL.md +80 -0
- package/.agents/skills/platform-service-adapters/SKILL.md +108 -0
- package/.agents/skills/unitup-development-and-qa/SKILL.md +88 -0
- package/.agents/skills/zero-downtime-deployment/SKILL.md +135 -0
- package/CHANGELOG.md +5 -3
- package/README.md +89 -8
- package/biome.json +0 -1
- package/docs/index.html +188 -234
- package/index.d.ts +467 -440
- package/package.json +9 -3
- package/src/cli.js +561 -103
- package/src/core/drain-manager.js +70 -0
- package/src/core/generation-manager.js +414 -0
- package/src/core/index.js +8 -0
- package/src/core/ipc.js +236 -0
- package/src/core/lock.js +133 -0
- package/src/core/process-manager.js +313 -0
- package/src/core/readiness.js +243 -0
- package/src/core/router.js +390 -0
- package/src/core/supervisor.js +222 -0
- package/src/deploy/deployment-manager.js +407 -0
- package/src/deploy/index.js +9 -0
- package/src/deploy/rollback-manager.js +186 -0
- package/src/doctor.js +123 -56
- package/src/errors.js +131 -0
- package/src/index.js +186 -59
- package/src/logs.js +148 -0
- package/src/platform/base.js +211 -0
- package/src/platform/index.js +43 -0
- package/src/platform/linux.js +155 -0
- package/src/platform/macos.js +726 -0
- package/src/platform/windows-host.js +246 -0
- package/src/platform/windows.js +498 -0
- package/src/runtimes/bun.js +1 -1
- package/src/runtimes/common.js +24 -6
- package/src/runtimes/deno.js +1 -1
- package/src/runtimes/elixir.js +1 -1
- package/src/runtimes/go.js +1 -1
- package/src/runtimes/index.js +7 -7
- package/src/runtimes/native.js +2 -4
- package/src/runtimes/node.js +1 -1
- package/src/runtimes/php.js +1 -1
- package/src/runtimes/python.js +1 -1
- package/src/runtimes/ruby.js +1 -1
- package/src/runtimes/shell.js +1 -1
- package/src/schedule.js +31 -24
- package/src/service/config.js +63 -0
- package/src/service/manager.js +233 -0
- package/src/service/normalize.js +245 -0
- package/src/systemd.js +84 -38
- package/src/unit.js +17 -19
- package/src/utils.js +112 -18
- package/tsconfig.json +1 -1
- package/types-test/index.ts +158 -0
package/src/cli.js
CHANGED
|
@@ -1,47 +1,59 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
3
|
import readline from 'node:readline';
|
|
4
4
|
import { runDoctor } from './doctor.js';
|
|
5
|
+
import { getAdapter } from './platform/index.js';
|
|
6
|
+
import {
|
|
7
|
+
createSchedule,
|
|
8
|
+
disableSchedule,
|
|
9
|
+
enableSchedule,
|
|
10
|
+
getScheduleStatus,
|
|
11
|
+
listSchedules,
|
|
12
|
+
removeSchedule,
|
|
13
|
+
runSchedule
|
|
14
|
+
} from './schedule.js';
|
|
15
|
+
import { defaultManager } from './service/manager.js';
|
|
5
16
|
import {
|
|
6
17
|
addService,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
18
|
+
executeJournalctlMaintenance,
|
|
19
|
+
findNodeExecutable,
|
|
20
|
+
getAllServicesMemoryUsage,
|
|
21
|
+
getServiceFailures,
|
|
22
|
+
getServiceMemoryUsage,
|
|
11
23
|
getServiceStatus,
|
|
12
24
|
getServiceStatusRaw,
|
|
13
|
-
listServices,
|
|
14
|
-
runJournalctlLogs,
|
|
15
|
-
isLinux,
|
|
16
|
-
isSystemctlAvailable,
|
|
17
|
-
findNodeExecutable,
|
|
18
25
|
getServicesByGroup,
|
|
19
26
|
inspectService,
|
|
20
|
-
|
|
27
|
+
isLinux,
|
|
28
|
+
isSystemctlAvailable,
|
|
29
|
+
listServices,
|
|
30
|
+
removeService,
|
|
31
|
+
restartService,
|
|
32
|
+
runJournalctlLogs,
|
|
21
33
|
setServiceLimits,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
getAllServicesMemoryUsage
|
|
34
|
+
startService,
|
|
35
|
+
stopService
|
|
25
36
|
} from './systemd.js';
|
|
26
37
|
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
runSchedule,
|
|
31
|
-
enableSchedule,
|
|
32
|
-
disableSchedule,
|
|
33
|
-
removeSchedule
|
|
34
|
-
} from './schedule.js';
|
|
35
|
-
import {
|
|
36
|
-
sanitizeServiceName,
|
|
38
|
+
findProjectConfig,
|
|
39
|
+
formatDuration,
|
|
40
|
+
formatRelativeTime,
|
|
37
41
|
formatTable,
|
|
42
|
+
parseDurationMs,
|
|
38
43
|
readGlobalConfig,
|
|
39
|
-
saveGlobalConfig,
|
|
40
|
-
validateMemorySize,
|
|
41
|
-
findProjectConfig,
|
|
42
44
|
readProjectConfig,
|
|
43
|
-
|
|
45
|
+
sanitizeServiceName,
|
|
46
|
+
saveGlobalConfig,
|
|
47
|
+
saveProjectConfig,
|
|
48
|
+
validateMemorySize
|
|
44
49
|
} from './utils.js';
|
|
50
|
+
import { GenerationManager } from './core/generation-manager.js';
|
|
51
|
+
import { IPCClient } from './core/ipc.js';
|
|
52
|
+
import { Supervisor } from './core/supervisor.js';
|
|
53
|
+
import {
|
|
54
|
+
defaultDeploymentManager as deploymentManager,
|
|
55
|
+
defaultRollbackManager as rollbackManager
|
|
56
|
+
} from './deploy/index.js';
|
|
45
57
|
|
|
46
58
|
/**
|
|
47
59
|
* Custom light CLI argument parser without runtime dependencies.
|
|
@@ -75,6 +87,8 @@ export function parseArgs(argv) {
|
|
|
75
87
|
help: false,
|
|
76
88
|
version: false,
|
|
77
89
|
force: false,
|
|
90
|
+
system: false,
|
|
91
|
+
verbose: false,
|
|
78
92
|
memoryHigh: '',
|
|
79
93
|
memoryMax: '',
|
|
80
94
|
memorySwapMax: '',
|
|
@@ -98,7 +112,13 @@ export function parseArgs(argv) {
|
|
|
98
112
|
onBoot: '',
|
|
99
113
|
onActive: '',
|
|
100
114
|
randomDelay: '',
|
|
101
|
-
persistent: false
|
|
115
|
+
persistent: false,
|
|
116
|
+
port: '',
|
|
117
|
+
zeroDowntime: false,
|
|
118
|
+
ready: '',
|
|
119
|
+
drain: '',
|
|
120
|
+
canary: false,
|
|
121
|
+
weight: ''
|
|
102
122
|
}
|
|
103
123
|
};
|
|
104
124
|
|
|
@@ -112,6 +132,12 @@ export function parseArgs(argv) {
|
|
|
112
132
|
} else if (arg === '-v' || arg === '--version') {
|
|
113
133
|
result.flags.version = true;
|
|
114
134
|
i++;
|
|
135
|
+
} else if (arg === '--verbose') {
|
|
136
|
+
result.flags.verbose = true;
|
|
137
|
+
i++;
|
|
138
|
+
} else if (arg === '--system') {
|
|
139
|
+
result.flags.system = true;
|
|
140
|
+
i++;
|
|
115
141
|
} else if (arg === '--runtime') {
|
|
116
142
|
result.flags.runtime = argv[i + 1] || '';
|
|
117
143
|
i += 2;
|
|
@@ -191,10 +217,10 @@ export function parseArgs(argv) {
|
|
|
191
217
|
result.flags.args.push(arg.slice(7));
|
|
192
218
|
i++;
|
|
193
219
|
} else if (arg === '--lines' || arg === '-n') {
|
|
194
|
-
result.flags.lines = parseInt(argv[i + 1] || '100', 10);
|
|
220
|
+
result.flags.lines = Number.parseInt(argv[i + 1] || '100', 10);
|
|
195
221
|
i += 2;
|
|
196
222
|
} else if (arg.startsWith('--lines=')) {
|
|
197
|
-
result.flags.lines = parseInt(arg.slice(8), 10);
|
|
223
|
+
result.flags.lines = Number.parseInt(arg.slice(8), 10);
|
|
198
224
|
i++;
|
|
199
225
|
} else if (arg === '--start') {
|
|
200
226
|
result.flags.start = true;
|
|
@@ -344,6 +370,36 @@ export function parseArgs(argv) {
|
|
|
344
370
|
} else if (arg === '--persistent') {
|
|
345
371
|
result.flags.persistent = true;
|
|
346
372
|
i++;
|
|
373
|
+
} else if (arg === '--port') {
|
|
374
|
+
result.flags.port = argv[i + 1] || '';
|
|
375
|
+
i += 2;
|
|
376
|
+
} else if (arg.startsWith('--port=')) {
|
|
377
|
+
result.flags.port = arg.slice(7);
|
|
378
|
+
i++;
|
|
379
|
+
} else if (arg === '--zero-downtime') {
|
|
380
|
+
result.flags.zeroDowntime = true;
|
|
381
|
+
i++;
|
|
382
|
+
} else if (arg === '--ready') {
|
|
383
|
+
result.flags.ready = argv[i + 1] || '';
|
|
384
|
+
i += 2;
|
|
385
|
+
} else if (arg.startsWith('--ready=')) {
|
|
386
|
+
result.flags.ready = arg.slice(8);
|
|
387
|
+
i++;
|
|
388
|
+
} else if (arg === '--drain') {
|
|
389
|
+
result.flags.drain = argv[i + 1] || '';
|
|
390
|
+
i += 2;
|
|
391
|
+
} else if (arg.startsWith('--drain=')) {
|
|
392
|
+
result.flags.drain = arg.slice(8);
|
|
393
|
+
i++;
|
|
394
|
+
} else if (arg === '--canary') {
|
|
395
|
+
result.flags.canary = true;
|
|
396
|
+
i++;
|
|
397
|
+
} else if (arg === '--weight') {
|
|
398
|
+
result.flags.weight = argv[i + 1] || '';
|
|
399
|
+
i += 2;
|
|
400
|
+
} else if (arg.startsWith('--weight=')) {
|
|
401
|
+
result.flags.weight = arg.slice(9);
|
|
402
|
+
i++;
|
|
347
403
|
} else if (!arg.startsWith('-')) {
|
|
348
404
|
if (!result.command) {
|
|
349
405
|
result.command = arg;
|
|
@@ -352,7 +408,6 @@ export function parseArgs(argv) {
|
|
|
352
408
|
}
|
|
353
409
|
i++;
|
|
354
410
|
} else {
|
|
355
|
-
// Unknown flag, treat as positional or ignore
|
|
356
411
|
i++;
|
|
357
412
|
}
|
|
358
413
|
}
|
|
@@ -362,29 +417,37 @@ export function parseArgs(argv) {
|
|
|
362
417
|
|
|
363
418
|
export function printHelp() {
|
|
364
419
|
console.log(`
|
|
365
|
-
unitup - Minimal systemd user service wrapper
|
|
420
|
+
unitup - Minimal systemd user service wrapper & cross-platform service manager (Linux, macOS, Windows)
|
|
366
421
|
|
|
367
422
|
Usage:
|
|
368
423
|
unitup doctor Run system readiness and runtime check
|
|
369
424
|
unitup init [script] Create local project config file (unitup.config.json)
|
|
370
|
-
unitup add <script>
|
|
425
|
+
unitup install / add <script> Install script or executable as native service (--dry-run)
|
|
371
426
|
unitup start <name|@group> Start a service (--enable to enable on boot)
|
|
372
427
|
unitup stop <name|@group> Stop a service
|
|
373
428
|
unitup restart <name|@group> Restart a service
|
|
374
|
-
unitup
|
|
375
|
-
unitup
|
|
429
|
+
unitup deploy <name> Perform zero-downtime deployment (--canary, --weight 10%)
|
|
430
|
+
unitup rollback <name> Roll back a service to its previous generation or abort canary
|
|
431
|
+
unitup promote <name> Promote a canary generation to 100% active
|
|
432
|
+
unitup generations <name> List all runtime generations and ports for a service
|
|
433
|
+
unitup status <name|@group> Show status summary (--raw, --verbose)
|
|
434
|
+
unitup enable <name> Enable service on startup/boot
|
|
435
|
+
unitup disable <name> Disable service on startup/boot
|
|
436
|
+
unitup logs <name|@group> Show service logs (-f/--follow, -n/--lines N, -c/--cat)
|
|
376
437
|
unitup inspect <name> View detailed app configuration and status
|
|
377
438
|
unitup failures List all failed services with exit code & restarts
|
|
378
|
-
unitup remove
|
|
439
|
+
unitup uninstall / remove Stop, disable and delete a service (--force)
|
|
379
440
|
unitup list / unitup ls List all services (--group <group>)
|
|
380
441
|
unitup memory / unitup top Show memory usage for all apps or a specific app
|
|
381
442
|
unitup config Manage global configuration (--default-memory 1G)
|
|
382
443
|
|
|
383
444
|
Config Options:
|
|
384
445
|
--config <path> Path to custom project config file (defaults to unitup.config.json)
|
|
446
|
+
--system Install as system-wide service (LaunchDaemons on macOS / root systemd)
|
|
447
|
+
--dry-run Display generated service configuration without installing
|
|
385
448
|
|
|
386
|
-
Schedule Commands:
|
|
387
|
-
unitup schedule <script> [options] Create a
|
|
449
|
+
Schedule Commands (systemd):
|
|
450
|
+
unitup schedule <script> [options] Create a timer schedule
|
|
388
451
|
unitup schedules / unitup timers List all schedule timers
|
|
389
452
|
unitup schedule-status <name> Show detailed schedule timer status
|
|
390
453
|
unitup schedule-run <name> Manually run schedule service unit once
|
|
@@ -398,21 +461,13 @@ Memory Options:
|
|
|
398
461
|
--memory-max <size> Hard memory limit (e.g. 512M, 1G)
|
|
399
462
|
--default-memory [sz] Apply default memory limit (defaults to 1G)
|
|
400
463
|
|
|
401
|
-
Schedule Options:
|
|
402
|
-
--every <duration> Execution interval (e.g., 30s, 10m, 2h, 1d)
|
|
403
|
-
--calendar <expr> Calendar expression (e.g., daily, "Mon..Fri 09:00")
|
|
404
|
-
--on-boot <duration> Delay relative to system boot (e.g., 5m)
|
|
405
|
-
--on-active <duration> Delay relative to timer activation
|
|
406
|
-
--random-delay <dur> Random delay to prevent stampedes (e.g., 10m)
|
|
407
|
-
--persistent Run immediately if a scheduled run was missed
|
|
408
|
-
--start Enable and start timer immediately after creation
|
|
409
|
-
|
|
410
464
|
Examples:
|
|
411
|
-
unitup
|
|
412
|
-
unitup
|
|
413
|
-
unitup
|
|
414
|
-
unitup
|
|
415
|
-
unitup
|
|
465
|
+
unitup install server.js --name api --start
|
|
466
|
+
unitup install ./app.js --dry-run
|
|
467
|
+
unitup start api
|
|
468
|
+
unitup status api --verbose
|
|
469
|
+
unitup logs api --follow
|
|
470
|
+
unitup uninstall api
|
|
416
471
|
`);
|
|
417
472
|
}
|
|
418
473
|
|
|
@@ -468,12 +523,12 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
468
523
|
if (fs.existsSync(targetConfigPath) && !flags.force) {
|
|
469
524
|
throw new Error(
|
|
470
525
|
`Configuration file "${path.basename(targetConfigPath)}" already exists at ${targetConfigPath}.\n` +
|
|
471
|
-
|
|
526
|
+
`Use --force (-f) to overwrite.`
|
|
472
527
|
);
|
|
473
528
|
}
|
|
474
529
|
|
|
475
530
|
const scriptArg = positionals[0];
|
|
476
|
-
|
|
531
|
+
const scriptPath = scriptArg || '';
|
|
477
532
|
let name = flags.name;
|
|
478
533
|
if (!name) {
|
|
479
534
|
if (scriptPath) {
|
|
@@ -521,6 +576,7 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
521
576
|
break;
|
|
522
577
|
}
|
|
523
578
|
|
|
579
|
+
case 'install':
|
|
524
580
|
case 'add': {
|
|
525
581
|
const targetCwd = flags.cwd ? path.resolve(process.cwd(), flags.cwd) : process.cwd();
|
|
526
582
|
const configPath = flags.config ? path.resolve(targetCwd, flags.config) : findProjectConfig(targetCwd);
|
|
@@ -528,18 +584,23 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
528
584
|
|
|
529
585
|
const scriptArg = positionals[0] || (projectCfg ? projectCfg.script : undefined);
|
|
530
586
|
let name = flags.name || (projectCfg ? projectCfg.name : undefined);
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
587
|
+
const cmdFlag = flags.command || (projectCfg ? projectCfg.command : undefined);
|
|
588
|
+
const runtimeFlag = flags.runtime || (projectCfg ? projectCfg.runtime : undefined);
|
|
589
|
+
const groupFlag = flags.group || (projectCfg ? projectCfg.group : undefined) || 'default';
|
|
590
|
+
const restartFlag =
|
|
591
|
+
flags.restart !== 'on-failure' ? flags.restart : (projectCfg && projectCfg.restart) || 'on-failure';
|
|
592
|
+
const envFileFlag = flags.envFile || (projectCfg ? projectCfg.envFile : undefined);
|
|
593
|
+
const argsFlag = flags.args && flags.args.length > 0 ? flags.args : (projectCfg && projectCfg.args) || [];
|
|
594
|
+
const runtimeArgsFlag =
|
|
595
|
+
flags.runtimeArgs && flags.runtimeArgs.length > 0
|
|
596
|
+
? flags.runtimeArgs
|
|
597
|
+
: (projectCfg && projectCfg.runtimeArgs) || [];
|
|
598
|
+
const memoryHighFlag = flags.memoryHigh || projectCfg?.resources?.memoryHigh || projectCfg?.memoryHigh || '';
|
|
599
|
+
const memoryMaxFlag = flags.memoryMax || projectCfg?.resources?.memoryMax || projectCfg?.memoryMax || '';
|
|
600
|
+
const memorySwapMaxFlag =
|
|
601
|
+
flags.memorySwapMax || projectCfg?.resources?.memorySwapMax || projectCfg?.memorySwapMax || '';
|
|
602
|
+
|
|
603
|
+
const absScriptPath = scriptArg ? path.resolve(targetCwd, scriptArg) : undefined;
|
|
543
604
|
|
|
544
605
|
if (flags.node) {
|
|
545
606
|
const resolvedNode = await findNodeExecutable(flags.node);
|
|
@@ -573,7 +634,7 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
573
634
|
}
|
|
574
635
|
}
|
|
575
636
|
|
|
576
|
-
const envObj =
|
|
637
|
+
const envObj = projectCfg && projectCfg.env && typeof projectCfg.env === 'object' ? { ...projectCfg.env } : {};
|
|
577
638
|
for (const e of flags.env) {
|
|
578
639
|
const idx = e.indexOf('=');
|
|
579
640
|
if (idx !== -1) {
|
|
@@ -583,7 +644,7 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
583
644
|
}
|
|
584
645
|
}
|
|
585
646
|
|
|
586
|
-
const
|
|
647
|
+
const installOptions = {
|
|
587
648
|
name,
|
|
588
649
|
group: groupFlag,
|
|
589
650
|
script: absScriptPath,
|
|
@@ -597,14 +658,34 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
597
658
|
restart: restartFlag,
|
|
598
659
|
args: argsFlag,
|
|
599
660
|
start: flags.start,
|
|
661
|
+
system: flags.system,
|
|
600
662
|
memoryHigh: memoryHighFlag,
|
|
601
663
|
memoryMax: memoryMaxFlag,
|
|
602
664
|
memorySwapMax: memorySwapMaxFlag,
|
|
603
665
|
defaultMemory: flags.defaultMemory,
|
|
604
|
-
force: flags.force
|
|
605
|
-
|
|
666
|
+
force: flags.force,
|
|
667
|
+
port: flags.port ? Number(flags.port) : projectCfg?.port,
|
|
668
|
+
deploy: {
|
|
669
|
+
...(projectCfg?.deploy || {}),
|
|
670
|
+
...(flags.zeroDowntime ? { zeroDowntime: true } : {}),
|
|
671
|
+
...(flags.ready ? { ready: flags.ready } : {}),
|
|
672
|
+
...(flags.drain ? { drain: flags.drain } : {})
|
|
673
|
+
}
|
|
674
|
+
};
|
|
606
675
|
|
|
607
|
-
|
|
676
|
+
if (flags.dryRun) {
|
|
677
|
+
const generated = await defaultManager.generate(installOptions);
|
|
678
|
+
if (typeof generated === 'string') {
|
|
679
|
+
console.log(generated);
|
|
680
|
+
} else {
|
|
681
|
+
console.log(JSON.stringify(generated, null, 2));
|
|
682
|
+
}
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const res = await defaultManager.install(installOptions);
|
|
687
|
+
|
|
688
|
+
console.log(`✓ Service "${res.name}" created at ${res.unitPath || res.serviceName || res.name}`);
|
|
608
689
|
if (flags.start) {
|
|
609
690
|
console.log(`✓ Service "${res.name}" enabled and started.`);
|
|
610
691
|
} else {
|
|
@@ -638,7 +719,7 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
638
719
|
}
|
|
639
720
|
const targetNames = await resolveTargetNames(nameArg);
|
|
640
721
|
for (const name of targetNames) {
|
|
641
|
-
await
|
|
722
|
+
await defaultManager.start(name, { enable: flags.enable, system: flags.system });
|
|
642
723
|
console.log(`✓ Service "${sanitizeServiceName(name)}" ${flags.enable ? 'enabled & ' : ''}started.`);
|
|
643
724
|
}
|
|
644
725
|
break;
|
|
@@ -651,7 +732,7 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
651
732
|
}
|
|
652
733
|
const targetNames = await resolveTargetNames(nameArg);
|
|
653
734
|
for (const name of targetNames) {
|
|
654
|
-
await
|
|
735
|
+
await defaultManager.stop(name, { system: flags.system });
|
|
655
736
|
console.log(`✓ Service "${sanitizeServiceName(name)}" stopped.`);
|
|
656
737
|
}
|
|
657
738
|
break;
|
|
@@ -660,20 +741,294 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
660
741
|
case 'restart': {
|
|
661
742
|
const nameArg = positionals[0];
|
|
662
743
|
if (!nameArg) {
|
|
663
|
-
throw new Error(
|
|
744
|
+
throw new Error(
|
|
745
|
+
'Service name or @group is required.\nExample: unitup restart api or unitup restart @myproject'
|
|
746
|
+
);
|
|
664
747
|
}
|
|
665
748
|
const targetNames = await resolveTargetNames(nameArg);
|
|
666
749
|
for (const name of targetNames) {
|
|
667
|
-
await
|
|
750
|
+
await defaultManager.restart(name, { system: flags.system });
|
|
668
751
|
console.log(`✓ Service "${sanitizeServiceName(name)}" restarted.`);
|
|
669
752
|
}
|
|
670
753
|
break;
|
|
671
754
|
}
|
|
672
755
|
|
|
756
|
+
case 'deploy': {
|
|
757
|
+
const nameArg = positionals[0];
|
|
758
|
+
if (!nameArg) {
|
|
759
|
+
throw new Error('Service name is required.\nExample: unitup deploy api');
|
|
760
|
+
}
|
|
761
|
+
const safeName = sanitizeServiceName(nameArg);
|
|
762
|
+
console.log(`${safeName}\n`);
|
|
763
|
+
|
|
764
|
+
const isCanary = flags.canary;
|
|
765
|
+
const weight = flags.weight ? Number(flags.weight.replace('%', '')) : undefined;
|
|
766
|
+
|
|
767
|
+
const onProgress = (evt) => {
|
|
768
|
+
if (evt.state === 'STARTING') {
|
|
769
|
+
console.log(`✓ generation #${evt.generation} started`);
|
|
770
|
+
} else if (evt.state === 'WAITING_READY') {
|
|
771
|
+
// waiting for readiness
|
|
772
|
+
} else if (evt.state === 'SETTING_CANARY') {
|
|
773
|
+
const pct = Math.round(evt.weight > 1 ? evt.weight : evt.weight * 100);
|
|
774
|
+
console.log('✓ ready');
|
|
775
|
+
console.log(`✓ canary traffic set: ${pct}% to generation #${evt.generation}`);
|
|
776
|
+
} else if (evt.state === 'SWITCHING') {
|
|
777
|
+
console.log('✓ ready');
|
|
778
|
+
console.log('✓ traffic switched');
|
|
779
|
+
} else if (evt.state === 'STOPPING_PREVIOUS') {
|
|
780
|
+
console.log('✓ previous generation drained\n');
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
const res = await deploymentManager.deploy(
|
|
785
|
+
safeName,
|
|
786
|
+
{},
|
|
787
|
+
{
|
|
788
|
+
publicPort: flags.port ? Number(flags.port) : undefined,
|
|
789
|
+
readyPath: flags.ready || undefined,
|
|
790
|
+
drainTimeout: flags.drain ? parseDurationMs(flags.drain) : undefined,
|
|
791
|
+
canary: isCanary,
|
|
792
|
+
weight,
|
|
793
|
+
onProgress
|
|
794
|
+
}
|
|
795
|
+
);
|
|
796
|
+
|
|
797
|
+
if (res.isCanary) {
|
|
798
|
+
const pct = Math.round(res.canaryWeight > 1 ? res.canaryWeight : res.canaryWeight * 100);
|
|
799
|
+
console.log(
|
|
800
|
+
`\ncanary: v${res.canaryGeneration} (${pct}%) active alongside v${res.activeGeneration} (${100 - pct}%)`
|
|
801
|
+
);
|
|
802
|
+
console.log(`Run "unitup promote ${safeName}" to shift 100% traffic to v${res.canaryGeneration}.`);
|
|
803
|
+
} else {
|
|
804
|
+
const prev = res.previousGeneration ? `v${res.previousGeneration}` : 'none';
|
|
805
|
+
const curr = `v${res.currentGeneration}`;
|
|
806
|
+
console.log(`${prev} -> ${curr}`);
|
|
807
|
+
console.log(`downtime: ${res.downtimeMs ?? 0}ms`);
|
|
808
|
+
}
|
|
809
|
+
break;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
case 'promote': {
|
|
813
|
+
const nameArg = positionals[0];
|
|
814
|
+
if (!nameArg) {
|
|
815
|
+
throw new Error('Service name is required.\nExample: unitup promote api');
|
|
816
|
+
}
|
|
817
|
+
const safeName = sanitizeServiceName(nameArg);
|
|
818
|
+
console.log(`Promoting canary for "${safeName}"...\n`);
|
|
819
|
+
|
|
820
|
+
const onProgress = (evt) => {
|
|
821
|
+
if (evt.state === 'PROMOTING') {
|
|
822
|
+
console.log(`✓ Promoting generation #${evt.generation} to 100%`);
|
|
823
|
+
} else if (evt.state === 'DRAINING') {
|
|
824
|
+
console.log(`✓ Draining previous generation #${evt.generation}`);
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
const res = await deploymentManager.promote(
|
|
829
|
+
safeName,
|
|
830
|
+
{},
|
|
831
|
+
{
|
|
832
|
+
drainTimeout: flags.drain ? parseDurationMs(flags.drain) : undefined,
|
|
833
|
+
onProgress
|
|
834
|
+
}
|
|
835
|
+
);
|
|
836
|
+
|
|
837
|
+
console.log(`\n✓ Promoted generation #${res.promotedGeneration} to 100% active.`);
|
|
838
|
+
if (res.previousGeneration) {
|
|
839
|
+
console.log(`v${res.previousGeneration} -> v${res.promotedGeneration}`);
|
|
840
|
+
}
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
case 'rollback': {
|
|
845
|
+
const nameArg = positionals[0];
|
|
846
|
+
if (!nameArg) {
|
|
847
|
+
throw new Error('Service name is required.\nExample: unitup rollback api');
|
|
848
|
+
}
|
|
849
|
+
const safeName = sanitizeServiceName(nameArg);
|
|
850
|
+
console.log(`Rolling back "${safeName}"...\n`);
|
|
851
|
+
|
|
852
|
+
const onProgress = (evt) => {
|
|
853
|
+
if (evt.state === 'ABORTING_CANARY') {
|
|
854
|
+
console.log(`✓ Aborting and draining canary generation #${evt.canaryGeneration}`);
|
|
855
|
+
} else if (evt.state === 'SWITCHING') {
|
|
856
|
+
console.log(`✓ Traffic switched back to generation #${evt.targetGeneration}`);
|
|
857
|
+
} else if (evt.state === 'DRAINING_CURRENT') {
|
|
858
|
+
console.log(`✓ Draining generation #${evt.generation}`);
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
const res = await rollbackManager.rollback(
|
|
863
|
+
safeName,
|
|
864
|
+
{},
|
|
865
|
+
{
|
|
866
|
+
readyPath: flags.ready || undefined,
|
|
867
|
+
drainTimeout: flags.drain ? parseDurationMs(flags.drain) : undefined,
|
|
868
|
+
onProgress
|
|
869
|
+
}
|
|
870
|
+
);
|
|
871
|
+
|
|
872
|
+
if (res.abortedCanary) {
|
|
873
|
+
console.log(
|
|
874
|
+
`\n✓ Aborted canary generation #${res.abortedCanary}. 100% traffic retained on generation #${res.activeGeneration}.`
|
|
875
|
+
);
|
|
876
|
+
} else {
|
|
877
|
+
console.log(`\n✓ Rolled back service "${safeName}" to generation #${res.activeGeneration}.`);
|
|
878
|
+
}
|
|
879
|
+
break;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
case 'supervisor': {
|
|
883
|
+
const nameArg = positionals[0];
|
|
884
|
+
if (!nameArg) {
|
|
885
|
+
throw new Error('Service name is required for supervisor mode.\nExample: unitup supervisor api');
|
|
886
|
+
}
|
|
887
|
+
await Supervisor.run(nameArg);
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
case 'history':
|
|
892
|
+
case 'generations': {
|
|
893
|
+
const nameArg = positionals[0];
|
|
894
|
+
if (!nameArg) {
|
|
895
|
+
throw new Error('Service name is required.\nExample: unitup generations api');
|
|
896
|
+
}
|
|
897
|
+
const safeName = sanitizeServiceName(nameArg);
|
|
898
|
+
const gm = new GenerationManager();
|
|
899
|
+
let list = gm.listGenerations(safeName);
|
|
900
|
+
|
|
901
|
+
// Check if supervisor is running via IPC to enrich with live in-flight data
|
|
902
|
+
let liveStatus = null;
|
|
903
|
+
const ipcClient = new IPCClient(safeName);
|
|
904
|
+
if (await ipcClient.isAlive()) {
|
|
905
|
+
try {
|
|
906
|
+
liveStatus = await ipcClient.send({ action: 'status' }, null, 1000);
|
|
907
|
+
if (liveStatus?.generations) {
|
|
908
|
+
list = liveStatus.generations;
|
|
909
|
+
}
|
|
910
|
+
} catch {
|
|
911
|
+
// fallback to local state
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (!list || list.length === 0) {
|
|
916
|
+
if (flags.json) {
|
|
917
|
+
console.log(JSON.stringify([], null, 2));
|
|
918
|
+
} else {
|
|
919
|
+
console.log(`No generations recorded for service "${safeName}".`);
|
|
920
|
+
}
|
|
921
|
+
break;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const tableData = list.map((g) => {
|
|
925
|
+
let activeFor = '-';
|
|
926
|
+
if (g.status === 'active') {
|
|
927
|
+
const start = g.activatedAt
|
|
928
|
+
? new Date(g.activatedAt).getTime()
|
|
929
|
+
: g.createdAt
|
|
930
|
+
? new Date(g.createdAt).getTime()
|
|
931
|
+
: null;
|
|
932
|
+
if (start && !Number.isNaN(start)) {
|
|
933
|
+
activeFor = formatDuration(Date.now() - start);
|
|
934
|
+
}
|
|
935
|
+
} else if (g.activatedAt) {
|
|
936
|
+
const start = new Date(g.activatedAt).getTime();
|
|
937
|
+
const end = g.deactivatedAt
|
|
938
|
+
? new Date(g.deactivatedAt).getTime()
|
|
939
|
+
: g.stoppedAt
|
|
940
|
+
? new Date(g.stoppedAt).getTime()
|
|
941
|
+
: g.drainingAt
|
|
942
|
+
? new Date(g.drainingAt).getTime()
|
|
943
|
+
: null;
|
|
944
|
+
if (start && end && !Number.isNaN(start) && !Number.isNaN(end) && end >= start) {
|
|
945
|
+
activeFor = formatDuration(end - start);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
return {
|
|
950
|
+
gen: `#${g.id}`,
|
|
951
|
+
status: g.status || 'unknown',
|
|
952
|
+
pid: g.pid ? String(g.pid) : '-',
|
|
953
|
+
port: g.internalPort ? String(g.internalPort) : '-',
|
|
954
|
+
created: formatRelativeTime(g.createdAt),
|
|
955
|
+
activeFor,
|
|
956
|
+
raw: g
|
|
957
|
+
};
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
if (flags.json) {
|
|
961
|
+
console.log(
|
|
962
|
+
JSON.stringify(
|
|
963
|
+
tableData.map((d) => ({
|
|
964
|
+
id: d.raw.id,
|
|
965
|
+
status: d.raw.status,
|
|
966
|
+
pid: d.raw.pid,
|
|
967
|
+
internalPort: d.raw.internalPort,
|
|
968
|
+
createdAt: d.raw.createdAt,
|
|
969
|
+
activatedAt: d.raw.activatedAt || null,
|
|
970
|
+
stoppedAt: d.raw.stoppedAt || null,
|
|
971
|
+
deactivatedAt: d.raw.deactivatedAt || null,
|
|
972
|
+
activeFor: d.activeFor
|
|
973
|
+
})),
|
|
974
|
+
null,
|
|
975
|
+
2
|
|
976
|
+
)
|
|
977
|
+
);
|
|
978
|
+
break;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
console.log(`=== Generations: ${safeName} ===\n`);
|
|
982
|
+
if (liveStatus?.routerPort) {
|
|
983
|
+
console.log(`Public Router: :${liveStatus.routerPort}`);
|
|
984
|
+
console.log(`In-Flight Requests: ${liveStatus.inFlightRequests || 0}\n`);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
const table = formatTable(tableData, [
|
|
988
|
+
{ key: 'gen', label: 'GEN' },
|
|
989
|
+
{ key: 'status', label: 'STATUS' },
|
|
990
|
+
{ key: 'pid', label: 'PID' },
|
|
991
|
+
{ key: 'port', label: 'PORT' },
|
|
992
|
+
{ key: 'created', label: 'CREATED' },
|
|
993
|
+
{ key: 'activeFor', label: 'ACTIVE FOR' }
|
|
994
|
+
]);
|
|
995
|
+
console.log(table);
|
|
996
|
+
break;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
case 'enable': {
|
|
1000
|
+
const nameArg = positionals[0];
|
|
1001
|
+
if (!nameArg) {
|
|
1002
|
+
throw new Error('Service name or @group is required.\nExample: unitup enable api');
|
|
1003
|
+
}
|
|
1004
|
+
const targetNames = await resolveTargetNames(nameArg);
|
|
1005
|
+
for (const name of targetNames) {
|
|
1006
|
+
await defaultManager.enable(name, { system: flags.system });
|
|
1007
|
+
console.log(`✓ Service "${sanitizeServiceName(name)}" enabled on startup.`);
|
|
1008
|
+
}
|
|
1009
|
+
break;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
case 'disable': {
|
|
1013
|
+
const nameArg = positionals[0];
|
|
1014
|
+
if (!nameArg) {
|
|
1015
|
+
throw new Error('Service name or @group is required.\nExample: unitup disable api');
|
|
1016
|
+
}
|
|
1017
|
+
const targetNames = await resolveTargetNames(nameArg);
|
|
1018
|
+
for (const name of targetNames) {
|
|
1019
|
+
await defaultManager.disable(name, { system: flags.system });
|
|
1020
|
+
console.log(`✓ Service "${sanitizeServiceName(name)}" disabled on startup.`);
|
|
1021
|
+
}
|
|
1022
|
+
break;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
case 'ps':
|
|
673
1026
|
case 'status': {
|
|
674
1027
|
const nameArg = positionals[0];
|
|
675
1028
|
if (!nameArg) {
|
|
676
|
-
throw new Error(
|
|
1029
|
+
throw new Error(
|
|
1030
|
+
'Service name or @group is required.\nExample: unitup status api or unitup status @myproject'
|
|
1031
|
+
);
|
|
677
1032
|
}
|
|
678
1033
|
|
|
679
1034
|
const targetNames = await resolveTargetNames(nameArg);
|
|
@@ -682,25 +1037,103 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
682
1037
|
const raw = await getServiceStatusRaw(name);
|
|
683
1038
|
console.log(raw);
|
|
684
1039
|
} else {
|
|
685
|
-
const status = await
|
|
1040
|
+
const status = await defaultManager.status(name, { system: flags.system });
|
|
1041
|
+
|
|
1042
|
+
// Enrich with generation info if available
|
|
1043
|
+
const gm = new GenerationManager();
|
|
1044
|
+
const genState = gm.loadState(name);
|
|
1045
|
+
let genDetails = null;
|
|
1046
|
+
if (genState?.activeGeneration || genState?.canaryGeneration) {
|
|
1047
|
+
const activeGen = gm.getActive(name);
|
|
1048
|
+
const canaryGen = gm.getCanary(name);
|
|
1049
|
+
genDetails = {
|
|
1050
|
+
activeGeneration: genState.activeGeneration,
|
|
1051
|
+
internalPort: activeGen?.internalPort || null,
|
|
1052
|
+
canaryGeneration: canaryGen?.id || null,
|
|
1053
|
+
canaryPort: canaryGen?.internalPort || null,
|
|
1054
|
+
canaryWeight: canaryGen?.canaryWeight || null,
|
|
1055
|
+
routerPort: null,
|
|
1056
|
+
inFlightRequests: 0
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
const ipcClient = new IPCClient(name);
|
|
1060
|
+
if (await ipcClient.isAlive()) {
|
|
1061
|
+
try {
|
|
1062
|
+
const live = await ipcClient.send({ action: 'status' }, null, 500);
|
|
1063
|
+
if (live?.routerPort) {
|
|
1064
|
+
genDetails.routerPort = live.routerPort;
|
|
1065
|
+
}
|
|
1066
|
+
if (live?.inFlightRequests !== undefined) {
|
|
1067
|
+
genDetails.inFlightRequests = live.inFlightRequests;
|
|
1068
|
+
}
|
|
1069
|
+
if (live?.canaryGeneration) {
|
|
1070
|
+
genDetails.canaryGeneration = live.canaryGeneration.id;
|
|
1071
|
+
genDetails.canaryPort = live.canaryGeneration.internalPort;
|
|
1072
|
+
}
|
|
1073
|
+
if (live?.canaryWeight !== undefined) {
|
|
1074
|
+
genDetails.canaryWeight = live.canaryWeight;
|
|
1075
|
+
}
|
|
1076
|
+
} catch {
|
|
1077
|
+
// ignore
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
if (flags.json) {
|
|
1083
|
+
const jsonObj = {
|
|
1084
|
+
...status,
|
|
1085
|
+
...(genDetails ? { deployment: genDetails } : {})
|
|
1086
|
+
};
|
|
1087
|
+
console.log(JSON.stringify(jsonObj, null, 2));
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
686
1091
|
console.log(`${status.name}\n`);
|
|
687
|
-
console.log(`Status: ${status.status}`);
|
|
1092
|
+
console.log(`Status: ${status.status || status.state}`);
|
|
688
1093
|
console.log(`PID: ${status.pid}`);
|
|
689
1094
|
console.log(`Started: ${status.started}`);
|
|
690
1095
|
console.log(`Restarts: ${status.restarts}`);
|
|
691
1096
|
console.log(`Command: ${status.command}`);
|
|
692
1097
|
console.log(`Arguments: ${status.arguments}`);
|
|
693
1098
|
console.log(`Working directory: ${status.cwd}`);
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
1099
|
+
|
|
1100
|
+
if (genDetails) {
|
|
1101
|
+
if (genDetails.activeGeneration) {
|
|
1102
|
+
console.log(`Active Generation: #${genDetails.activeGeneration}`);
|
|
1103
|
+
}
|
|
1104
|
+
if (genDetails.canaryGeneration) {
|
|
1105
|
+
const pct = Math.round(
|
|
1106
|
+
genDetails.canaryWeight > 1 ? genDetails.canaryWeight : genDetails.canaryWeight * 100
|
|
1107
|
+
);
|
|
1108
|
+
console.log(
|
|
1109
|
+
`Canary: #${genDetails.canaryGeneration} (${pct}% traffic) -> :${genDetails.canaryPort || 'none'}`
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
if (genDetails.routerPort) {
|
|
1113
|
+
const targetPort = genDetails.internalPort ? `:${genDetails.internalPort}` : 'none';
|
|
1114
|
+
console.log(`Router: :${genDetails.routerPort} -> ${targetPort}`);
|
|
1115
|
+
console.log(`In-Flight Requests: ${genDetails.inFlightRequests}`);
|
|
1116
|
+
} else if (genDetails.internalPort) {
|
|
1117
|
+
console.log(`Internal Port: ${genDetails.internalPort}`);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
if (status.memory) console.log(`Memory: ${status.memory}`);
|
|
1122
|
+
if (status.memoryPeak) console.log(`Memory Peak: ${status.memoryPeak}`);
|
|
1123
|
+
if (status.memoryHigh) console.log(`Memory High: ${status.memoryHigh}`);
|
|
1124
|
+
if (status.memoryMax) console.log(`Memory Max: ${status.memoryMax}`);
|
|
1125
|
+
if (status.memorySwapMax) console.log(`Swap Max: ${status.memorySwapMax}`);
|
|
1126
|
+
if (flags.verbose) {
|
|
1127
|
+
console.log(`Platform: ${status.platform}`);
|
|
1128
|
+
console.log(`Manager: ${status.manager}`);
|
|
1129
|
+
if (status.unitPath) console.log(`Unit Path: ${status.unitPath}`);
|
|
1130
|
+
}
|
|
699
1131
|
}
|
|
700
1132
|
}
|
|
701
1133
|
break;
|
|
702
1134
|
}
|
|
703
1135
|
|
|
1136
|
+
case 'log':
|
|
704
1137
|
case 'logs': {
|
|
705
1138
|
const nameArg = positionals[0];
|
|
706
1139
|
if (!nameArg) {
|
|
@@ -712,7 +1145,7 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
712
1145
|
if (targetNames.length > 1) {
|
|
713
1146
|
console.log(`=== Logs for ${name} ===`);
|
|
714
1147
|
}
|
|
715
|
-
const output = await
|
|
1148
|
+
const output = await defaultManager.logs(name, {
|
|
716
1149
|
follow: flags.follow,
|
|
717
1150
|
lines: flags.lines,
|
|
718
1151
|
cat: flags.cat,
|
|
@@ -723,9 +1156,24 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
723
1156
|
grep: flags.grep,
|
|
724
1157
|
boot: flags.boot,
|
|
725
1158
|
json: flags.json,
|
|
726
|
-
diskUsage: flags.diskUsage
|
|
1159
|
+
diskUsage: flags.diskUsage,
|
|
1160
|
+
system: flags.system
|
|
727
1161
|
});
|
|
728
|
-
if (typeof output === '
|
|
1162
|
+
if (output && typeof output.on === 'function') {
|
|
1163
|
+
await new Promise((resolve) => {
|
|
1164
|
+
const cleanup = () => {
|
|
1165
|
+
if (output.stop) output.stop();
|
|
1166
|
+
resolve();
|
|
1167
|
+
};
|
|
1168
|
+
process.on('SIGINT', cleanup);
|
|
1169
|
+
process.on('SIGTERM', cleanup);
|
|
1170
|
+
output.on('close', resolve);
|
|
1171
|
+
output.on('error', (err) => {
|
|
1172
|
+
console.error(err.message);
|
|
1173
|
+
cleanup();
|
|
1174
|
+
});
|
|
1175
|
+
});
|
|
1176
|
+
} else if (typeof output === 'string') {
|
|
729
1177
|
console.log(output);
|
|
730
1178
|
}
|
|
731
1179
|
}
|
|
@@ -735,16 +1183,21 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
735
1183
|
case 'journal': {
|
|
736
1184
|
const action = positionals[0];
|
|
737
1185
|
if (!action) {
|
|
738
|
-
throw new Error(
|
|
1186
|
+
throw new Error(
|
|
1187
|
+
'Journal action is required (disk-usage, rotate, vacuum).\nExample: unitup journal vacuum --size 500M'
|
|
1188
|
+
);
|
|
739
1189
|
}
|
|
740
1190
|
|
|
741
1191
|
if (action === 'vacuum' && !flags.yes && !flags.dryRun) {
|
|
742
1192
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
743
1193
|
const answer = await new Promise((resolve) => {
|
|
744
|
-
rl.question(
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
1194
|
+
rl.question(
|
|
1195
|
+
'This operation affects archived journal logs system-wide,\nnot only services managed by unitup.\n\nContinue? [y/N] ',
|
|
1196
|
+
(ans) => {
|
|
1197
|
+
rl.close();
|
|
1198
|
+
resolve(ans.trim());
|
|
1199
|
+
}
|
|
1200
|
+
);
|
|
748
1201
|
});
|
|
749
1202
|
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
750
1203
|
console.log('Aborted.');
|
|
@@ -763,14 +1216,19 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
763
1216
|
break;
|
|
764
1217
|
}
|
|
765
1218
|
|
|
1219
|
+
case 'uninstall':
|
|
1220
|
+
case 'rm':
|
|
1221
|
+
case 'delete':
|
|
766
1222
|
case 'remove': {
|
|
767
1223
|
const nameArg = positionals[0];
|
|
768
1224
|
if (!nameArg) {
|
|
769
|
-
throw new Error(
|
|
1225
|
+
throw new Error(
|
|
1226
|
+
'Service name or @group is required.\nExample: unitup remove api or unitup remove @myproject'
|
|
1227
|
+
);
|
|
770
1228
|
}
|
|
771
1229
|
const targetNames = await resolveTargetNames(nameArg);
|
|
772
1230
|
for (const name of targetNames) {
|
|
773
|
-
await
|
|
1231
|
+
await defaultManager.uninstall(name, { force: flags.force, system: flags.system });
|
|
774
1232
|
console.log(`✓ Service "${sanitizeServiceName(name)}" removed.`);
|
|
775
1233
|
}
|
|
776
1234
|
break;
|
|
@@ -778,7 +1236,7 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
778
1236
|
|
|
779
1237
|
case 'ls':
|
|
780
1238
|
case 'list': {
|
|
781
|
-
const services = await
|
|
1239
|
+
const services = await defaultManager.list({ group: flags.group });
|
|
782
1240
|
if (services.length === 0) {
|
|
783
1241
|
if (flags.group) {
|
|
784
1242
|
console.log(`No services found in group "${flags.group}".`);
|
|
@@ -804,7 +1262,7 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
804
1262
|
if (!nameArg) {
|
|
805
1263
|
throw new Error('Service name is required.\nExample: unitup inspect api');
|
|
806
1264
|
}
|
|
807
|
-
const info = await
|
|
1265
|
+
const info = await defaultManager.inspect(nameArg, { system: flags.system });
|
|
808
1266
|
console.log(`Name: ${info.name}`);
|
|
809
1267
|
console.log(`Runtime: ${info.runtime}`);
|
|
810
1268
|
console.log(`Group: ${info.group}`);
|
|
@@ -812,17 +1270,17 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
812
1270
|
console.log(`Command: ${info.command}`);
|
|
813
1271
|
console.log(`Arguments: ${info.arguments}`);
|
|
814
1272
|
console.log(`Working directory: ${info.cwd}`);
|
|
815
|
-
console.log(`Unit: ${info.unit}`);
|
|
816
|
-
console.log(`Memory: ${info.memory}`);
|
|
817
|
-
console.log(`Memory Peak: ${info.memoryPeak}`);
|
|
818
|
-
console.log(`Memory High: ${info.memoryHigh}`);
|
|
819
|
-
console.log(`Memory Max: ${info.memoryMax}`);
|
|
820
|
-
console.log(`Swap Max: ${info.memorySwapMax}`);
|
|
1273
|
+
if (info.unit || info.unitFile) console.log(`Unit: ${info.unit || info.unitFile}`);
|
|
1274
|
+
if (info.memory) console.log(`Memory: ${info.memory}`);
|
|
1275
|
+
if (info.memoryPeak) console.log(`Memory Peak: ${info.memoryPeak}`);
|
|
1276
|
+
if (info.memoryHigh) console.log(`Memory High: ${info.memoryHigh}`);
|
|
1277
|
+
if (info.memoryMax) console.log(`Memory Max: ${info.memoryMax}`);
|
|
1278
|
+
if (info.memorySwapMax) console.log(`Swap Max: ${info.memorySwapMax}`);
|
|
821
1279
|
break;
|
|
822
1280
|
}
|
|
823
1281
|
|
|
824
1282
|
case 'failures': {
|
|
825
|
-
const failures = await
|
|
1283
|
+
const failures = await defaultManager.failures();
|
|
826
1284
|
if (failures.length === 0) {
|
|
827
1285
|
console.log('✓ No failed services.');
|
|
828
1286
|
break;
|