#!/bin/bash

#
# Automated Mysql installer/configurator, tested to work on CentOS5+,
# Ubuntu LTS, and Debian5+ dervived systems. objective is to install and safely
# secure mysql with an admin user password to /root/.mysqlp and verify mysql
# is running sanely
# Created 2011-Jan-20th by Glenn Enright
#

# support defaults
export DEBIAN_FRONTEND="noninteractive"
CALLER=$(ps ax | grep "^ *$PPID" | awk '{print $NF}')

# operating defaults
ERRORMSG=
PROMPT="y"
PERL="y"
PHP="y"
APACHE="y"
SECURE="y"
RESTART="y"
ONBOOT="y"
SYSTEMD="n"
REALNAME="mysql" # or mariadb later

# package manager identification
INSTALLCOMM="yum -q -y install"
[ -e /etc/debian_version ] && INSTALLCOMM="apt-get -y -qq install"

# login defaults
DEFAULTADMINUSER="root"
ADMINUSER=
ADMINPASS=

# Add some debug in the future
DEBUG_LOG_FILE=/dev/null

##
# Function: echolog
# print message as is to log and stdout. Empty string ok
#
function echolog {
  echo "$*" | tee -a "${DEBUG_LOG_FILE}" 1>&2
  return 0
}

##
# Function: error_exit
# exit due to program error, after printing a string containing any error 
# message. Pretty prints to stderr. Example usage is ...
#   true || eror_exit "$LINENO: An error has occurred."
# Reference: http://www.linuxcommand.org/wss0150.php
#
function error_exit() {
  echo "! ${0##*/} exit: ${1:-'Unknown Error'}" | tee -a "${DEBUG_LOG_FILE}" 1>&2
  echo; exit 1
}

function version {
  echo "
  $0 
  Copyright RimuHosting.com
  Installs and secures the mysql packages provided by your system
"
}

###
# Function: usage
# Handy function to tell users how to...
#
function usage {
  echo "  Usage: $0 [options]

  Option:               Description:
  --noprompt            run script without user interaction
  --adminuser name      mysql user with full access, usually ${DEFAULTADMINUSER}
  --adminpass password  password for the admin user
  --noperl              skip optional perl libraries
  --noapache            skip optional apache libraries
  --nophp               skip optional php libraries
  --nosecure            skip security steps (ignore --adminuser and --adminpass)
  --minimal             skips optional libraries and the securing step
  --norestart           dont start/restart mysql
  --notonboot           dont set to start on boot (but dont disable it either)
  --debug               enable debugging to log file
"
}

###
# Function: parsecommandline
# Take parameters as given on command line and set those up so we can do
# cooler stuff, or complain that nothing will work. Set some reasonable
# defaults so we dont have to type so much.
#
function parsecommandline {
  while [ -n "$1" ]; do
    PARAM=$1
    case "$1" in
    --adminuser)
      shift
      if [ -z "$1" ]; then
        ERRORMSG="$PARAM given without value"
        return 1
      fi
      ADMINUSER=$1
      ;;
    --adminpass)
      shift
      if [ -z "$1" ]; then
        ERRORMSG="$PARAM given without value"
        return 1
      fi
      ADMINPASS=$1
      ;;
    --minimal)
      PERL="n"
      PHP="n"
      APACHE="n"
      SECURE="n"
      ;;
    --noperl)
      PERL="n"
      ;;
    --nophp)
      PHP="n"
      ;;
    --noapache)
      APACHE="n"
      ;;
    --nosecure)
      SECURE="n"
      ;;
    --norestart)
      RESTART="n"
      ;;
    --notonboot)
      ONBOOT="n"
      ;;
    --noprompt)
      NOPROMPT="n"
      ;;
    --debug)
      DEBUG_LOG_FILE="/root/$0-debug.log"
      touch "${DEBUG_LOG_FILE}"
      ;;
    h|-h|--help|?|-?)
      version
      usage
      exit 0
      ;;
    *)
      ERRORMSG="unrecognised paramter '$PARAM'"
      return 1
      ;;
    esac
    shift
  done
  ADMINUSER=${ADMINUSER:-$DEFAULTADMINUSER}
  if [ $(id -u) != "0" ] ; then
    ERRORMSG="you need to be logged in as the 'root' user to run this (e.g. sudo $0 $* )"
    return 1
  fi
  if [ -e "/etc/psa/.psa.shadow" ] || [ -e "/usr/local/cpanel" ]; then
    ERRORMSG="this server appears to be managed by a control panel (Plesk or Cpanel/WHM), if MySQL is not already installed please do that using the web interface."
    return 1
  fi
  if ( which systemctl > /dev/null 2>&1 ); then
    SYSTEMD=y
  fi
  return 0
}

