c8ctl-plugin-nano 1.3.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -6
- package/c8ctl-plugin.js +167 -56
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -30,6 +30,30 @@ c8ctl nano start|status|stop|restart|logs|clean|set|config
|
|
|
30
30
|
on `localhost` (round-robin partition ownership), tracks them in a state file,
|
|
31
31
|
and waits until every node is reachable.
|
|
32
32
|
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
This is a plugin for the [Camunda 8 CLI](https://www.npmjs.com/package/@camunda8/cli)
|
|
36
|
+
(`c8ctl`). Install the CLI, then load the plugin from npm:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# 1. Install the Camunda 8 CLI (once); requires Node.js 18+
|
|
40
|
+
npm install -g @camunda8/cli
|
|
41
|
+
|
|
42
|
+
# 2. Load this plugin from the npm registry
|
|
43
|
+
c8ctl load plugin c8ctl-plugin-nano
|
|
44
|
+
|
|
45
|
+
# 3. Verify it's available
|
|
46
|
+
c8ctl nano --help
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The prebuilt Nano BPM server binary for your platform is pulled in automatically
|
|
50
|
+
as an npm `optionalDependency`, so there is nothing to compile. To pull a newer
|
|
51
|
+
release later, run `c8ctl nano update` (see
|
|
52
|
+
[Updating to a new release](#updating-to-a-new-release-update)).
|
|
53
|
+
|
|
54
|
+
> Loading from a local checkout instead? Use
|
|
55
|
+
> `c8ctl load plugin --from file:///path/to/c8ctl-nano`.
|
|
56
|
+
|
|
33
57
|
## Usage
|
|
34
58
|
|
|
35
59
|
```bash
|
|
@@ -113,6 +137,35 @@ c8ctl nano stop --purge # stop and remove engine data in one step
|
|
|
113
137
|
|
|
114
138
|
`clean` refuses to run while any node is alive.
|
|
115
139
|
|
|
140
|
+
### Stress / throughput runs: bounding disk and RAM
|
|
141
|
+
|
|
142
|
+
The engine journal (`journal.jsonl`) is **append-only** — there is currently no
|
|
143
|
+
compaction or rotation — and the read-model retains every terminal instance by
|
|
144
|
+
default. Under sustained high load (tens of thousands of PI/s) this fills the
|
|
145
|
+
disk quickly. Two `start` flags keep a long run bounded:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Pure throughput: no journal / read-model on disk at all (in-memory engine).
|
|
149
|
+
# State is lost on stop/restart, and instances live in RAM — so cap them.
|
|
150
|
+
c8ctl nano start --in-memory --history-max 50000
|
|
151
|
+
|
|
152
|
+
# Exercise the disk path but cap the read model's terminal-instance history.
|
|
153
|
+
# (The journal still grows append-only; watch free space.)
|
|
154
|
+
c8ctl nano start --history-max 50000
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
- `--in-memory` (alias `--no-journal`) routes the engine to an in-memory journal
|
|
158
|
+
and a `:memory:` read store — nothing is written under `NANOBPMN_DATA_DIR`.
|
|
159
|
+
- `--history-max <n>` sets `NANOBPMN_HISTORY_MAX_INSTANCES`, continuously pruning
|
|
160
|
+
all but the most recent *n* terminal instances from the read model (`0`/unset =
|
|
161
|
+
unbounded). Works in both storage modes.
|
|
162
|
+
|
|
163
|
+
`c8ctl nano status` reports the active storage mode (`in-memory` vs `on-disk`)
|
|
164
|
+
and the history cap.
|
|
165
|
+
|
|
166
|
+
> ⚠️ With `--in-memory`, restart recovers nothing, and Raft/replicated logs are
|
|
167
|
+
> not persisted. Use it for stress/throughput testing, not durability testing.
|
|
168
|
+
|
|
116
169
|
## Configuration (`set` / `config`)
|
|
117
170
|
|
|
118
171
|
Persistent settings are stored in `<state home>/config.json`:
|
|
@@ -370,13 +423,13 @@ ProcessOS is the optimization-plane server that analyses a running Nano BPM
|
|
|
370
423
|
engine. The plugin can manage a single local ProcessOS instance with the same
|
|
371
424
|
start/stop/status/logs lifecycle as `nano`.
|
|
372
425
|
|
|
373
|
-
> **ProcessOS is a closed
|
|
426
|
+
> **ProcessOS is a closed beta.** The operational commands (`start`, `stop`,
|
|
374
427
|
> `status`, `logs`, `restart`) stay locked with a *"not available yet"* notice
|
|
375
428
|
> until you opt in. Only `set` and `config` work before then. Opt in either by
|
|
376
429
|
> setting the download URL you were given by the Nano BPM team, or by pointing
|
|
377
430
|
> the plugin at a binary you already have.
|
|
378
431
|
|
|
379
|
-
### Quick install (closed-
|
|
432
|
+
### Quick install (closed-beta invitees)
|
|
380
433
|
|
|
381
434
|
If you were given a ProcessOS download URL, this one-liner installs the Camunda 8
|
|
382
435
|
CLI (`@camunda8/cli`) and this plugin, then configures the download URL:
|
|
@@ -391,7 +444,7 @@ curl -fsSL https://gist.githubusercontent.com/jwulf/9015a7c660b274c568d80e85c391
|
|
|
391
444
|
|
|
392
445
|
|
|
393
446
|
```bash
|
|
394
|
-
# Closed-
|
|
447
|
+
# Closed-beta channel: persist the download URL, then start
|
|
395
448
|
c8ctl processos set download-url <url you were given>
|
|
396
449
|
c8ctl processos start # fetches processos-<os>-<arch> on first run
|
|
397
450
|
|
|
@@ -423,7 +476,7 @@ c8ctl processos stop
|
|
|
423
476
|
|
|
424
477
|
### Automatic update notice
|
|
425
478
|
|
|
426
|
-
When you're on the closed-
|
|
479
|
+
When you're on the closed-beta channel (download URL configured), the
|
|
427
480
|
plugin checks for newer ProcessOS builds in the background and prints a short
|
|
428
481
|
one-line notice (at most **once per day**) when the published version is newer
|
|
429
482
|
than the one you're running. It compares your installed binary's version against
|
|
@@ -473,7 +526,7 @@ Settings persist under a `processos` key in the same `config.json` as `nano`.
|
|
|
473
526
|
|
|
474
527
|
```bash
|
|
475
528
|
c8ctl processos set bin <path> # path to the downloaded ProcessOS binary
|
|
476
|
-
c8ctl processos set download-url <url> # closed-
|
|
529
|
+
c8ctl processos set download-url <url> # closed-beta binary download URL (enables ProcessOS)
|
|
477
530
|
c8ctl processos set port <n> # listen port (default 8090)
|
|
478
531
|
c8ctl processos set nano-url <url> # target Nano BPM engine (default http://localhost:8080)
|
|
479
532
|
c8ctl processos set data-dir <path> # PROCESSOS_DATA_DIR (default <stateHome>/processos-data)
|
|
@@ -489,7 +542,7 @@ configured download URL (`set download-url` / `$PROCESSOS_DOWNLOAD_URL`). Typed
|
|
|
489
542
|
settings (`port`, `nano-url`, `data-dir`) always
|
|
490
543
|
win over generic `env` passthrough values when launching.
|
|
491
544
|
|
|
492
|
-
## Installing
|
|
545
|
+
## Installing from a local checkout (development)
|
|
493
546
|
|
|
494
547
|
```bash
|
|
495
548
|
c8ctl load plugin --from file:///path/to/c8ctl-nano
|
|
@@ -501,6 +554,8 @@ Then verify it shows up:
|
|
|
501
554
|
c8ctl help | grep nano
|
|
502
555
|
```
|
|
503
556
|
|
|
557
|
+
For the normal npm install, see [Installation](#installation) above.
|
|
558
|
+
|
|
504
559
|
## Distribution & releasing
|
|
505
560
|
|
|
506
561
|
Releases are automated with **semantic-release** (`.github/workflows/release.yml`,
|
package/c8ctl-plugin.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*
|
|
20
20
|
* Usage:
|
|
21
21
|
* c8ctl nano start [<nodes>] [--port <basePort>] [--partitions <n>] [--rf <n>]
|
|
22
|
+
* [--in-memory] [--history-max <n>]
|
|
22
23
|
* c8ctl nano status
|
|
23
24
|
* c8ctl nano stop [--purge]
|
|
24
25
|
* c8ctl nano logs [<nodeId>] [--follow]
|
|
@@ -63,6 +64,21 @@ function readBundledBinaryInfo() {
|
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
/** Run `<binary> --version` and extract a semver-ish token. Null on failure. */
|
|
68
|
+
function binaryVersion(binary) {
|
|
69
|
+
if (!binary) return null;
|
|
70
|
+
try {
|
|
71
|
+
const res = spawnSync(binary, ['--version'], { encoding: 'utf8', timeout: 3000 });
|
|
72
|
+
if (res.status === 0) {
|
|
73
|
+
const m = String(res.stdout || res.stderr || '').match(/(\d+\.\d+\.\d+[^\s]*)/);
|
|
74
|
+
if (m) return m[1];
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
/* ignore */
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
66
82
|
/**
|
|
67
83
|
* Locate the nanobpmn binary shipped by the matching platform package
|
|
68
84
|
* (an optionalDependency such as @nanobpm/c8ctl-plugin-nano-darwin-arm64).
|
|
@@ -104,7 +120,7 @@ const UPDATE_CACHE_FILE = 'update-check.json';
|
|
|
104
120
|
const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
|
|
105
121
|
const UPDATE_NOTIFY_TTL_MS = 24 * 60 * 60 * 1000;
|
|
106
122
|
|
|
107
|
-
// ProcessOS is a closed
|
|
123
|
+
// ProcessOS is a closed beta distributed out-of-band: the binary lives in an
|
|
108
124
|
// S3 bucket whose base URL is handed to enabled users via PROCESSOS_DOWNLOAD_URL.
|
|
109
125
|
// `<base>/processos-<os>-<arch>[.exe]` is the per-platform binary and
|
|
110
126
|
// `<base>/version.json` is the {version,commit,updated} metadata the CI writes
|
|
@@ -307,6 +323,8 @@ function parseRequest(args, flags) {
|
|
|
307
323
|
purge: Boolean(flags?.purge),
|
|
308
324
|
force: Boolean(flags?.force),
|
|
309
325
|
capture: Boolean(flags?.capture),
|
|
326
|
+
inMemory: Boolean(flags?.['in-memory'] || flags?.['no-journal']),
|
|
327
|
+
historyMax: intFlag('history-max'),
|
|
310
328
|
workspace: Boolean(flags?.workspace),
|
|
311
329
|
check: Boolean(flags?.check),
|
|
312
330
|
binary: flags?.binary,
|
|
@@ -481,6 +499,8 @@ async function startCluster(req) {
|
|
|
481
499
|
// Raft is required for replication; auto-enable when RF > 1, allow override.
|
|
482
500
|
const raft = req.raft === undefined ? rf > 1 : Boolean(req.raft);
|
|
483
501
|
const capture = Boolean(req.capture);
|
|
502
|
+
const inMemory = Boolean(req.inMemory);
|
|
503
|
+
const historyMax = req.historyMax;
|
|
484
504
|
|
|
485
505
|
if (partitions < nodeCount) {
|
|
486
506
|
logger.warn(
|
|
@@ -491,6 +511,21 @@ async function startCluster(req) {
|
|
|
491
511
|
if (req.rf && req.rf > nodeCount) {
|
|
492
512
|
logger.warn(`--rf ${req.rf} clamped to node count (${nodeCount}).`);
|
|
493
513
|
}
|
|
514
|
+
if (inMemory) {
|
|
515
|
+
logger.warn(
|
|
516
|
+
'In-memory mode: no journal or read-model is written to disk. Engine state is ' +
|
|
517
|
+
'lost on stop/restart, and every retained instance lives in RAM' +
|
|
518
|
+
(historyMax === undefined
|
|
519
|
+
? ' — pair with --history-max <N> to bound RAM under sustained load.'
|
|
520
|
+
: '.'),
|
|
521
|
+
);
|
|
522
|
+
if (raft || rf > 1) {
|
|
523
|
+
logger.warn(
|
|
524
|
+
'In-memory mode with Raft/replication: replicated logs are not persisted; ' +
|
|
525
|
+
'a restarted node recovers nothing.',
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
494
529
|
|
|
495
530
|
const binary = findBinary(req);
|
|
496
531
|
|
|
@@ -533,7 +568,9 @@ async function startCluster(req) {
|
|
|
533
568
|
|
|
534
569
|
logger.info(
|
|
535
570
|
`Starting Nano BPM cluster: ${nodeCount} node(s), ${partitions} partition(s), ` +
|
|
536
|
-
`RF=${rf}${raft ? ', Raft on' : ''}${capture ? ', trace capture on' : ''}
|
|
571
|
+
`RF=${rf}${raft ? ', Raft on' : ''}${capture ? ', trace capture on' : ''}` +
|
|
572
|
+
`${inMemory ? ', in-memory (no disk)' : ''}` +
|
|
573
|
+
`${historyMax !== undefined ? `, history-max=${historyMax}` : ''}`,
|
|
537
574
|
);
|
|
538
575
|
logger.info(`Binary: ${binary}`);
|
|
539
576
|
logger.info(`Workspace: ${workspaceDir} (models/, workers/)`);
|
|
@@ -543,7 +580,7 @@ async function startCluster(req) {
|
|
|
543
580
|
const port = ports[id];
|
|
544
581
|
const dataDir = join(getDataDir(), `node-${id}`);
|
|
545
582
|
const logFile = join(getLogDir(), `node-${id}.log`);
|
|
546
|
-
mkdirSync(dataDir, { recursive: true });
|
|
583
|
+
if (!inMemory) mkdirSync(dataDir, { recursive: true });
|
|
547
584
|
|
|
548
585
|
const env = {
|
|
549
586
|
...process.env,
|
|
@@ -552,7 +589,6 @@ async function startCluster(req) {
|
|
|
552
589
|
NANOBPMN_NODES: nodesEnv,
|
|
553
590
|
NANOBPMN_PARTITIONS: String(partitions),
|
|
554
591
|
NANOBPMN_RF: String(rf),
|
|
555
|
-
NANOBPMN_DATA_DIR: dataDir,
|
|
556
592
|
// Default to async durability (group-commit) for throughput; the user can
|
|
557
593
|
// override per the spread of process.env above by exporting
|
|
558
594
|
// NANOBPMN_DURABILITY (e.g. "sync") before running.
|
|
@@ -569,6 +605,22 @@ async function startCluster(req) {
|
|
|
569
605
|
// outside the per-node data dir so "nano clean" never wipes it.
|
|
570
606
|
NANOBPMN_WORKSPACE_DIR: workspaceDir,
|
|
571
607
|
};
|
|
608
|
+
// Storage axis: an on-disk journal + read-model under the per-node data dir
|
|
609
|
+
// (default), or a fully in-memory engine (in-memory journal + :memory: read
|
|
610
|
+
// store) when --in-memory is set. In in-memory mode, scrub any inherited
|
|
611
|
+
// path vars so nothing leaks back to disk.
|
|
612
|
+
if (inMemory) {
|
|
613
|
+
delete env.NANOBPMN_DATA_DIR;
|
|
614
|
+
delete env.NANOBPMN_JOURNAL;
|
|
615
|
+
delete env.NANOBPMN_READ_DB;
|
|
616
|
+
} else {
|
|
617
|
+
env.NANOBPMN_DATA_DIR = dataDir;
|
|
618
|
+
}
|
|
619
|
+
// Bound retained terminal instances in the read model when requested. Works
|
|
620
|
+
// in both storage modes (caps disk growth on-disk; caps RAM in-memory).
|
|
621
|
+
if (historyMax !== undefined) {
|
|
622
|
+
env.NANOBPMN_HISTORY_MAX_INSTANCES = String(historyMax);
|
|
623
|
+
}
|
|
572
624
|
if (raft) env.NANOBPMN_RAFT = '1';
|
|
573
625
|
// Trace capture: a single flag enables the Tier 2 recorded-input (stimuli)
|
|
574
626
|
// log AND auto-enables Tier 1 variable capture, so historical replay /
|
|
@@ -599,7 +651,7 @@ async function startCluster(req) {
|
|
|
599
651
|
process.exit(1);
|
|
600
652
|
}
|
|
601
653
|
|
|
602
|
-
nodes.push({ id, port, pid: child.pid, url: peers[id], dataDir, logFile });
|
|
654
|
+
nodes.push({ id, port, pid: child.pid, url: peers[id], dataDir: inMemory ? null : dataDir, logFile });
|
|
603
655
|
logger.info(` node ${id}: pid ${child.pid} → ${peers[id]} (log: ${logFile})`);
|
|
604
656
|
}
|
|
605
657
|
|
|
@@ -612,6 +664,8 @@ async function startCluster(req) {
|
|
|
612
664
|
rf,
|
|
613
665
|
raft,
|
|
614
666
|
capture,
|
|
667
|
+
inMemory,
|
|
668
|
+
historyMax: historyMax ?? null,
|
|
615
669
|
basePort,
|
|
616
670
|
nodes,
|
|
617
671
|
};
|
|
@@ -648,7 +702,7 @@ async function printSummary(state) {
|
|
|
648
702
|
console.log('');
|
|
649
703
|
console.log(
|
|
650
704
|
`Nano BPM cluster is up: ${state.nodes.length} node(s), ${state.partitions} partition(s), ` +
|
|
651
|
-
`RF=${state.rf}${state.raft ? ', Raft on' : ''}`,
|
|
705
|
+
`RF=${state.rf}${state.raft ? ', Raft on' : ''}${state.inMemory ? ', in-memory (no disk)' : ''}`,
|
|
652
706
|
);
|
|
653
707
|
console.log('');
|
|
654
708
|
for (const n of state.nodes) {
|
|
@@ -855,8 +909,16 @@ async function statusCluster(req) {
|
|
|
855
909
|
`${state.raft ? ' raft: on' : ''}${state.capture ? ' trace capture: on' : ''}`,
|
|
856
910
|
);
|
|
857
911
|
console.log(` binary: ${state.binary}`);
|
|
912
|
+
console.log(` version: ${binaryVersion(state.binary) ?? 'unknown'}`);
|
|
858
913
|
console.log(` workspace: ${state.workspaceDir || getWorkspaceDir()}`);
|
|
859
|
-
|
|
914
|
+
const historyNote =
|
|
915
|
+
state.historyMax != null ? `, history-max ${state.historyMax}` : '';
|
|
916
|
+
if (state.inMemory) {
|
|
917
|
+
console.log(` storage: in-memory (no journal/read-model on disk${historyNote})`);
|
|
918
|
+
} else {
|
|
919
|
+
console.log(` storage: on-disk${historyNote}`);
|
|
920
|
+
console.log(` data: ${getDataDir()}`);
|
|
921
|
+
}
|
|
860
922
|
console.log('');
|
|
861
923
|
console.log(' NODE PORT PID PROCESS HEALTH URL');
|
|
862
924
|
for (const c of checks) {
|
|
@@ -1186,8 +1248,22 @@ function isGlobalInstall() {
|
|
|
1186
1248
|
|
|
1187
1249
|
function updatePlugin(req) {
|
|
1188
1250
|
const { name, version: current } = pluginPackage();
|
|
1251
|
+
|
|
1252
|
+
// The nano server binary ships with the plugin as its platform package
|
|
1253
|
+
// (an optionalDependency pinned to the plugin version), so a plugin update is
|
|
1254
|
+
// what delivers a new server. Surface the resolved binary's version, and flag
|
|
1255
|
+
// it when the platform package isn't installed for this host.
|
|
1256
|
+
let nanoBin = null;
|
|
1257
|
+
try {
|
|
1258
|
+
nanoBin = findBinary({});
|
|
1259
|
+
} catch {
|
|
1260
|
+
nanoBin = null;
|
|
1261
|
+
}
|
|
1189
1262
|
const bundled = readBundledBinaryInfo();
|
|
1190
|
-
const
|
|
1263
|
+
const nanoVer = nanoBin ? binaryVersion(nanoBin) : null;
|
|
1264
|
+
const nanoNote = nanoBin
|
|
1265
|
+
? ` (nano server ${nanoVer ?? bundled?.version ?? 'present'})`
|
|
1266
|
+
: ' (nano server: not installed for this platform)';
|
|
1191
1267
|
const manual = ` npm install -g ${name}@latest`;
|
|
1192
1268
|
|
|
1193
1269
|
console.log(`Installed: ${name} v${current ?? '?'}${nanoNote}`);
|
|
@@ -1205,6 +1281,13 @@ function updatePlugin(req) {
|
|
|
1205
1281
|
console.log('');
|
|
1206
1282
|
|
|
1207
1283
|
if (current && compareSemver(current, latest) >= 0) {
|
|
1284
|
+
if (!nanoBin) {
|
|
1285
|
+
// Plugin is current but npm never fetched the matching server binary.
|
|
1286
|
+
console.log('Plugin is current, but the nano server binary is not installed for this platform.');
|
|
1287
|
+
console.log('Provision it by reinstalling the plugin so npm fetches the platform package:');
|
|
1288
|
+
console.log(' c8ctl sync plugin');
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1208
1291
|
console.log('Already on the latest release — nothing to do.');
|
|
1209
1292
|
return;
|
|
1210
1293
|
}
|
|
@@ -1401,7 +1484,7 @@ function getProcessosNanoUrl() {
|
|
|
1401
1484
|
}
|
|
1402
1485
|
|
|
1403
1486
|
/**
|
|
1404
|
-
* The closed-
|
|
1487
|
+
* The closed-beta download URL: env var (PROCESSOS_DOWNLOAD_URL) wins, then the
|
|
1405
1488
|
* persisted `processos set download-url` config value. Null when neither is set.
|
|
1406
1489
|
*/
|
|
1407
1490
|
function getProcessosDownloadUrl() {
|
|
@@ -1451,7 +1534,7 @@ function clearProcessosState() {
|
|
|
1451
1534
|
* only when an *explicitly* configured source points at a missing file (so the
|
|
1452
1535
|
* user gets an actionable error rather than a silent fallthrough).
|
|
1453
1536
|
*/
|
|
1454
|
-
function findConfiguredProcessosBinary(req) {
|
|
1537
|
+
function findConfiguredProcessosBinary(req, { includeCached = true } = {}) {
|
|
1455
1538
|
const cfg = readProcessosConfig();
|
|
1456
1539
|
const sources = [
|
|
1457
1540
|
{ val: req?.binary && String(req.binary), from: '--binary' },
|
|
@@ -1467,9 +1550,7 @@ function findConfiguredProcessosBinary(req) {
|
|
|
1467
1550
|
return abs;
|
|
1468
1551
|
}
|
|
1469
1552
|
|
|
1470
|
-
|
|
1471
|
-
if (existsSync(cached)) return cached;
|
|
1472
|
-
|
|
1553
|
+
// A local source build wins over a downloaded copy for developers in the repo.
|
|
1473
1554
|
let repo = null;
|
|
1474
1555
|
try {
|
|
1475
1556
|
repo = getRepoRoot();
|
|
@@ -1485,6 +1566,14 @@ function findConfiguredProcessosBinary(req) {
|
|
|
1485
1566
|
if (existsSync(c)) return c;
|
|
1486
1567
|
}
|
|
1487
1568
|
}
|
|
1569
|
+
|
|
1570
|
+
// The auto-downloaded copy. The resolver skips it (includeCached:false) so it
|
|
1571
|
+
// can manage that copy with a version check and re-fetch newer published
|
|
1572
|
+
// builds; all other callers still see it as the installed binary.
|
|
1573
|
+
if (includeCached) {
|
|
1574
|
+
const cached = getProcessosCachedBinaryPath();
|
|
1575
|
+
if (existsSync(cached)) return cached;
|
|
1576
|
+
}
|
|
1488
1577
|
return null;
|
|
1489
1578
|
}
|
|
1490
1579
|
|
|
@@ -1598,33 +1687,55 @@ async function downloadProcessosBinary(url, dest) {
|
|
|
1598
1687
|
* configured/local binary -> cached download -> fresh download -> error.
|
|
1599
1688
|
*/
|
|
1600
1689
|
async function resolveProcessosBinary(req) {
|
|
1601
|
-
|
|
1690
|
+
// An explicitly configured or local source build wins and is used as-is (no
|
|
1691
|
+
// auto-update). The auto-downloaded copy is handled below with a version
|
|
1692
|
+
// check so `start` can pull a newer published build.
|
|
1693
|
+
const configured = findConfiguredProcessosBinary(req, { includeCached: false });
|
|
1602
1694
|
if (configured) return configured;
|
|
1603
1695
|
|
|
1604
1696
|
const dlUrl = getProcessosDownloadUrl();
|
|
1697
|
+
const cached = getProcessosCachedBinaryPath();
|
|
1698
|
+
|
|
1605
1699
|
if (dlUrl) {
|
|
1606
|
-
const dest = getProcessosCachedBinaryPath();
|
|
1607
1700
|
const meta = await fetchProcessosVersionMeta(dlUrl);
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
})
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1701
|
+
const have = readProcessosBinaryMeta();
|
|
1702
|
+
const remoteVer = meta?.version ?? null;
|
|
1703
|
+
const haveVer = have?.version ?? null;
|
|
1704
|
+
const haveCached = existsSync(cached);
|
|
1705
|
+
|
|
1706
|
+
// Download when there is no cached copy, or when the published version.json
|
|
1707
|
+
// reports a version different from the one recorded for the cached copy.
|
|
1708
|
+
// This also covers binaries cached before version tracking (no haveVer).
|
|
1709
|
+
const needDownload = !haveCached || (remoteVer && remoteVer !== haveVer);
|
|
1710
|
+
if (needDownload) {
|
|
1711
|
+
const logger = getLogger();
|
|
1712
|
+
if (haveCached && remoteVer) {
|
|
1713
|
+
logger.info(`Updating ProcessOS ${haveVer ?? '?'} -> ${remoteVer} ...`);
|
|
1714
|
+
}
|
|
1715
|
+
await downloadProcessosBinary(processosBinaryUrl(dlUrl), cached);
|
|
1716
|
+
// Record what we fetched so the update notifier/status can compare later.
|
|
1717
|
+
try {
|
|
1718
|
+
mkdirSync(getProcessosBinDir(), { recursive: true });
|
|
1719
|
+
writeFileSync(
|
|
1720
|
+
getProcessosBinaryMetaPath(),
|
|
1721
|
+
JSON.stringify({
|
|
1722
|
+
version: meta?.version ?? null,
|
|
1723
|
+
commit: meta?.commit ?? null,
|
|
1724
|
+
updated: meta?.updated ?? null,
|
|
1725
|
+
source: processosDownloadBase(dlUrl),
|
|
1726
|
+
downloaded: new Date().toISOString(),
|
|
1727
|
+
}),
|
|
1728
|
+
);
|
|
1729
|
+
} catch {
|
|
1730
|
+
/* sidecar is best-effort */
|
|
1731
|
+
}
|
|
1624
1732
|
}
|
|
1625
|
-
return
|
|
1733
|
+
if (existsSync(cached)) return cached;
|
|
1626
1734
|
}
|
|
1627
1735
|
|
|
1736
|
+
// A previously downloaded copy still runs even if the URL is now unset.
|
|
1737
|
+
if (existsSync(cached)) return cached;
|
|
1738
|
+
|
|
1628
1739
|
throw new Error(
|
|
1629
1740
|
`Could not find or download the ProcessOS binary.\n` +
|
|
1630
1741
|
`Set the download URL you were given (PROCESSOS_DOWNLOAD_URL), point the plugin at a\n` +
|
|
@@ -1634,7 +1745,7 @@ async function resolveProcessosBinary(req) {
|
|
|
1634
1745
|
}
|
|
1635
1746
|
|
|
1636
1747
|
/**
|
|
1637
|
-
* Whether ProcessOS is enabled for this user. It is a closed
|
|
1748
|
+
* Whether ProcessOS is enabled for this user. It is a closed beta, so the
|
|
1638
1749
|
* operational commands stay locked until the user either has the binary on
|
|
1639
1750
|
* their system (configured path / cached download / local build) or has been
|
|
1640
1751
|
* given a PROCESSOS_DOWNLOAD_URL to fetch it from.
|
|
@@ -1645,16 +1756,16 @@ function processosEnabled(req) {
|
|
|
1645
1756
|
if (findConfiguredProcessosBinary(req)) return true;
|
|
1646
1757
|
} catch {
|
|
1647
1758
|
// A configured-but-missing path still means the user opted in; let the real
|
|
1648
|
-
// not-found error surface from the command rather than the closed-
|
|
1759
|
+
// not-found error surface from the command rather than the closed-beta gate.
|
|
1649
1760
|
return true;
|
|
1650
1761
|
}
|
|
1651
1762
|
return false;
|
|
1652
1763
|
}
|
|
1653
1764
|
|
|
1654
|
-
function
|
|
1765
|
+
function printProcessosClosedBeta() {
|
|
1655
1766
|
const logger = getLogger();
|
|
1656
1767
|
logger.error(
|
|
1657
|
-
'ProcessOS is in closed
|
|
1768
|
+
'ProcessOS is in closed beta and is not available yet.\n' +
|
|
1658
1769
|
'\n' +
|
|
1659
1770
|
'To enable it, set the download URL you were given by the Nano BPM team:\n' +
|
|
1660
1771
|
' c8ctl processos set download-url <url> # persists it for this machine\n' +
|
|
@@ -1707,17 +1818,7 @@ function getInstalledProcessosVersion(req) {
|
|
|
1707
1818
|
} catch {
|
|
1708
1819
|
binary = null;
|
|
1709
1820
|
}
|
|
1710
|
-
|
|
1711
|
-
try {
|
|
1712
|
-
const res = spawnSync(binary, ['--version'], { encoding: 'utf8', timeout: 3000 });
|
|
1713
|
-
if (res.status === 0) {
|
|
1714
|
-
const m = String(res.stdout || '').match(/(\d+\.\d+\.\d+[^\s]*)/);
|
|
1715
|
-
if (m) return m[1];
|
|
1716
|
-
}
|
|
1717
|
-
} catch {
|
|
1718
|
-
/* ignore */
|
|
1719
|
-
}
|
|
1720
|
-
return null;
|
|
1821
|
+
return binaryVersion(binary);
|
|
1721
1822
|
}
|
|
1722
1823
|
|
|
1723
1824
|
/**
|
|
@@ -1760,7 +1861,7 @@ function printProcessosUpdateNotice(current, latest) {
|
|
|
1760
1861
|
* Best-effort, non-blocking ProcessOS update check. Triggers a background
|
|
1761
1862
|
* version.json fetch when the cache is stale and prints a notice (at most once
|
|
1762
1863
|
* per day) when the published version is newer than the installed one. Only
|
|
1763
|
-
* meaningful when a download URL is configured (the closed-
|
|
1864
|
+
* meaningful when a download URL is configured (the closed-beta channel).
|
|
1764
1865
|
*/
|
|
1765
1866
|
function maybeNotifyProcessosUpdate(req) {
|
|
1766
1867
|
try {
|
|
@@ -1990,10 +2091,14 @@ async function statusProcessos() {
|
|
|
1990
2091
|
|
|
1991
2092
|
const alive = isPidAlive(state.pid);
|
|
1992
2093
|
const healthy = alive ? await probeProcessosHealthy(state.url) : false;
|
|
2094
|
+
// Prefer the actual running binary's reported version; fall back to the
|
|
2095
|
+
// recorded download metadata if the binary can't be probed.
|
|
2096
|
+
const version = binaryVersion(state.binary) ?? getInstalledProcessosVersion() ?? 'unknown';
|
|
1993
2097
|
|
|
1994
2098
|
console.log('ProcessOS status:');
|
|
1995
2099
|
console.log('');
|
|
1996
2100
|
console.log(` pid: ${state.pid} ${alive ? '(alive)' : '(dead — stale state)'}`);
|
|
2101
|
+
console.log(` version: ${version}`);
|
|
1997
2102
|
console.log(` url: ${state.url}`);
|
|
1998
2103
|
console.log(` health: ${healthy ? 'ok' : 'unreachable'} (${state.url}/health)`);
|
|
1999
2104
|
console.log(` target: ${state.nanoUrl}`);
|
|
@@ -2055,7 +2160,7 @@ function printProcessosSetUsage() {
|
|
|
2055
2160
|
const logger = getLogger();
|
|
2056
2161
|
logger.info('Usage: c8ctl processos set <field> <value>');
|
|
2057
2162
|
logger.info(' bin <path> Path to the downloaded ProcessOS binary');
|
|
2058
|
-
logger.info(' download-url <url> Closed-
|
|
2163
|
+
logger.info(' download-url <url> Closed-beta binary download URL (enables ProcessOS)');
|
|
2059
2164
|
logger.info(' port <n> Listen port (default 8090)');
|
|
2060
2165
|
logger.info(' nano-url <url> Target Nano BPM engine URL (default http://localhost:8080)');
|
|
2061
2166
|
logger.info(' data-dir <path> ProcessOS data directory');
|
|
@@ -2170,14 +2275,14 @@ function showProcessosConfig() {
|
|
|
2170
2275
|
}
|
|
2171
2276
|
}
|
|
2172
2277
|
console.log('');
|
|
2173
|
-
console.log(' closed-
|
|
2278
|
+
console.log(' closed-beta channel:');
|
|
2174
2279
|
const dlUrl = getProcessosDownloadUrl();
|
|
2175
2280
|
const dlSource = process.env.PROCESSOS_DOWNLOAD_URL && String(process.env.PROCESSOS_DOWNLOAD_URL).trim()
|
|
2176
2281
|
? ' (from $PROCESSOS_DOWNLOAD_URL)'
|
|
2177
2282
|
: cfg.downloadUrl
|
|
2178
2283
|
? ' (from "processos set download-url")'
|
|
2179
2284
|
: '';
|
|
2180
|
-
console.log(` download url ${dlUrl ? dlUrl + dlSource : '(not set — ProcessOS is a closed
|
|
2285
|
+
console.log(` download url ${dlUrl ? dlUrl + dlSource : '(not set — ProcessOS is a closed beta; "c8ctl processos set download-url <url>" to enable)'}`);
|
|
2181
2286
|
const cached = getProcessosCachedBinaryPath();
|
|
2182
2287
|
const meta = readProcessosBinaryMeta();
|
|
2183
2288
|
console.log(` cached binary ${existsSync(cached) ? cached : '(none — downloaded on first "processos start")'}`);
|
|
@@ -2203,7 +2308,7 @@ function printProcessosUsage() {
|
|
|
2203
2308
|
console.log(' c8ctl processos set bin <path> | download-url <url> | port <n> | nano-url <url> | data-dir <path> | env KEY=VALUE');
|
|
2204
2309
|
console.log(' c8ctl processos config');
|
|
2205
2310
|
console.log('');
|
|
2206
|
-
console.log('ProcessOS is a closed
|
|
2311
|
+
console.log('ProcessOS is a closed beta. Enable it with the download URL you were given:');
|
|
2207
2312
|
console.log(' c8ctl processos set download-url <url> # plugin downloads + runs the matching binary');
|
|
2208
2313
|
console.log('or point the plugin at a binary you already have: "c8ctl processos set bin <path>".');
|
|
2209
2314
|
console.log('By default ProcessOS spawns its own internal pilot Nano engine (the plugin auto-wires the nano');
|
|
@@ -2252,6 +2357,7 @@ export const metadata = {
|
|
|
2252
2357
|
},
|
|
2253
2358
|
{ command: 'c8ctl nano start 3 --port 9000', description: 'Start 3 nodes on ports 9000..9002' },
|
|
2254
2359
|
{ command: 'c8ctl nano start --capture', description: 'Start with trace capture for historical replay/analysis' },
|
|
2360
|
+
{ command: 'c8ctl nano start --in-memory --history-max 50000', description: 'Stress mode: no disk journal, cap retained instances in RAM' },
|
|
2255
2361
|
{ command: 'c8ctl nano status', description: 'Show cluster status and per-node health' },
|
|
2256
2362
|
{ command: 'c8ctl nano pause 1', description: 'Freeze node 1 (SIGSTOP) to simulate a node failure' },
|
|
2257
2363
|
{ command: 'c8ctl nano resume 1', description: 'Resume node 1 (SIGCONT) to bring it back online' },
|
|
@@ -2269,7 +2375,7 @@ export const metadata = {
|
|
|
2269
2375
|
processos: {
|
|
2270
2376
|
description: 'Manage a local ProcessOS instance — start, status, stop, logs, config',
|
|
2271
2377
|
examples: [
|
|
2272
|
-
{ command: 'c8ctl processos set download-url <url>', description: 'Enable the closed
|
|
2378
|
+
{ command: 'c8ctl processos set download-url <url>', description: 'Enable the closed beta + auto-download the matching binary' },
|
|
2273
2379
|
{ command: 'c8ctl processos set bin <path>', description: 'Point the plugin at a ProcessOS binary you already have' },
|
|
2274
2380
|
{ command: 'c8ctl processos start', description: 'Start ProcessOS against the local Nano BPM engine' },
|
|
2275
2381
|
{ command: 'c8ctl processos start --nano-url http://localhost:8080', description: 'Start against a specific engine' },
|
|
@@ -2294,6 +2400,9 @@ export const commands = {
|
|
|
2294
2400
|
rf: { type: 'string', description: 'Replication factor; >1 enables Raft (default 1)' },
|
|
2295
2401
|
raft: { type: 'boolean', description: 'Force per-partition Raft on/off (default: on when rf>1)' },
|
|
2296
2402
|
capture: { type: 'boolean', description: 'start: enable trace capture (recorded-input replay) on every node' },
|
|
2403
|
+
'in-memory': { type: 'boolean', description: 'start: run with NO on-disk journal/read-model (in-memory engine; state lost on restart). Alias: --no-journal' },
|
|
2404
|
+
'no-journal': { type: 'boolean', description: 'start: alias for --in-memory' },
|
|
2405
|
+
'history-max': { type: 'string', description: 'start: cap retained terminal instances in the read model (NANOBPMN_HISTORY_MAX_INSTANCES; 0/unset = unbounded)' },
|
|
2297
2406
|
follow: { type: 'boolean', description: 'logs: stream output (tail -F)', short: 'f' },
|
|
2298
2407
|
purge: { type: 'boolean', description: 'stop: also delete per-node engine data' },
|
|
2299
2408
|
force: { type: 'boolean', description: 'start: stop any existing cluster first' },
|
|
@@ -2376,12 +2485,12 @@ export const commands = {
|
|
|
2376
2485
|
return;
|
|
2377
2486
|
}
|
|
2378
2487
|
|
|
2379
|
-
// ProcessOS is a closed
|
|
2488
|
+
// ProcessOS is a closed beta: gate the operational commands until the
|
|
2380
2489
|
// user has opted in (download URL set or a binary on their system).
|
|
2381
2490
|
// `set`/`config` stay open so users can configure/inspect at any time.
|
|
2382
2491
|
const ungated = req.subcommand === 'set' || req.subcommand === 'config';
|
|
2383
2492
|
if (!ungated && !processosEnabled(req)) {
|
|
2384
|
-
|
|
2493
|
+
printProcessosClosedBeta();
|
|
2385
2494
|
process.exit(1);
|
|
2386
2495
|
}
|
|
2387
2496
|
|
|
@@ -2425,7 +2534,7 @@ export const commands = {
|
|
|
2425
2534
|
|
|
2426
2535
|
function printUsage() {
|
|
2427
2536
|
console.log('Usage:');
|
|
2428
|
-
console.log(' c8ctl nano start [<nodes>] [--port <basePort>] [--partitions <n>] [--rf <n>] [--raft] [--capture] [--binary <path>]');
|
|
2537
|
+
console.log(' c8ctl nano start [<nodes>] [--port <basePort>] [--partitions <n>] [--rf <n>] [--raft] [--capture] [--in-memory] [--history-max <n>] [--binary <path>]');
|
|
2429
2538
|
console.log(' c8ctl nano status [--port <port>]');
|
|
2430
2539
|
console.log(' c8ctl nano stop [--purge]');
|
|
2431
2540
|
console.log(' c8ctl nano logs [<nodeId>] [--follow]');
|
|
@@ -2457,6 +2566,8 @@ function printUsage() {
|
|
|
2457
2566
|
console.log(' --rf <n> Replication factor; >1 enables Raft (default 1)');
|
|
2458
2567
|
console.log(' --raft Force Raft on (default: on iff rf>1)');
|
|
2459
2568
|
console.log(' --capture start: enable trace capture (recorded-input replay) on every node');
|
|
2569
|
+
console.log(' --in-memory start: run with NO on-disk journal/read-model (alias --no-journal; state lost on restart)');
|
|
2570
|
+
console.log(' --history-max <n> start: cap retained terminal instances in the read model (0/unset = unbounded)');
|
|
2460
2571
|
console.log(' --binary <path> Path to the nanobpmn server binary (overrides "set bin")');
|
|
2461
2572
|
console.log(' --purge stop: also delete per-node engine data');
|
|
2462
2573
|
console.log(' --force start: stop any existing cluster first');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c8ctl-plugin-nano",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "c8ctl plugin to start, inspect, and stop a local Nano BPM (nanobpmn) cluster",
|
|
6
6
|
"main": "c8ctl-plugin.js",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"semantic-release": "^25.0.3"
|
|
50
50
|
},
|
|
51
51
|
"optionalDependencies": {
|
|
52
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.
|
|
53
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.
|
|
54
|
-
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.
|
|
55
|
-
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.
|
|
56
|
-
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.
|
|
52
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.4.0",
|
|
53
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.4.0",
|
|
54
|
+
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.4.0",
|
|
55
|
+
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.4.0",
|
|
56
|
+
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.4.0"
|
|
57
57
|
}
|
|
58
58
|
}
|