@constellation-network/node-pilot 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.
Files changed (90) hide show
  1. package/README.md +26 -15
  2. package/bin/dev.js +1 -6
  3. package/bin/run.js +1 -1
  4. package/dist/base-command.d.ts +9 -0
  5. package/dist/base-command.js +20 -0
  6. package/dist/checks/check-hardware.js +3 -3
  7. package/dist/checks/check-initial-setup.js +2 -0
  8. package/dist/checks/check-layers.js +7 -7
  9. package/dist/checks/check-network.d.ts +2 -0
  10. package/dist/checks/check-network.js +46 -11
  11. package/dist/checks/check-node-ctl.js +4 -4
  12. package/dist/checks/check-project.d.ts +1 -0
  13. package/dist/checks/check-project.js +15 -4
  14. package/dist/checks/check-wallet.d.ts +3 -0
  15. package/dist/checks/check-wallet.js +37 -0
  16. package/dist/clm.d.ts +1 -0
  17. package/dist/clm.js +3 -0
  18. package/dist/commands/config/get.d.ts +6 -0
  19. package/dist/commands/config/get.js +57 -11
  20. package/dist/commands/config/set.d.ts +0 -1
  21. package/dist/commands/config/set.js +13 -11
  22. package/dist/commands/config.js +17 -22
  23. package/dist/commands/info.js +3 -2
  24. package/dist/commands/logs.d.ts +1 -1
  25. package/dist/commands/logs.js +7 -3
  26. package/dist/commands/restart.d.ts +10 -2
  27. package/dist/commands/restart.js +65 -9
  28. package/dist/commands/shutdown.js +3 -3
  29. package/dist/commands/status.js +4 -0
  30. package/dist/commands/test.js +10 -3
  31. package/dist/config-store.d.ts +47 -31
  32. package/dist/config-store.js +98 -42
  33. package/dist/helpers/config-helper.js +2 -2
  34. package/dist/helpers/env-templates.d.ts +4 -3
  35. package/dist/helpers/env-templates.js +28 -20
  36. package/dist/helpers/key-file-helper.d.ts +2 -0
  37. package/dist/helpers/key-file-helper.js +51 -16
  38. package/dist/helpers/project-helper.d.ts +2 -2
  39. package/dist/helpers/project-helper.js +37 -38
  40. package/dist/helpers/prompt-helper.d.ts +0 -1
  41. package/dist/helpers/prompt-helper.js +15 -15
  42. package/dist/services/archiver-service.d.ts +17 -0
  43. package/dist/services/archiver-service.js +104 -0
  44. package/dist/services/cluster-service.d.ts +10 -6
  45. package/dist/services/cluster-service.js +45 -45
  46. package/dist/services/docker-service.d.ts +9 -0
  47. package/dist/{helpers/docker-helper.js → services/docker-service.js} +11 -9
  48. package/dist/services/fastforward-service.js +3 -3
  49. package/dist/services/get-random-node.js +1 -1
  50. package/dist/{helpers/github-helper.d.ts → services/github-service.d.ts} +1 -1
  51. package/dist/{helpers/github-helper.js → services/github-service.js} +1 -1
  52. package/dist/services/node-service.js +14 -14
  53. package/dist/services/notify-service.d.ts +1 -0
  54. package/dist/services/notify-service.js +1 -0
  55. package/dist/services/systemd-service.d.ts +3 -0
  56. package/dist/services/systemd-service.js +45 -0
  57. package/dist/test.d.ts +1 -0
  58. package/dist/test.js +50 -0
  59. package/dist/types.d.ts +6 -0
  60. package/install-dependencies.sh +0 -2
  61. package/oclif.manifest.json +33 -4
  62. package/package.json +9 -8
  63. package/projects/custom/pilot.json +9 -0
  64. package/projects/hypergraph/Dockerfile +24 -18
  65. package/projects/hypergraph/docker-compose.yml +14 -14
  66. package/projects/hypergraph/networks/integrationnet/gl0.env +4 -0
  67. package/projects/hypergraph/networks/integrationnet/gl1.env +4 -0
  68. package/projects/hypergraph/networks/integrationnet/network.env +8 -0
  69. package/projects/hypergraph/networks/{integrationnet.env → integrationnet/source-nodes.env} +1 -9
  70. package/projects/hypergraph/networks/mainnet/gl0.env +4 -0
  71. package/projects/hypergraph/networks/mainnet/gl1.env +4 -0
  72. package/projects/hypergraph/networks/mainnet/network.env +8 -0
  73. package/projects/hypergraph/networks/{mainnet.env → mainnet/source-nodes.env} +0 -8
  74. package/projects/hypergraph/networks/testnet/gl0.env +5 -0
  75. package/projects/hypergraph/networks/testnet/gl1.env +4 -0
  76. package/projects/hypergraph/networks/testnet/network.env +8 -0
  77. package/projects/hypergraph/networks/{testnet.env → testnet/source-nodes.env} +0 -8
  78. package/projects/hypergraph/scripts/check-version.sh +31 -0
  79. package/projects/hypergraph/scripts/install.sh +30 -25
  80. package/projects/hypergraph/seedlist +268 -0
  81. package/scripts/autoheal.sh +8 -0
  82. package/scripts/restart_logger.sh +3 -0
  83. package/scripts/services/io.constellationnetwork.nodepilot.Updater.plist +16 -0
  84. package/scripts/services/node-pilot-autoheal.service +12 -0
  85. package/scripts/services/node-pilot-restarter.service +11 -0
  86. package/scripts/services/node-pilot-updater.service +13 -0
  87. package/scripts/update_logger.sh +3 -0
  88. package/dist/helpers/docker-helper.d.ts +0 -7
  89. package/projects/hypergraph/layers/gl1.env +0 -3
  90. package/projects/scripts/docker-cleanup.sh +0 -64