function get_distrorelease() {
  echolog "* Getting distro details"
  if ( ! which lsb_release > /dev/null 2>&1 ); then
    echolog "  installing 'lsb_release' command"
    if [ -e /etc/redhat-release ]; then
      yum -q -y install redhat-lsb-core >> "$DEBUG_LOG_FILE" 2>&1
      [ $? -ne 0 ] &&  error_exit "installing redhat-lsb-core package on failed"
    elif [ -e /etc/debian_version ]; then
      apt-get $APTMOD install lsb-release  >> "$DEBUG_LOG_FILE" 2>&1
      [ $? -ne 0 ] && error_exit "installing lsb-release package failed"
    else 
      error_exit "Running on unknown distro, and missing lsb_release command."
    fi
  fi
  DISTRO=$( lsb_release -is )
  RELEASE=$( lsb_release -rs |cut -d. -f1 )
  [ -z "${DISTRO}" ] && error_exit "Was not able to identify distribution"
  [ -z "${RELEASE}" ] && error_exit "Was not able to identify release"
  echolog "  Using distro \"$DISTRO\" and release \"${RELEASE}\""
  
  if [ "${DISTRO}" = "CentOS" ] && [ "${RELEASE}" -ge "7" ]; then
    REALNAME="mariadb"
  elif [ "${DISTRO}" = "Ubuntu" ] && [ "${RELEASE}" -ge "16" ]; then
    REALNAME="mariadb"
  elif [ "${DISTRO}" = "Debian" ] && [ "${RELEASE}" -ge "9" ]; then
    REALNAME="mariadb"
  fi
  echolog "  installing packages as \"${REALNAME}\""
  return 0
}


# assumes package cache already updated
# prints name of each successfully installed package
function installpackages() {
  for package in $*; do
    ${INSTALLCOMM} ${package} >> "$DEBUG_LOG_FILE" 2>&1
    [ $? -ne 0 ] && return 1
    echolog "    $package"
  done
  return 0
}

# Install mysql packages, if needed. Often these may already be in place,
# but make sure anyhow, and start the service
function installmysql {
  local ret=0
  echolog "* Installing core server packages"
  if [ $(which mysqld_safe 2>/dev/null) ]; then
    echolog "  seems to be installed already"
    return 0
  fi
  if [ -e /etc/redhat-release ]; then
    yum -q clean all >> "$DEBUG_LOG_FILE" 2>&1
    installpackages "${REALNAME} ${REALNAME}-server mysql-connector-odbc"
  elif [ -e /etc/debian_version ]; then
    apt-get clean >> "$DEBUG_LOG_FILE" 2>&1
    apt-get -q update >> "$DEBUG_LOG_FILE" 2>&1
    [ $? -ne 0 ] && error_exit "problem with apt package manager, check log for details"
    installpackages "${REALNAME}-client ${REALNAME}-server"
  else
    # covers all later cases without needing an explicit test
    error_exit "Unsupported distribution"
  fi
  ret=$? # falls through from above conditional
  [ $ret -ne 0 ] && error_exit "install failed with code $ret on ${packlist}"
  
  # https://mariadb.com/kb/en/library/authentication-plugin-unix-socket/
  # The unix_socket authentication plugin is also installed by default in new 
  # installations that use the .deb packages provided by Debian's default 
  # repositories in Debian 9 and later and Ubuntu's default repositories 
  # in Ubuntu 15.10 and later,
  # turn that off to allow other things like phpmyadmin to work as expected.
  if [ -d /etc/mysql/conf.d ]; then
    echo "[mariadb]
unix_socket=OFF" > /etc/mysql/conf.d/no-socket.con
  fi
  return 0
}

