#!/bin/bash # shellcheck disable=SC2155,SC2207,SC2015 ######################################################################### # Title: XenOrchestraInstallerUpdater # # Author: Roni Väyrynen # # Repository: https://github.com/ronivay/XenOrchestraInstallerUpdater # ######################################################################### SCRIPT_DIR="$(dirname "$0")" SAMPLE_CONFIG_FILE="$SCRIPT_DIR/sample.xo-install.cfg" CONFIG_FILE="$SCRIPT_DIR/xo-install.cfg" # Deploy default configuration file if the user doesn't have their own yet. if [[ ! -s "$CONFIG_FILE" ]]; then cp "$SAMPLE_CONFIG_FILE" "$CONFIG_FILE" fi # See this file for all script configuration variables. # shellcheck disable=SC1090 source "$CONFIG_FILE" # Set some default variables if sourcing config file fails for some reason SELFUPGRADE=${SELFUPGRADE:-"true"} PORT=${PORT:-80} INSTALLDIR=${INSTALLDIR:-"/opt/xo"} BRANCH=${BRANCH:-"master"} LOGPATH=${LOGPATH:-$(dirname "$(realpath "$0")")/logs} AUTOUPDATE=${AUTOUPDATE:-"true"} PRESERVE=${PRESERVE:-"3"} XOUSER=${XOUSER:-"root"} CONFIGPATH=$(getent passwd "$XOUSER" | cut -d: -f6) CONFIGPATH_PROXY=$(getent passwd root | cut -d: -f6) PLUGINS="${PLUGINS:-"none"}" ADDITIONAL_PLUGINS="${ADDITIONAL_PLUGINS:-"none"}" REPOSITORY="${REPOSITORY:-"https://github.com/vatesfr/xen-orchestra"}" OS_CHECK="${OS_CHECK:-"true"}" ARCH_CHECK="${ARCH_CHECK:-"true"}" PATH_TO_HTTPS_CERT="${PATH_TO_HTTPS_CERT:-""}" PATH_TO_HTTPS_KEY="${PATH_TO_HTTPS_KEY:-""}" AUTOCERT="${AUTOCERT:-"false"}" USESUDO="${USESUDO:-"false"}" GENSUDO="${GENSUDO:-"false"}" # set variables not changeable in configfile TIME=$(date +%Y%m%d%H%M) LOGTIME=$(date "+%Y-%m-%d %H:%M:%S") LOGFILE="${LOGPATH}/xo-install.log-$TIME" NODEVERSION="14" FORCE="false" INTERACTIVE="false" SUDOERSFILE="/etc/sudoers.d/xo-server-$XOUSER" # Set path where new source is cloned/pulled XO_SRC_DIR="$INSTALLDIR/xo-src/xen-orchestra" # Set variables for stdout print COLOR_N='\e[0m' COLOR_GREEN='\e[1;32m' COLOR_RED='\e[1;31m' COLOR_BLUE='\e[1;34m' COLOR_WHITE='\e[1;97m' OK="[${COLOR_GREEN}ok${COLOR_N}]" FAIL="[${COLOR_RED}fail${COLOR_N}]" INFO="[${COLOR_BLUE}info${COLOR_N}]" PROGRESS="[${COLOR_BLUE}..${COLOR_N}]" # Protocol to use for webserver. If both of the X.509 certificate paths are defined, # then assume that we want to enable HTTPS for the server. if [[ -n "$PATH_TO_HTTPS_CERT" ]] && [[ -n "$PATH_TO_HTTPS_KEY" ]]; then HTTPS=true else HTTPS=false fi # create logpath if doesn't exist if [[ ! -d "$LOGPATH" ]]; then mkdir -p "$LOGPATH" fi function CheckUser { # Make sure the script is ran as root if [[ ! $(runcmd_stdout "id -u") == "0" ]]; then printfail "This script needs to be ran as root" exit 1 fi } # script self upgrade function SelfUpgrade { set -o pipefail if [[ "$SELFUPGRADE" != "true" ]]; then return 0 fi if [[ -d "$SCRIPT_DIR/.git" ]] && [[ -n $(runcmd_stdout "command -v git") ]]; then local REMOTE="$(runcmd_stdout "cd $SCRIPT_DIR && git config --get remote.origin.url")" if [[ "$REMOTE" == *"ronivay/XenOrchestraInstallerUpdater"* ]]; then if [[ -n $(runcmd_stdout "cd $SCRIPT_DIR && git status --porcelain") ]]; then printfail "Local changes in this script directory. Not attempting to self upgrade" return 0 fi runcmd "cd $SCRIPT_DIR && git fetch" local OLD_SCRIPT_VERSION="$(runcmd_stdout "cd $SCRIPT_DIR && git rev-parse --short HEAD")" local NEW_SCRIPT_VERSION="$(runcmd_stdout "cd $SCRIPT_DIR && git rev-parse --short FETCH_HEAD")" if [[ $(runcmd_stdout "cd $SCRIPT_DIR && git diff --name-only @{upstream}| grep xo-install.sh") ]]; then printinfo "Newer version of script available, attempting to self upgrade from '$OLD_SCRIPT_VERSION' to '$NEW_SCRIPT_VERSION'" runcmd "cd $SCRIPT_DIR && git pull --ff-only" && { printok "Self upgrade done" exec "$SCRIPT_DIR/xo-install.sh" "$@" } || printfail "Failed to self upgrade. Check $LOGFILE for more details. Continuing with current version" fi fi fi } # log script version (git commit) and configuration variables to logfile function ScriptInfo { set -o pipefail local SCRIPTVERSION=$(cd "$SCRIPT_DIR" 2>/dev/null && git rev-parse --short HEAD 2>/dev/null) [ -z "$SCRIPTVERSION" ] && SCRIPTVERSION="undefined" echo "Running script version $SCRIPTVERSION with config:" >>"$LOGFILE" echo >>"$LOGFILE" [ -s "$CONFIG_FILE" ] && grep -Eo '^[A-Z_]+.*' "$CONFIG_FILE" >>"$LOGFILE" || echo "No config file found" >>"$LOGFILE" echo >>"$LOGFILE" } # log actual command and it's stderr/stdout to logfile in one go function runcmd { echo "+ $1" >>"$LOGFILE" bash -c -o pipefail "$1" >>"$LOGFILE" 2>&1 } # log actual command and it's stderr to logfile in one go function runcmd_stdout { echo "+ $1" >>"$LOGFILE" # shellcheck disable=SC2094 bash -c -o pipefail "$1" 2>>"$LOGFILE" | tee -a "$LOGFILE" } # make output we print pretty function printprog { echo -ne "${PROGRESS} $*" } function printok { # shellcheck disable=SC1117 echo -e "\r${OK} $*" } function printfail { echo -e "${FAIL} $*" } function printinfo { echo -e "${INFO} $*" } # if script fails at a stage where installation is not complete, we don't want to keep the install specific directory and content # this is called by trap inside different functions function ErrorHandling { set -eu echo printfail "Something went wrong, exiting. Check $LOGFILE for more details and use rollback feature if needed" if [[ -d "$INSTALLDIR/xo-builds/xen-orchestra-$TIME" ]]; then echo printfail "Removing $INSTALLDIR/xo-builds/xen-orchestra-$TIME because of failed installation." runcmd "rm -rf $INSTALLDIR/xo-builds/xen-orchestra-$TIME" echo fi exit 1 } # install package dependencies to rpm distros, based on: https://xen-orchestra.com/docs/from_the_sources.html function InstallDependenciesRPM { set -uo pipefail trap ErrorHandling ERR INT # Install necessary dependencies for XO build # only install epel-release if doesn't exist if [[ -z $(runcmd_stdout "rpm -qa epel-release") ]]; then echo printprog "Installing epel-repo" runcmd "yum -y install epel-release" printok "Installing epel-repo" fi # install packages echo printprog "Installing build dependencies, redis server, python, git, nfs-utils, cifs-utils, lvm2, ntfs-3g" runcmd "yum -y install gcc gcc-c++ make openssl-devel redis libpng-devel python3 git nfs-utils cifs-utils lvm2 ntfs-3g" printok "Installing build dependencies, redis server, python, git, nfs-utils, cifs-utils, lvm2, ntfs-3g" # only run automated node install if executable not found if [[ -z $(runcmd_stdout "command -v node") ]]; then echo printprog "Installing node.js" runcmd "curl -s -L https://rpm.nodesource.com/setup_${NODEVERSION}.x | bash -" printok "Installing node.js" else UpdateNodeYarn fi # only install yarn repo and package if not found if [[ -z $(runcmd_stdout "command -v yarn") ]]; then echo printprog "Installing yarn" runcmd "curl -s -o /etc/yum.repos.d/yarn.repo https://dl.yarnpkg.com/rpm/yarn.repo && yum -y install yarn" printok "Installing yarn" fi # only install libvhdi-tools if vhdimount is not present if [[ -z $(runcmd_stdout "command -v vhdimount") ]]; then echo printprog "Installing libvhdi-tools from forensics repository" runcmd "rpm -ivh https://forensics.cert.org/cert-forensics-tools-release-el8.rpm" runcmd "sed -i 's/enabled=1/enabled=0/g' /etc/yum.repos.d/cert-forensics-tools.repo" runcmd "yum --enablerepo=forensics install -y libvhdi-tools" printok "Installing libvhdi-tools from forensics repository" fi echo printprog "Enabling and starting redis service" runcmd "/bin/systemctl enable redis && /bin/systemctl start redis" printok "Enabling and starting redis service" echo printprog "Enabling and starting rpcbind service" runcmd "/bin/systemctl enable rpcbind && /bin/systemctl start rpcbind" printok "Enabling and starting rpcbind service" } # install package dependencies to deb distros, based on: https://xen-orchestra.com/docs/from_the_sources.html function InstallDependenciesDeb { set -uo pipefail trap ErrorHandling ERR INT # Install necessary dependencies for XO build if [[ "$OSNAME" == "Ubuntu" ]]; then echo printprog "OS Ubuntu so making sure universe repository is enabled" runcmd "apt-get install -y software-properties-common" runcmd "add-apt-repository -y universe" printok "OS Ubuntu so making sure universe repository is enabled" fi echo printprog "Running apt-get update" runcmd "apt-get update" printok "Running apt-get update" #determine which python package is needed. Ubuntu 20/Debian 11 require python2-minimal, others have python-minimal if [[ "$OSNAME" =~ ^(Ubuntu|Debian)$ ]] && [[ "$OSVERSION" =~ ^(20|11)$ ]]; then local PYTHON="python2-minimal" else local PYTHON="python-minimal" fi # install packages echo printprog "Installing build dependencies, redis server, python, git, libvhdi-utils, lvm2, nfs-common, cifs-utils, curl, ntfs-3g" runcmd "apt-get install -y build-essential redis-server libpng-dev git libvhdi-utils $PYTHON lvm2 nfs-common cifs-utils curl ntfs-3g" printok "Installing build dependencies, redis server, python, git, libvhdi-utils, lvm2, nfs-common, cifs-utils, curl, ntfs-3g" # Install apt-transport-https and ca-certificates because of yarn https repo url echo printprog "Installing apt-transport-https and ca-certificates packages to support https repos" runcmd "apt-get install -y apt-transport-https ca-certificates" printok "Installing apt-transport-https and ca-certificates packages to support https repos" if [[ "$OSNAME" == "Debian" ]] && [[ "$OSVERSION" =~ ^(10|11)$ ]]; then echo printprog "Debian 10/11, so installing gnupg also" runcmd "apt-get install gnupg -y" printok "Debian 10/11, so installing gnupg also" fi # install setcap for non-root port binding if missing if [[ -z $(runcmd_stdout "command -v setcap") ]]; then echo printprog "Installing setcap" runcmd "apt-get install -y libcap2-bin" printok "Installing setcap" fi # only run automated node install if executable not found if [[ -z $(runcmd_stdout "command -v node") ]] || [[ -z $(runcmd_stdout "command -v npm") ]]; then echo printprog "Installing node.js" runcmd "curl -sL https://deb.nodesource.com/setup_${NODEVERSION}.x | bash -" runcmd "apt-get install -y nodejs" printok "Installing node.js" else UpdateNodeYarn fi # only install yarn repo and package if not found if [[ -z $(runcmd_stdout "command -v yarn") ]]; then echo printprog "Installing yarn" runcmd "curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -" runcmd "echo \"deb https://dl.yarnpkg.com/debian/ stable main\" | tee /etc/apt/sources.list.d/yarn.list" runcmd "apt-get update" runcmd "apt-get install -y yarn" printok "Installing yarn" fi echo printprog "Enabling and starting redis service" runcmd "/bin/systemctl enable redis-server && /bin/systemctl start redis-server" printok "Enabling and starting redis service" echo printprog "Enabling and starting rpcbind service" runcmd "/bin/systemctl enable rpcbind && /bin/systemctl start rpcbind" printok "Enabling and starting rpcbind service" } # keep node.js and yarn up to date function UpdateNodeYarn { # user has an option to disable this behaviour in xo-install.cfg if [[ "$AUTOUPDATE" != "true" ]]; then return 0 fi echo printinfo "Checking current node.js version" local NODEV=$(runcmd_stdout "node -v 2>/dev/null| grep -Eo '[0-9.]+' | cut -d'.' -f1") if [ "$PKG_FORMAT" == "rpm" ]; then if [[ -n "$NODEV" ]] && [[ "$NODEV" -lt "${NODEVERSION}" ]]; then echo printprog "node.js version is $NODEV, upgrading to ${NODEVERSION}.x" runcmd "curl -sL https://rpm.nodesource.com/setup_${NODEVERSION}.x | bash -" runcmd "yum clean all" runcmd "yum install -y nodejs" printok "node.js version is $NODEV, upgrading to ${NODEVERSION}.x" else if [[ "$TASK" == "Update" ]]; then echo printprog "node.js version already on $NODEV, checking updates" runcmd "yum update -y nodejs yarn" printok "node.js version already on $NODEV, checking updates" elif [[ "$TASK" == "Installation" ]]; then echo printinfo "node.js version already on $NODEV" fi fi fi if [ "$PKG_FORMAT" == "deb" ]; then if [[ -n "$NODEV" ]] && [[ "$NODEV" -lt "${NODEVERSION}" ]]; then echo printprog "node.js version is $NODEV, upgrading to ${NODEVERSION}.x" runcmd "curl -sL https://deb.nodesource.com/setup_${NODEVERSION}.x | bash -" runcmd "apt-get install -y nodejs" printok "node.js version is $NODEV, upgrading to ${NODEVERSION}.x" else if [[ "$TASK" == "Update" ]]; then echo printprog "node.js version already on $NODEV, checking updates" runcmd "apt-get update" runcmd "apt-get install -y --only-upgrade nodejs yarn" printok "node.js version already on $NODEV, checking updates" elif [[ "$TASK" == "Installation" ]]; then echo printinfo "node.js version already on $NODEV" fi fi fi } # get source code for 3rd party plugins if any configured in xo-install.cfg function InstallAdditionalXOPlugins { set -uo pipefail trap ErrorHandling ERR INT if [[ -z "$ADDITIONAL_PLUGINS" ]] || [[ "$ADDITIONAL_PLUGINS" == "none" ]]; then echo printinfo "No 3rd party plugins to install" return 0 fi echo printprog "Fetching 3rd party plugin(s) source code" # shellcheck disable=SC1117 local ADDITIONAL_PLUGIN_REGEX="^https?:\/\/.*.git$" local ADDITIONAL_PLUGIN IFS=',' read -ra ADDITIONAL_PLUGIN <<<"$ADDITIONAL_PLUGINS" for x in "${ADDITIONAL_PLUGIN[@]}"; do if ! [[ $x =~ $ADDITIONAL_PLUGIN_REGEX ]]; then echo printfail "$x format is not correct for 3rd party plugin, skipping.." continue fi local PLUGIN_NAME=$(runcmd_stdout "basename '$x' | rev | cut -c 5- | rev") local PLUGIN_SRC_DIR=$(runcmd_stdout "realpath -m '$XO_SRC_DIR/../$PLUGIN_NAME'") if [[ ! -d "$PLUGIN_SRC_DIR" ]]; then runcmd "mkdir -p \"$PLUGIN_SRC_DIR\"" runcmd "git clone \"${x}\" \"$PLUGIN_SRC_DIR\"" else runcmd "cd \"$PLUGIN_SRC_DIR\" && git pull --ff-only" runcmd "cd $SCRIPT_DIR" fi runcmd "cp -r $PLUGIN_SRC_DIR $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/" done printok "Fetching 3rd party plugin(s) source code" } # symlink plugins in place based on what is set in xo-install.cfg function InstallXOPlugins { set -uo pipefail trap ErrorHandling ERR INT if [[ -z "$PLUGINS" ]] || [[ "$PLUGINS" == "none" ]]; then echo printinfo "No plugins to install" return 0 fi echo printprog "Installing plugins" if [[ "$PLUGINS" == "all" ]]; then # shellcheck disable=SC1117 runcmd "find \"$INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/\" -maxdepth 1 -mindepth 1 -not -name \"xo-server\" -not -name \"xo-web\" -not -name \"xo-server-cloud\" -exec ln -sn {} \"$INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/xo-server/node_modules/\" \;" else local PLUGIN IFS=',' read -ra PLUGIN <<<"$PLUGINS" for x in "${PLUGIN[@]}"; do if [[ $(runcmd_stdout "find $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages -type d -name '$x'") ]]; then runcmd "ln -sn $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/$x $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/xo-server/node_modules/" fi done fi printok "Installing plugins" } # install sudo package and generate config if defined in configuration function InstallSudo { set -uo pipefail trap ErrorHandling ERR INT if [[ -z $(runcmd_stdout "command -v sudo") ]]; then if [[ "$PKG_FORMAT" == "deb" ]]; then echo printprog "Installing sudo" runcmd "apt-get install -y sudo" printok "Installing sudo" elif [[ "$PKG_FORMAT" == "rpm" ]]; then printprog "Installing sudo" runcmd "yum install -y sudo" printok "Installing sudo" fi fi if [[ "$GENSUDO" == "true" ]] && [[ ! -f "$SUDOERSFILE" ]]; then echo printinfo "Generating sudoers configuration to $SUDOERSFILE" TMPSUDOERS="$(mktemp /tmp/xo-sudoers.XXXXXX)" runcmd "echo '$XOUSER ALL=(root) NOPASSWD: /bin/mount, /bin/umount' > '$TMPSUDOERS'" if runcmd "visudo -cf $TMPSUDOERS"; then runcmd "mv $TMPSUDOERS $SUDOERSFILE" else printfail "sudoers syntax check failed, not activating $SUDOERSFILE" runcmd "rm -f $TMPSUDOERS" fi fi } function PrepInstall { if [[ "$XO_SVC" == "xo-server" ]]; then local XO_SVC_DESC="Xen Orchestra" fi if [[ "$XO_SVC" == "xo-proxy" ]]; then local XO_SVC_DESC="Xen Orchestra Proxy" fi # Create installation directory if doesn't exist already if [[ ! -d "$INSTALLDIR" ]]; then echo printinfo "Creating missing basedir to $INSTALLDIR" runcmd "mkdir -p \"$INSTALLDIR\"" fi # Create missing xo-builds directory if doesn't exist already if [[ ! -d "$INSTALLDIR/xo-builds" ]]; then echo printinfo "Creating missing xo-builds directory to $INSTALLDIR/xo-builds" runcmd "mkdir \"$INSTALLDIR/xo-builds\"" fi echo # keep the actual source code in one directory and either clone or git pull depending on if directory exists already printinfo "Fetching $XO_SVC_DESC source code" if [[ ! -d "$XO_SRC_DIR" ]]; then runcmd "mkdir -p \"$XO_SRC_DIR\"" runcmd "git clone \"${REPOSITORY}\" \"$XO_SRC_DIR\"" else runcmd "cd \"$XO_SRC_DIR\" && git pull --ff-only" runcmd "cd $SCRIPT_DIR" fi # Deploy the latest xen-orchestra source to the new install directory. echo printinfo "Creating install directory: $INSTALLDIR/xo-builds/xen-orchestra-$TIME" runcmd "rm -rf \"$INSTALLDIR/xo-builds/xen-orchestra-$TIME\"" runcmd "cp -r \"$XO_SRC_DIR\" \"$INSTALLDIR/xo-builds/xen-orchestra-$TIME\"" # get the latest available tagged release if branch is set to release. this is to make configuration more simple to user if [[ "$BRANCH" == "release" ]]; then runcmd "cd $INSTALLDIR/xo-builds/xen-orchestra-$TIME" local TAG=$(runcmd_stdout "git describe --tags '$(git rev-list --tags --max-count=1)'") echo printinfo "Checking out latest tagged release '$TAG'" runcmd "cd $INSTALLDIR/xo-builds/xen-orchestra-$TIME && git checkout $TAG" runcmd "cd $SCRIPT_DIR" # we don't need to do much magic if specific branch set so just checkout elif [[ "$BRANCH" != "master" ]]; then echo printinfo "Checking out source code from branch/commit '$BRANCH'" runcmd "cd $INSTALLDIR/xo-builds/xen-orchestra-$TIME && git checkout $BRANCH" runcmd "cd $SCRIPT_DIR" fi # Check if the new repo is any different from the currently-installed # one. If not, then skip the build and delete the repo we just cloned. # Get the commit ID of the to-be-installed xen-orchestra. local NEW_REPO_HASH=$(runcmd_stdout "cd $INSTALLDIR/xo-builds/xen-orchestra-$TIME && git rev-parse HEAD") local NEW_REPO_HASH_SHORT=$(runcmd_stdout "cd $INSTALLDIR/xo-builds/xen-orchestra-$TIME && git rev-parse --short HEAD") runcmd "cd $SCRIPT_DIR" # Get the commit ID of the currently-installed xen-orchestra (if one # exists). if [[ -L "$INSTALLDIR/$XO_SVC" ]] && [[ -n $(runcmd_stdout "readlink -e $INSTALLDIR/$XO_SVC") ]]; then local OLD_REPO_HASH=$(runcmd_stdout "cd $INSTALLDIR/$XO_SVC && git rev-parse HEAD") local OLD_REPO_HASH_SHORT=$(runcmd_stdout "cd $INSTALLDIR/$XO_SVC && git rev-parse --short HEAD") runcmd "cd $SCRIPT_DIR" else # If there's no existing installation, then we definitely want # to proceed with the bulid. local OLD_REPO_HASH="" local OLD_REPO_HASH_SHORT="" fi # If the new install is no different from the existing install, then don't # proceed with the build. if [[ "$NEW_REPO_HASH" == "$OLD_REPO_HASH" ]] && [[ "$FORCE" != "true" ]]; then echo # if any non interactive arguments used in script startup, we don't want to show any prompts if [[ "$INTERACTIVE" == "true" ]]; then printinfo "No changes to $XO_SVC_DESC since previous install. Run update anyway?" read -r -p "[y/N]: " answer answer="${answer:-n}" case "$answer" in y) : ;; n) printinfo "Cleaning up install directory: $INSTALLDIR/xo-builds/xen-orchestra-$TIME" runcmd "rm -rf $INSTALLDIR/xo-builds/xen-orchestra-$TIME" exit 0 ;; esac else printinfo "No changes to $XO_SVC_DESC since previous install. Skipping build. Use the --force to update anyway." printinfo "Cleaning up install directory: $INSTALLDIR/xo-builds/xen-orchestra-$TIME" runcmd "rm -rf $INSTALLDIR/xo-builds/xen-orchestra-$TIME" exit 0 fi fi # If this isn't a fresh install, then list the upgrade the user is making. if [[ -n "$OLD_REPO_HASH" ]]; then echo if [[ "$FORCE" != "true" ]]; then printinfo "Updating $XO_SVC_DESC from '$OLD_REPO_HASH_SHORT' to '$NEW_REPO_HASH_SHORT'" echo "Updating $XO_SVC_DESC from '$OLD_REPO_HASH_SHORT' to '$NEW_REPO_HASH_SHORT'" >>"$LOGFILE" else printinfo "Updating $XO_SVC_DESC (forced) from '$OLD_REPO_HASH_SHORT' to '$NEW_REPO_HASH_SHORT'" echo "Updating $XO_SVC_DESC (forced) from '$OLD_REPO_HASH_SHORT' to '$NEW_REPO_HASH_SHORT'" >>"$LOGFILE" fi else echo printinfo "Installing $XO_SVC_DESC from branch: $BRANCH - commit: $NEW_REPO_HASH_SHORT" echo "Installing $XO_SVC_DESC from branch: $BRANCH - commit: $NEW_REPO_HASH_SHORT" >>"$LOGFILE" TASK="Installation" fi } # run actual xen orchestra installation. procedure is the same for new installation and update. we always build it from scratch. function InstallXO { set -uo pipefail trap ErrorHandling ERR INT # Create user if doesn't exist (if defined) if [[ "$XOUSER" != "root" ]]; then if [[ -z $(runcmd_stdout "getent passwd $XOUSER") ]]; then echo printprog "Creating missing $XOUSER user" runcmd "useradd -s /sbin/nologin $XOUSER -m" printok "Creating missing $XOUSER user" CONFIGPATH=$(getent passwd "$XOUSER" | cut -d: -f6) fi if [[ "$USESUDO" == "true" ]]; then InstallSudo fi fi PrepInstall # Now that we know we're going to be building a new xen-orchestra, make # sure there's no already-running xo-server process. if [[ $(runcmd_stdout "pgrep -f xo-server") ]]; then echo printprog "Shutting down xo-server" runcmd "/bin/systemctl stop xo-server" || { printfail "failed to stop service, exiting..." exit 1 } printok "Shutting down xo-server" fi # Fetch 3rd party plugins source code InstallAdditionalXOPlugins echo printinfo "xo-server and xo-web build takes quite a while. Grab a cup of coffee and lay back" echo printprog "Running installation" runcmd "cd $INSTALLDIR/xo-builds/xen-orchestra-$TIME && yarn && yarn build" printok "Running installation" # Install plugins (takes care of 3rd party plugins as well) InstallXOPlugins echo printinfo "Fixing binary path in systemd service configuration file" # shellcheck disable=SC1117 runcmd "sed -i \"s#ExecStart=.*#ExecStart=$INSTALLDIR\/xo-server\/dist\/cli.mjs#\" $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/xo-server/xo-server.service" printinfo "Adding WorkingDirectory parameter to systemd service configuration file" # shellcheck disable=SC1117 runcmd "sed -i \"/ExecStart=.*/a WorkingDirectory=$INSTALLDIR/xo-server\" $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/xo-server/xo-server.service" # if service not running as root, we need to deal with the fact that port binding might not be allowed if [[ "$XOUSER" != "root" ]]; then printinfo "Adding user to systemd config" # shellcheck disable=SC1117 runcmd "sed -i \"/SyslogIdentifier=.*/a User=$XOUSER\" $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/xo-server/xo-server.service" if [ "$PORT" -le "1024" ]; then local NODEBINARY=$(runcmd_stdout "command -v node") if [[ -L "$NODEBINARY" ]]; then local NODEBINARY=$(runcmd_stdout "readlink -e $NODEBINARY") fi if [[ -n "$NODEBINARY" ]]; then printprog "Attempting to set cap_net_bind_service permission for $NODEBINARY" runcmd "setcap 'cap_net_bind_service=+ep' $NODEBINARY" && printok "Attempting to set cap_net_bind_service permission for $NODEBINARY" || { printfail "Attempting to set cap_net_bind_service permission for $NODEBINARY" echo " Non-privileged user might not be able to bind to <1024 port. xo-server won't start most likely" } else printfail "Can't find node executable, or it's a symlink to non existing file. Not trying to setcap. xo-server won't start most likely" fi fi fi # fix to prevent older installations to not update because systemd service is not symlinked anymore if [[ $(runcmd_stdout "find /etc/systemd/system -maxdepth 1 -type l -name 'xo-server.service'") ]]; then runcmd "rm -f /etc/systemd/system/xo-server.service" fi printinfo "Replacing systemd service configuration file" # always replace systemd service configuration if it changes in future updates runcmd "/bin/cp -f $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/xo-server/xo-server.service /etc/systemd/system/xo-server.service" sleep 2 printinfo "Reloading systemd configuration" runcmd "/bin/systemctl daemon-reload" sleep 2 # if xen orchestra configuration file doesn't exist or configuration update is not disabled in xo-install.cfg, we create it if [[ ! -f "$CONFIGPATH/.config/xo-server/config.toml" ]] || [[ "$CONFIGUPDATE" == "true" ]]; then echo printinfo "Fixing relative path to xo-web installation in xo-server configuration file" # shellcheck disable=SC1117 runcmd "sed -i \"s%#'/any/url' = '/path/to/directory'%'/' = '$INSTALLDIR/xo-web/dist/'%\" $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/xo-server/sample.config.toml" sleep 2 if [[ "$PORT" != "80" ]]; then printinfo "Changing port in xo-server configuration file" # shellcheck disable=SC1117 runcmd "sed -i \"s/port = 80/port = $PORT/\" $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/xo-server/sample.config.toml" sleep 2 fi if [[ "$HTTPS" == "true" ]]; then printinfo "Enabling HTTPS in xo-server configuration file" # shellcheck disable=SC1117 runcmd "sed -i \"s%# cert = '.\/certificate.pem'%cert = '$PATH_TO_HTTPS_CERT'%\" $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/xo-server/sample.config.toml" # shellcheck disable=SC1117 runcmd "sed -i \"s%# key = '.\/key.pem'%key = '$PATH_TO_HTTPS_KEY'%\" $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/xo-server/sample.config.toml" if [[ "$AUTOCERT" == "true" ]]; then # shellcheck disable=SC1117 runcmd "sed -i \"s%# autoCert = false%autoCert = true%\" $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/xo-server/sample.config.toml" fi sleep 2 fi if [[ "$USESUDO" == "true" ]] && [[ "$XOUSER" != "root" ]]; then printinfo "Enabling useSudo in xo-server configuration file" runcmd "sed -i \"s/#useSudo = false/useSudo = true/\" $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/xo-server/sample.config.toml" printinfo "Changing default mountsDir in xo-server configuration file" runcmd "sed -i \"s%#mountsDir.*%mountsDir = '$INSTALLDIR/mounts'%\" $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/xo-server/sample.config.toml" runcmd "mkdir -p $INSTALLDIR/mounts" runcmd "chown -R $XOUSER:$XOUSER $INSTALLDIR/mounts" fi printinfo "Activating modified configuration file" runcmd "mkdir -p $CONFIGPATH/.config/xo-server" runcmd "mv -f $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/xo-server/sample.config.toml $CONFIGPATH/.config/xo-server/config.toml" fi echo # install/update is the same procedure so always symlink to most recent installation printinfo "Symlinking fresh xo-server install/update to $INSTALLDIR/xo-server" runcmd "ln -sfn $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/xo-server $INSTALLDIR/xo-server" sleep 2 printinfo "Symlinking fresh xo-web install/update to $INSTALLDIR/xo-web" runcmd "ln -sfn $INSTALLDIR/xo-builds/xen-orchestra-$TIME/packages/xo-web $INSTALLDIR/xo-web" # if not running as root, xen orchestra startup might not be able to create data directory so we create it here just in case if [[ "$XOUSER" != "root" ]]; then runcmd "chown -R $XOUSER:$XOUSER $INSTALLDIR/xo-builds/xen-orchestra-$TIME" if [ ! -d /var/lib/xo-server ]; then runcmd "mkdir /var/lib/xo-server" fi runcmd "chown -R $XOUSER:$XOUSER /var/lib/xo-server" runcmd "chown -R $XOUSER:$XOUSER $CONFIGPATH/.config/xo-server" fi echo printinfo "Starting xo-server..." runcmd "/bin/systemctl start xo-server" # no need to exit/trap on errors anymore set +eo pipefail trap - ERR INT VerifyServiceStart } function VerifyServiceStart { set -u if [[ "$XO_SVC" == "xo-proxy" ]]; then local PORT="443" fi PROXY_CONFIG_UPDATED=${PROXY_CONFIG_UPDATED:-"false"} # loop service logs for 60 seconds and look for line that indicates service was started. we only care about lines generated after script was started (LOGTIME) local count=0 local limit=6 # shellcheck disable=SC1117 local servicestatus="$(runcmd_stdout "journalctl --since '$LOGTIME' -u $XO_SVC | grep 'Web server listening on https\{0,1\}:\/\/.*:$PORT'")" while [[ -z "$servicestatus" ]] && [[ "$count" -lt "$limit" ]]; do echo " waiting for port to be open" sleep 10 # shellcheck disable=SC1117 local servicestatus="$(runcmd_stdout "journalctl --since '$LOGTIME' -u $XO_SVC | grep 'Web server listening on https\{0,1\}:\/\/.*:$PORT'")" ((count++)) done # if it looks like service started successfully based on logs.. if [[ -n "$servicestatus" ]]; then echo if [[ "$XO_SVC" == "xo-server" ]]; then echo -e " ${COLOR_GREEN}WebUI started in port $PORT. Make sure you have firewall rules in place to allow access.${COLOR_N}" # print username and password only when install was ran and skip while updating if [[ "$TASK" == "Installation" ]]; then echo -e " ${COLOR_GREEN}Default username: admin@admin.net password: admin${COLOR_N}" fi fi if [[ "$XO_SVC" == "xo-proxy" ]]; then echo -e " ${COLOR_GREEN}Proxy started in port $PORT. Make sure you have firewall rules in place to allow access from xen orchestra.${COLOR_N}" # print json config only when install was ran and skip while Updating if [[ "$TASK" == "Installation" ]] && [[ "$PROXY_CONFIG_UPDATED" == "true" ]]; then echo -e " ${COLOR_GREEN}Save following line as json file and use config import in Xen Orchestra to add proxy${COLOR_N}" echo echo "{\"proxies\":[{\"authenticationToken\":\"${PROXY_TOKEN}\",\"name\":\"${PROXY_NAME}\",\"vmUuid\":\"${PROXY_VM_UUID}\",\"id\":\"${PROXY_RANDOM_UUID}\"}]}" fi fi echo printinfo "$TASK successful. Enabling $XO_SVC service to start on reboot" echo "" >>"$LOGFILE" echo "$TASK succesful" >>"$LOGFILE" runcmd "/bin/systemctl enable $XO_SVC" echo # if service startup failed... else echo printfail "$TASK completed, but looks like there was a problem when starting $XO_SVC. Check $LOGFILE for more details" # shellcheck disable=SC2129 echo "" >>"$LOGFILE" echo "$TASK failed" >>"$LOGFILE" echo "$XO_SVC service log:" >>"$LOGFILE" echo "" >>"$LOGFILE" runcmd "journalctl --since '$LOGTIME' -u $XO_SVC >> $LOGFILE" echo echo "Control $SERVICE service with systemctl for stop/start/restart etc." exit 1 fi } # run xen orchestra installation but also cleanup old installations based on value in xo-install.cfg function UpdateXO { if [[ "$XO_SVC" == "xo-server" ]]; then InstallXO fi if [[ "$XO_SVC" == "xo-proxy" ]]; then InstallXOProxy fi set -uo pipefail if [[ "$PRESERVE" == "0" ]]; then printinfo "PRESERVE variable is set to 0. This needs to be at least 1. Not doing a cleanup" return 0 fi # remove old builds. leave as many as defined in PRESERVE variable printprog "Removing old inactive installations after update. Leaving $PRESERVE latest" local INSTALLATIONS="$(runcmd_stdout "find $INSTALLDIR/xo-builds/ -maxdepth 1 -type d -name \"xen-orchestra*\" -printf \"%T@ %p\\n\" | sort -n | cut -d' ' -f2- | head -n -$PRESERVE")" local XO_SERVER_ACTIVE="$(runcmd_stdout "readlink -e $INSTALLDIR/xo-server")" local XO_WEB_ACTIVE="$(runcmd_stdout "readlink -e $INSTALLDIR/xo-web")" local XO_PROXY_ACTIVE="$(runcmd_stdout "readlink -e $INSTALLDIR/xo-proxy")" for DELETABLE in $INSTALLATIONS; do if [[ "$XO_SERVER_ACTIVE" != "${DELETABLE}"* ]] && [[ "$XO_WEB_ACTIVE" != "${DELETABLE}"* ]] && [[ "$XO_PROXY_ACTIVE" != "${DELETABLE}"* ]]; then runcmd "rm -rf $DELETABLE" fi done printok "Removing old inactive installations after update. Leaving $PRESERVE latest" echo } function InstallXOProxy { set -uo pipefail PrepInstall # check that xo-proxy is not running if [[ $(runcmd_stdout "pgrep -f xo-proxy") ]]; then echo printprog "Shutting down xo-proxy" runcmd "/bin/systemctl stop xo-proxy" || { printfail "failed to stop service, exiting..." exit 1 } printok "Shutting down xo-proxy" fi echo printinfo "xo-proxy build takes quite a while. Grab a cup of coffee and lay back" echo printprog "Running installation" runcmd "cd $INSTALLDIR/xo-builds/xen-orchestra-$TIME && yarn && yarn build" runcmd "cd $INSTALLDIR/xo-builds/xen-orchestra-$TIME/@xen-orchestra/proxy && yarn cross-env NODE_ENV=development yarn run _build" printok "Running installation" echo printinfo "Generate systemd service configuration file" cat </etc/systemd/system/xo-proxy.service [Unit] Description=xo-proxy After=network-online.target [Service] ExecStart=$INSTALLDIR/xo-proxy/dist/index.mjs Restart=always SyslogIdentifier=xo-proxy [Install] WantedBy=multi-user.target EOF printinfo "Reloading systemd configuration" runcmd "/bin/systemctl daemon-reload" # if xen orchestra proxy configuration file doesn't exist or configuration update is not disabled in xo-install.cfg, we create it if [[ ! -f "$CONFIGPATH_PROXY/.config/xo-proxy/config.toml" ]]; then PROXY_VM_UUID="$(dmidecode -t system | grep UUID | awk '{print $NF}')" PROXY_RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" PROXY_TOKEN="$(tr -dc A-Z-a-z0-9_-