diff --git a/CMakeLists.txt b/CMakeLists.txt index b882e49..86e39e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,13 +76,13 @@ target_precompile_headers(sagent PUBLIC src/pchheader.hpp) # Add target to generate the installer setup. add_custom_target(installer - COMMAND mkdir -p ./build/sashimono-installer - COMMAND bash -c "cp -r ./build/{sagent,sashi,hpfs,user-install.sh,user-uninstall.sh,contract_template} ./build/sashimono-installer/" - COMMAND bash -c "cp -r ./installer/{docker-install.sh,registry-install.sh,registry-uninstall.sh,prereq.sh,sashimono-install.sh,sashimono-uninstall.sh} ./build/sashimono-installer/" - COMMAND bash -c "cp -r ./dependencies/{user-cgcreate.sh,libblake3.so} ./build/sashimono-installer/" - COMMAND bash -c "cp -r ./mb-xrpl/dist ./build/sashimono-installer/mb-xrpl" - COMMAND tar cfz ./build/sashimono-installer.tar.gz --directory=./build/ sashimono-installer - COMMAND rm -r ./build/sashimono-installer + COMMAND mkdir -p ./build/installer + COMMAND bash -c "cp -r ./build/{sagent,sashi,hpfs,user-install.sh,user-uninstall.sh,contract_template} ./build/installer/" + COMMAND bash -c "cp -r ./installer/{docker-install.sh,registry-install.sh,registry-uninstall.sh,prereq.sh,sashimono-install.sh,sashimono-uninstall.sh} ./build/installer/" + COMMAND bash -c "cp -r ./dependencies/{user-cgcreate.sh,libblake3.so} ./build/installer/" + COMMAND bash -c "cp -r ./mb-xrpl/dist ./build/installer/mb-xrpl" + COMMAND tar cfz ./build/installer.tar.gz --directory=./build/ installer + COMMAND rm -r ./build/installer ) set_target_properties(installer PROPERTIES EXCLUDE_FROM_ALL TRUE) add_dependencies(installer sagent) diff --git a/README.md b/README.md index b073843..0aa0659 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Tested on Ubuntu 20.04 1. Run `make` (Sashimono agent binary 'sagent' and dependencies will be placed in build directory) ## Build Sashimono installer -Run `make installer` ('sashimono-installer.tar.gz' will be placed in build directory) +Run `make installer` ('installer.tar.gz' will be placed in build directory) ## Run Sashimono 1. `./build/sagent new` (This will create the Sashimono config in build directory. You only have to do this once) diff --git a/dependencies/user-cgcreate.sh b/dependencies/user-cgcreate.sh index 97e44ff..c460443 100755 --- a/dependencies/user-cgcreate.sh +++ b/dependencies/user-cgcreate.sh @@ -1,4 +1,5 @@ #!/bin/bash +# Call 'cgcreate' for all Sashimono users to specify resource restrictions for each user. datadir=$1 if [ -z "$datadir" ]; then @@ -14,7 +15,7 @@ if [ ! -f "$saconfig" ]; then exit 1 fi -# Calculate resourses +# Calculate resources # Read config values max_mem_kbytes=$(jq '.system.max_mem_kbytes' $saconfig) diff --git a/installer/sashimono-install.sh b/installer/sashimono-install.sh index d658ddc..fe39cda 100755 --- a/installer/sashimono-install.sh +++ b/installer/sashimono-install.sh @@ -1,8 +1,8 @@ #!/bin/bash -# Sashimono agent installation script. +# Sashimono agent installation script. This supports fresh installations as well as upgrades. # This must be executed with root privileges. -echo "---Sashimono installer---" +[ "$UPGRADE" == "0" ] && echo "---Sashimono installer---" || echo "---Sashimono installer (upgrade)---" inetaddr=$1 countrycode=$2 @@ -19,9 +19,6 @@ function stage() { echo "STAGE $1" # This is picked up by the setup console output filter. } -# Check cgroup rule config exists. -[ ! -f /etc/cgred.conf ] && echo "Cgroup is not configured. Make sure you've installed and configured cgroup-tools." && exit 1 - function rollback() { echo "Rolling back sashimono installation." "$script_dir"/sashimono-uninstall.sh @@ -29,18 +26,33 @@ function rollback() { exit 1 } +function cgrulesengd_servicename() { + # Find the cgroups rules engine service. + local cgrulesengd_filepath=$(grep "ExecStart.*=.*/cgrulesengd$" /etc/systemd/system/*.service | head -1 | awk -F : ' { print $1 } ') + if [ -n "$cgrulesengd_filepath" ] ; then + local cgrulesengd_filename=$(basename $cgrulesengd_filepath) + echo "${cgrulesengd_filename%.*}" + fi +} + +# Check cgroup rule config exists. +[ ! -f /etc/cgred.conf ] && echo "cgroups is not configured. Make sure you've installed and configured cgroup-tools." && exit 1 + +# Create directories if not exist. mkdir -p $SASHIMONO_BIN mkdir -p $DOCKER_BIN mkdir -p $SASHIMONO_DATA +# Copy contract template (delete existing) +rm -r "$SASHIMONO_DATA"/contract_template > /dev/null 2>&1 +cp -r "$script_dir"/contract_template $SASHIMONO_DATA + # Install Sashimono agent binaries into sashimono bin dir. cp "$script_dir"/{sagent,hpfs,user-cgcreate.sh,user-install.sh,user-uninstall.sh,sashimono-uninstall.sh} $SASHIMONO_BIN chmod -R +x $SASHIMONO_BIN -# Blake3 -[ ! -f /usr/local/lib/libblake3.so ] && cp "$script_dir"/libblake3.so /usr/local/lib/ -# Update linker library cache. -ldconfig +# Copy Blake3 and update linker library cache. +[ ! -f /usr/local/lib/libblake3.so ] && cp "$script_dir"/libblake3.so /usr/local/lib/ && ldconfig # Install Sashimono CLI binaries into user bin dir. cp "$script_dir"/sashi $USER_BIN @@ -60,33 +72,28 @@ stage "Installing docker packages" # registry_addr=$inetaddr:$DOCKER_REGISTRY_PORT # Setting up Sashimono admin group. -! groupadd $SASHIADMIN_GROUP && echo "Admin group creation failed." && rollback +! grep -q $SASHIADMIN_GROUP /etc/group && ! groupadd $SASHIADMIN_GROUP && echo "$SASHIADMIN_GROUP group creation failed." && rollback # If installing with sudo, add current logged-in user to Sashimono admin group. [ -n "$SUDO_USER" ] && usermod -a -G $SASHIADMIN_GROUP $SUDO_USER -# Setup Sashimono data dir. -cp -r "$script_dir"/contract_template $SASHIMONO_DATA - stage "Configuring Sashimono services" -# Find the cgroups rules engine service. -cgrulesengd_filepath=$(grep "ExecStart.*=.*/cgrulesengd$" /etc/systemd/system/*.service | head -1 | awk -F : ' { print $1 } ') -if [ -n "$cgrulesengd_filepath" ] ; then - cgrulesengd_filename=$(basename $cgrulesengd_filepath) - cgrulesengd_service="${cgrulesengd_filename%.*}" -fi +cgrulesengd_service=$(cgrulesengd_servicename) [ -z "$cgrulesengd_service" ] && echo "cgroups rules engine service does not exist." && rollback -# Setting up cgroup rules. +# Setting up cgroup rules with sashiusers group (if not already setup). echo "Creating cgroup rules..." -! groupadd $SASHIUSER_GROUP && echo "Group creation failed." && rollback -! echo "@$SASHIUSER_GROUP cpu,memory %u$CG_SUFFIX" >>/etc/cgrules.conf && echo "Cgroup rule creation failed." && rollback -# Restart the service to apply the cgrules config. -echo "Restarting the '$cgrulesengd_service' service." -systemctl restart $cgrulesengd_service || rollback +! grep -q $SASHIUSER_GROUP /etc/group && ! groupadd $SASHIUSER_GROUP && echo "$SASHIUSER_GROUP group creation failed." && rollback +if ! grep -q $SASHIUSER_GROUP /etc/cgrules.conf ; then + ! echo "@$SASHIUSER_GROUP cpu,memory %u$CG_SUFFIX" >>/etc/cgrules.conf && echo "Cgroup rule creation failed." && rollback + # Restart the service to apply the cgrules config. + echo "Restarting the '$cgrulesengd_service' service." + systemctl restart $cgrulesengd_service || rollback +fi # Install Sashimono Agent cgcreate service. -# This is a oneshot service which runs only once. +# This is a oneshot service which runs once at system startup. The intention is to run 'cgcreate' for +# all sashimono users every time the system boots up. echo "[Unit] Description=Sashimono cgroup creation service. After=network.target @@ -99,8 +106,14 @@ ExecStart=$SASHIMONO_BIN/user-cgcreate.sh $SASHIMONO_DATA WantedBy=multi-user.target" >/etc/systemd/system/$CGCREATE_SERVICE.service echo "Configuring sashimono agent service..." -# Rollback if 'sagent new' failed. -$SASHIMONO_BIN/sagent new $SASHIMONO_DATA $inetaddr $inst_count $cpuMicroSec $ramKB $swapKB $diskKB || rollback + +# Create sashimono agent config (if not exists). +if [ -f $SASHIMONO_DATA/sa.cfg ]; then + echo "Existing Sashimono data directory found. Updating..." + ! $SASHIMONO_BIN/sagent upgrade $SASHIMONO_DATA && rollback +else + ! $SASHIMONO_BIN/sagent new $SASHIMONO_DATA $inetaddr $inst_count $cpuMicroSec $ramKB $swapKB $diskKB && rollback +fi # Install Sashimono Agent systemd service. # StartLimitIntervalSec=0 to make unlimited retries. RestartSec=5 is to keep 5 second gap between restarts. @@ -123,7 +136,8 @@ systemctl daemon-reload systemctl enable $CGCREATE_SERVICE systemctl start $CGCREATE_SERVICE systemctl enable $SASHIMONO_SERVICE -# We only enable this service, so it'll automatically start on the next boot. +# Here, $SASHIMONO_SERVICE is only enabled. Not started. It'll be started after pending reboot checks at +# the bottom of this script. # Both of these services needed to be restarted if sa.cfg max instance resources are manually changed. # Install xrpl message board only of NO_MB environment is not set. @@ -133,16 +147,17 @@ if [ "$NO_MB" == "" ]; then cp -r "$script_dir"/mb-xrpl $SASHIMONO_BIN - # Creating message board user. - useradd --shell /usr/sbin/nologin -m $MB_XRPL_USER - 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. + # Creating message board user (if not exists). + if ! grep -q "^$MB_XRPL_USER:" /etc/passwd ; then + useradd --shell /usr/sbin/nologin -m $MB_XRPL_USER + usermod --lock $MB_XRPL_USER + usermod -a -G $SASHIADMIN_GROUP $MB_XRPL_USER + loginctl enable-linger $MB_XRPL_USER # Enable lingering to support service installation. + fi # First create the folder from root and then transfer ownership to the user # since the folder is created in /etc/sashimono directory. - mkdir -p $MB_XRPL_DATA - [ "$?" == "1" ] && echo "Could not create '$MB_XRPL_DATA'. Make sure you are running as sudo." && exit 1 + ! mkdir -p $MB_XRPL_DATA && echo "Could not create '$MB_XRPL_DATA'. Make sure you are running as sudo." && exit 1 # Change ownership to message board user. chown "$MB_XRPL_USER":"$MB_XRPL_USER" $MB_XRPL_DATA @@ -163,17 +178,18 @@ if [ "$NO_MB" == "" ]; then done [ "$user_systemd" != "running" ] && echo "NO_MB_USER_SYSTEMD" && rollback - # Generate beta host account. - stage "Configuring host xrpl account" - ! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN betagen $EVERNODE_REGISTRY_ADDRESS && echo "XRPLACC_FAILURE" && rollback - # Register the host on Evernode. - stage "Registering host on Evernode" - ! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN register \ - $countrycode $cpuMicroSec $ramKB $swapKB $diskKB $description && echo "REG_FAILURE" && rollback - - ! (sudo -u $MB_XRPL_USER mkdir -p "$mb_user_dir"/.config/systemd/user/) && echo "Message board user systemd folder creation failed" && rollback + # Generate beta host account (if not already setup). + 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" + ! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN betagen $EVERNODE_REGISTRY_ADDRESS && echo "XRPLACC_FAILURE" && rollback + # Register the host on Evernode. + stage "Registering host on Evernode" + ! sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN register \ + $countrycode $cpuMicroSec $ramKB $swapKB $diskKB $description && echo "REG_FAILURE" && rollback + fi stage "Configuring xrpl message board service" + ! (sudo -u $MB_XRPL_USER mkdir -p "$mb_user_dir"/.config/systemd/user/) && echo "Message board user systemd folder creation failed" && rollback # StartLimitIntervalSec=0 to make unlimited retries. RestartSec=5 is to keep 5 second gap between restarts. echo "[Unit] Description=Running and monitoring evernode xrpl transactions. @@ -189,13 +205,14 @@ if [ "$NO_MB" == "" ]; then [Install] WantedBy=default.target" | sudo -u $MB_XRPL_USER tee "$mb_user_dir"/.config/systemd/user/$MB_XRPL_SERVICE.service >/dev/null - # This service needs to be restarted when mb-xrpl.cfg is changed. + # This service needs to be restarted whenever mb-xrpl.cfg is changed. sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user enable $MB_XRPL_SERVICE - # We only enable this service, so it'll automatically start on the next boot. + # We only enable this service. It'll be started after pending reboot checks at the bottom of this script. echo "Installed Evernode xrpl message board." fi -# If there's no pending reboot, start the sashimono and message board services. +# If there's no pending reboot, start the sashimono and message board services now. Otherwise +# they'll get started at next startup. if [ ! -f /run/reboot-required.pkgs ] || [ ! -n "$(grep sashimono /run/reboot-required.pkgs)" ]; then echo "Starting the sashimono and message board services." systemctl start $SASHIMONO_SERVICE diff --git a/installer/sashimono-uninstall.sh b/installer/sashimono-uninstall.sh index bdacaae..73bdf4a 100755 --- a/installer/sashimono-uninstall.sh +++ b/installer/sashimono-uninstall.sh @@ -2,59 +2,69 @@ # Sashimono agent uninstall script. # This must be executed with root privileges. -echo "---Sashimono uninstaller---" +[ "$UPGRADE" == "0" ] && echo "---Sashimono uninstaller---" || echo "---Sashimono uninstaller (for upgrade)---" + +function cgrulesengd_servicename() { + # Find the cgroups rules engine service. + local cgrulesengd_filepath=$(grep "ExecStart.*=.*/cgrulesengd$" /etc/systemd/system/*.service | head -1 | awk -F : ' { print $1 } ') + if [ -n "$cgrulesengd_filepath" ] ; then + local cgrulesengd_filename=$(basename $cgrulesengd_filepath) + echo "${cgrulesengd_filename%.*}" + fi +} [ ! -d $SASHIMONO_BIN ] && echo "$SASHIMONO_BIN does not exist. Aborting uninstall." && exit 1 -# Find the cgroups rules engine service. -cgrulesengd_filepath=$(grep "ExecStart.*=.*/cgrulesengd$" /etc/systemd/system/*.service | head -1 | awk -F : ' { print $1 } ') -if [ -n "$cgrulesengd_filepath" ] ; then - cgrulesengd_filename=$(basename $cgrulesengd_filepath) - cgrulesengd_service="${cgrulesengd_filename%.*}" -fi -[ -z "$cgrulesengd_service" ] && echo "Warning: cgroups rules engine service does not exist." +# Message board--------------------- +# Check whether mb user exists. If so uninstall message board. +if grep -q "^$MB_XRPL_USER:" /etc/passwd ; then + + mb_user_dir=/home/"$MB_XRPL_USER" + mb_user_id=$(id -u "$MB_XRPL_USER") + mb_user_runtime_dir="/run/user/$mb_user_id" + # Remove xrpl message board service if exists. + if [ -f "$mb_user_dir"/.config/systemd/user/$MB_XRPL_SERVICE.service ]; then + sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user stop $MB_XRPL_SERVICE + sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user disable $MB_XRPL_SERVICE + fi + + if [ "$UPGRADE" == "0" ]; then + # Deregister evernode message board host registration. + echo "Attempting Evernode host deregistration..." + sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN deregister + fi + + echo "Deleting message board user..." + killall -u $MB_XRPL_USER # Kill any running processes. + userdel -f "$MB_XRPL_USER" + rm -r /home/"${MB_XRPL_USER:?}" -# Message board user. -mb_user_dir=/home/"$MB_XRPL_USER" -mb_user_id=$(id -u "$MB_XRPL_USER") -mb_user_runtime_dir="/run/user/$mb_user_id" -# Remove xrpl message board service if exists. -if [ -f "$mb_user_dir"/.config/systemd/user/$MB_XRPL_SERVICE.service ]; then - sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user stop $MB_XRPL_SERVICE - sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user disable $MB_XRPL_SERVICE fi -# Deregister evernode message board host registration. -echo "Attempting Evernode xrpl message board host deregistration..." -sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN deregister - -echo "Deleting message board user..." -killall -u $MB_XRPL_USER # Kill any running processes. -userdel -f "$MB_XRPL_USER" -rm -r /home/"${MB_XRPL_USER:?}" - -# Uninstall all contract instance users -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 - -ucount=${#sashiusers[@]} -if [ $ucount -gt 0 ]; then - - echo "Detected $ucount contract instances." - for user in "${sashiusers[@]}"; do - echo "$user" +# Uninstall all contract instance users--------------------------- +if [ "$UPGRADE" == "0" ]; then + 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 - echo "Deleting $ucount contract instances..." - for user in "${sashiusers[@]}"; do - output=$($SASHIMONO_BIN/user-uninstall.sh $user | tee /dev/stderr) - [ "${output: -10}" != "UNINST_SUC" ] && echo "Uninstall user '$user' failed. Aborting." && exit 1 - done + ucount=${#sashiusers[@]} + if [ $ucount -gt 0 ]; then + + echo "Detected $ucount contract instances." + for user in "${sashiusers[@]}"; do + echo "$user" + done + + echo "Deleting $ucount contract instances..." + for user in "${sashiusers[@]}"; do + output=$($SASHIMONO_BIN/user-uninstall.sh $user | tee /dev/stderr) + [ "${output: -10}" != "UNINST_SUC" ] && echo "Uninstall user '$user' failed. Aborting." && exit 1 + done + fi fi echo "Removing Sashimono cgroup creation service..." @@ -76,19 +86,25 @@ rm -r $SASHIMONO_BIN echo "Deleting Sashimono CLI..." rm $USER_BIN/sashi -echo "Deleting data folder..." -rm -r $SASHIMONO_DATA +if [ "$UPGRADE" == "0" ]; then + echo "Deleting data directory..." + rm -r $SASHIMONO_DATA -# When removing the cgrule, -# We first edit the config and restart the service to apply the config. -# Then we remove the attached group. -echo "Deleting cgroup rules..." -sed -i -r "/^@$SASHIUSER_GROUP\s+cpu,memory\s+%u$CG_SUFFIX/d" /etc/cgrules.conf -echo "Restarting the '$cgrulesengd_service' service..." -systemctl restart $cgrulesengd_service -groupdel $SASHIUSER_GROUP + # When removing the cgrules service, we first edit the config and restart the service to apply the config. + # Then we remove the attached group. + echo "Deleting cgroup rules..." + sed -i -r "/^@$SASHIUSER_GROUP\s+cpu,memory\s+%u$CG_SUFFIX/d" /etc/cgrules.conf + + cgrulesengd_service=$(cgrulesengd_servicename) + [ -z "$cgrulesengd_service" ] && echo "Warning: cgroups rules engine service does not exist." + + echo "Restarting the '$cgrulesengd_service' service..." + systemctl restart $cgrulesengd_service + groupdel $SASHIUSER_GROUP +fi groupdel $SASHIADMIN_GROUP -echo "Sashimono uninstalled successfully." +[ "$UPGRADE" == "0" ] && echo "Sashimono uninstalled successfully." || echo "Sashimono uninstalled successfully. Your data has been preserved." + exit 0 \ No newline at end of file diff --git a/installer/setup.sh b/installer/setup.sh index 8da8938..6d7b963 100755 --- a/installer/setup.sh +++ b/installer/setup.sh @@ -10,8 +10,10 @@ alloc_ratio=80 memKB_per_instance=819200 evernode_alias=/usr/bin/evernode log_dir=/tmp/evernode-beta -script_url="https://sthotpocket.blob.core.windows.net/sashimono/setup.sh" -installer="https://sthotpocket.blob.core.windows.net/sashimono/sashimono-installer.tar.gz" +cloud_storage="https://sthotpocket.blob.core.windows.net/sashimono" +script_url="$cloud_storage/setup.sh" +installer_url="$cloud_storage/installer.tar.gz" +version_timestamp_file="version.timestamp" # export vars used by Sashimono installer. export USER_BIN=/usr/bin @@ -32,7 +34,7 @@ export DOCKER_REGISTRY_PORT=4444 export CG_SUFFIX="-cg" export EVERNODE_REGISTRY_ADDRESS="rPmxne3NGeBJ5YY97tshCop2WVoS43bMez" -[ -f $SASHIMONO_DATA/sa.cfg ] && sashimono_installed=true || sashimono_installed=false +[ -f $SASHIMONO_BIN/sagent ] && sashimono_installed=true || sashimono_installed=false # Helper to print multi line text. # (When passed as a parameter, bash auto strips spaces and indentation which is what we want) @@ -49,18 +51,19 @@ if ! $sashimono_installed ; then \ninstall - Install Sashimono and register on $evernode" \ && exit 1 else - [ "$1" != "uninstall" ] && [ "$1" != "status" ] && [ "$1" != "list" ] \ + [ "$1" != "uninstall" ] && [ "$1" != "status" ] && [ "$1" != "list" ] && [ "$1" != "update" ] \ && echomult "$evernode host management tool \nYour system is registered on $evernode. \nSupported commands: \nstatus - View $evernode registration info \nlist - View contract instances running on this system + \nupdate - Check and install $evernode software updates \nuninstall - Uninstall and deregister from $evernode" \ && exit 1 fi mode=$1 -if [ "$mode" == "install" ] || [ "$mode" == "uninstall" ] ; then +if [ "$mode" == "install" ] || [ "$mode" == "uninstall" ] || [ "$mode" == "update" ] ; 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 @@ -269,31 +272,51 @@ function uninstall_failure() { exit 1 } -function install_sashimono() { - echo "Starting Sashimono installation..." +function online_version_timestamp() { + # Send HTTP HEAD request and get last modified timestamp of the installer package. + curl --silent --head $installer_url | 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 version_timestamp=$(online_version_timestamp) + [ -z "$version_timestamp" ] && echo "Online installer not found." && exit 1 local tmp=$(mktemp -d) cd $tmp - curl -s $installer --output installer.tgz + 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" - echo "Installing prerequisites..." - ! ./prereq.sh $cgrulesengd_service 2>&1 \ - | tee -a $logfile | stdbuf --output=L grep "STAGE" | cut -d ' ' -f 2- && install_failure + + 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 + echo "Installing Sashimono..." - ! ./sashimono-install.sh $inetaddr $countrycode $alloc_instcount \ + ! UPGRADE=$upgrade ./sashimono-install.sh $inetaddr $countrycode $alloc_instcount \ $alloc_cpu $alloc_ramKB $alloc_swapKB $alloc_diskKB $description 2>&1 \ | tee -a $logfile | stdbuf --output=L grep "STAGE" | cut -d ' ' -f 2- && install_failure set +o pipefail - + rm -r $tmp + + # Write the verison timestamp to a file for later updated version comparison. + echo $version_timestamp > $SASHIMONO_DATA/$version_timestamp_file + # Create evernode cli alias. + create_evernode_alias } -function uninstall_sashimono() { +function uninstall_evernode() { + + local upgrade=$1 # Check for existing contract instances. local users=$(cut -d: -f1 /etc/passwd | grep "^$SASHIUSER_PREFIX" | sort) @@ -305,11 +328,32 @@ function uninstall_sashimono() { done local ucount=${#sashiusers[@]} - $interactive && [ $ucount -gt 0 ] && ! confirm "This will delete $ucount contract instances. Do you still want to uninstall?" && exit 1 - ! $interactive && echo "$ucount contract instances will be deleted." + if [ "$upgrade" == "0" ] ; then + $interactive && [ $ucount -gt 0 ] && ! confirm "This will delete $ucount contract instances. Do you still want to uninstall?" && exit 1 + ! $interactive && echo "$ucount contract instances will be deleted." + fi - echo "Uninstalling Sashimono..." - ! $SASHIMONO_BIN/sashimono-uninstall.sh && uninstall_failure + [ "$upgrade" == "0" ] && echo "Uninstalling..." || echo "Uninstalling for upgrade..." + ! UPGRADE=$upgrade $SASHIMONO_BIN/sashimono-uninstall.sh && uninstall_failure + + remove_evernode_alias +} + +function update_evernode() { + echo "Checking for updates..." + local latest=$(online_version_timestamp) + [ -z "$latest" ] && echo "Could not check for updates. Online installer not found." && exit 1 + + local current=$(cat $SASHIMONO_DATA/$version_timestamp_file) + [ "$latest" == "$current" ] && 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 "Do you want to install the update?" && exit 1 + + uninstall_evernode 1 + echo "Starting upgrade..." + install_evernode 1 + echo "Upgrade complete." } # Create a copy of this same script as a command. @@ -392,18 +436,22 @@ if [ "$mode" == "install" ]; then 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" - install_sashimono - create_evernode_alias + 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 - $interactive && ! confirm "Are you sure you want to uninstall Sashimono and deregister from $evernode?" && exit 1 + 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'. This is irreversible. Make sure you have your account address and + secret elsewhere before proceeding.\n" - uninstall_sashimono - remove_evernode_alias + $interactive && ! confirm "Have you read above warning and backed up your account credentials?" && exit 1 + $interactive && ! confirm "Are you sure you want to uninstall $evernode?" && exit 1 + + uninstall_evernode 0 echo "Uninstallation complete!" elif [ "$mode" == "status" ]; then @@ -411,6 +459,10 @@ elif [ "$mode" == "status" ]; then elif [ "$mode" == "list" ]; then sashi list + +elif [ "$mode" == "update" ]; then + update_evernode + fi [ "$mode" != "uninstall" ] && check_installer_pending_finish diff --git a/mb-xrpl/app.js b/mb-xrpl/app.js index db5354b..b279517 100644 --- a/mb-xrpl/app.js +++ b/mb-xrpl/app.js @@ -29,7 +29,13 @@ async function main() { await new Setup().deregister(); } else if (process.argv.length === 3 && process.argv[2] === 'reginfo') { - await new Setup().regInfo(); + await new Setup().regInfo(false); + } + else if (process.argv.length === 4 && process.argv[2] === 'reginfo' && process.argv[3] === 'basic') { + await new Setup().regInfo(true); + } + else if (process.argv.length === 3 && process.argv[2] === 'upgrade') { + await new Setup().upgrade(); } else if (process.argv[2] === 'help') { console.log(`Usage: @@ -40,6 +46,7 @@ async function main() { node index.js register [countryCode] [cpuMicroSec] [ramKb] [swapKb] [diskKb] [description] - Register the host on Evernode. node index.js deregister - Deregister the host from Evernode. node index.js reginfo - Display Evernode registration info. + node index.js upgrade - Upgrade message board data. node index.js help - Print help.`); } else { diff --git a/mb-xrpl/lib/appenv.js b/mb-xrpl/lib/appenv.js index 91a1239..27ef0e4 100644 --- a/mb-xrpl/lib/appenv.js +++ b/mb-xrpl/lib/appenv.js @@ -18,7 +18,7 @@ appenv = { REDEEM_CREATE_TIMEOUT_THRESHOLD: 0.8, REDEEM_WAIT_TIMEOUT_THRESHOLD: 0.4, SASHI_CLI_PATH: appenv.IS_DEV_MODE ? "../build/sashi" : "/usr/bin/sashi", - MB_VERSION: '1.0.0', + MB_VERSION: '0.0.5', } Object.freeze(appenv); diff --git a/mb-xrpl/lib/setup.js b/mb-xrpl/lib/setup.js index 0dfd6fe..acb2ffc 100644 --- a/mb-xrpl/lib/setup.js +++ b/mb-xrpl/lib/setup.js @@ -43,10 +43,21 @@ class Setup { return result; } - #getConfigAccount() { + #getConfig() { if (!fs.existsSync(appenv.CONFIG_PATH)) throw `Config file does not exist at ${appenv.CONFIG_PATH}`; - return JSON.parse(fs.readFileSync(appenv.CONFIG_PATH).toString()).xrpl; + return JSON.parse(fs.readFileSync(appenv.CONFIG_PATH).toString()); + } + + #saveConfig(cfg) { + fs.writeFileSync(appenv.CONFIG_PATH, JSON.stringify(cfg, null, 2), { mode: 0o600 }); // Set file permission so only current user can read/write. + } + + newConfig(address = "", secret = "", registryAddress = "", token = "") { + this.#saveConfig({ + version: appenv.MB_VERSION, + xrpl: { address: address, secret: secret, registryAddress: registryAddress, token: token } + }); } async generateBetaHostAccount(registryAddress) { @@ -113,20 +124,9 @@ class Setup { return acc; } - newConfig(address = "", secret = "", registryAddress = "", token = "") { - if (fs.existsSync(appenv.CONFIG_PATH)) - throw `Config file already exists at ${appenv.CONFIG_PATH}`; - - const configJson = JSON.stringify({ - version: MB_VERSION, - xrpl: { address: address, secret: secret, registryAddress: registryAddress, token: token } - }, null, 2); - fs.writeFileSync(appenv.CONFIG_PATH, configJson, { mode: 0o600 }); // Set file permission so only current user can read/write. - } - async register(countryCode, cpuMicroSec, ramKb, swapKb, diskKb, description) { console.log("Registering host..."); - const acc = this.#getConfigAccount(); + const acc = this.#getConfig().xrpl; evernode.Defaults.set({ registryAddress: acc.registryAddress }); @@ -159,7 +159,7 @@ class Setup { async deregister() { console.log("Deregistering host..."); - const acc = this.#getConfigAccount(); + const acc = this.#getConfig().xrpl; evernode.Defaults.set({ registryAddress: acc.registryAddress }); @@ -170,26 +170,41 @@ class Setup { await hostClient.disconnect(); } - async regInfo() { - const acc = this.#getConfigAccount(); - evernode.Defaults.set({ - registryAddress: acc.registryAddress - }); - + async regInfo(isBasic) { + const acc = this.#getConfig().xrpl; + console.log(`Registry address: ${acc.registryAddress}`); console.log(`Host account address: ${acc.address}`); console.log(`Hosting token: ${acc.token}`); - try { - const hostClient = new evernode.HostClient(acc.address, acc.secret); - console.log('Retrieving EVR balance...') - await hostClient.connect(); - const evrBalance = await hostClient.getEVRBalance(); - console.log(`EVR balance: ${evrBalance}`); - await hostClient.disconnect(); + + if (!isBasic) { + evernode.Defaults.set({ + registryAddress: acc.registryAddress + }); + + try { + const hostClient = new evernode.HostClient(acc.address, acc.secret); + console.log('Retrieving EVR balance...') + await hostClient.connect(); + const evrBalance = await hostClient.getEVRBalance(); + console.log(`EVR balance: ${evrBalance}`); + await hostClient.disconnect(); + } + catch { + console.log('EVR balance: [Error occured when retrieving EVR balance]'); + } } - catch { - console.log('EVR balance: [Error occured when retrieving EVR balance]'); - } - console.log(`Registry address: ${acc.registryAddress}`); + } + + // Upgrades existing message board data to the new version. + async upgrade() { + + // Do a simple version change in the config. + // In the future we could have real upgrade/data migration logic here. + const cfg = this.#getConfig(); + cfg.version = appenv.MB_VERSION; + this.#saveConfig(cfg); + + await Promise.resolve(); // async placeholder. } } diff --git a/mb-xrpl/package-lock.json b/mb-xrpl/package-lock.json index 16b5b2b..a0cbb04 100644 --- a/mb-xrpl/package-lock.json +++ b/mb-xrpl/package-lock.json @@ -772,9 +772,9 @@ "dev": true }, "evernode-js-client": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.4.10.tgz", - "integrity": "sha512-naVmRgYmiGmNBjAF3LUhx0GEzz9/9bV1Fhr11HdZPokWGpGFbP04RBWR4ebtZsFCh2QEjz5fES7xEYnXfw570Q==", + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.4.11.tgz", + "integrity": "sha512-6SWdFaXC9+JRIvSgopZ3rC0g4l8tblm5mDxonQUH1vFMNl8s+AeTtqbVqYDy+L0FkRkyFh581w8NA/vmHjGrdg==", "requires": { "elliptic": "6.5.4", "ripple-address-codec": "4.2.0", diff --git a/mb-xrpl/package.json b/mb-xrpl/package.json index 1363726..8027151 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.4.10", + "evernode-js-client": "0.4.11", "sqlite3": "5.0.2" }, "devDependencies": { diff --git a/sashi-cli/main.cpp b/sashi-cli/main.cpp index 0926271..d6a0614 100644 --- a/sashi-cli/main.cpp +++ b/sashi-cli/main.cpp @@ -3,6 +3,7 @@ **/ #include "pchheader.hpp" #include "cli-manager.hpp" +#include "version.hpp" std::string exec_dir; @@ -124,7 +125,7 @@ int parse_cmd(int argc, char **argv) // Verifying subcommands. if (version->parsed()) { - std::cout << "Sashimono CLI version 1.0.0" << std::endl; + std::cout << "Sashimono CLI version " << version::CLI_VERSION << std::endl; return 0; } else if (status->parsed()) diff --git a/sashi-cli/version.hpp b/sashi-cli/version.hpp new file mode 100644 index 0000000..d287309 --- /dev/null +++ b/sashi-cli/version.hpp @@ -0,0 +1,9 @@ +#ifndef _CLI_VERSION_ +#define _CLI_VERSION_ + +namespace version +{ + constexpr const char *CLI_VERSION = "0.5.0"; +} + +#endif \ No newline at end of file diff --git a/src/conf.cpp b/src/conf.cpp index 18b98fe..f2e38ca 100644 --- a/src/conf.cpp +++ b/src/conf.cpp @@ -143,7 +143,7 @@ namespace conf if (!util::is_file_exists(path) && !util::is_dir_exists(path)) { if (path == ctx.config_file) - std::cerr << path << " does not exist. Initialize with command.\n"; + std::cerr << path << " config file does not exist. Initialize with command.\n"; else std::cerr << path << " does not exist.\n"; return -1; diff --git a/src/main.cpp b/src/main.cpp index 128b8f4..87481f9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,7 @@ std::cerr << "sagent version\n"; \ std::cerr << "sagent new [data_dir] [host_addr] [registry_addr] [inst_count] [cpu_us] [ram_kbytes] [swap_kbytes] [disk_kbytes]\n"; \ std::cerr << "sagent run [data_dir]\n"; \ + std::cerr << "sagent upgrade [data_dir]\n"; \ std::cerr << "Example: sagent run /etc/sashimono\n"; \ return -1; \ } @@ -38,6 +39,7 @@ int parse_cmd(int argc, char **argv) if ((conf::ctx.command == "new" && argc >= 2 && argc <= 10) || (conf::ctx.command == "run" && argc >= 2 && argc <= 3) || + (conf::ctx.command == "upgrade" && argc >= 2 && argc <= 3) || (conf::ctx.command == "version" && argc == 2)) return 0; } @@ -156,7 +158,7 @@ int main(int argc, char **argv) if (conf::init() != 0) return 1; - salog::init(); // Initialize logger for SA. + salog::init(); if (crypto::init() == -1) return 1; @@ -182,6 +184,21 @@ int main(int argc, char **argv) LOG_INFO << "sashimono agent exited normally."; } + else if (conf::ctx.command == "upgrade") + { + conf::set_dir_paths(argv[0], (argc == 3) ? argv[2] : ""); + + if (conf::init() != 0) + return 1; + + salog::init(); + + // Do a simple version change in the config. + // In the future we could have real upgrade/data migration logic here. + conf::cfg.version = version::AGENT_VERSION; + if (conf::write_config(conf::cfg) != 0) + return -1; + } return 0; } \ No newline at end of file diff --git a/src/version.hpp b/src/version.hpp index 997ec03..39b70fc 100644 --- a/src/version.hpp +++ b/src/version.hpp @@ -6,10 +6,10 @@ namespace version { // Sashimono agent version. Written to new configs. - constexpr const char *AGENT_VERSION = "1.0.0"; + constexpr const char *AGENT_VERSION = "0.5.0"; // Minimum compatible config version (this will be used to validate configs). - constexpr const char *MIN_CONFIG_VERSION = "1.0.0"; + constexpr const char *MIN_CONFIG_VERSION = "0.5.0"; int version_compare(const std::string &x, const std::string &y); } diff --git a/test/vmsetup.sh b/test/vmsetup.sh deleted file mode 100755 index 36eec2a..0000000 --- a/test/vmsetup.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash -# Sashimono test vm setup script. -# This must be executed with root privileges. - -# This script performs following activities. -# 1. Install debugging tools. -# 2. Install Sashimono prerequisites and reboots. -# 3. Install Sashimono after reboot. -# 4. Reboot after installing Sashimono. - -# Install debugging tools -# sqlite is used to fetch lcl from cluster script -# smem is a memory reporting tool used to analyze instance users memory usage. -apt-get -y install sqlite3 smem - -cloudstorage="https://sthotpocket.blob.core.windows.net/sashimono" -bootscript="/usr/local/bin/sashimono-install.sh" -boot_service="sashimonoboot" - -# Create boot script. -echo "#!/bin/bash - -# Remove ourselves. -rm $bootscript -systemctl disable $boot_service -rm /etc/systemd/system/$boot_service.service -systemctl daemon-reload - -# Install Sashimono and reboot. -curl -fsSL $cloudstorage/install.sh | bash -s -- -q -reboot" >$bootscript - -chmod +x $bootscript - -echo "[Unit] -Description=Sashimono test vm setup one-time boot script -ConditionPathExists=$bootscript -[Service] -ExecStart=$bootscript -q -[Install] -WantedBy=multi-user.target" >/etc/systemd/system/$boot_service.service -# Schedule the boot script to run at startup. -systemctl enable $boot_service - -# Install Sashimono prerequisites and reboot. -curl -fsSL $cloudstorage/prereq.sh | bash -s -- -q \ No newline at end of file