#!/bin/bash

# Copyright 2014,2015,2016,2017,2018,2019,2020,2021 Security Onion Solutions, LLC

#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.

. /usr/sbin/so-common

UPDATE_DIR=/tmp/sogh/securityonion
INSTALLEDVERSION=$(cat /etc/soversion)
POSTVERSION=$INSTALLEDVERSION
INSTALLEDSALTVERSION=$(salt --versions-report | grep Salt: | awk '{print $2}')
BATCHSIZE=5
SOUP_LOG=/root/soup.log
INFLUXDB_MIGRATION_LOG=/opt/so/log/influxdb/soup_migration.log
WHATWOULDYOUSAYYAHDOHERE=soup
whiptail_title='Security Onion UPdater'

check_err() {
  local exit_code=$1
  local err_msg="Unhandled error occured, please check $SOUP_LOG for details."

  [[ $ERR_HANDLED == true ]] && exit $exit_code
  if [[ $exit_code -ne 0 ]]; then
    printf '%s' "Soup failed with error $exit_code: "
    case $exit_code in
      2)
        echo 'No such file or directory'
      ;;
      5)
        echo 'Interrupted system call'
      ;;
      12)
        echo 'Out of memory'
      ;;
      28)
        echo 'No space left on device'
        echo 'Likely ran out of space on disk, please review hardware requirements for Security Onion: https://docs.securityonion.net/en/2.3/hardware.html'
      ;;
      30)
        echo 'Read-only file system'
      ;;
      35)
        echo 'Resource temporarily unavailable'
      ;;
      64)
        echo 'Machine is not on the network'
      ;;
      67)
        echo 'Link has been severed'
      ;;
      100)
        echo 'Network is down'
      ;;
      101)
        echo 'Network is unreachable'
      ;;
      102)
        echo 'Network reset'
      ;;
      110)
        echo 'Connection timed out'
      ;;
      111)
        echo 'Connection refused'
      ;;
      112)
        echo 'Host is down'
      ;;
      113)
        echo 'No route to host'
      ;;
      *)
        echo 'Unhandled error'
        echo "$err_msg"
      ;;
    esac
    if [[ $exit_code -ge 64 && $exit_code -le 113 ]]; then
      echo "$err_msg"
    fi
    exit $exit_code
  fi

}

add_common() {
  cp $UPDATE_DIR/salt/common/tools/sbin/so-common $DEFAULT_SALT_DIR/salt/common/tools/sbin/
  cp $UPDATE_DIR/salt/common/tools/sbin/so-image-common $DEFAULT_SALT_DIR/salt/common/tools/sbin/
  salt-call state.apply common queue=True
  echo "Run soup one more time"
  exit 0
}

airgap_mounted() {
  # Let's see if the ISO is already mounted. 
  if [ -f /tmp/soagupdate/SecurityOnion/VERSION ]; then
    echo "The ISO is already mounted"
  else
    echo ""
    echo "Looks like we need access to the upgrade content"
    echo "" 
    echo "If you just copied the .iso file over you can specify the path."
    echo "If you burned the ISO to a disk the standard way you can specify the device."
    echo "Example: /home/user/securityonion-2.X.0.iso"
    echo "Example: /dev/sdx1"
    echo ""
    read -rp 'Enter the location of the iso: ' ISOLOC
    if [[ -f $ISOLOC ]]; then
      # Mounting the ISO image
      mkdir -p /tmp/soagupdate
      mount -t iso9660 -o loop $ISOLOC /tmp/soagupdate
      # Make sure mounting was successful
      if [ ! -f /tmp/soagupdate/SecurityOnion/VERSION ]; then
        echo "Something went wrong trying to mount the ISO."
        echo "Ensure you verify the ISO that you downloaded."
        exit 0
      else
        echo "ISO has been mounted!"
      fi  
    elif [[ -f $ISOLOC/SecurityOnion/VERSION ]]; then
      ln -s $ISOLOC /tmp/soagupdate
      echo "Found the update content"
    else 
      mkdir -p /tmp/soagupdate
      mount $ISOLOC /tmp/soagupdate
      if [ ! -f /tmp/soagupdate/SecurityOnion/VERSION ]; then
        echo "Something went wrong trying to mount the device."
        echo "Ensure you verify the ISO that you downloaded."
        exit 0
      else
        echo "Device has been mounted!"
      fi        
    fi
  fi
}

airgap_update_dockers() {
  if [[ $is_airgap -eq 0 ]]; then
    # Let's copy the tarball
    if [[ ! -f $AGDOCKER/registry.tar ]]; then
      echo "Unable to locate registry. Exiting"
      exit 1
    else
      echo "Stopping the registry docker"
      docker stop so-dockerregistry
      docker rm so-dockerregistry
      echo "Copying the new dockers over"
      tar xvf "$AGDOCKER/registry.tar" -C /nsm/docker-registry/docker
      echo "Add Registry back"
      docker load -i "$AGDOCKER/registry_image.tar"
    fi
  fi
}

