diff --git a/evernode-bootstrap-contract b/evernode-bootstrap-contract index e40c3ae..1e0e816 160000 --- a/evernode-bootstrap-contract +++ b/evernode-bootstrap-contract @@ -1 +1 @@ -Subproject commit e40c3ae7053458ff3c3b72cc66359bf02b811dab +Subproject commit 1e0e816abbb12f317f0a447c059e0081b78c266a diff --git a/installer/jshelper/index.js b/installer/jshelper/index.js index a4a477a..897764f 100644 --- a/installer/jshelper/index.js +++ b/installer/jshelper/index.js @@ -4,6 +4,10 @@ const evernode = require("evernode-js-client"); const process = require("process"); const fs = require("fs"); const ip6addr = require('ip6addr'); +const keypairs = require('ripple-keypairs'); +const http = require('http'); +const crypto = require('crypto'); +const { appenv } = require("../../mb-xrpl/lib/appenv"); function checkParams(args, count) { for (let i = 0; i < count; i++) { @@ -18,11 +22,16 @@ const funcs = { 'validate-server': async (args) => { checkParams(args, 1); const rippledUrl = args[0]; - const xrplApi = new evernode.XrplApi(rippledUrl, { autoReconnect: false }); + await evernode.Defaults.useNetwork(appenv.NETWORK); + evernode.Defaults.set({ + rippledServer: rippledUrl + }); + const xrplApi = new evernode.XrplApi(null, { autoReconnect: false }); await xrplApi.connect(); await xrplApi.disconnect(); return { success: true }; }, + 'validate-account': async (args) => { checkParams(args, 3); const rippledUrl = args[0]; @@ -30,15 +39,22 @@ const funcs = { const accountAddress = args[2]; const validateFor = args[3] || "register"; - const xrplApi = new evernode.XrplApi(rippledUrl, { autoReconnect: false }); + await evernode.Defaults.useNetwork(appenv.NETWORK); + + evernode.Defaults.set({ + rippledServer: rippledUrl, + governorAddress: governorAddress + }); + + const xrplApi = new evernode.XrplApi(null, { autoReconnect: false }); await xrplApi.connect(); - const hostClient = new evernode.HostClient(accountAddress, null, { - rippledServer: rippledUrl, - governorAddress: governorAddress, + evernode.Defaults.set({ xrplApi: xrplApi }); + const hostClient = new evernode.HostClient(accountAddress, null); + if (!await hostClient.xrplAcc.exists()) return { success: false, result: "Account not found." }; @@ -71,15 +87,26 @@ const funcs = { await xrplApi.disconnect(); return { success: true }; }, + 'validate-keys': async (args) => { checkParams(args, 3); const rippledUrl = args[0]; const accountAddress = args[1]; const accountSecret = args[2]; - const xrplApi = new evernode.XrplApi(rippledUrl, { autoReconnect: false }); + await evernode.Defaults.useNetwork(appenv.NETWORK); + + evernode.Defaults.set({ + rippledServer: rippledUrl + }); + + const xrplApi = new evernode.XrplApi(null, { autoReconnect: false }); await xrplApi.connect(); + evernode.Defaults.set({ + xrplApi: xrplApi + }); + const xrplAcc = new evernode.XrplAccount(accountAddress, accountSecret, { xrplApi: xrplApi }); @@ -91,31 +118,33 @@ const funcs = { }, 'access-evernode-cfg': async (args) => { - checkParams(args, 4); + checkParams(args, 3); const rippledUrl = args[0]; const governorAddress = args[1]; - const accountAddress = args[2]; - const configName = args[3]; + const configName = args[2]; - const xrplApi = new evernode.XrplApi(rippledUrl, { autoReconnect: false }); + await evernode.Defaults.useNetwork(appenv.NETWORK); + + evernode.Defaults.set({ + rippledServer: rippledUrl, + governorAddress: governorAddress + }); + + const xrplApi = new evernode.XrplApi(null, { autoReconnect: false }); await xrplApi.connect(); - const hostClient = new evernode.HostClient(accountAddress, null, { - rippledServer: rippledUrl, - governorAddress: governorAddress, + evernode.Defaults.set({ xrplApi: xrplApi }); - if (!await hostClient.xrplAcc.exists()) - return { success: false, result: "Account not found." }; + const governorClient = await evernode.HookClientFactory.create(evernode.HookTypes.governor); + await governorClient.connect(); + const config = await governorClient.config; - await hostClient.connect(); - const config = hostClient.config; - - await hostClient.disconnect(); + await governorClient.disconnect(); await xrplApi.disconnect(); - return { success: true, result: config[configName] }; + return { success: true, result: typeof config[configName] === 'object' ? JSON.stringify(config[configName]) : `${config[configName]}` }; }, 'transfer': async (args) => { @@ -126,15 +155,22 @@ const funcs = { const accountSecret = args[3]; const transfereeAddress = args[4]; - const xrplApi = new evernode.XrplApi(rippledUrl, { autoReconnect: false }); + await evernode.Defaults.useNetwork(appenv.NETWORK); + + evernode.Defaults.set({ + rippledServer: rippledUrl, + governorAddress: governorAddress + }); + + const xrplApi = new evernode.XrplApi(null, { autoReconnect: false }); await xrplApi.connect(); - const hostClient = new evernode.HostClient(accountAddress, accountSecret, { - rippledServer: rippledUrl, - governorAddress: governorAddress, + evernode.Defaults.set({ xrplApi: xrplApi }); + const hostClient = new evernode.HostClient(accountAddress, accountSecret); + if (!await hostClient.xrplAcc.exists()) return { success: false, result: "Account not found." }; @@ -203,6 +239,223 @@ const funcs = { } return { success: false }; + }, + + 'check-balance': async (args) => { + checkParams(args, 5); + const rippledUrl = args[0]; + const governorAddress = args[1]; + const accountAddress = args[2]; + const tokenType = args[3]; + const expectedBalance = args[4]; + + const WAIT_PERIOD = 120; // seconds + + await evernode.Defaults.useNetwork(appenv.NETWORK); + + evernode.Defaults.set({ + rippledServer: rippledUrl, + governorAddress: governorAddress + }); + + const xrplApi = new evernode.XrplApi(null, { autoReconnect: false }); + await xrplApi.connect(); + + evernode.Defaults.set({ + xrplApi: xrplApi + }); + + const hostClient = new evernode.HostClient(accountAddress, null); + const terminateConnections = async () => { + await hostClient.disconnect(); + await xrplApi.disconnect(); + } + + let attempts = 0; + let balance = 0; + while (attempts >= 0) { + try { + // In order to handle the account not found issue via catch block. + await hostClient.connect(); + + await new Promise(resolve => setTimeout(resolve, 1000)); + if (tokenType === 'NATIVE') + balance = Number((await hostClient.xrplAcc.getInfo()).Balance) / 1000000; + else + balance = Number(await hostClient.getEVRBalance()); + + if (balance < expectedBalance) { + if (++attempts <= WAIT_PERIOD) + continue; + + await terminateConnections(); + return { success: false, result: "Funds not received within timeout." }; + } + + break; + } catch (err) { + if (err.data?.error === 'actNotFound' && ++attempts <= WAIT_PERIOD) { + await new Promise(resolve => setTimeout(resolve, 1000)); + continue; + } + await terminateConnections(); + return { success: false, result: (err.data?.error === 'actNotFound') ? "Funds not received within timeout." : "Error occurred in account balance check." }; + } + } + + await terminateConnections(); + return { success: true, result: `${balance}` }; + }, + + 'generate-account': async (args) => { + let seed = null; + if (args[0]) + seed = args[0]; + else + seed = keypairs.generateSeed({ algorithm: "ecdsa-secp256k1" }); + + const keypair = keypairs.deriveKeypair(seed); + const createdKeypair = { + address: keypairs.deriveAddress(keypair.publicKey), + secret: seed + } + return { success: true, result: typeof createdKeypair === 'object' ? JSON.stringify(createdKeypair) : `${createdKeypair}` }; + }, + + 'prepare-host': async (args) => { + checkParams(args, 4); + const rippledUrl = args[0]; + const governorAddress = args[1]; + const accountAddress = args[2]; + const accountSecret = args[3]; + // Optional + const domain = args[4] ? args[4] : ""; + + const WAIT_PERIOD = 120; // seconds + + await evernode.Defaults.useNetwork(appenv.NETWORK); + + evernode.Defaults.set({ + rippledServer: rippledUrl, + governorAddress: governorAddress + }); + + const xrplApi = new evernode.XrplApi(null, { autoReconnect: false }); + await xrplApi.connect(); + + evernode.Defaults.set({ + xrplApi: xrplApi + }); + + const hostClient = new evernode.HostClient(accountAddress, accountSecret); + await hostClient.connect(); + + const terminateConnections = async () => { + await hostClient.disconnect(); + await xrplApi.disconnect(); + } + + { + let attempts = 0; + while (attempts >= 0) { + try { + await hostClient.prepareAccount(domain); + break; + } + catch (err) { + if (err.data?.error === 'actNotFound' && ++attempts <= WAIT_PERIOD) { + // Wait and retry. + await new Promise(resolve => setTimeout(resolve, 1000)); + continue; + } + + await terminateConnections(); + return { success: false, result: "Error occurred in account preparation." }; + } + } + } + + await terminateConnections(); + return { success: true }; + + }, + + // Starts an HTTP server on port 80 and check whether that's reachable via + // the provided domain. + 'validate-domain': async (args) => { + checkParams(args, 2); + const domain = args[0]; + const port = parseInt(args[1]); + const urlPath = "/" + crypto.randomBytes(16).toString('hex'); + const responseString = crypto.randomBytes(16).toString('hex'); + + const server = http.createServer((req, res) => { + if (req.url === urlPath) { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end(responseString + '\n'); + } else { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('Not Found\n'); + } + }); + + try { + await new Promise((resolve, reject) => { + server.on('error', function (e) { + // We assume this is an error when starting to listen. + reject("listen_error"); + }); + + server.listen(port, () => { + // Server started. Now send a request via public domain. + + const reqOptions = { + hostname: domain, + port: port, + path: urlPath, + method: "GET" + }; + + const req = http.request(reqOptions, (res) => { + let data = ""; + + res.on("data", (chunk) => { + data += chunk; + }); + + // request completion event. + res.on("end", () => { + server.close(); + if (data.startsWith(responseString)) { + resolve(); + } else { + // Return string does not match our responseString. Most probably response was + // sent by some other server. Not by us. + reject("domain_error") + } + }); + }); + + req.on("error", (e) => { + server.close(); + reject("domain_error") + }); + + req.setTimeout(3000, () => { // 3 second request timeout + req.destroy(); + server.close(); + reject("domain_error"); + }); + + req.end(); + }); + }); + + return { success: true, result: "ok" }; + + } catch (errorCode) { + return { success: false, result: errorCode }; + } } } diff --git a/installer/jshelper/package-lock.json b/installer/jshelper/package-lock.json index ba831f4..95e80b7 100644 --- a/installer/jshelper/package-lock.json +++ b/installer/jshelper/package-lock.json @@ -6,8 +6,9 @@ "": { "name": "evernode-setup-helper", "dependencies": { - "evernode-js-client": "0.6.20", - "ip6addr": "0.2.5" + "evernode-js-client": "0.6.21", + "ip6addr": "0.2.5", + "ripple-keypairs": "1.3.1" } }, "node_modules/@noble/hashes": { @@ -363,9 +364,9 @@ } }, "node_modules/evernode-js-client": { - "version": "0.6.20", - "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.20.tgz", - "integrity": "sha512-OC6VNAhwqnNvUc0NhffxwNI9bTDH+BkD/KBTC5Xuwoiq8BhRfYhmfHBnD6M9K5AvLqv+Jxdufc3l1AlzHgILWg==", + "version": "0.6.21", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.21.tgz", + "integrity": "sha512-Q5P6caMTzx3xaUNKhnP1vJTw3wTP/d2J2xSQEMn4m1+t/t67d8+eii3/FeQapRBSZEbNRHm9EbRry9PJhb9xcg==", "dependencies": { "elliptic": "6.5.4", "libsodium-wrappers": "0.7.10", @@ -376,6 +377,27 @@ "xrpl-binary-codec": "1.4.2" } }, + "node_modules/evernode-js-client/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "node_modules/evernode-js-client/node_modules/ripple-keypairs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-1.1.0.tgz", + "integrity": "sha512-Zlmbtn2YUpW4uKlLm2/tpkY5RC/EXQlkJwIIKp0AoF9D23pJ43/EuipNW2F6qURdbkUezDwB0bMV7uRXip3x2w==", + "dependencies": { + "bn.js": "^5.1.1", + "brorand": "^1.0.5", + "elliptic": "^6.5.4", + "hash.js": "^1.0.3", + "ripple-address-codec": "^4.2.0" + }, + "engines": { + "node": ">= 10", + "npm": ">=7.0.0" + } + }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -818,19 +840,18 @@ } }, "node_modules/ripple-keypairs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-1.1.0.tgz", - "integrity": "sha512-Zlmbtn2YUpW4uKlLm2/tpkY5RC/EXQlkJwIIKp0AoF9D23pJ43/EuipNW2F6qURdbkUezDwB0bMV7uRXip3x2w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-1.3.1.tgz", + "integrity": "sha512-dmPlraWKJciFJxHcoubDahGnoIalG5e/BtV6HNDUs7wLXmtnLMHt6w4ed9R8MTL2zNrVPiIdI/HCtMMo0Tm7JQ==", "dependencies": { "bn.js": "^5.1.1", "brorand": "^1.0.5", "elliptic": "^6.5.4", "hash.js": "^1.0.3", - "ripple-address-codec": "^4.2.0" + "ripple-address-codec": "^4.3.1" }, "engines": { - "node": ">= 10", - "npm": ">=7.0.0" + "node": ">= 10" } }, "node_modules/ripple-keypairs/node_modules/bn.js": { @@ -838,6 +859,18 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, + "node_modules/ripple-keypairs/node_modules/ripple-address-codec": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-4.3.1.tgz", + "integrity": "sha512-Qa3+9wKVvpL/xYtT6+wANsn0A1QcC5CT6IMZbRJZ/1lGt7gmwIfsrCuz1X0+LCEO7zgb+3UT1I1dc0k/5dwKQQ==", + "dependencies": { + "base-x": "^3.0.9", + "create-hash": "^1.1.2" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/ripple-secret-codec": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ripple-secret-codec/-/ripple-secret-codec-1.0.3.tgz", @@ -1119,21 +1152,6 @@ "node": ">= 10" } }, - "node_modules/xrpl-accountlib/node_modules/ripple-keypairs": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-1.3.0.tgz", - "integrity": "sha512-LzM3Up9Pwz3dYqnczzNptimN3AxtjeGbDGeiOzREzbkslKiZcJ615b/ghBN4H23SC6W1GAL95juEzzimDi4THw==", - "dependencies": { - "bn.js": "^5.1.1", - "brorand": "^1.0.5", - "elliptic": "^6.5.4", - "hash.js": "^1.0.3", - "ripple-address-codec": "^4.3.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/xrpl-binary-codec": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/xrpl-binary-codec/-/xrpl-binary-codec-1.4.2.tgz", @@ -1190,38 +1208,6 @@ "ripple-keypairs": "^1.1.5" } }, - "node_modules/xrpl-secret-numbers/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, - "node_modules/xrpl-secret-numbers/node_modules/ripple-address-codec": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-4.3.0.tgz", - "integrity": "sha512-Tvd81i7hpDmNqHvkj6iYlj8Tv3I1Romw5gfjni9eacewJvGV2xe+p2y0FAw39z72qfciRMhQyHvpnviBcWVBNw==", - "dependencies": { - "base-x": "^3.0.9", - "create-hash": "^1.1.2" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/xrpl-secret-numbers/node_modules/ripple-keypairs": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-1.3.0.tgz", - "integrity": "sha512-LzM3Up9Pwz3dYqnczzNptimN3AxtjeGbDGeiOzREzbkslKiZcJ615b/ghBN4H23SC6W1GAL95juEzzimDi4THw==", - "dependencies": { - "bn.js": "^5.1.1", - "brorand": "^1.0.5", - "elliptic": "^6.5.4", - "hash.js": "^1.0.3", - "ripple-address-codec": "^4.3.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/xrpl-sign-keypairs": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xrpl-sign-keypairs/-/xrpl-sign-keypairs-2.2.0.tgz", @@ -1233,11 +1219,6 @@ "ripple-keypairs": "^1.1.4" } }, - "node_modules/xrpl-sign-keypairs/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, "node_modules/xrpl-sign-keypairs/node_modules/ripple-address-codec": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-4.3.0.tgz", @@ -1267,26 +1248,6 @@ "node": ">= 10" } }, - "node_modules/xrpl-sign-keypairs/node_modules/ripple-keypairs": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-1.3.0.tgz", - "integrity": "sha512-LzM3Up9Pwz3dYqnczzNptimN3AxtjeGbDGeiOzREzbkslKiZcJ615b/ghBN4H23SC6W1GAL95juEzzimDi4THw==", - "dependencies": { - "bn.js": "^5.1.1", - "brorand": "^1.0.5", - "elliptic": "^6.5.4", - "hash.js": "^1.0.3", - "ripple-address-codec": "^4.3.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/xrpl/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, "node_modules/xrpl/node_modules/ripple-address-codec": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-4.3.0.tgz", @@ -1299,21 +1260,6 @@ "node": ">= 10" } }, - "node_modules/xrpl/node_modules/ripple-keypairs": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-1.3.0.tgz", - "integrity": "sha512-LzM3Up9Pwz3dYqnczzNptimN3AxtjeGbDGeiOzREzbkslKiZcJ615b/ghBN4H23SC6W1GAL95juEzzimDi4THw==", - "dependencies": { - "bn.js": "^5.1.1", - "brorand": "^1.0.5", - "elliptic": "^6.5.4", - "hash.js": "^1.0.3", - "ripple-address-codec": "^4.3.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/yaeti": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", @@ -1597,9 +1543,9 @@ } }, "evernode-js-client": { - "version": "0.6.20", - "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.20.tgz", - "integrity": "sha512-OC6VNAhwqnNvUc0NhffxwNI9bTDH+BkD/KBTC5Xuwoiq8BhRfYhmfHBnD6M9K5AvLqv+Jxdufc3l1AlzHgILWg==", + "version": "0.6.21", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.21.tgz", + "integrity": "sha512-Q5P6caMTzx3xaUNKhnP1vJTw3wTP/d2J2xSQEMn4m1+t/t67d8+eii3/FeQapRBSZEbNRHm9EbRry9PJhb9xcg==", "requires": { "elliptic": "6.5.4", "libsodium-wrappers": "0.7.10", @@ -1608,6 +1554,25 @@ "xrpl": "2.2.1", "xrpl-accountlib": "2.2.0", "xrpl-binary-codec": "1.4.2" + }, + "dependencies": { + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "ripple-keypairs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-1.1.0.tgz", + "integrity": "sha512-Zlmbtn2YUpW4uKlLm2/tpkY5RC/EXQlkJwIIKp0AoF9D23pJ43/EuipNW2F6qURdbkUezDwB0bMV7uRXip3x2w==", + "requires": { + "bn.js": "^5.1.1", + "brorand": "^1.0.5", + "elliptic": "^6.5.4", + "hash.js": "^1.0.3", + "ripple-address-codec": "^4.2.0" + } + } } }, "ext": { @@ -1943,21 +1908,30 @@ } }, "ripple-keypairs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-1.1.0.tgz", - "integrity": "sha512-Zlmbtn2YUpW4uKlLm2/tpkY5RC/EXQlkJwIIKp0AoF9D23pJ43/EuipNW2F6qURdbkUezDwB0bMV7uRXip3x2w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-1.3.1.tgz", + "integrity": "sha512-dmPlraWKJciFJxHcoubDahGnoIalG5e/BtV6HNDUs7wLXmtnLMHt6w4ed9R8MTL2zNrVPiIdI/HCtMMo0Tm7JQ==", "requires": { "bn.js": "^5.1.1", "brorand": "^1.0.5", "elliptic": "^6.5.4", "hash.js": "^1.0.3", - "ripple-address-codec": "^4.2.0" + "ripple-address-codec": "^4.3.1" }, "dependencies": { "bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "ripple-address-codec": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-4.3.1.tgz", + "integrity": "sha512-Qa3+9wKVvpL/xYtT6+wANsn0A1QcC5CT6IMZbRJZ/1lGt7gmwIfsrCuz1X0+LCEO7zgb+3UT1I1dc0k/5dwKQQ==", + "requires": { + "base-x": "^3.0.9", + "create-hash": "^1.1.2" + } } } }, @@ -2134,11 +2108,6 @@ "ws": "^8.2.2" }, "dependencies": { - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, "ripple-address-codec": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-4.3.0.tgz", @@ -2147,18 +2116,6 @@ "base-x": "^3.0.9", "create-hash": "^1.1.2" } - }, - "ripple-keypairs": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-1.3.0.tgz", - "integrity": "sha512-LzM3Up9Pwz3dYqnczzNptimN3AxtjeGbDGeiOzREzbkslKiZcJ615b/ghBN4H23SC6W1GAL95juEzzimDi4THw==", - "requires": { - "bn.js": "^5.1.1", - "brorand": "^1.0.5", - "elliptic": "^6.5.4", - "hash.js": "^1.0.3", - "ripple-address-codec": "^4.3.0" - } } } }, @@ -2209,18 +2166,6 @@ "decimal.js": "^10.2.0", "ripple-address-codec": "^4.3.0" } - }, - "ripple-keypairs": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-1.3.0.tgz", - "integrity": "sha512-LzM3Up9Pwz3dYqnczzNptimN3AxtjeGbDGeiOzREzbkslKiZcJ615b/ghBN4H23SC6W1GAL95juEzzimDi4THw==", - "requires": { - "bn.js": "^5.1.1", - "brorand": "^1.0.5", - "elliptic": "^6.5.4", - "hash.js": "^1.0.3", - "ripple-address-codec": "^4.3.0" - } } } }, @@ -2274,34 +2219,6 @@ "@types/brorand": "^1.0.30", "brorand": "^1.1.0", "ripple-keypairs": "^1.1.5" - }, - "dependencies": { - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, - "ripple-address-codec": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-4.3.0.tgz", - "integrity": "sha512-Tvd81i7hpDmNqHvkj6iYlj8Tv3I1Romw5gfjni9eacewJvGV2xe+p2y0FAw39z72qfciRMhQyHvpnviBcWVBNw==", - "requires": { - "base-x": "^3.0.9", - "create-hash": "^1.1.2" - } - }, - "ripple-keypairs": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-1.3.0.tgz", - "integrity": "sha512-LzM3Up9Pwz3dYqnczzNptimN3AxtjeGbDGeiOzREzbkslKiZcJ615b/ghBN4H23SC6W1GAL95juEzzimDi4THw==", - "requires": { - "bn.js": "^5.1.1", - "brorand": "^1.0.5", - "elliptic": "^6.5.4", - "hash.js": "^1.0.3", - "ripple-address-codec": "^4.3.0" - } - } } }, "xrpl-sign-keypairs": { @@ -2315,11 +2232,6 @@ "ripple-keypairs": "^1.1.4" }, "dependencies": { - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, "ripple-address-codec": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-4.3.0.tgz", @@ -2341,18 +2253,6 @@ "decimal.js": "^10.2.0", "ripple-address-codec": "^4.3.0" } - }, - "ripple-keypairs": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-1.3.0.tgz", - "integrity": "sha512-LzM3Up9Pwz3dYqnczzNptimN3AxtjeGbDGeiOzREzbkslKiZcJ615b/ghBN4H23SC6W1GAL95juEzzimDi4THw==", - "requires": { - "bn.js": "^5.1.1", - "brorand": "^1.0.5", - "elliptic": "^6.5.4", - "hash.js": "^1.0.3", - "ripple-address-codec": "^4.3.0" - } } } }, diff --git a/installer/jshelper/package.json b/installer/jshelper/package.json index 4aad456..6cd21ba 100644 --- a/installer/jshelper/package.json +++ b/installer/jshelper/package.json @@ -4,7 +4,8 @@ "build": "ncc build index.js --minify -o dist" }, "dependencies": { - "evernode-js-client": "0.6.20", - "ip6addr": "0.2.5" + "evernode-js-client": "0.6.21", + "ip6addr": "0.2.5", + "ripple-keypairs": "1.3.1" } } diff --git a/installer/prereq.sh b/installer/prereq.sh index 6c5f53f..588d3f4 100755 --- a/installer/prereq.sh +++ b/installer/prereq.sh @@ -28,6 +28,7 @@ stage "Installing dependencies" # To fix - Repository 'https://apprepo.vultr.com/ubuntu universal InRelease' changed its 'Codename' value from 'buster' to 'universal' apt-get update --allow-releaseinfo-change apt-get install -y uidmap fuse3 cgroup-tools quota curl openssl jq + # uidmap # Required for rootless docker. # slirp4netns # Required for high performance rootless networking. # fuse3 # Required for hpfs. @@ -40,8 +41,13 @@ apt-get install -y uidmap fuse3 cgroup-tools quota curl openssl jq # Install nodejs if not exists. if ! command -v node &>/dev/null; then stage "Installing nodejs" - apt-get -y install ca-certificates # In case nodejs package certitficates are renewed. - curl -sL https://deb.nodesource.com/setup_16.x | bash - + apt-get install -y ca-certificates curl gnupg + mkdir -p /etc/apt/keyrings + curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg + + NODE_MAJOR=16 + echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list + apt-get update apt-get -y install nodejs else version=$(node -v | cut -d '.' -f1) diff --git a/installer/sashimono-install.sh b/installer/sashimono-install.sh index 3906741..9042fd4 100755 --- a/installer/sashimono-install.sh +++ b/installer/sashimono-install.sh @@ -16,7 +16,7 @@ diskKB=${9} lease_amount=${10} rippled_server=${11} xrpl_account_address=${12} -xrpl_account_secret=${13} +xrpl_account_secret_path=${13} email_address=${14} tls_key_file=${15} tls_cert_file=${16} @@ -28,16 +28,49 @@ ipv6_net_interface=${20} script_dir=$(dirname "$(realpath "$0")") desired_slirp4netns_version="1.2.1" setup_helper_dir="/tmp/evernode-setup-helpers" +secret_backup_location="/root/.evernode/.host-account-secret.key" +previous_secret_path_note=/root/.evernode/previous_secret_path.txt +default_key_filepath="/home/$MB_XRPL_USER/.evernode-host/.host-account-secret.key" + +secret_stored_path="-" function stage() { echo "STAGE $1" # This is picked up by the setup console output filter. } +function confirm() { + echo -en $1" [Y/n] " + local yn="" + read yn /etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.service - - # Create a timer for the service (every two hours). - echo "[Unit] -Description=Timer for the Evernode auto-update. -# Allow manual starts -RefuseManualStart=no -# Allow manual stops -RefuseManualStop=no -[Timer] -Unit=$EVERNODE_AUTO_UPDATE_SERVICE.service -OnCalendar=0/12:00:00 -# Execute job if it missed a run due to machine being off -Persistent=true -# To prevent rush time, adding 2 hours delay -RandomizedDelaySec=7200 -[Install] -WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer - - # Reload the systemd daemon. - systemctl daemon-reload - - echo "Enabling Evernode auto update service..." - systemctl enable $EVERNODE_AUTO_UPDATE_SERVICE.service - - echo "Enabling Evernode auto update timer..." - systemctl enable $EVERNODE_AUTO_UPDATE_SERVICE.timer - echo "Starting Evernode auto update timer..." - systemctl start $EVERNODE_AUTO_UPDATE_SERVICE.timer -} - function setup_certbot() { stage "Setting up letsencrypt certbot" @@ -231,13 +222,13 @@ rm -r "$tmp" openssl req -newkey rsa:2048 -new -nodes -x509 -days 365 -keyout $SASHIMONO_DATA/contract_template/cfg/tlskey.pem \ -out $SASHIMONO_DATA/contract_template/cfg/tlscert.pem -subj "/C=HP/CN=$(jq -r '.hp.host_address' $SASHIMONO_DATA/sa.cfg)" -# Setup tls certs used for contract instance websockets. -[ "$UPGRADE" == "0" ] && setup_tls_certs - # Install Sashimono agent binaries into sashimono bin dir. cp "$script_dir"/{sagent,hpfs,user-cgcreate.sh,user-install.sh,user-uninstall.sh,docker-registry-uninstall.sh} $SASHIMONO_BIN chmod -R +x $SASHIMONO_BIN +# Setup tls certs used for contract instance websockets. +[ "$UPGRADE" == "0" ] && setup_tls_certs + # Copy the temporary setup-helper directory content to SASHIMONO_BIN directory. cp -Rdp $setup_helper_dir $SASHIMONO_BIN/evernode-setup-helpers @@ -279,14 +270,37 @@ if [ "$NO_MB" == "" ]; then cp -r "$script_dir"/mb-xrpl $SASHIMONO_BIN - # Creating message board user (if not exists). + # Create MB_XRPL_USER if does not exists. if ! grep -q "^$MB_XRPL_USER:" /etc/passwd; then useradd --shell /usr/sbin/nologin -m $MB_XRPL_USER + fi + + # Assign message board user priviledges. + if ! id -nG "$MB_XRPL_USER" | grep -qw "$SASHIADMIN_GROUP"; then usermod --lock $MB_XRPL_USER usermod -a -G $SASHIADMIN_GROUP $MB_XRPL_USER loginctl enable-linger $MB_XRPL_USER # Enable lingering to support service installation. fi + if [ "$UPGRADE" != "0" ]; then + # Restore keyfile. + if [ -f $secret_backup_location ]; then + echo "Restoring secret file via $secret_backup_location." + secret_stored_path=$(cat $previous_secret_path_note) + + if [ "$secret_stored_path" == "$default_key_filepath" ]; then + key_directory=$(dirname "$secret_stored_path") + if [ ! -d "$key_directory" ]; then + mkdir -p "$key_directory" + fi + fi + + [ -d $(dirname "$secret_stored_path") ] && mv $secret_backup_location $secret_stored_path && \ + chown $MB_XRPL_USER: $secret_stored_path && \ + chmod 600 $secret_stored_path && rm -f $previous_secret_path_note + fi + fi + # First create the folder from root and then transfer ownership to the user # since the folder is created in /etc/sashimono directory. ! mkdir -p $MB_XRPL_DATA && echo "Could not create '$MB_XRPL_DATA'. Make sure you are running as sudo." && exit 1 @@ -304,7 +318,7 @@ if [ "$NO_MB" == "" ]; then # ! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN betagen $EVERNODE_GOVERNOR_ADDRESS $inetaddr $lease_amount $rippled_server $xrpl_account_secret && echo "XRPLACC_FAILURE" && rollback # doreg=1 - ! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN new $xrpl_account_address $xrpl_account_secret $EVERNODE_GOVERNOR_ADDRESS $inetaddr $lease_amount $rippled_server $ipv6_subnet $ipv6_net_interface && echo "XRPLACC_FAILURE" && rollback + ! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN new $xrpl_account_address $xrpl_account_secret_path $EVERNODE_GOVERNOR_ADDRESS $inetaddr $lease_amount $rippled_server $ipv6_subnet $ipv6_net_interface && echo "XRPLACC_FAILURE" && rollback doreg=1 fi @@ -452,11 +466,6 @@ if [ ! -f /run/reboot-required.pkgs ] || [ ! -n "$(grep sashimono /run/reboot-re fi fi -stage "Configuring auto updater service" - -# Enable the Evernode Auto Updater Service. -enable_evernode_auto_updater - echo "Sashimono installed successfully." exit 0 diff --git a/installer/sashimono-uninstall.sh b/installer/sashimono-uninstall.sh index c14181c..23de3d1 100755 --- a/installer/sashimono-uninstall.sh +++ b/installer/sashimono-uninstall.sh @@ -2,9 +2,13 @@ # Sashimono agent uninstall script. # This must be executed with root privileges. +export TRANSFER=${TRANSFER:-0} + [ "$UPGRADE" == "0" ] && echo "---Sashimono uninstaller---" || echo "---Sashimono uninstaller (for upgrade)---" force=$1 +secret_backup_location="/root/.evernode/.host-account-secret.key" +previous_secret_path_note=/root/.evernode/previous_secret_path.txt function confirm() { echo -en $1" [Y/n] " @@ -30,24 +34,6 @@ function cgrulesengd_servicename() { fi } -function remove_evernode_auto_updater() { - - echo "Removing Evernode auto update timer..." - systemctl stop $EVERNODE_AUTO_UPDATE_SERVICE.timer - systemctl disable $EVERNODE_AUTO_UPDATE_SERVICE.timer - service_path="/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer" - rm $service_path - - echo "Removing Evernode auto update service..." - systemctl stop $EVERNODE_AUTO_UPDATE_SERVICE.service - systemctl disable $EVERNODE_AUTO_UPDATE_SERVICE.service - service_path="/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.service" - rm $service_path - - # Reload the systemd daemon. - systemctl daemon-reload -} - function cleanup_certbot_ssl() { # revoke/delete certs if certbot is used. if command -v certbot &>/dev/null && [ -f "$SASHIMONO_DATA/sa.cfg" ]; then @@ -86,6 +72,16 @@ if grep -q "^$MB_XRPL_USER:" /etc/passwd; then sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user disable $MB_XRPL_SERVICE fi + # Backup the secret in upgrade process. + if [[ "$UPGRADE" != "0" ]]; then + + mb_xrpl_config_data=$(cat $MB_XRPL_DATA/mb-xrpl.cfg) + secret_stored_path=$(echo $mb_xrpl_config_data | jq -r '.xrpl.secretPath') + backup_dir=$(dirname $secret_backup_location) + echo "Backing up account secret at $secret_backup_location." && mkdir -p $backup_dir && cp -p --no-preserve=ownership $secret_stored_path $secret_backup_location + echo $secret_stored_path >$previous_secret_path_note + fi + fi # Uninstall all contract instance users--------------------------- @@ -186,6 +182,15 @@ if grep -q "^$MB_XRPL_USER:" /etc/passwd; then ! confirm "Evernode host deregistration failed. Still do you want to continue uninstallation?" && echo "Aborting uninstallation. Try again later." && exit 1 echo "Continuing uninstallation..." fi + + mb_xrpl_config_data=$(cat $MB_XRPL_DATA/mb-xrpl.cfg) + current_secret_path=$(echo $mb_xrpl_config_data | jq -r '.xrpl.secretPath') + + # Remove secret from the saved location.(This is applied for secrets not specified in default path) + rm -f $current_secret_path + + # Remove Evernode util directory + [ -d "/root/.evernode" ] && rm -rf "/root/.evernode" fi echo "Deleting message board user..." @@ -215,7 +220,4 @@ groupdel $SASHIADMIN_GROUP [ "$UPGRADE" == "0" ] && echo "Sashimono uninstalled successfully." || echo "Sashimono uninstalled successfully. Your data has been preserved." -# Remove the Evernode Auto Updater Service. -[ "$UPGRADE" == "0" ] && remove_evernode_auto_updater - exit 0 diff --git a/installer/setup-old.sh b/installer/setup-old.sh new file mode 100644 index 0000000..5e5aa33 --- /dev/null +++ b/installer/setup-old.sh @@ -0,0 +1,1447 @@ +#!/bin/bash +# Evernode host setup tool to manage Sashimono installation and host registration. +# This script is also used as the 'evernode' cli alias after the installation. +# usage: ./setup.sh install + +# surrounding braces are needed make the whole script to be buffered on client before execution. +{ + +# set the LANG environment variable to a universal encoding +export LANG=C.UTF-8 + +evernode="Evernode" +maxmind_creds="687058:FtcQjM0emHFMEfgI" +cgrulesengd_default="cgrulesengd" +alloc_ratio=80 +ramKB_per_instance=524288 +instances_per_core=3 +max_non_ipv6_instances=5 +max_ipv6_prefix_len=112 +evernode_alias=/usr/bin/evernode +log_dir=/tmp/evernode-beta +cloud_storage="https://stevernode.blob.core.windows.net/evernode-dev-v3-a86733dc-c0fc-4b1f-97cf-2071ae9c5bee" +setup_script_url="$cloud_storage/setup.sh" +installer_url="$cloud_storage/installer.tar.gz" +licence_url="$cloud_storage/licence.txt" +nodejs_url="$cloud_storage/node" +jshelper_url="$cloud_storage/setup-jshelper.tar.gz" +installer_version_timestamp_file="installer.version.timestamp" +setup_version_timestamp_file="setup.version.timestamp" +default_rippled_server="wss://hooks-testnet-v3.xrpl-labs.com" +setup_helper_dir="/tmp/evernode-setup-helpers" +nodejs_util_bin="$setup_helper_dir/node" +jshelper_bin="$setup_helper_dir/jshelper/index.js" + +# export vars used by Sashimono installer. +export USER_BIN=/usr/bin +export SASHIMONO_BIN=/usr/bin/sashimono +export MB_XRPL_BIN=$SASHIMONO_BIN/mb-xrpl +export DOCKER_BIN=$SASHIMONO_BIN/dockerbin +export SASHIMONO_DATA=/etc/sashimono +export MB_XRPL_DATA=$SASHIMONO_DATA/mb-xrpl +export SASHIMONO_SERVICE="sashimono-agent" +export CGCREATE_SERVICE="sashimono-cgcreate" +export MB_XRPL_SERVICE="sashimono-mb-xrpl" +export SASHIADMIN_GROUP="sashiadmin" +export SASHIUSER_GROUP="sashiuser" +export SASHIUSER_PREFIX="sashi" +export MB_XRPL_USER="sashimbxrpl" +export CG_SUFFIX="-cg" +export EVERNODE_AUTO_UPDATE_SERVICE="evernode-auto-update" + +# TODO: Verify if the correct Governor address is present in the DEV/BETA envs. +export EVERNODE_GOVERNOR_ADDRESS="raVhw4Q8FQr296jdaDLDfZ4JDhh7tFG7SF" +export MIN_EVR_BALANCE=5120 + +# Private docker registry (not used for now) +export DOCKER_REGISTRY_USER="sashidockerreg" +export DOCKER_REGISTRY_PORT=0 + +# We execute some commands as unprivileged user for better security. +# (we execute as the user who launched this script as sudo) +noroot_user=${SUDO_USER:-$(whoami)} + +# Helper to print multi line text. +# (When passed as a parameter, bash auto strips spaces and indentation which is what we want) +function echomult() { + echo -e $1 +} + +function confirm() { + echo -en $1" [Y/n] " + local yn="" + read yn /dev/null; then + version=$(node -v | cut -d '.' -f1) + version=${version:1} + if [[ $version -lt 16 ]]; then + echo "$evernode requires NodeJs 16.x or later. You system has NodeJs $version installed. Either remove the NodeJs installation or upgrade to NodeJs 16.x." + exit 1 + fi + fi + + # Check bc command is installed. + if ! command -v bc &>/dev/null; then + echo "bc command not found. Installing.." + apt-get -y install bc >/dev/null + fi + + # Check host command is installed. + if ! command -v host &> /dev/null; then + echo "host command not found. Installing.." + apt-get -y install bind9-host >/dev/null + fi +} + +function check_sys_req() { + + # Assign sys resource info to global vars since these will also be used for instance allocation later. + ramKB=$(free | grep Mem | awk '{print $2}') + swapKB=$(free | grep -i Swap | awk '{print $2}') + diskKB=$(df | grep -w /home | head -1 | awk '{print $4}') + [ -z "$diskKB" ] && diskKB=$(df | grep -w / | head -1 | awk '{print $4}') + + [ "$SKIP_SYSREQ" == "1" ] && echo "System requirements check skipped." && return 0 + + local proc1=$(ps --no-headers -o comm 1) + if [ "$proc1" != "systemd" ]; then + echo "$evernode host installation requires systemd. Your system does not have systemd running. Aborting." + exit 1 + fi + + local os=$(grep -ioP '^ID=\K.+' /etc/os-release) + local osversion=$(grep -ioP '^VERSION_ID=\K.+' /etc/os-release) + + local errors="" + ([ "$os" != "ubuntu" ] || [ "$osversion" != '"20.04"' ]) && errors=" OS: $os $osversion (required: Ubuntu 20.04)\n" + [ $ramKB -lt 2000000 ] && errors="$errors RAM: $(GB $ramKB) (required: 2 GB RAM)\n" + [ $swapKB -lt 2000000 ] && errors="$errors Swap: $(GB $swapKB) (required: 2 GB Swap)\n" + [ $diskKB -lt 4000000 ] && errors="$errors Disk space (/home): $(GB $diskKB) (required: 4 GB)\n" + + if [ -z "$errors" ]; then + echo "System check complete. Your system is capable of becoming an $evernode host." + else + echomult "Your system does not meet following $evernode system requirements:\n $errors" + echomult "$evernode host registration requires Ubuntu 20.04 with minimum 2 GB RAM, + 2 GB Swap and 4 GB free disk space for /home. Aborting setup." + exit 1 + fi +} + +function init_setup_helpers() { + + echo "Downloading setup support files..." + + local jshelper_dir=$(dirname $jshelper_bin) + rm -r $jshelper_dir >/dev/null 2>&1 + sudo -u $noroot_user mkdir -p $jshelper_dir + + [ ! -f "$nodejs_util_bin" ] && sudo -u $noroot_user curl $nodejs_url --output $nodejs_util_bin + [ ! -f "$nodejs_util_bin" ] && echo "Could not download nodejs for setup checks." && exit 1 + chmod +x $nodejs_util_bin + + if [ ! -f "$jshelper_bin" ]; then + pushd $jshelper_dir >/dev/null 2>&1 + sudo -u $noroot_user curl $jshelper_url --output jshelper.tar.gz + sudo -u $noroot_user tar zxf jshelper.tar.gz --strip-components=1 + rm jshelper.tar.gz + popd >/dev/null 2>&1 + fi + [ ! -f "$jshelper_bin" ] && echo "Could not download helper tool for setup checks." && exit 1 + echo -e "Done.\n" +} + +function exec_jshelper() { + + # Create fifo file to read response data from the helper script. + local resp_file=$setup_helper_dir/helper_fifo + [ -p $resp_file ] || sudo -u $noroot_user mkfifo $resp_file + + # Execute js helper asynchronously while collecting response to fifo file. + sudo -u $noroot_user RESPFILE=$resp_file $nodejs_util_bin $jshelper_bin "$@" >/dev/null 2>&1 & + local pid=$! + local result=$(cat $resp_file) && [ "$result" != "-" ] && echo $result + + # Wait for js helper to exit and reflect the error exit code in this function return. + wait $pid && [ $? -eq 0 ] && rm $resp_file && return 0 + rm $resp_file && return 1 +} + +function resolve_filepath() { + # name reference the variable name provided as first argument. + local -n filepath=$1 + local option=$2 + local prompt="${*:3} " + + while [ -z "$filepath" ]; do + read -p "$prompt" filepath /dev/null 2>&1 && return 0 + inetaddr="" && return 1 +} + +function validate_inet_addr() { + # inert address cannot be empty and cannot contain spaces. + [ -z "$inetaddr" ] || [[ $inetaddr = *" "* ]] && inetaddr="" && return 1 + + # Attempt to resolve ip (in case inetaddr is a DNS address) + # This will resolve correctly if inetaddr is a valid ip or dns address. + + local resolved_ips=$(getent hosts $inetaddr | wc -l) + + # Check if there is more than one IP address + if [ $resolved_ips -eq 1 ]; then + return 0 + elif [ $resolved_ips -gt 1 ]; then + echo "Your domain ($inetaddr) must point to a single IP address." + fi + + # If invalid, reset inetaddr and return with non-zero code. + inetaddr="" && return 1 + +} + +function validate_positive_decimal() { + ! [[ $1 =~ ^(0*[1-9][0-9]*(\.[0-9]+)?|0+\.[0-9]*[1-9][0-9]*)$ ]] && return 1 + return 0 +} + +function validate_rippled_url() { + ! [[ $1 =~ ^(wss?:\/\/)([^\/|^:|^ ]{3,})(:([0-9]{1,5}))?$ ]] && echo "Rippled URL must be a valid URL that starts with 'wss://'" && return 1 + + echo "Checking server $1..." + ! exec_jshelper validate-server $1 && echo "Could not communicate with the rippled server." && return 1 + return 0 +} + +function validate_email_address() { + local emailAddress=$1 + email_address_length=${#emailAddress} + ( ( ! [[ "$email_address_length" -le 40 ]] && echo "Email address length should not exceed 40 characters." ) || + ( ! [[ $emailAddress =~ .+@.+ ]] && echo "Email address is invalid." ) ) || return 0 + return 1 +} + +function set_inet_addr() { + + if $interactive && [ "$NO_DOMAIN" == "" ] ; then + echo "" + while [ -z "$inetaddr" ]; do + read -p "Please specify the domain name that this host is reachable at: " inetaddr &1 \ + | tee -a $logfile | stdbuf --output=L grep "STAGE" | cut -d ' ' -f 2- && install_failure + fi + + # Create evernode cli alias at the begining. + # So, if the installation attempt failed user can uninstall the failed installation using evernode commands. + ! create_evernode_alias && install_failure + + # Currently the domain address saved only in account_info and an empty value in Hook states. + # Set description to empty value ('_' will be treated as empty) + description="_" + + echo "Installing Sashimono..." + + init_setup_helpers + registry_address=$(exec_jshelper access-evernode-cfg $rippled_server $EVERNODE_GOVERNOR_ADDRESS $xrpl_account_address registryAddress) + + # Filter logs with STAGE prefix and ommit the prefix when echoing. + # If STAGE log contains -p arg, move the cursor to previous log line and overwrite the log. + ! UPGRADE=$upgrade EVERNODE_REGISTRY_ADDRESS=$registry_address ./sashimono-install.sh $inetaddr $init_peer_port $init_user_port $countrycode $alloc_instcount \ + $alloc_cpu $alloc_ramKB $alloc_swapKB $alloc_diskKB $lease_amount $rippled_server $xrpl_account_address $xrpl_account_secret $email_address \ + $tls_key_file $tls_cert_file $tls_cabundle_file $description $ipv6_subnet $ipv6_net_interface 2>&1 \ + | tee -a $logfile | stdbuf --output=L grep "STAGE\|ERROR" \ + | while read line ; do [[ $line =~ ^STAGE[[:space:]]-p(.*)$ ]] && echo -e \\e[1A\\e[K"${line:9}" || echo ${line:6} ; done \ + && remove_evernode_alias && install_failure + set +o pipefail + + rm -r $tmp + + # Write the verison timestamp to a file for later updated version comparison. + echo $installer_version_timestamp > $SASHIMONO_DATA/$installer_version_timestamp_file + echo $setup_version_timestamp > $SASHIMONO_DATA/$setup_version_timestamp_file +} + +function check_exisiting_contracts() { + + local upgrade=$1 + + # Check the condition of existing contract instances. + local users=$(cut -d: -f1 /etc/passwd | grep "^$SASHIUSER_PREFIX" | sort) + readarray -t userarr <<<"$users" + local sashiusers=() + for user in "${userarr[@]}"; do + [ ${#user} -lt 24 ] || [ ${#user} -gt 32 ] || [[ ! "$user" =~ ^$SASHIUSER_PREFIX[0-9]+$ ]] && continue + sashiusers+=("$user") + done + local ucount=${#sashiusers[@]} + + if [ "$upgrade" == "0" ] ; then + $interactive && [ $ucount -gt 0 ] && ! confirm "This will delete $ucount contract instances. \n\nDo you still want to continue?" && exit 1 + ! $interactive && echo "$ucount contract instances will be deleted." + fi +} + +function uninstall_evernode() { + + local upgrade=$1 + + if ! $transfer ; then + [ "$upgrade" == "0" ] && echo "Uninstalling..." || echo "Uninstalling for upgrade..." + ! UPGRADE=$upgrade TRANSFER=0 $SASHIMONO_BIN/sashimono-uninstall.sh $2 && uninstall_failure + else + echo "Intiating Transfer..." + echo "Uninstalling for transfer..." + ! UPGRADE=$upgrade TRANSFER=1 $SASHIMONO_BIN/sashimono-uninstall.sh $2 && uninstall_failure + fi + # Remove the evernode alias at the end. + # So, if the uninstallation failed user can try uninstall again with evernode commands. + remove_evernode_alias +} + +function update_evernode() { + echo "Checking for updates..." + local latest_installer_script_version=$(online_version_timestamp $installer_url) + local latest_setup_script_version=$(online_version_timestamp $setup_script_url) + [ -z "$latest_installer_script_version" ] && echo "Could not check for updates. Online installer not found." && exit 1 + + local current_installer_script_version=$(cat $SASHIMONO_DATA/$installer_version_timestamp_file) + local current_setup_script_version=$(cat $SASHIMONO_DATA/$setup_version_timestamp_file) + [ "$latest_installer_script_version" == "$current_installer_script_version" ] && [ "$latest_setup_script_version" == "$current_setup_script_version" ] && echo "Your $evernode installation is up to date." && exit 0 + + echo "New $evernode update available. Setup will re-install $evernode with updated software. Your account and contract instances will be preserved." + $interactive && ! confirm "\nDo you want to install the update?" && exit 1 + + echo "Starting upgrade..." + # Alias for setup.sh is created during 'install_evernode' too. + # If only the setup.sh is updated but not the installer, then the alias should be created again. + if [ "$latest_installer_script_version" != "$current_installer_script_version" ] ; then + uninstall_evernode 1 + install_evernode 1 + elif [ "$latest_setup_script_version" != "$current_setup_script_version" ] ; then + [ -d $log_dir ] || mkdir -p $log_dir + logfile="$log_dir/installer-$(date +%s).log" + remove_evernode_alias + ! create_evernode_alias && echo "Alias creation failed." + echo $latest_setup_script_version > $SASHIMONO_DATA/$setup_version_timestamp_file + fi + + rm -r $setup_helper_dir >/dev/null 2>&1 + + echo "Upgrade complete." +} + +function init_evernode_transfer() { + + if ! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN transfer $transferee_address && + [ "$force" != "-f" ] && [ -f $mb_service_path ]; then + ! confirm "Evernode transfer initiation was failed. Still do you want to continue the unistallation?" && echo "Aborting unistallation. Try again later." && exit 1 + echo "Continuing uninstallation..." + fi + +} + +function create_log() { + tempfile=$(mktemp /tmp/evernode.XXXXXXXXX.log) + { + echo "System:" + uname -r + lsb_release -a + echo "" + echo "sa.cfg:" + cat "$SASHIMONO_DATA/sa.cfg" + echo "" + echo "mb-xrpl.cfg:" + cat "$MB_XRPL_DATA/mb-xrpl.cfg" + echo "" + echo "Sashimono log:" + journalctl -u sashimono-agent.service | tail -n 200 + echo "" + echo "Message board log:" + sudo -u sashimbxrpl bash -c journalctl --user -u sashimono-mb-xrpl | tail -n 200 + echo "" + echo "Auto updater service log:" + journalctl -u evernode-auto-update | tail -n 200 + } > "$tempfile" 2>&1 + echo "Evernode log saved to $tempfile" +} + +# Create a copy of this same script as a command. +function create_evernode_alias() { + ! curl -fsSL $setup_script_url --output $evernode_alias >> $logfile 2>&1 && echo "Error in creating alias." && return 1 + ! chmod +x $evernode_alias >> $logfile 2>&1 && echo "Error in changing permission for the alias." && return 1 + return 0 +} + +function remove_evernode_alias() { + rm $evernode_alias +} + +function check_installer_pending_finish() { + if [ -f /run/reboot-required.pkgs ] && [ -n "$(grep sashimono /run/reboot-required.pkgs)" ]; then + echo "Your system needs to be rebooted in order to complete Sashimono installation." + $interactive && confirm "Reboot now?" && reboot + ! $interactive && echo "Rebooting..." && reboot + return 0 + else + # If reboot not required, check whether re-login is required in case the setup was run with sudo. + # This is because the user account gets added to sashiadmin group and re-login is needed for group permission to apply. + # without this, user cannot run "sashi" cli commands without sudo. + if [ "$mode" == "install" ] && [ -n "$SUDO_USER" ] ; then + echo "You need to logout and log back in, to complete Sashimono installation." + return 0 + else + return 1 + fi + fi +} + +function reg_info() { + echo "" + if MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN reginfo ; then + local sashimono_agent_status=$(systemctl is-active sashimono-agent.service) + local mb_user_id=$(id -u "$MB_XRPL_USER") + local mb_user_runtime_dir="/run/user/$mb_user_id" + local sashimono_mb_xrpl_status=$(sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user is-active $MB_XRPL_SERVICE) + echo "Sashimono agent status: $sashimono_agent_status" + echo "Sashimono mb xrpl status: $sashimono_mb_xrpl_status" + echo -e "\nYour account details are stored in $MB_XRPL_DATA/mb-xrpl.cfg and $MB_XRPL_DATA/secret.cfg." + fi +} + +function apply_ssl() { + [ "$EUID" -ne 0 ] && echo "Please run with root privileges (sudo)." && exit 1 + + local tls_key_file=$1 + local tls_cert_file=$2 + local tls_cabundle_file=$3 + + ([ ! -f "$tls_key_file" ] || [ ! -f "$tls_cert_file" ] || \ + ([ "$tls_cabundle_file" != "" ] && [ ! -f "$tls_cabundle_file" ])) && + echo -e "One or more invalid files provided.\nusage: applyssl " && exit 1 + + echo "Applying new SSL certificates for $evernode" + echo "Key: $tls_key_file" && cp $tls_key_file $SASHIMONO_DATA/contract_template/cfg/tlskey.pem || exit 1 + echo "Cert: $tls_cert_file" && cp $tls_cert_file $SASHIMONO_DATA/contract_template/cfg/tlscert.pem || exit 1 + # ca bundle is optional. + [ "$tls_cabundle_file" != "" ] && echo "CA bundle: $tls_cabundle_file" && (cat $tls_cabundle_file >> $SASHIMONO_DATA/contract_template/cfg/tlscert.pem || exit 1) + + sashi list | jq -rc '.[]' | while read -r inst; do \ + local instuser=$(echo $inst | jq -r '.user'); \ + local instname=$(echo $inst | jq -r '.name'); \ + echo -e "\nStopping contract instance $instname" && sashi stop -n $instname && \ + echo "Updating SSL certificates" && \ + cp $SASHIMONO_DATA/contract_template/cfg/tlskey.pem $SASHIMONO_DATA/contract_template/cfg/tlscert.pem /home/$instuser/$instname/cfg/ && \ + chmod 644 /home/$instuser/$instname/cfg/tlscert.pem && chmod 600 /home/$instuser/$instname/cfg/tlskey.pem && \ + chown -R $instuser:$instuser /home/$instuser/$instname/cfg/*.pem && \ + echo -e "Starting contract instance $instname" && sashi start -n $instname; \ + done + + echo "Done." +} + +function reconfig_sashi() { + echomult "Configuaring sashimono...\n" + + ! $SASHIMONO_BIN/sagent reconfig $SASHIMONO_DATA $alloc_instcount $alloc_cpu $alloc_ramKB $alloc_swapKB $alloc_diskKB && + echomult "There was an error in updating sashimono configuration." && return 1 + + # Update cgroup allocations. + ( [[ $alloc_ramKB -gt 0 ]] || [[ $alloc_swapKB -gt 0 ]] || [[ $alloc_instcount -gt 0 ]] ) && + echomult "Updating the cgroup configuration..." && + ! $SASHIMONO_BIN/user-cgcreate.sh $SASHIMONO_DATA && echomult "Error occured while upgrading cgroup allocations" && return 1 + + # Update disk quotas. + if ( [[ $alloc_diskKB -gt 0 ]] || [[ $alloc_instcount -gt 0 ]] ) ; then + echomult "Updating the disk quotas..." + + users=$(cut -d: -f1 /etc/passwd | grep "^$SASHIUSER_PREFIX" | sort) + readarray -t userarr <<<"$users" + sashiusers=() + for user in "${userarr[@]}"; do + [ ${#user} -lt 24 ] || [ ${#user} -gt 32 ] || [[ ! "$user" =~ ^$SASHIUSER_PREFIX[0-9]+$ ]] && continue + sashiusers+=("$user") + done + + max_storage_kbytes=$(jq '.system.max_storage_kbytes' $saconfig) + max_instance_count=$(jq '.system.max_instance_count' $saconfig) + disk=$(expr $max_storage_kbytes / $max_instance_count) + ucount=${#sashiusers[@]} + if [ $ucount -gt 0 ]; then + for user in "${sashiusers[@]}"; do + setquota -g -F vfsv0 "$user" "$disk" "$disk" 0 0 / + done + fi + fi + + return 0 +} + +function reconfig_mb() { + echomult "Configuaring message board...\n" + + ! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN reconfig $lease_amount $alloc_instcount $rippled_server $ipv6_subnet $ipv6_net_interface && + echo "There was an error in updating message board configuration." && return 1 + return 0 +} + +function config() { + [ "$EUID" -ne 0 ] && echo "Please run with root privileges (sudo)." && exit 1 + + alloc_instcount=0 + alloc_cpu=0 + alloc_ramKB=0 + alloc_swapKB=0 + alloc_diskKB=0 + lease_amount=0 + rippled_server='-' + ipv6_subnet='-' + ipv6_net_interface='-' + + local saconfig="$SASHIMONO_DATA/sa.cfg" + local max_instance_count=$(jq '.system.max_instance_count' $saconfig) + local max_mem_kbytes=$(jq '.system.max_mem_kbytes' $saconfig) + local max_swap_kbytes=$(jq '.system.max_swap_kbytes' $saconfig) + local max_storage_kbytes=$(jq '.system.max_storage_kbytes' $saconfig) + + local mbconfig="$MB_XRPL_DATA/mb-xrpl.cfg" + local cfg_lease_amount=$(jq '.xrpl.leaseAmount' $mbconfig) + local cfg_rippled_server=$(jq -r '.xrpl.rippledServer' $mbconfig) + + local cfg_ipv6_subnet=$(jq -r '.networking.ipv6.subnet' $mbconfig) + local cfg_ipv6_net_interface=$(jq -r '.networking.ipv6.interface' $mbconfig) + + local update_sashi=0 + local update_mb=0 + + local sub_mode=${1} + local occupied_instance_count=$(sashi list | jq length) + + if [ "$sub_mode" == "resources" ] ; then + + local ramMB=${2} # memory to allocate for contract instances. + local swapMB=${3} # Swap to allocate for contract instances. + local diskMB=${4} # Disk space to allocate for contract instances. + local instcount=${5} # Total contract instance count. + + [ -z $ramMB ] && [ -z $swapMB ] && [ -z $diskMB ] && [ -z $instcount ] && + echomult "Your current resource allocation is: + \n Memory: $(GB $max_mem_kbytes) + \n Swap: $(GB $max_swap_kbytes) + \n Disk space: $(GB $max_storage_kbytes) + \n Instance count: $max_instance_count\n" && exit 0 + + + local help_text="Usage: evernode config resources | evernode config resources \n" + [ ! -z $ramMB ] && [[ $ramMB != 0 ]] && ! validate_positive_decimal $ramMB && + echomult "Invalid memory size.\n $help_text" && exit 1 + [ ! -z $swapMB ] && [[ $swapMB != 0 ]] && ! validate_positive_decimal $swapMB && + echomult "Invalid swap size.\n $help_text" && exit 1 + [ ! -z $diskMB ] && [[ $diskMB != 0 ]] && ! validate_positive_decimal $diskMB && + echomult "Invalid disk size.\n $help_text" && exit 1 + [ ! -z $instcount ] && [[ $instcount != 0 ]] && ! validate_positive_decimal $instcount && + echomult "Invalid instance count.\n $help_text" && exit 1 + + [ -z $instcount ] && instcount=0 + alloc_instcount=$instcount + alloc_ramKB=$(( ramMB * 1000 )) + alloc_swapKB=$(( swapMB * 1000 )) + alloc_diskKB=$(( diskMB * 1000 )) + + ( ( [[ $alloc_instcount -eq 0 ]] || [[ $max_instance_count == $alloc_instcount ]] ) && + ( [[ $alloc_ramKB -eq 0 ]] || [[ $max_mem_kbytes == $alloc_ramKB ]] ) && + ( [[ $alloc_swapKB -eq 0 ]] || [[ $max_swap_kbytes == $alloc_swapKB ]] ) && + ( [[ $alloc_diskKB -eq 0 ]] || [[ $max_storage_kbytes == $alloc_diskKB ]] ) ) && + echomult "Resource configuration values are already configured!\n" && exit 0 + + echomult "Using allocation" + [[ $alloc_ramKB -gt 0 ]] && echomult "$(GB $alloc_ramKB) memory" + [[ $alloc_swapKB -gt 0 ]] && echomult "$(GB $alloc_swapKB) Swap" + [[ $alloc_diskKB -gt 0 ]] && echomult "$(GB $alloc_diskKB) disk space" + [[ $alloc_instcount -gt 0 ]] && echomult "Distributed among $alloc_instcount contract instances" + + update_sashi=1 + [[ $alloc_instcount -gt 0 ]] && update_mb=1 + + elif [ "$sub_mode" == "leaseamt" ] ; then + + local amount=${2} # Contract instance lease amount in EVRs. + [ -z $amount ] && echomult "Your current lease amount is: $cfg_lease_amount EVRs.\n" && exit 0 + + + ! validate_positive_decimal $amount && + echomult "Invalid lease amount.\n Usage: evernode config leaseamt | evernode config leaseamt \n" && + exit 1 + lease_amount=$amount + [[ $cfg_lease_amount == $lease_amount ]] && echomult "Lease amount is already configured!\n" && exit 0 + + echomult "Using lease amount $lease_amount EVRs." + + update_mb=1 + + elif [ "$sub_mode" == "rippled" ] ; then + + local server=${2} # Rippled server URL + [ -z $server ] && echomult "Your current rippled server is: $cfg_rippled_server\n" && exit 0 + + ! validate_rippled_url $server && + echomult "\nUsage: evernode config rippled | evernode config rippled \n" && + exit 1 + rippled_server=$server + [[ $cfg_rippled_server == $rippled_server ]] && echomult "Rippled server is already configured!\n" && exit 0 + + echomult "Using the rippled address '$rippled_server'." + + update_mb=1 + + elif [ "$sub_mode" == "email" ] ; then + + local email_address=${2} # Email address + + local cfg_host_address=$(jq -r '.xrpl.address' $mbconfig) + + local mbsecretconfig="$MB_XRPL_DATA/secret.cfg" + local cfg_host_secret=$(jq -r '.xrpl.secret' $mbsecretconfig) + + [ ! -z $email_address ] && ! validate_email_address $email_address && + echomult "\nUsage: evernode config email | evernode config email \n" && + exit 1 + + # Get info of the host. + local host_info=$(sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN hostinfo) || exit 1 + local cur_email_address=$(echo $host_info | jq -r '.email') + + [ -z $email_address ] && echomult "Your current email address is: $cur_email_address\n" && exit 0 + + [[ $cur_email_address == $email_address ]] && echomult "Email address is already configured!\n" && exit 0 + + echomult "Using the email address '$email_address'." + + # If certbot installed, Sashimono might have been setup with letsencrypt certificates. + if command -v certbot &>/dev/null ; then + local inet_addr=$(jq -r '.hp.host_address' $saconfig) + + local key_file="/etc/letsencrypt/live/$inet_addr/privkey.pem" + local cert_file="/etc/letsencrypt/live/$inet_addr/fullchain.pem" + local renewed_key_file="$RENEWED_LINEAGE/privkey.pem" + local sashimono_key_file="$SASHIMONO_DATA/contract_template/cfg/tlskey.pem" + + # If sashimono containes the letsencrypt certificates, Update them with new email. + if ( [ -f $key_file ] && cmp -s $key_file $sashimono_key_file ) || ( [ -f $renewed_key_file ] && cmp -s $renewed_key_file $sashimono_key_file ) ; then + + # Get the current registration email if there's any. + local lenc_acc_email=$(certbot show_account 2>/dev/null | grep "Email contact:" | cut -d ':' -f2 | sed 's/ *//g') + + # If the emails are different, we need to update the letsencrypt email. + if [[ $lenc_acc_email != $email_address ]]; then + # If there are other certificates from this letsencrypt account, + # Complain that sashimono can't update the email since this account is used by other certificates. + local count=$(certbot certificates 2>/dev/null | grep "Certificate Name" | grep -v -c "$inet_addr") + [ $count -gt 0 ] && + echomult "Existing letsencrypt account with $lenc_acc_email has other certificates which are related to sashimono.\n + So letsencrypt email cannot be changed, Please use the same email or update the letsencrypt email with certbot." && + return 1 + + ! certbot -n update_account -m $email_address && + echo "Could not update the letsencrypt email." && return 1 + fi + + fi + fi + + # Send update meassage to the registry. + ! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN update $email_address && + echo "Could not update host info." && return 1 + + # We do not need to restart services for email update. + echomult "\nSuccessfully changed the email address!\n" && exit 0 + + elif [ "$sub_mode" == "instance" ] ; then + local attribute=${2} + + if [ "$attribute" == "ipv6" ] ; then + ([ "$cfg_ipv6_subnet" != null ] && [ "$cfg_ipv6_net_interface" != null ]) && + echomult "You have already enabled IPv6 for instance outbound communication. + \n Network Interface: $cfg_ipv6_net_interface + \n Subnet: $cfg_ipv6_subnet" && + ! confirm "\nDo you want to go for a reconfiguration?" && return 0 + + if ( [[ $occupied_instance_count -gt 0 ]] ); then + echomult "Could not proceed the reconfiguration as there are occupied instances." && exit 1 + fi + + set_ipv6_subnet + if [[ "$ipv6_subnet" == "-" || "$ipv6_net_interface" == "-" ]]; then + echo -e "Could not proceed with provided details." && exit 1 + fi + + echo -e "Using $ipv6_subnet IPv6 subnet on $ipv6_net_interface for contract instances.\n" + update_mb=1 + + else + echomult "Invalid arguments.\n Usage: evernode config instance [ipv6]\n" && exit 1 + fi + + else + echomult "Invalid arguments.\n Usage: evernode config [resources|leaseamt|rippled|email|instance] [arguments]\n" && exit 1 + fi + + local mb_user_id=$(id -u "$MB_XRPL_USER") + local mb_user_runtime_dir="/run/user/$mb_user_id" + local has_error=0 + + echomult "\nStarting the reconfiguration...\n" + + # Stop the message board service. + echomult "Stopping the message board..." + sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user stop $MB_XRPL_SERVICE + + # Stop the sashimono service. + if [ $update_sashi == 1 ] ; then + echomult "Stopping the sashimono..." + systemctl stop $SASHIMONO_SERVICE + + ! reconfig_sashi && has_error=1 + + echomult "Starting the sashimono..." + systemctl start $SASHIMONO_SERVICE + fi + + if [ $has_error == 0 ] && [ $update_mb == 1 ] ; then + ! reconfig_mb && has_error=1 + fi + + echomult "Starting the message board..." + sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user start $MB_XRPL_SERVICE + + [ $has_error == 1 ] && echomult "\nChanging the configuration exited with an error.\n" && exit 1 + + echomult "\nSuccessfully changed the configuration!\n" +} + +function delete_instance() +{ + [ "$EUID" -ne 0 ] && echo "Please run with root privileges (sudo)." && exit 1 + + instance_name=$1 + echo "Deleting instance $instance_name" + ! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN delete $instance_name && + echo "There was an error in deleting the instance." && exit 1 + + # Restart the message board to update the instance count + local mb_user_id=$(id -u "$MB_XRPL_USER") + local mb_user_runtime_dir="/run/user/$mb_user_id" + + sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user restart $MB_XRPL_SERVICE + + echo "Instance deletion completed." +} + +# Begin setup execution flow -------------------- + +if [ "$mode" == "install" ]; then + + if ! $interactive ; then + inetaddr=${3} # IP or DNS address. + init_peer_port=${4} # Starting peer port for instances. + init_user_port=${5} # Starting user port for instances. + countrycode=${6} # 2-letter country code. + alloc_cpu=${7} # CPU microsec to allocate for contract instances (max 1000000). + alloc_ramKB=${8} # Memory to allocate for contract instances. + alloc_swapKB=${9} # Swap to allocate for contract instances. + alloc_diskKB=${10} # Disk space to allocate for contract instances. + alloc_instcount=${11} # Total contract instance count. + lease_amount=${12} # Contract instance lease amount in EVRs. + rippled_server=${13} # Rippled server URL + xrpl_account_address=${14} # XRPL account address. + xrpl_account_secret=${15} # XRPL account secret. + email_address=${16} # User email address + tls_key_file=${17} # File path to the tls private key. + tls_cert_file=${18} # File path to the tls certificate. + tls_cabundle_file=${19} # File path to the tls ca bundle. + ipv6_subnet=${20} # ipv6 subnet to be used for ipv6 instance address assignment. + ipv6_net_interface=${21} # ipv6 bound network interface to be used for outbound communication. + fi + + $interactive && ! confirm "This will install Sashimono, Evernode's contract instance management software, + and register your system as an $evernode host. + \nMake sure your system does not currently contain any other workloads important + to you since we will be making modifications to your system configuration. + \n\nContinue?" && exit 1 + + check_sys_req + check_prereq + + + # Display licence file and ask for concent. + printf "\n*****************************************************************************************************\n\n" + curl --silent $licence_url | cat + printf "\n\n*****************************************************************************************************\n" + $interactive && ! confirm "\nDo you accept the terms of the licence agreement?" && exit 1 + + init_setup_helpers + + if [ "$NO_MB" == "" ]; then + set_rippled_server + echo -e "Using Rippled server '$rippled_server'.\n" + set_host_xrpl_account + echo -e "Using xrpl account $xrpl_account_address with the specified secret.\n" + fi + + set_email_address + echo -e "Using the contact email address '$email_address'.\n" + + set_inet_addr + echo -e "Using '$inetaddr' as host internet address.\n" + + set_country_code + echo -e "Using '$countrycode' as country code.\n" + + set_ipv6_subnet + [ "$ipv6_subnet" != "-" ] && [ "$ipv6_net_interface" != "-" ] && echo -e "Using $ipv6_subnet IPv6 subnet on $ipv6_net_interface for contract instances.\n" + + set_cgrules_svc + echo -e "Using '$cgrulesengd_service' as cgroups rules engine service.\n" + + set_instance_alloc + echo -e "Using allocation $(GB $alloc_ramKB) memory, $(GB $alloc_swapKB) Swap, $(GB $alloc_diskKB) disk space, distributed among $alloc_instcount contract instances.\n" + + set_init_ports + echo -e "Using peer port range $init_peer_port-$((init_peer_port + alloc_instcount)) and user port range $init_user_port-$((init_user_port + alloc_instcount))).\n" + + if [ "$NO_MB" == "" ]; then + set_lease_amount + echo -e "Lease amount set as $lease_amount EVRs per Moment.\n" + fi + + $interactive && ! confirm "\n\nSetup will now begin the installation. Continue?" && exit 1 + + echo "Starting installation..." + install_evernode 0 + + rm -r $setup_helper_dir >/dev/null 2>&1 + + echomult "Installation successful! Installation log can be found at $logfile + \n\nYour system is now registered on $evernode. You can check your system status with 'evernode status' command." + +elif [ "$mode" == "uninstall" ]; then + + # echomult "\nWARNING! Uninstalling will deregister your host from $evernode and you will LOSE YOUR XRPL ACCOUNT credentials + # stored in '$MB_XRPL_DATA/mb-xrpl.cfg' and '$MB_XRPL_DATA/secret.cfg'. This is irreversible. Make sure you have your account address and + # secret elsewhere before proceeding.\n" + + # $interactive && ! confirm "\nHave you read above warning and backed up your account credentials?" && exit 1 + $interactive && ! confirm "\nAre you sure you want to uninstall $evernode?" && exit 1 + + # Check contract condtion. + check_exisiting_contracts 0 + + # Force uninstall on quiet mode. + $interactive && uninstall_evernode 0 || uninstall_evernode 0 -f + echo "Uninstallation complete!" + +elif [ "$mode" == "transfer" ]; then + # If evernode is not installed download setup helpers and call for transfer. + if $installed ; then + $interactive && ! confirm "\nThis will uninstall and deregister this host from $evernode + while allowing you to transfer the registration to a preferred transferee. + \n\nAre you sure you want to transfer $evernode registration from this host?" && exit 1 + + if ! $interactive ; then + transferee_address=${3} # Address of the transferee. + fi + + # Set transferee based on the user input. + set_transferee_address + + # Check contract condtion. + check_exisiting_contracts 0 + + # Initiate transferring. + init_evernode_transfer + + # Execute oftware uninstallation (Force uninstall on quiet mode). + $interactive && uninstall_evernode 0 || uninstall_evernode 0 -f + + else + if ! $interactive ; then + xrpl_account_address=${3} # XRPL account address. + xrpl_account_secret=${4} # XRPL account secret. + transferee_address=${5} # Address of the transferee. + rippled_server=${6} # Rippled server URL + fi + + init_setup_helpers + + # Set rippled server based on the user input. + set_rippled_server + echo -e "Using Rippled server '$rippled_server'.\n" + + # Set host account based on the user input. + set_host_xrpl_account "transfer" + + # Set transferee based on the user input. + set_transferee_address + + $interactive && ! confirm "\nThis will deregister $xrpl_account_address from $evernode + while allowing you to transfer the registration to $([ -z $transferee_address ] && echo "same account" || echo "$transferee_address"). + \n\nAre you sure you want to transfer $evernode registration?" && exit 1 + + # Execute transfer from js helper. + exec_jshelper transfer $rippled_server $EVERNODE_GOVERNOR_ADDRESS $xrpl_account_address $xrpl_account_secret $transferee_address + + rm -r $setup_helper_dir >/dev/null 2>&1 + fi + + echo "Transfer process was sucessfully initiated. You can now install and register $evernode using the account $transferee_address." + +elif [ "$mode" == "status" ]; then + reg_info + +elif [ "$mode" == "list" ]; then + sashi list + +elif [ "$mode" == "update" ]; then + update_evernode + +elif [ "$mode" == "log" ]; then + create_log + +elif [ "$mode" == "applyssl" ]; then + apply_ssl $2 $3 $4 + +elif [ "$mode" == "config" ]; then + config $2 $3 $4 $5 $6 + +elif [ "$mode" == "delete" ]; then + [ -z "$2" ] && echomult "A contract instance name must be specified (see 'evernode list').\n Usage: evernode delete " && exit 1 + + delete_instance "$2" + +elif [ "$mode" == "governance" ]; then + [[ "$2" == "" || "$2" == "help" ]] && echomult "Governance management tool + \nSupported commands: + \npropose [hashFile] [shortName] - Propose new governance candidate. + \nwithdraw [candidateId] - Withdraw proposed governance candidate. + \nvote [candidateId] - Vote for a governance candidate. + \nunvote [candidateId] - Remove vote from voted governance candidate. + \nstatus - Get governance info of this host. + \nreport [dudHostAddress] - Report a dud host. + \nhelp - Print help." && exit 0 + ! MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN ${*:1} && exit 1 + +fi + +[ "$mode" != "uninstall" ] && check_installer_pending_finish + +exit 0 + +# surrounding braces are needed make the whole script to be buffered on client before execution. +} diff --git a/installer/setup.sh b/installer/setup.sh index 5e5aa33..e1afa40 100755 --- a/installer/setup.sh +++ b/installer/setup.sh @@ -19,18 +19,38 @@ max_non_ipv6_instances=5 max_ipv6_prefix_len=112 evernode_alias=/usr/bin/evernode log_dir=/tmp/evernode-beta -cloud_storage="https://stevernode.blob.core.windows.net/evernode-dev-v3-a86733dc-c0fc-4b1f-97cf-2071ae9c5bee" -setup_script_url="$cloud_storage/setup.sh" -installer_url="$cloud_storage/installer.tar.gz" -licence_url="$cloud_storage/licence.txt" -nodejs_url="$cloud_storage/node" -jshelper_url="$cloud_storage/setup-jshelper.tar.gz" + +repo_owner="EvernodeXRPL" +repo_name="evernode-resources" +desired_branch="main" + +latest_version_endpoint="https://api.github.com/repos/$repo_owner/$repo_name/releases/latest" +latest_version_data=$(curl -s "$latest_version_endpoint") +latest_version=$(echo "$latest_version_data" | jq -r '.tag_name') +if [ -z "$latest_version" ]|| [ "$latest_version" = "null" ]; then + echo "Failed to retrieve latest version data." + exit 1 +fi + +# Prepare resources URLs +resource_storage="https://github.com/$repo_owner/$repo_name/releases/download/$latest_version" +licence_url="https://raw.githubusercontent.com/$repo_owner/$repo_name/$desired_branch/installer/licence.txt" +config_url="https://raw.githubusercontent.com/$repo_owner/$repo_name/$desired_branch/definitions/definitions.json" +setup_script_url="$resource_storage/setup.sh" +installer_url="$resource_storage/installer.tar.gz" +jshelper_url="$resource_storage/setup-jshelper.tar.gz" + installer_version_timestamp_file="installer.version.timestamp" -setup_version_timestamp_file="setup.version.timestamp" default_rippled_server="wss://hooks-testnet-v3.xrpl-labs.com" setup_helper_dir="/tmp/evernode-setup-helpers" -nodejs_util_bin="$setup_helper_dir/node" +nodejs_util_bin="/usr/bin/node" jshelper_bin="$setup_helper_dir/jshelper/index.js" +config_json_path="$setup_helper_dir/configuration.json" +operation="register" +min_xrp_amount_per_month=25 +spinner=( '|' '/' '-' '\'); +xrpl_address="-" +xrpl_secret="-" # export vars used by Sashimono installer. export USER_BIN=/usr/bin @@ -49,9 +69,7 @@ export MB_XRPL_USER="sashimbxrpl" export CG_SUFFIX="-cg" export EVERNODE_AUTO_UPDATE_SERVICE="evernode-auto-update" -# TODO: Verify if the correct Governor address is present in the DEV/BETA envs. -export EVERNODE_GOVERNOR_ADDRESS="raVhw4Q8FQr296jdaDLDfZ4JDhh7tFG7SF" -export MIN_EVR_BALANCE=5120 +export NETWORK="${NETWORK:-devnet}" # Private docker registry (not used for now) export DOCKER_REGISTRY_USER="sashidockerreg" @@ -61,6 +79,14 @@ export DOCKER_REGISTRY_PORT=0 # (we execute as the user who launched this script as sudo) noroot_user=${SUDO_USER:-$(whoami)} +# Default key path is set to a path in MB_XRPL_USER home +default_key_filepath="/home/$MB_XRPL_USER/.evernode-host/.host-account-secret.key" + +# Backed up secret location. +# Used to restore secret related to a previous installation attempt +secret_backup_location="/root/.evernode/.host-account-secret.key" + + # Helper to print multi line text. # (When passed as a parameter, bash auto strips spaces and indentation which is what we want) function echomult() { @@ -68,26 +94,64 @@ function echomult() { } function confirm() { - echo -en $1" [Y/n] " + local prompt=$1 + local defaultChoice=${2:-y} #Default choice is set to 'y' if $2 parameter is not provided. + + local choiceDisplay="[Y/n]" + if [ "$defaultChoice" == "n" ]; then + choiceDisplay="[y/N]" + fi + + echo -en $prompt $choiceDisplay local yn="" read yn /dev/null; then + echomult "\nChecking initial level pre-requisites..." + + if ! command -v node &>/dev/null; then + echo "Installing nodejs..." + install_nodejs_utility >/dev/null + else version=$(node -v | cut -d '.' -f1) version=${version:1} if [[ $version -lt 16 ]]; then @@ -180,6 +260,12 @@ function check_prereq() { echo "host command not found. Installing.." apt-get -y install bind9-host >/dev/null fi + + # Check qrencode command is installed. + if ! command -v qrencode &>/dev/null; then + stage "qrencode command not found. Installing.." + apt-get install -y qrencode >/dev/null + fi } function check_sys_req() { @@ -190,7 +276,9 @@ function check_sys_req() { diskKB=$(df | grep -w /home | head -1 | awk '{print $4}') [ -z "$diskKB" ] && diskKB=$(df | grep -w / | head -1 | awk '{print $4}') - [ "$SKIP_SYSREQ" == "1" ] && echo "System requirements check skipped." && return 0 + # Skip system requirement check in non-production environments if SKIP_SYSREQ=1. + ([ "$NETWORK" != "mainnet" ] && [ "$SKIP_SYSREQ" == "1" ]) && echo "System requirements check skipped." && return 0 + local proc1=$(ps --no-headers -o comm 1) if [ "$proc1" != "systemd" ]; then @@ -217,6 +305,25 @@ function check_sys_req() { fi } +function set_environment_configs() { + + sudo -u $noroot_user mkdir -p $setup_helper_dir + echomult "\nDownloading Environment configuration...\n" + sudo -u $noroot_user curl $config_url --output $config_json_path + + # Network config selection. + + echomult "\nChecking Evernode $NETWORK environment details..." + + if ! jq -e ".${NETWORK}" "$config_json_path" >/dev/null 2>&1; then + echomult "Sorry the specified environment has not been configured yet..\n" && exit 1 + fi + + export EVERNODE_GOVERNOR_ADDRESS=${OVERRIDE_EVERNODE_GOVERNOR_ADDRESS:-$(jq -r ".$NETWORK.governorAddress" $config_json_path)} + default_rippled_server=$(jq -r ".$NETWORK.rippledServer" $config_json_path) + +} + function init_setup_helpers() { echo "Downloading setup support files..." @@ -225,13 +332,10 @@ function init_setup_helpers() { rm -r $jshelper_dir >/dev/null 2>&1 sudo -u $noroot_user mkdir -p $jshelper_dir - [ ! -f "$nodejs_util_bin" ] && sudo -u $noroot_user curl $nodejs_url --output $nodejs_util_bin - [ ! -f "$nodejs_util_bin" ] && echo "Could not download nodejs for setup checks." && exit 1 - chmod +x $nodejs_util_bin if [ ! -f "$jshelper_bin" ]; then pushd $jshelper_dir >/dev/null 2>&1 - sudo -u $noroot_user curl $jshelper_url --output jshelper.tar.gz + sudo -u $noroot_user curl -L $jshelper_url --output jshelper.tar.gz sudo -u $noroot_user tar zxf jshelper.tar.gz --strip-components=1 rm jshelper.tar.gz popd >/dev/null 2>&1 @@ -256,6 +360,22 @@ function exec_jshelper() { rm $resp_file && return 1 } +function exec_jshelper_root() { + + # Create fifo file to read response data from the helper script. + local resp_file=$setup_helper_dir/helper_fifo + [ -p $resp_file ] || mkfifo $resp_file + + # Execute js helper asynchronously while collecting response to fifo file. + RESPFILE=$resp_file $nodejs_util_bin $jshelper_bin "$@" >/dev/null 2>&1 & + local pid=$! + local result=$(cat $resp_file) && [ "$result" != "-" ] && echo $result + + # Wait for js helper to exit and reflect the error exit code in this function return. + wait $pid && [ $? -eq 0 ] && rm $resp_file && return 0 + rm $resp_file && return 1 +} + function resolve_filepath() { # name reference the variable name provided as first argument. local -n filepath=$1 @@ -297,7 +417,25 @@ function set_domain_certs() { } function validate_inet_addr_domain() { - host $inetaddr >/dev/null 2>&1 && return 0 + if host $inetaddr >/dev/null 2>&1 ; then + local port="80" + echo "Verifying domain $inetaddr on port $port..." + local domain_result=$(exec_jshelper_root validate-domain $inetaddr $port) + [[ "$domain_result" == "ok" ]] && echo "Domain verification successful." && return 0 + + if [ "$domain_result" == "listen_error" ]; then + echomult "Could not initiate domain verification. It's likely that port $port is already in use by another application.\n + It's recommended that you abandon the setup and correct this. You should consider continuing only if you are an advanced user + who knows what they are doing, and is going to provide your own SSL certificates." + confirm "Do you want to abandon the setup (recommended)?" && echo "Setup abandoned." && exit 1 + echo "Continuing with unverified domain $inetaddr" && return 0 + fi + + [[ "$domain_result" == "domain_error" ]] && + echo "Domain verification for $inetaddr failed. Please make sure that this host is reachable via $inetaddr" + fi + + # Reaching this point means some error has occured. So we clear the inetaddress to allow to try again. inetaddr="" && return 1 } @@ -345,38 +483,36 @@ function validate_email_address() { function set_inet_addr() { - if $interactive && [ "$NO_DOMAIN" == "" ] ; then + # TODO : Remove NO_DOMAIN usage (Kept for local testing) + if [ "$NO_DOMAIN" == "" ] ; then echo "" while [ -z "$inetaddr" ]; do - read -p "Please specify the domain name that this host is reachable at: " inetaddr Usage: generate_qrcode " + return 1 + fi + local input_string="$1" + qrencode -s 1 -l L -t UTF8 "$input_string" +} + +function generate_and_save_keyfile() { + + local account_json=$(exec_jshelper generate-account) + xrpl_address=$(jq -r '.address' <<< "$account_json") + xrpl_secret=$(jq -r '.secret' <<< "$account_json") + + if [ "$#" -ne 1 ]; then + echomult "Error: Please provide the full path of the secret file." + return 1 + fi + + key_file_path="$1" + + key_dir=$(dirname "$key_file_path") + if [ ! -d "$key_dir" ]; then + mkdir -p "$key_dir" + fi + + if [ -e "$key_file_path" ]; then + if ! confirm "The file '$key_file_path' already exists. Do you want to override it?"; then + existing_secret=$(jq -r '.xrpl.secret' "$key_file_path" 2>/dev/null) + if [ "$existing_secret" != "null" ] && [ "$existing_secret" != "-" ]; then + account_json=$(exec_jshelper generate-account $existing_secret) + xrpl_address=$(jq -r '.address' <<< "$account_json") + xrpl_secret=$(jq -r '.secret' <<< "$account_json") + echomult "Retrived account details via secret." + return 0 + else + echomult "Error: Existing secret file does not have the expected format." + return 1 + fi + fi + fi + + if [ "$key_file_path" == "$default_key_filepath" ]; then + parent_directory=$(dirname "$key_file_path") + chown -R $MB_XRPL_USER: "$parent_directory" + chmod -R 700 "$parent_directory" + fi + + echo "{ \"xrpl\": { \"secret\": \"$xrpl_secret\" } }" > "$key_file_path" + chmod 600 "$key_file_path" + echomult "Key file saved successfully at $key_file_path" + + chown $MB_XRPL_USER: $key_file_path + + return 0 +} function set_host_xrpl_account() { local account_validate_criteria="register" + local required_balance=0 [ ! -z $1 ] && account_validate_criteria=$1 - if $interactive; then - [ "$account_validate_criteria" == "register" ] && - echomult "In order to register in Evernode you need to have an XRPL account with sufficient Ever (EVR) balance.\n" - local xrpl_address="" - local xrpl_secret="" - while true ; do + local reg_fee=$(exec_jshelper access-evernode-cfg $rippled_server $EVERNODE_GOVERNOR_ADDRESS hostRegFee) - read -p "Specify the XRPL account address: " xrpl_address /dev/null) + if [ "$existing_secret" != "null" ] && [ "$existing_secret" != "-" ]; then + account_json=$(exec_jshelper generate-account $existing_secret) + xrpl_address=$(jq -r '.address' <<< "$account_json") + xrpl_secret=$(jq -r '.secret' <<< "$account_json") + + key_file_dir=$(dirname "$key_file_path") + if [ ! -d "$key_file_dir" ]; then + mkdir -p "$key_file_dir" + fi + + # Modify the permissions accordingly + chown $MB_XRPL_USER: $key_file_path && \ + chmod 600 $key_file_path && \ + echomult "Retrived account details via the backed-up secret." || (echomult "Error occurred in secret restoring." && exit 1) + else + echomult "Error: Backup secret file format does not support." && exit 1 + fi + else + + echomult "Generating new keypair for the host...\n" + generate_and_save_keyfile "$key_file_path" + fi + + echomult "Your host account with the address $xrpl_address has been generated on Xahau $NETWORK. + \nThe secret key of the account is located at $key_file_path. + \n\nThis is the account that will represent this host on the Evernode host registry. You need to load up the account with following funds in order to continue with the installation. + \n1. At least $min_xrp_amount_per_month XAH (Xahau XRP) to cover regular transaction fees for first month. + \n2. At least $reg_fee EVR to cover Evernode registration fee. + \n\nYou can scan the following QR code in your wallet app to send funds:\n" + + generate_qrcode "$xrpl_address" + + required_balance=$min_xrp_amount_per_month + while true ; do + wait_call "exec_jshelper check-balance $rippled_server $EVERNODE_GOVERNOR_ADDRESS $xrpl_address NATIVE $required_balance" "Thank you. [OUTPUT] XAH balance is there in your host account." \ + && break + confirm "\nDo you want to re-check the balance?\nPressing 'n' would terminate the installation." || exit 1 + done + + echomult "\nPreparing account with EVR trust-line..." + while true ; do + wait_call "exec_jshelper prepare-host $rippled_server $EVERNODE_GOVERNOR_ADDRESS $xrpl_address $xrpl_secret $inetaddr" "Account preparation is successfull." && break + confirm "\nDo you want to re-try account preparation?\nPressing 'n' would terminate the installation." || exit 1 + done + + echomult "\n\nIn order to register in Evernode you need to have $reg_fee EVR balance in your host account. Please deposit the required registration fee in EVRs. + \nYou can scan the following QR code in your wallet app to send funds:" + + required_balance=$reg_fee + while true ; do + wait_call "exec_jshelper check-balance $rippled_server $EVERNODE_GOVERNOR_ADDRESS $xrpl_address ISSUED $required_balance" "Thank you. [OUTPUT] EVR balance is there in your host account." \ + && break + confirm "\nDo you want to re-check the balance?\nPressing 'n' would terminate the installation." || exit 1 + done + + + elif [ "$account_validate_criteria" == "transfer" ] || [ "$account_validate_criteria" == "re-register" ]; then + + if [ "$account_validate_criteria" == "re-register" ]; then + account_validate_criteria="register" + fi + + while true ; do + read -ep "Specify the XRPL account address: " xrpl_address /etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.service + + # Create a timer for the service (every two hours). + echo "[Unit] +Description=Timer for the Evernode auto-update. +# Allow manual starts +RefuseManualStart=no +# Allow manual stops +RefuseManualStop=no +[Timer] +Unit=$EVERNODE_AUTO_UPDATE_SERVICE.service +OnCalendar=0/12:00:00 +# Execute job if it missed a run due to machine being off +Persistent=true +# To prevent rush time, adding 2 hours delay +RandomizedDelaySec=7200 +[Install] +WantedBy=timers.target" >/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer + + # Reload the systemd daemon. + systemctl daemon-reload + + echo "Enabling Evernode auto update service..." + systemctl enable $EVERNODE_AUTO_UPDATE_SERVICE.service + + echo "Enabling Evernode auto update timer..." + systemctl enable $EVERNODE_AUTO_UPDATE_SERVICE.timer + echo "Starting Evernode auto update timer..." + systemctl start $EVERNODE_AUTO_UPDATE_SERVICE.timer +} + +function remove_evernode_auto_updater() { + [ "$EUID" -ne 0 ] && echo "Please run with root privileges (sudo)." && exit 1 + enable_auto_update=false + + echo "Removing Evernode auto update timer..." + systemctl stop $EVERNODE_AUTO_UPDATE_SERVICE.timer + systemctl disable $EVERNODE_AUTO_UPDATE_SERVICE.timer + service_path="/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.timer" + rm -f $service_path + + echo "Removing Evernode auto update service..." + systemctl stop $EVERNODE_AUTO_UPDATE_SERVICE.service + systemctl disable $EVERNODE_AUTO_UPDATE_SERVICE.service + service_path="/etc/systemd/system/$EVERNODE_AUTO_UPDATE_SERVICE.service" + rm -f $service_path + + # Reload the systemd daemon. + systemctl daemon-reload } function install_evernode() { local upgrade=$1 # Get installer version (timestamp). We use this later to check for Evernode software updates. - local installer_version_timestamp=$(online_version_timestamp $installer_url) + local installer_version_timestamp=$(online_version_timestamp) [ -z "$installer_version_timestamp" ] && echo "Online installer not found." && exit 1 - # Get setup version (timestamp). - local setup_version_timestamp=$(online_version_timestamp $setup_script_url) local tmp=$(mktemp -d) cd $tmp - curl --silent $installer_url --output installer.tgz + curl --silent -L $installer_url --output installer.tgz tar zxf $tmp/installer.tgz --strip-components=1 rm installer.tgz @@ -734,7 +1080,7 @@ function install_evernode() { logfile="$log_dir/installer-$(date +%s).log" if [ "$upgrade" == "0" ] ; then - echo "Installing prerequisites..." + echo "Installing other prerequisites..." ! ./prereq.sh $cgrulesengd_service 2>&1 \ | tee -a $logfile | stdbuf --output=L grep "STAGE" | cut -d ' ' -f 2- && install_failure fi @@ -750,23 +1096,29 @@ function install_evernode() { echo "Installing Sashimono..." init_setup_helpers - registry_address=$(exec_jshelper access-evernode-cfg $rippled_server $EVERNODE_GOVERNOR_ADDRESS $xrpl_account_address registryAddress) + registry_address=$(exec_jshelper access-evernode-cfg $rippled_server $EVERNODE_GOVERNOR_ADDRESS registryAddress) # Filter logs with STAGE prefix and ommit the prefix when echoing. # If STAGE log contains -p arg, move the cursor to previous log line and overwrite the log. - ! UPGRADE=$upgrade EVERNODE_REGISTRY_ADDRESS=$registry_address ./sashimono-install.sh $inetaddr $init_peer_port $init_user_port $countrycode $alloc_instcount \ - $alloc_cpu $alloc_ramKB $alloc_swapKB $alloc_diskKB $lease_amount $rippled_server $xrpl_account_address $xrpl_account_secret $email_address \ + ! UPGRADE=$upgrade EVERNODE_REGISTRY_ADDRESS=$registry_address OPERATION=$operation ./sashimono-install.sh $inetaddr $init_peer_port $init_user_port $countrycode $alloc_instcount \ + $alloc_cpu $alloc_ramKB $alloc_swapKB $alloc_diskKB $lease_amount $rippled_server $xrpl_account_address $xrpl_account_secret_path $email_address \ $tls_key_file $tls_cert_file $tls_cabundle_file $description $ipv6_subnet $ipv6_net_interface 2>&1 \ | tee -a $logfile | stdbuf --output=L grep "STAGE\|ERROR" \ | while read line ; do [[ $line =~ ^STAGE[[:space:]]-p(.*)$ ]] && echo -e \\e[1A\\e[K"${line:9}" || echo ${line:6} ; done \ && remove_evernode_alias && install_failure + + # Enable the Evernode Auto Updater Service. + if [ "$enable_auto_update" = true ]; then + stage "Configuring auto updater service" + enable_evernode_auto_updater + fi + set +o pipefail rm -r $tmp # Write the verison timestamp to a file for later updated version comparison. echo $installer_version_timestamp > $SASHIMONO_DATA/$installer_version_timestamp_file - echo $setup_version_timestamp > $SASHIMONO_DATA/$setup_version_timestamp_file } function check_exisiting_contracts() { @@ -796,6 +1148,9 @@ function uninstall_evernode() { if ! $transfer ; then [ "$upgrade" == "0" ] && echo "Uninstalling..." || echo "Uninstalling for upgrade..." ! UPGRADE=$upgrade TRANSFER=0 $SASHIMONO_BIN/sashimono-uninstall.sh $2 && uninstall_failure + + # Remove the Evernode Auto Updater Service. + [ "$upgrade" == "0" ] && systemctl list-unit-files | grep -q $EVERNODE_AUTO_UPDATE_SERVICE.service && remove_evernode_auto_updater else echo "Intiating Transfer..." echo "Uninstalling for transfer..." @@ -808,13 +1163,11 @@ function uninstall_evernode() { function update_evernode() { echo "Checking for updates..." - local latest_installer_script_version=$(online_version_timestamp $installer_url) - local latest_setup_script_version=$(online_version_timestamp $setup_script_url) + local latest_installer_script_version=$(online_version_timestamp) [ -z "$latest_installer_script_version" ] && echo "Could not check for updates. Online installer not found." && exit 1 local current_installer_script_version=$(cat $SASHIMONO_DATA/$installer_version_timestamp_file) - local current_setup_script_version=$(cat $SASHIMONO_DATA/$setup_version_timestamp_file) - [ "$latest_installer_script_version" == "$current_installer_script_version" ] && [ "$latest_setup_script_version" == "$current_setup_script_version" ] && echo "Your $evernode installation is up to date." && exit 0 + [ "$latest_installer_script_version" == "$current_installer_script_version" ] && echo "Your $evernode installation is up to date." && exit 0 echo "New $evernode update available. Setup will re-install $evernode with updated software. Your account and contract instances will be preserved." $interactive && ! confirm "\nDo you want to install the update?" && exit 1 @@ -825,12 +1178,6 @@ function update_evernode() { if [ "$latest_installer_script_version" != "$current_installer_script_version" ] ; then uninstall_evernode 1 install_evernode 1 - elif [ "$latest_setup_script_version" != "$current_setup_script_version" ] ; then - [ -d $log_dir ] || mkdir -p $log_dir - logfile="$log_dir/installer-$(date +%s).log" - remove_evernode_alias - ! create_evernode_alias && echo "Alias creation failed." - echo $latest_setup_script_version > $SASHIMONO_DATA/$setup_version_timestamp_file fi rm -r $setup_helper_dir >/dev/null 2>&1 @@ -887,8 +1234,7 @@ function remove_evernode_alias() { function check_installer_pending_finish() { if [ -f /run/reboot-required.pkgs ] && [ -n "$(grep sashimono /run/reboot-required.pkgs)" ]; then echo "Your system needs to be rebooted in order to complete Sashimono installation." - $interactive && confirm "Reboot now?" && reboot - ! $interactive && echo "Rebooting..." && reboot + confirm "Reboot now?" && reboot return 0 else # If reboot not required, check whether re-login is required in case the setup was run with sudo. @@ -912,7 +1258,7 @@ function reg_info() { local sashimono_mb_xrpl_status=$(sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user is-active $MB_XRPL_SERVICE) echo "Sashimono agent status: $sashimono_agent_status" echo "Sashimono mb xrpl status: $sashimono_mb_xrpl_status" - echo -e "\nYour account details are stored in $MB_XRPL_DATA/mb-xrpl.cfg and $MB_XRPL_DATA/secret.cfg." + echo -e "\nYour account details are stored in $MB_XRPL_DATA/mb-xrpl.cfg" fi } @@ -1107,9 +1453,6 @@ function config() { local cfg_host_address=$(jq -r '.xrpl.address' $mbconfig) - local mbsecretconfig="$MB_XRPL_DATA/secret.cfg" - local cfg_host_secret=$(jq -r '.xrpl.secret' $mbsecretconfig) - [ ! -z $email_address ] && ! validate_email_address $email_address && echomult "\nUsage: evernode config email | evernode config email \n" && exit 1 @@ -1248,29 +1591,7 @@ function delete_instance() if [ "$mode" == "install" ]; then - if ! $interactive ; then - inetaddr=${3} # IP or DNS address. - init_peer_port=${4} # Starting peer port for instances. - init_user_port=${5} # Starting user port for instances. - countrycode=${6} # 2-letter country code. - alloc_cpu=${7} # CPU microsec to allocate for contract instances (max 1000000). - alloc_ramKB=${8} # Memory to allocate for contract instances. - alloc_swapKB=${9} # Swap to allocate for contract instances. - alloc_diskKB=${10} # Disk space to allocate for contract instances. - alloc_instcount=${11} # Total contract instance count. - lease_amount=${12} # Contract instance lease amount in EVRs. - rippled_server=${13} # Rippled server URL - xrpl_account_address=${14} # XRPL account address. - xrpl_account_secret=${15} # XRPL account secret. - email_address=${16} # User email address - tls_key_file=${17} # File path to the tls private key. - tls_cert_file=${18} # File path to the tls certificate. - tls_cabundle_file=${19} # File path to the tls ca bundle. - ipv6_subnet=${20} # ipv6 subnet to be used for ipv6 instance address assignment. - ipv6_net_interface=${21} # ipv6 bound network interface to be used for outbound communication. - fi - - $interactive && ! confirm "This will install Sashimono, Evernode's contract instance management software, + ! confirm "This will install Sashimono, Evernode's contract instance management software, and register your system as an $evernode host. \nMake sure your system does not currently contain any other workloads important to you since we will be making modifications to your system configuration. @@ -1282,22 +1603,26 @@ if [ "$mode" == "install" ]; then # Display licence file and ask for concent. printf "\n*****************************************************************************************************\n\n" - curl --silent $licence_url | cat + curl -s -L $licence_url | cat printf "\n\n*****************************************************************************************************\n" - $interactive && ! confirm "\nDo you accept the terms of the licence agreement?" && exit 1 + ! confirm "\nDo you accept the terms of the licence agreement?" && exit 1 + + ! confirm "\nAre you performing a fresh Evernode installation? + \nNOTE: Pressing 'n' implies that you are in the process of transferring from a previous installation in $NETWORK." && operation="re-register" + + set_environment_configs init_setup_helpers - if [ "$NO_MB" == "" ]; then + if [ "$NO_MB" == "" ]; then set_rippled_server echo -e "Using Rippled server '$rippled_server'.\n" - set_host_xrpl_account - echo -e "Using xrpl account $xrpl_account_address with the specified secret.\n" fi set_email_address echo -e "Using the contact email address '$email_address'.\n" + # TODO - CHECKPOINT - 01 set_inet_addr echo -e "Using '$inetaddr' as host internet address.\n" @@ -1319,10 +1644,22 @@ if [ "$mode" == "install" ]; then if [ "$NO_MB" == "" ]; then set_lease_amount echo -e "Lease amount set as $lease_amount EVRs per Moment.\n" + + # TODO - CHECKPOINT - 02 + set_host_xrpl_account $operation + echo -e "\nAccount setup is complete." + fi + + set_auto_update + if [ "$enable_auto_update" = true ]; then + echo -e "Auto updater will be enabled." + else + echo -e "Auto updater will be disabled." fi $interactive && ! confirm "\n\nSetup will now begin the installation. Continue?" && exit 1 + # TODO - CHECKPOINT - 03 echo "Starting installation..." install_evernode 0 @@ -1333,29 +1670,43 @@ if [ "$mode" == "install" ]; then elif [ "$mode" == "uninstall" ]; then - # echomult "\nWARNING! Uninstalling will deregister your host from $evernode and you will LOSE YOUR XRPL ACCOUNT credentials - # stored in '$MB_XRPL_DATA/mb-xrpl.cfg' and '$MB_XRPL_DATA/secret.cfg'. This is irreversible. Make sure you have your account address and - # secret elsewhere before proceeding.\n" + ! confirm "\nAre you sure you want to uninstall $evernode?" && exit 1 - # $interactive && ! confirm "\nHave you read above warning and backed up your account credentials?" && exit 1 - $interactive && ! confirm "\nAre you sure you want to uninstall $evernode?" && exit 1 + echomult "\nWARNING! Uninstalling will deregister your host from $evernode and you will LOSE YOUR ACCOUNT address + stored in '$MB_XRPL_DATA/mb-xrpl.cfg' and the secret in the specified path. + \nNOTE: Secret path can be found at '$MB_XRPL_DATA/mb-xrpl.cfg'. + \nThis is irreversible. Make sure you have your account address and + secret elsewhere before proceeding.\n" + + ! confirm "\nHave you read above warning and backed up your account credentials?" && exit 1 # Check contract condtion. check_exisiting_contracts 0 - # Force uninstall on quiet mode. - $interactive && uninstall_evernode 0 || uninstall_evernode 0 -f + # Perform Evernode uninstall + uninstall_evernode 0 echo "Uninstallation complete!" elif [ "$mode" == "transfer" ]; then # If evernode is not installed download setup helpers and call for transfer. if $installed ; then - $interactive && ! confirm "\nThis will uninstall and deregister this host from $evernode - while allowing you to transfer the registration to a preferred transferee. - \n\nAre you sure you want to transfer $evernode registration from this host?" && exit 1 if ! $interactive ; then transferee_address=${3} # Address of the transferee. + else + + ! confirm "\nThis will uninstall and deregister this host from $evernode + while allowing you to transfer the registration to a preferred transferee. + \n\nAre you sure you want to transfer $evernode registration from this host?" && exit 1 + + echomult "\nWARNING! By proceeding this you will LOSE YOUR ACCOUNT address + stored in '$MB_XRPL_DATA/mb-xrpl.cfg' and the secret in the specified path. + \nNOTE: Secret path can be found at '$MB_XRPL_DATA/mb-xrpl.cfg'. + \nThis is irreversible. Make sure you have your account address and + secret elsewhere before proceeding.\n" + + ! confirm "\nHave you read above warning and backed up your account credentials?" && exit 1 + fi # Set transferee based on the user input. @@ -1372,10 +1723,10 @@ elif [ "$mode" == "transfer" ]; then else if ! $interactive ; then - xrpl_account_address=${3} # XRPL account address. - xrpl_account_secret=${4} # XRPL account secret. - transferee_address=${5} # Address of the transferee. - rippled_server=${6} # Rippled server URL + xrpl_account_address=${3} # XRPL account address. + xrpl_account_secret=$(<"${4}") # XRPL account secret based on the provided path. + transferee_address=${5} # Address of the transferee. + rippled_server=${6} # Rippled server URL fi init_setup_helpers @@ -1409,6 +1760,9 @@ elif [ "$mode" == "list" ]; then sashi list elif [ "$mode" == "update" ]; then + config_json_path="$SASHIMONO_BIN/evernode-setup-helpers/configuration.json" + export EVERNODE_GOVERNOR_ADDRESS=${OVERRIDE_EVERNODE_GOVERNOR_ADDRESS:-$(jq -r ".$NETWORK.governorAddress" $config_json_path)} + update_evernode elif [ "$mode" == "log" ]; then @@ -1437,6 +1791,17 @@ elif [ "$mode" == "governance" ]; then \nhelp - Print help." && exit 0 ! MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN ${*:1} && exit 1 +elif [ "$mode" == "auto-update" ]; then + if [ "$2" == "enable" ]; then + enable_evernode_auto_updater && exit 0 + elif [ "$2" == "disable" ]; then + remove_evernode_auto_updater && exit 0 + else + echomult "$evernode auto update + \nSupported commands: + \nenable - Enable $evernode auto updater service. + \ndisable - Disable $evernode auto updater service." && exit 1 + fi fi [ "$mode" != "uninstall" ] && check_installer_pending_finish diff --git a/mb-xrpl/app.js b/mb-xrpl/app.js index f407744..b77a49e 100644 --- a/mb-xrpl/app.js +++ b/mb-xrpl/app.js @@ -15,25 +15,26 @@ async function main() { if (process.argv.length >= 3) { if (process.argv.length >= 9 && process.argv[2] === 'new') { const accountAddress = process.argv[3]; - const accountSecret = process.argv[4]; + const accountSecretPath = process.argv[4]; const governorAddress = process.argv[5]; - const domain = process.argv[6]; + // const domain = process.argv[6]; const leaseAmount = process.argv[7]; const rippledServer = process.argv[8]; const ipv6Subnet = (process.argv[9] === '-') ? null : process.argv[9]; const ipv6NetInterface = (process.argv[10] === '-') ? null : process.argv[10]; + const network = process.argv.length > 11 ? process.argv[11] : appenv.NETWORK; const setup = new Setup(); - const acc = await setup.setupHostAccount(accountAddress, accountSecret, rippledServer, governorAddress, domain); - setup.newConfig(acc.address, acc.secret, governorAddress, parseFloat(leaseAmount), rippledServer, ipv6Subnet, ipv6NetInterface); + setup.newConfig(accountAddress, accountSecretPath, governorAddress, parseFloat(leaseAmount), rippledServer, ipv6Subnet, ipv6NetInterface, network); } else if (process.argv.length === 7 && process.argv[2] === 'betagen') { const governorAddress = process.argv[3]; const domain = process.argv[4]; const leaseAmount = process.argv[5]; const rippledServer = process.argv[6]; + const network = process.argv.length > 7 ? process.argv[7] : appenv.NETWORK; const setup = new Setup(); - const acc = await setup.generateBetaHostAccount(rippledServer, governorAddress, domain); - setup.newConfig(acc.address, acc.secret, governorAddress, parseFloat(leaseAmount), rippledServer); + const acc = await setup.generateBetaHostAccount(rippledServer, governorAddress, domain, network); + setup.newConfig(acc.address, acc.secret, governorAddress, parseFloat(leaseAmount), rippledServer, null, null, network); } else if (process.argv.length >= 13 && process.argv[2] === 'register') { await new Setup().register(process.argv[3], parseInt(process.argv[4]), parseInt(process.argv[5]), @@ -77,9 +78,9 @@ async function main() { console.log(`Usage: node index.js - Run message board. node index.js version - Print version. - node index.js new [address] [secret] [governorAddress] [leaseAmount] [rippledServer] [ipv6Subnet] [ipv6Interface] - Create new config files. + node index.js new [address] [secret] [governorAddress] [leaseAmount] [rippledServer] [ipv6Subnet] [ipv6Interface] [network] - Create new config files. node index.js betagen [governorAddress] [domain or ip] [leaseAmount] [rippledServer] - Generate beta host account and populate the configs. - node index.js register [countryCode] [cpuMicroSec] [ramKb] [swapKb] [diskKb] [totalInstanceCount] [description] - Register the host on Evernode. + node index.js register [countryCode] [cpuMicroSec] [ramKb] [swapKb] [diskKb] [totalInstanceCount] [description] [network] - Register the host on Evernode. node index.js transfer [transfereeAddress] - Initiate a transfer. node index.js deregister - Deregister the host from Evernode. node index.js reginfo - Display Evernode registration info. diff --git a/mb-xrpl/lib/appenv.js b/mb-xrpl/lib/appenv.js index 81bfbc9..00a6adc 100644 --- a/mb-xrpl/lib/appenv.js +++ b/mb-xrpl/lib/appenv.js @@ -1,19 +1,18 @@ const process = require('process'); const path = require('path'); +const fs = require('fs'); let appenv = { IS_DEV_MODE: process.env.MB_DEV === "1", FILE_LOG_ENABLED: process.env.MB_FILE_LOG === "1", DATA_DIR: process.env.MB_DATA_DIR || __dirname, FAUCET_URL: process.env.MB_FAUCET_URL || "https://hooks-testnet-v3.xrpl-labs.com/newcreds", - DEFAULT_RIPPLED_SERVER: 'wss://hooks-testnet-v3.xrpl-labs.com', DEFAULT_FULL_HISTORY_NODE: 'wss://hooks-testnet-v3.xrpl-labs.com' // If we migrate to Main NET, this should be configured with the relevant full history Node WebSocket. } appenv = { ...appenv, CONFIG_PATH: appenv.DATA_DIR + '/mb-xrpl.cfg', - SECRET_CONFIG_PATH: appenv.DATA_DIR + '/secret.cfg', GOVERNANCE_CONFIG_PATH: appenv.DATA_DIR + '/governance.cfg', LOG_PATH: appenv.DATA_DIR + '/log/mb-xrpl.log', DB_PATH: appenv.DATA_DIR + '/mb-xrpl.sqlite', @@ -28,9 +27,17 @@ appenv = { ORPHAN_PRUNE_SCHEDULER_INTERVAL_HOURS: 4, SASHIMONO_SCHEDULER_INTERVAL_SECONDS: 2, SASHI_CLI_PATH: appenv.IS_DEV_MODE ? "../build/sashi" : "/usr/bin/sashi", - MB_VERSION: '0.7.2', - TOS_HASH: '757A0237B44D8B2BBB04AE2BAD5813858E0AECD2F0B217075E27E0630BA74314' // This is the sha256 hash of TOS text. + MB_VERSION: '0.8.0', + TOS_HASH: '757A0237B44D8B2BBB04AE2BAD5813858E0AECD2F0B217075E27E0630BA74314', // This is the sha256 hash of TOS text. + NETWORK: 'testnet' } + +const getSecretPath = () => { + return fs.existsSync(appenv.CONFIG_PATH) ? JSON.parse(fs.readFileSync(appenv.CONFIG_PATH).toString()).xrpl.secretPath : ""; +} + +appenv = { ...appenv, SECRET_CONFIG_PATH: getSecretPath() } + Object.freeze(appenv); module.exports = { diff --git a/mb-xrpl/lib/config-helper.js b/mb-xrpl/lib/config-helper.js index bc72faa..938b24c 100644 --- a/mb-xrpl/lib/config-helper.js +++ b/mb-xrpl/lib/config-helper.js @@ -28,15 +28,10 @@ class ConfigHelper { return config; } - static writeConfig(config, configPath, secretConfigPath) { + static writeConfig(config, configPath) { let publicCfg = JSON.parse(JSON.stringify(config)); // Make a copy. So, referenced object won't get changed. - const secretCfg = { - xrpl: { - secret: publicCfg.xrpl.secret - } - } - delete publicCfg.xrpl.secret; - fs.writeFileSync(secretConfigPath, JSON.stringify(secretCfg, null, 2), { mode: 0o600 }); // Set file permission so only current user can read/write. + if ('secret' in publicCfg.xrpl) + delete publicCfg.xrpl.secret; fs.writeFileSync(configPath, JSON.stringify(publicCfg, null, 2), { mode: 0o644 }); // Set file permission so only current user can read/write and others can read. } diff --git a/mb-xrpl/lib/governance-manager.js b/mb-xrpl/lib/governance-manager.js index e591b7b..029ce2f 100644 --- a/mb-xrpl/lib/governance-manager.js +++ b/mb-xrpl/lib/governance-manager.js @@ -4,12 +4,18 @@ const fs = require('fs'); const { appenv } = require('./appenv'); const { ConfigHelper } = require('./config-helper'); -function setEvernodeDefaults(governorAddress, rippledServer, xrplApi) { - evernode.Defaults.set({ - governorAddress: governorAddress, - rippledServer: rippledServer, - xrplApi: xrplApi - }); +async function setEvernodeDefaults(network, governorAddress, rippledServer) { + await evernode.Defaults.useNetwork(network || appenv.NETWORK); + + if (governorAddress) + evernode.Defaults.set({ + governorAddress: governorAddress + }); + + if (rippledServer) + evernode.Defaults.set({ + rippledServer: rippledServer + }); } class GovernanceManager { @@ -181,7 +187,7 @@ class GovernanceManager { // Secret is needed for propose, withdraw, and report in order to send the transaction const sashiMBConfig = ConfigHelper.readConfig(appenv.CONFIG_PATH, (command == 'propose' || command === 'withdraw' || command === 'report') ? appenv.SECRET_CONFIG_PATH : null); - setEvernodeDefaults(sashiMBConfig.xrpl.governorAddress, sashiMBConfig.xrpl.rippledServer); + await setEvernodeDefaults(sashiMBConfig.xrpl.network, sashiMBConfig.xrpl.governorAddress, sashiMBConfig.xrpl.rippledServer); hostClient = new evernode.HostClient(sashiMBConfig.xrpl.address, sashiMBConfig.xrpl.secret); } const mgr = new GovernanceManager(appenv.GOVERNANCE_CONFIG_PATH); diff --git a/mb-xrpl/lib/message-board.js b/mb-xrpl/lib/message-board.js index 8c9ce02..7449a43 100644 --- a/mb-xrpl/lib/message-board.js +++ b/mb-xrpl/lib/message-board.js @@ -52,9 +52,20 @@ class MessageBoard { if (!this.cfg.version || !this.cfg.xrpl.address || !this.cfg.xrpl.secret || !this.cfg.xrpl.governorAddress) throw "Required cfg fields cannot be empty."; - this.xrplApi = new evernode.XrplApi(this.cfg.xrpl.rippledServer); + await evernode.Defaults.useNetwork(this.cfg.xrpl.network || appenv.NETWORK); + + if (this.cfg.xrpl.governorAddress) + evernode.Defaults.set({ + governorAddress: this.cfg.xrpl.governorAddress + }); + + if (this.cfg.xrpl.rippledServer) + evernode.Defaults.set({ + rippledServer: this.cfg.xrpl.rippledServer + }); + + this.xrplApi = new evernode.XrplApi(); evernode.Defaults.set({ - governorAddress: this.cfg.xrpl.governorAddress, xrplApi: this.xrplApi }) await this.xrplApi.connect(); @@ -1076,7 +1087,7 @@ class MessageBoard { } persistConfig() { - ConfigHelper.writeConfig(this.cfg, this.configPath, this.secretConfigPath); + ConfigHelper.writeConfig(this.cfg, this.configPath); } } diff --git a/mb-xrpl/lib/setup.js b/mb-xrpl/lib/setup.js index bf327b7..e69dec2 100644 --- a/mb-xrpl/lib/setup.js +++ b/mb-xrpl/lib/setup.js @@ -8,12 +8,18 @@ const { ConfigHelper } = require('./config-helper'); const { SashiCLI } = require('./sashi-cli'); const { UtilHelper } = require('./util-helper'); -function setEvernodeDefaults(governorAddress, rippledServer, xrplApi = null) { - evernode.Defaults.set({ - governorAddress: governorAddress, - rippledServer: rippledServer || appenv.DEFAULT_RIPPLED_SERVER, - xrplApi: xrplApi - }); +async function setEvernodeDefaults(network, governorAddress, rippledServer) { + await evernode.Defaults.useNetwork(network || appenv.NETWORK); + + if (governorAddress) + evernode.Defaults.set({ + governorAddress: governorAddress + }); + + if (rippledServer) + evernode.Defaults.set({ + rippledServer: rippledServer + }); } class Setup { @@ -54,15 +60,16 @@ class Setup { } #saveConfig(cfg) { - ConfigHelper.writeConfig(cfg, appenv.CONFIG_PATH, appenv.SECRET_CONFIG_PATH); + ConfigHelper.writeConfig(cfg, appenv.CONFIG_PATH); } - newConfig(address = "", secret = "", governorAddress = "", leaseAmount = 0, rippledServer = null, ipv6Subnet = null, ipv6NetInterface = null) { + newConfig(address = "", secretPath = "", governorAddress = "", leaseAmount = 0, rippledServer = null, ipv6Subnet = null, ipv6NetInterface = null, network = "") { const baseConfig = { version: appenv.MB_VERSION, xrpl: { + network: network, address: address, - secret: secret, + secretPath: secretPath, governorAddress: governorAddress, rippledServer: rippledServer || appenv.DEFAULT_RIPPLED_SERVER, leaseAmount: leaseAmount @@ -72,50 +79,9 @@ class Setup { this.#saveConfig(ipv6NetInterface ? { ...baseConfig, networking: { ipv6: { subnet: ipv6Subnet, interface: ipv6NetInterface } } } : baseConfig); } - async setupHostAccount(address, secret, rippledServer, governorAddress, domain) { + async generateBetaHostAccount(rippledServer, governorAddress, domain, network = null) { - setEvernodeDefaults(governorAddress, rippledServer); - - const xrplApi = new evernode.XrplApi(rippledServer); - const acc = new evernode.XrplAccount(address, secret, { xrplApi: xrplApi }); - - // Prepare host account. - { - const hostClient = new evernode.HostClient(acc.address, acc.secret); - await hostClient.connect(); - - console.log(`Preparing host account:${acc.address} (domain:${domain} registry:${hostClient.config.registryAddress})`); - - // Sometimes we may get 'account not found' error from rippled when some servers in the cluster - // haven't still updated the ledger. In such cases, we retry several times before giving up. - { - let attempts = 0; - while (attempts >= 0) { - try { - await hostClient.prepareAccount(domain); - break; - } - catch (err) { - if (err.data?.error === 'actNotFound' && ++attempts <= 5) { - console.log("actNotFound - retrying...") - // Wait and retry. - await new Promise(resolve => setTimeout(resolve, 3000)); - continue; - } - throw err; - } - } - } - - await hostClient.disconnect(); - } - - return acc; - } - - async generateBetaHostAccount(rippledServer, governorAddress, domain) { - - setEvernodeDefaults(governorAddress, rippledServer); + await setEvernodeDefaults(network, governorAddress, rippledServer); const acc = await this.#generateFaucetAccount(); @@ -181,13 +147,15 @@ class Setup { let cpuModelFormatted = cpuModel.replaceAll('_', ' '); const config = this.#getConfig(); const acc = config.xrpl; - setEvernodeDefaults(acc.governorAddress, acc.rippledServer); + await setEvernodeDefaults(acc.network, acc.governorAddress, acc.rippledServer); const hostClient = new evernode.HostClient(acc.address, acc.secret); await hostClient.connect(); // Update the Defaults with "xrplApi" of the client. - setEvernodeDefaults(acc.governorAddress, acc.rippledServer, hostClient.xrplApi); + evernode.Defaults.set({ + xrplApi: hostClient.xrplApi + }); const isAReReg = await hostClient.isTransferee(); const evrBalance = await hostClient.getEVRBalance(); @@ -230,13 +198,15 @@ class Setup { async deregister() { console.log("Deregistering host..."); const acc = this.#getConfig().xrpl; - setEvernodeDefaults(acc.governorAddress, acc.rippledServer); + await setEvernodeDefaults(acc.network, acc.governorAddress, acc.rippledServer); const hostClient = new evernode.HostClient(acc.address, acc.secret); await hostClient.connect(); // Update the Defaults with "xrplApi" of the client. - setEvernodeDefaults(acc.governorAddress, acc.rippledServer, hostClient.xrplApi); + evernode.Defaults.set({ + xrplApi: hostClient.xrplApi + }); await this.burnMintedURITokens(hostClient.xrplAcc); await hostClient.deregister(); @@ -249,7 +219,7 @@ class Setup { console.log(`Governor address: ${acc?.governorAddress}`); if (!isBasic) { - setEvernodeDefaults(acc.governorAddress, acc.rippledServer); + await setEvernodeDefaults(acc.network, acc.governorAddress, acc.rippledServer); try { const hostClient = new evernode.HostClient(acc.address); @@ -257,7 +227,9 @@ class Setup { console.log(`Registry address: ${hostClient.config.registryAddress}`); console.log(`Heartbeat address: ${hostClient.config.heartbeatAddress}`); - setEvernodeDefaults(acc.governorAddress, acc.rippledServer, hostClient.xrplApi); + evernode.Defaults.set({ + xrplApi: hostClient.xrplApi + }); const [evrBalance, hostInfo] = await Promise.all([hostClient.getEVRBalance(), hostClient.getRegistration()]); if (hostInfo) { @@ -290,12 +262,14 @@ class Setup { cfg.xrpl.rippledServer = appenv.DEFAULT_RIPPLED_SERVER if (!cfg.xrpl.governorAddress) { - setEvernodeDefaults(governorAddress, cfg.xrpl.rippledServer); + await setEvernodeDefaults(cfg.xrpl.network, governorAddress, cfg.xrpl.rippledServer); const hostClient = new evernode.HostClient(cfg.xrpl.address, cfg.xrpl.secret); await hostClient.connect(); - setEvernodeDefaults(governorAddress, cfg.xrpl.rippledServer, hostClient.xrplApi); + evernode.Defaults.set({ + xrplApi: hostClient.xrplApi + }); cfg.xrpl.governorAddress = governorAddress; @@ -311,13 +285,15 @@ class Setup { async hostInfo() { const acc = this.#getConfig(false).xrpl; - setEvernodeDefaults(acc.governorAddress, acc.rippledServer); + await setEvernodeDefaults(acc.network, acc.governorAddress, acc.rippledServer); const hostClient = new evernode.HostClient(acc.address); await hostClient.connect(); // Update the Defaults with "xrplApi" of the client. - setEvernodeDefaults(acc.governorAddress, acc.rippledServer, hostClient.xrplApi); + evernode.Defaults.set({ + xrplApi: hostClient.xrplApi + }); const hostInfo = await hostClient.getHostInfo(); @@ -331,13 +307,15 @@ class Setup { console.log("Updating host..."); const acc = this.#getConfig().xrpl; - setEvernodeDefaults(acc.governorAddress, acc.rippledServer); + await setEvernodeDefaults(acc.network, acc.governorAddress, acc.rippledServer); const hostClient = new evernode.HostClient(acc.address, acc.secret); await hostClient.connect(); // Update the Defaults with "xrplApi" of the client. - setEvernodeDefaults(acc.governorAddress, acc.rippledServer, hostClient.xrplApi); + evernode.Defaults.set({ + xrplApi: hostClient.xrplApi + }); const hostInfo = await hostClient.getHostInfo(); await hostClient.updateRegInfo(hostInfo.activeInstances, null, null, null, null, null, null, null, null, emailAddress); @@ -385,12 +363,14 @@ class Setup { async transfer(transfereeAddress) { console.log("Transferring host..."); const acc = this.#getConfig().xrpl; - setEvernodeDefaults(acc.governorAddress, acc.rippledServer); + await setEvernodeDefaults(acc.network, acc.governorAddress, acc.rippledServer); const hostClient = new evernode.HostClient(acc.address, acc.secret); await hostClient.connect(); - setEvernodeDefaults(acc.governorAddress, acc.rippledServer, hostClient.xrplApi); + evernode.Defaults.set({ + xrplApi: hostClient.xrplApi + }); await hostClient.transfer(transfereeAddress); await this.burnMintedURITokens(hostClient.xrplAcc); @@ -448,13 +428,14 @@ class Setup { let hostClient; async function initClients(rippledServer) { - setEvernodeDefaults(acc.governorAddress, rippledServer); + await setEvernodeDefaults(acc.network, acc.governorAddress, rippledServer); xrplApi = new evernode.XrplApi(); hostClient = new evernode.HostClient(acc.address, acc.secret, { xrplApi: xrplApi }); await xrplApi.connect(); await hostClient.connect(); - setEvernodeDefaults(acc.governorAddress, acc.rippledServer, xrplApi); - + evernode.Defaults.set({ + xrplApi: xrplApi + }); } async function deinitClients() { @@ -594,12 +575,14 @@ class Setup { lease = lease[0]; const acc = this.#getConfig().xrpl; - setEvernodeDefaults(acc.governorAddress, acc.rippledServer); + await setEvernodeDefaults(acc.network, acc.governorAddress, acc.rippledServer); xrplApi = new evernode.XrplApi(acc.rippledServer); await xrplApi.connect(); - setEvernodeDefaults(acc.governorAddress, acc.rippledServer, xrplApi); + evernode.Defaults.set({ + xrplApi: xrplApi + }); // Get the existing uriToken of the lease. const uriToken = (await (new evernode.XrplAccount(lease.tenant_xrp_address, null, { xrplApi: xrplApi }).getURITokens()))?.find(n => n.index == lease.container_name); diff --git a/mb-xrpl/package-lock.json b/mb-xrpl/package-lock.json index 80aa29d..b1f2c1e 100644 --- a/mb-xrpl/package-lock.json +++ b/mb-xrpl/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "mb-xrpl", "dependencies": { - "evernode-js-client": "0.6.20", + "evernode-js-client": "0.6.21", "ip6addr": "0.2.5", "sqlite3": "5.0.2" }, @@ -980,9 +980,9 @@ } }, "node_modules/evernode-js-client": { - "version": "0.6.20", - "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.20.tgz", - "integrity": "sha512-OC6VNAhwqnNvUc0NhffxwNI9bTDH+BkD/KBTC5Xuwoiq8BhRfYhmfHBnD6M9K5AvLqv+Jxdufc3l1AlzHgILWg==", + "version": "0.6.21", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.21.tgz", + "integrity": "sha512-Q5P6caMTzx3xaUNKhnP1vJTw3wTP/d2J2xSQEMn4m1+t/t67d8+eii3/FeQapRBSZEbNRHm9EbRry9PJhb9xcg==", "dependencies": { "elliptic": "6.5.4", "libsodium-wrappers": "0.7.10", @@ -4010,9 +4010,9 @@ "dev": true }, "evernode-js-client": { - "version": "0.6.20", - "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.20.tgz", - "integrity": "sha512-OC6VNAhwqnNvUc0NhffxwNI9bTDH+BkD/KBTC5Xuwoiq8BhRfYhmfHBnD6M9K5AvLqv+Jxdufc3l1AlzHgILWg==", + "version": "0.6.21", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.21.tgz", + "integrity": "sha512-Q5P6caMTzx3xaUNKhnP1vJTw3wTP/d2J2xSQEMn4m1+t/t67d8+eii3/FeQapRBSZEbNRHm9EbRry9PJhb9xcg==", "requires": { "elliptic": "6.5.4", "libsodium-wrappers": "0.7.10", diff --git a/mb-xrpl/package.json b/mb-xrpl/package.json index 680ea10..80e8938 100644 --- a/mb-xrpl/package.json +++ b/mb-xrpl/package.json @@ -5,7 +5,7 @@ "build": "npm run lint && ncc build app.js --minify -o dist" }, "dependencies": { - "evernode-js-client": "0.6.20", + "evernode-js-client": "0.6.21", "sqlite3": "5.0.2", "ip6addr": "0.2.5" }, diff --git a/src/version.hpp b/src/version.hpp index 45f7209..19fa068 100644 --- a/src/version.hpp +++ b/src/version.hpp @@ -6,7 +6,7 @@ namespace version { // Sashimono agent version. Written to new configs. - constexpr const char *AGENT_VERSION = "0.7.2"; + constexpr const char *AGENT_VERSION = "0.8.0"; // Minimum compatible config version (this will be used to validate configs). constexpr const char *MIN_CONFIG_VERSION = "0.5.0";