# These are extras, if a package doesnt exist or install fails we dont mind
function install_libraries() {
  echolog "* Installing supporting libraries"
  [ "${PERL}" = "y" ] && installpackages "libdbd-mysql-perl libdbi-perl"
  [ "${PHP}" = "y" ] && installpackages "php-mysql php-pdo php5-mysql php5-odbc php-mcrypt"
  [ "${APACHE}" = "y" ] && installpackages "apr-util-mysql libaprutil1-dbd-mysql"
  return 0
}

function restart_mysql() {
  echolog "* Restarting service"
  if [ "${RESTART}" = "n" ]; then
    echolog "  skipping due to --norestart option"
    return 0
  fi
  local command
  if [ "${SYSTEMD}" = "y" ]; then
    systemctl restart "${REALNAME}" >> "$DEBUG_LOG_FILE" 2>&1
  else
    # fix for rimuhosting.com disabled mysql server script in ubuntu lucid
    [ -e /etc/init/mysql.conf.disabled ] && mv /etc/init/mysql.conf{.disabled,}
    # this cascade of tests should work under all our older distros
    if [ -e /etc/init.d/mysqld ]; then # older centos default
      echolog "  using \"/etc/init.d/mysqld restart\""
      /etc/init.d/mysqld restart  >> "$DEBUG_LOG_FILE" 2>&1 
    elif [ -e /etc/init.d/mysql ]; then # older debians
      echolog "  using \"/etc/init.d/mysql restart\""
      /etc/init.d/mysql restart >> "$DEBUG_LOG_FILE" 2>&1
    elif [ -e /etc/init/mysql.conf ]; then # any debian/ubuntu using upstart
      echolog "  using \"service mysql restart\""
      service mysql restart >> "$DEBUG_LOG_FILE" 2>&1
    fi
  fi
  [ $? -ne 0 ] && error_exit "unable to restart MySQL, this is probably a bug"
  # just in case lets trap on either unmapped command or command fail
  echo -n "  waiting for service to settle:"
  for i in $(seq 10 -1 1); do sleep 1; echo -n " $i"; done;
  echo
  [ $(netstat -plnt | grep -c mysql) -eq 0 ] && error_exit "Service not running after restart, please check manually"
  return 0
}

function start_mysql_on_boot() {
  echolog "* Setting service to start on boot"
  if [ "${ONBOOT}" = "n" ]; then
    echolog "  skipping due to --notonboot option"
    return 0
  fi
  if [ "${SYSTEMD}" = "y" ]; then
    systemctl enable "${REALNAME}" >> "$DEBUG_LOG_FILE" 2>&1
  elif [ -e /etc/redhat-release ]; then
    chkconfig --add mysqld >> "$DEBUG_LOG_FILE" 2>&1
    chkconfig mysqld on >> "$DEBUG_LOG_FILE" 2>&1
  else
    update-rc.d mysql defaults >> "$DEBUG_LOG_FILE" 2>&1
  fi
  return 0
}

