@constellation-network/node-pilot 0.0.1
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 +213 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +19 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +15 -0
- package/dist/checks/check-hardware.d.ts +3 -0
- package/dist/checks/check-hardware.js +50 -0
- package/dist/checks/check-initial-setup.d.ts +3 -0
- package/dist/checks/check-initial-setup.js +15 -0
- package/dist/checks/check-layers.d.ts +7 -0
- package/dist/checks/check-layers.js +87 -0
- package/dist/checks/check-network.d.ts +6 -0
- package/dist/checks/check-network.js +72 -0
- package/dist/checks/check-project.d.ts +6 -0
- package/dist/checks/check-project.js +108 -0
- package/dist/clm.d.ts +9 -0
- package/dist/clm.js +30 -0
- package/dist/commands/config/get.d.ts +9 -0
- package/dist/commands/config/get.js +37 -0
- package/dist/commands/config/set.d.ts +11 -0
- package/dist/commands/config/set.js +41 -0
- package/dist/commands/config.d.ts +6 -0
- package/dist/commands/config.js +65 -0
- package/dist/commands/info.d.ts +6 -0
- package/dist/commands/info.js +32 -0
- package/dist/commands/logs.d.ts +12 -0
- package/dist/commands/logs.js +28 -0
- package/dist/commands/restart.d.ts +6 -0
- package/dist/commands/restart.js +25 -0
- package/dist/commands/shutdown.d.ts +6 -0
- package/dist/commands/shutdown.js +18 -0
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.js +22 -0
- package/dist/commands/test.d.ts +6 -0
- package/dist/commands/test.js +13 -0
- package/dist/config-store.d.ts +109 -0
- package/dist/config-store.js +151 -0
- package/dist/helpers/config-helper.d.ts +14 -0
- package/dist/helpers/config-helper.js +39 -0
- package/dist/helpers/docker-helper.d.ts +7 -0
- package/dist/helpers/docker-helper.js +37 -0
- package/dist/helpers/env-templates.d.ts +4 -0
- package/dist/helpers/env-templates.js +33 -0
- package/dist/helpers/github-helper.d.ts +3 -0
- package/dist/helpers/github-helper.js +13 -0
- package/dist/helpers/key-file-helper.d.ts +9 -0
- package/dist/helpers/key-file-helper.js +136 -0
- package/dist/helpers/project-helper.d.ts +9 -0
- package/dist/helpers/project-helper.js +85 -0
- package/dist/helpers/prompt-helper.d.ts +8 -0
- package/dist/helpers/prompt-helper.js +135 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/info/cluster-info.d.ts +1 -0
- package/dist/info/cluster-info.js +16 -0
- package/dist/services/cluster-service.d.ts +9 -0
- package/dist/services/cluster-service.js +67 -0
- package/dist/services/fastforward-service.d.ts +14 -0
- package/dist/services/fastforward-service.js +84 -0
- package/dist/services/node-service.d.ts +10 -0
- package/dist/services/node-service.js +117 -0
- package/dist/services/shell-service.d.ts +9 -0
- package/dist/services/shell-service.js +53 -0
- package/dist/styles.d.ts +4 -0
- package/dist/styles.js +5 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.js +1 -0
- package/oclif.manifest.json +241 -0
- package/package.json +98 -0
- package/projects/hypergraph/Dockerfile +41 -0
- package/projects/hypergraph/docker-compose.yml +54 -0
- package/projects/hypergraph/entrypoint.sh +35 -0
- package/projects/hypergraph/layers/gl1.env +3 -0
- package/projects/hypergraph/networks/integrationnet.env +9 -0
- package/projects/hypergraph/networks/mainnet.env +8 -0
- package/projects/hypergraph/networks/testnet.env +9 -0
- package/projects/hypergraph/pilot.env +2 -0
- package/projects/hypergraph/scripts/docker-build.sh +7 -0
- package/projects/hypergraph/scripts/install-dependencies.sh +318 -0
- package/projects/hypergraph/scripts/install.sh +149 -0
- package/projects/scripts/docker-cleanup.sh +64 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
import chalk from "chalk";
|
2
|
+
import dotenv from "dotenv";
|
3
|
+
import fs from "node:fs";
|
4
|
+
import path from "node:path";
|
5
|
+
import { clm } from "../clm.js";
|
6
|
+
import { configStore } from "../config-store.js";
|
7
|
+
export const configHelper = {
|
8
|
+
assertProject(prefix = '') {
|
9
|
+
if (configStore.hasProjects())
|
10
|
+
return;
|
11
|
+
clm.error(prefix + 'Please create a new project first.');
|
12
|
+
},
|
13
|
+
async getReleaseInfo() {
|
14
|
+
const { projectDir } = configStore.getProjectInfo();
|
15
|
+
const lastInstallVersion = path.resolve(projectDir, "dist/version.sh");
|
16
|
+
if (!fs.existsSync(lastInstallVersion)) {
|
17
|
+
return;
|
18
|
+
}
|
19
|
+
const versionObj = this.parseEnvFile(lastInstallVersion);
|
20
|
+
return {
|
21
|
+
network: versionObj.RELEASE_NETWORK_TYPE,
|
22
|
+
version: versionObj.RELEASE_NETWORK_VERSION
|
23
|
+
};
|
24
|
+
},
|
25
|
+
parseEnvFile(filePath) {
|
26
|
+
const fileStr = fs.readFileSync(filePath, 'utf8');
|
27
|
+
return dotenv.parse(fileStr);
|
28
|
+
},
|
29
|
+
showEnvInfo(name, value) {
|
30
|
+
console.log(`${chalk.white(name)}=${chalk.cyan(value)}`);
|
31
|
+
},
|
32
|
+
showEnvInfoList(list) {
|
33
|
+
const formatted = list.map(i => {
|
34
|
+
const value = (i.name === 'CL_PASSWORD') ? '************' : i.value;
|
35
|
+
return `${chalk.white(i.name)}=${chalk.cyan(value || '')}`;
|
36
|
+
}).join('\n');
|
37
|
+
console.log(formatted);
|
38
|
+
}
|
39
|
+
};
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import { clm } from "../clm.js";
|
2
|
+
import { configStore } from "../config-store.js";
|
3
|
+
import { shellService } from "../services/shell-service.js";
|
4
|
+
import { projectHelper } from "./project-helper.js";
|
5
|
+
export const dockerHelper = {
|
6
|
+
async dockerBuild() {
|
7
|
+
if (shellService.existsScript('scripts/docker-build.sh')) {
|
8
|
+
clm.preStep('Building the node container...');
|
9
|
+
await shellService.runCommand('bash scripts/docker-build.sh');
|
10
|
+
}
|
11
|
+
},
|
12
|
+
async dockerDown() {
|
13
|
+
await run('down');
|
14
|
+
},
|
15
|
+
async dockerRestart() {
|
16
|
+
await run('restart');
|
17
|
+
},
|
18
|
+
async dockerUp() {
|
19
|
+
// If docker is already running, stop it
|
20
|
+
if (await this.isRunning()) {
|
21
|
+
await this.dockerDown();
|
22
|
+
}
|
23
|
+
await projectHelper.generateLayerEnvFiles();
|
24
|
+
// const userId = await shellService.runCommandWithOutput('echo "$(id -u):$(id -g)"')
|
25
|
+
// console.log('Setting DOCKER_USER_ID to', userId);
|
26
|
+
// configStore.setDockerEnvInfo({ DOCKER_USER_ID: userId });
|
27
|
+
await run('up -d');
|
28
|
+
},
|
29
|
+
async isRunning() {
|
30
|
+
return shellService.runCommand('docker ps | grep entrypoint.sh', undefined, true).then(() => true).catch(() => false);
|
31
|
+
}
|
32
|
+
};
|
33
|
+
function run(command, layers) {
|
34
|
+
const { layersToRun } = configStore.getProjectInfo();
|
35
|
+
const args = (layers || layersToRun).map(l => `--profile ${l}`).join(' ');
|
36
|
+
return shellService.runCommand(`docker compose ${args} ${command}`, configStore.getDockerEnvInfo());
|
37
|
+
}
|
@@ -0,0 +1,4 @@
|
|
1
|
+
import { EnvCommonInfo, EnvLayerInfo, NetworkType } from "../config-store.js";
|
2
|
+
import { TessellationLayer } from "../types.js";
|
3
|
+
export declare function getLayerEnvFileContent(layer: TessellationLayer, network: NetworkType, commonInfo: EnvCommonInfo, layerInfo: EnvLayerInfo): string;
|
4
|
+
export declare function getKeyFileContent(commonInfo: EnvCommonInfo): string;
|
@@ -0,0 +1,33 @@
|
|
1
|
+
export function getLayerEnvFileContent(layer, network, commonInfo, layerInfo) {
|
2
|
+
return `
|
3
|
+
# Node
|
4
|
+
CL_EXTERNAL_IP=${commonInfo.CL_EXTERNAL_IP}
|
5
|
+
CL_DOCKER_JAVA_OPTS="${layerInfo.CL_DOCKER_JAVA_OPTS}"
|
6
|
+
CL_KEYSTORE="/app/key.p12"
|
7
|
+
CL_KEYALIAS="${commonInfo.CL_KEYALIAS}"
|
8
|
+
CL_PASSWORD="${commonInfo.CL_PASSWORD}"
|
9
|
+
CL_TESSELATION_LAYER=${layer}
|
10
|
+
|
11
|
+
# NETWORK
|
12
|
+
CL_APP_ENV=${commonInfo.CL_APP_ENV}
|
13
|
+
CL_COLLATERAL=${network === 'mainnet' ? 25000000000000 : 0}
|
14
|
+
CL_L0_PEER_HTTP_PORT=${commonInfo.CL_L0_PEER_HTTP_PORT}
|
15
|
+
CL_L0_PEER_HTTP_HOST=${commonInfo.CL_L0_PEER_HTTP_HOST}
|
16
|
+
CL_L0_PEER_ID=${commonInfo.CL_L0_PEER_ID}
|
17
|
+
CL_GLOBAL_L0_PEER_HTTP_PORT=${commonInfo.CL_GLOBAL_L0_PEER_HTTP_PORT}
|
18
|
+
CL_GLOBAL_L0_PEER_HOST=${commonInfo.CL_GLOBAL_L0_PEER_HOST}
|
19
|
+
CL_GLOBAL_L0_PEER_ID=${commonInfo.CL_GLOBAL_L0_PEER_ID}
|
20
|
+
|
21
|
+
# LAYER
|
22
|
+
CL_PUBLIC_HTTP_PORT=${layerInfo.CL_PUBLIC_HTTP_PORT}
|
23
|
+
CL_P2P_HTTP_PORT=${layerInfo.CL_P2P_HTTP_PORT}
|
24
|
+
CL_CLI_HTTP_PORT=${layerInfo.CL_CLI_HTTP_PORT}
|
25
|
+
`;
|
26
|
+
}
|
27
|
+
export function getKeyFileContent(commonInfo) {
|
28
|
+
return `
|
29
|
+
export CL_KEYSTORE="${commonInfo.CL_KEYSTORE}"
|
30
|
+
export CL_KEYALIAS="${commonInfo.CL_KEYALIAS}"
|
31
|
+
export CL_PASSWORD="${commonInfo.CL_PASSWORD}"
|
32
|
+
`;
|
33
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
export const githubHelper = {
|
2
|
+
hasAssetInRelease(assetName, repo) {
|
3
|
+
return fetch(`https://api.github.com/repos/${repo}/releases/latest`)
|
4
|
+
.then(async (res) => {
|
5
|
+
if (res.ok) {
|
6
|
+
const json = await res.json();
|
7
|
+
return json.assets.some(a => a.name.includes(assetName));
|
8
|
+
}
|
9
|
+
return false;
|
10
|
+
})
|
11
|
+
.catch(() => false);
|
12
|
+
}
|
13
|
+
};
|
@@ -0,0 +1,9 @@
|
|
1
|
+
export declare const keyFileHelper: {
|
2
|
+
generate(): Promise<void>;
|
3
|
+
getAddress(): Promise<string>;
|
4
|
+
getId(): Promise<string>;
|
5
|
+
importKeyFile(): Promise<void>;
|
6
|
+
promptForKeyFile(): Promise<void>;
|
7
|
+
promptIfNoKeyFile(): Promise<void>;
|
8
|
+
showKeyFileInfo(prompt4ShowPassword?: boolean): Promise<void>;
|
9
|
+
};
|
@@ -0,0 +1,136 @@
|
|
1
|
+
import { input, password, select } from "@inquirer/prompts";
|
2
|
+
import chalk from "chalk";
|
3
|
+
import fs from "node:fs";
|
4
|
+
import os from "node:os";
|
5
|
+
import path from "node:path";
|
6
|
+
import { clm } from "../clm.js";
|
7
|
+
import { configStore } from "../config-store.js";
|
8
|
+
import { shellService } from "../services/shell-service.js";
|
9
|
+
import { configHelper } from "./config-helper.js";
|
10
|
+
import { getKeyFileContent } from "./env-templates.js";
|
11
|
+
export const keyFileHelper = {
|
12
|
+
async generate() {
|
13
|
+
const { projectDir } = configStore.getProjectInfo();
|
14
|
+
const keyFilePath = path.join(projectDir, "key.p12");
|
15
|
+
if (fs.existsSync(keyFilePath)) {
|
16
|
+
const answer = await input({ default: 'n', message: 'A key file already exists. Do you want to overwrite it? (y/n): ' });
|
17
|
+
if (answer.toLowerCase() === 'y') {
|
18
|
+
fs.rmSync(keyFilePath, { force: true });
|
19
|
+
}
|
20
|
+
else {
|
21
|
+
clm.echo('Key file generation cancelled.');
|
22
|
+
process.exit(0);
|
23
|
+
}
|
24
|
+
}
|
25
|
+
const keyPassword = await password({ message: 'Enter the key file password:', validate: value => value.length > 0 });
|
26
|
+
const env = {
|
27
|
+
CL_KEYALIAS: "alias", CL_KEYSTORE: keyFilePath, CL_PASSWORD: keyPassword
|
28
|
+
};
|
29
|
+
await shellService.runCommand(`java -jar ${projectDir}/dist/keytool.jar generate`, env);
|
30
|
+
configStore.setEnvCommonInfo(env);
|
31
|
+
const dagAddress = await this.getAddress();
|
32
|
+
const nodeId = await this.getId();
|
33
|
+
configStore.setProjectInfo({ dagAddress, nodeId });
|
34
|
+
clm.postStep('Key file generated successfully.\n');
|
35
|
+
const answer = await input({ default: 'y', message: 'Would you like to save a backup of the key file to your home directory? (y/n): ' });
|
36
|
+
if (answer.toLowerCase() === 'y') {
|
37
|
+
fs.cpSync(keyFilePath, path.join(os.homedir(), 'key.p12'));
|
38
|
+
fs.writeFileSync(path.join(os.homedir(), 'key-env.sh'), getKeyFileContent({ ...env, CL_KEYSTORE: 'key.p12' }));
|
39
|
+
clm.postStep(`A copy of the Key file has been saved to your home directory - ${chalk.cyan(path.join(os.homedir(), 'key.p12'))}`);
|
40
|
+
}
|
41
|
+
},
|
42
|
+
async getAddress() {
|
43
|
+
const { projectDir } = configStore.getProjectInfo();
|
44
|
+
const env = configStore.getEnvCommonInfo();
|
45
|
+
return shellService.runCommandWithOutput(`java -jar ${projectDir}/dist/wallet.jar show-address`, env);
|
46
|
+
},
|
47
|
+
async getId() {
|
48
|
+
const { projectDir } = configStore.getProjectInfo();
|
49
|
+
const env = configStore.getEnvCommonInfo();
|
50
|
+
return shellService.runCommandWithOutput(`java -jar ${projectDir}/dist/wallet.jar show-id`, env);
|
51
|
+
},
|
52
|
+
async importKeyFile() {
|
53
|
+
const userKeyPath = await input({
|
54
|
+
message: 'Enter the path to your key file:',
|
55
|
+
validate(value) {
|
56
|
+
const fullPath = shellService.resolvePath(value.trim());
|
57
|
+
if (!fs.existsSync(fullPath)) {
|
58
|
+
return `${fullPath} does not exist. Please provide a valid path.`;
|
59
|
+
}
|
60
|
+
return true;
|
61
|
+
}
|
62
|
+
});
|
63
|
+
const { projectDir } = configStore.getProjectInfo();
|
64
|
+
const fullPath = shellService.resolvePath(userKeyPath.trim());
|
65
|
+
const keyStorePath = path.join(projectDir, "key.p12");
|
66
|
+
try {
|
67
|
+
fs.copyFileSync(fullPath, keyStorePath);
|
68
|
+
}
|
69
|
+
catch (error) {
|
70
|
+
clm.error('Failed to import key file:' + error);
|
71
|
+
}
|
72
|
+
// prompt for password
|
73
|
+
const keyPassword = await password({ message: 'Enter the key file password:' });
|
74
|
+
const keyAlias = await input({ message: 'Enter the key file alias:' });
|
75
|
+
configStore.setEnvCommonInfo({ CL_KEYALIAS: keyAlias, CL_KEYSTORE: keyStorePath, CL_PASSWORD: keyPassword });
|
76
|
+
try {
|
77
|
+
const dagAddress = await this.getAddress();
|
78
|
+
const nodeId = await this.getId();
|
79
|
+
configStore.setProjectInfo({ dagAddress, nodeId });
|
80
|
+
}
|
81
|
+
catch {
|
82
|
+
clm.warn('Failed to unlock the key file. Please check your key file information and try again.');
|
83
|
+
fs.rmSync(keyStorePath);
|
84
|
+
await this.promptForKeyFile();
|
85
|
+
return;
|
86
|
+
}
|
87
|
+
clm.postStep('Key file imported successfully.\n');
|
88
|
+
},
|
89
|
+
async promptForKeyFile() {
|
90
|
+
const choices = [
|
91
|
+
{ name: 'Generate a new key file', value: 'generate' },
|
92
|
+
{ name: 'Import an existing key file', value: 'import' }
|
93
|
+
];
|
94
|
+
const { projectDir } = configStore.getProjectInfo();
|
95
|
+
const keyFilePath = path.join(projectDir, "key.p12");
|
96
|
+
if (fs.existsSync(keyFilePath)) {
|
97
|
+
choices.push({ name: 'Skip', value: 'skip' });
|
98
|
+
}
|
99
|
+
else {
|
100
|
+
clm.preStep('A key file is required to run the node.');
|
101
|
+
}
|
102
|
+
const answer = await select({ choices, message: 'Choose an option:' });
|
103
|
+
if (answer === 'generate') {
|
104
|
+
await this.generate();
|
105
|
+
await this.showKeyFileInfo();
|
106
|
+
}
|
107
|
+
else if (answer === 'import') {
|
108
|
+
await this.importKeyFile();
|
109
|
+
await this.showKeyFileInfo(false);
|
110
|
+
}
|
111
|
+
},
|
112
|
+
async promptIfNoKeyFile() {
|
113
|
+
const { projectDir } = configStore.getProjectInfo();
|
114
|
+
const keyFilePath = path.join(projectDir, "key.p12");
|
115
|
+
if (fs.existsSync(keyFilePath)) {
|
116
|
+
return;
|
117
|
+
}
|
118
|
+
await this.promptForKeyFile();
|
119
|
+
},
|
120
|
+
async showKeyFileInfo(prompt4ShowPassword = true) {
|
121
|
+
clm.preStep('Key File Information:');
|
122
|
+
const { dagAddress, nodeId } = configStore.getProjectInfo();
|
123
|
+
configHelper.showEnvInfo('Node ID', nodeId);
|
124
|
+
configHelper.showEnvInfo('DAG Address', dagAddress);
|
125
|
+
const { CL_KEYALIAS, CL_KEYSTORE, CL_PASSWORD } = configStore.getEnvCommonInfo();
|
126
|
+
configHelper.showEnvInfo('CL_KEYSTORE', CL_KEYSTORE || '');
|
127
|
+
configHelper.showEnvInfo('CL_KEYALIAS', CL_KEYALIAS || '');
|
128
|
+
configHelper.showEnvInfo('CL_PASSWORD', '*********');
|
129
|
+
if (prompt4ShowPassword) {
|
130
|
+
const show = await input({ default: 'n', message: 'Show the password for the key file (y/n):' });
|
131
|
+
if (show === 'y') {
|
132
|
+
configHelper.showEnvInfo('CL_PASSWORD', CL_PASSWORD || '');
|
133
|
+
}
|
134
|
+
}
|
135
|
+
}
|
136
|
+
};
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { TessellationLayer } from "../types.js";
|
2
|
+
export declare const projectHelper: {
|
3
|
+
generateLayerEnvFiles(layers?: TessellationLayer[]): Promise<void>;
|
4
|
+
importLayerEnvFiles(): Promise<void>;
|
5
|
+
importNetworkEnvFiles(): Promise<void>;
|
6
|
+
installEmbedded(name: string): Promise<void>;
|
7
|
+
installFromGithub(_repo: string): Promise<never>;
|
8
|
+
installHypergraph(): Promise<void>;
|
9
|
+
};
|
@@ -0,0 +1,85 @@
|
|
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 { configHelper } from "./config-helper.js";
|
7
|
+
import { getLayerEnvFileContent } from "./env-templates.js";
|
8
|
+
export const projectHelper = {
|
9
|
+
async generateLayerEnvFiles(layers) {
|
10
|
+
const { layersToRun, projectDir } = configStore.getProjectInfo();
|
11
|
+
const { type } = configStore.getNetworkInfo();
|
12
|
+
const commonInfo = configStore.getEnvCommonInfo();
|
13
|
+
layers = layers || layersToRun;
|
14
|
+
for (const n of layers) {
|
15
|
+
const filePath = path.join(projectDir, `${n}.env`);
|
16
|
+
const envInfo = configStore.getEnvLayerInfo(n);
|
17
|
+
const fileContents = getLayerEnvFileContent(n, type, commonInfo, envInfo);
|
18
|
+
clm.debug(`Writing layer env file: ${filePath}`);
|
19
|
+
fs.writeFileSync(filePath, fileContents);
|
20
|
+
}
|
21
|
+
},
|
22
|
+
async importLayerEnvFiles() {
|
23
|
+
const { projectDir } = configStore.getProjectInfo();
|
24
|
+
const possibleLayers = ['gl0', 'gl1', 'ml0', 'cl1', 'dl1'];
|
25
|
+
for (const n of possibleLayers) {
|
26
|
+
const filePath = path.join(projectDir, 'layers', `${n}.env`);
|
27
|
+
if (fs.existsSync(filePath)) {
|
28
|
+
configStore.setEnvLayerInfo(n, configHelper.parseEnvFile(filePath));
|
29
|
+
}
|
30
|
+
}
|
31
|
+
},
|
32
|
+
async importNetworkEnvFiles() {
|
33
|
+
const { projectDir } = configStore.getProjectInfo();
|
34
|
+
const possibleNetworks = ['mainnet', 'testnet', 'integrationnet'];
|
35
|
+
const supportedTypes = [];
|
36
|
+
const networkEnvInfo = {};
|
37
|
+
for (const n of possibleNetworks) {
|
38
|
+
const filePath = path.join(projectDir, 'networks', `${n}.env`);
|
39
|
+
if (fs.existsSync(filePath)) {
|
40
|
+
supportedTypes.push(n);
|
41
|
+
networkEnvInfo[n] = configHelper.parseEnvFile(filePath);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
if (supportedTypes.length === 0) {
|
45
|
+
clm.error('No supported networks found in the project folder.');
|
46
|
+
}
|
47
|
+
configStore.setNetworkInfo({ supportedTypes });
|
48
|
+
configStore.setNetworkEnvInfo(networkEnvInfo);
|
49
|
+
// eslint-disable-next-line no-warning-comments
|
50
|
+
// TODO: verify all required env variables are present
|
51
|
+
},
|
52
|
+
async installEmbedded(name) {
|
53
|
+
const projectFolder = path.resolve(path.dirname(fileURLToPath(import.meta.url)), `../../projects/${name}`);
|
54
|
+
if (!fs.existsSync(projectFolder)) {
|
55
|
+
clm.error(`Project folder not found: ${projectFolder}`);
|
56
|
+
}
|
57
|
+
await configStore.applyNewProjectStore(name);
|
58
|
+
const { projectDir } = configStore.getProjectInfo();
|
59
|
+
clm.debug(`Installing project from ${projectFolder} to ${projectDir}`);
|
60
|
+
fs.cpSync(projectFolder, projectDir, { recursive: true });
|
61
|
+
},
|
62
|
+
// curl -s https://api.github.com/repos/Constellation-Labs/pacaswap-metagraph/releases/latest | jq -r '.assets[] | select(.name | contains("node-pilot"))'
|
63
|
+
// use .tag_name for the release version
|
64
|
+
async installFromGithub(_repo) {
|
65
|
+
throw new Error('installFromGithub - Not implemented');
|
66
|
+
},
|
67
|
+
async installHypergraph() {
|
68
|
+
await this.installEmbedded('hypergraph');
|
69
|
+
const { projectDir } = configStore.getProjectInfo();
|
70
|
+
// Create app-data folders for fast forward feature before Docker does
|
71
|
+
const gl0DataDir = path.join(projectDir, 'app-data', 'gl0-data');
|
72
|
+
fs.mkdirSync(path.join(gl0DataDir, 'incremental_snapshot'), { recursive: true });
|
73
|
+
fs.mkdirSync(path.join(gl0DataDir, 'snapshot_info'));
|
74
|
+
fs.mkdirSync(path.join(gl0DataDir, 'tmp'));
|
75
|
+
// Set hypergraph layer defaults
|
76
|
+
configStore.setEnvLayerInfo('gl0', {
|
77
|
+
CL_CLI_HTTP_PORT: '9002', CL_DOCKER_JAVA_OPTS: '-Xms1024M -Xmx7G -Xss256K', CL_P2P_HTTP_PORT: '9001', CL_PUBLIC_HTTP_PORT: '9000'
|
78
|
+
});
|
79
|
+
configStore.setEnvLayerInfo('gl1', {
|
80
|
+
CL_CLI_HTTP_PORT: '9102', CL_DOCKER_JAVA_OPTS: '-Xms1024M -Xmx3G -Xss256K', CL_P2P_HTTP_PORT: '9101', CL_PUBLIC_HTTP_PORT: '9100'
|
81
|
+
});
|
82
|
+
await this.importNetworkEnvFiles();
|
83
|
+
await this.importLayerEnvFiles();
|
84
|
+
}
|
85
|
+
};
|
@@ -0,0 +1,8 @@
|
|
1
|
+
export declare const promptHelper: {
|
2
|
+
configureAutoRestart(): Promise<void>;
|
3
|
+
configureJavaMemoryArguments(): Promise<void>;
|
4
|
+
doYouWishToContinue(defaultAnswer?: "n" | "y"): Promise<void>;
|
5
|
+
selectLayers(): Promise<void>;
|
6
|
+
selectNetwork(): Promise<void>;
|
7
|
+
selectProject(): Promise<void>;
|
8
|
+
};
|
@@ -0,0 +1,135 @@
|
|
1
|
+
import { checkbox, input, number, select } from "@inquirer/prompts";
|
2
|
+
import chalk from "chalk";
|
3
|
+
import { clm } from "../clm.js";
|
4
|
+
import { configStore } from "../config-store.js";
|
5
|
+
import { githubHelper } from "./github-helper.js";
|
6
|
+
import { projectHelper } from "./project-helper.js";
|
7
|
+
export const promptHelper = {
|
8
|
+
async configureAutoRestart() {
|
9
|
+
const answer = await input({
|
10
|
+
default: 'y',
|
11
|
+
message: 'Do you want to enable auto-restart? (y/n): '
|
12
|
+
});
|
13
|
+
configStore.setProjectInfo({ autoRestart: answer === 'y' });
|
14
|
+
},
|
15
|
+
async configureJavaMemoryArguments() {
|
16
|
+
const { memory } = configStore.getSystemInfo();
|
17
|
+
const { layersToRun, name } = configStore.getProjectInfo();
|
18
|
+
const xmx = Number(memory);
|
19
|
+
if (xmx === 8 && layersToRun.length > 1) {
|
20
|
+
clm.warn('Minimum 8GB memory detected. Only a single layer will be allowed to run');
|
21
|
+
await promptHelper.doYouWishToContinue();
|
22
|
+
configStore.setProjectInfo({ layersToRun: [layersToRun[0]] });
|
23
|
+
configStore.setEnvLayerInfo(layersToRun[0], { CL_DOCKER_JAVA_OPTS: '-Xms1024M -Xmx7G -Xss256K' });
|
24
|
+
}
|
25
|
+
else if (name === 'hypergraph') {
|
26
|
+
// prompt to use all detected memory
|
27
|
+
let answer = await number({
|
28
|
+
default: xmx - 1,
|
29
|
+
message: `How much of the detected memory (${xmx}GB) do you want to use?: `,
|
30
|
+
validate: v => v !== undefined && v >= 4 && v <= xmx
|
31
|
+
});
|
32
|
+
if (answer === xmx)
|
33
|
+
answer--;
|
34
|
+
const subLayerMem = layersToRun.length > 1 ? Math.floor(answer / 3) : 0;
|
35
|
+
const mainLayerMem = answer - subLayerMem;
|
36
|
+
clm.postStep(`${layersToRun[0]} memory allocation: ${mainLayerMem}GB`);
|
37
|
+
configStore.setEnvLayerInfo(layersToRun[0], { CL_DOCKER_JAVA_OPTS: `-Xms1024M -Xmx${mainLayerMem}G -Xss256K` });
|
38
|
+
if (subLayerMem) {
|
39
|
+
clm.postStep(`${layersToRun[1]} memory allocation: ${subLayerMem}GB`);
|
40
|
+
configStore.setEnvLayerInfo(layersToRun[1], { CL_DOCKER_JAVA_OPTS: `-Xms1024M -Xmx${subLayerMem}G -Xss256K` });
|
41
|
+
}
|
42
|
+
}
|
43
|
+
},
|
44
|
+
async doYouWishToContinue(defaultAnswer = 'y') {
|
45
|
+
const result = await input({
|
46
|
+
default: defaultAnswer,
|
47
|
+
message: 'Do you wish to continue? (y/n)',
|
48
|
+
validate(value) {
|
49
|
+
if (value.toLowerCase() === 'y' || value.toLowerCase() === 'n') {
|
50
|
+
return true;
|
51
|
+
}
|
52
|
+
return 'Please enter "y" or "n"';
|
53
|
+
}
|
54
|
+
});
|
55
|
+
if (result.toLowerCase() === 'n') {
|
56
|
+
process.exit(0);
|
57
|
+
}
|
58
|
+
},
|
59
|
+
async selectLayers() {
|
60
|
+
const { name } = configStore.getProjectInfo();
|
61
|
+
const choices = name === 'hypergraph' ? ['gl0', 'gl1'] : ['ml0', 'cl1', 'dl1'];
|
62
|
+
const result = await checkbox({ choices, message: `Select network layers to run for ${chalk.cyan(name)}:`, validate: v => v.length > 0 });
|
63
|
+
configStore.setProjectInfo({ layersToRun: result });
|
64
|
+
},
|
65
|
+
async selectNetwork() {
|
66
|
+
const { supportedTypes } = configStore.getNetworkInfo();
|
67
|
+
if (supportedTypes.length === 0) {
|
68
|
+
throw new Error('No supported networks found');
|
69
|
+
}
|
70
|
+
if (supportedTypes.length === 1) {
|
71
|
+
configStore.setNetworkInfo({ type: supportedTypes[0], version: "latest" });
|
72
|
+
configStore.setEnvCommonInfo(configStore.getNetworkEnvInfo(supportedTypes[0]));
|
73
|
+
return;
|
74
|
+
}
|
75
|
+
const networkType = await select({
|
76
|
+
choices: [
|
77
|
+
{ disabled: !supportedTypes.includes('mainnet'), name: 'Mainnet', value: 'mainnet' },
|
78
|
+
{ disabled: !supportedTypes.includes('testnet'), name: 'Testnet', value: 'testnet' },
|
79
|
+
{ disabled: !supportedTypes.includes('integrationnet'), name: 'Integrationnet', value: 'integrationnet' }
|
80
|
+
],
|
81
|
+
message: 'Select network type:'
|
82
|
+
});
|
83
|
+
configStore.setNetworkInfo({ type: networkType, version: "latest" });
|
84
|
+
configStore.setEnvCommonInfo(configStore.getNetworkEnvInfo(networkType));
|
85
|
+
},
|
86
|
+
async selectProject() {
|
87
|
+
// prompt user to install hypergraph or metagraph
|
88
|
+
const networkType = await select({
|
89
|
+
choices: [
|
90
|
+
{ name: 'Hypergraph (Global layer)', value: 'hypergraph' },
|
91
|
+
{ disabled: true, name: 'Metagraph (Dor, Pacaswap, and more)', value: 'metagraph' },
|
92
|
+
],
|
93
|
+
message: 'Select network:'
|
94
|
+
});
|
95
|
+
if (networkType === 'hypergraph') {
|
96
|
+
await projectHelper.installHypergraph();
|
97
|
+
}
|
98
|
+
else if (networkType === 'metagraph') {
|
99
|
+
const project = await select({
|
100
|
+
choices: [
|
101
|
+
{ name: 'Dor', value: 'dor' },
|
102
|
+
{ name: 'Custom (Enter repo)', value: 'custom' },
|
103
|
+
],
|
104
|
+
message: 'Select metagraph:'
|
105
|
+
});
|
106
|
+
const ghRepoRegex = /^https?:\/\/(?:www\.)?github\.com\/([A-Za-z0-9](?:-?[A-Za-z0-9]){0,38})\/([A-Za-z0-9._-]+)(?:\.git)?\/?$/;
|
107
|
+
if (project === 'dor') {
|
108
|
+
await projectHelper.installEmbedded('dor');
|
109
|
+
}
|
110
|
+
else if (project === 'custom') {
|
111
|
+
let repo = await input({
|
112
|
+
message: `Enter Github repository URL:`,
|
113
|
+
validate(value) {
|
114
|
+
const m = value.trim().match(ghRepoRegex);
|
115
|
+
if (m)
|
116
|
+
return true;
|
117
|
+
return 'Please enter a valid Github repo URL (e.g., https://github.com/user/repo or .git)';
|
118
|
+
}
|
119
|
+
});
|
120
|
+
if (repo.endsWith('.git'))
|
121
|
+
repo = repo.slice(0, -4);
|
122
|
+
const m = repo.trim().match(ghRepoRegex);
|
123
|
+
const userRepo = `${m[1]}/${m[2]}`; // owner/repo
|
124
|
+
clm.preStep(`Installing from Github repository: ${chalk.cyan(userRepo)}`);
|
125
|
+
if (await githubHelper.hasAssetInRelease('node-pilot', userRepo)) {
|
126
|
+
await projectHelper.installFromGithub(userRepo);
|
127
|
+
}
|
128
|
+
else {
|
129
|
+
clm.warn(`The repository ${repo} does not contain a release asset with the name "node-pilot"`);
|
130
|
+
await this.selectProject();
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}
|
134
|
+
}
|
135
|
+
};
|
package/dist/index.d.ts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export { run } from '@oclif/core';
|
package/dist/index.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export { run } from '@oclif/core';
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,16 @@
|
|
1
|
+
/* DISPLAY INFORMATION ABOUT THE CLUSTER
|
2
|
+
1. number of nodes in cluster
|
3
|
+
2. your node id
|
4
|
+
3. is your node included in seedlist
|
5
|
+
4. is your node in the cluster
|
6
|
+
6. version of the node
|
7
|
+
*/
|
8
|
+
export {};
|
9
|
+
/* Verify cluster information (Health-check)
|
10
|
+
|
11
|
+
1. version match
|
12
|
+
2. included in seedlist
|
13
|
+
3. included in cluster
|
14
|
+
4. majority ordinal
|
15
|
+
5. majority snapshot hash matches
|
16
|
+
*/
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { ClusterInfo, NodeInfo } from "../types.js";
|
2
|
+
export declare const clusterService: {
|
3
|
+
fastForwardSnapshot(): Promise<void>;
|
4
|
+
getClusterInfo(): Promise<ClusterInfo[]>;
|
5
|
+
getLatestOrdinal(): Promise<any>;
|
6
|
+
getNodeInfo(): Promise<NodeInfo>;
|
7
|
+
getReleaseVersion(): Promise<string>;
|
8
|
+
getSourceNodeInfo(): Promise<NodeInfo>;
|
9
|
+
};
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import { input } from "@inquirer/prompts";
|
2
|
+
import { configStore } from "../config-store.js";
|
3
|
+
import { FastforwardService } from "./fastforward-service.js";
|
4
|
+
export const clusterService = {
|
5
|
+
async fastForwardSnapshot() {
|
6
|
+
const { fastForward } = configStore.getProjectInfo();
|
7
|
+
if (fastForward === false) {
|
8
|
+
return;
|
9
|
+
}
|
10
|
+
if (!configStore.getProjectInfo().layersToRun.includes('gl0')) {
|
11
|
+
return;
|
12
|
+
}
|
13
|
+
if (fastForward === undefined) {
|
14
|
+
const answer = await input({
|
15
|
+
default: 'y', message: 'Do you want to use the snapshot fast forward feature? (y/n): '
|
16
|
+
});
|
17
|
+
if (answer !== 'y') {
|
18
|
+
configStore.setProjectInfo({ fastForward: false });
|
19
|
+
return;
|
20
|
+
}
|
21
|
+
configStore.setProjectInfo({ fastForward: true });
|
22
|
+
}
|
23
|
+
await FastforwardService.synctoLatestSnapshot();
|
24
|
+
},
|
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(() => []);
|
34
|
+
},
|
35
|
+
async getLatestOrdinal() {
|
36
|
+
const { type } = configStore.getNetworkInfo();
|
37
|
+
return fetch(`https://l0-lb-${type}.constellationnetwork.io/global-snapshots/latest`)
|
38
|
+
.then(res => {
|
39
|
+
if (res.ok)
|
40
|
+
return res.json().then(i => (i?.value?.ordinal || 0));
|
41
|
+
return 0;
|
42
|
+
})
|
43
|
+
.catch(() => 0);
|
44
|
+
},
|
45
|
+
async getNodeInfo() {
|
46
|
+
const { type } = configStore.getNetworkInfo();
|
47
|
+
return fetch(`https://l0-lb-${type}.constellationnetwork.io/node/info`)
|
48
|
+
.then(res => {
|
49
|
+
if (res.ok)
|
50
|
+
return res.json();
|
51
|
+
throw new Error(`Failed`);
|
52
|
+
})
|
53
|
+
.catch(() => ({ state: "Unavailable" }));
|
54
|
+
},
|
55
|
+
async getReleaseVersion() {
|
56
|
+
return this.getNodeInfo().then(i => i.version);
|
57
|
+
},
|
58
|
+
async getSourceNodeInfo() {
|
59
|
+
const { CL_L0_PEER_HTTP_HOST, CL_L0_PEER_HTTP_PORT } = configStore.getEnvCommonInfo();
|
60
|
+
return fetch(`http://${CL_L0_PEER_HTTP_HOST}:${CL_L0_PEER_HTTP_PORT}/node/info`)
|
61
|
+
.then(res => {
|
62
|
+
if (res.ok)
|
63
|
+
return res.json();
|
64
|
+
throw new Error(`Failed`);
|
65
|
+
});
|
66
|
+
}
|
67
|
+
};
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import 'json-bigint-patch';
|
2
|
+
export declare class FastforwardService {
|
3
|
+
private readonly dataDir;
|
4
|
+
private readonly lbUrl;
|
5
|
+
private readonly network;
|
6
|
+
private readonly tmpDir;
|
7
|
+
constructor();
|
8
|
+
static synctoLatestSnapshot(): Promise<void>;
|
9
|
+
runFastForwardSnapshot(): Promise<void>;
|
10
|
+
private fetchLatestSnapshot;
|
11
|
+
private fetchSnapshot;
|
12
|
+
private fetchSnapshotHash;
|
13
|
+
private saveSnapshotFiles;
|
14
|
+
}
|