@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.
- package/README.md +26 -15
- package/bin/dev.js +1 -6
- package/bin/run.js +1 -1
- package/dist/base-command.d.ts +9 -0
- package/dist/base-command.js +20 -0
- package/dist/checks/check-hardware.js +3 -3
- package/dist/checks/check-initial-setup.js +2 -0
- package/dist/checks/check-layers.js +7 -7
- package/dist/checks/check-network.d.ts +2 -0
- package/dist/checks/check-network.js +46 -11
- package/dist/checks/check-node-ctl.js +4 -4
- package/dist/checks/check-project.d.ts +1 -0
- package/dist/checks/check-project.js +15 -4
- package/dist/checks/check-wallet.d.ts +3 -0
- package/dist/checks/check-wallet.js +37 -0
- package/dist/clm.d.ts +1 -0
- package/dist/clm.js +3 -0
- package/dist/commands/config/get.d.ts +6 -0
- package/dist/commands/config/get.js +57 -11
- package/dist/commands/config/set.d.ts +0 -1
- package/dist/commands/config/set.js +13 -11
- package/dist/commands/config.js +17 -22
- package/dist/commands/info.js +3 -2
- package/dist/commands/logs.d.ts +1 -1
- package/dist/commands/logs.js +7 -3
- package/dist/commands/restart.d.ts +10 -2
- package/dist/commands/restart.js +65 -9
- package/dist/commands/shutdown.js +3 -3
- package/dist/commands/status.js +4 -0
- package/dist/commands/test.js +10 -3
- package/dist/config-store.d.ts +47 -31
- package/dist/config-store.js +98 -42
- package/dist/helpers/config-helper.js +2 -2
- package/dist/helpers/env-templates.d.ts +4 -3
- package/dist/helpers/env-templates.js +28 -20
- package/dist/helpers/key-file-helper.d.ts +2 -0
- package/dist/helpers/key-file-helper.js +51 -16
- package/dist/helpers/project-helper.d.ts +2 -2
- package/dist/helpers/project-helper.js +37 -38
- package/dist/helpers/prompt-helper.d.ts +0 -1
- package/dist/helpers/prompt-helper.js +15 -15
- package/dist/services/archiver-service.d.ts +17 -0
- package/dist/services/archiver-service.js +104 -0
- package/dist/services/cluster-service.d.ts +10 -6
- package/dist/services/cluster-service.js +45 -45
- package/dist/services/docker-service.d.ts +9 -0
- package/dist/{helpers/docker-helper.js → services/docker-service.js} +11 -9
- package/dist/services/fastforward-service.js +3 -3
- package/dist/services/get-random-node.js +1 -1
- package/dist/{helpers/github-helper.d.ts → services/github-service.d.ts} +1 -1
- package/dist/{helpers/github-helper.js → services/github-service.js} +1 -1
- package/dist/services/node-service.js +14 -14
- package/dist/services/notify-service.d.ts +1 -0
- package/dist/services/notify-service.js +1 -0
- package/dist/services/systemd-service.d.ts +3 -0
- package/dist/services/systemd-service.js +45 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +50 -0
- package/dist/types.d.ts +6 -0
- package/install-dependencies.sh +0 -2
- package/oclif.manifest.json +33 -4
- package/package.json +9 -8
- package/projects/custom/pilot.json +9 -0
- package/projects/hypergraph/Dockerfile +24 -18
- package/projects/hypergraph/docker-compose.yml +14 -14
- package/projects/hypergraph/networks/integrationnet/gl0.env +4 -0
- package/projects/hypergraph/networks/integrationnet/gl1.env +4 -0
- package/projects/hypergraph/networks/integrationnet/network.env +8 -0
- package/projects/hypergraph/networks/{integrationnet.env → integrationnet/source-nodes.env} +1 -9
- package/projects/hypergraph/networks/mainnet/gl0.env +4 -0
- package/projects/hypergraph/networks/mainnet/gl1.env +4 -0
- package/projects/hypergraph/networks/mainnet/network.env +8 -0
- package/projects/hypergraph/networks/{mainnet.env → mainnet/source-nodes.env} +0 -8
- package/projects/hypergraph/networks/testnet/gl0.env +5 -0
- package/projects/hypergraph/networks/testnet/gl1.env +4 -0
- package/projects/hypergraph/networks/testnet/network.env +8 -0
- package/projects/hypergraph/networks/{testnet.env → testnet/source-nodes.env} +0 -8
- package/projects/hypergraph/scripts/check-version.sh +31 -0
- package/projects/hypergraph/scripts/install.sh +30 -25
- package/projects/hypergraph/seedlist +268 -0
- package/scripts/autoheal.sh +8 -0
- package/scripts/restart_logger.sh +3 -0
- package/scripts/services/io.constellationnetwork.nodepilot.Updater.plist +16 -0
- package/scripts/services/node-pilot-autoheal.service +12 -0
- package/scripts/services/node-pilot-restarter.service +11 -0
- package/scripts/services/node-pilot-updater.service +13 -0
- package/scripts/update_logger.sh +3 -0
- package/dist/helpers/docker-helper.d.ts +0 -7
- package/projects/hypergraph/layers/gl1.env +0 -3
- 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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
clm.postStep
|
38
|
-
|
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.
|
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.
|
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
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
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
|
36
|
-
|
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
|
46
|
-
|
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
|
-
|
56
|
-
|
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.
|
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
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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 {
|
5
|
-
import {
|
6
|
-
export const
|
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.
|
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.
|
22
|
+
const env = configStore.getEnvNetworkInfo(type);
|
23
23
|
// this.lbUrl = `https://l0-lb-${this.network}.constellationnetwork.io`;
|
24
|
-
this.lbUrl = `http://${env.
|
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(
|
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,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.
|
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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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(
|
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(
|
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,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;
|
package/install-dependencies.sh
CHANGED
@@ -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)
|