diff --git a/CMakeLists.txt b/CMakeLists.txt index 9638c56..ab785f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,7 +78,7 @@ target_precompile_headers(sagent PUBLIC src/pchheader.hpp) 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,sashimono-install.sh,sashimono-uninstall.sh} ./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 diff --git a/dependencies/user-cgcreate.sh b/dependencies/user-cgcreate.sh index 22cfdcb..97e44ff 100755 --- a/dependencies/user-cgcreate.sh +++ b/dependencies/user-cgcreate.sh @@ -54,6 +54,7 @@ for user in "${validusers[@]}"; do # Setup user cgroup. if [ $instance_cpu_us -gt 0 ] && ! (cgcreate -g cpu:$user$cgroupsuffix && + echo "1000000" > /sys/fs/cgroup/cpu/$user$cgroupsuffix/cpu.cfs_period_us && echo "$instance_cpu_us" > /sys/fs/cgroup/cpu/$user$cgroupsuffix/cpu.cfs_quota_us); then echo "CPU cgroup creation for $user failed." has_err=1 diff --git a/dependencies/user-install.sh b/dependencies/user-install.sh index 9b63fef..75a16b5 100755 --- a/dependencies/user-install.sh +++ b/dependencies/user-install.sh @@ -39,11 +39,24 @@ function rollback() { echo "$1,INST_ERR" && exit 1 } +# Waits until a service becomes ready up to 3 seconds. +function service_ready() { + local svcstat="" + for ((i = 0; i < 30; i++)); do + sleep 0.1 + svcstat=$(sudo -u "$user" XDG_RUNTIME_DIR="$user_runtime_dir" systemctl --user is-active $1) + if [ "$svcstat" == "active" ] ; then + return 0 # Success + fi + done + return 1 # Error +} + # Setup user and dockerd service. -useradd --shell /usr/sbin/nologin -m "$user" -usermod --lock "$user" -usermod -a -G $group "$user" -loginctl enable-linger "$user" # Enable lingering to support rootless dockerd service installation. +useradd --shell /usr/sbin/nologin -m $user +usermod --lock $user +usermod -a -G $group $user +loginctl enable-linger $user # Enable lingering to support rootless dockerd service installation. chmod o-rwx "$user_dir" echo "Created '$user' user." @@ -68,6 +81,7 @@ dockerd_socket="unix://$user_runtime_dir/docker.sock" # Setup user cgroup. ! (cgcreate -g cpu:$user$cgroupsuffix && + echo "1000000" >/sys/fs/cgroup/cpu/$user$cgroupsuffix/cpu.cfs_period_us && echo "$cpu" >/sys/fs/cgroup/cpu/$user$cgroupsuffix/cpu.cfs_quota_us) && rollback "CGROUP_CPU_CREAT" ! (cgcreate -g memory:$user$cgroupsuffix && echo "${memory}K" >/sys/fs/cgroup/memory/$user$cgroupsuffix/memory.limit_in_bytes && @@ -97,11 +111,11 @@ done echo "Installing rootless dockerd for user." sudo -H -u "$user" PATH="$docker_bin":"$PATH" XDG_RUNTIME_DIR="$user_runtime_dir" "$docker_bin"/dockerd-rootless-setuptool.sh install -svcstat=$(sudo -u "$user" XDG_RUNTIME_DIR="$user_runtime_dir" systemctl --user is-active $docker_service) -[ "$svcstat" != "active" ] && rollback "NO_DOCKERSVC" - -mkdir "$user_dir"/.config/systemd/user/$docker_service.d && echo "[Service] -Environment='DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=slirp4netns'" >"$user_dir"/.config/systemd/user/$docker_service.d/override.conf +mkdir "$user_dir"/.config/systemd/user/$docker_service.d +echo "[Service] + Environment='DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=slirp4netns'" >"$user_dir"/.config/systemd/user/$docker_service.d/override.conf +sudo -u "$user" XDG_RUNTIME_DIR="$user_runtime_dir" systemctl --user restart $docker_service +service_ready $docker_service || rollback "NO_DOCKERSVC" echo "Installed rootless dockerd." @@ -134,6 +148,6 @@ RestartSec=5 WantedBy=default.target" >"$user_dir"/.config/systemd/user/ledger_fs.service sudo -u "$user" XDG_RUNTIME_DIR="$user_runtime_dir" systemctl --user daemon-reload -sudo -u "$user" XDG_RUNTIME_DIR="$user_runtime_dir" systemctl --user restart $docker_service + echo "$user_id,$user,$dockerd_socket,INST_SUC" exit 0 diff --git a/dev-setup.sh b/dev-setup.sh index 601ae84..08c38a5 100755 --- a/dev-setup.sh +++ b/dev-setup.sh @@ -71,12 +71,13 @@ popd > /dev/null 2>&1 popd > /dev/null 2>&1 rm v2.0.0.tar.gz && sudo rm -r CLI11-2.0.0 -# Library dependencies. +# Library and tools dependencies. sudo apt-get install -y \ libsodium-dev \ sqlite3 libsqlite3-dev \ libboost-stacktrace-dev \ fuse3 + jq sudo cp $scriptdir/dependencies/libblake3.so /usr/local/lib/ diff --git a/installer/cloud/install.sh b/installer/cloud/install.sh deleted file mode 100755 index 89705e8..0000000 --- a/installer/cloud/install.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# Sashimono cloud installer script. -# This will download, extract and install the Sashimono installer package. -# -q for non-interactive (quiet) mode - -package="https://sthotpocket.blob.core.windows.net/sashimono/sashimono-installer.tar.gz" - -tmp=$(mktemp -d) -cd $tmp -curl $package --output installer.tgz -tar zxf $tmp/installer.tgz --strip-components=1 -rm installer.tgz - -./sashimono-install.sh "$@" - -rm -r $tmp \ No newline at end of file diff --git a/installer/cloud/uninstall.sh b/installer/cloud/uninstall.sh deleted file mode 100755 index 7304eb7..0000000 --- a/installer/cloud/uninstall.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# Sashimono cloud uninstaller bootstrapper script. -# This will download and extract the installer and then uninstall Sashimono. -# -q for non-interactive (quiet) mode - -package="https://sthotpocket.blob.core.windows.net/sashimono/sashimono-installer.tar.gz" - -tmp=$(mktemp -d) -cd $tmp -curl $package --output installer.tgz -tar zxf $tmp/installer.tgz --strip-components=1 -rm installer.tgz - -./sashimono-uninstall.sh "$@" - -rm -r $tmp \ No newline at end of file diff --git a/installer/docker-install.sh b/installer/docker-install.sh index bfd8709..be41fb0 100755 --- a/installer/docker-install.sh +++ b/installer/docker-install.sh @@ -13,8 +13,8 @@ echo "Installing rootless docker packages into $docker_bin" tmp=$(mktemp -d) cd $tmp -curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.7.tgz --output docker.tgz -curl https://download.docker.com/linux/static/stable/x86_64/docker-rootless-extras-20.10.7.tgz --output rootless.tgz +curl -s https://download.docker.com/linux/static/stable/x86_64/docker-20.10.7.tgz --output docker.tgz +curl -s https://download.docker.com/linux/static/stable/x86_64/docker-rootless-extras-20.10.7.tgz --output rootless.tgz cd $docker_bin tar zxf $tmp/docker.tgz --strip-components=1 diff --git a/prereq.sh b/installer/prereq.sh similarity index 77% rename from prereq.sh rename to installer/prereq.sh index 571133d..179441e 100755 --- a/prereq.sh +++ b/installer/prereq.sh @@ -1,25 +1,43 @@ #!/bin/bash # Sashimono Ubuntu prerequisites installation script. # This must be executed with root privileges. -# -q for non-interactive (quiet) mode - -quiet=$1 # Adding user disk quota limitation capability # Enable user quota in fstab for root mount. -# We do not edit original file, instead we create a temp file with original and edit it. -# Replace temp file with original only if success. -tmp=$(mktemp -d) +# Enable cgroup memory and swapaccount capability. +# Setup cgroups rules engine service. + +echo "---Sashimono prerequisites installer---" + +[ -z "$1" ] && echo "cgrules engine service name not specified." && exit 1 + +tmp=$(mktemp -d) tmpfstab=$tmp.tmp originalfstab=/etc/fstab cp $originalfstab "$tmpfstab" backup=$originalfstab.sashi.bk +cgrulesengd_service=$1 # cgroups rules engine service name + +function stage() { + echo "STAGE $1" # This is picked up by the setup console output filter. +} + +stage "Installing dependencies" apt-get update -apt-get install uidmap -y +apt-get install -y uidmap slirp4netns fuse3 cgroup-tools quota curl openssl jq +# uidmap # Required for rootless docker. +# slirp4netns # Required for high performance rootless networking. +# fuse3 # Required for hpfs. +# cgroup-tools # Required to setup contract instances resource limits. +# quota # Required for disk space group quota. +# curl # Required to download installation artifacts. +# openssl # Required by Sashimono agent to create contract tls certs. +# jq # Used for json config file manipulation. # Install nodejs 14 if not exists. if ! command -v node &>/dev/null; then + stage "Installing nodejs" apt-get -y install ca-certificates # In case nodejs package certitficates are renewed. curl -sL https://deb.nodesource.com/setup_14.x | bash - apt-get -y install nodejs @@ -30,14 +48,15 @@ else fi fi -# Install slirp4netns if not exists (required for high performance rootless networking). -if ! command -v slirp4netns &>/dev/null; then - apt-get -y install slirp4netns -fi +# ------------------------------- +# fstab changes +# We do not edit original file, instead we create a temp file with original and edit it. +# Replace temp file with original only if success. # Check for pattern # And whether Options is *grpjquota=aquota.group or jqfmt=vfsv0* # If not add groupquota to the options. +stage "Configuring fstab" updated=0 sed -n -r -e "/^[^#]\S+\s+\/\s+\S+\s+\S+\s+[0-9]+\s+[0-9]+\s*/{ /^\S+\s+\/\s+\S+\s+\S*grpjquota=aquota.group\S*/{q100} }" "$tmpfstab" res=$? @@ -47,7 +66,7 @@ if [ $res -eq 0 ]; then updated=1 fi -# If the res is not success(0) or alredy exist(100). +# If the res is not success(0) or already exist(100). [ ! $res -eq 0 ] && [ ! $res -eq 100 ] && echo "fstab update failed." && exit 1 sed -n -r -e "/^[^#]\S+\s+\/\s+\S+\s+\S+\s+[0-9]+\s+[0-9]+\s*/{ /^\S+\s+\/\s+\S+\s+\S*jqfmt=vfsv0\S*/{q100} }" "$tmpfstab" @@ -77,19 +96,15 @@ fi # Check and turn on group quota if not enabled. if [ ! -f /aquota.group ]; then - # quota package is not installed. - if ! command -v quota &>/dev/null; then - apt-get install -y quota - fi quotacheck -ugm / quotaon -v / fi # ------------------------------- +stage "Configuring fuse" # Check fuse config exists. -apt-get install -y fuse3 -[ ! -f /etc/fuse.conf ] && echo "Fuse config does not exist, Make sure you've installed fuse." +[ ! -f /etc/fuse.conf ] && echo "Fuse config does not exist, Make sure you've installed fuse." && exit 1 # Set user_allow_other if not already configured # We create a temp of the config file and replace with original file only if success. @@ -135,11 +150,7 @@ else fi # ------------------------------- - -# Install cgroup-tools if not exists (required to setup resource control groups). -if ! command -v /usr/sbin/cgconfigparser >/dev/null || ! command -v /usr/sbin/cgrulesengd >/dev/null; then - apt-get install -y cgroup-tools -fi +stage "Configuring cgroup rules engine" # Copy cgred.conf from examples if not exists to setup control groups. [ ! -f /etc/cgred.conf ] && cp /usr/share/doc/cgroup-tools/examples/cgred.conf /etc/ @@ -150,29 +161,30 @@ fi # Create new cgrules.conf if not exists to setup control groups. [ ! -f /etc/cgrules.conf ] && : >/etc/cgrules.conf -# Setup a service to run cgroup rules generator. -cgrulesengd_service=cgrulesengdsvc +# Setup a service if not exists to run cgroup rules generator. +cgrulesengd_file="/etc/systemd/system/$cgrulesengd_service.service" +if ! [ -f "$cgrulesengd_file" ]; then + echo "[Unit] + Description=cgroups rules generator + After=network.target -echo "[Unit] -Description=cgroup rules generator -After=network.target + [Service] + User=root + Group=root + Type=forking + EnvironmentFile=-/etc/cgred.conf + ExecStart=/usr/sbin/cgrulesengd + Restart=on-failure -[Service] -User=root -Group=root -Type=forking -EnvironmentFile=-/etc/cgred.conf -ExecStart=/usr/sbin/cgrulesengd -Restart=on-failure - -[Install] -WantedBy=multi-user.target" >/etc/systemd/system/$cgrulesengd_service.service - -systemctl daemon-reload + [Install] + WantedBy=multi-user.target" >$cgrulesengd_file + systemctl daemon-reload +fi systemctl enable $cgrulesengd_service systemctl start $cgrulesengd_service # ------------------------------- +stage "Configuring grub" # Enable cgroup memory and swapaccount if not already configured # We create a temp of the grub file and replace with original file only if success. @@ -201,7 +213,7 @@ if [ $res -eq 100 ]; then sed -n -r -e "/^GRUB_CMDLINE_LINUX_DEFAULT=/{ /swapaccount=1/{q100}; }" "$tmpgrub" res=$? if [ $res -eq 0 ]; then - # Check whether there's swapaccount value other that 1, If so replace value with 1. + # Check whether there's swapaccount value other than 1, If so replace value with 1. # Otherwise add swapaccount=1 after cgroup_enable=memory. sed -n -r -e "/^GRUB_CMDLINE_LINUX_DEFAULT=/{ /swapaccount=/{q100}; }" "$tmpgrub" res=$? @@ -236,17 +248,14 @@ if [ $updated -eq 1 ]; then mv $grub_backup /etc/default/grub echo "Grub update failed." exit 1 - fi - echo "Updated grub. System needs to be rebooted to apply grub changes." - - if [ "$quiet" != "-q" ]; then - read -p "Type 'yes' to reboot: " confirmation < /dev/tty - [ "$confirmation" != "yes" ] && echo "Rebooting cancelled." && exit 0 fi - echo "Rebooting..." - reboot + # Indicate pending reboot in the standard reboot required file. + touch /run/reboot-required + rebootpkgs=/run/reboot-required.pkgs + (! [ -f $rebootpkgs ] || [ -z "$(grep sashimono $rebootpkgs)" ]) && echo "sashimono" >>$rebootpkgs + echo "Updated grub. System needs to be rebooted to apply grub changes." else rm -r "$tmp" echo "Grub already configured." diff --git a/installer/registry-install.sh b/installer/registry-install.sh index 1318f3a..871e11c 100755 --- a/installer/registry-install.sh +++ b/installer/registry-install.sh @@ -8,6 +8,19 @@ hubacc="hotpocketdev" images=("sashimono:hp.latest-ubt.20.04" "sashimono:hp.latest-ubt.20.04-njs.14") user_dir=/home/$user +# Waits until a service becomes ready up to 3 seconds. +function service_ready() { + local svcstat="" + for ((i = 0; i < 30; i++)); do + sleep 0.1 + svcstat=$(sudo -u "$user" XDG_RUNTIME_DIR="$user_runtime_dir" systemctl --user is-active $1) + if [ "$svcstat" == "active" ] ; then + return 0 # Success + fi + done + return 1 # Error +} + # Check if users already exists. [ "$(id -u "$user" 2>/dev/null || echo -1)" -ge 0 ] && echo "$user already exists." && exit 1 @@ -39,9 +52,7 @@ done echo "Installing rootless dockerd for user." sudo -H -u "$user" PATH="$docker_bin":"$PATH" XDG_RUNTIME_DIR="$user_runtime_dir" "$docker_bin"/dockerd-rootless-setuptool.sh install - -svcstat=$(sudo -u "$user" XDG_RUNTIME_DIR="$user_runtime_dir" systemctl --user is-active docker.service) -[ "$svcstat" != "active" ] && rollback "NO_DOCKERSVC" +service_ready "docker.service" || rollback "NO_DOCKERSVC" echo "Installed rootless dockerd for docker registry." diff --git a/installer/sashimono-install.sh b/installer/sashimono-install.sh index 5ee2ff9..e9be16b 100755 --- a/installer/sashimono-install.sh +++ b/installer/sashimono-install.sh @@ -1,181 +1,89 @@ #!/bin/bash # Sashimono agent installation script. # This must be executed with root privileges. -# -q for non-interactive (quiet) mode (This will skip the installation of xrpl message board) -user_bin=/usr/bin -sashimono_bin=/usr/bin/sashimono-agent -mb_xrpl_bin=$sashimono_bin/mb-xrpl -docker_bin=$sashimono_bin/dockerbin -sashimono_data=/etc/sashimono -mb_xrpl_data=$sashimono_data/mb-xrpl -sashimono_service="sashimono-agent" -cgcreate_service="sashimono-cgcreate" -mb_xrpl_service="sashimono-mb-xrpl" -hook_address="rntPzkVidFxnymL98oF3RAFhhBSmsyB5HP" -group="sashiuser" -admin_group="sashiadmin" -mb_user="sashimbxrpl" -cgroupsuffix="-cg" -registryuser="sashidockerreg" -registryport=4444 +echo "---Sashimono installer---" + +inetaddr=$1 +countrycode=$2 +inst_count=$3 +cpuMicroSec=$4 +ramKB=$5 +swapKB=$6 +diskKB=$7 +description=$8 + script_dir=$(dirname "$(realpath "$0")") -def_cgrulesengd_service="cgrulesengdsvc" -quiet=$1 -[ -d $sashimono_bin ] && [ -n "$(ls -A $sashimono_bin)" ] && - echo "Aborting installation. Previous Sashimono installation detected at $sashimono_bin" && exit 1 +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 -# Create bin dirs first so it automatically checks for privileged access. -mkdir -p $sashimono_bin -[ "$?" == "1" ] && echo "Could not create '$sashimono_bin'. Make sure you are running as sudo." && exit 1 -mkdir -p $docker_bin -[ "$?" == "1" ] && echo "Could not create '$docker_bin'. Make sure you are running as sudo." && exit 1 -mkdir -p $sashimono_data -[ "$?" == "1" ] && echo "Could not create '$sashimono_data'. Make sure you are running as sudo." && exit 1 - -echo "Installing Sashimono..." - -# Install curl if not exists (required to download installation artifacts). -if ! command -v curl &>/dev/null; then - apt-get install -y curl -fi - -# Install openssl if not exists (required by Sashimono agent to create contract tls certs). -if ! command -v openssl &>/dev/null; then - apt-get install -y openssl -fi - -# Blake3 -if [ ! -f /usr/local/lib/libblake3.so ]; then - cp "$script_dir"/libblake3.so /usr/local/lib/ -fi - -# jq command is used for json manipulation. -if ! command -v jq &>/dev/null; then - apt-get install -y jq -fi - -# Libfuse -apt-get install -y fuse3 - -# Update linker library cache. -sudo ldconfig - function rollback() { echo "Rolling back sashimono installation." - "$script_dir"/sashimono-uninstall.sh -q # Quiet uninstall. + "$script_dir"/sashimono-uninstall.sh echo "Rolled back the installation." exit 1 } +mkdir -p $SASHIMONO_BIN +mkdir -p $DOCKER_BIN +mkdir -p $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_bin -chmod -R +x $sashimono_bin +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 # Install Sashimono CLI binaries into user bin dir. -cp "$script_dir"/sashi $user_bin +cp "$script_dir"/sashi $USER_BIN # Download and install rootless dockerd. -"$script_dir"/docker-install.sh $docker_bin +stage "Installing docker packages" +"$script_dir"/docker-install.sh $DOCKER_BIN # Check whether docker installation dir is still empty. -[ -z "$(ls -A $docker_bin 2>/dev/null)" ] && echo "Rootless Docker installation failed." && rollback - -# Detect self host address -selfip=$(hostname -I) +[ -z "$(ls -A $DOCKER_BIN 2>/dev/null)" ] && echo "Rootless Docker installation failed." && rollback # Install private docker registry. +# stage "Installing private docker registry" # (Disabled until secure registry configuration) -# ./registry-install.sh $docker_bin $registryuser $registryport +# ./registry-install.sh $DOCKER_BIN $REGISTRY_USER $REGISTRY_PORT # [ "$?" == "1" ] && rollback -# registry_addr=$selfip:$registryport +# registry_addr=$inetaddr:$REGISTRY_PORT # Setting up Sashimono admin group. -! groupadd $admin_group && echo "Admin group creation failed." && rollback +! groupadd $SASHIADMIN_GROUP && echo "Admin 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 +cp -r "$script_dir"/contract_template $SASHIMONO_DATA -if [ "$quiet" == "-q" ]; then +stage "Configuring Sashimono services" - # We are in the quiet mode. Hence we auto-generate an XRPL test account and token details for the host. - # (This is done for testing purposes during development) - - xrpl_faucet_url="https://hooks-testnet.xrpl-labs.com/newcreds" - hook_secret="shgNKT14iCV6S4HdT9r7mgqyx94Xt" - func_url="https://func-hotpocket.azurewebsites.net/api/evrfaucet?code=pPUyV1q838ryrihA5NVlobVXj8ZGgn9HsQjGGjl6Vhgxlfha4/xCgQ==" - - # Generate new fauset account. - echo "Generating XRP faucet account..." - new_acc=$(curl -X POST $xrpl_faucet_url) - # If result is not a json, account generation failed. - [[ ! "$new_acc" =~ \{.+\} ]] && echo "Xrpl faucet account generation failed." && rollback - xrp_address=$(echo $new_acc | jq -r '.address') - xrp_secret=$(echo $new_acc | jq -r '.secret') - ([ "$xrp_address" == "" ] || [ "$xrp_address" == "null" ] || - [ "$xrp_secret" == "" ] || [ "$xrp_secret" == "null" ]) && echo "Invalid generated xrpl account details: $new_acc" && rollback - - # Wait a small interval so the XRP account gets replicated in the testnet (otherwise we may get 'Account not found' errors). - sleep 4 - - # Setup the host xrpl account with an EVR balance and default rippling flag. - echo "Setting up host XRP account..." - acc_setup_func="$func_url&action=setuphost&hookaddr=$hook_address&hooksecret=$hook_secret&addr=$xrp_address&secret=$xrp_secret" - func_code=$(curl -o /dev/null -s -w "%{http_code}\n" -d "" -X POST "$acc_setup_func") - [ "$func_code" != "200" ] && echo "Host XRP account setup failed. code:$func_code" && rollback - - # Generate random hosting token. - token=$(tr -dc A-Z >/etc/cgrules.conf && echo "Cgroup rule creation failed." && rollback +! 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." +echo "Restarting the '$cgrulesengd_service' service." systemctl restart $cgrulesengd_service || rollback # Install Sashimono Agent cgcreate service. -# This is a onshot service which runs only once. +# This is a oneshot service which runs only once. echo "[Unit] Description=Sashimono cgroup creation service. After=network.target @@ -183,14 +91,14 @@ After=network.target User=root Group=root Type=oneshot -ExecStart=$sashimono_bin/user-cgcreate.sh $sashimono_data +ExecStart=$SASHIMONO_BIN/user-cgcreate.sh $SASHIMONO_DATA [Install] -WantedBy=multi-user.target" >/etc/systemd/system/$cgcreate_service.service +WantedBy=multi-user.target" >/etc/systemd/system/$CGCREATE_SERVICE.service # Install xrpl message board systemd service. -echo "Initiating the sashimono agent..." +echo "Configuring sashimono agent service..." # Rollback if 'sagent new' failed. -$sashimono_bin/sagent new $sashimono_data $cgrulesengd_service $selfip $registry_addr || rollback +$SASHIMONO_BIN/sagent new $SASHIMONO_DATA $inetaddr $inst_count $cpuMicroSec $ramKB $swapKB $diskKB || rollback # Install Sashimono Agent systemd service. # StartLimitIntervalSec=0 to make unlimited retries. RestartSec=5 is to keep 5 second gap between restarts. @@ -202,41 +110,40 @@ StartLimitIntervalSec=0 User=root Group=root Type=simple -WorkingDirectory=$sashimono_bin -ExecStart=$sashimono_bin/sagent run $sashimono_data +WorkingDirectory=$SASHIMONO_BIN +ExecStart=$SASHIMONO_BIN/sagent run $SASHIMONO_DATA Restart=on-failure RestartSec=5 [Install] -WantedBy=multi-user.target" >/etc/systemd/system/$sashimono_service.service +WantedBy=multi-user.target" >/etc/systemd/system/$SASHIMONO_SERVICE.service systemctl daemon-reload -systemctl enable $cgcreate_service -systemctl start $cgcreate_service -systemctl enable $sashimono_service -systemctl start $sashimono_service +systemctl enable $CGCREATE_SERVICE +systemctl start $CGCREATE_SERVICE +systemctl enable $SASHIMONO_SERVICE +systemctl start $SASHIMONO_SERVICE # Both of these services needed to be restarted if sa.cfg max instance resources are manually changed. # Install xrpl message board systemd service. echo "Installing Evernode xrpl message board..." -cp -r "$script_dir"/mb-xrpl $sashimono_bin - +cp -r "$script_dir"/mb-xrpl $SASHIMONO_BIN # Creating message board user. -useradd --shell /usr/sbin/nologin -m "$mb_user" -usermod --lock "$mb_user" -usermod -a -G $admin_group "$mb_user" -loginctl enable-linger "$mb_user" # Enable lingering to support service installation. +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. # 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 +[ "$?" == "1" ] && echo "Could not create '$MB_XRPL_DATA'. Make sure you are running as sudo." && exit 1 # Change ownership to message board user. -chown "$mb_user":"$mb_user" $mb_xrpl_data +chown "$MB_XRPL_USER":"$MB_XRPL_USER" $MB_XRPL_DATA -mb_user_dir=/home/"$mb_user" -mb_user_id=$(id -u "$mb_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" # Setup env variable for the message board user. @@ -247,17 +154,22 @@ echo "Updated mb user .bashrc." user_systemd="" for ((i = 0; i < 30; i++)); do sleep 0.1 - user_systemd=$(sudo -u "$mb_user" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user is-system-running 2>/dev/null) + user_systemd=$(sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user is-system-running 2>/dev/null) [ "$user_systemd" == "running" ] && break done [ "$user_systemd" != "running" ] && echo "NO_MB_USER_SYSTEMD" && rollback -# Populate the message board config file. -# Run as the message board user. -sudo -u $mb_user MB_DATA_DIR=$mb_xrpl_data node $mb_xrpl_bin new "$xrp_address" "$xrp_secret" $hook_address "$token" +# 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 $HOOK_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_user mkdir -p "$mb_user_dir"/.config/systemd/user/) && echo "user systemd folder creation failed" && rollback +! (sudo -u $MB_XRPL_USER mkdir -p "$mb_user_dir"/.config/systemd/user/) && echo "Message board user systemd folder creation failed" && rollback +stage "Configuring xrpl message board service" # 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. @@ -265,18 +177,18 @@ After=network.target StartLimitIntervalSec=0 [Service] Type=simple -WorkingDirectory=$mb_xrpl_bin -Environment=\"MB_DATA_DIR=$mb_xrpl_data\" -ExecStart=/usr/bin/node $mb_xrpl_bin +WorkingDirectory=$MB_XRPL_BIN +Environment=\"MB_DATA_DIR=$MB_XRPL_DATA\" +ExecStart=/usr/bin/node $MB_XRPL_BIN Restart=on-failure RestartSec=5 [Install] -WantedBy=default.target" | sudo -u $mb_user tee "$mb_user_dir"/.config/systemd/user/$mb_xrpl_service.service > /dev/null +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. -sudo -u "$mb_user" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user enable $mb_xrpl_service -sudo -u "$mb_user" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user start $mb_xrpl_service +sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user enable $MB_XRPL_SERVICE +sudo -u "$MB_XRPL_USER" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user start $MB_XRPL_SERVICE echo "Installed Evernode xrpl message board." echo "Sashimono installed successfully." -exit 0 +exit 0 \ No newline at end of file diff --git a/installer/sashimono-uninstall.sh b/installer/sashimono-uninstall.sh index 8146280..2478688 100755 --- a/installer/sashimono-uninstall.sh +++ b/installer/sashimono-uninstall.sh @@ -1,142 +1,91 @@ #!/bin/bash # Sashimono agent uninstall script. -# -q for non-interactive (quiet) mode +# This must be executed with root privileges. -user_bin=/usr/bin -sashimono_bin=/usr/bin/sashimono-agent -mb_xrpl_bin=$sashimono_bin/mb-xrpl -docker_bin=$sashimono_bin/dockerbin -sashimono_data=/etc/sashimono -sashimono_conf=$sashimono_data/sa.cfg -mb_xrpl_data=$sashimono_data/mb-xrpl -sashimono_service="sashimono-agent" -cgcreate_service="sashimono-cgcreate" -mb_xrpl_service="sashimono-mb-xrpl" -registryuser="sashidockerreg" -mb_user="sashimbxrpl" -group="sashiuser" -admin_group="sashiadmin" -cgroupsuffix="-cg" -quiet=$1 +echo "---Sashimono uninstaller---" -[ ! -d $sashimono_bin ] && echo "$sashimono_bin does not exist. Aborting uninstall." && exit 1 +[ ! -d $SASHIMONO_BIN ] && echo "$SASHIMONO_BIN does not exist. Aborting uninstall." && exit 1 -if [ "$quiet" != "-q" ]; then - echo "Are you sure you want to uninstall Sashimono?" - read -p "Type 'yes' to confirm uninstall: " confirmation &1 \ + | tee -a $logfile | stdbuf --output=L grep "STAGE" | cut -d ' ' -f 2- && install_failure + echo "Installing Sashimono..." + ! ./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 +} + +function uninstall_sashimono() { + + # 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[@]} + + $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." + + echo "Uninstalling Sashimono..." + ! $SASHIMONO_BIN/sashimono-uninstall.sh && uninstall_failure +} + +# Create a copy of this same script as a command. +function create_evernode_alias() { + ! curl -fsSL $script_url --output $evernode_alias >> $logfile 2>&1 && install_failure + ! chmod +x $evernode_alias >> $logfile 2>&1 && install_failure +} + +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 sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN reginfo ; then + echo -e "\nYou are receiving $evernode rewards to the Host account. The account secret is stored in $MB_XRPL_DATA/mb-xrpl.cfg" + 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. + countrycode=${4} # 2-letter country code. + alloc_cpu=${5} # CPU microsec to allocate for contract instances (max 1000000). + alloc_ramKB=${6} # RAM to allocate for contract instances. + alloc_swapKB=${7} # Swap to allocate for contract instances. + alloc_diskKB=${8} # Disk space to allocate for contract instances. + alloc_instcount=${9} # Total contract instance count. + description=${10} # Registration description (underscore for spaces). + else + description="Evernode_host" + fi + + $interactive && ! confirm "This will install Sashimono, Evernode's contract instance management software, + and register your system as an $evernode host on the public XRPL hooks testnet.\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 + $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. Continue?" && exit 1 + + 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" + + install_sashimono + create_evernode_alias + + 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 + + uninstall_sashimono + remove_evernode_alias + echo "Uninstallation complete!" + +elif [ "$mode" == "status" ]; then + reg_info + +elif [ "$mode" == "list" ]; then + sashi list +fi + +[ "$mode" != "uninstall" ] && check_installer_pending_finish + +exit 0 \ No newline at end of file diff --git a/installer/cloud/upload.sh b/installer/upload.sh similarity index 100% rename from installer/cloud/upload.sh rename to installer/upload.sh diff --git a/mb-xrpl/mb-xrpl.js b/mb-xrpl/mb-xrpl.js index a4225ac..c5d06a2 100644 --- a/mb-xrpl/mb-xrpl.js +++ b/mb-xrpl/mb-xrpl.js @@ -5,13 +5,15 @@ const { exec } = require("child_process"); const logger = require('./lib/logger'); const evernode = require('evernode-js-client'); const { SqliteDatabase, DataTypes } = require('./lib/sqlite-handler'); +const https = require('https'); // Environment variables. const IS_DEV_MODE = process.env.MB_DEV === "1"; const FILE_LOG_ENABLED = process.env.MB_FILE_LOG === "1"; -const IS_DEREGISTER = process.env.MB_DEREGISTER === "1"; const RIPPLED_URL = process.env.MB_RIPPLED_URL || "wss://hooks-testnet.xrpl-labs.com"; const DATA_DIR = process.env.MB_DATA_DIR || __dirname; +const FAUCET_URL = process.env.MB_FAUCET_URL || "https://hooks-testnet.xrpl-labs.com/newcreds" +const EVR_SEND_URL = process.env.MB_EVR_SEND_URL || "https://func-hotpocket.azurewebsites.net/api/evrfaucet?code=pPUyV1q838ryrihA5NVlobVXj8ZGgn9HsQjGGjl6Vhgxlfha4/xCgQ==&action=fundhost&hostaddr=" const CONFIG_PATH = DATA_DIR + '/mb-xrpl.cfg'; const LOG_PATH = DATA_DIR + '/log/mb-xrpl.log'; @@ -48,19 +50,6 @@ class MessageBoard { this.db = new SqliteDatabase(dbPath); } - new(address = "", secret = "", hostAddress = "", token = "") { - if (fs.existsSync(CONFIG_PATH)) - throw `Config file already exists at ${CONFIG_PATH}`; - - const configJson = JSON.stringify({ - version: MB_VERSION, - xrpl: { address: address, secret: secret, hookAddress: hostAddress, token: token, regFeeHash: "" } - }, null, 2); - fs.writeFileSync(CONFIG_PATH, configJson, { mode: 0o600 }); // Set file permission so only current user can read/write. - - console.log(`Config file created at ${CONFIG_PATH}`); - } - async init() { if (!fs.existsSync(this.configPath)) throw `${this.configPath} does not exist.`; @@ -71,7 +60,6 @@ class MessageBoard { console.log("Using hook " + this.cfg.xrpl.hookAddress); - this.xrplApi = new evernode.XrplApi(this.rippledServer); evernode.Defaults.set({ hookAddress: this.cfg.xrpl.hookAddress, @@ -84,25 +72,15 @@ class MessageBoard { await this.hostClient.connect(); this.evernodeHookConf = this.hostClient.hookConfig; - if (IS_DEREGISTER) { - await this.deregisterHost(); - await this.hostClient.disconnect(); - await this.xrplApi.disconnect(); - return; - } - this.hookClient = new evernode.HookClient(); await this.hookClient.connect(); - // Check whether registration fee is already paid and trustline is made. - await this.checkForRegistration(); - this.db.open(); // Create redeem table if not exist. await this.createRedeemTableIfNotExists(); await this.createUtilDataTableIfNotExists(); - this.lastValidatedLedgerIndex = await this.xrplApi.ledgerIndex; + this.lastValidatedLedgerIndex = this.xrplApi.ledgerIndex; const redeems = await this.getRedeemedRecords(); for (const redeem of redeems) @@ -119,7 +97,7 @@ class MessageBoard { if (currentMoment % this.hostClient.hookConfig.hostHeartbeatFreq === 0 && currentMoment !== this.lastRechargedMoment) { this.lastRechargedMoment = currentMoment; - console.log(`Recharding at Moment ${this.lastRechargedMoment}...`) + console.log(`Recharging at Moment ${this.lastRechargedMoment}...`) try { await this.hostClient.recharge(); @@ -233,38 +211,6 @@ class MessageBoard { this.db.close(); } - async checkForRegistration() { - if (!this.cfg.xrpl.regFeeHash) { - - try { - console.log('Preparing host account...') - await this.hostClient.prepareAccount(); - console.log('Registering host...') - const tx = await this.hostClient.register(this.cfg.xrpl.token, "AU", 1000, 1024, 4096, "AUTO test Sashimono"); - - this.cfg.xrpl.regFeeHash = tx.id; - this.persistConfig(); - console.log('Registration complete. Tx hash: ' + tx.id); - } - catch (err) { - console.log("Registration failed.", err); - } - } - } - - async deregisterHost() { - // Sends evernode host de-registration transaction. - console.log(`Performing Evernode host deregistration...`); - - try { - const tx = await this.hostClient.deregister(); - console.log('Deregistration complete. ' + tx.id); - } - catch (err) { - console.log("Deregistration failed.", err) - } - } - addToExpiryList(txHash, containerName, expiryMoment) { this.expiryList.push({ txHash: txHash, @@ -428,38 +374,262 @@ class SashiCLI { } } -async function main() { - if (process.argv.length === 3) { - if (process.argv[2] === 'version') { - console.log(MB_VERSION); - process.exit(0); +class Setup { + + #httpPost(url) { + return new Promise((resolve, reject) => { + const req = https.request(url, { method: 'POST' }, (resp) => { + let data = ''; + resp.on('data', (chunk) => data += chunk); + resp.on('end', () => { + if (resp.statusCode == 200) + resolve(data); + else + reject(data); + }); + }) + + req.on("error", reject); + req.on('timeout', () => reject('Request timed out.')) + req.end() + }) + } + + async #generateFaucetAccount() { + console.log("Generating faucet account..."); + const resp = await this.#httpPost(FAUCET_URL); + const json = JSON.parse(resp); + return { + address: json.address, + secret: json.secret + }; + } + + #getRandomToken() { + const randomChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + let result = ''; + for (var i = 0; i < 3; i++) { + result += randomChars.charAt(Math.floor(Math.random() * randomChars.length)); } - else if (process.argv[2] === 'help') { - console.log(`Usage: + return result; + } + + #getConfigAccount() { + if (!fs.existsSync(CONFIG_PATH)) + throw `Config file does not exist at ${CONFIG_PATH}`; + return JSON.parse(fs.readFileSync(CONFIG_PATH).toString()).xrpl; + } + + async #sendEversFromHook(hostAddress) { + console.log("Sending EVRs..."); + + // Sometimes we may get error from func execution when some rippled servers in the testnet cluster + // haven't still updated the ledger. In such cases, we retry several times before giving up. + let attempts = 0; + while (attempts >= 0) { + try { + await this.#httpPost(EVR_SEND_URL + hostAddress); + break; + } + catch (err) { + if (++attempts <= 5) + continue; + + throw err; + } + } + } + + async generateBetaHostAccount(hookAddress) { + + evernode.Defaults.set({ + hookAddress: hookAddress, + rippledServer: RIPPLED_URL + }); + + const acc = await this.#generateFaucetAccount(); + acc.token = this.#getRandomToken(); + + // Prepare host account. + { + console.log(`Preparing host account:${acc.address} (token:${acc.token} hook:${hookAddress})`); + const hostClient = new evernode.HostClient(acc.address, acc.secret); + await hostClient.connect(); + + // Sometimes we may get 'account not found' error from rippled when some servers in the testnet cluster + // haven't still updated the ledger. In such cases, we retry several times before giving up. + let attempts = 0; + while (attempts >= 0) { + try { + await hostClient.prepareAccount(); + break; + } + catch (err) { + if (err.data.error === 'actNotFound' && ++attempts <= 5) { + console.log("actNotFound - retrying...") + // Wait and retry. + await new Promise(resolve => setTimeout(resolve, 3000)); + continue; + } + throw err; + } + } + + await hostClient.disconnect(); + } + + // Send EVRs from hook to host account. + { + await this.#sendEversFromHook(acc.address); + } + + return acc; + } + + newConfig(address = "", secret = "", hookAddress = "", token = "") { + if (fs.existsSync(CONFIG_PATH)) + throw `Config file already exists at ${CONFIG_PATH}`; + + const configJson = JSON.stringify({ + version: MB_VERSION, + xrpl: { address: address, secret: secret, hookAddress: hookAddress, token: token } + }, null, 2); + fs.writeFileSync(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(); + evernode.Defaults.set({ + hookAddress: acc.hookAddress, + rippledServer: RIPPLED_URL + }); + + const hostClient = new evernode.HostClient(acc.address, acc.secret); + await hostClient.connect(); + + // Sometimes we may get 'tecPATH_DRY' error from rippled when some servers in the testnet cluster + // haven't still updated the ledger. In such cases, we retry several times before giving up. + let attempts = 0; + while (attempts >= 0) { + try { + await hostClient.register(acc.token, countryCode, cpuMicroSec, + Math.floor((ramKb + swapKb) / 1024), Math.floor(diskKb / 1024), description.replace('_', ' ')); + break; + } + catch (err) { + if (err.code === 'tecPATH_DRY' && ++attempts <= 5) { + console.log("tecPATH_DRY - retrying...") + // Wait and retry. + await new Promise(resolve => setTimeout(resolve, 3000)); + continue; + } + throw err; + } + } + + await hostClient.disconnect(); + } + + async deregister() { + console.log("Deregistering host..."); + const acc = this.#getConfigAccount(); + evernode.Defaults.set({ + hookAddress: acc.hookAddress, + rippledServer: RIPPLED_URL + }); + + const hostClient = new evernode.HostClient(acc.address, acc.secret); + await hostClient.connect(); + await hostClient.deregister(); + await hostClient.disconnect(); + } + + async regInfo() { + const acc = this.#getConfigAccount(); + evernode.Defaults.set({ + hookAddress: acc.hookAddress, + rippledServer: RIPPLED_URL + }); + + 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(); + } + catch { + console.log('EVR balance: [Error occured when retrieving EVR balance]'); + } + console.log(`Hook address: ${acc.hookAddress}`); + } +} + +async function main() { + + if (process.argv[2] === 'version') { + console.log(MB_VERSION); + } + + try { + if (process.argv.length >= 3) { + if (process.argv.length >= 3 && process.argv[2] === 'new') { + new Setup().newConfig(process.argv[3], process.argv[4], process.argv[5], process.argv[6]); + } + else if (process.argv.length === 4 && process.argv[2] === 'betagen') { + const hookAddress = process.argv[3]; + const setup = new Setup(); + const acc = await setup.generateBetaHostAccount(hookAddress); + setup.newConfig(acc.address, acc.secret, hookAddress, acc.token); + } + else if (process.argv.length === 9 && process.argv[2] === 'register') { + await new Setup().register(process.argv[3], parseInt(process.argv[4]), parseInt(process.argv[5]), + parseInt(process.argv[6]), parseInt(process.argv[7]), process.argv[8]); + } + else if (process.argv.length === 3 && process.argv[2] === 'deregister') { + await new Setup().deregister(); + } + else if (process.argv.length === 3 && process.argv[2] === 'reginfo') { + await new Setup().regInfo(); + } + else if (process.argv[2] === 'help') { + console.log(`Usage: node index.js - Run message board. node index.js version - Print version. node index.js new [address] [secret] [hookAddress] [token] - Create new config file. + node index.js betagen [hookAddress] - Generate beta host account and populate config. + 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 help - Print help.`); - process.exit(0); + } + else { + throw "Invalid args."; + } } + else { + // Logs are formatted with the timestamp and a log file will be created inside log directory. + logger.init(LOG_PATH, FILE_LOG_ENABLED); + + console.log('Starting the Evernode xrpl message board.' + (IS_DEV_MODE ? ' (in dev mode)' : '')); + console.log('Data dir: ' + DATA_DIR); + console.log('Rippled server: ' + RIPPLED_URL); + console.log('Using Sashimono cli: ' + SASHI_CLI_PATH); + + const mb = new MessageBoard(CONFIG_PATH, DB_PATH, SASHI_CLI_PATH, RIPPLED_URL); + await mb.init(); + } + } - - const mb = new MessageBoard(CONFIG_PATH, DB_PATH, SASHI_CLI_PATH, RIPPLED_URL); - - if (process.argv.length >= 3 && process.argv[2] === 'new') { - mb.new(process.argv[3], process.argv[4], process.argv[5], process.argv[6]); - process.exit(0); + catch (err) { + console.log(err); + console.log("Evernode xrpl message board exiting with error."); + process.exit(1); } - - // Logs are formatted with the timestamp and a log file will be created inside log directory. - logger.init(LOG_PATH, FILE_LOG_ENABLED); - - console.log('Starting the Evernode xrpl message board.' + (IS_DEV_MODE ? ' (in dev mode)' : '')); - console.log('Data dir: ' + DATA_DIR); - console.log('Rippled server: ' + RIPPLED_URL); - console.log('Using Sashimono cli: ' + SASHI_CLI_PATH); - - await mb.init(); } main().catch(console.error); \ No newline at end of file diff --git a/mb-xrpl/package-lock.json b/mb-xrpl/package-lock.json index 071e091..ec20383 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.0", - "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.4.0.tgz", - "integrity": "sha512-vHmFq2NmifFcbBQnQFsebB8hKhDpc8h5HDI5MvQesPKLqZNhYlSaswrczP/SrucdkOQtg+0MahMtKAOMyGBxHg==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.4.2.tgz", + "integrity": "sha512-Ewa0F85l/mUPVYW8arQcRxie73Gko+IfFn6UVxTL7PvA3UpiB11Z0v+h0H3iw78EqYZ+fSdptG/Px6NjQCSmyA==", "requires": { "elliptic": "6.5.4", "ripple-address-codec": "4.2.0", diff --git a/mb-xrpl/package.json b/mb-xrpl/package.json index d28e2ca..bebe129 100644 --- a/mb-xrpl/package.json +++ b/mb-xrpl/package.json @@ -5,7 +5,7 @@ "build": "npm run lint && ncc build mb-xrpl.js --minify -o dist" }, "dependencies": { - "evernode-js-client": "0.4.0", + "evernode-js-client": "0.4.2", "sqlite3": "5.0.2" }, "devDependencies": { diff --git a/src/conf.cpp b/src/conf.cpp index f292b96..18b98fe 100644 --- a/src/conf.cpp +++ b/src/conf.cpp @@ -33,7 +33,8 @@ namespace conf * Create config here. * @return 0 for success. -1 for failure. */ - int create(std::string_view cgrulesengd_service, std::string_view host_addr, std::string_view registry_addr) + int create(std::string_view host_addr, std::string_view registry_addr, const size_t inst_count, + const size_t cpu_us, const size_t ram_kbytes, const size_t swap_kbytes, const size_t disk_kbytes) { if (util::is_file_exists(ctx.config_file)) { @@ -42,7 +43,7 @@ namespace conf } else { - // Recursivly create contract directory. Return an error if unable to create + // Recursively create contract directory. Return an error if unable to create if (util::create_dir_tree_recursive(ctx.log_dir) == -1 || util::create_dir_tree_recursive(ctx.data_dir) == -1) { @@ -62,11 +63,11 @@ namespace conf cfg.hp.init_peer_port = 22861; cfg.hp.init_user_port = 8081; - cfg.system.max_instance_count = 3; - cfg.system.max_mem_kbytes = 1048576; // Total 1GB RAM - cfg.system.max_swap_kbytes = 3145728; // Total 3GB swap. - cfg.system.max_cpu_us = 5000000; // CPU cfs period cannot be less than 1ms (i.e. 1000) or larger than 1s (i.e. 1000000) per instance. - cfg.system.max_storage_kbytes = 5242880; // Total 5GB disk storage. + cfg.system.max_instance_count = !inst_count ? 3 : inst_count; + cfg.system.max_mem_kbytes = !ram_kbytes ? 1048576 : ram_kbytes; + cfg.system.max_swap_kbytes = !swap_kbytes ? 3145728 : swap_kbytes; + cfg.system.max_cpu_us = !cpu_us ? 900000 : cpu_us; // Total CPU allocation out of 1000000 microsec (1 sec). + cfg.system.max_storage_kbytes = !disk_kbytes ? 5242880 : disk_kbytes; const std::string img_prefix = registry_addr.empty() ? "hotpocketdev" : std::string(registry_addr); cfg.docker.images["hp.latest-ubt.20.04"] = img_prefix + "/sashimono:hp.latest-ubt.20.04"; @@ -77,8 +78,6 @@ namespace conf cfg.log.log_level = "inf"; cfg.log.loggers.emplace("console"); - cfg.service.cgrulesengd = cgrulesengd_service; - // We don't enable file logging by default because Sashimono running as a systemd service // would automatically log console output to journal log. // cfg.log.loggers.emplace("file"); @@ -301,22 +300,6 @@ namespace conf } } - // service - { - jpath = "service"; - - try - { - const jsoncons::ojson &service = d["service"]; - cfg.service.cgrulesengd = service["cgrulesengd"].as(); - } - catch (const std::exception &e) - { - print_missing_field_error(jpath, e); - return -1; - } - } - return 0; } @@ -383,15 +366,6 @@ namespace conf d.insert_or_assign("log", log_config); } - // Service configs. - { - jsoncons::ojson service_config; - - service_config.insert_or_assign("cgrulesengd", cfg.service.cgrulesengd); - - d.insert_or_assign("service", service_config); - } - return write_json_file(ctx.config_file, d); } diff --git a/src/conf.hpp b/src/conf.hpp index f42231f..d40bca8 100644 --- a/src/conf.hpp +++ b/src/conf.hpp @@ -75,10 +75,10 @@ namespace conf struct system_config { - size_t max_cpu_us = 0; // Max CPU time the agent process can consume. - size_t max_mem_kbytes = 0; // Max memory the agent process can allocate in KB. - size_t max_swap_kbytes = 0; // Max swap memory the agent process can allocate in KB. - size_t max_storage_kbytes = 0; // Max physical storage the agent process can allocate in KB. + size_t max_cpu_us = 0; // Max CPU time allocated to all instances (out of 1000000 microsec). + size_t max_mem_kbytes = 0; // Max memory allocated to all instances in KB. + size_t max_swap_kbytes = 0; // Max swap memory allocated to all instances in KB. + size_t max_storage_kbytes = 0; // Max physical storage allocated to all instances in KB. size_t max_instance_count = 0; // Max number of instances that can be created. }; @@ -87,11 +87,6 @@ namespace conf std::unordered_map images; }; - struct service_config - { - std::string cgrulesengd; // Cgroup rule generator service. - }; - struct sa_config { std::string version; @@ -99,7 +94,6 @@ namespace conf system_config system; docker_config docker; log_config log; - service_config service; }; struct sa_context @@ -129,7 +123,8 @@ namespace conf int init(); - int create(std::string_view cgrulesengd_service, std::string_view host_addr, std::string_view registry_addr); + int create(std::string_view host_addr, std::string_view registry_addr, const size_t inst_count, + const size_t cpu_us, const size_t ram_kbytes, const size_t swap_kbytes, const size_t disk_kbytes); void set_dir_paths(std::string exepath, std::string datadir); diff --git a/src/main.cpp b/src/main.cpp index 4700b69..128b8f4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,15 +13,15 @@ #include "util/util.hpp" #include "killswitch/killswitch.h" -#define PARSE_ERROR \ - { \ - std::cerr << "Arguments mismatch.\n"; \ - std::cerr << "Usage:\n"; \ - std::cerr << "sagent version\n"; \ - std::cerr << "sagent new [data_dir] [cgrulesengd_service] [host_addr] [registry_addr]\n"; \ - std::cerr << "sagent run [data_dir]\n"; \ - std::cerr << "Example: sagent run /etc/sashimono\n"; \ - return -1; \ +#define PARSE_ERROR \ + { \ + std::cerr << "Arguments mismatch.\n"; \ + std::cerr << "Usage:\n"; \ + 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 << "Example: sagent run /etc/sashimono\n"; \ + return -1; \ } /** @@ -36,7 +36,7 @@ int parse_cmd(int argc, char **argv) { conf::ctx.command = argv[1]; - if ((conf::ctx.command == "new" && argc >= 2 && argc <= 6) || + if ((conf::ctx.command == "new" && argc >= 2 && argc <= 10) || (conf::ctx.command == "run" && argc >= 2 && argc <= 3) || (conf::ctx.command == "version" && argc == 2)) return 0; @@ -118,7 +118,7 @@ int main(int argc, char **argv) // Extract the CLI args // This call will populate conf::ctx if (parse_cmd(argc, argv) != 0) - return -1; + return 1; if (conf::ctx.command == "version") { @@ -129,11 +129,19 @@ int main(int argc, char **argv) conf::set_dir_paths(argv[0], (argc >= 3) ? argv[2] : ""); // This will create a new config. - const std::string cgrulesengd_service = (argc >= 4) ? argv[3] : ""; - const std::string host_addr = (argc >= 5) ? argv[4] : ""; - const std::string registry_addr = (argc >= 6) ? argv[5] : ""; - if (conf::create(cgrulesengd_service, host_addr, registry_addr) != 0) - return -1; + const std::string host_addr = (argc >= 4) ? argv[3] : ""; + size_t inst_count = 0, cpu_us = 0, ram_kbytes = 0, swap_kbytes = 0, disk_kbytes = 0; + + if (((argc >= 5) && (util::stoull(argv[4], inst_count) != 0 || inst_count == 0)) || + ((argc >= 6) && (util::stoull(argv[5], cpu_us) != 0 || cpu_us == 0)) || + ((argc >= 7) && (util::stoull(argv[6], ram_kbytes) != 0 || ram_kbytes == 0)) || + ((argc >= 8) && (util::stoull(argv[7], swap_kbytes) != 0 || swap_kbytes == 0)) || + ((argc >= 9) && (util::stoull(argv[8], disk_kbytes) != 0 || disk_kbytes == 0)) || + conf::create(host_addr, "", inst_count, cpu_us, ram_kbytes, swap_kbytes, disk_kbytes) != 0) + { + std::cerr << "Invalid Sashimono Agent config creation args.\n"; + return 1; + } } else if (conf::ctx.command == "run") { @@ -142,16 +150,16 @@ int main(int argc, char **argv) if (kill_switch(util::get_epoch_milliseconds())) { std::cerr << "Sashimono Agent usage limit failure.\n"; - return -1; + return 1; } if (conf::init() != 0) - return -1; + return 1; salog::init(); // Initialize logger for SA. if (crypto::init() == -1) - return -1; + return 1; LOG_INFO << "Sashimono agent (version " << version::AGENT_VERSION << ")"; LOG_INFO << "Log level: " << conf::cfg.log.log_level; @@ -160,7 +168,7 @@ int main(int argc, char **argv) if (comm::init() == -1 || hp::init() == -1) { deinit(); - return -1; + return 1; } // After initializing primary subsystems, register the exit handler. diff --git a/src/util/util.cpp b/src/util/util.cpp index 6330ff8..298215c 100644 --- a/src/util/util.cpp +++ b/src/util/util.cpp @@ -279,6 +279,26 @@ namespace util return 0; } + /** + * Converts given string to a uint_64. A wrapper function for std::stoull. + * @param str String variable. + * @param result Variable to store the answer from the conversion. + * @return Returns 0 in a successful conversion and -1 on error. + */ + int stoull(const std::string &str, uint64_t &result) + { + try + { + result = std::stoull(str); + } + catch (const std::exception &e) + { + // Return -1 if any exceptions are captured. + return -1; + } + return 0; + } + /** * Construct the user contract directory path when username is given. * @param username Username of the user. @@ -404,7 +424,8 @@ namespace util { params.append(*itr); if (std::next(itr) != input_params.end()) - params.append(" ");; + params.append(" "); + ; } const int len = 23 + (file_name.length() * 2) + params.length(); char command[len]; diff --git a/src/util/util.hpp b/src/util/util.hpp index 39d0107..e429837 100644 --- a/src/util/util.hpp +++ b/src/util/util.hpp @@ -49,6 +49,8 @@ namespace util int stoul(const std::string &str, uint16_t &result); + int stoull(const std::string &str, uint64_t &result); + const std::string get_user_contract_dir(const std::string &username, std::string_view container_name); int get_system_user_info(std::string_view username, user_info &user_info); diff --git a/test/vm-cluster/cluster.sh b/test/vm-cluster/cluster.sh index d58d60c..bb63d7b 100755 --- a/test/vm-cluster/cluster.sh +++ b/test/vm-cluster/cluster.sh @@ -18,7 +18,7 @@ # lcl - Get lcl of the hosts. # peers - Get the cfg peer list of the hosts. # logs - Get the log lines grep by a given keywords. -# replacebin - Replaces a given file to /usr/bin/sashimono-agent dir and keep a backup of existing file. +# replacebin - Replaces a given file to /usr/bin/sashimono dir and keep a backup of existing file. # create - Create new sashimono hotpocket instance in each node. # createall - Create sashimono hotpocket instances in all nodes parallely. @@ -59,11 +59,6 @@ else exit 1 fi -# jq command is used for json manipulation. -if ! command -v jq &>/dev/null; then - sudo apt-get install -y jq -fi - configfile=config.json if [ ! -f $configfile ]; then # Create default config file. @@ -186,12 +181,12 @@ if [ $mode == "reconfig" ]; then max_instance_count=-1 fi - cgrulesengd_service="cgrulesengdsvc" + cgrulesengd_service="cgrulesengd" sashimono_service="sashimono-agent" saconfig="/etc/sashimono/sa.cfg" - uninstall="curl -fsSL https://sthotpocket.blob.core.windows.net/sashimono/uninstall.sh | bash -s -- -q" - install="curl -fsSL https://sthotpocket.blob.core.windows.net/sashimono/install.sh | bash -s -- -q" + uninstall="evernode uninstall -q" + install="curl -fsSL https://sthotpocket.blob.core.windows.net/evernode/setup.sh | cat | SKIP_SYSREQ=1 bash -s install -q auto auto 1000000 1000000 2097152 3145728 3 Auto_host" restartcgrs="systemctl restart $cgrulesengd_service.service" restartsas="systemctl restart $sashimono_service.service" @@ -207,12 +202,12 @@ if [ $mode == "reconfig" ]; then # Reinstall sashimono only if reinstall specified. if [ ! -z $reinstall ] && [ $reinstall == "R" ]; then - command="$uninstall && $install && $changecfg && $restartcgrs && $restartsas" + command="$uninstall &>/dev/null && echo 'Sashimono uninstalled.' && $install &>/dev/null && echo 'Sashimono installed.' && $changecfg && $restartcgrs && $restartsas" else command="$changecfg && $restartsas" fi - if ! sshskp $sshuser@$hostaddr $command &>/dev/null; then + if ! sshskp $sshuser@$hostaddr $command ; then printf "$PRINTFORMAT" "$nodeno" "Error occured reconfiguring sashimono." else # Remove host info if reinstall. @@ -339,8 +334,8 @@ if [ $mode == "replacebin" ]; then nodeno=$(expr $1 + 1) replace=$2 filename=$(basename $replace) - original="/usr/bin/sashimono-agent/$filename" - backup="/usr/bin/sashimono-agent/$filename.bk" + original="/usr/bin/sashimono/$filename" + backup="/usr/bin/sashimono/$filename.bk" sshskp $sshuser@$hostaddr "mv $original $backup" && scpskp -q $replace $sshuser@$hostaddr:$original echo "node$nodeno: Updated $original, Kept backup $backup" } @@ -359,7 +354,7 @@ if [ $mode == "replacebin" ]; then fi if [ $mode == "docker-pull" ]; then - dockerbin=/usr/bin/sashimono-agent/dockerbin/docker + dockerbin=/usr/bin/sashimono/dockerbin/docker repo=$(echo $continfo | jq -r '.docker.repo') if [ "$repo" == "" ] || [ "$repo" == "null" ]; then echo "repo not specified." diff --git a/test/vm-cluster/evcluster.js b/test/vm-cluster/evcluster.js index e906d68..de93276 100644 --- a/test/vm-cluster/evcluster.js +++ b/test/vm-cluster/evcluster.js @@ -168,7 +168,7 @@ async function initHosts() { async function initHostAccountData(host) { const output = await execSsh(host, "cat /etc/sashimono/mb-xrpl/mb-xrpl.cfg"); if (!output || output.trim() === "") { - console.log("ERROR: No output from mb-xrpl config read.") + console.log(host, "ERROR: No output from mb-xrpl config read.") return; } diff --git a/test/vm-cluster/package-lock.json b/test/vm-cluster/package-lock.json index 0020025..23ffb8d 100644 --- a/test/vm-cluster/package-lock.json +++ b/test/vm-cluster/package-lock.json @@ -258,9 +258,9 @@ "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=" }, "evernode-js-client": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.3.4.tgz", - "integrity": "sha512-uaGNP+IH4Gqw1j1gxlg5WUe2coSN85GJ65jnQDUT216oCHXnKeHCzWn/EUiCSd+0T8J5MReM0IVjV/7f1CwnMA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/evernode-js-client/-/evernode-js-client-0.4.2.tgz", + "integrity": "sha512-Ewa0F85l/mUPVYW8arQcRxie73Gko+IfFn6UVxTL7PvA3UpiB11Z0v+h0H3iw78EqYZ+fSdptG/Px6NjQCSmyA==", "requires": { "elliptic": "6.5.4", "ripple-address-codec": "4.2.0", diff --git a/test/vm-cluster/package.json b/test/vm-cluster/package.json index 02e20a0..9e899ae 100644 --- a/test/vm-cluster/package.json +++ b/test/vm-cluster/package.json @@ -2,7 +2,7 @@ "name": "evcluster", "type": "module", "dependencies": { - "evernode-js-client": "0.3.4", + "evernode-js-client": "0.4.2", "node-fetch": "3.0.0" } }