Files
sashimono/installer/setup.sh
2023-04-06 12:55:54 +05:30

1172 lines
49 KiB
Bash
Executable File

#!/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.
{
evernode="Evernode Beta v2"
maxmind_creds="687058:FtcQjM0emHFMEfgI"
cgrulesengd_default="cgrulesengd"
alloc_ratio=80
ramKB_per_instance=524288
instances_per_core=3
evernode_alias=/usr/bin/evernode
log_dir=/tmp/evernode-beta
cloud_storage="https://stevernode.blob.core.windows.net/evernode-beta"
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-v2.xrpl-labs.com"
setup_helper_dir="/tmp/evernode-setup-helpers"
nodejs_temp_bin="$setup_helper_dir/node"
jshelper_temp_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_REGISTRY_ADDRESS="r3cNR2bdao1NyvQ5ZuQvCUgqkoWGmgF34E"
export EVERNODE_AUTO_UPDATE_SERVICE="evernode-auto-update"
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/tty
# Default choice is 'y'
[ -z $yn ] && yn="y"
while ! [[ $yn =~ ^[Yy|Nn]$ ]]; do
read -p "'y' or 'n' expected: " yn </dev/tty
done
echo "" # Insert new line after answering.
[[ $yn =~ ^[Yy]$ ]] && return 0 || return 1 # 0 means success.
}
# Configuring the sashimono service is the last stage of the installation.
# Removing the sashimono service is the first stage of ununstallation.
# So if the service exists, Previous sashimono installation has been complete.
# Creating bin dir is the first stage of installation.
# Removing bin dir is the last stage of uninstalltion.
# So if the service does not exists but the bin dir exists, Previous installation or uninstalltion is failed partially.
if [ -f /etc/systemd/system/$SASHIMONO_SERVICE.service ] && [ -d $SASHIMONO_BIN ] ; then
[ "$1" == "install" ] \
&& echo "$evernode is already installed on your host. Use the 'evernode' command to manage your host." \
&& exit 1
[ "$1" != "uninstall" ] && [ "$1" != "status" ] && [ "$1" != "list" ] && [ "$1" != "update" ] && [ "$1" != "log" ] && [ "$1" != "applyssl" ] && [ "$1" != "transfer" ] && [ "$1" != "config" ] && [ "$1" != "delete" ] \
&& echomult "$evernode host management tool
\nYour host is registered on $evernode.
\nSupported commands:
\nstatus - View $evernode registration info
\nlist - View contract instances running on this system
\nlog - Generate evernode log file.
\napplyssl - Apply new SSL certificates for contracts.
\nconfig - View and update host configuration.
\nupdate - Check and install $evernode software updates
\ntransfer - Initiate an $evernode transfer for your machine
\ndelete - Remove an instance from the system and recreate the lease
\nuninstall - Uninstall and deregister from $evernode" \
&& exit 1
elif [ -d $SASHIMONO_BIN ] ; then
[ "$1" != "install" ] && [ "$1" != "uninstall" ] \
&& echomult "$evernode host management tool
\nYour system has a previous failed partial $evernode installation.
\nYou can repair previous $evernode installation by installing again.
\nSupported commands:
\nuninstall - Uninstall previous $evernode installation" \
&& exit 1
# If partially installed and interactive mode, Allow user to repair.
[ "$2" != "-q" ] && [ "$1" == "install" ] \
&& ! confirm "$evernode host management tool
\nYour system has a previous failed partial $evernode installation.
\nYou can run:
\nuninstall - Uninstall previous $evernode installation.
\n\nDo you want to repair previous $evernode installation?" \
&& exit 1
else
[ "$1" != "install" ] \
&& echomult "$evernode host management tool
\nYour system is not registered on $evernode.
\nSupported commands:
\ninstall - Install Sashimono and register on $evernode"\
&& exit 1
fi
mode=$1
if [ "$mode" == "install" ] || [ "$mode" == "uninstall" ] || [ "$mode" == "update" ] || [ "$mode" == "log" ] || [ "$mode" == "transfer" ] ; then
[ -n "$2" ] && [ "$2" != "-q" ] && [ "$2" != "-i" ] && echo "Second arg must be -q (Quiet) or -i (Interactive)" && exit 1
[ "$2" == "-q" ] && interactive=false || interactive=true
[ "$mode" == "transfer" ] && transfer=true || transfer=false
[ "$EUID" -ne 0 ] && echo "Please run with root privileges (sudo)." && exit 1
fi
# Format the given KB number into GB units.
function GB() {
echo "$(bc <<<"scale=2; $1 / 1000000") GB"
}
function check_prereq() {
# Check if node js installed.
if command -v node &>/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
}
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 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"' ]) && errs=" 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_temp_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
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_temp_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_temp_bin $jshelper_temp_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/tty
# if optional accept empty path as "-"
[ "$option" == "o" ] && [ -z "$filepath" ] && filepath="-"
# Check for valid path.
([ "$option" == "r" ] || ([ "$option" == "o" ] && [ "$filepath" != "-" ])) \
&& [ ! -f "$filepath" ] && echo "Invalid file path" && filepath=""
done
}
function set_domain_certs() {
if confirm "\n$evernode can automatically setup free SSL certificates and renewals for '$inetaddr'
using Let's Encrypt (https://letsencrypt.org/).
\nDo you want to setup Let's Encrypt automatic SSL (recommended)?" && \
confirm "Do you agree to have Let's Encrypt send SSL certificate notifications to your email '$email_address' (required)?" && \
confirm "Do you agree with Let's Encrypt Terms of Service at https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf ?" ; then
tls_key_file="letsencrypt"
tls_cert_file="letsencrypt"
tls_cabundle_file="letsencrypt"
else
echomult "You have opted out of automatic SSL setup. You need to have obtained SSL certificate files for '$inetaddr'
from a trusted authority. Please specify the certificate files you have obtained below.\n"
resolve_filepath tls_key_file r "Please specify location of the private key (usually ends with .key):"
resolve_filepath tls_cert_file r "Please specify location of the certificate (usually ends with .crt):"
resolve_filepath tls_cabundle_file o "Please specify location of ca bundle (usually ends with .ca-bundle [Optional]):"
fi
return 0
}
function validate_inet_addr_domain() {
host $inetaddr >/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=$(getent hosts $inetaddr | head -1 | awk '{ print $1 }')
# If invalid, reset inetaddr and return with non-zero code.
[ -z "$resolved" ] && inetaddr="" && return 1
return 0
}
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 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 </dev/tty
validate_inet_addr && validate_inet_addr_domain && set_domain_certs && return 0
echo "Invalid or unreachable domain name."
done
fi
# Rest of this function flow will be used for debugging and internal testing puposes only.
tls_key_file="self"
tls_cert_file="self"
tls_cabundle_file="self"
# Attempt auto-detection.
if [ "$inetaddr" == "auto" ] || $interactive ; then
inetaddr=$(hostname -I | awk '{print $1}')
validate_inet_addr && $interactive && confirm "Detected ip address '$inetaddr'. This needs to be publicly reachable over
internet.\n\nIs this the ip address you want others to use to reach your host?" && return 0
$interactive && inetaddr=""
fi
if $interactive ; then
while [ -z "$inetaddr" ]; do
read -p "Please specify the public ip/domain address your server is reachable at: " inetaddr </dev/tty
validate_inet_addr && return 0
echo "Invalid ip/domain address."
done
fi
! validate_inet_addr && echo "Invalid ip/domain address" && exit 1
}
function check_port_validity() {
# Port should be a number and between 1 through 65535.
# 1 through 1023 are used by system-supplied TCP/IP applications.
[[ $1 =~ ^[0-9]+$ ]] && [ $1 -ge 1024 ] && [ $1 -le 65535 ] && return 0
return 1
}
function set_init_ports() {
# Take default ports in interactive mode or if 'default' is specified.
# Picked default ports according to https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers
# (22223 - 23073) and (26000 - 26822) range is uncommon.
([ "$init_peer_port" == "default" ] || $interactive) && init_peer_port=22861
([ "$init_user_port" == "default" ] || $interactive) && init_user_port=26201
if $interactive ; then
if [ -n "$init_peer_port" ] && [ -n "$init_user_port" ] && confirm "Selected default port ranges (Peer: $init_peer_port-$((init_peer_port + alloc_instcount)), User: $init_user_port-$((init_user_port + alloc_instcount))).
This needs to be publicly reachable over internet. \n\nAre these the ports you want to use?" ; then
return 0
fi
init_peer_port=""
init_user_port=""
while [ -z "$init_peer_port" ]; do
read -p "Please specify the starting port of the public 'Peer port range' your server is reachable at: " init_peer_port </dev/tty
! check_port_validity $init_peer_port && init_peer_port="" && echo "Invalid port."
done
while [ -z "$init_user_port" ]; do
read -p "Please specify the starting port of the public 'User port range' your server is reachable at: " init_user_port </dev/tty
! check_port_validity $init_user_port && init_user_port="" && echo "Invalid port."
done
else
[ -z "$init_peer_port" ] && echo "Invalid starting peer port '$init_peer_port'" && exit 1
[ -z "$init_user_port" ] && echo "Invalid starting user port '$init_user_port'" && exit 1
fi
}
# Validate country code and convert to uppercase if valid.
function resolve_countrycode() {
# If invalid, reset countrycode and return with non-zero code.
if ! [[ $countrycode =~ ^[A-Za-z][A-Za-z]$ ]] ; then
countrycode=""
return 1
else
countrycode=$(echo $countrycode | tr 'a-z' 'A-Z')
return 0
fi
}
function set_country_code() {
# Attempt to auto-detect in interactive mode or if 'auto' is specified.
if [ "$countrycode" == "auto" ] || $interactive ; then
echo "Checking country code..."
echo "Using GeoLite2 data created by MaxMind, available from https://www.maxmind.com"
# MaxMind needs a ip address to detect country code. DNS is not supported by it.
# Use getent to resolve ip address in case inetaddr is a DNS name.
local mxm_ip=$(getent hosts $inetaddr | head -1 | awk '{ print $1 }')
# If getent fails (mxm_ip empty) for some reason, keep using inetaddr for MaxMind api call.
[ -z "$mxm_ip" ] && mxm_ip="$inetaddr"
local detected=$(curl -s -u "$maxmind_creds" "https://geolite.info/geoip/v2.1/country/$mxm_ip?pretty" | grep "iso_code" | head -1 | awk '{print $2}')
countrycode=${detected:1:2}
resolve_countrycode || echo "Could not detect country code."
fi
if $interactive ; then
# Uncomment this if we want the user to manually change the auto-detected country code.
# if [ -n "$countrycode" ] && ! confirm "Based on the internet address '$inetaddr' we have detected that your country
# code is '$countrycode'. Do you want to specify a different country code" ; then
# return 0
# fi
# countrycode=""
while [ -z "$countrycode" ]; do
# This will be asked if auto-detection fails or if user wants to specify manually.
read -p "Please specify the two-letter country code where your server is located in (eg. AU): " countrycode </dev/tty
resolve_countrycode || echo "Invalid country code."
done
else
resolve_countrycode || (echo "Invalid country code '$countrycode'" && exit 1)
fi
}
function set_cgrules_svc() {
local filepath=$(grep "ExecStart.*=.*/cgrulesengd$" /etc/systemd/system/*.service | head -1 | awk -F : ' { print $1 } ')
if [ -n "$filepath" ] ; then
local filename=$(basename $filepath)
cgrulesengd_service="${filename%.*}"
fi
# If service not detected, use the default name.
[ -z "$cgrulesengd_service" ] && cgrulesengd_service=$cgrulesengd_default || echo "cgroups rules engine service found: '$cgrulesengd_service'"
}
function set_instance_alloc() {
[ -z $alloc_ramKB ] && alloc_ramKB=$(( (ramKB / 100) * alloc_ratio ))
[ -z $alloc_swapKB ] && alloc_swapKB=$(( (swapKB / 100) * alloc_ratio ))
[ -z $alloc_diskKB ] && alloc_diskKB=$(( (diskKB / 100) * alloc_ratio ))
[ -z $alloc_cpu ] && alloc_cpu=$(( (1000000 / 100) * alloc_ratio ))
# If instance count is not specified, decide it based on some rules.
if [ -z $alloc_instcount ]; then
# Instance count based on total RAM
local ram_c=$(( alloc_ramKB / ramKB_per_instance ))
# Instance count based on no. of CPU cores.
local cores=$(grep -c ^processor /proc/cpuinfo)
local cpu_c=$(( cores * instances_per_core ))
# Final instance count will be the lower of the two.
alloc_instcount=$(( ram_c < cpu_c ? ram_c : cpu_c ))
fi
if $interactive; then
echomult "Based on your system resources, we have chosen the following allocation:\n
$(GB $alloc_ramKB) memory\n
$(GB $alloc_swapKB) Swap\n
$(GB $alloc_diskKB) disk space\n
Distributed among $alloc_instcount contract instances"
confirm "\nIs this the allocation you want to use?" && return 0
local ramMB=0 swapMB=0 diskMB=0
while true ; do
read -p "Specify the number of contract instances that you wish to host: " alloc_instcount </dev/tty
! [[ $alloc_instcount -gt 0 ]] && echo "Invalid instance count." || break
done
while true ; do
read -p "Specify the total memory in megabytes to distribute among all contract instances: " ramMB </dev/tty
! [[ $ramMB -gt 0 ]] && echo "Invalid memory size." || break
done
while true ; do
read -p "Specify the total Swap in megabytes to distribute among all contract instances: " swapMB </dev/tty
! [[ $swapMB -gt 0 ]] && echo "Invalid swap size." || break
done
while true ; do
read -p "Specify the total disk space in megabytes to distribute among all contract instances: " diskMB </dev/tty
! [[ $diskMB -gt 0 ]] && echo "Invalid disk size." || break
done
alloc_ramKB=$(( ramMB * 1000 ))
alloc_swapKB=$(( swapMB * 1000 ))
alloc_diskKB=$(( diskMB * 1000 ))
fi
if ! [[ $alloc_ramKB -gt 0 ]] || ! [[ $alloc_swapKB -gt 0 ]] || ! [[ $alloc_diskKB -gt 0 ]] ||
! [[ $alloc_cpu -gt 0 ]] || ! [[ $alloc_instcount -gt 0 ]]; then
echo "Invalid allocation." && exit 1
fi
}
function set_lease_amount() {
# Lease amount is mandatory field set by the user
if $interactive; then
local amount=0
while true ; do
read -p "Specify the lease amount in EVRs for your contract instances (per moment charge per contract): " amount </dev/tty
! validate_positive_decimal $amount && echo "Lease amount should be a numerical value greater than zero." || break
done
lease_amount=$amount
fi
}
function set_email_address() {
if $interactive; then
local emailAddress=""
while true ; do
read -p "Specify the contact email address (this will be published on the ledger): " emailAddress </dev/tty
email_address_length=${#emailAddress}
( ( ! [[ "$email_address_length" -le 40 ]] && echo "Email address length should not exceed 40 characters." ) ||
( ! [[ $emailAddress =~ .+@.+ ]] && echo "Email address is invalid." ) ) || break
done
email_address=$emailAddress
fi
non_interactive_email_address_length=${#email_address}
! ( ( ! [[ "$non_interactive_email_address_length" -le 40 ]] && echo "Email address length should not exceed 40 characters." ) ||
( ! [[ $email_address =~ .+@.+ ]] && echo "Email address is invalid." ) ) || exit 1
}
function set_rippled_server() {
([ -z $rippled_server ] || [ "$rippled_server" == "default" ]) && rippled_server=$default_rippled_server
if $interactive; then
if confirm "Do you want to connect to the default rippled server ($default_rippled_server)?" ; then
! validate_rippled_url $rippled_server && exit 1
else
local new_url=""
while true ; do
read -p "Specify the Rippled server URL: " new_url </dev/tty
! validate_rippled_url $new_url || break
done
rippled_server=$new_url
fi
fi
}
function set_transferee_address() {
# Here we set the default transferee address as 'CURRENT_HOST_ADDRESS', but we set it to the exact current host address in host client side.
[ -z $transferee_address ] && transferee_address=''
if $interactive; then
confirm "\nDo you want to set the current host account as the transferee's account?" && return 0
local address=''
while true ; do
read -p "Specify the XRPL account address of the transferee: " address </dev/tty
! [[ $address =~ ^r[a-zA-Z0-9]{24,34}$ ]] && echo "Invalid XRPL account address." || break
done
transferee_address=$address
fi
! [[ $transferee_address =~ ^r[a-zA-Z0-9]{24,34}$ ]] && echo "Invalid XRPL account address." && exit 1
}
function set_host_xrpl_account() {
if $interactive; then
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
read -p "Specify the XRPL account address: " xrpl_address </dev/tty
! [[ $xrpl_address =~ ^r[0-9a-zA-Z]{24,34}$ ]] && echo "Invalid XRPL account address." && continue
echo "Checking account $xrpl_address..."
! exec_jshelper validate-account $rippled_server $EVERNODE_REGISTRY_ADDRESS $xrpl_address && xrpl_address="" && continue
# Take hidden input and print empty echo (new line) at the end.
read -s -p "Specify the XRPL account secret (your input will be hidden on screen): " xrpl_secret </dev/tty && echo ""
! [[ $xrpl_secret =~ ^s[1-9A-HJ-NP-Za-km-z]{25,35}$ ]] && echo "Invalid XRPL account secret." && continue
echo "Checking account keys..."
! exec_jshelper validate-keys $rippled_server $xrpl_address $xrpl_secret && xrpl_secret="" && continue
break
done
xrpl_account_address=$xrpl_address
xrpl_account_secret=$xrpl_secret
fi
}
function install_failure() {
echo "There was an error during installation. Please provide the file $logfile to Evernode team. Thank you."
exit 1
}
function uninstall_failure() {
echo "There was an error during uninstallation."
exit 1
}
function online_version_timestamp() {
# Send HTTP HEAD request and get last modified timestamp of the installer package or setup.sh.
curl --silent --head $1 | grep 'Last-Modified:' | sed 's/[^ ]* //'
}
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)
[ -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
tar zxf $tmp/installer.tgz --strip-components=1
rm installer.tgz
set -o pipefail # We need installer exit code to detect failures (ignore the tee pipe exit code).
mkdir -p $log_dir
logfile="$log_dir/installer-$(date +%s).log"
if [ "$upgrade" == "0" ] ; then
echo "Installing prerequisites..."
! ./prereq.sh $cgrulesengd_service 2>&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
# Adding ip address as the host description.
# Currently the domain address saved only in account_info and an empty value in Hook states )
description=""
echo "Installing Sashimono on Evernode Beta V2 (Disabled Network)..."
# 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 ./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 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 uninstall_evernode() {
local upgrade=$1
# Check for 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
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
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
echo "Evernode transfer initiation was failed. Try again later." && exit 1
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
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 <private key file> <cert file> <ca bundle file (optional)>" && 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 &&
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=''
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 update_sashi=0
local update_mb=0
local sub_mode=${1}
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 <memory MB> <swap MB> <disk MB> <max instance count>\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 <lease amount>\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 <rippled server>\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
else
echomult "Invalid arguments.\n Usage: evernode config [resources|leaseamt|rippled] [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 "\nStaring 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 --------------------
echo "Thank you for trying out $evernode!"
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 secret.
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.
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
# 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
# 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
if [ "$NO_MB" == "" ]; then
init_setup_helpers
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_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
# Force uninstall on quiet mode.
$interactive && uninstall_evernode 0 || uninstall_evernode 0 -f
echo "Uninstallation complete!"
elif [ "$mode" == "transfer" ]; 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_address
init_evernode_transfer
uninstall_evernode 0
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 <instance name>" && exit 1
delete_instance "$2"
fi
[ "$mode" != "uninstall" ] && check_installer_pending_finish
exit 0
# surrounding braces are needed make the whole script to be buffered on client before execution.
}