update_registry() {
  docker stop so-dockerregistry
  docker rm so-dockerregistry
  salt-call state.apply registry queue=True
}

check_sudoers() {
  if grep -q "so-setup" /etc/sudoers; then
    echo "There is an entry for so-setup in the sudoers file, this can be safely deleted using \"visudo\"."
  fi
}

check_log_size_limit() {

  local num_minion_pillars
  num_minion_pillars=$(find /opt/so/saltstack/local/pillar/minions/ -type f | wc -l)
  
  if [[ $num_minion_pillars -gt 1 ]]; then
    if find /opt/so/saltstack/local/pillar/minions/ -type f | grep -q "_heavynode"; then
      lsl_msg='distributed'
    fi
  else
    local minion_id
    minion_id=$(lookup_salt_value "id" "" "grains")

    local minion_arr
    IFS='_' read -ra minion_arr <<< "$minion_id"

    local node_type="${minion_arr[0]}"
    
    local current_limit
    current_limit=$(lookup_pillar "log_size_limit" "elasticsearch")
    
    local percent
    case $node_type in
      'standalone' | 'eval')
        percent=50
      ;;
      *)
        percent=80
      ;;
    esac

    local disk_dir="/"
    if [ -d /nsm ]; then
      disk_dir="/nsm"
    fi

    local disk_size_1k
    disk_size_1k=$(df $disk_dir | grep -v "^Filesystem" | awk '{print $2}')

    local ratio="1048576"

    local disk_size_gb
    disk_size_gb=$( echo "$disk_size_1k" "$ratio" | awk '{print($1/$2)}' )

    local new_limit
    new_limit=$( echo "$disk_size_gb" "$percent" | awk '{printf("%.0f", $1 * ($2/100))}')

    if [[ $current_limit != "$new_limit" ]]; then
      lsl_msg='single-node'
      lsl_details=( "$current_limit" "$new_limit" "$minion_id" )
    fi
  fi
}

check_os_updates() {
  # Check to see if there are OS updates
  NEEDUPDATES="We have detected missing operating system (OS) updates. Do you want to install these OS updates now? This could take a while depending on the size of your grid and how many packages are missing, but it is recommended to keep your system updated."
  if [[ $OS == 'ubuntu' ]]; then
    OSUPDATES=$(apt list --upgradeable | grep -v "^Listing..." | grep -v "^docker-ce" | grep -v "^wazuh-" | grep -v "^salt-" | wc -l)
  else
    OSUPDATES=$(yum -q list updates | wc -l)
  fi
  if [[ "$OSUPDATES" -gt 0 ]]; then
      echo $NEEDUPDATES
      echo ""
      read -p "Press U to update OS packages (recommended), C to continue without updates, or E to exit: " confirm

      if [[ "$confirm" == [cC] ]]; then
          echo "Continuing without updating packages"
      elif [[ "$confirm" == [uU] ]]; then
          echo "Applying Grid Updates"
          set +e
          run_check_net_err "salt '*' -b 5 state.apply patch.os queue=True" 'Could not apply OS updates, please check your network connection.'
          set -e
      else
          echo "Exiting soup"
          exit 0
      fi
  else
      echo "Looks like you have an updated OS"
  fi
    
}

clean_dockers() {
  # Place Holder for cleaning up old docker images
  echo "Trying to clean up old dockers."
  docker system prune -a -f

}

clone_to_tmp() {
  # Clean old files
  rm -rf /tmp/sogh
  # Make a temp location for the files
  mkdir -p /tmp/sogh
  cd /tmp/sogh
  SOUP_BRANCH=""
  if [ -n "$BRANCH" ]; then
    SOUP_BRANCH="-b $BRANCH"
  fi
  set +e
  run_check_net_err "git clone $SOUP_BRANCH https://github.com/Security-Onion-Solutions/securityonion.git" "Could not clone repo, please ensure network access to https://github.com"
  set -e
  cd /tmp
  if [ ! -f $UPDATE_DIR/VERSION ]; then
    echo "Update was unable to pull from github. Please check your internet."
    exit 0
  fi
}

generate_and_clean_tarballs() {
  local new_version
  new_version=$(cat $UPDATE_DIR/VERSION)
  [ -d /opt/so/repo ] || mkdir -p /opt/so/repo
  tar -czf "/opt/so/repo/$new_version.tar.gz" "$UPDATE_DIR"
  find "/opt/so/repo" -type f -not -name "$new_version.tar.gz" -exec rm -rf {} \;
}

highstate() {
  # Run a highstate.
  salt-call state.highstate -l info queue=True
}

