#!/usr/bin/env bash # vim:foldmarker={,} # vim:foldmethod=marker # vim: ts=4 sw=4 et export LC_ALL=C # set -o errexit # set -o nounset # set -o pipefail if [[ -n "${TRACE-}" ]]; then set -o xtrace fi # # ClusterControl is a management and monitoring application for your database infrastructure. # The installation script installs the frontend, backend and a LAMP stack. # ask() { if [[ ! -z $PROMPT_USER ]]; then read -p "$1" x [[ -z "$x" ]] || [[ "$x" == ["$2${2^^}"] ]] && return 0 return 1 else # always return true when prompt user is off return 0 fi } ask_p() { [[ ! -z $S9S_CMON_PASSWORD ]] && return 0 read -p "$1" x [[ -z "$x" ]] || [[ "$x" == ["$2${2^^}"] ]] && return 0 return 1 } confirm() { read -p "$1" x [[ -z "$x" ]] || [[ "$x" == ["$2${2^^}"] ]] && return 0 return 1 } ask_generic_distro() { PS3="Please select a generic distribution or exit the installation: " options=("redhat" "debian" "exit") select opt in "${options[@]}" do case $opt in "redhat") dist="redhat" break ;; "debian") dist="debian" break ;; "exit") exit 1 ;; *) echo invalid option;; esac done } do_lsb() { os_codename=$(lsb_release -sc) os_release=$(lsb_release -rs) if [[ -f /etc/centos-release || -f /etc/redhat-release ]]; then $(echo $os_release | grep -q "7.") [[ $? -eq 0 ]] && rhel_version=7 $(echo $os_release | grep -q "8.*") [[ $? -eq 0 ]] && rhel_version=8 $(echo $os_release | grep -q "9.*") [[ $? -eq 0 ]] && rhel_version=9 [[ -f /etc/centos-release ]] && CENTOS=1 [[ -f /etc/oracle-release ]] && ORACLE=1 && yum install -y oraclelinux-release-el${rhel_version} fi lsb=$(lsb_release -d) [[ $lsb =~ $regex_lsb ]] && dist=${BASH_REMATCH[1]} ; return 0 return 1 } do_release_file() { etc_files=$(ls /etc/*[-_]{release,version} 2>/dev/null) for file in $etc_files; do if [[ $file =~ $regex_etc ]]; then dist=${BASH_REMATCH[1]} # tolower. bash subs only in bash 4.x #dist=${dist,,} dist=$(echo $dist | tr '[:upper:]' '[:lower:]') if [[ $dist == "redhat" || $dist == "red" || $dist == "fedora" ]]; then $(grep -q " 7." $file) [[ $? -eq 0 ]] && rhel_version=7 && break $(grep -q " 8.*" $file) [[ $? -eq 0 ]] && rhel_version=8 && break $(grep -q " 9.*" $file) [[ $? -eq 0 ]] && rhel_version=9 && break break else # AlmaLinux/RockyLinux.. etc other RedHat compatible ones $(grep -q "platform:el7" $file) [[ $? -eq 0 ]] && dist="redhat" && rhel_version=7 && break $(grep -q "platform:el8" $file) [[ $? -eq 0 ]] && dist="redhat" && rhel_version=8 && break $(grep -q "platform:el9*" $file) [[ $? -eq 0 ]] && dist="redhat" && rhel_version=9 && break fi fi done [[ -f /etc/centos-release ]] && CENTOS=1 [[ -f /etc/oracle-release ]] && ORACLE=1 && yum install -y oraclelinux-release-el${rhel_version} } add_s9s_apt () { repo="deb [arch=amd64] ${repo_http_protocol}://repo.severalnines.com/deb ubuntu main" repo_source_file=/etc/apt/sources.list.d/s9s-repo.list if [[ ! -z ${USE_REPO} ]]; then repo="deb [arch=amd64] ${repo_http_protocol}://repo.severalnines.com/${USE_REPO}/deb ubuntu main" repo_source_file="/etc/apt/sources.list.d/s9s-${USE_REPO}.list" fi if [[ ! -e $repo_source_file ]]; then if [[ ! -z ${USE_REPO} ]]; then wget ${repo_http_protocol}://repo.severalnines.com/${USE_REPO}/severalnines-repos.asc -O- | apt-key add - && echo "$repo" | tee -a $repo_source_file else wget ${repo_http_protocol}://repo.severalnines.com/severalnines-repos.asc -O- | apt-key add - && echo "$repo" | tee -a $repo_source_file fi log_msg "Added ${repo_source_file}" log_msg "Updating repo ..." waitForLocks apt-get update else log_msg "Repo file $repo_source_file already exists" waitForLocks apt-get update fi } start_cmon_services() { if [[ $systemd == 1 ]]; then pidof -s cmon-events &>/dev/null || systemctl start cmon-events systemctl enable cmon-events pidof -s cmon-ssh &>/dev/null || systemctl start cmon-ssh systemctl enable cmon-ssh pidof -s cmon-cloud &>/dev/null || systemctl start cmon-cloud systemctl enable cmon-cloud else pidof -s cmon-events &>/dev/null || service cmon-events start pidof -s cmon-ssh &>/dev/null || service cmon-ssh start pidof -s cmon-cloud &>/dev/null || service cmon-cloud start if [[ "$dist" == "debian" ]]; then update-rc.d cmon-events defaults update-rc.d cmon-ssh defaults update-rc.d cmon-cloud defaults else chkconfig --levels 235 cmon-ssh on chkconfig --levels 235 cmon-events on chkconfig --levels 235 cmon-cloud on fi fi } add_s9s_commandline_apt() { # Available distros: wheezy, jessie, precise, trusty, xenial, yakkety, zesty repo_source_file=/etc/apt/sources.list.d/s9s-tools.list if [[ ! -e $repo_source_file ]]; then wget -qO - ${repo_http_protocol}://repo.severalnines.com/s9s-tools/${os_codename}/Release.key | apt-key add - echo "deb ${repo_http_protocol}://repo.severalnines.com/s9s-tools/${os_codename}/ ./" | tee /etc/apt/sources.list.d/s9s-tools.list else log_msg "Repo file $repo_source_file already exists" fi } add_s9s_yum () { repo_source_file=/etc/yum.repos.d/s9s-repo.repo [[ ! -z ${USE_REPO} ]] && repo_source_file="/etc/yum.repos.d/s9s-repo-${USE_REPO}.repo" if [[ ! -e $repo_source_file ]]; then if [[ ! -z ${USE_REPO} ]]; then cat > $repo_source_file << EOF [s9s-repo-${USE_REPO}] name=Severalnines ${USE_REPO} Repository baseurl = ${repo_http_protocol}://repo.severalnines.com/${USE_REPO}/rpm/os/x86_64 enabled = 1 gpgkey = ${repo_http_protocol}://repo.severalnines.com/${USE_REPO}/severalnines-repos.asc gpgcheck = 1 EOF else cat > $repo_source_file << EOF [s9s-repo] name=Severalnines Repository baseurl = ${repo_http_protocol}://repo.severalnines.com/rpm/os/x86_64 enabled = 1 gpgkey = ${repo_http_protocol}://repo.severalnines.com/severalnines-repos.asc gpgcheck = 1 EOF fi log_msg "Added ${repo_source_file}" else log_msg "Repo file $repo_source_file already exists" fi } add_s9s_commandline_yum() { repo_source_file=/etc/yum.repos.d/s9s-tools.repo if [[ ! -e $repo_source_file ]]; then if [[ -z $CENTOS ]]; then REPO="RHEL_7" [[ $rhel_version == "8" ]] && REPO="RHEL_8" [[ $rhel_version == "9" ]] && REPO="RHEL_9" else REPO="CentOS_7" [[ $rhel_version == "8" ]] && REPO="CentOS_8" [[ $rhel_version == "9" ]] && REPO="CentOS_9" fi cat > $repo_source_file << EOF [s9s-tools] name=s9s-tools (${REPO}) type=rpm-md baseurl=${repo_http_protocol}://repo.severalnines.com/s9s-tools/${REPO} gpgcheck=1 gpgkey=${repo_http_protocol}://repo.severalnines.com/s9s-tools/${REPO}/repodata/repomd.xml.key enabled=1 EOF log_msg "Added ${repo_source_file}" else log_msg "Repo file $repo_source_file already exists" fi } find_mysql() { mysql_bin="/usr/bin/mysql" if ! command -v mysql &>/dev/null; then log_msg "Cannot find a mysql client in your PATH!" log_msg "Provide the full path to your mysql client, for example /opt/mysql/bin/mysql" read -p "=> Absolute path to the MySQL client: " x [[ ! -z $x ]] && mysql_bin="$x" [[ ! -f $mysql_bin ]] && { log_msg "Cannot find ${mysql_bin}. ..."; exit 1; } fi } install_frontend() { clustercontrol_build=${CLUSTERCONTROL_BUILD:-"clustercontrol2"} if [[ ${cc_version} == "2" ]]; then clustercontrol_build=${CLUSTERCONTROL_BUILD:-"clustercontrol2"} elif [[ ${cc_version} == "3" ]]; then clustercontrol_build=${CLUSTERCONTROL_BUILD:-"clustercontrol-mcc clustercontrol-notifications clustercontrol-ssh clustercontrol-cloud"} fi log_msg "Installing ClusterControl frontend packages ..." if [[ $dist == "debian" ]]; then waitForLocks if [[ -z ${OFFLINE+x} ]]; then add_s9s_commandline_apt add_s9s_apt fi waitForLocks LC_ALL=en_US.utf8 DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true apt-get -yq install ${clustercontrol_build} || { log_msg "Failed to install clustercontrol packages. (apt-get -yq install ${clustercontrol_build})"; exit 1; } elif [[ $dist == "redhat" ]]; then if [[ -z ${OFFLINE+x} ]]; then add_s9s_commandline_yum add_s9s_yum fi yum -yq install ${clustercontrol_build} || { log_msg "Failed to install clustercontrol packages. (yum -yq install ${clustercontrol_build})"; exit 1; } fi if [[ ${cc_version} == "2" ]]; then # change registration form [[ -f /var/www/html/clustercontrol2/config.js ]] && sed -i "s|^[ \t]*USER_REGISTRATION:.*| USER_REGISTRATION: 1,|g" /var/www/html/clustercontrol2/config.js # check edition local cc_edition="${CC_EDITION:-##__CC_EDITION__##}" if [[ "${cc_edition}" == "1" ]]; then [[ -f /var/www/html/clustercontrol2/config.js ]] && sed -i "/^[ \t]*USER_REGISTRATION:.*/a\ COMMUNITY_EDITION: 1," /var/www/html/clustercontrol2/config.js fi fi } create_my_cnf() { cat > $1 << EOF [mysqld] user = mysql #basedir = $mysql_basedir datadir = $mysql_datadir pid_file = $mysql_datadir/mysqld.pid socket = $mysql_socket port = ${db_port} #log_error = error.log max_allowed_packet = 128M #event_scheduler = 1 innodb_buffer_pool_size = ${INNODB_BUFFER_POOL_SIZE}M innodb_flush_log_at_trx_commit = 2 innodb_file_per_table = 1 #innodb_data_file_path = ibdata1:100M:autoextend innodb_log_file_size = 512M innodb_log_files_in_group = 2 #innodb_buffer_pool_instances = 4 innodb_thread_concurrency = 0 innodb_flush_method = O_DIRECT sysdate_is_now = 1 max_connections = 512 thread_cache_size = 128 #table_open_cache=512 lower_case_table_names = 0 #skip_name_resolve skip-log-bin EOF case "${os_codename}" in 'disco' |'bionic'|'xenial'|'jammy'|'noble'|'stretch'|'buster'|'bullseye') cat >> $1 << EOF plugin-load-add = auth_socket.so EOF ;; esac cat >> $1 << EOF [mysql] socket=$mysql_socket [client] socket=$mysql_socket [mysqld_safe] pid-file=$mysql_datadir/mysqld.pid #log-error=error.log basedir=$mysql_basedir datadir=$mysql_datadir EOF } install_web_server() { case "${dist}" in 'debian') www_user=www-data install_packages="apt-get -y install apache2" enable_mods="ssl rewrite headers proxy proxy_http proxy_wstunnel" cert_file="/etc/ssl/certs/s9server.crt" key_file="/etc/ssl/private/s9server.key" restart_apache="service apache2 restart" update_repo="apt-get update" if [[ -z ${OFFLINE+x} ]]; then if ask "=> Do $update_repo? (Y/n): " "y"; then $update_repo fi fi if [[ -n $install_packages ]]; then waitForLocks LC_ALL=en_US.utf8 DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true $install_packages [[ $? -ne 0 ]] && log_msg "Installing packages ${install_packages}, failed" && exit 1 fi for m in $enable_mods; do log_msg "Enabling apache module ${m} ..." a2enmod $m done # enable sameorigin header if [[ -f /etc/apache2/conf-available/security.conf ]]; then # enable for header sed -ibak "s|^#Header set X-Frame-Options: \"sameorigin\"|Header set X-Frame-Options: \"sameorigin\"|g" /etc/apache2/conf-available/security.conf ln -sfn /etc/apache2/conf-available/security.conf /etc/apache2/conf-enabled/security.conf fi [[ -e /etc/apache2/sites-enabled/000-default.conf ]] && rm -f /etc/apache2/sites-enabled/000-default.conf # restart the web server after the controller has been installed ;; 'redhat') apache_conf=/etc/httpd/conf/httpd.conf apache_conf_ssl=/etc/httpd/conf.d/ssl.conf www_user=apache install_packages="yum -y install httpd mod_ssl" enable_mods="" cert_file="/etc/pki/tls/certs/s9server.crt" key_file="/etc/pki/tls/private/s9server.key" restart_apache="service httpd restart" [[ ! -z $ORACLE ]] && restart_apache="systemctl restart httpd" update_repo="yum update" chkconfig_apache="chkconfig --levels 235 httpd on" if [[ -z ${OFFLINE+x} ]]; then if ! ask "=> Do $update_repo? (y/N): " "n"; then $update_repo fi fi if [[ -n $install_packages ]]; then $install_packages &>/dev/null #[[ $? -ne 0 ]] && log_msg "Installing packages ${install_packages}, failed" && exit 1 fi $chkconfig_apache apache_version=$(apachectl -v | grep -i "server version" | cut -d' ' -f3) [[ "${apache_version%.*}" == "Apache/2.4" ]] && use_apache24=1 if [[ ! -z $use_apache24 ]]; then # enable sameorigin header if [[ ! -f /etc/httpd/conf.d/security.conf ]]; then # enable for header cat > /etc/httpd/conf.d/security.conf << EOF Header set X-Frame-Options: "sameorigin" EOF fi # restart the web server after the controller has been installed fi ;; esac # generate new cert if command -v openssl &>/dev/null; then mkdir -p /tmp/ssl && cd /tmp/ssl create_cert # copy the files from the clustercontrol package cp -f /tmp/ssl/server.crt ${cert_file} &>/dev/null cp -f /tmp/ssl/server.key ${key_file} &>/dev/null rm -rf /tmp/ssl &>/dev/null fi } configure_ccv2_web() { # change default port 9443 to 443 # ask to set the servername servername="${S9S_WEB_SERVER_HOST:-$host}" if [[ -z ${S9S_WEB_SERVER_HOST} ]]; then if ! ask_p "=> The Apache web 'ServerName' will be set to ${servername}. Do you want to change it? (y/N): " "n"; then read -p "=> Enter the 'ServerName': " x [[ ! -z $x ]] && servername="$x" fi fi log_msg "The Apache 'ServerName' will be set to ${servername} ..." case "${dist}" in 'debian') sed -i "s|^[ \t]*ServerName.*| ServerName ${servername}|g" /etc/apache2/sites-available/cc-frontend.conf if [[ ${cc_version} == "2" ]]; then sed -i "s|https://cc2.severalnines.local:9443.*|https://${servername}\/|g" /etc/apache2/sites-available/cc-frontend.conf sed -i "s|Listen 9443|#Listen 443|g" /etc/apache2/sites-available/cc-frontend.conf sed -i "s|9443|443|g" /etc/apache2/sites-available/cc-frontend.conf fi ;; 'redhat') if [[ ! -e /etc/httpd/conf.d/cc-frontend.conf ]]; then [[ -e /usr/share/cmon/apache/cc-frontend.conf ]] && cp /usr/share/cmon/apache/cc-frontend.conf /etc/httpd/conf.d/ fi sed -i "s|^[ \t]*ServerName.*| ServerName ${servername}|g" /etc/httpd/conf.d/cc-frontend.conf if [[ ${cc_version} == "2" ]]; then sed -i "s|https://cc2.severalnines.local:9443.*|https://${servername}\/|g" /etc/httpd/conf.d/cc-frontend.conf sed -i "s|Listen 9443|#Listen 443|g" /etc/httpd/conf.d/cc-frontend.conf sed -i "s|9443|443|g" /etc/httpd/conf.d/cc-frontend.conf fi ;; esac } configure_mcc() { local web_port=${1:-443} local web_root="${2:-/var/www/html/clustercontrol-mcc}" log_msg "Configuring ccmgr (cmon-proxy) ..." ccmgradm init --local-cmon -p ${web_port} -f ${web_root} systemctl restart cmon-proxy || exit 1 # log_msg "Please enter your first name: " # read first_name # log_msg "Please enter your last name: " # read last_name # log_msg "Please enter your email address: " # read admin_user_email # # create admin user # log_msg "Please enter an admin username: " # read admin_user # until set_admin_password; do # log_msg "" # log_msg "Password mismatch! Try again." # sleep 1 # done # [ -z ${admin_user_password+x} ] || S9S_ADMIN_USER_PASSWORD="${admin_user_password}" # s9s user --create --first-name=${first_name} --last-name=${last_name} --email-address="${admin_user_email}" \ # --new-password=${admin_user_password} --group=admins \ # --generate-key ${admin_user} || exit 1 } install_database_server() { case "${dist}" in 'debian') install_packages="apt install -y mysql-server mysql-client" [[ -n $use_existing_mysql ]] && install_packages="" stop_mysql="service mysql stop" start_mysql="service mysql start" update_repo="apt-get update" mysql_basedir=/usr mysql_datadir=/var/lib/mysql my_cnf=/etc/mysql/my.cnf mysql_socket="/var/run/mysqld/mysqld.sock" case "${os_codename}" in 'jammy'|'noble') install_packages="apt-get -y install mysql-server mysql-client wget" [[ -n $use_existing_mysql ]] && install_packages="apt-get -y install wget" ;; 'buster') install_packages="`echo $install_packages | sed 's/mysql-/default-mysql-/g'`" ;; 'bullseye'|'bookworm') install_packages="`echo $install_packages | sed 's/mysql-/mariadb-/g'`" ;; esac if [[ -z ${OFFLINE+x} ]]; then if ask "=> Do $update_repo? (Y/n): " "y"; then $update_repo fi fi if [[ -n $install_packages ]]; then waitForLocks LC_ALL=en_US.utf8 DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true $install_packages [[ $? -ne 0 ]] && log_msg "Installing packages ${install_packages}, failed" && exit 1 fi if [[ -z $use_existing_mysql ]]; then log_msg "Stoppping MySQL Server before updating configuration ..." #killall -15 mysqld mysqld_safe &>/dev/null $stop_mysql [[ $? -ne 0 ]] && [[ $stop_mysql != "" ]] && log_msg "Failed to stop the MySQL Server. ..." && exit 1 create_my_cnf /tmp/my.cnf if [[ "${os_codename}" == "bookworm" || "${os_codename}" == "bullseye" ]]; then cp -f /tmp/my.cnf /etc/mysql/mariadb.conf.d/55-s9s-server.cnf || exit 1 else cp -f /tmp/my.cnf $my_cnf || exit 1 rm -rf $mysql_datadir/ib_log* $mysql_datadir/mysqld.pid $mysql_datadir/cmon $mysql_datadir/dcps fi rm -f /tmp/my.cnf fi ;; 'redhat') install_packages="yum -y install mysql-server mysql" update_repo="yum update" mysql_basedir=/usr mysql_datadir=/var/lib/mysql stop_mysql="" start_mysql="service mysqld start" chkconfig="chkconfig --levels 235 mysqld on" mysql_socket="/var/lib/mysql/mysql.sock" if [[ ! -z $rhel_version ]]; then case "$rhel_version" in 7|8|9) install_packages="yum -y install mariadb-server" [[ -n $use_existing_mysql ]] && install_packages="" ;; *) log_msg "Unknown/Unsupported Centos/Red Hat version $rhel_version" exit 1 ;; esac fi if [[ -z ${OFFLINE+x} ]]; then if ! ask "=> Do $update_repo? (y/N): " "n"; then $update_repo fi fi if [[ -n $install_packages ]]; then $install_packages [[ $? -ne 0 ]] && log_msg "Installing packages ${install_packages}, failed" && exit 1 fi if [[ ! -z $rhel_version ]]; then case "$rhel_version" in 7|8|9) yum list installed | grep -q mariadb-server if [[ $? -eq 0 ]]; then start_mysql="service mariadb start" [[ ! -z $ORACLE ]] && start_mysql="systemctl start mariadb" chkconfig="chkconfig --levels 235 mariadb on" fi ;; esac fi # workaround for mysql 8, not safe $(yum list installed | grep mysql | grep -q mysql80) if [[ $? -eq 0 ]]; then mysqld8="mysqld --initialize-insecure --user=mysql --basedir=/usr --datadir=/var/lib/mysql" $mysqld8 fi $chkconfig ;; esac echo -e "\n=> Starting database. This may take a couple of minutes. Do NOT press any key." $start_mysql [[ $? -ne 0 ]] && log_msg "Failed to start the database server. ..." && exit 1 log_msg "Securing the MySQL Server ..." log_msg "!! In order to complete the installation you need to set a MySQL root password !!" log_msg "Supported special password characters: ~!@#$%^&*()_+{}<>?" [[ -z $S9S_ROOT_PASSWORD ]] && read -n 1 -s -r -p "=> Press any key to proceed ..." echo "" case "${os_codename}" in 'focal'|'disco'|'bionic'|'xenial'|'jammy'|'noble') $mysql_basedir/bin/mysql -uroot -P${db_port} -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''" ;; esac if [[ -z ${S9S_CMON_PASSWORD} ]]; then [[ -e /var/lib/mysql/mysql.sock ]] && mysql_secure_installation --socket="/var/lib/mysql/mysql.sock" || mysql_secure_installation [[ $? -ne 0 ]] && log_msg "Unable to secure the MySQL server running mysql_secure_installation, exiting ..." && exit 1 echo -e "\n=> Please enter the MySQL root password that was set to continue!" until set_root_password; do log_msg "" log_msg "Password mismatch! Try again." sleep 1 done else log_msg "!! Setting MySQL root user password !!" [[ -e /var/lib/mysql/mysql.sock ]] && $mysql_basedir/bin/mysqladmin --socket="/var/lib/mysql/mysql.sock" -uroot password "${S9S_ROOT_PASSWORD}" || $mysql_basedir/bin/mysqladmin -uroot password "${S9S_ROOT_PASSWORD}" log_msg "!! Please run mysql_secure_installation after the setup !!" fi } configure_database_server() { find_mysql if [[ $root_password == "" ]] || [[ -z $root_password ]]; then IFS="" read -s -p "=> Enter your MySQL root user's password: " x [[ ! -z $x ]] && root_password="$x" fi $mysql_bin -uroot -p''"${root_password}"'' -P${db_port} -e "SELECT USER();" &>/dev/null if [[ $? -ne 0 ]]; then log_msg "Cannot Connect! Try again." IFS="" read -s -p "=> Enter your MySQL root user's password: " x [[ ! -z $x ]] && root_password="$x" log_msg "" fi log_msg "Set a password for ClusterControl's MySQL user (cmon) [${cmon_password}]" log_msg "Supported special characters: ~!@#$%^&*()_+{}<>?" until set_cmon_password; do log_msg "" log_msg "Password mismatch! Try again." sleep 1 done cat > /tmp/ui.sql << EOF BEGIN; CREATE USER 'cmon'@'localhost' identified by '${cmon_password}'; GRANT ALL PRIVILEGES ON *.* to 'cmon'@'localhost' WITH GRANT OPTION; CREATE USER 'cmon'@'127.0.0.1' identified by '${cmon_password}'; GRANT ALL PRIVILEGES ON *.* to 'cmon'@'127.0.0.1' WITH GRANT OPTION; CREATE USER 'cmon'@'${host}' identified by '${cmon_password}'; GRANT ALL PRIVILEGES ON *.* to 'cmon'@'${host}' WITH GRANT OPTION; COMMIT; EOF log_msg "Creating the MySQL cmon user ..." if [[ -z $use_existing_mysql ]]; then $mysql_bin -uroot -P${db_port} -p''"${root_password}"'' < /tmp/ui.sql else cat > /tmp/drop.sql << EOF DROP USER 'cmon'@'localhost'; DROP USER 'cmon'@'127.0.0.1'; DROP USER 'cmon'@'${host}'; EOF $mysql_bin -f -uroot -P${db_port} -p''"${root_password}"'' < /tmp/drop.sql &>/dev/null [[ $? -ne 0 ]] && log_msg "Failed to drop cmon user! ..." && exit 1 rm -f /tmp/drop.sql $mysql_bin -f -uroot -P${db_port} -p''"${root_password}"'' < /tmp/ui.sql &>/dev/null fi [[ $? -ne 0 ]] && log_msg "Failed to add cmon user! ..." && exit 1 rm -f /tmp/ui.sql } check_emailaddress() { if [[ "$1" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$ ]]; then return 0 else log_msg "Invalid email address" return 1 fi } set_cmon_password() { [[ ! -z $S9S_CMON_PASSWORD ]] && return 0 IFS="" read -s -p "=> Enter a CMON user password: " x if [[ -z $x ]]; then log_msg "The password cannot be blank. Try again." IFS="" read -s -p "=> Enter a CMON user password: " x fi cmon_password="$x" log_msg "" IFS="" read -s -p "=> Enter the CMON user password again: " x if [[ "$cmon_password" == "$x" ]]; then return 0 else return 1 fi } set_root_password() { [[ ! -z $S9S_ROOT_PASSWORD ]] && return 0 IFS="" read -s -p "=> Enter the MySQL root password: " x while [[ -z $x ]]; do log_msg "The password cannot be blank. Try again." IFS="" read -s -p "=> Enter the MySQL root password: " x done root_password="$x" log_msg "" IFS="" read -s -p "=> Enter the MySQL root password again: " x if [[ "$root_password" == "$x" ]]; then return 0 else return 1 fi } set_admin_password() { [[ ! -z $S9S_ADMIN_USER_PASSWORD ]] && return 0 IFS="" read -s -p "Enter the admin user password: " x while [[ -z $x ]]; do log_msg "The password cannot be blank. Try again." IFS="" read -s -p "Enter the admin user password: " x done admin_user_password="$x" log_msg "" IFS="" read -s -p "Enter the admin user password again: " x if [[ "$admin_user_password" == "$x" ]]; then return 0 else return 1 fi } install_controller() { log_msg "Installing ClusterControl Controller ..." cmon_controller="clustercontrol-controller" cmon_basedir="/usr" [[ ! -z ${CONTROLLER_BUILD} ]] && cmon_controller=${CONTROLLER_BUILD} if [[ $dist == "redhat" ]]; then case "$rhel_version" in 9) if [[ -z ${OFFLINE+x} ]]; then if [[ -f /etc/almalinux-release ]] || [[ -f /etc/rocky-release ]]; then dnf config-manager --set-enabled crb dnf -y install epel-release elif [[ -f /etc/centos-release ]]; then dnf config-manager --set-enabled crb dnf -y install epel-release epel-next-release else # assume RHEL 9 ... subscription-manager repos --enable codeready-builder-for-rhel-9-$(arch)-rpms dnf -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm [[ $? -ne 0 ]] && setup_php7_redhat fi fi ;; 8) if [[ -z ${OFFLINE+x} ]]; then if [[ -f /etc/almalinux-release ]] || [[ -f /etc/rocky-release ]]; then dnf config-manager --set-enabled powertools dnf -y install epel-release elif [[ -f /etc/centos-release ]]; then dnf config-manager --set-enabled powertools dnf -y install epel-release epel-next-release else # assume RHEL 8 ... subscription-manager repos --enable codeready-builder-for-rhel-8-$(arch)-rpms dnf -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm fi fi ;; 7) if [[ -z ${OFFLINE+x} ]]; then if [[ -f /etc/centos-release ]]; then yum -y install epel-release elif [[ -f /etc/redhat-release ]]; then subscription-manager repos --enable rhel-*-optional-rpms \ --enable rhel-*-extras-rpms \ --enable rhel-ha-for-rhel-*-server-rpms yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm fi fi ;; esac # fix CMON API port for SELinux log_msg "Adding CMON API port 19501 for SELinux ..." semanage port -a -t http_port_t -p tcp 19501 &>/dev/null log_msg "Adding CMON port 19510 for events ..." semanage port -a -t http_port_t -p tcp 19510 &>/dev/null log_msg "Adding CMON port 19518 for clouds ..." semanage port -a -t http_port_t -p tcp 19518 &>/dev/null # enable httpd for network connections just in case it's off by default log_msg "Setting httpd_can_network_connect to 'on' for SELinux ..." semanage boolean --modify --on httpd_can_network_connect &>/dev/null enable_service="chkconfig --levels 235 cmon on" yum -y install $cmon_controller [[ $? -ne 0 ]] && exit 1 elif [[ $dist == "debian" ]]; then enable_service="update-rc.d cmon defaults" waitForLocks apt-get -y install $cmon_controller [[ $? -ne 0 ]] && exit 1 fi cat >> /etc/default/cmon << EOF # New events client http callback as of v1.4.2! EVENTS_CLIENT="http://127.0.0.1:${cmon_events_port}" CLOUD_SERVICE="http://127.0.0.1:${cmon_cloud_port}" EOF # use the cmon token for the global rcp_token rpc_key=${cc_api_token} # import cmon schema using cmon --init # rm /etc/cmon/cnf that came with the package rm -f /etc/cmon.cnf cmon --init \ --mysql-hostname="127.0.0.1" \ --mysql-port="${db_port}" \ --mysql-username="${cmon_user}" \ --mysql-password="${cmon_password}" \ --mysql-database="cmon" \ --hostname="${host}" \ --rpc-token="${rpc_key}" \ --controller-id="clustercontrol" [[ $? -ne 0 ]] && log_msg "Unable to init cmon! Exiting ..." && exit 1 $enable_service log_msg "Starting the Controller process." if [[ $systemd == 1 ]]; then systemctl restart cmon systemctl enable cmon else service cmon restart fi if [[ $dist == "redhat" ]]; then if [[ $rhel_version = "7" ]]; then yum install -y ntp ntpdate chkconfig ntpd on ntpdate pool.ntp.org service ntpd start fi fi } install_s9s_commandline() { s9s_tools=s9s-tools [[ ! -z ${S9S_TOOLS_BUILD} ]] && s9s_tools=${S9S_TOOLS_BUILD} if [[ $dist == "redhat" ]]; then yum -y install $s9s_tools elif [[ $dist == "debian" ]]; then waitForLocks apt-get -y install $s9s_tools fi [[ $? -ne 0 ]] && log_msg "Unable to install s9s-tools" && exit 1 echo "=> Waiting 60s until the CMON Controller completes its startup cycle ..." sleep 60 # wait for cmon to start on slower systems local email="${S9S_ADMIN_EMAIL:-##__EMAIL__##}" if [[ "$email" != *"@"* ]]; then email="" fi export S9S_USER_CONFIG=/tmp/ccsetup.conf log_msg "Create temporary cc setup user ..." s9s user --create --new-password=admin --group=admins --email-address="${email}" --controller="https://localhost:9501" ccsetup if [[ $? -ne 0 ]]; then log_msg "*** Unable to create a 'ccsetup' user! ***" exit 1 fi unset S9S_USER_CONFIG log_msg "*** Restarting the Controller process to generate CMON API rpc_tls files." if [[ $systemd == 1 ]]; then systemctl restart cmon else service cmon restart fi } log_msg() { LAST_MSG="$1" echo "$(date +'%Y-%m-%d %H:%M:%S %Z') -- ${LAST_MSG}" } track_install_init() { [[ -n ${INSTALLATION_PROGRESS} ]] && wget -qO- --no-check-certificate --post-data="container=${CONTAINER}&email=##__EMAIL__##&uuid=${cc_api_token}&version=${VERSION}&os=${OS}&step=1" https://severalnines.com/service/install_log.php &>/dev/null } track_install() { [[ -n ${INSTALLATION_PROGESS} ]] && wget -qO- --no-check-certificate --post-data="uuid=${cc_api_token}&step=${1}" https://severalnines.com/service/install_log.php &>/dev/null } checkLock() { if command -v fuser >/dev/null 2>/dev/null; then fuser $@ 2>/dev/null >/dev/null return $? fi # fuser (psmisc) not installed, go with lsof if [[ "`lsof $@ 2>/dev/null >/dev/null`x" != "x" ]]; then return 0 fi return 1 } waitForLocks() { if checkLock /var/lib/dpkg/lock /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock /var/cache/apt/archives/lock; then echo -n "=> Waiting for APT/DPKG locks." else return fi while checkLock /var/lib/dpkg/lock /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock /var/cache/apt/archives/lock; do echo -n . sleep 1 done echo . } cleanup() { if [[ ! -z ${SEND_DIAGNOSTICS} ]]; then [[ $(command -v cmon) ]] && VERSION=$(cmon --version | awk '/version/ {print $3}') if [[ $CONTAINER != "" ]]; then UUID=$(hostname | md5sum | cut -d' ' -f1) else UUID=$(dmidecode --string system-uuid 2>/dev/null | sed 's#-##g' | sha256sum | awk '{print $1}') fi OS=$(cat /proc/version) MEM=$(free -m | awk '/Mem:/ { print "T:" $2, "F:" $4}') if [[ -z $PYTHON3 ]]; then OS=$(python -c "import sys,urllib; print urllib.quote('${OS}')") MEM=$(python -c "import sys,urllib; print urllib.quote('${MEM}')") LAST_MSG=$(python -c "import sys,urllib; print urllib.quote('${LAST_MSG}')") else OS=$(python -c "import sys,urllib.parse; print(urllib.parse.quote('${OS}'))") MEM=$(python -c "import sys,urllib.parse; print(urllib.parse.quote('${MEM}'))") LAST_MSG=$(python -c "import sys,urllib.parse; print(urllib.parse.quote('${LAST_MSG}'))") fi wget -qO- --post-data="version=${VERSION:=NA}&uuid=${UUID}&os=${OS}&mem=${MEM}&rc=${INSTALLATION_STATUS}&msg=${LAST_MSG}&container=${CONTAINER}" https://severalnines.com/service/diag.php &>/dev/null [[ ${INSTALLATION_STATUS} == "1" ]] && echo "Please contact Severalnines support at http://support.severalnines.com if you have installation issues that cannot be resolved." fi } check_os() { dist="Unknown" regex_lsb="Description:[[:space:]]*([^ ]*)" regex_etc="/etc/(.*)[-_]" systemd=0 [[ $(readlink /sbin/init) == *"systemd"* ]] && systemd=1 # install lsb-release on debian|ubuntu if apt-get --version >/dev/null 2>/dev/null; then log_msg "Installing lsb-release ..." if [[ -z ${OFFLINE+x} ]]; then waitForLocks apt-get update -qq fi apt-get install -yq lsb-release fi if command -v lsb_release &>/dev/null; then do_lsb [[ $? -ne 0 ]] && do_release_file else do_release_file fi dist=$(echo $dist | tr '[:upper:]' '[:lower:]') [[ ! -z $CENTOS ]] && dist="centos" case $dist in debian) dist="debian";; ubuntu) dist="debian";; red) dist="redhat";; redhat) dist="redhat";; centos) dist="redhat";; fedora) dist="redhat";; oracle) dist="redhat";; system) dist="redhat" # amazon ami log_msg "This distro is not supported! Supported OS, https://severalnines.com/docs/requirements.html#operating-system" exit 1 ;; # amazon ami *) log_msg "This script couldn't detect a supported distriution (dists parsed: ''$dist')."; ask_generic_distro esac } create_cert() { local domain=*.severalnines.local local commonname=$domain local san=dev.severalnines.local local country=SE local state=Stockholm local locality=Stockholm local organization='Severalnines AB' local organizationalunit=Severalnines local email=support@severalnines.com local keylength=2048 local expires=1825 local keyname=server.key local certname=server.crt local csrname=server.csr cat > /tmp/v3.ext << EOF basicConstraints = CA:FALSE #authorityKeyIdentifier=keyid,issuer keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment extendedKeyUsage = clientAuth, serverAuth subjectAltName = DNS:${san} EOF echo "==> Generating tls certificate for $domain" # ubunutu 18.0x workaround touch $HOME/.rnd openssl genrsa -out $keyname $keylength openssl req -new -key $keyname -out $csrname \ -addext "subjectAltName = DNS:${san}" \ -subj "/C=$country/ST=$state/L=$locality/O=$organization/OU=$organizationalunit/CN=$commonname/emailAddress=$email" &>/dev/null if [[ $? -ne 0 ]]; then # centos 6 -addtext is not avaiable openssl req -new -key $keyname -out $csrname \ -subj "/C=$country/ST=$state/L=$locality/O=$organization/OU=$organizationalunit/CN=$commonname/emailAddress=$email" fi openssl x509 -req -extfile /tmp/v3.ext -days $expires -sha256 -in $csrname -signkey $keyname -out $certname rm -f /tmp/v3.ext cd - &>/dev/null } main() { # check distro check_os if [[ "${dist}" == "redhat" ]]; then command -v chkconfig || dnf -y install chkconfig fi # trap cleanup EXIT INSTALLATION_STATUS=1 INSTALLATION_PROGRESS=1 LAST_MSG="" repo_http_protocol="http" [[ ! -z $REPO_USE_TLS ]] && repo_http_protocol="https" message="This script will add Severalnines repository server for deb and rpm packages and \ \ninstall the ClusterControl Web Applicaiton and Controller. \ \nAn Apache and MySQL server will also be installed. An existing MySQL Server on this host can be used." [[ `whoami` != "root" ]] && echo -e "Do: sudo $(basename $0) or run as root\n" && echo -e "$message" && exit 1 # Check if the OS is a 64 bit one. If it's a 32 bit, the installation fails # with a non'very descriptive message if [[ "$(uname -m)" != 'x86_64' ]]; then log_msg "ClusterControl is only compatible with x86_64 systems" && exit 1 fi if [[ ! -z $NO_INET ]]; then log_msg "" log_msg "Detected NO_INET is set, i.e., OFFLINE install." log_msg "Please follow the instructions in the online manual, https://severalnines.com/docs/installation.html#offline-installation" exit 0 fi while [[ $# > 0 ]]; do arg="$1" case "$arg" in -v|--verbose) PROMPT_USER=1 shift ;; -u|--uninstall) uninstall_packages exit 0 ;; -r|--release) ;; -e|--edge) USE_REPO="edge" ;; -1|--version1) # install only v1 of the frontend # deprecated / not available any longer! echo "CCv1 is no longer available!! Exiting ..." && exit 1 ;; -2|--version2) # install only v2 of the frontend # this is the default UI for now. later mcc should be the default to use CCVERSION=2 ;; -3|--version3) # install CC Ops-C / multi cmon controller support # mcc should be default CCVERSION=3 ;; *) log_msg "Unknown option $arg" exit 1 ;; esac shift done # install CCv2 by default cc_version=${CCVERSION:-2} echo "!!" log_msg "Only RHEL/RockyLinux/AlmaLinux 8|9, Debian 11|12, Ubuntu 20.04|22.04|24.04 LTS versions are supported" # FREE_DISKSPACE=$(df -hP $PWD | awk '/[0-9]%/{print $(NF-2)}' | cut -d'G' -f1 | awk -F '.' '{ print $1 }') # REQ_DISKSPACE=5 # if [[ $FREE_DISKSPACE -lt $REQ_DISKSPACE ]]; then # log_msg "** Less than ${REQ_DISKSPACE}GB disk space left. Stopping ..." # exit 1 # fi cc_api_token=$(cat /proc/sys/kernel/random/uuid | sha1sum | cut -f1 -d' ') CONTAINER="NA" [[ -f /.dockerenv ]] && CONTAINER="docker" grep -qa container=lxc /proc/1/environ &>/dev/null [[ $? -eq 0 ]] && CONTAINER="lxc" running_mysql=1 if [[ -f /usr/libexec/mysqld || -f /usr/sbin/mysqld ]]; then ps -C mysqld &>/dev/null running_mysql=$? elif [[ -f /usr/sbin/mariadbd ]]; then ps -C mariadbd &>/dev/null running_mysql=$? fi if [[ $running_mysql -eq 1 ]]; then MEM_TOTAL=$(free -m | awk '/Mem:/ { print $2}') MEM_FREE=$(free -m | awk '/Mem:/ { print $ 4}') # default 512M if (( ${MEM_TOTAL} < 1500 )); then log_msg "Minimum system requirements: 2GB+ RAM, 2+ CPU cores" log_msg "Server Memory: ${MEM_TOTAL}M total, ${MEM_FREE}M free" INNODB_BUFFER_POOL_SIZE=${INNODB_BUFFER_POOL_SIZE:-512} else SIZE=$((50*${MEM_TOTAL}/100)) INNODB_BUFFER_POOL_SIZE=${INNODB_BUFFER_POOL_SIZE:-${SIZE}} log_msg "System RAM is > 1.5G" log_msg "Setting MySQL innodb_buffer_pool_size to 50% of system RAM" fi log_msg "MySQL innodb_buffer_pool_size set to ${INNODB_BUFFER_POOL_SIZE}M" echo "" MIN_FREE_MEMORY=${MIN_FREE_MEMORY:-100} if (( ${INNODB_BUFFER_POOL_SIZE} + ${MIN_FREE_MEMORY} > ${MEM_FREE} )); then log_msg "You do not have enough free memory ${MEM_FREE}M available to set innodb_buffer_pool_size=${INNODB_BUFFER_POOL_SIZE}M" log_msg "Need at least 100M or more free memory in addition to what innodb_buffer_pool_size will allocate." if [[ -z $S9S_CMON_PASSWORD ]]; then read -p "=> Enter new innodb_buffer_pool_size (in MB, e.g, 512): " x [[ ! -z $x ]] && INNODB_BUFFER_POOL_SIZE=$x log_msg "Using new innodb_buffer_pool_size ${INNODB_BUFFER_POOL_SIZE}M" echo "" else exit 1 fi fi fi log_msg "Severalnines would like your help improving our installation process." log_msg "Information such as OS, memory and install success helps us improve how we onboard our users." log_msg "None of the collected information identifies you personally." log_msg "!!" if ask_p "=> Would you like to help us by sending installation log after the installation? (Y/n): " "y"; then SEND_DIAGNOSTICS=1 fi echo "" [[ ! -z $message ]] && echo -e $message echo "" until set_root_password; do log_msg "" log_msg "Password mismatch! Try again." sleep 1 done [ -z ${root_password+x} ] || S9S_ROOT_PASSWORD="${root_password}" echo "" until set_cmon_password; do log_msg "" log_msg "Password mismatch! Try again." sleep 1 done [ -z ${cmon_password+x} ] || S9S_CMON_PASSWORD="${cmon_password}" if [[ "$dist" == "debian" ]]; then waitForLocks log_msg "Installing required packages ..." apt-get install -yq wget bc gnupg dmidecode case "${os_codename}" in 'focal'|'bionic'|'jammy'|'noble') log_msg "Installing python ..." apt-get install -yq python3 update-alternatives --install /usr/bin/python python /usr/bin/python3 1 ;; *) log_msg "Installing python ..." apt-get install -yq python ;; esac python --version | grep -q 3. [[ $? -eq 0 ]] && PYTHON3=1 if ! command -v add-apt-repository &>/dev/null; then case "${os_codename}" in 'stretch'|'buster'|'bullseye'|'bookworm'|'xenial'|'focal'|'jammy'|'noble') log_msg "Installing software-properties-common ..." apt-get install -yq software-properties-common ;; *) log_msg "Installing python-software-properties ..." apt-get install -yq python-software-properties ;; esac fi fi if [[ "$dist" == "redhat" ]]; then log_msg "Installing required packages ..." yum install -yq wget dmidecode hostname log_msg "Installing python ..." if [[ $rhel_version == "8" ]]; then yum install -y python36 alternatives --set python /usr/bin/python3 else yum install -y python fi python --version | grep -q 3. [[ $? -eq 0 ]] && PYTHON3=1 fi wwwroot="/var/www/html" if [[ "$dist" == "debian" ]]; then wwwroot="/var/www" distro_id=$(lsb_release -s -i) distro_release=$(lsb_release -s -r) if [[ $? -eq 0 ]]; then distro_release=${distro_release%%.*} # Apache 2.4 uses new config if [[ ${distro_id} == "Ubuntu" ]]; then if (( $(echo "$distro_release > 12" | bc) )); then use_apache24=1 fi elif [[ ${distro_id} == "Debian" ]]; then if (( $(echo "$distro_release > 7" | bc) )); then use_apache24=1 fi fi else log_msg "Unable to determine distro release number ..." log_msg "Assuming > apache 2.4..." use_apache24=1 fi [[ ! -z $use_apache24 ]] && wwwroot="/var/www/html" fi ip=($(hostname -I)) || ip=($(hostname -i)) host="${HOST:-${ip[0]}}" [[ ${#ip[@]} > 1 && -z ${HOST} ]] && message="$message\nNOTE: Detected more than one IP: ${ip[@]}\nUsing hostname ${host} or do 'export HOST=<hostname>' to explicitly set a host" echo -e $message if command -v dig &>/dev/null; then echo "" echo "Determining network interfaces ..." ext_ip=$(dig +short myip.opendns.com @resolver1.opendns.com 2>/dev/null) log_msg "Web application IP => https://${ext_ip}" fi if [[ -z ${HOST} ]]; then echo "" if ! ask_p "=> The CMON Controller's hostname will be set to $host. Do you want to change it? (y/N): " "n"; then read -p "=> Enter the hostname: " x [[ ! -z $x ]] && host="$x" fi fi log_msg "Using hostname $host" db_port=${S9S_DB_PORT:-3306} cmon_user=${S9S_CMON_USER:-"cmon"} cmon_password=${S9S_CMON_PASSWORD:-"cmon"} cmon_events_port=${S9S_EVENTS_PORT:-9510} cmon_cloud_port=${S9S_CLOUD_PORT:-9518} root_password=${S9S_ROOT_PASSWORD:-""} echo "" log_msg "Using web document root $wwwroot" mkdir -p $wwwroot install_cmon=0 if ask "=> Install the ClusterControl Controller? (Y/n): " "y"; then install_cmon=1 if [[ -z ${S9S_CMON_PASSWORD} && -f /etc/cmon.cnf ]]; then log_msg "An existing Controller installation detected!" log_msg "A re-installation of the Controller will overwrite the /etc/cmon.cnf file" if ask_p "=> Install the Controller? (y/N): " "n"; then install_cmon=0 fi fi fi if [[ $running_mysql -eq 0 ]]; then if ask_p "=> Detected a running MySQL server. Should I use your existing MySQL server? (Y/n): " "y"; then use_existing_mysql=1 fi fi if [[ -z $use_existing_mysql ]]; then log_msg "Install the default distro MySQL Server ..." fi log_msg "Installing the ClusterControl Web Application ..." # install web app install_frontend track_install_init log_msg "Installing MySQL server ..." track_install "2" install_database_server log_msg "Configuring the MySQL server ..." track_install "3" configure_database_server track_install "4" if [[ ${cc_version} == "2" ]]; then log_msg "Installing Web / Apache server ..." install_web_server configure_ccv2_web log_msg "ClusterControl v2 frontend installed ..." fi # Install Controller if [[ $install_cmon -eq 1 ]]; then track_install "5" install_controller install_s9s_commandline # start events and ssh services start_cmon_services # restart the apache server - this is needed for the rpc_tls.crt [[ -f /var/lib/cmon/ca/cmon/rpc_tls.crt ]] || sleep 5 if [[ ${cc_version} == "2" ]]; then log_msg "Restarting Apache server ..." $restart_apache || log_msg "Unable to restart the Apache Server ..." fi fi if [[ ${cc_version} == "3" ]]; then # Setup CC MCC / Ops-C log_msg "Configuring ClusterControl MCC / Ops-C ..." configure_mcc log_msg "ClusterControl MCC / Ops-C frontend installed ..." fi # success INSTALLATION_STATUS=0 [[ ! -z ${ext_ip} ]] && host="${ext_ip}" log_msg "ClusterControl installation completed!" track_install "6" echo "Open your web browser to https://${host} to start using ClusterControl." echo "If you want to uninstall ClusterControl then please follow the instructions here, https://severalnines.com/docs/administration.html#uninstall" exit 0 } main "$@"