@constellation-network/node-pilot 0.13.0-testnet → 0.13.1-testnet

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 CHANGED
@@ -21,7 +21,7 @@ $ npm install -g @constellation-network/node-pilot
21
21
  $ cpilot COMMAND
22
22
  running command...
23
23
  $ cpilot (--version|-v)
24
- @constellation-network/node-pilot/0.13.0-testnet darwin-arm64 node-v22.15.0
24
+ @constellation-network/node-pilot/0.13.1-testnet darwin-arm64 node-v22.15.0
25
25
  $ cpilot --help [COMMAND]
26
26
  USAGE
27
27
  $ cpilot COMMAND
@@ -47,6 +47,7 @@ If no command is entered, node-pilot will automatically perform a series of chec
47
47
  * [`cpilot restart [LAYER]`](#cpilot-restart-layer)
48
48
  * [`cpilot shutdown`](#cpilot-shutdown)
49
49
  * [`cpilot status`](#cpilot-status)
50
+ * [`cpilot uninstall`](#cpilot-uninstall)
50
51
 
51
52
  ## `cpilot clean [LAYER]`
52
53
 
@@ -71,7 +72,7 @@ EXAMPLES
71
72
  $ cpilot clean
72
73
  ```
73
74
 
74
- _See code: [src/commands/clean.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.0-testnet/src/commands/clean.ts)_
75
+ _See code: [src/commands/clean.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.1-testnet/src/commands/clean.ts)_
75
76
 
76
77
  ## `cpilot config`
77
78
 
@@ -88,7 +89,7 @@ EXAMPLES
88
89
  $ cpilot config
89
90
  ```
90
91
 
91
- _See code: [src/commands/config.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.0-testnet/src/commands/config.ts)_
92
+ _See code: [src/commands/config.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.1-testnet/src/commands/config.ts)_
92
93
 
93
94
  ## `cpilot config get [NAME]`
94
95
 
@@ -112,7 +113,7 @@ EXAMPLES
112
113
  $ cpilot config get gl0:CL_PUBLIC_HTTP_PORT
113
114
  ```
114
115
 
115
- _See code: [src/commands/config/get.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.0-testnet/src/commands/config/get.ts)_
116
+ _See code: [src/commands/config/get.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.1-testnet/src/commands/config/get.ts)_
116
117
 
117
118
  ## `cpilot config set NAME VALUE`
118
119
 
@@ -135,7 +136,7 @@ EXAMPLES
135
136
  $ cpilot config set gl0:CL_PUBLIC_HTTP_PORT 9000
136
137
  ```
137
138
 
138
- _See code: [src/commands/config/set.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.0-testnet/src/commands/config/set.ts)_
139
+ _See code: [src/commands/config/set.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.1-testnet/src/commands/config/set.ts)_
139
140
 
140
141
  ## `cpilot help [COMMAND]`
141
142
 
@@ -172,7 +173,7 @@ EXAMPLES
172
173
  $ cpilot info
173
174
  ```
174
175
 
175
- _See code: [src/commands/info.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.0-testnet/src/commands/info.ts)_
176
+ _See code: [src/commands/info.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.1-testnet/src/commands/info.ts)_
176
177
 
177
178
  ## `cpilot logs LAYER`
178
179
 
@@ -198,7 +199,7 @@ EXAMPLES
198
199
  $ cpilot logs
199
200
  ```
200
201
 
201
- _See code: [src/commands/logs.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.0-testnet/src/commands/logs.ts)_
202
+ _See code: [src/commands/logs.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.1-testnet/src/commands/logs.ts)_
202
203
 
203
204
  ## `cpilot restart [LAYER]`
204
205
 
@@ -226,7 +227,7 @@ EXAMPLES
226
227
  $ cpilot restart
227
228
  ```
228
229
 
229
- _See code: [src/commands/restart.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.0-testnet/src/commands/restart.ts)_
230
+ _See code: [src/commands/restart.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.1-testnet/src/commands/restart.ts)_
230
231
 
231
232
  ## `cpilot shutdown`
232
233
 
@@ -243,7 +244,7 @@ EXAMPLES
243
244
  $ cpilot shutdown
244
245
  ```
245
246
 
246
- _See code: [src/commands/shutdown.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.0-testnet/src/commands/shutdown.ts)_
247
+ _See code: [src/commands/shutdown.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.1-testnet/src/commands/shutdown.ts)_
247
248
 
248
249
  ## `cpilot status`
249
250
 
@@ -257,5 +258,22 @@ DESCRIPTION
257
258
  Display node status and configuration settings
258
259
  ```
259
260
 
260
- _See code: [src/commands/status.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.0-testnet/src/commands/status.ts)_
261
+ _See code: [src/commands/status.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.1-testnet/src/commands/status.ts)_
262
+
263
+ ## `cpilot uninstall`
264
+
265
+ Uninstall Node Pilot
266
+
267
+ ```
268
+ USAGE
269
+ $ cpilot uninstall
270
+
271
+ DESCRIPTION
272
+ Uninstall Node Pilot
273
+
274
+ EXAMPLES
275
+ $ cpilot uninstall
276
+ ```
277
+
278
+ _See code: [src/commands/uninstall.ts](https://github.com/Constellation-Labs/node-pilot/blob/v0.13.1-testnet/src/commands/uninstall.ts)_
261
279
  <!-- commandsstop -->
@@ -22,22 +22,29 @@ export const checkDependencies = async () => {
22
22
  }
23
23
  const isDockerInstalled = await shellService.checkCommandAvailable('docker');
24
24
  const pilotDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), `../..`);
25
- if (fs.existsSync(path.join(pilotDir, 'install-dependencies.sh'))) {
26
- if (!isDockerInstalled) {
27
- clm.step('Docker is required and needs to be installed.');
28
- await promptHelper.doYouWishToContinue();
29
- await installDependencies(pilotDir);
30
- clm.postStep(`\nDocker has been installed. Please logout and login again to ensure the changes take effect. Then run cpilot again.`);
31
- clm.echo('');
32
- process.exit(0);
25
+ const hasInstallScript = fs.existsSync(path.join(pilotDir, 'install-dependencies.sh'));
26
+ if (isDockerInstalled) {
27
+ const user = os.userInfo().username;
28
+ clm.preStep(`Checking if user ${user} has been added to the docker group...`);
29
+ const groups = await shellService.runCommandWithOutput('groups');
30
+ if (groups.includes('docker')) {
31
+ clm.postStep('✅ User has already been added');
33
32
  }
34
- const isACLInstalled = await shellService.checkCommandAvailable('setfacl');
35
- if (!isACLInstalled && os.platform() === 'linux') {
36
- clm.step('ACL is required and needs to be installed.');
37
- await promptHelper.doYouWishToContinue();
38
- await installDependencies(pilotDir);
33
+ else {
34
+ await shellService.runCommand(`sudo usermod -aG docker ${user}`);
35
+ clm.postStep(`\n✅ Added user "${user}" to the docker group`);
36
+ clm.warn('\nPlease logout and login again in order for the group changes take effect.\n');
37
+ process.exit(0);
39
38
  }
40
39
  }
40
+ else if (hasInstallScript) {
41
+ clm.step('Docker is required and needs to be installed.');
42
+ await promptHelper.doYouWishToContinue();
43
+ await installDependencies(pilotDir);
44
+ clm.postStep(`\nDocker has been installed. Please logout and login again to ensure the changes take effect. Then run cpilot again.`);
45
+ clm.echo('');
46
+ process.exit(0);
47
+ }
41
48
  };
42
49
  async function installDependencies(pilotDir) {
43
50
  clm.debug(`Running install-dependencies.sh from ${pilotDir}`);
@@ -1,3 +1,13 @@
1
1
  export declare const checkInitialSetup: {
2
+ checkExistingUsers(): Promise<void>;
2
3
  firstTimeRun(): Promise<void>;
4
+ getExistingInstallations(): Promise<{
5
+ currentUser: string;
6
+ otherUsers: never[];
7
+ rootUserHasPilot?: undefined;
8
+ } | {
9
+ currentUser: string;
10
+ otherUsers: string[];
11
+ rootUserHasPilot: boolean;
12
+ }>;
3
13
  };
@@ -1,19 +1,67 @@
1
- import chalk from "chalk";
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { clm } from "../clm.js";
2
5
  import { configStore } from "../config-store.js";
6
+ import { shellService } from "../services/shell-service.js";
3
7
  import { systemdService } from "../services/systemd-service.js";
4
8
  import { checkDependencies } from "./check-dependencies.js";
5
9
  import { checkHardware } from "./check-hardware.js";
6
10
  export const checkInitialSetup = {
11
+ async checkExistingUsers() {
12
+ const { otherUsers } = await this.getExistingInstallations();
13
+ if (otherUsers.length > 0) {
14
+ clm.warn(`There are other users that have Node Pilot installed.\n ${otherUsers.join('\n ')}`);
15
+ clm.warn('To uninstall for another user, login as the user and run "cpilot uninstall".');
16
+ clm.error('Node Pilot can only be installed for one user at a time.\n');
17
+ }
18
+ },
7
19
  async firstTimeRun() {
8
- if (configStore.getSystemInfo() !== null) {
20
+ if (configStore.hasProjectFlag('firstTimeChecked')) {
9
21
  return;
10
22
  }
11
23
  await checkDependencies();
12
- console.log(chalk.whiteBright("\n ****************************************"));
13
- console.log(" " + chalk.whiteBright("CONSTELLATION NETWORK") + " ");
14
- console.log(" " + chalk.whiteBright("NODE PILOT") + " ");
15
- console.log(chalk.whiteBright(" ****************************************"));
16
- await checkHardware.systemRequirements();
24
+ await this.checkExistingUsers();
25
+ if (configStore.getSystemInfo() === null) {
26
+ await checkHardware.systemRequirements();
27
+ }
28
+ // console.log(chalk.whiteBright("\n ****************************************"));
29
+ // console.log(" " + chalk.whiteBright("CONSTELLATION NETWORK") + " ");
30
+ // console.log(" " + chalk.whiteBright("NODE PILOT") + " ");
31
+ // console.log(chalk.whiteBright(" ****************************************"));
17
32
  await systemdService.install();
33
+ configStore.setProjectFlag('firstTimeChecked', true);
18
34
  },
35
+ async getExistingInstallations() {
36
+ const platform = os.platform();
37
+ const isLinux = platform === 'linux';
38
+ const homeFolder = isLinux ? '/home' : '/Users';
39
+ const currentUser = os.userInfo().username;
40
+ if (!fs.existsSync(homeFolder)) {
41
+ clm.warn(`Home folder path does not exist: ${homeFolder}`);
42
+ return { currentUser, otherUsers: [] };
43
+ }
44
+ const userFolders = fs.readdirSync(homeFolder).filter(file => fs.lstatSync(path.join(homeFolder, file)).isDirectory());
45
+ const usersWithPilot = [];
46
+ for (const folder of userFolders) {
47
+ const dirPath = path.join(homeFolder, folder, '.node-pilot');
48
+ // eslint-disable-next-line no-await-in-loop
49
+ const exists = await shellService.runCommandWithOutput(`sudo test -d "${dirPath}" && echo 1 || echo 0`);
50
+ if (exists === '1')
51
+ usersWithPilot.push(folder);
52
+ }
53
+ let rootUserHasPilot = false;
54
+ if (isLinux) {
55
+ const dirPath = path.join('/root', '.node-pilot');
56
+ const exists = await shellService.runCommandWithOutput(`sudo test -d "${dirPath}" && echo 1 || echo 0`);
57
+ if (exists === '1') {
58
+ usersWithPilot.push('root');
59
+ rootUserHasPilot = true;
60
+ }
61
+ }
62
+ // console.log(`Current currentUser: ${currentUser}`);
63
+ // console.log(`Users with Node Pilot installed: ${usersWithPilot.join(', ')}`);
64
+ // console.log(`Root currentUser has Node Pilot installed: ${rootUserHasPilot}`);
65
+ return { currentUser, otherUsers: usersWithPilot.filter(u => u !== currentUser), rootUserHasPilot };
66
+ }
19
67
  };
@@ -1,6 +1,7 @@
1
1
  import semver from "semver";
2
2
  export declare const checkNodePilot: {
3
3
  checkDiscordRegistration(): Promise<void>;
4
+ checkMultipleUsers(): Promise<void>;
4
5
  checkVersion(): Promise<void>;
5
6
  compareVersions(): Promise<{
6
7
  currentVer: undefined;
@@ -1,6 +1,5 @@
1
1
  import os from "node:os";
2
2
  import semver from "semver";
3
- import packageJson from '../../package.json' with { type: 'json' };
4
3
  import { clm } from "../clm.js";
5
4
  import { configStore } from "../config-store.js";
6
5
  import { healthCheckConfig } from "../helpers/health-check-config.js";
@@ -9,6 +8,7 @@ import { promptHelper } from "../helpers/prompt-helper.js";
9
8
  import { dockerService } from "../services/docker-service.js";
10
9
  import { nodeService } from "../services/node-service.js";
11
10
  import { shellService } from "../services/shell-service.js";
11
+ import { checkInitialSetup } from "./check-initial-setup.js";
12
12
  const REGISTRY_URL = 'https://registry.npmjs.org/';
13
13
  export const checkNodePilot = {
14
14
  async checkDiscordRegistration() {
@@ -18,6 +18,17 @@ export const checkNodePilot = {
18
18
  await this.promptDiscordRegistration();
19
19
  configStore.setProjectFlag('discordChecked', true);
20
20
  },
21
+ async checkMultipleUsers() {
22
+ if (configStore.hasProjectFlag('multipleUsersChecked')) {
23
+ return;
24
+ }
25
+ const { currentUser, otherUsers } = await checkInitialSetup.getExistingInstallations();
26
+ if (otherUsers.length > 0) {
27
+ clm.warn(`Multiple users have Node Pilot installed.\n ${currentUser} <-- current user\n ${otherUsers.join('\n ')}`);
28
+ clm.error('Login and run "cpilot uninstall" to remove the extra installation(s).');
29
+ }
30
+ configStore.setProjectFlag('multipleUsersChecked', true);
31
+ },
21
32
  async checkVersion() {
22
33
  const { currentVer, latestVer } = await this.compareVersions();
23
34
  if (!latestVer || !currentVer || latestVer.compare(currentVer) === 0)
@@ -60,13 +71,14 @@ export const checkNodePilot = {
60
71
  await shellService.runCommand(`sudo npm install -g @constellation-network/node-pilot@${latestVer.version}`);
61
72
  if (hasMajorMinorChange) {
62
73
  clm.step('Updating scripts and configuration files...');
63
- await projectHelper.upgradeHypergraph();
74
+ projectHelper.upgradeHypergraph();
64
75
  }
65
76
  clm.postStep('Update completed. Run cpilot again to use the latest version');
66
77
  process.exit(0);
67
78
  },
68
79
  async compareVersions() {
69
- const packageUrl = new URL(encodeURIComponent(packageJson.name).replace(/^%40/, '@'), REGISTRY_URL);
80
+ const { name: pilotReleaseName, version: pilotVersion } = configStore.getPilotReleaseInfo();
81
+ const packageUrl = new URL(encodeURIComponent(pilotReleaseName).replace(/^%40/, '@'), REGISTRY_URL);
70
82
  const headers = {
71
83
  accept: 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*',
72
84
  };
@@ -74,10 +86,24 @@ export const checkNodePilot = {
74
86
  if (!result) {
75
87
  return { currentVer: undefined, latestVer: undefined };
76
88
  }
77
- const { type } = configStore.getNetworkInfo();
78
- const networkTag = type === 'testnet' ? 'testnet' : 'latest';
79
- const latestVer = semver.parse(result['dist-tags'][networkTag]);
80
- const currentVer = semver.parse(packageJson.version);
89
+ let distTag = 'latest';
90
+ if (pilotVersion.includes('testnet')) {
91
+ distTag = 'testnet';
92
+ }
93
+ else if (pilotVersion.includes('omegatest')) {
94
+ distTag = 'omegatest';
95
+ }
96
+ else if (pilotVersion.includes('intnet')) {
97
+ distTag = 'intnet';
98
+ }
99
+ const latestVer = semver.parse(result['dist-tags'][distTag]);
100
+ const currentVer = semver.parse(pilotVersion);
101
+ if (latestVer !== null && currentVer !== null) {
102
+ clm.debug(`Current version: ${currentVer?.version}, Latest version: ${latestVer?.version}`);
103
+ }
104
+ else {
105
+ clm.warn(`Unable to resolve version from dist-tag ${distTag} - ${JSON.stringify(result['dist-tags'])}`);
106
+ }
81
107
  return { currentVer, latestVer };
82
108
  },
83
109
  isDiscordAlertsEnabled() {
@@ -117,7 +143,7 @@ export const checkNodePilot = {
117
143
  const hasMajorMinorChange = latestVer.major !== currentVer.major || latestVer.minor !== currentVer.minor;
118
144
  if (hasMajorMinorChange) {
119
145
  clm.step('Updating scripts and configuration files...');
120
- await projectHelper.upgradeHypergraph();
146
+ projectHelper.upgradeHypergraph();
121
147
  }
122
148
  }
123
149
  };
@@ -25,7 +25,7 @@ export default class Config extends Command {
25
25
  { name: 'Java Memory', value: 'javaMemory' },
26
26
  { name: 'Key File', value: 'keyFile' },
27
27
  { name: 'Layers To Run', value: 'layersToRun' },
28
- { name: `Network`, value: 'network' },
28
+ { name: `Hypergraph Network`, value: 'network' },
29
29
  ],
30
30
  message: 'What would you like to change?:',
31
31
  });
@@ -1,5 +1,4 @@
1
1
  import { Command } from '@oclif/core';
2
- import packageJson from '../../package.json' with { type: 'json' };
3
2
  import { checkNodePilot } from "../checks/check-pilot.js";
4
3
  import { configStore } from "../config-store.js";
5
4
  import { configHelper } from "../helpers/config-helper.js";
@@ -14,10 +13,11 @@ export default class Info extends Command {
14
13
  const networkInfo = configStore.getNetworkInfo();
15
14
  const { CL_EXTERNAL_IP: currentIpAddress } = configStore.getEnvInfo();
16
15
  const { CL_DOCKER_JAVA_OPTS } = configStore.getEnvLayerInfo(networkInfo.type, 'gl0');
16
+ const { version } = configStore.getPilotReleaseInfo();
17
17
  // Project Name
18
18
  configHelper.showEnvInfo('Project Name', projectInfo.name);
19
19
  // Pilot Version
20
- configHelper.showEnvInfo('Node Pilot Version', packageJson.version);
20
+ configHelper.showEnvInfo('Node Pilot Version', version);
21
21
  // External IP Address
22
22
  configHelper.showEnvInfo('External IP Address', currentIpAddress);
23
23
  // DAG Address
@@ -2,31 +2,28 @@ import { Command } from '@oclif/core';
2
2
  import { checkInitialSetup } from "../checks/check-initial-setup.js";
3
3
  import { checkLayers } from "../checks/check-layers.js";
4
4
  import { checkNetwork } from "../checks/check-network.js";
5
- import { checkNodeCtl } from "../checks/check-node-ctl.js";
6
5
  import { checkNodePilot } from "../checks/check-pilot.js";
7
6
  import { checkProject } from "../checks/check-project.js";
8
7
  import { checkWallet } from "../checks/check-wallet.js";
9
8
  import { keyFileHelper } from "../helpers/key-file-helper.js";
10
9
  import { migrationService } from "../services/migration-service.js";
11
10
  export default class Status extends Command {
12
- // eslint-disable-next-line no-warning-comments
13
- // TODO add -f flag to continuously monitor status
14
11
  static description = 'Display node status and configuration settings';
15
12
  async run() {
16
13
  await checkInstallationAndConfigurationStatus();
17
14
  }
18
15
  }
19
16
  export async function checkInstallationAndConfigurationStatus() {
20
- migrationService.runMigrations();
21
17
  await checkInitialSetup.firstTimeRun();
18
+ await checkNodePilot.checkMultipleUsers();
22
19
  await checkProject.projectInstallation();
20
+ await checkNodePilot.checkVersion();
21
+ migrationService.runMigrations();
23
22
  await checkProject.checkJavaMemory();
24
23
  await checkNetwork.checkExternalIpAddress();
25
24
  await checkNetwork.isNetworkConnectable();
26
25
  await checkNodePilot.checkDiscordRegistration();
27
- await checkNodePilot.checkVersion();
28
26
  await checkProject.releaseVersion();
29
- await checkNodeCtl.check4Migration();
30
27
  await keyFileHelper.promptIfNoKeyFile();
31
28
  await checkNetwork.checkSeedList();
32
29
  await checkNetwork.checkForExistingNodeIdInCluster();
@@ -0,0 +1,6 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Uninstall extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<void>;
6
+ }
@@ -0,0 +1,24 @@
1
+ import { Command } from '@oclif/core';
2
+ import chalk from "chalk";
3
+ import os from "node:os";
4
+ import { clm } from "../clm.js";
5
+ import { promptHelper } from "../helpers/prompt-helper.js";
6
+ import { shellService } from "../services/shell-service.js";
7
+ import { systemdService } from "../services/systemd-service.js";
8
+ export default class Uninstall extends Command {
9
+ static description = 'Uninstall Node Pilot';
10
+ static examples = [
11
+ '<%= config.bin %> <%= command.id %>',
12
+ ];
13
+ async run() {
14
+ const user = os.userInfo().username;
15
+ clm.warn(`You are about to uninstall Node Pilot for ${chalk.cyan(user)}.\nThis will also remove all the data and logs from your validator node.`);
16
+ await promptHelper.doYouWishToContinue();
17
+ clm.preStep('Uninstalling Node Pilot...');
18
+ await systemdService.uninstall();
19
+ const homeDir = os.homedir();
20
+ const nodePilotDir = `${homeDir}/.node-pilot`;
21
+ await shellService.runCommand(`sudo rm -rf ${nodePilotDir}`);
22
+ console.log(`Node Pilot uninstalled successfully for ${chalk.cyan(user)}.`);
23
+ }
24
+ }
@@ -12,6 +12,10 @@ declare class ConfigStore {
12
12
  getEnvNetworkInfo(network: NetworkType): EnvNetworkInfo;
13
13
  getLayerPortInfo(layer: TessellationLayer): PortInfo;
14
14
  getNetworkInfo(): NetworkInfo;
15
+ getPilotReleaseInfo(): {
16
+ name: string;
17
+ version: string;
18
+ };
15
19
  getProjectInfo(): ProjectInfo;
16
20
  getProjects(): string[];
17
21
  getRunningProjects(): string[];
@@ -3,7 +3,9 @@ import { JSONStorage } from "node-localstorage";
3
3
  import fs from "node:fs";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
+ import packageJson from '../package.json' with { type: 'json' };
6
7
  import { clm } from "./clm.js";
8
+ const pilotReleaseInfo = { name: packageJson.name, version: packageJson.version };
7
9
  class EmptyStorage extends JSONStorage {
8
10
  constructor() { super("/tmp"); }
9
11
  getItem(_key) { return null; }
@@ -82,6 +84,9 @@ class ConfigStore {
82
84
  getNetworkInfo() {
83
85
  return this.projectStore.getItem('network');
84
86
  }
87
+ getPilotReleaseInfo() {
88
+ return pilotReleaseInfo;
89
+ }
85
90
  getProjectInfo() {
86
91
  return this.projectStore.getItem('project') || {};
87
92
  }
@@ -79,6 +79,8 @@ export const projectHelper = {
79
79
  clm.error(`Project folder not found: ${projectFolder}`);
80
80
  }
81
81
  await this.installProject(name, projectFolder);
82
+ // Set the project version to match the latest Pilot version. This prevents previous migration scripts from running.
83
+ configStore.setProjectInfo({ version: configStore.getPilotReleaseInfo().version });
82
84
  },
83
85
  // curl -s https://api.github.com/repos/Constellation-Labs/pacaswap-metagraph/releases/latest | jq -r '.assets[] | select(.name | contains("node-pilot"))'
84
86
  // use .tag_name for the release version
@@ -87,14 +89,15 @@ export const projectHelper = {
87
89
  },
88
90
  async installHypergraph() {
89
91
  await this.installEmbedded('hypergraph');
90
- const { projectDir } = configStore.getProjectInfo();
91
- const { platform } = configStore.getSystemInfo();
92
92
  this.prepareDataFolder();
93
- if (platform === 'linux') {
94
- const layerDir = path.join(projectDir, 'gl0');
95
- // set permission for group "docker" on the layer folder and any subfolders created later
96
- await shellService.runCommand(`sudo setfacl -Rm g:docker:rwX -dm g:docker:rwX ${layerDir}`);
97
- }
93
+ // const {projectDir} = configStore.getProjectInfo();
94
+ // const {platform} = configStore.getSystemInfo();
95
+ //
96
+ // if (platform === 'linux') {
97
+ // const layerDir = path.join(projectDir,'gl0');
98
+ // // set permission for group "docker" on the layer folder and any subfolders created later
99
+ // await shellService.runCommand(`sudo setfacl -Rm g:docker:rwX -dm g:docker:rwX ${layerDir}`)
100
+ // }
98
101
  this.importEnvFiles();
99
102
  },
100
103
  async installProject(name, projectFolder) {
@@ -1,5 +1,4 @@
1
1
  import semver from "semver";
2
- import packageJson from '../../package.json' with { type: 'json' };
3
2
  import { clm } from "../clm.js";
4
3
  import { configStore } from "../config-store.js";
5
4
  export const migrationService = {
@@ -9,18 +8,18 @@ export const migrationService = {
9
8
  // add more migrations as needed
10
9
  };
11
10
  const { version = '0.0.0' } = configStore.getProjectInfo();
11
+ const { version: pilotVersion } = configStore.getPilotReleaseInfo();
12
12
  const lastMigratedVersion = semver.parse(version);
13
- const currentVersion = semver.parse(packageJson.version);
13
+ const currentVersion = semver.parse(pilotVersion);
14
14
  if (!lastMigratedVersion || !currentVersion) {
15
15
  return;
16
16
  }
17
- // console.log(`Running migrations from ${lastMigratedVersion.version} to ${currentVersion.version}`);
18
- // console.log(`semver.gt(v, version): ${semver.gt('0.8.0', version)}`);
19
- // console.log(`semver.lte(v, currentVersion.version): ${semver.lte('0.8.0', currentVersion.version)}`)
17
+ clm.debug(`Running migrations from ${lastMigratedVersion.version} to ${currentVersion.version}`);
18
+ clm.debug(`semver.gt(v, version): ${semver.gt('0.8.0', version)}`);
19
+ clm.debug(`semver.lte(v, currentVersion.version): ${semver.lte('0.8.0', currentVersion.version)}`);
20
20
  const migrationVersions = Object.keys(migrations)
21
21
  .filter(v => semver.gt(v, version) && semver.lte(v, currentVersion.version))
22
22
  .sort(semver.compare);
23
- // TODO shutdown node if migration is in progress
24
23
  if (migrationVersions.length > 0) {
25
24
  clm.preStep(`Migration versions to run: ${migrationVersions}`);
26
25
  for (const version of migrationVersions) {
@@ -7,7 +7,7 @@ import { configStore } from "../config-store.js";
7
7
  export const shellService = {
8
8
  async checkCommandAvailable(cmd) {
9
9
  clm.debug(`Checking if command ${cmd} is available...`);
10
- return this.runCommand(`command -v ${cmd}`)
10
+ return this.runCommand(`command -v ${cmd}`, undefined, process.env.DEBUG !== 'true')
11
11
  .then(() => true)
12
12
  .catch((error) => {
13
13
  clm.debug(`Run command error: ${error}`);
@@ -16,8 +16,9 @@ export const shellService = {
16
16
  },
17
17
  async execDockerShell(serviceName, command) {
18
18
  const { projectDir } = configStore.getProjectInfo();
19
+ const silent = process.env.DEBUG !== 'true';
19
20
  clm.debug(`Running command: docker compose exec ${serviceName} bash -c "${command}"`);
20
- const result = shell.exec(`docker compose exec ${serviceName} bash -c "${command}"`, { cwd: projectDir, silent: true });
21
+ const result = shell.exec(`docker compose exec ${serviceName} bash -c "${command}"`, { cwd: projectDir, silent });
21
22
  if (result.stderr && result.code > 0) {
22
23
  clm.warn(`Command Failed - ${command} with stderr: ${result.stderr}`);
23
24
  throw new Error(`Command Failed - ${command} with stderr: ${result.stderr}`);
@@ -46,13 +47,15 @@ export const shellService = {
46
47
  const result = shell.exec(command, { env: nodeEnv, silent });
47
48
  clm.debug(`END ${command}. Exit code: ${result.code}`);
48
49
  if (result.code > 0) {
49
- throw new Error(`Failed running command: ${result.stderr}`);
50
+ throw new Error(`Failed running command. Exited with error message:\n${result.stderr}`);
50
51
  }
51
52
  return result;
52
53
  },
53
54
  async runCommandWithOutput(command, env) {
54
55
  const result = await this.runCommand(command, env, true);
55
- return result.stdout.trim();
56
+ const output = result.stdout.trim();
57
+ clm.debug(`Command Output: "${output}"`);
58
+ return output;
56
59
  },
57
60
  async runProjectCommand(command, env, silent = false) {
58
61
  const { projectDir } = configStore.getProjectInfo();
@@ -1,3 +1,4 @@
1
1
  export declare const systemdService: {
2
2
  install(): Promise<void>;
3
+ uninstall(): Promise<void>;
3
4
  };
@@ -1,22 +1,35 @@
1
1
  import fs from "node:fs";
2
+ import os from "node:os";
2
3
  import path from "node:path";
3
4
  import { fileURLToPath } from "node:url";
4
5
  import { clm } from "../clm.js";
5
- import { configStore } from "../config-store.js";
6
6
  import { shellService } from "./shell-service.js";
7
7
  export const systemdService = {
8
8
  async install() {
9
- const { platform } = configStore.getSystemInfo();
9
+ const platform = os.platform();
10
10
  if (platform !== 'linux') {
11
11
  clm.warn('Node Pilot services can only be installed on Linux systems. Skipping systemd service installation.\n');
12
12
  return;
13
13
  }
14
- const installServicesScript = path.resolve(path.dirname(fileURLToPath(import.meta.url)), `../../scripts/install_services.sh`);
15
- if (!fs.existsSync(installServicesScript)) {
16
- clm.error(`Node Pilot install system services script not found: ${installServicesScript}`);
14
+ const scriptFile = path.resolve(path.dirname(fileURLToPath(import.meta.url)), `../../scripts/install_services.sh`);
15
+ if (!fs.existsSync(scriptFile)) {
16
+ clm.error(`Node Pilot install system services script not found: ${scriptFile}`);
17
17
  }
18
18
  clm.preStep('Installing Node Pilot system services...');
19
- await shellService.runCommand(installServicesScript);
19
+ await shellService.runCommand(scriptFile);
20
20
  clm.postStep('\nNode Pilot system service started successfully.');
21
+ },
22
+ async uninstall() {
23
+ const platform = os.platform();
24
+ if (platform !== 'linux') {
25
+ clm.warn('Node Pilot services can only be installed on Linux systems. Skipping systemd service uninstallation.\n');
26
+ }
27
+ const scriptFile = path.resolve(path.dirname(fileURLToPath(import.meta.url)), `../../scripts/uninstall_services.sh`);
28
+ if (!fs.existsSync(scriptFile)) {
29
+ clm.error(`Node Pilot uninstall system services script not found: ${scriptFile}`);
30
+ }
31
+ clm.preStep('Uninstalling Node Pilot system services...');
32
+ await shellService.runCommand(scriptFile);
33
+ clm.postStep('\nNode Pilot system service uninstalled successfully.');
21
34
  }
22
35
  };
@@ -67,5 +67,5 @@ check_docker() {
67
67
 
68
68
  # Run all checks
69
69
  echo "🔍 Checking and installing required dependencies..."
70
- check_acl
70
+ # check_acl
71
71
  check_docker
@@ -272,6 +272,29 @@
272
272
  "test.js"
273
273
  ]
274
274
  },
275
+ "uninstall": {
276
+ "aliases": [],
277
+ "args": {},
278
+ "description": "Uninstall Node Pilot",
279
+ "examples": [
280
+ "<%= config.bin %> <%= command.id %>"
281
+ ],
282
+ "flags": {},
283
+ "hasDynamicHelp": false,
284
+ "hiddenAliases": [],
285
+ "id": "uninstall",
286
+ "pluginAlias": "@constellation-network/node-pilot",
287
+ "pluginName": "@constellation-network/node-pilot",
288
+ "pluginType": "core",
289
+ "strict": true,
290
+ "enableJsonFlag": false,
291
+ "isESM": true,
292
+ "relativePath": [
293
+ "dist",
294
+ "commands",
295
+ "uninstall.js"
296
+ ]
297
+ },
275
298
  "config:get": {
276
299
  "aliases": [],
277
300
  "args": {
@@ -340,5 +363,5 @@
340
363
  ]
341
364
  }
342
365
  },
343
- "version": "0.13.0-testnet"
366
+ "version": "0.13.1-testnet"
344
367
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@constellation-network/node-pilot",
3
3
  "description": "An easy deployment and monitoring tool for Constellation nodes.",
4
- "version": "0.13.0-testnet",
4
+ "version": "0.13.1-testnet",
5
5
  "author": "Frank Fox",
6
6
  "bin": {
7
7
  "cpilot": "bin/run.js"
@@ -69,7 +69,6 @@
69
69
  "dist",
70
70
  "oclif.manifest.json",
71
71
  "install-dependencies.sh",
72
- "install-java-21.sh",
73
72
  "projects",
74
73
  "scripts",
75
74
  "README.md",
@@ -26,7 +26,7 @@ services:
26
26
  test: ["CMD", "cpilotHC"]
27
27
  # test: ["CMD", "/health-check/bin/run.sh"]
28
28
  interval: 15s
29
- retries: 1
29
+ retries: 3
30
30
  timeout: 15s
31
31
  start_period: 60s
32
32
  profiles: ["gl0"]
@@ -50,7 +50,7 @@ services:
50
50
  test: ["CMD", "cpilotHC"]
51
51
  # test: ["CMD", "/health-check/bin/run.sh"]
52
52
  interval: 15s
53
- retries: 1
53
+ retries: 3
54
54
  timeout: 15s
55
55
  start_period: 60s
56
56
  profiles: ["gl1"]
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env bash
2
+ set -Eeuo pipefail
3
+ IFS=$'\n\t'
2
4
 
3
5
  # Find cpilot in PATH
4
6
  CPILOT=$(command -v cpilot)
@@ -16,7 +18,7 @@ if [ "$CURRENT_USER" = "root" ]; then
16
18
  UNIT_TARGET="multi-user.target"
17
19
  else
18
20
  IS_ROOT=false
19
- HOME_DIR=$(eval echo ~$CURRENT_USER)
21
+ HOME_DIR=$(eval echo ~"$CURRENT_USER")
20
22
  WORKING_DIR="$HOME_DIR/.config/systemd/user"
21
23
  UNIT_TARGET="default.target"
22
24
  fi
@@ -24,10 +26,14 @@ fi
24
26
  BIN_DIRECTORY="$HOME_DIR/.node-pilot/scripts"
25
27
 
26
28
  mkdir -p "$BIN_DIRECTORY"
27
-
28
- # Create working directory if it doesn't exist
29
29
  mkdir -p "$WORKING_DIR"
30
30
 
31
+ # Only check the first service file for existence
32
+ if [ -f "$WORKING_DIR/restart-unhealthy.service" ]; then
33
+ echo "restart-unhealthy.service already exists in $WORKING_DIR. Skipping installation."
34
+ exit 0
35
+ fi
36
+
31
37
  # Create restart-unhealthy.service
32
38
  cat << EOF > "$WORKING_DIR/restart-unhealthy.service"
33
39
  [Unit]
@@ -61,7 +67,6 @@ cat << EOF > "$BIN_DIRECTORY/restart-unhealthy.sh"
61
67
  #!/usr/bin/env bash
62
68
  docker ps -q -f health=unhealthy | xargs --no-run-if-empty docker restart
63
69
  EOF
64
-
65
70
  chmod +x "$BIN_DIRECTORY/restart-unhealthy.sh"
66
71
 
67
72
  # Create node-pilot-autostart.service
@@ -116,4 +121,4 @@ else
116
121
  loginctl enable-linger "$CURRENT_USER"
117
122
  fi
118
123
  #systemctl --user status node-pilot-update.service
119
- # systemctl --user status restart-unhealthy
124
+ # systemctl --user status restart-unhealthy
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Uninstall Node Pilot systemctl services and timers
4
+
5
+ CURRENT_USER=$(id -un)
6
+ if [ "$CURRENT_USER" = "root" ]; then
7
+ IS_ROOT=true
8
+ WORKING_DIR="/etc/systemd/system"
9
+ else
10
+ IS_ROOT=false
11
+ HOME_DIR=$(eval echo ~$CURRENT_USER)
12
+ WORKING_DIR="$HOME_DIR/.config/systemd/user"
13
+ fi
14
+
15
+ SERVICES=(restart-unhealthy.timer restart-unhealthy.service node-pilot-autostart.service node-pilot-update.service)
16
+
17
+ if [ "$IS_ROOT" = true ]; then
18
+ for SERVICE in "${SERVICES[@]}"; do
19
+ systemctl stop "$SERVICE" 2>/dev/null
20
+ systemctl disable "$SERVICE" 2>/dev/null
21
+ rm -f "$WORKING_DIR/$SERVICE"
22
+ echo "$SERVICE uninstalled."
23
+ done
24
+ systemctl daemon-reload
25
+ else
26
+ for SERVICE in "${SERVICES[@]}"; do
27
+ systemctl --user stop "$SERVICE" 2>/dev/null
28
+ systemctl --user disable "$SERVICE" 2>/dev/null
29
+ rm -f "$WORKING_DIR/$SERVICE"
30
+ echo "$SERVICE uninstalled ($CURRENT_USER)."
31
+ done
32
+ systemctl --user daemon-reload
33
+ # Optionally disable linger (uncomment if needed)
34
+ loginctl disable-linger "$CURRENT_USER"
35
+ fi
36
+
37
+ echo "Node Pilot services and timers have been uninstalled."
@@ -1,5 +0,0 @@
1
- export declare const checkNodeCtl: {
2
- check4Migration(): Promise<void>;
3
- importKeyInfo(cnPath: string): Promise<void>;
4
- promptForKeyFile(pilotKeyPath: string): Promise<void>;
5
- };
@@ -1,88 +0,0 @@
1
- import { input, password } from "@inquirer/prompts";
2
- import chalk from "chalk";
3
- import fs from "node:fs";
4
- import path from "node:path";
5
- import yaml from "yaml";
6
- import { clm } from "../clm.js";
7
- import { configStore } from "../config-store.js";
8
- import { keyFileHelper } from "../helpers/key-file-helper.js";
9
- import { promptHelper } from "../helpers/prompt-helper.js";
10
- import { dockerService } from "../services/docker-service.js";
11
- import { nodeService } from "../services/node-service.js";
12
- import { shellService } from "../services/shell-service.js";
13
- export const checkNodeCtl = {
14
- async check4Migration() {
15
- if (configStore.hasProjectFlag('nodeCtlChecked')) {
16
- return;
17
- }
18
- const { name } = configStore.getProjectInfo();
19
- if (name.toLowerCase() !== 'hypergraph') {
20
- configStore.setProjectFlag('nodeCtlChecked', true);
21
- return;
22
- }
23
- const hasNodeAdminUser = fs.existsSync('/home/nodeadmin');
24
- if (hasNodeAdminUser) {
25
- const isDockerRunning = await dockerService.isRunning();
26
- const isPortOpen = await nodeService.isPortInUse(9000);
27
- clm.step(chalk.bold('NODECTL has been detected.'));
28
- if (!isDockerRunning && isPortOpen) {
29
- clm.error('Please shutdown any Nodes being managed by NODECTL before proceeding.');
30
- }
31
- const cnPath = path.resolve('/var/tessellation/nodectl/cn-config.yaml');
32
- if (fs.existsSync(cnPath)) {
33
- const answer = await input({
34
- default: 'y',
35
- message: 'Would you like to import key file from nodectl? (y/n): '
36
- });
37
- if (answer.toLowerCase() === 'y') {
38
- await this.importKeyInfo(cnPath);
39
- }
40
- }
41
- }
42
- configStore.setProjectFlag('nodeCtlChecked', true);
43
- },
44
- async importKeyInfo(cnPath) {
45
- clm.step('Importing key file from nodectl...');
46
- try {
47
- clm.preStep('Making a sudo call to read the nodectl config file...');
48
- await shellService.runCommand(`sudo chmod +r ${cnPath}`);
49
- const doc = yaml.parse(fs.readFileSync(cnPath, 'utf8'));
50
- // console.log(JSON.stringify(doc,null,2));
51
- // eslint-disable-next-line camelcase
52
- const { key_location, key_name } = doc.nodectl.global_p12;
53
- const nodeCtlKeyPath = path.resolve(key_location, key_name);
54
- clm.debug(`Found key file path in nodectl config file: ${nodeCtlKeyPath}`);
55
- clm.step('Key file found at ' + chalk.cyan(nodeCtlKeyPath));
56
- clm.preStep('Importing key file...');
57
- const { projectDir } = configStore.getProjectInfo();
58
- const pilotKeyPath = path.join(projectDir, 'key.p12');
59
- // copy file to home directory, change owner to current user, and make it readable by all
60
- clm.preStep('Making a sudo call to copy the key file...');
61
- await shellService.runCommand(`sudo cp ${nodeCtlKeyPath} ${pilotKeyPath}; sudo chown $(whoami) ${pilotKeyPath}; chmod +r ${pilotKeyPath}`);
62
- await this.promptForKeyFile(pilotKeyPath);
63
- }
64
- catch (error) {
65
- console.error(error);
66
- clm.warn('Failed to import key information from nodectl. You will need to import it manually.');
67
- await promptHelper.doYouWishToContinue();
68
- }
69
- },
70
- async promptForKeyFile(pilotKeyPath) {
71
- // prompt for password
72
- const keyPassword = await password({ message: 'Enter the key file password:' });
73
- const keyAlias = await input({ message: 'Enter the key file alias:' });
74
- configStore.setEnvInfo({ CL_KEYALIAS: keyAlias, CL_KEYSTORE: pilotKeyPath, CL_PASSWORD: keyPassword });
75
- try {
76
- const dagAddress = await keyFileHelper.getAddress();
77
- const nodeId = await keyFileHelper.getId();
78
- configStore.setProjectInfo({ dagAddress, nodeId });
79
- }
80
- catch {
81
- clm.warn('Failed to unlock the key file. Please check your key file information and try again.');
82
- fs.rmSync(pilotKeyPath);
83
- await this.promptForKeyFile(pilotKeyPath);
84
- return;
85
- }
86
- clm.postStep('Key file imported successfully.\n');
87
- }
88
- };
@@ -1,110 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- # Break on any error
4
- set -e
5
-
6
-
7
- check_java_home() {
8
- # echo "Checking if JAVA_HOME env is set"
9
-
10
- if [ -z "$JAVA_HOME" ] || ! grep -q 'JAVA_HOME' "$HOME/.bashrc"; then
11
- JAVA_HOME_LINE='export JAVA_HOME="$(dirname "$(dirname "$(readlink -f "$(which java)")")")"'
12
- echo "JAVA_HOME is not set. Attempting to set automatically"
13
- echo "Please ensure the following line is in your ~/.bashrc or ~/.zshrc file:"
14
- echo "Script will attempt to now add it for you and run it, but this only adds to .bashrc"
15
- echo $JAVA_HOME_LINE
16
- echo "$JAVA_HOME_LINE" >> $HOME/.bashrc
17
- echo "Adding JAVA_HOME to current environment"
18
- eval $JAVA_HOME_LINE
19
- echo "JAVA_HOME is now set to: $JAVA_HOME"
20
- fi
21
-
22
- }
23
-
24
- # Check and install Java 21
25
- check_java() {
26
- # echo "Checking for Java 21..."
27
- if command -v java >/dev/null 2>&1; then
28
- java_version=$(java -version 2>&1 | awk -F '"' '/version/ {print $2}')
29
- # echo "Found Java version: $java_version"
30
- if [[ "$java_version" == 21* ]] || [[ "$java_version" == 1.21* ]]; then
31
- echo "✅ Java 21 is installed."
32
- return 0
33
- else
34
- echo "⚠️ Java is installed but not version 21. Will attempt to install Java 21."
35
- fi
36
- else
37
- echo "⚠️ Java not found. Will attempt to install Java 21."
38
- fi
39
-
40
-
41
- case "$(uname)" in
42
- Linux)
43
- if command -v apt >/dev/null 2>&1; then
44
- echo "Installing Java 21 using apt..."
45
- sudo apt install -y openjdk-21-jdk
46
- elif command -v yum >/dev/null 2>&1; then
47
- echo "Installing Java 21 using yum..."
48
- sudo yum install -y java-21-openjdk-devel
49
- else
50
- echo "⚠️ Unsupported Linux distribution. Please install Java 21 manually."
51
- return 1
52
- fi
53
- ;;
54
- Darwin)
55
- if command -v brew >/dev/null 2>&1; then
56
- echo "Installing Java 21 using Homebrew..."
57
- brew tap adoptopenjdk/openjdk
58
- brew install --cask adoptopenjdk21
59
- else
60
- echo "⚠️ Homebrew not found. Please install Java 21 manually."
61
- return 1
62
- fi
63
- ;;
64
- MINGW*|MSYS*|CYGWIN*)
65
- echo "On Windows, please install Java 21 manually from https://adoptopenjdk.net/"
66
- return 1
67
- ;;
68
- *)
69
- echo "⚠️ Unsupported OS: $(uname). Please install Java 21 manually."
70
- return 1
71
- ;;
72
- esac
73
-
74
- echo "✅ Java 21 installation complete."
75
- return 0
76
- }
77
-
78
- check_system_update() {
79
- echo ""
80
- echo "Checking for system updates..."
81
- echo ""
82
-
83
- if [ "$(uname)" = "Linux" ]; then
84
- sudo apt-get update && sudo apt-get upgrade -y
85
- echo "System packages updated."
86
- fi
87
- }
88
-
89
- check_system_update
90
- check_java
91
- check_java_home
92
-
93
- if [ -f /var/run/reboot-required ]; then
94
- echo ""
95
- echo "⚠️ A system reboot is required to complete updates. Please reboot your system."
96
- echo ""
97
- echo "Then run 'cpilot' to get started"
98
- echo ""
99
- echo "Would you like to reboot now? (y/n)"
100
- read -r response
101
- if [[ ! "$response" =~ ^[Yy]$ ]]; then
102
- echo "Reboot skipped. Please reboot manually to proceed."
103
- else
104
- sudo reboot
105
- fi
106
- else
107
- echo ""
108
- echo "Run 'cpilot' to get started"
109
- fi
110
-