Install Script

Script to perform an all-in-one installation, configuration and startup of FIO

The following script encapsulates the workflow and commands from FIO Package Install and FIO Nodeos Replay. It is offered as a means to quickly build and run a node. As such, details on how to connect, and register as a FIO Chain node are not provided. In addition assumptions have been made as to the local environment as well as FIO package URLs, package versioning and authenticity, etc., which may have to be verified and/or updated for the following script to function properly. Please refer to the aforementioned pages for detailed information.

🚧

Destructive

The following script must be run as root/using sudo. Note that the use of 'sudo' is for Advanced users!

#!/usr/bin/env bash

echo
if [[ "$EUID" -ne 0 ]]; then
  echo "ERROR: Script must be run as root! Use sudo command as follows; sudo ./<script name>"
  echo
  exit 1
fi

# Utility functions
function pause() {
  echo
  read -s -n 1 -p "Press any key to continue (CTRL-c to exit)..."
  echo
  echo
}

function getAndExtract() {
  uri=${1}
  rsrc=${2}

  exitStatus=0

  echo
  echo "Cleaning any previous archive download..."
  rm -f ${rsrc}
  echo
  echo "Cleaning target directory '/var/lib/fio'..."
  rm -rf /var/lib/fio/data /var/lib/fio/history /var/lib/fio/history_index
  echo
  pause
  echo -n "Downloading archive..."
  wget -bq --tries 1 --timeout=10 ${uri}/${rsrc}
  echo
  max=3600
  for ((i=1; i<=max; i++)); do
    echo -n '.'
    sleep 1s
    if [[ $(pgrep -f latest) -eq 0 ]]; then
      break
    fi
  done
  echo
  echo
  if [[ ! -e ${rsrc} ]]; then
    exitStatus=99
    echo "ERROR: Something went wrong downloading archive ${rsrc} from ${uri}!"
    pause
  else
    echo Download complete!
    echo
    echo "Exracting archives to target directory (silently)..."
    tar -xS -I'pixz' -C /var/lib/fio -f ${rsrc}
    if [[ $? -ne 0 ]]; then
      exitStatus=98
      echo
      echo "ERROR: Something went wrong extracting contents of ${rsrc}! Inspect archive and fix any errors."
      pause
    else
      echo
      echo "Archive successfully extracted to '/var/lib/fio'"
      mkdir -p /var/lib/fio/history /var/lib/fio/history_index
      chown -R fio:fio /var/lib/fio
      find /var/lib/fio -type d -exec chmod 0755 {} \;

      echo
      read -N 1 -p "Remove downloaded archive (y/N)? " answer
      if [[ "${answer,,}" == "y" ]]; then
        rm -f ${rsrc}
      fi
    fi
  fi

  return ${exitStatus}
}

