Evernode setup installer. (#87)

This commit is contained in:
Ravin Perera
2022-01-05 15:17:26 +05:30
committed by GitHub
parent 3d531afd01
commit 6e59ca5f79
25 changed files with 980 additions and 536 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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/

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 <Not starting with a comment><Not whitespace(Device)><Whitespace></><Whitespace><Not whitespace(FS type)><Whitespace><No whitespace(Options)><Whitespace><Number(Dump)><Whitespace><Number(Pass)>
# And whether Options is <Not whitespace>*grpjquota=aquota.group or jqfmt=vfsv0<Not whitespace>*
# 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."

View File

@@ -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."

View File

@@ -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 </dev/urandom | head -c 3)
echo "Auto-generated host information."
else
echo "Please answer following questions to setup Evernode xrpl message board."
# Ask for input until a correct value is given
while [[ ! "$token" =~ ^[A-Z]{3}$ ]]; do
read -p "Token name? " token </dev/tty
[[ ! "$token" =~ ^[A-Z]{3}$ ]] && echo "Token name should be 3 UPPERCASE letters."
done
while [[ -z "$xrp_address" ]]; do
read -p "XRPL account address? " xrp_address </dev/tty
done
while [[ -z "$xrp_secret" ]]; do
read -p "XRPL account secret? " xrp_secret </dev/tty
done
# Ask for cgroup rule generator service until a valid service provided.
while true; do
read -p "Enter your cgroup rule generator service name (default: $def_cgrulesengd_service)? " cgrulesengd_service </dev/tty
# Set service name to default if user input is empty.
[ -z "$cgrulesengd_service" ] && cgrulesengd_service="$def_cgrulesengd_service"
# Remove '.service' if user has given the full name.
cgrulesengd_service=$(echo $cgrulesengd_service | awk '{print tolower($0)}' | sed 's/\.service$//')
# Break the loop if service is valid and exist.
[ -f /etc/systemd/system/"$cgrulesengd_service".service ] && break
echo "$cgrulesengd_service systemd service does not exist."
done
fi
# Set cgrulesengd_service to default if it's still empty.
if [[ -z "$cgrulesengd_service" ]]; then
cgrulesengd_service="$def_cgrulesengd_service"
[ ! -f /etc/systemd/system/"$cgrulesengd_service".service ] && echo "$cgrulesengd_service systemd service does not exist." && rollback
fi
# Find the cgroups rules engine service.
cgrulesengd_filename=$(basename $(grep "ExecStart.*=.*/cgrulesengd$" /etc/systemd/system/*.service | head -1 | awk -F : ' { print $1 } '))
cgrulesengd_service="${cgrulesengd_filename%.*}"
[ -z "$cgrulesengd_service" ] && echo "cgroups rules engine service does not exist." && rollback
# Setting up cgroup rules.
echo "Creating cgroup rules..."
! groupadd $group && echo "Group creation failed." && rollback
! echo "@$group cpu,memory %u$cgroupsuffix" >>/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

View File

@@ -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 </dev/tty
[ "$confirmation" != "yes" ] && echo "Uninstall cancelled." && exit 0
fi
# Get the cgrules service from the config.
cgrulesengd_service=$(jq -r '.service.cgrulesengd' $sashimono_conf | awk '{print tolower($0)}' | sed 's/\.service$//')
[ ! -f /etc/systemd/system/"$cgrulesengd_service".service ] && echo "Warning: $cgrulesengd_service systemd service does not exist."
# Find the cgroups rules engine service.
cgrulesengd_filename=$(basename $(grep "ExecStart.*=.*/cgrulesengd$" /etc/systemd/system/*.service | head -1 | awk -F : ' { print $1 } '))
cgrulesengd_service="${cgrulesengd_filename%.*}"
[ -z "$cgrulesengd_service" ] && echo "Warning: cgroups rules engine service does not exist."
# Message board user.
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"
# Remove xrpl message board service if exists.
if [ -f "$mb_user_dir"/.config/systemd/user/$mb_xrpl_service.service ]; then
sudo -u "$mb_user" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user stop $mb_xrpl_service
sudo -u "$mb_user" XDG_RUNTIME_DIR="$mb_user_runtime_dir" systemctl --user disable $mb_xrpl_service
fi
if [ "$quiet" == "-q" ]; then
# We only perform this for our testing setup during development.
echo "Cleaning up host XRP account..."
hook_address="rntPzkVidFxnymL98oF3RAFhhBSmsyB5HP"
func_url="https://func-hotpocket.azurewebsites.net/api/evrfaucet?code=pPUyV1q838ryrihA5NVlobVXj8ZGgn9HsQjGGjl6Vhgxlfha4/xCgQ=="
mb_xrpl_conf=$mb_xrpl_data/mb-xrpl.cfg
xrp_address=$(jq -r '.xrpl.address' $mb_xrpl_conf)
xrp_secret=$(jq -r '.xrpl.secret' $mb_xrpl_conf)
if [ "$xrp_address" != "" ] && [ "$xrp_secret" != "" ]; then
acc_clean_func="$func_url&action=cleanhost&hookaddr=$hook_address&addr=$xrp_address&secret=$xrp_secret"
func_code=$(curl -o /dev/null -s -w "%{http_code}\n" -d "" -X POST $acc_clean_func)
[ "$func_code" != "200" ] && echo "Host XRP account cleanup failed. code:$func_code"
[ "$func_code" == "200" ] && echo "Cleaned up host XRP account."
fi
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_user MB_DATA_DIR=$mb_xrpl_data MB_LOG=1 MB_DEREGISTER=1 node $mb_xrpl_bin
sudo -u $MB_XRPL_USER MB_DATA_DIR=$MB_XRPL_DATA node $MB_XRPL_BIN deregister
echo "Deleting message board user..."
killall -u $mb_user # Kill any running processes.
userdel -f "$mb_user"
rm -r /home/"${mb_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
prefix="sashi"
users=$(cut -d: -f1 /etc/passwd | grep "^$prefix" | sort)
users=$(cut -d: -f1 /etc/passwd | grep "^$SASHIUSER_PREFIX" | sort)
readarray -t userarr <<<"$users"
validusers=()
sashiusers=()
for user in "${userarr[@]}"; do
[ ${#user} -lt 24 ] || [ ${#user} -gt 32 ] || [[ ! "$user" =~ ^$prefix[0-9]+$ ]] && continue
validusers+=("$user")
[ ${#user} -lt 24 ] || [ ${#user} -gt 32 ] || [[ ! "$user" =~ ^$SASHIUSER_PREFIX[0-9]+$ ]] && continue
sashiusers+=("$user")
done
ucount=${#validusers[@]}
ucount=${#sashiusers[@]}
if [ $ucount -gt 0 ]; then
echo "Detected $ucount Sashimono contract instances."
for user in "${validusers[@]}"; do
echo "Detected $ucount contract instances."
for user in "${sashiusers[@]}"; do
echo "$user"
done
if [ "$quiet" != "-q" ]; then
echo "Are you sure you want to delete all $ucount Sashimono contract instances?"
read -p "Type $ucount to confirm deletion:" confirmation </dev/tty
else
confirmation="$ucount"
fi
if [ "$confirmation" == "$ucount" ]; then
echo "Deleting $ucount contract instances..."
for user in "${validusers[@]}"; do
output=$($sashimono_bin/user-uninstall.sh $user | tee /dev/stderr)
[ "${output: -10}" != "UNINST_SUC" ] && echo "Uninstall user '$user' failed. Aborting." && exit 1
done
else
echo "Uninstall cancelled."
exit 0
fi
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
echo "Removing Sashimono cgroup creation service..."
systemctl stop $cgcreate_service
systemctl disable $cgcreate_service
rm /etc/systemd/system/$cgcreate_service.service
systemctl stop $CGCREATE_SERVICE
systemctl disable $CGCREATE_SERVICE
rm /etc/systemd/system/$CGCREATE_SERVICE.service
echo "Removing Sashimono service..."
systemctl stop $sashimono_service
systemctl disable $sashimono_service
rm /etc/systemd/system/$sashimono_service.service
systemctl stop $SASHIMONO_SERVICE
systemctl disable $SASHIMONO_SERVICE
rm /etc/systemd/system/$SASHIMONO_SERVICE.service
# echo "Removing Sashimono private docker registry..."
# ./registry-uninstall.sh $docker_bin $registryuser
# ./registry-uninstall.sh $DOCKER_BIN $REGISTRY_USER
echo "Deleting binaries..."
rm -r $sashimono_bin
rm -r $SASHIMONO_BIN
echo "Deleting Sashimono CLI..."
rm $user_bin/sashi
rm $USER_BIN/sashi
echo "Deleting data folder..."
rm -r $sashimono_data
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 "/^@$group\s+cpu,memory\s+%u$cgroupsuffix/d" /etc/cgrules.conf
echo "Restarting the $cgrulesengd_service.service."
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 $group
groupdel $SASHIUSER_GROUP
groupdel $admin_group
groupdel $SASHIADMIN_GROUP
echo "Sashimono uninstalled successfully."
exit 0
exit 0

414
installer/setup.sh Executable file
View File

@@ -0,0 +1,414 @@
#!/bin/bash
# Evernode host setup tool to manage Sashimono installation and host registration.
# usage: ./setup.sh install
evernode="Evernode beta"
maxmind_creds="653000:0yB7wwsBqCiPO2m6"
cgrulesengd_default="cgrulesengd"
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/evernode/setup.sh"
installer="https://sthotpocket.blob.core.windows.net/evernode/sashimono-installer.tar.gz"
# export vars used by Sashimono installer.
export USER_BIN=/usr/bin
export SASHIMONO_BIN=/usr/bin/sashimono
export MB_XRPL_BIN=$SASHIMONO_BIN/mb-xrpl
export DOCKER_BIN=$SASHIMONO_BIN/dockerbin
export SASHIMONO_DATA=/etc/sashimono
export MB_XRPL_DATA=$SASHIMONO_DATA/mb-xrpl
export SASHIMONO_SERVICE="sashimono-agent"
export CGCREATE_SERVICE="sashimono-cgcreate"
export MB_XRPL_SERVICE="sashimono-mb-xrpl"
export SASHIADMIN_GROUP="sashiadmin"
export SASHIUSER_GROUP="sashiuser"
export SASHIUSER_PREFIX="sashi"
export MB_XRPL_USER="sashimbxrpl"
export REGISTRY_USER="sashidockerreg"
export CG_SUFFIX="-cg"
export REGISTRY_PORT=4444
export HOOK_ADDRESS="rKt4W57Rmh8k9HNCYLc2AvFD9rAEkF5RKP"
[ -f $SASHIMONO_DATA/sa.cfg ] && 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)
function echomult() {
echo -e $1
}
# The set of commands supported differs based on whether Sashimono is installed or not.
if ! $sashimono_installed ; then
[ "$1" != "install" ] \
&& echomult "$evernode host management tool
\nYour system is not registered on $evernode.
\nSupported commands:
\ninstall - Install Sashimono and register on $evernode" \
&& exit 1
else
[ "$1" != "uninstall" ] && [ "$1" != "status" ] && [ "$1" != "list" ] \
&& 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
\nuninstall - Uninstall and deregister from $evernode" \
&& exit 1
fi
mode=$1
if [ "$mode" == "install" ] || [ "$mode" == "uninstall" ] ; 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
[ "$EUID" -ne 0 ] && echo "Please run with root privileges (sudo)." && exit 1
fi
function confirm() {
echo -en $1" [y/n] "
local yn=""
read yn </dev/tty
while ! [[ $yn =~ ^[Yy|Nn]$ ]]; do
read -p "'y' or 'n' expected: " yn </dev/tty
done
echo "" # Insert new line after answering.
[[ $yn =~ ^[Yy]$ ]] && return 0 || return 1 # 0 means success.
}
# Format the given KB number into GB units.
function GB() {
echo "$(bc <<<"scale=2; $1 / 1048576") GB"
}
function check_sys_req() {
# Assign sys resource info to global vars since these will also be used for instance allocation later.
ramKB=$(free | grep Mem | awk '{print $2}')
swapKB=$(free | grep Swap | awk '{print $2}')
diskKB=$(df | grep -w /home | head -1 | awk '{print $4}')
[ -z "$diskKB" ] && diskKB=$(df | grep -w / | head -1 | awk '{print $4}')
[ "$SKIP_SYSREQ" == "1" ] && echo "System requirements check skipped." && return 0
local proc1=$(ps --no-headers -o comm 1)
if [ "$proc1" != "systemd" ]; then
echo "$evernode host installation requires systemd. Your system does not have systemd running. Aborting."
exit 1
fi
local os=$(grep -ioP '^ID=\K.+' /etc/os-release)
local osversion=$(grep -ioP '^VERSION_ID=\K.+' /etc/os-release)
if [ "$os" != "ubuntu" ] || [ "$osversion" != '"20.04"' ] || [ $ramKB -lt 2000000 ] || [ $swapKB -lt 2000000 ] || [ $diskKB -lt 4194304 ]; then
echomult "Your system specs are:
\n OS: $os $osversion
\n RAM: $(GB $ramKB)
\n Swap: $(GB $swapKB)
\n Disk space (/home): $(GB $diskKB)
\n$evernode host registration requires Ubuntu 20.04 with 2 GB RAM, 2 GB Swap and 4 GB free disk space for /home.
\nYour system does not meet some of the requirements. Aborting."
exit 1
fi
echo "System check complete. Your system is capable of becoming an $evernode host."
}
function resolve_ip_addr() {
# Attempt to resolve ip (in case inetaddr is a DNS address)
# This will resolve correctly if inetaddr is a valid ip or dns address.
ipaddr=$(getent hosts $inetaddr | head -1 | awk '{ print $1 }')
# If invalid, reset inetaddr and return with non-zero code.
if [ -z "$ipaddr" ] ; then
inetaddr=""
return 1
fi
}
function set_inet_addr() {
# Attempt to auto-detect in interactive mode or if 'auto' is specified.
([ "$inetaddr" == "auto" ] || $interactive) && inetaddr=$(hostname -I | awk '{print $1}')
resolve_ip_addr
if $interactive ; then
if [ -n "$inetaddr" ] && ! confirm "Detected ip address '$inetaddr'. This will be used to reach contract instances running
on your host. Do you want to specify a different IP or DNS address?" ; then
return 0
fi
inetaddr=""
while [ -z "$inetaddr" ]; do
# This will be asked if auto-detection fails or if user wants to specify manually.
read -p "Please specify the IP or DNS address your server is reachable at: " inetaddr </dev/tty
resolve_ip_addr || echo "Invalid IP or DNS address."
done
else
[ -z "$inetaddr" ] && echo "Invalid IP or DNS address '$inetaddr'" && exit 1
fi
}
# Validate country code and convert to uppercase if valid.
function resolve_countrycode() {
# If invalid, reset countrycode and return with non-zero code.
if ! [[ $countrycode =~ ^[A-Za-z][A-Za-z]$ ]] ; then
countrycode=""
return 1
else
countrycode=$(echo $countrycode | tr 'a-z' 'A-Z')
return 0
fi
}
function set_country_code() {
# Attempt to auto-detect in interactive mode or if 'auto' is specified.
if [ "$countrycode" == "auto" ] || $interactive ; then
echo "Checking country code..."
echo "Using GeoLite2 data created by MaxMind, available from https://www.maxmind.com"
local detected=$(curl -s -u "$maxmind_creds" "https://geolite.info/geoip/v2.1/country/$ipaddr?pretty" | grep "iso_code" | head -1 | awk '{print $2}')
countrycode=${detected:1:2}
resolve_countrycode || echo "Could not detect country code."
fi
if $interactive ; then
# Uncomment this if we want the user to manually change the auto-detected country code.
# if [ -n "$countrycode" ] && ! confirm "Based on the internet address '$inetaddr' we have detected that your country
# code is '$countrycode'. Do you want to specify a different country code" ; then
# return 0
# fi
# countrycode=""
while [ -z "$countrycode" ]; do
# This will be asked if auto-detection fails or if user wants to specify manually.
read -p "Please specify the two-letter country code where your server is located in (eg. AU): " countrycode </dev/tty
resolve_countrycode || echo "Invalid country code."
done
else
resolve_countrycode || (echo "Invalid country code '$countrycode'" && exit 1)
fi
}
function set_cgrules_svc() {
local filename=$(basename $(grep "ExecStart.*=.*/cgrulesengd$" /etc/systemd/system/*.service | head -1 | awk -F : ' { print $1 } '))
cgrulesengd_service="${filename%.*}"
# If service not detected, use the default name.
[ -z "$cgrulesengd_service" ] && cgrulesengd_service=$cgrulesengd_default || echo "cgroups rules engine service found: '$cgrulesengd_service'"
}
function set_instance_alloc() {
[ -z $alloc_ramKB ] && alloc_ramKB=$(( (ramKB / 100) * alloc_ratio ))
[ -z $alloc_swapKB ] && alloc_swapKB=$(( (swapKB / 100) * alloc_ratio ))
[ -z $alloc_diskKB ] && alloc_diskKB=$(( (diskKB / 100) * alloc_ratio ))
[ -z $alloc_cpu ] && alloc_cpu=$(( (1000000 / 100) * alloc_ratio ))
# We decide instance count based on total memory (ram+swap)
[ -z $alloc_instcount ] && alloc_instcount=$(( (alloc_ramKB + alloc_swapKB) / memKB_per_instance ))
if $interactive; then
echomult "Based on your system resources, we have chosen the following allocation:\n
$(GB $alloc_ramKB) RAM\n
$(GB $alloc_swapKB) Swap\n
$(GB $alloc_diskKB) disk space\n
Distributed among $alloc_instcount contract instances"
! confirm "Do you wish to change this allocation?" && return 0
local ramMB=0 swapMB=0 diskMB=0
while true ; do
read -p "Specify the number of contract instances that you wish to host: " alloc_instcount </dev/tty
! [[ $alloc_instcount -gt 0 ]] && echo "Invalid instance count." || break
done
while true ; do
read -p "Specify the total RAM in megabytes to distribute among all contract instances: " ramMB </dev/tty
! [[ $ramMB -gt 0 ]] && echo "Invalid amount." || break
done
while true ; do
read -p "Specify the total Swap in megabytes to distribute among all contract instances: " swapMB </dev/tty
! [[ $swapMB -gt 0 ]] && echo "Invalid amount." || break
done
while true ; do
read -p "Specify the total disk space in megabytes to distribute among all contract instances: " diskMB </dev/tty
! [[ $diskMB -gt 0 ]] && echo "Invalid amount." || break
done
alloc_ramKB=$(( ramMB * 1024 ))
alloc_swapKB=$(( swapMB * 1024 ))
alloc_diskKB=$(( diskMB * 1024 ))
fi
if ! [[ $alloc_ramKB -gt 0 ]] || ! [[ $alloc_swapKB -gt 0 ]] || ! [[ $alloc_diskKB -gt 0 ]] ||
! [[ $alloc_cpu -gt 0 ]] || ! [[ $alloc_instcount -gt 0 ]]; then
echo "Invalid allocation." && exit 1
fi
}
function install_failure() {
echo "There was an error during installation. Please provide the file $logfile to Evernode team. Thank you."
exit 1
}
function uninstall_failure() {
echo "There was an error during uninstallation."
exit 1
}
function install_sashimono() {
echo "Starting Sashimono installation..."
local tmp=$(mktemp -d)
cd $tmp
curl -s $installer --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
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

View File

@@ -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);

View File

@@ -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",

View File

@@ -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": {

View File

@@ -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<std::string>();
}
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);
}

View File

@@ -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<std::string, std::string> 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);

View File

@@ -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.

View File

@@ -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];

View File

@@ -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);

View File

@@ -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."

View File

@@ -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;
}

View File

@@ -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",

View File

@@ -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"
}
}