masterlock() {
  echo "Locking Salt Master"
  if [[ "$INSTALLEDVERSION" =~ rc.1 ]]; then
    TOPFILE=/opt/so/saltstack/default/salt/top.sls
    BACKUPTOPFILE=/opt/so/saltstack/default/salt/top.sls.backup
    mv -v $TOPFILE $BACKUPTOPFILE
    echo "base:" > $TOPFILE
    echo "  $MINIONID:" >> $TOPFILE
    echo "    - ca" >> $TOPFILE
    echo "    - ssl" >> $TOPFILE
    echo "    - elasticsearch" >> $TOPFILE
  fi
}

masterunlock() {
  echo "Unlocking Salt Master"
  if [[ "$INSTALLEDVERSION" =~ rc.1 ]]; then
    mv -v $BACKUPTOPFILE $TOPFILE
  fi
}

preupgrade_changes_2.3.50_repo() {
    # We made repo changes in 2.3.50 and this prepares for that on upgrade
    echo "Checking to see if 2.3.50 repo changes are needed."

    [[ "$INSTALLEDVERSION" == 2.3.30 || "$INSTALLEDVERSION" == 2.3.40 ]] && up_2.3.3X_to_2.3.50_repo
    true
}

preupgrade_changes() {
    # This function is to add any new pillar items if needed.
    echo "Checking to see if changes are needed."

    [[ "$INSTALLEDVERSION" =~ rc.1 ]] && rc1_to_rc2
    [[ "$INSTALLEDVERSION" =~ rc.2 ]] && rc2_to_rc3
    [[ "$INSTALLEDVERSION" =~ rc.3 ]] && rc3_to_2.3.0
    [[ "$INSTALLEDVERSION" == 2.3.0 || "$INSTALLEDVERSION" == 2.3.1 || "$INSTALLEDVERSION" == 2.3.2 || "$INSTALLEDVERSION" == 2.3.10 ]] && up_2.3.0_to_2.3.20
    [[ "$INSTALLEDVERSION" == 2.3.20 || "$INSTALLEDVERSION" == 2.3.21 ]] && up_2.3.2X_to_2.3.30
    [[ "$INSTALLEDVERSION" == 2.3.30 || "$INSTALLEDVERSION" == 2.3.40 ]] && up_2.3.3X_to_2.3.50
    true
}

postupgrade_changes() {
    # This function is to add any new pillar items if needed.
    echo "Running post upgrade processes."
    
    [[ "$POSTVERSION" =~ rc.1 ]] && post_rc1_to_rc2
    [[ "$POSTVERSION" == 2.3.20 || "$POSTVERSION" == 2.3.21 ]] && post_2.3.2X_to_2.3.30
    [[ "$POSTVERSION" == 2.3.30 ]] && post_2.3.30_to_2.3.40
    [[ "$POSTVERSION" == 2.3.50 ]] && post_2.3.5X_to_2.3.60
    true
}