function do_net() {
  echo
  if [[ -n "${type}" ]]; then
    read -N 1 -p "${type^^} selected; Change (y/N)? " answer
    echo
    if [[ "${answer,,}" != "y" ]]; then
      return
    fi
    echo
  fi

  local PS3="Select *Net: "

  local items=("MainNet" "TestNet" )

  while true; do
    select item in "${items[@]}" "Return to Main Menu"
    do
      echo
      echo -n "You've chosen "
      case $REPLY in
        1) echo "$item"; type="mainnet"; break 2;;
        2) echo "$item"; type="testnet"; break 2;;
        $((${#items[@]}+1))) echo "Return to Main Menu!"; echo; return 1;;
        *) echo "Ooops - unknown choice $REPLY"; echo; break;;
      esac
    done
  done
  return 0
}

function do_configure() {
  echo
  read -N 1 -p "Edit the FIO Nodeos configuration (y/N)? " answer
  if [[ "${answer,,}" == "y" ]]; then
    vi /etc/fio/nodeos/config.ini
  fi
  echo && echo
  read -N 1 -p "Edit the FIO Nodeos exec, e.g. add '--disable-replay-opts' for state history (y/N)? " answer
  if [[ "${answer,,}" == "y" ]]; then
    vi /usr/local/bin/fio-nodeos-run
  fi
  echo && echo
}

function do_configHelp() {
  local type="${1}"

  echo
  echo "Review configuration settings for your install"
  echo "- producer-name"
  echo "- endpoints and ports"
  echo "- history settings"
  echo
  if [[ ${type} == "v1" || ${type} == "all" ]]; then
    # Add/Uncomment the following configuration in the nodeos config.ini for history processing
    echo
    echo "Add/uncomment the following configuration in the fio-nodeos config.ini located"
    echo "in /etc/fio/nodeos"
    echo
    echo plugin = eosio::history_plugin
    echo plugin = eosio::history_api_plugin
    echo filter-on = *
    echo filter-out = eosio:onblock:
    echo history-per-account = 9223372036854775807
    echo history-index-state-db-size-mb = 1000000
    echo history-state-db-size-mb = 4000000
  fi
  if [[ ${type} == "state" || ${type} == "all" ]]; then
    echo
    echo "Add/uncomment the following configuration in the fio-nodeos config.ini located"
    echo "in /etc/fio/nodeos"
    echo
    echo plugin = eosio::state_history_plugin
    echo state-history-dir = state-history
    echo trace-history = true
    echo chain-state-history = true
    echo state-history-endpoint = 0.0.0.0:8080
  fi
}

function do_replayHelp() {
  echo
  echo Installation of FIO Blockchain data will include:
  echo "  Removal of any existing data located in '/var/lib/fio'"
  echo "  Download and installation of the latest archive to '/var/lib/fio'"
  echo "  Update of ownership to be accessible by the fio-nodeos process"
  echo "  Removal of the previously downloaded archive."
  echo
}

function do_installuninstall() {
  echo
  local PS3="Select Action: "

  local items=("Install FIO" "Uninstall FIO" )

  select item in "${items[@]}" "Return to Main Menu"
  do
    echo
    echo -n "You've chosen to "
    case $REPLY in
      1) echo "$item"; do_install; break;;
      2) echo "$item"; do_uninstall; break;;
      $((${#items[@]}+1))) echo "Return to Main Menu!"; echo; break 2;;
      *) echo "Ooops - unknown choice $REPLY"; echo; break;;
    esac
  done
}

function do_uninstall() {
  echo
  echo "Confirm complete removal of the fioprotocol package and all related artifacts..."
  pause

  do_stop true

  # Remove installed package
  echo
  echo "Removing fioprotocol package, including binaries, from system..."
  pause
  apt remove fioprotocol

  # Remove leftover artifacts from fio package install
  echo
  echo "Removing fioprotocol binaries from system..."
  rm -f /usr/local/bin/cleos
  rm -f /usr/local/bin/fio-cleos
  rm -f /usr/local/bin/clio
  rm -f /usr/local/bin/nodeos
  rm -f /usr/local/bin/fio-nodeos
  rm -f /usr/local/bin/fio-wallet

  # ********************************** WARNING ***********************************
  # The following commands remove runtime data that is critical for node execution
  # Only proceed if cleanup of an outdated install is desired
  # ******************************************************************************
  # Remove configuration, blocks log, history, state
  echo
  echo "Removing fioprotocol artifacts including configuration, blocks, state, and history, from system..."
  pause
  rm -rf /etc/fio
  rm -rf /var/lib/fio
  rm -rf /var/log/fio

  # Remove the fio user
  # This step is useful if not re-installing/upgrading the fio package
  echo
  echo "Removing fioprotocol user from system..."
  pause
  #deluser --system --remove-home --group fio fio
  deluser --force fio &>/dev/null
  echo
}

function auto_download() {
  # FIO package download examples
  # Release candidates and Release versions
  # https://github.com/fioprotocol/fio/releases/download/v3.5.1/fioprotocol-3.5.1-rc1-ubuntu-20.04-amd64.deb
  # https://github.com/fioprotocol/fio/releases/download/v3.5.1/fioprotocol-3.5.1-ubuntu-20.04-amd64.deb

  # https://github.com/fioprotocol/fio/releases/download/v3.5.1/fioprotocol-3.5.1-rc1-ubuntu-22.04-amd64.deb
  # https://github.com/fioprotocol/fio/releases/download/v3.5.1/fioprotocol-3.5.1-ubuntu-22.04-amd64.deb

  # Note: Release Name only is used in download action
  # GitHub
  # release=3.5.0-rc1
  # release=3.5.1

  PACKAGE=""
  release=""
  echo
  echo "The release package version is required and must match a release package located at"
  echo " - https://github.com/fioprotocol/fio/releases"
  echo "Examples include:"
  echo " - 3.5.0-rc1"
  echo " - 3.5.1"
  echo
  while
    read -p "Enter the release package version: " answer
    if [[ "${answer,,}" != "" ]]; then
      release="${answer}"
      echo
      read -N 1 -p "You input '${release}'. Correct (y/N)? " answer
      if [[ "${answer,,}" != "y" ]]; then
        echo && echo
        echo Try again...
        echo
      else
        break
      fi
    else
      echo && echo
      echo "No release package name entered, try again..."
      echo
    fi
  do true; done

  # Release path options
  # GitHub
  path="fio/releases/download/v${release}"

  # Package filename
  filename="fioprotocol-${release}-ubuntu-${VERSION_ID}-amd64.deb"

  # Full Release download URL
  url="${fioReleaseUri}/${path}/${filename}"

  echo && echo
  echo "Install package will be downloaded from the FIO Releases repository located at ${fioReleaseUri}"
  pause

  # Clean any previous downloads
  rm -f $filename

  # Check that file is at uri/filename
  status=$( curl -L -o /dev/null --silent -Iw '%{http_code}' "${url}" )
  if [[ ${status} -ne 200 ]]; then
    echo
    echo "Curl exited with status: $status; File, ${filename}, was not found at URL ${url}!"
    echo
    return
  else
    echo "Downloading FIO installation package..."
    curl -L -sO "${url}"
  fi

  #TODO: Use checksum here!
  # Check a file was downloaded and is non-zero size
  minimumsize=13142400
  actualsize=$(wc -c <"$filename")
  if [[ -e ${filename} && ( $actualsize -le $minimumsize) ]]; then
    echo
    echo "The file size appears incorrect (minimum size: 13142400, actual size: $actualsize). Was the file download interrupted or exit with an error?"
    echo
    pause
  fi
  PACKAGE=${filename}
}

function manual_download() {
  PACKAGE=""
  echo
  echo "Official packages may be found here; https://github.com/fioprotocol/fio/releases"
  echo "Download the desired package now..."
  pause
  echo
  while
    read -p "Enter the package path (including file name): " answer
    if [[ "${answer,,}" != "" ]]; then
      filename="${answer}"
      echo
      read -N 1 -p "You input '${filename}'. Correct (y/N)? " answer
      if [[ "${answer,,}" != "y" ]]; then
        echo && echo
        echo Try again!
        echo
      else
        if [[ ! -r ${filename} ]]; then
          echo && echo
          echo "Invalid path specified, ${filename}. Re-enter path..."
          echo
        else
          break
        fi
      fi
    else
      echo && echo
      echo "No package path entered, try again!"
      echo
    fi
  do true; done
  PACKAGE=${filename}
}

function do_install() {
  echo
  echo "An uninstall may need to be performed, otherwise, artifacts from a previous install may not be removed as part of this install"
  echo
  read -N 1 -p "Return to previous menu (to uninstall) (y/N)? " answer
  if [[ "${answer,,}" == "y" ]]; then
    return
  fi

  do_net
  if [[ $? -ne 0 ]]; then
    return
  fi

  # Determine OS
  . /etc/os-release
  if [[ ${VERSION_ID} != '18.04' && ${VERSION_ID} != '20.04' ]]; then
    echo
    echo "Only Ubuntu 18.04 and 20.04 are supported at this time. Exiting..."
    echo && return
  fi

  echo
  echo "Checking FIO Package dependencies..."
  dpkg-query -W pixz &>/dev/null
  if [[ $? -ne 0 ]]; then
    echo
    echo "Package dependency check failed! pixz is not installed and must be before proceeding."
    echo "Install using the commands;"
    echo "  sudo apt install pixz"
    echo
    echo "Note that you may need to run 'sudo apt update && sudo apt upgrade -y' first"
    echo
    read -N 1 -p "Do you wish to install 'pixz' now (y/N)? " answer
    if [[ "${answer,,}" == "y" ]]; then
      echo && apt update && echo
      apt install pixz
    else
      echo & return
    fi
  fi
  if [[ ${VERSION_ID} == '20.04' ]]; then
    dpkg-query -W libicu60\* &>/dev/null
    if [[ $? -ne 0 ]]; then
      echo
      echo "Package dependency check failed; libicu60 is not installed!"
      echo
      echo "Get and install libicu60 by;"
        echo "  Original URL: wget http://archive.ubuntu.com/ubuntu/pool/main/i/icu/libicu60_60.2-3ubuntu3_amd64.deb"
      echo "  FIO S3 URL: wget https://fioprotocol.s3.us-east-1.amazonaws.com/build-tools/libicu60_60.2-3ubuntu3_amd64.deb"
        echo
      echo "  sudo apt install ./libicu60_60.2-3ubuntu3_amd64.deb"
      echo
      echo "Note that you may need to run 'sudo apt upgrade && sudo apt update -y' first"
        echo
      read -N 1 -p "Do you wish to install 'libicu60' now (y/N)? " answer
      if [[ "${answer,,}" == "y" ]]; then
        echo && apt update && echo
        wget https://fioprotocol.s3.us-east-1.amazonaws.com/build-tools/libicu60_60.2-3ubuntu3_amd64.deb
        apt install ./libicu60_60.2-3ubuntu3_amd64.deb
      else
        echo && return
      fi
    fi
  fi
  echo "Package dependency check complete."

  echo
  echo "Install packages may be automatically downloaded (release packages only) or manually downloaded"
  echo " - Automatic download:"
  echo "     The release version is required and must match a release found here; https://github.com/fioprotocol/fio/releases"
  echo
  echo " - Manual download:"
  echo "     Download the desired package and provide the full path, including file name, on the command line"
  echo
  PACKAGE=""
  local PS3="Select Action: "
  local items=("Auto Download" "Manual Download" )

  select item in "${items[@]}" "Exit Install"
  do
    echo
    echo -n "You've chosen "
    case $REPLY in
      1) echo "$item"; auto_download; break;;
      2) echo "$item"; manual_download; break;;
      $((${#items[@]}+1))) echo "Return to Main Menu"; echo; break 2;;
      *) echo "Ooops - unknown choice $REPLY"; break;
    esac
  done

  echo
  read -N 1 -p "Install package (y/N)? " answer
  echo
  if [[ "${answer,,}" != "y" ]]; then
    echo
    echo "You have elected to NOT install package, ${PACKAGE}."
    echo
    return
  fi

  echo
  echo "Installing FIO package..."
  echo
  apt install ${PACKAGE}
  echo
  if [[ "testnet" == "${type}" ]]; then
    echo
    echo "Updating fio-nodeos configuration for TestNet"
    cp /etc/fio/nodeos/testnet-config.ini /etc/fio/nodeos/config.ini
    rm /etc/fio/nodeos/genesis.json
    ln -s /etc/fio/nodeos/genesis-testnet.json /etc/fio/nodeos/genesis.json
    echo
    echo "Note: Before starting fio-nodeos, verify that the nodeos configuration is"
    echo "appropriate for a ${type} install. Parameters may still be set for MainNet"
    echo "or may not be set at all, e.g. producer-name."
    echo "See /etc/fio/nodeos/config.ini, /etc/fio/nodeos/genesis.json"
    echo
  fi
  find /var/lib/fio -type d -exec chmod 0755 {} \;

  echo
  echo "It may be necessary to update the FIO configuration to;"
  echo "- Add producer name"
  echo "- Check settings"
  echo
  echo "Select 'Configure FIO' or proceed with synchronization/replay"
  echo
}

function do_startstop() {
  local PS3="Select Action: "

  local items=("Start FIO" "Stop FIO" )

  select item in "${items[@]}" "Return to Main Menu"
  do
    echo
    echo -n "You've chosen to "
    case $REPLY in
      1) echo "$item"; do_start; break;;
      2) echo "$item"; do_stop; break;;
      $((${#items[@]}+1))) echo "Return to Main Menu"; echo; break 2;;
      *) echo "Ooops - unknown choice $REPLY"; break;
    esac
  done
}

function do_start() {
  # Start fio-nodeos
  echo
  echo "Starting the FIO nodeos application may require configuration updates. Skip the"
  echo "the following step if those have not yet been performed. The application may be"
  echo "started at a later time using the commands;"
  echo "  - sudo systemctl enable fio-nodeos"
  echo "  - sudo systemctl start fio-nodeos"
  echo
  echo "To stop the fio-nodeos application for any reason, run the command;"
  echo "  - sudo systemctl stop fio-nodeos"
  echo
  read -N 1 -p "Start service 'fio-nodeos' (y/N)? " answer
  if [[ "${answer,,}" == "y" ]]; then
    systemctl enable fio-nodeos
    systemctl start fio-nodeos
  fi

  echo
  echo
  echo "fio-wallet: The following step will start the fio-wallet service. Things to note are:"
  echo "  This service is not required to run fio-nodeos."
  echo "  This service is not needed unless background management of keys is required."
  echo "    If so, please note that this service is NOT an enterprise application and no"
  echo "    gaurantee is stated or implied!"
  echo "  The fio-wallet service may be started anytime or run on demand when necessary"
  echo "    either stand-alone or via the clio command."
  echo
  read -N 1 -p "Start service 'fio-wallet' (y/N)? " answer
  if [[ "${answer,,}" == "y" ]]; then
    systemctl enable fio-wallet
    systemctl start fio-wallet
  fi
  echo
  echo "Review '/var/log/fio/nodeos' for relevant blockchain logging."
  echo
  echo "FIO installation complete."
  echo
}

function do_stop() {
  local disable=${1:-false}

  while
    read -N 1 -p "Stop service 'fio-nodeos' (y/N)? " answer
    if [[ "${answer,,}" == "y" ]]; then
      # Stop and disable fio-nodeo application
      echo
      echo "Stopping fio-nodeos..."
      systemctl stop fio-nodeos
      if ${disable}; then
        systemctl -q disable fio-nodeos
      fi
    fi

    read -N 1 -p "Stop service 'fio-wallet' (y/N)? " answer
    if [[ "${answer,,}" == "y" ]]; then
      # Stop and disable fio-wallet application
      echo
      echo "Stopping fio-wallet..."
        systemctl -q stop fio-wallet
      if [[ ${disable} ]]; then
        systemctl -q disable fio-wallet
      fi
    fi
    break
  do true; done
}

function do_sync() {
  echo
  echo "Synchronizing from genesis means that your configured node will connect to the FIO P2P network,"
  echo "process blocks from the first block of the inception of the chain up to present time, then"
  echo "continue processing blocks as produced by the blockchain block producers."
  echo
  echo "To synchronize from genesis is only a matter of installing FIO, configuring FIO if needed,"
  echo "and starting the chain"
  echo
  echo "Note:"
  echo " - Synchronization from genesis will remove all block data"
  echo " - Configuration is usually only needed when processing history during synchronization"
  do_configHelp "all"
  do_configure
  echo "Cleaning target directory '/var/lib/fio'..."
  pause
  find /var/lib/fio -type f -exec rm -f {} \;
}

# Install fio chain state, blocks.log and or history
function do_replay() {
  do_net
  if [[ $? -ne 0 ]]; then
    return
  fi

  echo
  echo "You have elected to replay blocks from archived blockchain data which may include"
  echo "state, a blocks.log and history. This will include download of an archive, extraction"
  echo "of the archive to '/var/lib/data' and configuration of fio-nodeos."
  echo
  echo "Note that if startup from genesis is desired (synchronization from the p2p network),"
  echo "the following steps are not needed other than possible configuration update."
  echo

  local PS3="Select Action: "
  local items=("Replay from snapshot" "Replay from blocks.log" "Replay with V1 History" "Replay with State History" )
  while true; do
    select item in "${items[@]}" "Return to Main Menu"
    do
      echo
      echo -n "You've chosen to "
      case $REPLY in
        1) echo "$item"; do_snap; break;;
        2) echo "$item"; do_blocks; break;;
        3) echo "$item"; do_v1history; break;;
        4) echo "$item"; do_statehistory; break;;
        $((${#items[@]}+1))) echo "Return to Main Menu!"; echo; break 2;;
        *) echo "Ooops - unknown choice $REPLY"; break;
      esac
    done
  done
}

# Snapshot archive
function do_snap() {
  do_replayHelp
  pause
  echo
  echo "Downloading FIO snapshot archive. Note that a snapshot download should take 1-2 minutes..."
  getAndExtract ${fioArchiveUri} ${type}-latest-snap.txz
  if [[ $? -eq 0 ]]; then
    stateInstalled=true
    do_configHelp "snapshot"
  fi
}

# blocks.log and snapshot
function do_blocks() {
  do_replayHelp
  pause
  echo
  echo "Downloading FIO blocks.log archive. Note that a blocks.log download should take ~40 minutes..."
  getAndExtract ${fioArchiveUri} ${type}-latest-blocks.txz
  if [[ $? -eq 0 ]]; then
    stateInstalled=true
    do_configHelp "blocks"
  fi
}

# v1 history, blocks.log, snapshot
function do_v1history() {
  do_replayHelp
  pause
  echo
  echo "Downloading FIO History archive. Note that a history download should take 50-60 minutes..."
  getAndExtract ${fioArchiveUri} ${type}-latest-history.txz
  if [[ $? -eq 0 ]]; then
    stateInstalled=true
    do_configHelp "v1"
  fi
}

# state history; blocks.log only
# grep ":state_history_plugin" /etc/fio/nodeos/config.ini | egrep "^[[:space:]]*#" &>/dev/null
# if [[ $? -ne 0 ]]; then
#   sed -i '/exec/s/$/ --disable-replay-opts/' /usr/local/bin/fio-nodeos-run
# fi
function do_statehistory() {
  do_replayHelp
  pause
  echo
  echo "Downloading FIO blocks.log archive. Note that a blocks.log download (needed to replay state history) shouild take ~40 minutes..."
  getAndExtract ${fioArchiveUri} ${type}-latest-blocks.txz
  if [[ $? -eq 0 ]]; then
    stateInstalled=true
    rm -f /var/lib/fio/data/blocks/blocks.index
    rm -f /var/lib/fio/data/blocks/reversible/shared_memory.bin
    do_configHelp "state"
  fi
}

# Constants, Variables

# FIO Releases
#fioReleaseUri="https://bin.fioprotocol.io" # S3 Bucket
fioReleaseUri="https://github.com/fioprotocol" # GitHub Releases

# FIO Snapshot, blocks.log, history archive URI
fioArchiveUri="https://snap.blockpane.com"

# TestNet/MainNet deployment type
type=""

# Booleans supporting install workflow
stateInstalled=false

# Main script functionality
PS3="Select Action: "

#items=("Install/Uninstall FIO" "Start/Stop FIO" "Install State" "Install Blocks.log" "Install V1 History" "Install State History" "Edit Config" )
items=("Install/Uninstall FIO" "Start/Stop FIO" "Sync From Genesis" "Replay From Archive" "Configure FIO" )

COLUMNS=1
while true; do
  select item in "${items[@]}" Quit
  do
    echo
    echo -n "You've chosen to "
    case $REPLY in
      1) echo "$item"; do_installuninstall; break;;
      2) echo "$item"; do_startstop; break;;
      3) echo "$item"; do_sync; break;;
      4) echo "$item"; do_replay; break;;
      5) echo "$item"; do_configure; break;;
      $((${#items[@]}+1))) echo "Exit!"; echo; break 2;;
      *) echo "Ooops - unknown choice $REPLY"; echo; break;
    esac
  done
  echo && echo "$item complete." && echo
done

What’s Next