Files
sashimono/installer/setup.sh
Chalith Desaman 2ff4a44ba6 Fixed existing validation issues (#207)
Fixed existing validation issues (#207)
2022-12-01 14:52:31 +05:30

1003 lines
44 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
evernode="Evernode beta"
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-dev-bb7ec110-f72e-430e-b297-9210468a4cbb"
setup_script_url="$cloud_storage/setup.sh"
installer_url="$cloud_storage/installer.tar.gz"
licence_url="$cloud_storage/licence.txt"
installer_version_timestamp_file="installer.version.timestamp"
setup_version_timestamp_file="setup.version.timestamp"
default_rippled_server="wss://hooks-testnet-v2.xrpl-labs.com"
# 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="raaFre81618XegCrzTzVotAmarBcqNSAvK"
export EVERNODE_AUTO_UPDATE_SERVICE="evernode-auto-update"
# Private docker registry (not used for now)
export DOCKER_REGISTRY_USER="sashidockerreg"
export DOCKER_REGISTRY_PORT=0
# 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" != "reconfig" ] \
&& 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.
\reconfig - Change the host configuration.
\nupdate - Check and install $evernode software updates
\ntransfer - Initiate an $evernode transfer for your machine
\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)
if [ "$os" != "ubuntu" ] || [ "$osversion" != '"20.04"' ] || [ $ramKB -lt 2000000 ] || [ $swapKB -lt 2000000 ] || [ $diskKB -lt 4000000 ]; then
echomult "Your system specs are:
\n OS: $os $osversion
\n RAM: $(GB $ramKB)
\n Swap: $(GB $swapKB)
\n Disk space (/home): $(GB $diskKB)
\n$evernode host registration requires Ubuntu 20.04 with 2 GB RAM, 2 GB Swap and 4 GB free disk space for /home.
\nYour system does not meet some of the requirements. Aborting."
exit 1
fi
echo "System check complete. Your system is capable of becoming an $evernode host."
}
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 "\nIt is recommended that you obtain an SSL certificate for '$inetaddr' from a trusted certificate authority.
If you don't provide a certificate, $evernode will generate a self-signed certificate which would not be accepted
by some clients including web browsers.
\n\nHave you obtained an SSL certificate for '$inetaddr' from a trusted authority?" ; then
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]):"
else
echo "SSL certificate not provided. $evernode will generate self-signed certificate.\n"
fi
return 0
}
function validate_inet_addr_domain() {
host $inetaddr 2>&1 > /dev/null && 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_ws_url() {
! [[ $1 =~ ^(wss?:\/\/)([^\/|^:|^ ]{3,})(:([0-9]{1,5}))?$ ]] && return 1
return 0
}
function set_inet_addr() {
if $interactive ; then
echo ""
if confirm "For greater compatibility with a wide range of clients, it is recommended that you own a domain name
that others can use to reach your host over internet. If you don't, your host will not be accepted by some clients
including web browsers. \n\nDo you own a domain name for this host?" ; then
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
fi
# 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) RAM\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 RAM in megabytes to distribute among all contract instances: " ramMB </dev/tty
! [[ $ramMB -gt 0 ]] && echo "Invalid RAM 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() {
# We take the default lease amount as 0, So it is taken from the purchaser target price.
# [ -z $lease_amount ] && lease_amount=0
# Lease amount is mandatory field set by the user
if $interactive; then
# If user hasn't specified, the default lease amount is taken from the target price set by the purchaser service.
# echo "Default contract instance lease amount is taken from purchaser service target price."
# ! confirm "Do you want to specify a contract instance lease amount?" && return 0
local amount=0
while true ; do
read -p "Specify the lease amount in EVRs for your contract instances (per moment charge): " amount </dev/tty
! validate_positive_decimal $amount && echo "Lease amount should be a positive numerical value greater than zero." || break
done
lease_amount=$amount
fi
! validate_positive_decimal $lease_amount && echo "Lease amount should be a positive numerical value greater than zero." && exit 1
}
function set_rippled_server() {
([ -z $rippled_server ] || [ "$rippled_server" == "default" ]) && rippled_server=$default_rippled_server
if $interactive; then
confirm "Do you want to connect to the default rippled server ('$default_rippled_server')?" && return 0
local newURL=""
while true ; do
read -p "Specify the rippled URL: " newURL </dev/tty
! validate_ws_url $newURL && echo "Rippled URL must be a valid URL that starts with 'wss://' ." || break
done
rippled_server=$newURL
fi
! validate_ws_url $rippled_server && echo "Rippled URL must be a valid URL that starts with 'wss://' ." && exit 1
}
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 also?" && 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_secret() {
if $interactive; then
echomult "In order to register in Evernode you need to have an XRPL account with EVRs.\n"
local secret=''
while true ; do
read -p "Specify the XRPL account secret: " secret </dev/tty
! [[ $secret =~ ^[a-zA-Z0-9]+$ ]] && echo "Invalid XRPL account secret." || break
done
xrpl_account_secret=$secret
fi
! [[ $xrpl_account_secret =~ ^[a-zA-Z0-9]+$ ]] && echo "Invalid XRPL account secret." && exit 1
}
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.
description=$inetaddr
echo "Installing Sashimono..."
# 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 $description $lease_amount $rippled_server $xrpl_account_secret $tls_key_file $tls_cert_file $tls_cabundle_file 2>&1 \
| tee -a $logfile | stdbuf --output=L grep "STAGE" \
| 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
cp $tls_key_file $SASHIMONO_DATA/contract_template/cfg/tlskey.pem || exit 1
cp $tls_cert_file $SASHIMONO_DATA/contract_template/cfg/tlscert.pem || exit 1
# ca bundle is optional.
[ "$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
}
function reconfig() {
[ "$EUID" -ne 0 ] && echo "Please run with root privileges (sudo)." && exit 1
local mb_user_id=$(id -u "$MB_XRPL_USER")
local mb_user_runtime_dir="/run/user/$mb_user_id"
echomult "\nStaring reconfiguration...\n"
local saconfig="$SASHIMONO_DATA/sa.cfg"
local max_instance_count=$(jq '.system.max_instance_count' $saconfig)
local max_cpu_us=$(jq '.system.max_cpu_us' $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
( ( [[ $alloc_instcount -gt 0 ]] && [[ $max_instance_count != $alloc_instcount ]] ) ||
( [[ $alloc_cpu -gt 0 ]] && [[ $max_cpu_us != $alloc_cpu ]] ) ||
( [[ $alloc_ramKB -gt 0 ]] && [[ $max_mem_kbytes != $alloc_ramKB ]] ) ||
( [[ $alloc_swapKB -gt 0 ]] && [[ $max_swap_kbytes != $alloc_swapKB ]] ) ||
( [[ $alloc_diskKB -gt 0 ]] && [[ $max_storage_kbytes != $alloc_diskKB ]] ) ) &&
update_sashi=1
local update_mb=0
( ( [ ! -z "$rippled_server" ] && [[ $cfg_rippled_server != $rippled_server ]] ) ||
( [[ $lease_amount -gt 0 ]] && [[ $cfg_lease_amount != $lease_amount ]] ) ||
( [[ $alloc_instcount -gt 0 ]] && [[ $max_instance_count != $alloc_instcount ]] ) ) &&
update_mb=1
# Update only if changed
[ $update_sashi == 0 ] && [ $update_mb == 0 ] && echomult "Given values are already configured!\n" && exit 0
# Stop the services to stop any activities.
# Stop the sashimono service.
if [ $update_sashi == 1 ] ; then
echomult "Stopping the sashimono...\n"
systemctl stop $SASHIMONO_SERVICE
fi
# Stop the message board service.
if [ $update_sashi == 1 ] || [ $update_mb == 1 ] ; then
echomult "Stopping the message board...\n"
sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user stop $MB_XRPL_SERVICE
fi
if [ $update_sashi == 1 ] ; then
echomult "Using allocation"
[[ $alloc_cpu -gt 0 ]] && echomult "$alloc_cpu Micro Sec CPU" || alloc_cpu=0
[[ $alloc_ramKB -gt 0 ]] && echomult "$(GB $alloc_ramKB) RAM" || alloc_ramKB=0
[[ $alloc_swapKB -gt 0 ]] && echomult "$(GB $alloc_swapKB) Swap" || alloc_swapKB=0
[[ $alloc_diskKB -gt 0 ]] && echomult "$(GB $alloc_diskKB) disk space" || alloc_diskKB=0
[[ $alloc_instcount -gt 0 ]] && echomult "Distributed among $alloc_instcount contract instances" || alloc_instcount=0
echomult "\nConfiguaring sashimono...\n"
! $SASHIMONO_BIN/sagent reconfig $SASHIMONO_DATA $alloc_instcount $alloc_cpu $alloc_ramKB $alloc_swapKB $alloc_diskKB &&
echomult "\nThere was an error in updating sashimono configuration.\n" && exit 1
# Update cgroup allocations.
( [ $alloc_cpu -gt 0 ] || [ $alloc_ramKB -gt 0 ] || [ $alloc_swapKB -gt 0 ] || [ $alloc_instcount -gt 0 ] ) &&
echomult "Updating the cgroup configuration...\n" &&
! $SASHIMONO_BIN/user-cgcreate.sh $SASHIMONO_DATA && echomult "\nError occured while upgrading cgroup allocations\n" && exit 1
# Update disk quotas.
if ( [ $alloc_diskKB -gt 0 ] || [ $alloc_instcount -gt 0 ] ) ; then
echomult "Updating the disk quotas...\n"
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
fi
if [ $update_mb == 1 ] ; then
[ ! -z "$rippled_server" ] && echomult "Using the rippled address '$rippled_server'."
[[ $lease_amount -gt 0 ]] && echomult "Using lease amount $lease_amount EVRs." || lease_amount=0
[[ $alloc_instcount -gt 0 ]] || alloc_instcount=0
echomult "\nConfiguaring 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." && exit 1
fi
# Start the sashimono service.
if [ $update_sashi == 1 ] ; then
echomult "Starting the sashimono...\n"
systemctl start $SASHIMONO_SERVICE
fi
# Start the message board service.
if [ $update_sashi == 1 ] || [ $update_mb == 1 ] ; then
echomult "Starting the message board...\n"
sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user start $MB_XRPL_SERVICE
fi
}
# 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} # RAM 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} # Ripple URL
xrpl_account_secret=${14} # XRPL account secret.
tls_key_file=${15} # File path to the tls private key.
tls_cert_file=${16} # File path to the tls certificate.
tls_cabundle_file=${17} # 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.\n
\nThe setup will go through the following steps:\n
- Check your system compatibility for $evernode.\n
- Collect information about your system to be published to users.\n
- Generate a testnet XRPL account to receive $evernode hosting rewards.\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
$interactive && ! confirm "Make sure your system does not currently contain any other workloads important
to you since we will be making modifications to your system configuration.
\nThis is beta software, so there's a chance things can go wrong. \n\nContinue?" && exit 1
set_host_xrpl_secret
echo -e "Using entered value as the XRPL account serect for the configuration.\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) RAM, $(GB $alloc_swapKB) Swap, $(GB $alloc_diskKB) disk space, $alloc_instcount contract instances.\n"
set_init_ports
echo -e "Using port ranges (Peer: $init_peer_port-$((init_peer_port + alloc_instcount)), User: $init_user_port-$((init_user_port + alloc_instcount))).\n"
set_lease_amount
# Commented for future consideration.
# (( $(echo "$lease_amount > 0" |bc -l) )) && echo -e "Using lease amount $lease_amount EVRs.\n" || echo -e "Using anchor tenant target price as lease amount.\n"
(( $(echo "$lease_amount > 0" |bc -l) )) && echo -e "Using lease amount $lease_amount EVRs.\n"
set_rippled_server
echo -e "Using the rippled address '$rippled_server'.\n"
echo "Starting installation..."
install_evernode 0
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 Sashimono, Evernode's contract instance management software and
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."
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" == "reconfig" ]; then
alloc_cpu=${2} # CPU microsec to allocate for contract instances (max 1000000).
alloc_ramKB=${3} # RAM to allocate for contract instances.
alloc_swapKB=${4} # Swap to allocate for contract instances.
alloc_diskKB=${5} # Disk space to allocate for contract instances.
alloc_instcount=${6} # Total contract instance count.
lease_amount=${7} # Contract instance lease amount in EVRs.
rippled_server=${8} # Ripple URL
( [ -z $alloc_cpu ] || [ -z $alloc_ramKB ] || [ -z $alloc_swapKB ] || [ -z $alloc_diskKB ] || [ -z $alloc_instcount ] || [ -z $lease_amount ] ) &&
echomult "Invalid arguments.\n Usage: sagent reconfig <cpu microsec> <ram kbytes> <swap kbytes> <disk kbytes> <max instance count> <lease amount> <rippled server>" && exit 1
[ ! -z $alloc_cpu ] && [ $alloc_cpu != 0 ] && ( ! ( validate_positive_decimal $alloc_cpu && [[ $alloc_cpu -le 1000000 ]] ) ) && echo "Invalid cpu allocation." && exit 1
[ ! -z $alloc_ramKB ] && [ $alloc_ramKB != 0 ] && ! validate_positive_decimal $alloc_ramKB && echo "Invalid ram size." && exit 1
[ ! -z $alloc_swapKB ] && [ $alloc_swapKB != 0 ] && ! validate_positive_decimal $alloc_swapKB && echo "Invalid swap size." && exit 1
[ ! -z $alloc_diskKB ] && [ $alloc_diskKB != 0 ] && ! validate_positive_decimal $alloc_diskKB && echo "Invalid disk size." && exit 1
[ ! -z $alloc_instcount ] && [ $alloc_instcount != 0 ] && ! validate_positive_decimal $alloc_instcount && echo "Invalid instance count." && exit 1
[ ! -z $lease_amount ] && [ $lease_amount != 0 ] && ! validate_positive_decimal $lease_amount && echo "Invalid lease amount." && exit 1
[ ! -z $rippled_server ] && ! validate_ws_url $rippled_server && echo "Rippled URL must be a valid URL that starts with 'wss://' ." && exit 1
reconfig
echo "Successfully changed the configuration!"
fi
[ "$mode" != "uninstall" ] && check_installer_pending_finish
exit 0