function secure_mysql() {
  echolog "* Securing mysql"
  if [ "$SECURE" = "n" ]; then
    echolog "  Skipping due to --nosecure option"
    return 0
  fi
  # work out password
  if [ -z ${ADMINPASS} ] && [ -e /root/.mysqlp ]; then
    echo "  admin pass not provided, found saved secret at /root/.mysqlp so trying with that"
    ADMINPASS=$(cat /root/.mysqlp)
  fi
  if [ -z "$ADMINPASS" ] && [ "$PROMPT" = "y" ]; then
    echo "  no password secret found or given."
    yn=
    while [ "$yn" != 'y' ] && [ "$yn" != 'n' ]; do
      echo -n "  Do you want to enter a new '$ADMINUSER' user password (Y/n)? "
      read yn
      [ "$yn" = "" ] && yn='y' # accept default value
      yn=$(echo $yn | tr [:upper:] [:lower:])
    done
    while [ "$yn" = 'y' ]; do
      echo -n "    Enter new password: "
      read -s ADMINPASS
      echo
      if [ "$ADMINPASS" = "" ]; then
        echo -n "    Sorry, you can't use an empty password here, retry (Y/n)? "
        read yn
        yn=$(echo $yn | tr [:upper:] [:lower:])
        [ "$yn" != 'n' ] && yn='y'
        continue
      fi
      echo -n "    Re-enter new password: "
      read -s CHECKPASS
      echo
      if [ "$ADMINPASS" != "$CHECKPASS" ]; then
        echo -n "    Sorry, passwords do not match, retry (Y/n)? "
        read yn
        yn=$(echo "$yn" | tr [:upper:] [:lower:])
        [ "$yn" != 'n' ] && yn='y'
        continue
      fi
      break
    done
  fi
  if [ -z "$ADMINPASS" ]; then
    echolog "! no password secret found or given, you will need to secure Mysql manually"
    return 0
  fi

  # find default connection method (socket, tcp, etc) and check access at the same time
  local secure="n"
  local ctype=$(mysql -u${ADMINUSER} -e "\s" 2>/dev/null | grep 'Connection:')
  if [ ! -n "${ctype}" ]; then
    ctype=$(mysql -u$ADMINUSER -p${ADMINPASS} -e "\s" 2>/dev/null | grep 'Connection:')
    secure=y
  fi
  # TODO try with debian maintenance account?
  if [ ! -n "${ctype}" ]; then
    echolog "! Unable to connect to mysql, make sure the service is running and credentials provided are correct."
    return 0
  fi

  # password not set, make it so
  if [ "${secure}" = "n" ]; then
    echolog "  setting password"
    mysql -u$ADMINUSER -e "UPDATE mysql.user SET Password=PASSWORD('$ADMINPASS') WHERE User='root'; FLUSH PRIVILEGES;"  >> "$DEBUG_LOG_FILE" 2>&1
    if [ $? -ne 0 ]; then
      echolog "! unable to set $ADMINUSER password, set manually"
      return 0
    fi
  fi

  echolog "  verifying access"
  mysql -u"$ADMINUSER" -p"$ADMINPASS" -e "use mysql;"  >> "$DEBUG_LOG_FILE" 2>&1
  [ $? -ne 0 ] && error_exit "can not connect with the updated credentials, this is a bug, please check manually"
  echo "$ADMINPASS" > /root/.mysqlp

  # non-essential but nice to do
  echolog "  remove remote root access"
  mysql -u$ADMINUSER -p$ADMINPASS -e "DELETE FROM mysql.user WHERE User='root' AND Host!='localhost';" >> "$DEBUG_LOG_FILE" 2>&1
  echolog "  remove anononymous users"
  mysql -u$ADMINUSER -p$ADMINPASS -e "DELETE FROM mysql.user WHERE User=''" >> "$DEBUG_LOG_FILE" 2>&1
  echolog "  remove test database"
  mysql -u$ADMINUSER -p$ADMINPASS -e "DROP DATABASE test;" >> "$DEBUG_LOG_FILE" 2>&1
  echolog "  remove test database privileges"
  mysql -u$ADMINUSER -p$ADMINPASS -e "DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%'" >> "$DEBUG_LOG_FILE" 2>&1
  echolog "  reloading privilege tables"
  mysql -u$ADMINUSER -p$ADMINPASS -e "FLUSH PRIVILEGES" >> "$DEBUG_LOG_FILE" 2>&1

  return 0
}

##
# put functions above, main process below
#

parsecommandline $*
if [ $? -ne 0 ]; then
  version
  usage
  error_exit "$ERRORMSG"
fi
if [ -z $NOPROMPT ] && [ "$CALLER" = "-bash" ]; then
  version
  if [ "${DEBUG_LOG_FILE}" != "/dev/null" ]; then
    echolog "! Debugging enabled, logging to ${DEBUG_LOG_FILE}"
    echo
  fi
fi
get_distrorelease
installmysql || exit 1
install_libraries
restart_mysql
start_mysql_on_boot
secure_mysql
echolog "* ${REALNAME} install complete. Version: $(mysql -V)"

exit 0
