diff --git a/.gitmodules b/.gitmodules index ea19230..8b1a17a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "evernode-bootstrap-contract"] path = evernode-bootstrap-contract url = https://github.com/HotPocketDev/evernode-bootstrap-contract.git + branch = release diff --git a/README.md b/README.md index 51f47cf..adc2afe 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Run `make installer` ('installer.tar.gz' will be placed in build directory) 1. Node app which is listening to the host xrpl account. 1. `cd mb-xrpl && npm install` (You only have to do this once) -1. `node app.js new [address] [secret] [governerAddress] [domain or ip] [leaseAmount] [rippledServer]` will create new config files called `mb-xrpl.cfg` and `secret.cfg` +1. `node app.js new [address] [secretPath] [governorAddress] [domain or ip] [leaseAmount] [rippledServer] [ipv6Subnet] [ipv6Interface] [network]` will create new config files called `mb-xrpl.cfg` and `secret.cfg` 1. `node app.js betagen [governerAddress] [domain or ip] [leaseAmount]` will generate beta host account and populate the configs. 1. `node app.js register [countryCode] [cpuMicroSec] [ramKb] [swapKb] [diskKb] [totalInstanceCount] [cpuModel] [cpuCount] [cpuSpeed] [emailAddress] [description(optional)]` will register the host on Evernode. 1. `node app.js deregister` will deregister the host from Evernode. diff --git a/dependencies/user-uninstall.sh b/dependencies/user-uninstall.sh index 40a7600..31c94d3 100755 --- a/dependencies/user-uninstall.sh +++ b/dependencies/user-uninstall.sh @@ -7,6 +7,7 @@ peer_port=$2 user_port=$3 instance_name=$4 prefix="sashi" +max_kill_attempts=5 # Check whether this is a valid sashimono username. [ ${#user} -lt 24 ] || [ ${#user} -gt 32 ] || [[ ! "$user" =~ ^$prefix[0-9]+$ ]] && echo "ARGS,UNINST_ERR" && exit 1 @@ -55,18 +56,16 @@ for mnt in "${mntarr[@]}"; do done # Force kill user processes. -procs=$(ps -U $user 2>/dev/null | wc -l) -if [ "$procs" != "0" ]; then - - # Wait for some time and check again. +i=0 +while true; do sleep 1 procs=$(ps -U $user 2>/dev/null | wc -l) - if [ "$procs" != "0" ]; then - echo "Force killing user processes." - pkill -SIGKILL -u "$user" - fi - -fi + [ "$procs" == "1" ] && echo "All user processes terminated." && break + [[ $i -ge $max_kill_attempts ]] && echo "Max force user process kill attempts $max_kill_attempts reached. Abondaning." && break + ((i++)) + echo "Force killing user processes. Retrying $i..." + pkill -SIGKILL -u "$user" +done echo "Removing cgroups" # Delete config values. diff --git a/evernode-bootstrap-contract b/evernode-bootstrap-contract index e40c3ae..13b6f70 160000 --- a/evernode-bootstrap-contract +++ b/evernode-bootstrap-contract @@ -1 +1 @@ -Subproject commit e40c3ae7053458ff3c3b72cc66359bf02b811dab +Subproject commit 13b6f708bf86af5a8518d27f7573291a5fdeeaab diff --git a/installer/jshelper/index.js b/installer/jshelper/index.js index a4a477a..fb7bd2f 100644 --- a/installer/jshelper/index.js +++ b/installer/jshelper/index.js @@ -4,6 +4,12 @@ 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"); + +let NETWORK = appenv.NETWORK; function checkParams(args, count) { for (let i = 0; i < count; i++) { @@ -18,11 +24,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(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 +41,22 @@ const funcs = { const accountAddress = args[2]; const validateFor = args[3] || "register"; - const xrplApi = new evernode.XrplApi(rippledUrl, { autoReconnect: false }); + await evernode.Defaults.useNetwork(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 +89,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(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 +120,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(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 +157,22 @@ const funcs = { const accountSecret = args[3]; const transfereeAddress = args[4]; - const xrplApi = new evernode.XrplApi(rippledUrl, { autoReconnect: false }); + await evernode.Defaults.useNetwork(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,7 +241,314 @@ const funcs = { } return { success: false }; + }, + + 'check-acc-condition': async (args) => { + checkParams(args, 3); + const rippledUrl = args[0]; + const governorAddress = args[1]; + const accountAddress = args[2]; + + await evernode.Defaults.useNetwork(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(); + } + + try { + // In order to handle the account not found issue via catch block. + await hostClient.connect(); + const trustline = await hostClient.xrplAcc.getTrustLines(evernode.EvernodeConstants.EVR, hostClient.config.evrIssuerAddress); + if (trustline.length > 0) { + await terminateConnections(); + return { success: true, result: 'RC-PREPARED' } + } else { + await terminateConnections(); + return { success: true, result: 'RC-FRESH' }; + } + } catch (err) { + await terminateConnections(); + + if ((err.data?.error === 'actNotFound')) + return { success: true, result: "RC-FRESH" }; + return { success: false, result: "Error occurred in account condition check." }; + } + }, + + '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(NETWORK); + + evernode.Defaults.set({ + rippledServer: rippledUrl, + governorAddress: governorAddress + }); + + try { + 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}` }; + } catch { + return { success: false, result: "Error occurred in websocket connection." }; + } + }, + + '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(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 }; + } + }, + + 'compute-xah-requirement': async (args) => { + checkParams(args, 2); + const rippledUrl = args[0]; + const incReserveCount = Number(args[1]); + + await evernode.Defaults.useNetwork(NETWORK); + + evernode.Defaults.set({ + rippledServer: rippledUrl + }); + + try { + const xrplApi = new evernode.XrplApi(null, { autoReconnect: false }); + await xrplApi.connect(); + + evernode.Defaults.set({ + xrplApi: xrplApi + }); + + const serverInfo = await xrplApi.getServerInfo(); + if (serverInfo?.info?.validated_ledger) { + const reserves = serverInfo.info.validated_ledger + const estimate = (reserves?.reserve_base_native ?? reserves?.reserve_base_xrp) + (reserves?.reserve_inc_native ?? reserves?.reserve_inc_xrp) * incReserveCount; + + if (estimate > 0) { + await xrplApi.disconnect(); + return { success: true, result: `${estimate}` }; + } + } + + await xrplApi.disconnect(); + return { success: false, result: "Failed to retrieve the estimation." }; + + + } catch { + return { success: false, result: "Error occurred in websocket connection." }; + } } + } function handleResponse(resp) { @@ -222,10 +567,20 @@ function handleResponse(resp) { async function app() { try { + const networkIdx = process.argv.findIndex(a => a.startsWith('network:')); + if (networkIdx >= 0) { + const sp = process.argv[networkIdx].split(':'); + if (sp.length > 1 && sp[1]) { + NETWORK = sp[1]; + process.argv.splice(networkIdx, 1); + } + } + const command = process.argv[2]; if (!command) throw "Command not specified."; + const resp = await funcs[command](process.argv.splice(3)); if (!resp) throw "No response."; diff --git a/installer/jshelper/package-lock.json b/installer/jshelper/package-lock.json index ba831f4..90e3428 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.24", + "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.24", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.24.tgz", + "integrity": "sha512-sT7eoN796ueo0+yZl6KpQOzINuXrYW88YlZCm2PxzRqv5G4TqgqUGFgs+GSESkxuVRkw2LBX9WcUCGwbAt6K9g==", "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.24", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.24.tgz", + "integrity": "sha512-sT7eoN796ueo0+yZl6KpQOzINuXrYW88YlZCm2PxzRqv5G4TqgqUGFgs+GSESkxuVRkw2LBX9WcUCGwbAt6K9g==", "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..edd39eb 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.24", + "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 12edd24..9663229 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} @@ -27,6 +27,7 @@ ipv6_net_interface=${20} script_dir=$(dirname "$(realpath "$0")") desired_slirp4netns_version="1.2.1" +setup_helper_dir="/tmp/evernode-setup-helpers" function stage() { echo "STAGE $1" # This is picked up by the setup console output filter. @@ -55,48 +56,6 @@ function set_cpu_info() { [ -z $cpu_mhz ] && cpu_mhz=$(lscpu | grep -i "^CPU MHz:" | sed 's/CPU MHz://g' | sed 's/\.[0-9]*//g' | xargs) } -function enable_evernode_auto_updater() { - # Create the service. - echo "[Unit] -Description=Service for the Evernode auto-update. -After=network.target -[Service] -User=root -Group=root -Type=oneshot -ExecStart=/usr/bin/evernode update -q -[Install] -WantedBy=multi-user.target" >/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" @@ -230,13 +189,16 @@ 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 + # Copy Blake3 and update linker library cache. [ ! -f /usr/local/lib/libblake3.so ] && cp "$script_dir"/libblake3.so /usr/local/lib/ && ldconfig @@ -275,9 +237,13 @@ 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. @@ -289,18 +255,14 @@ if [ "$NO_MB" == "" ]; then # Change ownership to message board user. chown -R "$MB_XRPL_USER":"$MB_XRPL_USER" $MB_XRPL_DATA - # Betage and register if not upgrade mode. + # Register if not upgrade mode. if [ "$UPGRADE" == "0" ]; then # Setup and register the account. if ! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN reginfo basic >/dev/null 2>&1; then stage "Configuring host xrpl account" echo "Using registry: $EVERNODE_REGISTRY_ADDRESS" - # Commented for now, because 'betagen' will no longer be used. - # ! 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 $NETWORK && echo "XRPLACC_FAILURE" && rollback doreg=1 fi @@ -364,7 +326,7 @@ else fi if [[ "$NO_MB" == "" && -f $MB_XRPL_DATA/mb-xrpl.cfg ]]; then - ! sudo -u "$MB_XRPL_USER" MB_DATA_DIR="$MB_XRPL_DATA" node "$MB_XRPL_BIN" upgrade $EVERNODE_GOVERNOR_ADDRESS && rollback + ! sudo -u "$MB_XRPL_USER" MB_DATA_DIR="$MB_XRPL_DATA" node "$MB_XRPL_BIN" upgrade && rollback fi # Install Sashimono Agent systemd service. @@ -448,11 +410,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..93cdde1 100755 --- a/installer/sashimono-uninstall.sh +++ b/installer/sashimono-uninstall.sh @@ -2,6 +2,8 @@ # 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 @@ -30,24 +32,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 @@ -195,7 +179,6 @@ if grep -q "^$MB_XRPL_USER:" /etc/passwd; then pkill -u $MB_XRPL_USER # Kill any running processes. sleep 0.5 userdel -f "$MB_XRPL_USER" - rm -r /home/"${MB_XRPL_USER:?}" fi @@ -215,7 +198,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 4b60e00..47bb97e 100755 --- a/installer/setup.sh +++ b/installer/setup.sh @@ -18,19 +18,41 @@ 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-beta-v3" -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" +log_dir=/tmp/evernode + +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/license/evernode-license.pdf" +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" +default_rippled_server="wss://xahau.network" setup_helper_dir="/tmp/evernode-setup-helpers" -nodejs_temp_bin="$setup_helper_dir/node" -jshelper_temp_bin="$setup_helper_dir/jshelper/index.js" +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_operational_cost_per_month=5 +# 3 Month initial operational duration is considered. +initial_operational_duration=3 +spinner=( '|' '/' '-' '\'); +xrpl_address="-" +xrpl_secret="-" # export vars used by Sashimono installer. export USER_BIN=/usr/bin @@ -49,9 +71,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="rGVHr1PrfL93UAjyw3DWZoi9adz2sLp2yL" -export MIN_EVR_BALANCE=5120 +export NETWORK="${NETWORK:-mainnet}" # Private docker registry (not used for now) export DOCKER_REGISTRY_USER="sashidockerreg" @@ -61,6 +81,9 @@ 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" + # 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 +91,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 || exit 1 + else version=$(node -v | cut -d '.' -f1) version=${version:1} if [[ $version -lt 16 ]]; then @@ -173,6 +259,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 + echo "qrencode command not found. Installing.." + apt-get install -y qrencode >/dev/null + fi } function check_sys_req() { @@ -183,7 +275,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 @@ -210,26 +304,42 @@ 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..." - local jshelper_dir=$(dirname $jshelper_temp_bin) + local jshelper_dir=$(dirname $jshelper_bin) rm -r $jshelper_dir >/dev/null 2>&1 sudo -u $noroot_user mkdir -p $jshelper_dir - [ ! -f "$nodejs_temp_bin" ] && sudo -u $noroot_user curl $nodejs_url --output $nodejs_temp_bin - [ ! -f "$nodejs_temp_bin" ] && echo "Could not download nodejs for setup checks." && exit 1 - chmod +x $nodejs_temp_bin - if [ ! -f "$jshelper_temp_bin" ]; then + 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 fi - [ ! -f "$jshelper_temp_bin" ] && echo "Could not download helper tool for setup checks." && exit 1 + [ ! -f "$jshelper_bin" ] && echo "Could not download helper tool for setup checks." && exit 1 echo -e "Done.\n" } @@ -240,7 +350,23 @@ function exec_jshelper() { [ -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_temp_bin $jshelper_temp_bin "$@" >/dev/null 2>&1 & + sudo -u $noroot_user RESPFILE=$resp_file $nodejs_util_bin $jshelper_bin "$@" "network:$NETWORK" >/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 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 "$@" "network:$NETWORK" >/dev/null 2>&1 & local pid=$! local result=$(cat $resp_file) && [ "$result" != "-" ] && echo $result @@ -290,7 +416,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 } @@ -338,38 +482,35 @@ function validate_email_address() { function set_inet_addr() { - if $interactive && [ "$NO_DOMAIN" == "" ] ; then + # Skip system requirement check in non-production environments if $NO_DOMAIN=1. + if [ "$NETWORK" == "mainnet" ] || [[ "$NETWORK" != "mainnet" && "$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() { + + account_json=$(exec_jshelper generate-account) || { echo "Error occurred in account setting up."; exit 1; } + 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 [ "$key_file_path" == "$default_key_filepath" ]; then + parent_directory=$(dirname "$key_file_path") + chmod -R 500 "$parent_directory" && \ + chown -R $MB_XRPL_USER: "$parent_directory" || { echomult "Error occurred in permission and ownership assignment of key file directory."; exit 1; } + fi + + if [ -e "$key_file_path" ]; then + if confirm "The file '$key_file_path' already exists. Do you want to continue using that key file?\nPressing 'n' would terminate the installation." ; then + echomult "Continuing with the existing key file." + 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) || { echomult "Error occurred when existing account retrieval."; exit 1; } + xrpl_address=$(jq -r '.address' <<< "$account_json") + xrpl_secret=$(jq -r '.secret' <<< "$account_json") + + chmod 400 "$key_file_path" && \ + chown $MB_XRPL_USER: $key_file_path || { echomult "Error occurred in permission and ownership assignment of key file."; exit 1; } + echomult "Retrived account details via secret.\n" + return 0 + else + echomult "Error: Existing secret file does not have the expected format." + exit 1 + fi + else + exit 1 + fi + else + + echo "{ \"xrpl\": { \"secret\": \"$xrpl_secret\" } }" > "$key_file_path" && \ + chmod 400 "$key_file_path" && \ + chown $MB_XRPL_USER: $key_file_path && \ + echomult "Key file saved successfully at $key_file_path" || { echomult "Error occurred in permission and ownership assignment of key file."; exit 1; } + + return 0 + fi + + exit 1 +} 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 reserve_base_xrp + reserve_inc_xrp * n + # reserve_inc_xrp * n => trustline reserve + reg_token_reserve + (reserve_inc_xrp * instance_count) + local inc_reserves_count=$((1 + 1 + $alloc_instcount)) + min_reserve_requirement=$(exec_jshelper compute-xah-requirement $rippled_server $inc_reserves_count) || { echomult "Error occuured in checking XAH requirement."; exit 1; } + + min_xah_requirement=$(echo "$min_operational_cost_per_month*$initial_operational_duration + $min_reserve_requirement" | bc ) + + generate_and_save_keyfile "$key_file_path" + + echomult "Your host account with the address $xrpl_address will be on Xahau $NETWORK. + \nThe secret key of the account is located at $key_file_path. + \nNOTE: It is your responsibility to safeguard/backup this file in a secure manner. + \nIf you lose it, you will not be able to access any funds in your Host account. NO ONE else can recover it. + + \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_xah_requirement XAH to cover regular transaction fees for the first three months. + \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 based on the account condition:\n" + + generate_qrcode "$xrpl_address" + + account_condition='-' + + echomult "\nChecking the account condition..." + while true ; do + account_condition=$(exec_jshelper check-acc-condition $rippled_server $EVERNODE_GOVERNOR_ADDRESS $xrpl_address) \ + && break + confirm "\nDo you want to re-check the account condition?\nPressing 'n' would terminate the installation." || exit 1 + done + + declare -Ar AccCondtionArry=( [0]="RC-FRESH" [1]="RC-PREPARED" ) + + if [ "$account_condition" == "${AccCondtionArry[0]}" ]; then + + echomult "To set up your host account, ensure a deposit of $min_xah_requirement XAH to cover the regular transaction fees for the first three months." + + required_balance=$min_xah_requirement + 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 + + account_condition=${AccCondtionArry[1]} + fi + + if [ "$account_condition" == "${AccCondtionArry[1]}" ]; then + 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 provided 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 + fi + + + 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 @@ -727,7 +1103,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 @@ -743,23 +1119,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() { @@ -789,6 +1171,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..." @@ -801,13 +1186,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 @@ -818,14 +1201,10 @@ 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 + echo "Upgrade complete." } @@ -878,8 +1257,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. @@ -895,16 +1273,26 @@ function check_installer_pending_finish() { } 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 + local reg_info=$( MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN reginfo || echo ERROR) + local error=$(echo "$reg_info" | tail -1) + [ "$error" == "ERROR" ] && echo "${reg_info/ERROR/""}" && exit 1 + + # Get raddress from first line. + local address_line=$(echo "$reg_info" | head -1) + local host_address=$( echo "$address_line" | awk -F : ' { print $2 } ') + echo -e "\n$address_line\n" + generate_qrcode "$host_address" + + # Remove first line and print. + echo -e "\n${reg_info/$address_line/""}" + + 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" } function apply_ssl() { @@ -1082,8 +1470,6 @@ function config() { local server=${2} # Rippled server URL [ -z $server ] && echomult "Your current rippled server is: $cfg_rippled_server\n" && exit 0 - init_setup_helpers - ! validate_rippled_url $server && echomult "\nUsage: evernode config rippled | evernode config rippled \n" && exit 1 @@ -1100,9 +1486,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 @@ -1170,8 +1553,6 @@ function config() { echomult "Could not proceed the reconfiguration as there are occupied instances." && exit 1 fi - init_setup_helpers - set_ipv6_subnet if [[ "$ipv6_subnet" == "-" || "$ipv6_net_interface" == "-" ]]; then echo -e "Could not proceed with provided details." && exit 1 @@ -1243,29 +1624,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. @@ -1276,23 +1635,31 @@ if [ "$mode" == "install" ]; then # 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 + printf "\n***********************************************************************************************************************\n\n" + echomult "EVERNODE SOFTWARE LICENCE AGREEMENT" + echomult "\nBy using this EVERNODE CLI Tool, you agree to be bound by the terms and conditions of the EVERNODE SOFTWARE LICENCE. + \nFor full details, please refer to the licence document available at: + \n$licence_url" + + printf "\n\n***********************************************************************************************************************\n" + ! 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" @@ -1314,10 +1681,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 @@ -1328,29 +1707,36 @@ 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" + echomult "\nNOTE: By continuing with this, you will not LOSE the SECRET; it remains within the specified path. + \nThe secret path can be found inside the configuration stored at '$MB_XRPL_DATA/mb-xrpl.cfg'." + + ! 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 # 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 "\nNOTE: By continuing with this, you will not LOSE the SECRET; it remains within the specified path. + \nThe secret path can be found inside the configuration stored at '$MB_XRPL_DATA/mb-xrpl.cfg'." + + ! confirm "\nAre you sure you want to continue?" && exit 1 + fi # Set transferee based on the user input. @@ -1367,12 +1753,14 @@ 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 + set_environment_configs + init_setup_helpers # Set rippled server based on the user input. @@ -1389,6 +1777,9 @@ elif [ "$mode" == "transfer" ]; then 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 + config_json_path="/tmp/evernode-setup-helpers/configuration.json" + export EVERNODE_GOVERNOR_ADDRESS=${OVERRIDE_EVERNODE_GOVERNOR_ADDRESS:-$(jq -r ".$NETWORK.governorAddress" $config_json_path)} + # Execute transfer from js helper. exec_jshelper transfer $rippled_server $EVERNODE_GOVERNOR_ADDRESS $xrpl_account_address $xrpl_account_secret $transferee_address @@ -1432,6 +1823,36 @@ 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 + confirm "Are you sure you want to subscribe for auto-updates?\nNOTE: The auto-update service is offered subject to the terms set out in the Evernode Software Licence." && 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 + +elif [ "$mode" == "regkey" ]; then + if [ "$2" == "set" ]; then + if [ -z "$3" ]; then + echo "Regular key to be set must be provided." && exit 1 + elif [[ ! "$3" =~ ^[[:alnum:]]{24,34}$ ]]; then + echo "Regular key is invalid." && exit 1 + fi + set_regular_key $3 + exit 0 + elif [ "$2" == "delete" ]; then + set_regular_key + exit 0 + else + echomult "Regular key management tool + \nSupported commands: + \nset [regularKey] - Assign or update the regular key. + \ndelete - Delete the regular key" && exit 1 + fi fi [ "$mode" != "uninstall" ] && check_installer_pending_finish diff --git a/mb-xrpl/app.js b/mb-xrpl/app.js index f407744..6143755 100644 --- a/mb-xrpl/app.js +++ b/mb-xrpl/app.js @@ -15,25 +15,20 @@ 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 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); - } - 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 setup = new Setup(); - const acc = await setup.generateBetaHostAccount(rippledServer, governorAddress, domain); - setup.newConfig(acc.address, acc.secret, governorAddress, parseFloat(leaseAmount), rippledServer); + setup.newConfig(accountAddress, accountSecretPath, governorAddress, parseFloat(leaseAmount), rippledServer, ipv6Subnet, ipv6NetInterface, network); + + if (appenv.IS_DEV_MODE) { + await setup.prepareHostAccount(domain); + } } 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]), @@ -51,8 +46,8 @@ async function main() { else if (process.argv.length === 4 && process.argv[2] === 'reginfo' && process.argv[3] === 'basic') { await new Setup().regInfo(true); } - else if (process.argv.length === 4 && process.argv[2] === 'upgrade') { - await new Setup().upgrade(process.argv[3]); + else if (process.argv.length >= 3 && process.argv[2] === 'upgrade') { + await new Setup().upgrade(); } else if ((process.argv.length === 8) && process.argv[2] === 'reconfig') { if (process.argv[5] == '-') process.argv[5] = null; @@ -73,13 +68,15 @@ async function main() { else if (process.argv.length >= 4 && process.argv[2] === 'governance') { await GovernanceManager.handleCommand(process.argv[3], ...process.argv.slice(4)); } + else if (process.argv.length >= 3 && process.argv[2] === 'regkey') { + await new Setup().setRegularKey(process.argv[3]); + } else if (process.argv[2] === 'help') { 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 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 new [address] [secretPath] [governorAddress] [domain or ip] [leaseAmount] [rippledServer] [ipv6Subnet] [ipv6Interface] [network] - Create new config files. + 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. @@ -87,6 +84,7 @@ async function main() { node index.js reconfig [leaseAmount] [totalInstanceCount] [rippledServer] - Update message board configuration. node index.js delete [containerName] - Delete an instance and recreate the lease offer node index.js governance [command] [args] - Governance handling. + node index.js regkey [regularKey] - Regular key management. node index.js help - Print help.`); } else { diff --git a/mb-xrpl/lib/appenv.js b/mb-xrpl/lib/appenv.js index 81bfbc9..23747c7 100644 --- a/mb-xrpl/lib/appenv.js +++ b/mb-xrpl/lib/appenv.js @@ -1,19 +1,16 @@ 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. + DATA_DIR: process.env.MB_DATA_DIR || __dirname } 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 +25,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: '0801677EBCB2F76EF97D531549D8B27DB2C7A4A8EE7F60032AE40184247F0810', // This is the sha256 hash of EVERNODE-HOSTING-PRINCIPLES.pdf. + NETWORK: 'mainnet' } + +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 cb29b1f..334b01f 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(); @@ -448,9 +459,6 @@ class MessageBoard { } async #startHeartBeatScheduler() { - // Sending a heartbeat at startup - await this.#sendHeartbeat(); - const momentSize = this.hostClient.config.momentSize; const halfMomentSize = momentSize / 2; // Getting half of moment size const timeout = momentSize * 1000; // Converting seconds to milliseconds. @@ -462,10 +470,24 @@ class MessageBoard { await this.#sendHeartbeat(); }; + const currentTimestamp = evernode.UtilHelpers.getCurrentUnixTime(); const currentMomentStartIdx = await this.hostClient.getMomentStartIndex(); + const currentMoment = await this.hostClient.getMoment(); + const currentMomentDuration = currentTimestamp - currentMomentStartIdx; + const hostInfo = await this.hostClient.getRegistration(); - // If the start index is in the begining of the moment, delay the heartbeat scheduler 1 minute to make sure the hook timestamp is not in previous moment when accepting the heartbeat. - const startTimeout = (evernode.UtilHelpers.getCurrentUnixTime() - currentMomentStartIdx) < halfMomentSize ? ((momentSize + 60) * 1000) : ((momentSize) * 1000); + // Schedule the next heartbeat based on last heartbeat occurrence. + // NOTE : Initially checks whether host has sent a heartbeat in the current moment or not. + // If schedule the next heartbeat based on its last heartbeat. + // If it is not further checks whether it is about to send the heartbeat at the second half of a moment or not. + // If the current timestamp lies in the second half of the moment, schedule the next heartbeat withing the next moment (in the its first half). + // If it is not schedule it right now. + const schedule = (this.lastHeartbeatMoment === currentMoment) + ? momentSize - (currentTimestamp - hostInfo.lastHeartbeatIndex) + : (currentMomentDuration > halfMomentSize && currentMomentDuration < momentSize) ? halfMomentSize : 0; + + // If the start index is in the beginning of the moment, delay the heartbeat scheduler 1 minute to make sure the hook timestamp is not in previous moment when accepting the heartbeat. + const startTimeout = (currentMomentDuration) < halfMomentSize ? ((schedule + 60) * 1000) : ((schedule) * 1000); setTimeout(async () => { await scheduler(); @@ -602,7 +624,7 @@ class MessageBoard { } async #catchupMissedLeases() { - const fullHistoryXrplApi = new evernode.XrplApi(appenv.DEFAULT_FULL_HISTORY_NODE); + const fullHistoryXrplApi = new evernode.XrplApi(); await fullHistoryXrplApi.connect(); this.db.open(); @@ -1065,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/sashi-cli.js b/mb-xrpl/lib/sashi-cli.js index 6c0616a..b57ad4c 100644 --- a/mb-xrpl/lib/sashi-cli.js +++ b/mb-xrpl/lib/sashi-cli.js @@ -49,7 +49,13 @@ class SashiCLI { execSashiCli(msg) { this.#waiting = true; return new Promise((resolve, reject) => { - exec(`${this.cliPath} json -m '${JSON.stringify(msg)}'`, { stdio: 'pipe' }, (err, stdout, stderr) => { + let command = `${this.cliPath} json -m '${JSON.stringify(msg)}'`; + + if (msg.type === "create") { + command = `DEV_MODE=1 ${command}`; + } + + exec(command, { stdio: 'pipe' }, (err, stdout, stderr) => { this.#waiting = false; if (err || stderr) { diff --git a/mb-xrpl/lib/setup.js b/mb-xrpl/lib/setup.js index bf327b7..031f99e 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 { @@ -37,32 +43,21 @@ class Setup { }) } - async #generateFaucetAccount() { - console.log("Generating faucet account..."); - const resp = await this.#httpPost(appenv.FAUCET_URL); - const json = JSON.parse(resp); - - // If Hooks TEST NET is used. - return { - address: json.address, - secret: json.secret - }; - } - #getConfig(readSecret = true) { return ConfigHelper.readConfig(appenv.CONFIG_PATH, readSecret ? appenv.SECRET_CONFIG_PATH : null); } #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,18 +67,22 @@ class Setup { this.#saveConfig(ipv6NetInterface ? { ...baseConfig, networking: { ipv6: { subnet: ipv6Subnet, interface: ipv6NetInterface } } } : baseConfig); } - async setupHostAccount(address, secret, rippledServer, governorAddress, domain) { + async prepareHostAccount(domain) { - setEvernodeDefaults(governorAddress, rippledServer); - - const xrplApi = new evernode.XrplApi(rippledServer); - const acc = new evernode.XrplAccount(address, secret, { xrplApi: xrplApi }); + const config = this.#getConfig(); + const acc = config.xrpl; + await setEvernodeDefaults(acc.network, acc.governorAddress, acc.rippledServer); // Prepare host account. { const hostClient = new evernode.HostClient(acc.address, acc.secret); await hostClient.connect(); + // Update the Defaults with "xrplApi" of the client. + evernode.Defaults.set({ + xrplApi: hostClient.xrplApi + }); + 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 @@ -110,70 +109,6 @@ class Setup { await hostClient.disconnect(); } - return acc; - } - - async generateBetaHostAccount(rippledServer, governorAddress, domain) { - - setEvernodeDefaults(governorAddress, rippledServer); - - const acc = await this.#generateFaucetAccount(); - - // 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 testnet 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; - } - } - } - - // Get beta EVRs from foundation to host account. - { - console.log("Requesting beta EVRs..."); - await hostClient.xrplAcc.makePayment(hostClient.config.foundationAddress, - evernode.XrplConstants.MIN_XRP_AMOUNT, - evernode.XrplConstants.XRP, - null, - [{ type: 'giftBetaEvr', format: '', data: '' }]); - - // Keep watching our EVR balance. - let attempts = 0; - while (attempts >= 0) { - await new Promise(resolve => setTimeout(resolve, 1000)); - const balance = await hostClient.getEVRBalance(); - if (balance === '0') { - if (++attempts <= 20) - continue; - throw "EVR funds not received within timeout."; - } - break; - } - } - - await hostClient.disconnect(); - } - - return acc; } async register(countryCode, cpuMicroSec, ramKb, swapKb, diskKb, totalInstanceCount, cpuModel, cpuCount, cpuSpeed, emailAddress, description) { @@ -181,13 +116,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 +167,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 +188,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 +196,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) { @@ -279,7 +220,7 @@ class Setup { } // Upgrades existing message board data to the new version. - async upgrade(governorAddress) { + async upgrade() { // Do a simple version change in the config. const cfg = this.#getConfig(); @@ -289,19 +230,6 @@ class Setup { if (!cfg.xrpl.rippledServer) cfg.xrpl.rippledServer = appenv.DEFAULT_RIPPLED_SERVER - if (!cfg.xrpl.governorAddress) { - setEvernodeDefaults(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); - - cfg.xrpl.governorAddress = governorAddress; - - await hostClient.disconnect(); - } - this.#saveConfig(cfg); await Promise.resolve(); // async placeholder. @@ -311,13 +239,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 +261,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 +317,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); @@ -410,7 +344,7 @@ class Setup { else if (totalInstanceCount && isNaN(totalInstanceCount)) throw 'Maximum instance count should be a number'; - const leaseAmountParsed = leaseAmount ? parseInt(leaseAmount) : 0; + const leaseAmountParsed = leaseAmount ? parseFloat(leaseAmount) : 0; const totalInstanceCountParsed = totalInstanceCount ? parseInt(totalInstanceCount) : 0; // Return if not changed. @@ -448,13 +382,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 +529,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); @@ -643,7 +580,43 @@ class Setup { if (xrplApi) await xrplApi.disconnect(); } + } + async setRegularKey(regularKey) { + { + const acc = this.#getConfig().xrpl; + await setEvernodeDefaults(acc.network, acc.governorAddress, acc.rippledServer); + + if (regularKey) { + console.log(`Setting Regular Key...`); + } + else { + console.log(`Deleting Regular Key...`); + } + + try { + await setEvernodeDefaults(acc.network, acc.governorAddress, acc.rippledServer); + + const xrplApi = new evernode.XrplApi(acc.rippledServer, { autoReconnect: false }); + await xrplApi.connect(); + + const xrplAcc = new evernode.XrplAccount(acc.address, acc.secret, { xrplApi: xrplApi }); + + await xrplAcc.setRegularKey(regularKey); + + if (regularKey) { + console.log(`Regular key ${regularKey} was assigned to account ${acc.address} successfully.`); + } + else { + console.log(`Regular key was deleted from account ${acc.address} successfully.`); + } + + await xrplApi.disconnect(); + } + catch (e) { + throw e; + } + } } } diff --git a/mb-xrpl/package-lock.json b/mb-xrpl/package-lock.json index 80aa29d..e3ef0be 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.24", "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.24", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.24.tgz", + "integrity": "sha512-sT7eoN796ueo0+yZl6KpQOzINuXrYW88YlZCm2PxzRqv5G4TqgqUGFgs+GSESkxuVRkw2LBX9WcUCGwbAt6K9g==", "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.24", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.6.24.tgz", + "integrity": "sha512-sT7eoN796ueo0+yZl6KpQOzINuXrYW88YlZCm2PxzRqv5G4TqgqUGFgs+GSESkxuVRkw2LBX9WcUCGwbAt6K9g==", "requires": { "elliptic": "6.5.4", "libsodium-wrappers": "0.7.10", diff --git a/mb-xrpl/package.json b/mb-xrpl/package.json index 680ea10..086e03a 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.24", "sqlite3": "5.0.2", "ip6addr": "0.2.5" }, diff --git a/sashi-cli/main.cpp b/sashi-cli/main.cpp index 1c2fa35..fc37b4f 100644 --- a/sashi-cli/main.cpp +++ b/sashi-cli/main.cpp @@ -5,6 +5,8 @@ #include "cli-manager.hpp" #include "version.hpp" +#define DEV_MODE "DEV_MODE" + std::string exec_dir; /** @@ -90,6 +92,7 @@ int parse_cmd(int argc, char **argv) std::string json_message; json->add_option("-m,--message", json_message, "JSON message"); + create->group(""); //Hides 'create' command from help-all std::string owner, contract_id, image, outbound_ipv6, outbound_net_interface; create->add_option("-o,--owner", owner, "Hex (ed-prefixed) public key of the instance owner"); create->add_option("-c,--contract-id", contract_id, "Contract Id (GUID) of the instance"); @@ -126,6 +129,25 @@ int parse_cmd(int argc, char **argv) } // Verifying subcommands. + + bool is_dev_mode = (getenv(DEV_MODE) != nullptr); + + if (!is_dev_mode) + { + if(create->parsed()){ + std::cout << "Command not supported: Run with --help or --help-all for more information." << std::endl; + return -1; + } + if(json->parsed() && !json_message.empty()){ + jsoncons::json json_data = jsoncons::json::parse(json_message); + if (json_data.contains("type") && json_data["type"].as_string() == "create") + { + std::cout << "Command not supported: Run with --help or --help-all for more information." << std::endl; + return -1; + } + } + } + if (version->parsed()) { std::cout << "Sashimono CLI version " << version::CLI_VERSION << std::endl; diff --git a/src/conf.cpp b/src/conf.cpp index 5c91cc1..faa9852 100644 --- a/src/conf.cpp +++ b/src/conf.cpp @@ -69,7 +69,7 @@ namespace conf cfg.system.max_cpu_us = !cpu_us ? 900000 : cpu_us; // Total CPU allocation out of 1000000 microsec (1 sec). cfg.system.max_storage_kbytes = !disk_kbytes ? 5242880 : disk_kbytes; - cfg.docker.image_prefix = "evernodedev/sashimono:"; + cfg.docker.image_prefix = "evernode/sashimono:"; cfg.docker.registry_port = docker_registry_port; cfg.log.max_file_count = 50; 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"; diff --git a/test/docker/Dockerfile.ubt.20.04 b/test/docker/Dockerfile.ubt.20.04 index 9c6f875..34b4051 100644 --- a/test/docker/Dockerfile.ubt.20.04 +++ b/test/docker/Dockerfile.ubt.20.04 @@ -1,4 +1,4 @@ -FROM evernodedev/hotpocket:0.6.3-ubt.20.04 +FROM evernode/hotpocket:0.6.4-ubt.20.04 RUN apt-get update \ && apt-get install --no-install-recommends -y unzip jq \ diff --git a/test/docker/Dockerfile.ubt.20.04-njs b/test/docker/Dockerfile.ubt.20.04-njs index 8ec3adf..2d740a3 100644 --- a/test/docker/Dockerfile.ubt.20.04-njs +++ b/test/docker/Dockerfile.ubt.20.04-njs @@ -1,4 +1,4 @@ -FROM evernodedev/hotpocket:0.6.3-ubt.20.04-njs.20 +FROM evernode/hotpocket:0.6.4-ubt.20.04-njs.20 RUN apt-get update \ && apt-get install --no-install-recommends -y unzip jq \ diff --git a/test/docker/build.sh b/test/docker/build.sh index fe58359..37b2c47 100755 --- a/test/docker/build.sh +++ b/test/docker/build.sh @@ -1,6 +1,6 @@ #!/bin/bash -img=evernodedev/sashimono +img=evernode/sashimono -docker build -t $img:hp.latest-ubt.20.04 -t $img:hp.0.6.3-ubt.20.04 -f ./Dockerfile.ubt.20.04 . -docker build -t $img:hp.latest-ubt.20.04-njs.20 -t $img:hp.0.6.3-ubt.20.04-njs.20 -f ./Dockerfile.ubt.20.04-njs . +docker build -t $img:hp.latest-ubt.20.04 -t $img:hp.0.6.4-ubt.20.04 -f ./Dockerfile.ubt.20.04 . +docker build -t $img:hp.latest-ubt.20.04-njs.20 -t $img:hp.0.6.4-ubt.20.04-njs.20 -f ./Dockerfile.ubt.20.04-njs . diff --git a/test/docker/push.sh b/test/docker/push.sh index 009c795..487adfe 100755 --- a/test/docker/push.sh +++ b/test/docker/push.sh @@ -1,5 +1,5 @@ #!/bin/bash -img=evernodedev/sashimono +img=evernode/sashimono docker image push --all-tags $img \ No newline at end of file diff --git a/test/vm-cluster/cluster.sh b/test/vm-cluster/cluster.sh index f6924d5..ffb5b3f 100755 --- a/test/vm-cluster/cluster.sh +++ b/test/vm-cluster/cluster.sh @@ -473,7 +473,7 @@ if [ $mode == "create" ] || [ $mode == "createall" ]; then config=$(echo "$config" | jq -c ".mesh.known_peers = [$peers]" | jq -c ".contract.unl = [\"$pubkey\"]") fi - command="sashi json -m '{\"type\":\"create\",\"owner_pubkey\":\"$ownerpubkey\",\"contract_id\":\"$contractid\",\"image\":\"$image\",\"config\":$config}'" + command="DEV_MODE=1 sashi json -m '{\"type\":\"create\",\"owner_pubkey\":\"$ownerpubkey\",\"contract_id\":\"$contractid\",\"image\":\"$image\",\"config\":$config}'" output=$(sshskp $sshuser@$hostaddr $command | tr '\0' '\n') # If an output received consider updating the json file. if [ ! "$output" = "" ]; then