post_rc1_to_2.3.21() {
  salt-call state.apply playbook.OLD_db_init
  rm -f /opt/so/rules/elastalert/playbook/*.yaml
  so-playbook-ruleupdate >> /root/soup_playbook_rule_update.log 2>&1 &
  POSTVERSION=2.3.21
}

post_2.3.2X_to_2.3.30() {
  so-playbook-sigma-refresh >> /root/soup_playbook_sigma_refresh.log 2>&1 &
  POSTVERSION=2.3.30
}

post_2.3.30_to_2.3.40() {
  so-playbook-sigma-refresh >> /root/soup_playbook_sigma_refresh.log 2>&1 &
  so-kibana-space-defaults
  POSTVERSION=2.3.40
}

post_2.3.5X_to_2.3.60() {
  POSTVERSION=2.3.60
}


rc1_to_rc2() {

  # Move the static file to global.sls
  echo "Migrating static.sls to global.sls"
  mv -v /opt/so/saltstack/local/pillar/static.sls /opt/so/saltstack/local/pillar/global.sls >> "$SOUP_LOG" 2>&1
  sed -i '1c\global:' /opt/so/saltstack/local/pillar/global.sls >> "$SOUP_LOG" 2>&1

  # Moving baseurl from minion sls file to inside global.sls
  local line=$(grep '^  url_base:' /opt/so/saltstack/local/pillar/minions/$MINIONID.sls)
  sed -i '/^  url_base:/d' /opt/so/saltstack/local/pillar/minions/$MINIONID.sls;
  sed -i "/^global:/a \\$line" /opt/so/saltstack/local/pillar/global.sls;

  # Adding play values to the global.sls
  local HIVEPLAYSECRET=$(get_random_value)
  local CORTEXPLAYSECRET=$(get_random_value)
  sed -i "/^global:/a \\  hiveplaysecret: $HIVEPLAYSECRET" /opt/so/saltstack/local/pillar/global.sls;
  sed -i "/^global:/a \\  cortexplaysecret: $CORTEXPLAYSECRET" /opt/so/saltstack/local/pillar/global.sls;

  # Move storage nodes to hostname for SSL
  # Get a list we can use:
  grep -A1 searchnode /opt/so/saltstack/local/pillar/data/nodestab.sls | grep -v '\-\-' | sed '$!N;s/\n/ /' | awk '{print $1,$3}' | awk '/_searchnode:/{gsub(/\_searchnode:/, "_searchnode"); print}' >/tmp/nodes.txt
  # Remove the nodes from cluster settings
  while read p; do
  local NAME=$(echo $p | awk '{print $1}')
  local IP=$(echo $p | awk '{print $2}')
  echo "Removing the old cross cluster config for $NAME"
  curl -XPUT -H 'Content-Type: application/json' http://localhost:9200/_cluster/settings -d '{"persistent":{"cluster":{"remote":{"'$NAME'":{"skip_unavailable":null,"seeds":null}}}}}'
  done </tmp/nodes.txt
  # Add the nodes back using hostname
  while read p; do
    local NAME=$(echo $p | awk '{print $1}')
    local EHOSTNAME=$(echo $p | awk -F"_" '{print $1}')
    local IP=$(echo $p | awk '{print $2}')
    echo "Adding the new cross cluster config for $NAME"
    curl -XPUT http://localhost:9200/_cluster/settings -H'Content-Type: application/json' -d '{"persistent": {"search": {"remote": {"'$NAME'": {"skip_unavailable": "true", "seeds": ["'$EHOSTNAME':9300"]}}}}}'
  done </tmp/nodes.txt

  INSTALLEDVERSION=rc.2
}

rc2_to_rc3() {

  # move location of local.rules
  cp /opt/so/saltstack/default/salt/idstools/localrules/local.rules /opt/so/saltstack/local/salt/idstools/local.rules
  
  if [ -f /opt/so/saltstack/local/salt/idstools/localrules/local.rules ]; then
    cat /opt/so/saltstack/local/salt/idstools/localrules/local.rules >> /opt/so/saltstack/local/salt/idstools/local.rules
  fi
  rm -rf /opt/so/saltstack/local/salt/idstools/localrules
  rm -rf /opt/so/saltstack/default/salt/idstools/localrules

  # Rename mdengine to MDENGINE
  sed -i "s/  zeekversion/  mdengine/g" /opt/so/saltstack/local/pillar/global.sls
  # Enable Strelka Rules
  sed -i "/  rules:/c\  rules: 1" /opt/so/saltstack/local/pillar/global.sls

  INSTALLEDVERSION=rc.3

}

rc3_to_2.3.0() {
  # Fix Tab Complete
  if [ ! -f /etc/profile.d/securityonion.sh ]; then
    echo "complete -cf sudo" > /etc/profile.d/securityonion.sh
  fi

  {
    echo "redis_settings:"
    echo "  redis_maxmemory: 827"
    echo "playbook:"
    echo "  api_key: de6639318502476f2fa5aa06f43f51fb389a3d7f" 
  } >> /opt/so/saltstack/local/pillar/global.sls

  sed -i 's/playbook:/playbook_db:/' /opt/so/saltstack/local/pillar/secrets.sls
  {
    echo "playbook_admin: $(get_random_value)"
    echo "playbook_automation: $(get_random_value)"
  } >> /opt/so/saltstack/local/pillar/secrets.sls

  INSTALLEDVERSION=2.3.0
}

up_2.3.0_to_2.3.20(){
  DOCKERSTUFFBIP=$(echo $DOCKERSTUFF | awk -F'.' '{print $1,$2,$3,1}' OFS='.')/24
  # Remove PCAP from global
  sed '/pcap:/d' /opt/so/saltstack/local/pillar/global.sls
  sed '/sensor_checkin_interval_ms:/d' /opt/so/saltstack/local/pillar/global.sls

  # Add checking interval to glbal
  echo "sensoroni:" >> /opt/so/saltstack/local/pillar/global.sls
  echo "  node_checkin_interval_ms: 10000" >> /opt/so/saltstack/local/pillar/global.sls

  # Update pillar fiels for new sensoroni functionality
  for file in /opt/so/saltstack/local/pillar/minions/*; do
    echo "sensoroni:" >> $file
    echo "  node_description:" >> $file
    local SOMEADDRESS=$(cat $file | grep mainip | tail -n 1 | awk '{print $2'})
    echo "  node_address: $SOMEADDRESS" >> $file
  done

  # Remove old firewall config to reduce confusion
  rm -f /opt/so/saltstack/default/pillar/firewall/ports.sls

  # Fix daemon.json by managing it
  echo "docker:" >> /opt/so/saltstack/local/pillar/global.sls
  DOCKERGREP=$(cat /etc/docker/daemon.json | grep base | awk {'print $3'} | cut -f1 -d"," | tr -d '"')
  if [ -z "$DOCKERGREP" ]; then
    echo "  range: '172.17.0.0/24'" >> /opt/so/saltstack/local/pillar/global.sls
    echo "  bip: '172.17.0.1/24'" >> /opt/so/saltstack/local/pillar/global.sls
  else
    DOCKERSTUFF="${DOCKERGREP//\"}"
    DOCKERSTUFFBIP=$(echo $DOCKERSTUFF | awk -F'.' '{print $1,$2,$3,1}' OFS='.')/24
    echo "  range: '$DOCKERSTUFF/24'" >> /opt/so/saltstack/local/pillar/global.sls
    echo "  bip: '$DOCKERSTUFFBIP'"  >> /opt/so/saltstack/local/pillar/global.sls

  fi

  INSTALLEDVERSION=2.3.20
}

up_2.3.2X_to_2.3.30() {
  # Replace any curly brace scalars with the same scalar in single quotes
  readarray -t minion_pillars <<< "$(find /opt/so/saltstack/local/pillar/minions -type f -name '*.sls')"
  for pillar in "${minion_pillars[@]}"; do
    sed -i -r "s/ (\{\{.*}})$/ '\1'/g" "$pillar"
  done

  # Change the IMAGEREPO
  sed -i "/  imagerepo: 'securityonion'/c\  imagerepo: 'security-onion-solutions'" /opt/so/saltstack/local/pillar/global.sls
  sed -i "/  imagerepo: securityonion/c\  imagerepo: 'security-onion-solutions'" /opt/so/saltstack/local/pillar/global.sls 

  # Strelka rule repo pillar addition
  if [[ $is_airgap -eq 0 ]]; then
      # Add manager as default Strelka YARA rule repo
      sed -i "/^strelka:/a \\  repos: \n    - https://$HOSTNAME/repo/rules/strelka" /opt/so/saltstack/local/pillar/global.sls;
  else
      # Add Github repo for Strelka YARA rules
      sed -i "/^strelka:/a \\  repos: \n    - https://github.com/Neo23x0/signature-base" /opt/so/saltstack/local/pillar/global.sls;
  fi
  check_log_size_limit
  INSTALLEDVERSION=2.3.30
}

up_2.3.3X_to_2.3.50_repo() {
  echo "Performing 2.3.50 repo actions."
  if [[ "$OS" == "centos" ]]; then
    # Import GPG Keys
    gpg_rpm_import
    echo "Disabling fastestmirror."
    disable_fastestmirror
    echo "Deleting unneeded repo files."
    DELREPOS=('CentOS-Base' 'CentOS-CR' 'CentOS-Debuginfo' 'docker-ce' 'CentOS-fasttrack' 'CentOS-Media' 'CentOS-Sources' 'CentOS-Vault' 'CentOS-x86_64-kernel' 'epel' 'epel-testing' 'saltstack' 'wazuh')

    for DELREPO in "${DELREPOS[@]}"; do
      if [[ -f "/etc/yum.repos.d/$DELREPO.repo" ]]; then
        echo "Deleting $DELREPO.repo"
        rm -f "/etc/yum.repos.d/$DELREPO.repo"
      fi
    done
    if [[ $is_airgap -eq 1 ]]; then
      # Copy the new repo file if not airgap
      cp $UPDATE_DIR/salt/repo/client/files/centos/securityonion.repo /etc/yum.repos.d/
      yum clean all
      yum repolist
    fi
  fi
}

up_2.3.3X_to_2.3.50() {
  
  cat <<EOF > /tmp/supersed.txt
/so-zeek:/ {
  p;
  n;
  /shards:/ {
    p;
    n;
    /warm:/ {
      p;
      n;
      /close:/ {
        s/close: 365/close: 45/;
        p;
        n;
        /delete:/ {
          s/delete: 45/delete: 365/;
          p;
          d;
        }
      }
    }
  }
}
p;
EOF
  sed -n -i -f /tmp/supersed.txt /opt/so/saltstack/local/pillar/global.sls
  rm /tmp/supersed.txt
  INSTALLEDVERSION=2.3.50
}

verify_upgradespace() {
  CURRENTSPACE=$(df -BG / | grep -v Avail | awk '{print $4}' | sed 's/.$//')
  if [ "$CURRENTSPACE" -lt "10" ]; then
      echo "You are low on disk space."
      return 1
  else
      return 0
  fi
}

upgrade_space() {
  if ! verify_upgradespace; then
    clean_dockers
    if ! verify_upgradespace; then
      echo "There is not enough space to perform the upgrade. Please free up space and try again"
      exit 1
    fi
  else
      echo "You have enough space for upgrade. Proceeding with soup."
  fi  
}

thehive_maint() {
  echo -n "Waiting for TheHive..."
  COUNT=0
  THEHIVE_CONNECTED="no"
  while [[ "$COUNT" -le 240 ]]; do
    curl --output /dev/null --silent --head --fail -k "https://localhost/thehive/api/alert"
      if [ $? -eq 0 ]; then
        THEHIVE_CONNECTED="yes"
        echo "connected!"
        break
      else
        ((COUNT+=1))
        sleep 1
        echo -n "."
      fi
  done
  if [ "$THEHIVE_CONNECTED" == "yes" ]; then
    echo "Migrating thehive databases if needed."
    curl -v -k -XPOST -L "https://localhost/thehive/api/maintenance/migrate"
    curl -v -k -XPOST -L "https://localhost/cortex/api/maintenance/migrate"
  fi
}

unmount_update() {
  cd /tmp
  umount /tmp/soagupdate
}

update_airgap_rules() {
  # Copy the rules over to update them for airgap.
  rsync -av $UPDATE_DIR/agrules/* /nsm/repo/rules/
}

update_centos_repo() {
  # Update the files in the repo
  echo "Syncing new updates to /nsm/repo"
  rsync -av $AGREPO/* /nsm/repo/
  echo "Creating repo"
  createrepo /nsm/repo
}

update_version() {
  # Update the version to the latest
  echo "Updating the Security Onion version file."
  echo $NEWVERSION > /etc/soversion
  echo $HOTFIXVERSION > /etc/sohotfix
  sed -i "/  soversion:/c\  soversion: $NEWVERSION" /opt/so/saltstack/local/pillar/global.sls
}

upgrade_check() {
  # Let's make sure we actually need to update.
  NEWVERSION=$(cat $UPDATE_DIR/VERSION)
  HOTFIXVERSION=$(cat $UPDATE_DIR/HOTFIX)
  [[ -f /etc/sohotfix ]] && CURRENTHOTFIX=$(cat /etc/sohotfix)
  if [ "$INSTALLEDVERSION" == "$NEWVERSION" ]; then
    echo "Checking to see if there are hotfixes needed"
    if [ "$HOTFIXVERSION" == "$CURRENTHOTFIX" ]; then
      echo "You are already running the latest version of Security Onion."
      exit 0
    else
      echo "We need to apply a hotfix"
      is_hotfix=true
    fi
  else
    is_hotfix=false
  fi 

}

upgrade_check_salt() {
  NEWSALTVERSION=$(grep version: $UPDATE_DIR/salt/salt/master.defaults.yaml | awk '{print $2}')
  if [ "$INSTALLEDSALTVERSION" == "$NEWSALTVERSION" ]; then
    echo "You are already running the correct version of Salt for Security Onion."
  else
    UPGRADESALT=1
  fi
}   

upgrade_salt() {
  SALTUPGRADED=True
  echo "Performing upgrade of Salt from $INSTALLEDSALTVERSION to $NEWSALTVERSION."
  echo ""
  # If CentOS
  if [[ $OS == 'centos' ]]; then
    echo "Removing yum versionlock for Salt."
    echo ""
    yum versionlock delete "salt-*"
    echo "Updating Salt packages and restarting services."
    echo ""
    set +e
    run_check_net_err \
    "sh $UPDATE_DIR/salt/salt/scripts/bootstrap-salt.sh -r -F -M -x python3 stable \"$NEWSALTVERSION\"" \
    "Could not update salt, please check $SOUP_LOG for details."
    set -e
    echo "Applying yum versionlock for Salt."
    echo ""
    yum versionlock add "salt-*"
  # Else do Ubuntu things
  elif [[ $OS == 'ubuntu' ]]; then
    echo "Removing apt hold for Salt."
    echo ""
    apt-mark unhold "salt-common"
    apt-mark unhold "salt-master"
    apt-mark unhold "salt-minion"
    echo "Updating Salt packages and restarting services."
    echo ""
    set +e
    run_check_net_err \
    "sh $UPDATE_DIR/salt/salt/scripts/bootstrap-salt.sh -F -M -x python3 stable \"$NEWSALTVERSION\"" \
    "Could not update salt, please check $SOUP_LOG for details."
    set -e
    echo "Applying apt hold for Salt."
    echo ""
    apt-mark hold "salt-common"
    apt-mark hold "salt-master"
    apt-mark hold "salt-minion"
  fi
}

verify_latest_update_script() {
  # Check to see if the update scripts match. If not run the new one.
  CURRENTSOUP=$(md5sum /opt/so/saltstack/default/salt/common/tools/sbin/soup | awk '{print $1}')
  GITSOUP=$(md5sum $UPDATE_DIR/salt/common/tools/sbin/soup | awk '{print $1}')
  CURRENTCMN=$(md5sum /opt/so/saltstack/default/salt/common/tools/sbin/so-common | awk '{print $1}')
  GITCMN=$(md5sum $UPDATE_DIR/salt/common/tools/sbin/so-common | awk '{print $1}')
  CURRENTIMGCMN=$(md5sum /opt/so/saltstack/default/salt/common/tools/sbin/so-image-common | awk '{print $1}')
  GITIMGCMN=$(md5sum $UPDATE_DIR/salt/common/tools/sbin/so-image-common | awk '{print $1}')

  if [[ "$CURRENTSOUP" == "$GITSOUP" && "$CURRENTCMN" == "$GITCMN" && "$CURRENTIMGCMN" == "$GITIMGCMN" ]]; then
    echo "This version of the soup script is up to date. Proceeding."
  else
    echo "You are not running the latest soup version. Updating soup and its components. Might take multiple runs to complete"
    cp $UPDATE_DIR/salt/common/tools/sbin/soup $DEFAULT_SALT_DIR/salt/common/tools/sbin/
    cp $UPDATE_DIR/salt/common/tools/sbin/so-common $DEFAULT_SALT_DIR/salt/common/tools/sbin/
    cp $UPDATE_DIR/salt/common/tools/sbin/so-image-common $DEFAULT_SALT_DIR/salt/common/tools/sbin/
    salt-call state.apply -l info common queue=True
    echo ""
    echo "soup has been updated. Please run soup again."
    exit 0
  fi
}

main() {
  set -e
  trap 'check_err $?' EXIT
  
  echo "### Preparing soup at $(date) ###"
  while getopts ":b" opt; do
    case "$opt" in
      b ) # process option b
        shift
        BATCHSIZE=$1
        if ! [[ "$BATCHSIZE" =~ ^[0-9]+$ ]]; then
          echo "Batch size must be a number greater than 0."
          exit 1
        fi
      ;;
      \? ) 
        echo "Usage: cmd [-b]"
      ;;
    esac
  done

  echo "Checking to see if this is a manager."
  echo ""
  require_manager
  set_minionid
  echo "Checking to see if this is an airgap install"
  echo ""
  check_airgap
  echo "Found that Security Onion $INSTALLEDVERSION is currently installed."
  echo ""
  set_os
  set_palette
  check_elastic_license
  echo ""
  if [[ $is_airgap -eq 0 ]]; then
    # Let's mount the ISO since this is airgap
    airgap_mounted
  else
    echo "Cloning Security Onion github repo into $UPDATE_DIR."
    echo "Removing previous upgrade sources."
    rm -rf $UPDATE_DIR
    clone_to_tmp
  fi
  check_os_updates
  echo ""
  echo "Verifying we have the latest soup script."
  verify_latest_update_script
  echo ""

  echo "Generating new repo archive"
  generate_and_clean_tarballs
  if [ -f /usr/sbin/so-image-common ]; then
    . /usr/sbin/so-image-common
  else 
  add_common
  fi

  echo "Let's see if we need to update Security Onion."
  upgrade_check
  upgrade_space

  echo "Checking for Salt Master and Minion updates."
  upgrade_check_salt


  if [ "$is_hotfix" == "true" ]; then
    echo "Applying $HOTFIXVERSION" 
    copy_new_files
    echo ""
    update_version
    salt-call state.highstate -l info queue=True
  else
    echo ""
    echo "Performing upgrade from Security Onion $INSTALLEDVERSION to Security Onion $NEWVERSION."
    echo ""

    echo "Updating dockers to $NEWVERSION."
    if [[ $is_airgap -eq 0 ]]; then
      airgap_update_dockers
      update_centos_repo
      yum clean all
      check_os_updates
    else
      update_registry
      set +e
      update_docker_containers "soup"
      set -e
    fi

    echo ""
    echo "Stopping Salt Minion service."
    systemctl stop salt-minion
    echo "Killing any remaining Salt Minion processes."
    set +e
    pkill -9 -ef /usr/bin/salt-minion
    set -e
    echo ""
    echo "Stopping Salt Master service."
    systemctl stop salt-master
    echo ""

    preupgrade_changes_2.3.50_repo

    # Does salt need upgraded. If so update it.
    if [[ $UPGRADESALT -eq 1 ]]; then
      echo "Upgrading Salt"
      # Update the repo files so it can actually upgrade
      upgrade_salt
    fi

    echo "Checking if Salt was upgraded."
    echo ""
    # Check that Salt was upgraded
    SALTVERSIONPOSTUPGRADE=$(salt --versions-report | grep Salt: | awk '{print $2}')
    if [[ "$SALTVERSIONPOSTUPGRADE" != "$NEWSALTVERSION" ]]; then
      echo "Salt upgrade failed. Check of indicators of failure in $SOUP_LOG."
      echo "Once the issue is resolved, run soup again."
      echo "Exiting."
      echo ""
      exit 1
    else
      echo "Salt upgrade success."
      echo ""
    fi

    preupgrade_changes
    echo ""

    if [[ $is_airgap -eq 0 ]]; then
      echo "Updating Rule Files to the Latest."
      update_airgap_rules
    fi

    # Only update the repo if its airgap
    if [[ $is_airgap -eq 0 && $UPGRADESALT -ne 1 ]]; then
      update_centos_repo
    fi

    echo ""
    echo "Copying new Security Onion code from $UPDATE_DIR to $DEFAULT_SALT_DIR."
    copy_new_files
    echo ""
    update_version

    echo ""
    echo "Locking down Salt Master for upgrade"
    masterlock

    echo ""
    echo "Starting Salt Master service."
    systemctl start salt-master

    # Testing that salt-master is up by checking that is it connected to itself
    set +e
    retry 50 10 "salt-call state.show_top -l error" || fail "salt-master could not be reached. Check $SOUP_LOG for details."
    set -e

    echo ""
    echo "Ensuring python modules for Salt are installed and patched."
    salt-call state.apply salt.python3-influxdb -l info queue=True
    echo ""

    # Only regenerate osquery packages if Fleet is enabled
    FLEET_MANAGER=$(lookup_pillar fleet_manager)
    FLEET_NODE=$(lookup_pillar fleet_node)
    if [[ "$FLEET_MANAGER" == "True" || "$FLEET_NODE" == "True" ]]; then
      echo ""
      echo "Regenerating Osquery Packages.... This will take several minutes."
      salt-call state.apply fleet.event_gen-packages -l info queue=True
      echo ""
    fi

    echo ""
    echo "Running a highstate to complete the Security Onion upgrade on this manager. This could take several minutes."
    salt-call state.highstate -l info queue=True
    echo ""
    echo "Upgrade from $INSTALLEDVERSION to $NEWVERSION complete."

    echo ""
    echo "Stopping Salt Master to remove ACL"
    systemctl stop salt-master

    masterunlock

    echo ""
    echo "Starting Salt Master service."
    systemctl start salt-master

    # Testing that salt-master is up by checking that is it connected to itself
    set +e
    retry 50 10 "salt-call state.show_top -l error" || fail "salt-master could not be reached. Check $SOUP_LOG for details."
    set -e
    
    echo "Running a highstate. This could take several minutes."
    salt-call state.highstate -l info queue=True
    postupgrade_changes
    [[ $is_airgap -eq 0 ]] && unmount_update
    thehive_maint

    if [[ $UPGRADESALT -eq 1 ]]; then
      if [[ $is_airgap -eq 0 ]]; then
        echo ""
        echo "Cleaning repos on remote Security Onion nodes."
        salt -C 'not *_eval and not *_helixsensor and not *_manager and not *_managersearch and not *_standalone and G@os:CentOS' cmd.run "yum clean all"
        echo ""
      fi
    fi

    check_sudoers

    if [[ -n $lsl_msg ]]; then
      case $lsl_msg in
        'distributed')
          echo "[INFO] The value of log_size_limit in any heavy node minion pillars may be incorrect."
          echo " -> We recommend checking and adjusting the values as necessary."
          echo " -> Minion pillar directory: /opt/so/saltstack/local/pillar/minions/"
        ;;
        'single-node')
          # We can assume the lsl_details array has been set if lsl_msg has this value
          echo "[WARNING] The value of log_size_limit (${lsl_details[0]}) does not match the recommended value of ${lsl_details[1]}."
          echo " -> We recommend checking and adjusting the value as necessary."
          echo " -> File: /opt/so/saltstack/local/pillar/minions/${lsl_details[2]}.sls"
        ;;
      esac
    fi

    NUM_MINIONS=$(ls /opt/so/saltstack/local/pillar/minions/*_*.sls | wc -l)

    if [[ $NUM_MINIONS -gt 1 ]]; then

      cat << EOF
    
  
  
This appears to be a distributed deployment. Other nodes should update themselves at the next Salt highstate (typically within 15 minutes). Do not manually restart anything until you know that all the search/heavy nodes in your deployment are updated. This is especially important if you are using true clustering for Elasticsearch.

Each minion is on a random 15 minute check-in period and things like network bandwidth can be a factor in how long the actual upgrade takes. If you have a heavy node on a slow link, it is going to take a while to get the containers to it. Depending on what changes happened between the versions, Elasticsearch might not be able to talk to said heavy node until the update is complete.

If it looks like you’re missing data after the upgrade, please avoid restarting services and instead make sure at least one search node has completed its upgrade. The best way to do this is to run 'sudo salt-call state.highstate' from a search node and make sure there are no errors. Typically if it works on one node it will work on the rest. Forward nodes are less complex and will update as they check in so you can monitor those from the Grid section of SOC.

For more information, please see https://docs.securityonion.net/en/2.3/soup.html#distributed-deployments.

EOF

    fi
  fi

  echo "### soup has been served at $(date) ###"
}

cat << EOF

SOUP - Security Onion UPdater

Please review the following for more information about the update process and recent updates:
https://docs.securityonion.net/soup
https://blog.securityonion.net

Press Enter to continue or Ctrl-C to cancel.

EOF

read -r input

main "$@" | tee -a $SOUP_LOG
  