@@ -3,22 +3,16 @@ import chalk from "chalk";
3
3
  import { clm } from "../clm.js";
4
4
  import { configStore } from "../config-store.js";
5
5
  export const promptHelper = {
6
- async configureAutoRestart() {
7
- const answer = await input({
8
- default: 'y',
9
- message: 'Do you want to enable auto-restart? (y/n): '
10
- });
11
- configStore.setProjectInfo({ autoRestart: answer === 'y' });
12
- },
13
6
  async configureJavaMemoryArguments() {
14
7
  const { memory } = configStore.getSystemInfo();
15
8
  const { layersToRun, name } = configStore.getProjectInfo();
9
+ const { type: currentNetwork } = configStore.getNetworkInfo();
16
10
  const xmx = Number(memory);
17
11
  if (xmx === 8 && layersToRun.length > 1) {
18
12
  clm.warn('Minimum 8GB memory detected. Only a single layer will be allowed to run');
19
13
  await promptHelper.doYouWishToContinue();
20
14
  configStore.setProjectInfo({ layersToRun: [layersToRun[0]] });
21
- configStore.setEnvLayerInfo(layersToRun[0], { CL_DOCKER_JAVA_OPTS: '-Xms1024M -Xmx7G -Xss256K' });
15
+ configStore.setEnvLayerInfo(currentNetwork, layersToRun[0], { CL_DOCKER_JAVA_OPTS: '-Xms1024M -Xmx7G -Xss256K' });
22
16
  }
23
17
  else if (name === 'hypergraph') {
24
18
  // prompt to use all detected memory
@@ -31,11 +25,16 @@ export const promptHelper = {
31
25
  answer--;
32
26
  const subLayerMem = layersToRun.length > 1 ? Math.floor(answer / 3) : 0;
33
27
  const mainLayerMem = answer - subLayerMem;
34
- clm.postStep(`${layersToRun[0]} memory allocation: ${mainLayerMem}GB`);
35
- configStore.setEnvLayerInfo(layersToRun[0], { CL_DOCKER_JAVA_OPTS: `-Xms1024M -Xmx${mainLayerMem}G -Xss256K` });
36
- if (subLayerMem) {
37
- clm.postStep(`${layersToRun[1]} memory allocation: ${subLayerMem}GB`);
38
- configStore.setEnvLayerInfo(layersToRun[1], { CL_DOCKER_JAVA_OPTS: `-Xms1024M -Xmx${subLayerMem}G -Xss256K` });
28
+ const { supportedTypes } = configStore.getNetworkInfo();
29
+ for (const type of supportedTypes) {
30
+ const network = type.toUpperCase();
31
+ const logMethod = type === currentNetwork ? clm.postStep : clm.debug;
32
+ logMethod(`${network}:: ${layersToRun[0]} memory allocation: ${mainLayerMem}GB`);
33
+ configStore.setEnvLayerInfo(type, layersToRun[0], { CL_DOCKER_JAVA_OPTS: `-Xms1024M -Xmx${mainLayerMem}G -Xss256K` });
34
+ if (subLayerMem) {
35
+ logMethod(`${network}:: ${layersToRun[1]} memory allocation: ${subLayerMem}GB`);
36
+ configStore.setEnvLayerInfo(type, layersToRun[1], { CL_DOCKER_JAVA_OPTS: `-Xms1024M -Xmx${subLayerMem}G -Xss256K` });
37
+ }
39
38
  }
40
39
  }
41
40
  },
@@ -67,7 +66,7 @@ export const promptHelper = {
67
66
  }
68
67
  if (supportedTypes.length === 1) {
69
68
  configStore.setNetworkInfo({ type: supportedTypes[0], version: "latest" });
70
- configStore.setEnvCommonInfo(configStore.getNetworkEnvInfo(supportedTypes[0]));
69
+ // configStore.setEnvNetworkInfo(configStore.getNetworkEnvInfo(supportedTypes[0]));
71
70
  return;
72
71
  }
73
72
  const networkType = await select({
@@ -79,6 +78,7 @@ export const promptHelper = {
79
78
  message: 'Select network type:'
80
79
  });
81
80
  configStore.setNetworkInfo({ type: networkType, version: "latest" });
82
- configStore.setEnvCommonInfo(configStore.getNetworkEnvInfo(networkType));
81
+ configStore.setProjectFlag('duplicateNodeIdChecked', false);
82
+ configStore.setProjectFlag('seedListChecked', false);
83
83
  }
84
84
  };
@@ -0,0 +1,17 @@
1
+ export declare const archiverService: {
2
+ checkLogsForMissingSnapshots(): Promise<void>;
3
+ getArchiveSnapshotInfo(): Promise<{
4
+ clusterOrdinal: number;
5
+ distance: number;
6
+ endOrdinal: number;
7
+ startOrdinal: number;
8
+ total: number;
9
+ url: string;
10
+ }>;
11
+ getDownloadedSnapshotRange(): {
12
+ chunkOrdinal: number;
13
+ endOrdinal: number;
14
+ startOrdinal: number;
15
+ };
16
+ syncToLatestSnapshot(): Promise<void>;
17
+ };
@@ -0,0 +1,104 @@
1
+ import chalk from "chalk";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { clm } from "../clm.js";
5
+ import { configStore } from "../config-store.js";
6
+ import { clusterService } from "./cluster-service.js";
7
+ import { FastforwardService } from "./fastforward-service.js";
8
+ import { shellService } from "./shell-service.js";
9
+ // http://5.161.243.241:7777/hash.txt
10
+ const remoteIndexMap = {
11
+ integrationnet: "http://5.161.243.241:7777",
12
+ mainnet: "http://128.140.33.142:7777",
13
+ testnet: "http://65.108.87.84:7777"
14
+ };
15
+ export const archiverService = {
16
+ async checkLogsForMissingSnapshots() {
17
+ const { projectDir } = configStore.getProjectInfo();
18
+ const dataDir = path.join(projectDir, 'app-data', 'gl0-logs', 'app.log');
19
+ const result = await shellService.runCommandWithOutput(`grep -i 'Global snapshot not found for ordinal' ${dataDir}`).catch(() => '');
20
+ let oldestOrdinal = Number.MAX_SAFE_INTEGER;
21
+ for (const line of result.split('\n')) {
22
+ const number = line.match(/\d+/);
23
+ if (number) {
24
+ oldestOrdinal = Math.min(oldestOrdinal, Number(number[0]));
25
+ }
26
+ }
27
+ console.log(`Oldest ordinal: ${oldestOrdinal}`);
28
+ },
29
+ async getArchiveSnapshotInfo() {
30
+ const { type } = configStore.getNetworkInfo();
31
+ const clusterOrdinal = await clusterService.getSourceNodeLatestOrdinal('gl0');
32
+ return fetch(remoteIndexMap[type] + '/hash.txt')
33
+ .then(res => res.text())
34
+ .then(txt => {
35
+ const lines = txt.trim().split('\n');
36
+ const lastLine = lines.at(-1);
37
+ const filename = lastLine.split(' ')[1];
38
+ const parseName = filename.split('.')[0].split('-');
39
+ const startOrdinal = Number(parseName.at(1)?.slice(1));
40
+ const endOrdinal = parseName.length < 4 ? startOrdinal + 20_000 - 1 : Number(parseName.at(3)?.slice(1));
41
+ const distance = clusterOrdinal - endOrdinal;
42
+ const total = endOrdinal - startOrdinal + 1;
43
+ clm.debug(`Cluster Ordinal: ${chalk.yellow(clusterOrdinal)}, Archive End Ordinal: ${chalk.yellow(endOrdinal)}, Total Archive Snapshots: ${chalk.yellow(total)}, Distance: ${chalk.yellow(distance)}`);
44
+ return { clusterOrdinal, distance, endOrdinal, startOrdinal, total, url: remoteIndexMap[type] + '/' + filename };
45
+ });
46
+ },
47
+ getDownloadedSnapshotRange() {
48
+ const { projectDir } = configStore.getProjectInfo();
49
+ const dataDir = path.join(projectDir, 'app-data', 'gl0-data', 'incremental_snapshot', 'ordinal');
50
+ const result = { chunkOrdinal: 0, endOrdinal: 0, startOrdinal: 0 };
51
+ if (!fs.existsSync(dataDir)) {
52
+ return result;
53
+ }
54
+ // get last filename in directory
55
+ let files = fs.readdirSync(dataDir);
56
+ if (files.length === 0)
57
+ return result;
58
+ const latestChunk = files.sort().at(-1);
59
+ if (!latestChunk)
60
+ return result;
61
+ result.chunkOrdinal = Number(latestChunk);
62
+ files = fs.readdirSync(path.join(dataDir, latestChunk));
63
+ if (files.length === 0)
64
+ return result;
65
+ const filesSorted = files.sort();
66
+ const firstFile = filesSorted.at(0);
67
+ const lastFile = filesSorted.at(-1);
68
+ result.startOrdinal = Number(firstFile);
69
+ result.endOrdinal = Number(lastFile);
70
+ return result;
71
+ },
72
+ async syncToLatestSnapshot() {
73
+ const { clusterOrdinal, distance: archiveDistance, endOrdinal: remoteArchiveEndOrdinal, startOrdinal: remoteArchiveStartOrdinal, total, url } = await this.getArchiveSnapshotInfo();
74
+ if (archiveDistance > 1000) {
75
+ clm.preStep('Archive is far behind cluster. Initiating fast forward...');
76
+ await FastforwardService.synctoLatestSnapshot();
77
+ return;
78
+ }
79
+ const { endOrdinal: localEndOrdinal, startOrdinal: localStartOrdinal } = this.getDownloadedSnapshotRange();
80
+ const localDistanceFromCluster = clusterOrdinal - localEndOrdinal;
81
+ // if archive can improve local's snapshot range
82
+ const needToSync = remoteArchiveStartOrdinal < localStartOrdinal || remoteArchiveEndOrdinal > localEndOrdinal;
83
+ if (!needToSync) {
84
+ clm.step(`Already near latest ordinal. Skipping sync. Distance: ${localDistanceFromCluster}`);
85
+ return;
86
+ }
87
+ const requiredOldestOrdinal = clusterOrdinal - 10_000;
88
+ const archiveStartOrdinalMeetsOldestRequirement = remoteArchiveStartOrdinal <= requiredOldestOrdinal;
89
+ if (!archiveStartOrdinalMeetsOldestRequirement) {
90
+ clm.preStep('Archive is not in the optimal range, but proceeding with available data.');
91
+ }
92
+ const { projectDir } = configStore.getProjectInfo();
93
+ const dataDir = path.join(projectDir, 'app-data', 'gl0-data');
94
+ fs.mkdirSync(dataDir, { recursive: true });
95
+ clm.preStep(`Downloading latest snapshot archive ${chalk.yellow(remoteArchiveStartOrdinal)}-${chalk.yellow(remoteArchiveEndOrdinal)}; distance from cluster: ${chalk.yellow(archiveDistance)}\nCurrent oldest local ordinal: ${chalk.yellow(localStartOrdinal)}, Latest cluster ordinal: ${chalk.yellow(clusterOrdinal)}`);
96
+ // await shellService.runCommand(`curl -L ${url} -o ${dataDir}/snapshot.tar.gz`);
97
+ await shellService.runCommand(`wget --progress=bar:force -O ${dataDir}/snapshot.tar.gz ${url}`);
98
+ clm.preStep(`Extracting snapshot...`);
99
+ await shellService.runCommand(`tar -xf ${dataDir}/snapshot.tar.gz -C ${dataDir}`);
100
+ await shellService.runCommand(`rm ${dataDir}/snapshot.tar.gz`);
101
+ clm.postStep(`Total snapshots downloaded: ${chalk.yellow(total)}, Synced to ordinal: ${chalk.yellow(remoteArchiveEndOrdinal)}, Cluster Ordinal: ${chalk.yellow(clusterOrdinal)}, Distance from cluster: ${chalk.yellow(archiveDistance)}`);
102
+ clm.postStep(`Snapshot downloaded and extracted successfully.`);
103
+ }
104
+ };
@@ -1,10 +1,14 @@
1
- import { ClusterConsensusInfo, ClusterInfo, NodeInfo } from "../types.js";
1
+ import { ClusterConsensusInfo, ClusterInfo, NodeInfo, TessellationLayer } from "../types.js";
2
2
  export declare const clusterService: {
3
3
  fastForwardSnapshot(): Promise<void>;
4
- getClusterInfo(): Promise<ClusterInfo[]>;
5
- getLatestConsensus(): Promise<ClusterConsensusInfo>;
6
- getLatestOrdinal(): Promise<any>;
7
- getNodeInfo(): Promise<NodeInfo>;
4
+ getClusterInfo(layer?: TessellationLayer): Promise<ClusterInfo[]>;
5
+ getClusterNodeInfo(layer?: TessellationLayer): Promise<NodeInfo>;
6
+ getLatestConsensusInfo(layer?: TessellationLayer): Promise<ClusterConsensusInfo>;
7
+ getLayer0(): "gl0" | "ml0";
8
8
  getReleaseVersion(): Promise<string>;
9
- getSourceNodeInfo(): Promise<NodeInfo>;
9
+ getSourceNodeInfo(layer: TessellationLayer): Promise<NodeInfo>;
10
+ getSourceNodeLatestOrdinal(layer: TessellationLayer): Promise<number>;
11
+ getSourceNodeOrdinalHash(layer: TessellationLayer, ordinal: number): Promise<string>;
12
+ makeClusterRequest(path: string, layer?: TessellationLayer): Promise<any>;
13
+ makeSourceNodeRequest(path: string, layer: TessellationLayer): Promise<any>;
10
14
  };
@@ -1,4 +1,5 @@
1
1
  import { input } from "@inquirer/prompts";
2
+ import { clm } from "../clm.js";
2
3
  import { configStore } from "../config-store.js";
3
4
  import { FastforwardService } from "./fastforward-service.js";
4
5
  export const clusterService = {
@@ -21,57 +22,56 @@ export const clusterService = {
21
22
  configStore.setProjectInfo({ fastForward: true });
22
23
  }
23
24
  await FastforwardService.synctoLatestSnapshot();
25
+ // await archiverService.syncToLatestSnapshot()
26
+ // .catch(() => {
27
+ // clm.warn(`Failed to download latest snapshots using Starchiver. Using fast forward to latest snapshot.`);
28
+ // clusterService.fastForwardSnapshot();
29
+ // })
24
30
  },
25
- async getClusterInfo() {
26
- const { type } = configStore.getNetworkInfo();
27
- return fetch(`https://l0-lb-${type}.constellationnetwork.io/cluster/info`)
28
- .then(res => {
29
- if (res.ok)
30
- return res.json();
31
- throw new Error(`Failed`);
32
- })
33
- .catch(() => []);
31
+ async getClusterInfo(layer) {
32
+ return this.makeClusterRequest('cluster/info', layer);
34
33
  },
35
- async getLatestConsensus() {
36
- const { type } = configStore.getNetworkInfo();
37
- return fetch(`https://l0-lb-${type}.constellationnetwork.io/consensus/latest/peers`)
38
- .then(res => {
39
- if (res.ok)
40
- return res.json();
41
- return 0;
42
- })
43
- .catch(() => 0);
34
+ async getClusterNodeInfo(layer) {
35
+ return this.makeClusterRequest('node/info', layer);
44
36
  },
45
- async getLatestOrdinal() {
46
- const { type } = configStore.getNetworkInfo();
47
- return fetch(`https://l0-lb-${type}.constellationnetwork.io/global-snapshots/latest`)
48
- .then(res => {
49
- if (res.ok)
50
- return res.json().then(i => (i?.value?.ordinal || 0));
51
- return 0;
52
- })
53
- .catch(() => 0);
37
+ async getLatestConsensusInfo(layer) {
38
+ return this.makeClusterRequest('consensus/latest/peers', layer);
54
39
  },
55
- async getNodeInfo() {
56
- const { type } = configStore.getNetworkInfo();
57
- return fetch(`https://l0-lb-${type}.constellationnetwork.io/node/info`)
58
- .then(res => {
59
- if (res.ok)
60
- return res.json();
61
- throw new Error(`Failed`);
62
- })
63
- .catch(() => ({ state: "Unavailable" }));
40
+ getLayer0() {
41
+ return configStore.getProjectInfo().layersToRun.includes('gl0') ? 'gl0' : 'ml0';
64
42
  },
65
43
  async getReleaseVersion() {
66
- return this.getNodeInfo().then(i => i.version);
44
+ return this.getClusterNodeInfo().then(i => i.version);
45
+ },
46
+ async getSourceNodeInfo(layer) {
47
+ return this.makeSourceNodeRequest('node/info', layer);
48
+ },
49
+ async getSourceNodeLatestOrdinal(layer) {
50
+ return this.makeSourceNodeRequest('global-snapshots/latest', layer).then(i => i.value.ordinal);
67
51
  },
68
- async getSourceNodeInfo() {
69
- const { CL_L0_PEER_HTTP_HOST, CL_L0_PEER_HTTP_PORT } = configStore.getEnvCommonInfo();
70
- return fetch(`http://${CL_L0_PEER_HTTP_HOST}:${CL_L0_PEER_HTTP_PORT}/node/info`)
71
- .then(res => {
72
- if (res.ok)
73
- return res.json();
74
- throw new Error(`Failed`);
75
- });
52
+ async getSourceNodeOrdinalHash(layer, ordinal) {
53
+ return this.makeSourceNodeRequest(`global-snapshots/${ordinal}/hash`, layer);
54
+ },
55
+ async makeClusterRequest(path, layer) {
56
+ layer = layer || this.getLayer0();
57
+ const { type } = configStore.getNetworkInfo();
58
+ const envLayerInfo = configStore.getEnvLayerInfo(type, layer);
59
+ if (envLayerInfo.CL_LB) {
60
+ return fetch(`${envLayerInfo.CL_LB}/${path}`)
61
+ .then(res => res.json())
62
+ .catch(() => {
63
+ clm.debug(`Failed to get node info from ${envLayerInfo.CL_LB}. Attempting source node...`);
64
+ return this.makeSourceNodeRequest(path, layer);
65
+ });
66
+ }
67
+ return this.makeSourceNodeRequest(path, layer);
68
+ },
69
+ async makeSourceNodeRequest(path, layer) {
70
+ const { type } = configStore.getNetworkInfo();
71
+ const { CL_PUBLIC_HTTP_PORT } = configStore.getEnvLayerInfo(type, layer);
72
+ const { CL_L0_PEER_HTTP_HOST } = configStore.getEnvNetworkInfo(type);
73
+ clm.debug(`http://${CL_L0_PEER_HTTP_HOST}:${CL_PUBLIC_HTTP_PORT}/${path}`);
74
+ return fetch(`http://${CL_L0_PEER_HTTP_HOST}:${CL_PUBLIC_HTTP_PORT}/${path}`)
75
+ .then(res => res.json());
76
76
  }
77
77
  };
@@ -0,0 +1,9 @@
1
+ import { TessellationLayer } from "../types.js";
2
+ export declare const dockerService: {
3
+ dockerBuild(): Promise<void>;
4
+ dockerDown(): Promise<void>;
5
+ dockerRestart(layer: TessellationLayer): Promise<void>;
6
+ dockerStartLayers(layers: TessellationLayer[]): Promise<void>;
7
+ dockerUp(): Promise<void>;
8
+ isRunning(): Promise<boolean>;
9
+ };
@@ -1,9 +1,9 @@
1
1
  import ora from "ora";
2
2
  import { clm } from "../clm.js";
3
3
  import { configStore } from "../config-store.js";
4
- import { shellService } from "../services/shell-service.js";
5
- import { projectHelper } from "./project-helper.js";
6
- export const dockerHelper = {
4
+ import { projectHelper } from "../helpers/project-helper.js";
5
+ import { shellService } from "./shell-service.js";
6
+ export const dockerService = {
7
7
  async dockerBuild() {
8
8
  if (shellService.existsProjectScript('scripts/docker-build.sh')) {
9
9
  const silent = !process.env.DEBUG;
@@ -24,9 +24,13 @@ export const dockerHelper = {
24
24
  },
25
25
  async dockerDown() {
26
26
  await run('down');
27
+ configStore.setProjectStatusToRunning(false);
27
28
  },
28
- async dockerRestart() {
29
- await run('restart');
29
+ async dockerRestart(layer) {
30
+ await run('restart', [layer]);
31
+ },
32
+ async dockerStartLayers(layers) {
33
+ await run('up -d', layers);
30
34
  },
31
35
  async dockerUp() {
32
36
  // If docker is already running, stop it
@@ -34,13 +38,11 @@ export const dockerHelper = {
34
38
  await this.dockerDown();
35
39
  }
36
40
  await projectHelper.generateLayerEnvFiles();
37
- // const userId = await shellService.runCommandWithOutput('echo "$(id -u):$(id -g)"')
38
- // console.log('Setting DOCKER_USER_ID to', userId);
39
- // configStore.setDockerEnvInfo({ DOCKER_USER_ID: userId });
40
41
  await run('up -d');
42
+ configStore.setProjectStatusToRunning(true);
41
43
  },
42
44
  async isRunning() {
43
- return shellService.runCommand('docker ps | grep entrypoint.sh', undefined, true).then(Boolean).catch(() => false);
45
+ return shellService.runProjectCommand('docker compose ps -q | grep .', undefined, true).then(Boolean).catch(() => false);
44
46
  }
45
47
  };
46
48
  function run(command, layers) {
@@ -19,9 +19,9 @@ export class FastforwardService {
19
19
  this.dataDir = path.join(projectDir, 'app-data', 'gl0-data'); // gl0
20
20
  fs.mkdirSync(this.tmpDir, { recursive: true });
21
21
  fs.mkdirSync(this.dataDir, { recursive: true });
22
- const env = configStore.getEnvCommonInfo();
22
+ const env = configStore.getEnvNetworkInfo(type);
23
23
  // this.lbUrl = `https://l0-lb-${this.network}.constellationnetwork.io`;
24
- this.lbUrl = `http://${env.CL_GLOBAL_L0_PEER_HOST}:${env.CL_GLOBAL_L0_PEER_HTTP_PORT}`;
24
+ this.lbUrl = `http://${env.CL_L0_PEER_HTTP_HOST}:${env.CL_L0_PEER_HTTP_PORT}`;
25
25
  }
26
26
  static async synctoLatestSnapshot() {
27
27
  const ffs = new FastforwardService();
@@ -38,7 +38,7 @@ export class FastforwardService {
38
38
  fs.writeFileSync(path.join(snapshotInfoDir, ordinal.toString()), compressedSnapshotInfo);
39
39
  fs.writeFileSync(path.join(this.tmpDir, ordinal.toString() + '.c'), compressedSnapshotIncremental);
40
40
  await this.saveSnapshotFiles(ordinal.toString(), hash);
41
- clm.postStep('Fastforward completed.');
41
+ clm.postStep(`Fastforward to snapshot "${ordinal}" completed.`);
42
42
  }
43
43
  async fetchLatestSnapshot() {
44
44
  const url = `${this.lbUrl}/global-snapshots/latest/combined`;
@@ -1,7 +1,7 @@
1
1
  export const getRandomNode = (nodes) => {
2
2
  const randomNodeIndex = Math.floor(Math.random() * nodes.length);
3
3
  const node = nodes[randomNodeIndex];
4
- console.log(`Getting random node from ${nodes.length} nodes: ${node.host}`);
4
+ console.log(`Getting random node from ${nodes.length} nodes: ${node.host}:${node.publicPort}`);
5
5
  return fetch(`http://${node.host}:${node.publicPort}/node/info`)
6
6
  .then(async (res) => {
7
7
  if (res.ok)
@@ -1,3 +1,3 @@
1
- export declare const githubHelper: {
1
+ export declare const githubService: {
2
2
  hasAssetInRelease(assetName: string, repo: string): Promise<boolean>;
3
3
  };
@@ -1,4 +1,4 @@
1
- export const githubHelper = {
1
+ export const githubService = {
2
2
  hasAssetInRelease(assetName, repo) {
3
3
  return fetch(`https://api.github.com/repos/${repo}/releases/latest`)
4
4
  .then(async (res) => {
@@ -1,3 +1,4 @@
1
+ import chalk from "chalk";
1
2
  import { clm } from "../clm.js";
2
3
  import { configStore } from "../config-store.js";
3
4
  import { clusterService } from "./cluster-service.js";
@@ -48,7 +49,7 @@ export const nodeService = {
48
49
  return;
49
50
  }
50
51
  const layerPortInfo = configStore.getLayerPortInfo(layer);
51
- const peerInfo = await clusterService.getSourceNodeInfo();
52
+ const peerInfo = await clusterService.getClusterNodeInfo(layer);
52
53
  const nodeId = peerInfo.id;
53
54
  const nodeIp = peerInfo.host;
54
55
  const cliPort = layerPortInfo.CLI;
@@ -105,25 +106,24 @@ export const nodeService = {
105
106
  for (let i = 1; i <= 60; i++) {
106
107
  // eslint-disable-next-line no-await-in-loop
107
108
  const { state } = await this.getNodeInfo(layer);
108
- if (state === "Unavailable" || state === "ReadyToJoin") {
109
- if (expectedState === 'Offline')
110
- return true;
111
- clm.echo(` Validator Node is not running.`);
112
- }
113
- if (layer === 'gl0' && expectedState === 'Ready' && state === "WaitingForDownload") {
114
- clm.echo(` [${layer}] Attempt ${i}: Current state is "${state}"`);
115
- }
116
- else {
117
- clm.echo(` [${layer}] Attempt ${i}: Current state is "${state}"`);
118
- }
109
+ if (expectedState === 'Offline' && (state === "Unavailable" || state === "ReadyToJoin" || state === "SessionStarted"))
110
+ return true;
111
+ clm.echoRepeatLine(`[${layer}] ${chalk.bgGray.cyan('Attempt ' + i)}: Current state is "${state}" `);
112
+ // eslint-disable-next-line no-await-in-loop
113
+ await sleep(0.5);
114
+ clm.echoRepeatLine(`[${layer}] Attempt ${i}: Current state is "${state}" `);
119
115
  if (state === expectedState) {
120
- clm.postStep(` ${layer} is ${expectedState}`);
116
+ clm.postStep(`${layer} is ${expectedState}`);
121
117
  return true;
122
118
  }
119
+ if (state === "Unavailable" && i > 2) {
120
+ clm.warn(`${layer} is not connectable. Please try again later.`);
121
+ return false;
122
+ }
123
123
  // eslint-disable-next-line no-await-in-loop
124
124
  await sleep(5);
125
125
  }
126
- clm.warn(` ${layer} is not ${expectedState} after 5 minutes`);
126
+ clm.warn(`${layer} is not ${expectedState} after 5 minutes`);
127
127
  return false;
128
128
  }
129
129
  };
@@ -0,0 +1 @@
1
+ export declare const notifyService: {};
@@ -0,0 +1 @@
1
+ export const notifyService = {};
@@ -0,0 +1,3 @@
1
+ export declare const systemdService: {
2
+ install(): Promise<void>;
3
+ };
@@ -0,0 +1,45 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { clm } from "../clm.js";
5
+ import { configStore } from "../config-store.js";
6
+ import { shellService } from "./shell-service.js";
7
+ const INSTALL_PATH = '/etc/systemd/system/';
8
+ export const systemdService = {
9
+ async install() {
10
+ const { platform } = configStore.getSystemInfo();
11
+ if (platform !== 'linux') {
12
+ clm.warn('Node Pilot services can only be installed on Linux systems. Skipping systemd service installation.\n');
13
+ return;
14
+ }
15
+ const projectFolder = path.resolve(path.dirname(fileURLToPath(import.meta.url)), `../../scripts/services`);
16
+ if (!fs.existsSync(projectFolder)) {
17
+ clm.error(`Node Pilot services's folder not found: ${projectFolder}`);
18
+ }
19
+ const restarterPath = path.join(INSTALL_PATH, 'node-pilot-restarter.service');
20
+ const updaterPath = path.join(INSTALL_PATH, 'node-pilot-updater.service');
21
+ let restarterInstalled = false;
22
+ let updaterInstalled = false;
23
+ if (!fs.existsSync(restarterPath)) {
24
+ fs.cpSync(path.join(projectFolder, 'node-pilot-restarter.service'), path.join(INSTALL_PATH, 'node-pilot-restarter.service'));
25
+ clm.step('Node Pilot restarter service installed successfully.');
26
+ restarterInstalled = true;
27
+ }
28
+ if (!fs.existsSync(updaterPath)) {
29
+ fs.cpSync(path.join(projectFolder, 'node-pilot-updater.service'), path.join(INSTALL_PATH, 'node-pilot-updater.service'));
30
+ clm.step('Node Pilot updater service installed successfully.');
31
+ updaterInstalled = true;
32
+ }
33
+ if (restarterInstalled || updaterInstalled) {
34
+ await shellService.runCommand('systemctl daemon-reload');
35
+ }
36
+ if (restarterInstalled) {
37
+ await shellService.runCommand('systemctl enable node-pilot-restarter.service');
38
+ clm.step('Node Pilot restarter service enabled successfully.');
39
+ }
40
+ if (updaterInstalled) {
41
+ await shellService.runCommand('systemctl enable node-pilot-updater.service');
42
+ clm.step('Node Pilot updater service enabled successfully.');
43
+ }
44
+ }
45
+ };
package/dist/test.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/test.js ADDED
@@ -0,0 +1,50 @@
1
+ import * as crypto from "node:crypto";
2
+ // convert base64 to hex
3
+ const val = 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgeYLAOmnJ6FYQ3Uddr6lq5PEVgoiAdWIJ89x6fx3n8JihRANCAASNoaVq/vT5k5Akdzjv8ifybl9icGnMKRpeyZhOxTMnwxGjxANTuKLfUGWUOEngV6HaWGxgkXTU/VXRma+mu0hD';
4
+ // const val = 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKy/2tKuoO5V0iXJmTlaWPl9PeDo8TO5ZD+JmD3pOlzihRANCAASffXYMsILTOYSM4pEuBiYO1RR/hfuO+k4WvnNup21HT7CGThWfRjIemS3QDMHncK0Shsy43nb6kD9Fo8DTKpI6';
5
+ const der = Buffer.from(val, 'base64').toString('hex');
6
+ console.log(der);
7
+ // convert DER to PEM
8
+ const ecKey = crypto.createPrivateKey({
9
+ encoding: "base64",
10
+ format: "der",
11
+ key: val,
12
+ type: "pkcs8",
13
+ });
14
+ console.log(ecKey.export({ 'format': 'jwk' }));
15
+ // Generate an RSA key pair
16
+ const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
17
+ modulusLength: 2048, // Recommended key size
18
+ privateKeyEncoding: {
19
+ format: 'der', // DER encoding for binary output
20
+ type: 'pkcs8' // PKCS#8 format
21
+ },
22
+ publicKeyEncoding: {
23
+ format: 'der',
24
+ type: 'spki' // SubjectPublicKeyInfo
25
+ }
26
+ });
27
+ // The 'privateKey' variable now holds a Buffer containing the PKCS#8 DER private key.
28
+ // To get the Base64 encoded string:
29
+ const privateKeyBase64 = privateKey.toString('base64');
30
+ console.log('PKCS#8 DER Private Key (Base64):');
31
+ console.log(privateKeyBase64);
32
+ // openssl pkcs8 -inform DER -in privatekey.der -outform PEM -out privatekey.pem
33
+ // openssl pkcs8 -inform DER -in privatekey.der -nocrypt -out privatekey.pem -traditional
34
+ async function createP256Pkcs8DerBase64PrivateKey() {
35
+ // Generate an ECDSA key pair with the P-256 named curve
36
+ const keyPair = await crypto.subtle.generateKey({
37
+ name: 'ECDSA',
38
+ namedCurve: 'P-256',
39
+ }, true, // extractable
40
+ ['sign', 'verify']);
41
+ // Export the private key in PKCS#8 DER format
42
+ const privateKeyDerBuffer = await crypto.subtle.exportKey('pkcs8', // format
43
+ keyPair.privateKey);
44
+ console.log('PKCS#8 DER Private Key (DER Buffer): ' + Buffer.from(privateKeyDerBuffer).toString('hex'));
45
+ // Convert the DER buffer to a Base64 encoded string
46
+ const privateKeyBase64 = Buffer.from(privateKeyDerBuffer).toString('base64');
47
+ console.log('PKCS#8 DER Private Key (Base64):' + privateKeyBase64);
48
+ return privateKeyBase64;
49
+ }
50
+ await createP256Pkcs8DerBase64PrivateKey();
package/dist/types.d.ts CHANGED
@@ -32,6 +32,12 @@ export type ClusterConsensusInfo = {
32
32
  state: 'Ready';
33
33
  }[];
34
34
  };
35
+ export type ClusterStats = {
36
+ ordinal: number;
37
+ ready: number;
38
+ startTime: string;
39
+ total: number;
40
+ };
35
41
  export type NodeStatusInfo = {
36
42
  clusterSession: string;
37
43
  id: string;
@@ -59,8 +59,6 @@ check_docker() {
59
59
  fi
60
60
 
61
61
  echo "⚠️ Docker not found. Will attempt to install Docker."
62
-
63
- # curl -fsSL https://raw.githubusercontent.com/Constellation-Labs/node-pilot/refs/heads/main/install.sh -o install-node-pilot.sh; source install-node-pilot.sh
64
62
 
65
63
  case "$(uname)" in
66
64
  Linux)