#!/bin/bash if [[ $EUID -ne 0 ]]; then echo "This script must be run as root" >&2 exit 1 fi # -------------------------------------------------------------------------- # # Copyright 2002-2026, OpenNebula Project, OpenNebula Systems # # # # Licensed under the Apache License, Version 2.0 (the "License"); you may # # not use this file except in compliance with the License. You may obtain # # a copy of the License at # # # # http://www.apache.org/licenses/LICENSE-2.0 # # # # Unless required by applicable law or agreed to in writing, software # # distributed under the License is distributed on an "AS IS" BASIS, # # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # # See the License for the specific language governing permissions and # # limitations under the License. # #--------------------------------------------------------------------------- # # default parameters values VERSION='7.4' EE='no' FORCE='no' VERBOSE='no' ASK='yes' PASSWORD=$(tr /dev/null) LIBVIRTD='libvirtd' ONE_WAIT_TIMEOUT=60 IMAGE_WAIT_TIMEOUT=300 STAR_NET='' AUGEAS_PKG='augeas-tools' PIP='pip3' PYTHON_PIP='python3-pip' REPO_BASE='' HAVE_CURL=false HAVE_WGET=false INSTALL_TERRAFORM=false TERRAFORM_ARCH='amd64' [ "$(arch)" = 'aarch64' ] && TERRAFORM_ARCH='arm64' TERRAFORM_URL="https://releases.hashicorp.com/terraform/1.6.3/terraform_1.6.3_linux_${TERRAFORM_ARCH}.zip" ETH0_IP='' PUBLIC_IP='' ONE_SERVICES=(opennebula opennebula-fireedge opennebula-flow opennebula-gate) ONE_FE_PKGS=(opennebula opennebula-common opennebula-common-onecfg opennebula-flow opennebula-fireedge opennebula-gate opennebula-libs opennebula-rubygems opennebula-tools opennebula-guacd) NODE_KVM_PKG='opennebula-node-kvm' REPO_KEY='repo2.key' SSHD_SERVICE='sshd' while true; do case "$1" in -v | --verbose) VERBOSE="yes" shift ;; -f | --force) FORCE="yes" shift ;; --help) usage exit 0 ;; --yes) ASK="no" shift ;; --frontend) NODE="no" NETWORKING="no" shift ;; --enterprise) EE='yes' EE_TOKEN="$2" shift 2 ;; --version) VERSION="$2" VERSION_GIVEN="yes" shift 2 ;; --ssh-pubkey) SSH_PUBKEY="$2" shift 2 ;; --password) PASSWORD="$2" shift 2 ;; --bridge-interface) BRIDGE_INTERFACE="$2" shift 2 ;; --nat-interface) NAT_INTERFACE="$2" shift 2 ;; --vnet-address) VNET_ADDRESS="$2" shift 2 ;; --vnet-netmask) VNET_NETMASK="$2" shift 2 ;; --vnet-gateway) VNET_GATEWAY="$2" shift 2 ;; --vnet-ar-ip-start) VNET_AR_IP_START="$2" shift 2 ;; --vnet-ar-ip-count) VNET_AR_IP_COUNT="$2" shift 2 ;; --marketapp-name) MARKET_APP_NAME="$2" shift 2 ;; --vm-password) VM_PASSWORD="$2" shift 2 ;; --lxc) LXC="yes" MARKET_APP_NAME="alpine_edge - LXD" shift ;; --sunstone-port) SUNSTONE_PORT="$2" shift 2 ;; --purge) PURGE="yes" shift ;; --preserve-user) DELETE_ONEADMIN="no" shift ;; --repo-base) REPO_BASE="$2" shift 2 ;; --db) DB_BACKEND="$2" shift 2 ;; --) shift break ;; *) usage exit 1 ;; esac done # Don't ask if there is no TTY on stdin [[ ! -t 0 ]] && ASK='no' # 172.16.0.0 ~> 172.16.*.*, but 10.0.1.0~> 10.0.1.* STAR_NET=${VNET_ADDRESS} for I in 1 2 3 4; do # shellcheck disable=SC2001 STAR_NET=$(echo "${STAR_NET}" | sed -e 's/\(.*\)\.0\([0\.\*]*\)$/\1.*\2/') done # compare version strings verlte() { [[ $1. =~ ^v*([0-9][0-9]*\.){1,3}$ ]] || return 1 [[ $2. =~ ^v*([0-9][0-9]*\.){1,3}$ ]] || return 1 [ "$1" = "$(echo -e "$1\n$2" | sort -V | head -n1)" ] } #------------------------------------------------------------------------------- # Options validation #------------------------------------------------------------------------------- if [[ "$DB_BACKEND" != 'sqlite' && "$DB_BACKEND" != 'mysql' ]]; then echo "Invalid --db value '${DB_BACKEND}', expected 'sqlite' or 'mysql'" >&2 exit 1 fi if [[ $EE == yes ]]; then REPO_URL="https://${EE_TOKEN}@enterprise.opennebula.io/repo" fi if [[ -z "$REPO_BASE" ]]; then REPO_BASE="$REPO_URL"/"$VERSION" else VERSION=$(echo "$REPO_BASE" | grep -oE 'build/[0-9]+\.[0-9]+(\.[0-9]+)?' | sed 's/build\///') fi #------------------------------------------------------------------------------- # Helpers and detection functions #------------------------------------------------------------------------------- title() { echo "" echo "### $*" } interface_exists() { local DEV=$1 ip link show dev "$DEV" >/dev/null } get_interface_name() { DEV=$(ip route | grep default | head -1 | awk '{print $5}' 2>/dev/null) if [[ -z "${DEV}" ]]; then DEV=$(ip addr | grep '^[0-9]' | awk -F": " '{print $2}' | head -2 | tail -1 2>/dev/null) fi echo "$DEV" } get_my_ip() { DEV=$(get_interface_name) IP=$(ip addr show dev "${DEV}" | grep 'inet ' | head -1 | awk '{print $2}') echo "${IP//\/[0-9]*/}" } get_public_ip() { dig +short myip.opendns.com @resolver1.opendns.com 2>/dev/null } get_distname_and_version() { local DIST local VER if type -t lsb_release >/dev/null; then DIST=$(lsb_release -si 2>/dev/null) VER=$(lsb_release -sr 2>/dev/null) elif [ -f /etc/redhat-release ]; then DIST=$(cut -d ' ' -f1 "${STDERR_TMP_FILE}" >"${STDOUT_TMP_FILE}" RC=$? if [ $RC -gt 0 ]; then [[ "$ON_FAIL" = "" && $TRIES -gt 1 ]] && echo -ne "retry $I " sleep 1 fi I=$((I + 1)) done STDERR=$(cat "$STDERR_TMP_FILE") STDOUT=$(cat "$STDOUT_TMP_FILE") unlink "${STDERR_TMP_FILE}" unlink "${STDOUT_TMP_FILE}" if [[ ${RC} = '0' ]]; then [[ ${VERBOSE} = 'yes' ]] && green "OK" return ${RC} else [[ ${VERBOSE} = 'no' ]] && echo -ne "${TEXT} " if [[ "$ON_FAIL" =~ "SKIP" ]]; then orange "${ON_FAIL}" return ${RC} elif [[ ${FORCE} = 'no' && -n "${ON_FAIL}" ]]; then red "FAILED" if [[ -n "${HINT}" ]]; then echo "${HINT}" else echo 'Consider running with "--force" to override' fi exit 1 elif [[ ${FORCE} = 'yes' && "${ON_FAIL}" =~ "IGNORE" ]]; then orange "${ON_FAIL}" return ${RC} else red "FAILED" if [[ -n "${ON_FAIL}" && ! "${ON_FAIL}" =~ "IGNORE" ]]; then echo "${ON_FAIL}" fi echo "${STDOUT}" if [[ -n "${STDERR}" ]]; then echo "--- STDERR ---" echo "${STDERR}" echo "--------------" fi exit 1 fi fi } fail() { echo -ne "$1 " red "FAILED" exit 1 } run_and_print_if_failed() { eval "$1" >/dev/null local RC=$? local MSG=$* [ ! "$RC" -eq 0 ] && echo "$MSG" return "$RC" } centos() { [[ "$DISTNAME" =~ RedHat|AlmaLinux ]] } el10() { [[ "$DISTNAME" =~ RedHat|AlmaLinux ]] && [[ "$DISTVER" == "10" ]] } redhat() { [[ "$DISTNAME" =~ RedHat ]] } debian() { [[ "$DISTNAME" =~ Ubuntu|Debian ]] } suse() { [[ "$DISTNAME" =~ SLES|openSUSE ]] } opensuse() { [[ "$DISTNAME" =~ openSUSE ]] } # Distributions where the node-kvm package switches to the modular libvirt # daemons (virtqemud, ...) instead of the monolithic libvirtd. Only the # RPM-based distros ship the modular daemons; Debian/Ubuntu keep the # monolithic libvirtd (their libvirt-daemon-driver-* packages are loadable # modules of libvirtd, not standalone daemons). Keep in sync with the # OpenNebula node-kvm packaging. libvirt_modular() { suse && return 0 centos && [[ "$DISTVER" -ge 9 ]] && return 0 return 1 } firewalld_running() { systemctl -q is-active firewalld } unattended_upgrades_present() { local UNIT for UNIT in "${APT_PERIODIC_UNITS[@]}"; do systemctl cat "$UNIT" >/dev/null 2>&1 && return 0 done return 1 } netplan_on() { type -t netplan >/dev/null || return 1 # Ubuntu 26.04 installs ifupdown too, leaving an ifstate holding just "lo=lo" ls /etc/netplan/*.yaml >/dev/null 2>&1 && return 0 [ ! -s /run/network/ifstate ] } node() { [[ $NODE == yes ]] } db_mysql() { [[ $DB_BACKEND == mysql ]] } aws() { # try detect AWS instance by seeing 'Server: EC2ws' in headers type -t curl >/dev/null || return 1 curl --connect-timeout 1 http://169.254.169.254 -v 2>&1 | grep -q EC2 } kvm() { [[ $NODE == yes && $LXC == no ]] } lxc() { [[ $NODE == yes && $LXC == yes ]] } networking() { [[ $NETWORKING == yes ]] } # encode MAJOR.MINOR[.PATCH] as an integer for range comparison, rounding # odd (pre-release) minors up to the final they lead to: 7.3.85 -> 704 version_key() { local MAJOR MINOR IFS=. read -r MAJOR MINOR _ <<< "$1" (( MINOR % 2 )) && MINOR=$((MINOR + 1)) echo $(( MAJOR * 100 + MINOR )) } supported_dist_ver() { local MIN MAX RANGE=${SUPPORTED[${DISTNAME}${DISTVER}]-} if [[ -z $RANGE ]]; then echo "Unsupported distribution: ${DISTNAME}${DISTVER}" >&2 return 2 fi read -r MIN MAX <<< "$RANGE" if (( $(version_key "$VERSION") < $(version_key "$MIN") || $(version_key "$VERSION") > $(version_key "$MAX") )); then echo "\"${DISTNAME}${DISTVER} ${VERSION}\" out of supported range ${MIN}-${MAX}" >&2 return 2 fi } repo_exists() { if [[ ! "$DISTNAME" =~ AlmaLinux|RedHat|Ubuntu|Debian|SLES|openSUSE ]]; then echo "Currently only RedHat, AlmaLinux, Ubuntu, Debian, SLES or openSUSE are supported" >&2 return 1 else URL="${REPO_BASE}/${DISTNAME}/${DISTVER}" if type -t curl >/dev/null; then HAVE_CURL=true # use -K to avoid leaking EE token in process list local CURL_CFG CURL_CFG=$(mktemp) || return 2 printf 'url = "%s"\n-f\n-s\n-S\n' "$URL" >"$CURL_CFG" curl -K "$CURL_CFG" >/dev/null local RC=$? rm -f "$CURL_CFG" return "$RC" elif type -t wget >/dev/null; then HAVE_WGET=true # use --input-file to avoid leaking EE token in process list echo "$URL" | wget -q --input-file=- -O /dev/null else echo "Missing curl/wget to check repository" >&2 return 2 fi fi } disk_free() { local LIMIT=$1 local WHERE=$2 read -r AVAIL TARGET <<<"$(df -BG --output=avail,target "$WHERE" | tail -1)" AVAIL=${AVAIL%G} if [[ "${AVAIL}" -lt "${LIMIT}" ]]; then echo "Insufficient disk space, expected at least ${LIMIT}G on" \ "\"${TARGET}\" filesystem" return 1 fi } #------------------------------------------------------------------------------- # Install functions #------------------------------------------------------------------------------- configure_selinux() { # Keep SELinux enforcing but grant what OpenNebula + libvirt/QEMU need: # label the image datastores so the svirt-confined QEMU can access them. local DS='/var/lib/one/datastores' [[ -d "$DS" ]] || return 0 semanage fcontext -a -t virt_image_t "${DS}(/.*)?" 2>/dev/null || semanage fcontext -m -t virt_image_t "${DS}(/.*)?" || return 1 restorecon -R "$DS" || return 1 # Allow QEMU/libvirt to use the storage backends OpenNebula relies on. setsebool -P virt_use_nfs on 2>/dev/null || true } modify_apparmor() { if [[ "${1:-}" = 'purge' ]]; then sed -i '/\/var\/lib\/one\/datastores/d' /etc/apparmor.d/abstractions/libvirt-qemu >/dev/null 2>&1 else if ! grep '/var/lib/one/datastores' /etc/apparmor.d/abstractions/libvirt-qemu >/dev/null 2>&1; then echo ' /var/lib/one/datastores/** rwk,' >>/etc/apparmor.d/abstractions/libvirt-qemu systemctl reload apparmor fi fi } install_pkg() { if centos; then run_and_print_if_failed "yum -y install $*" elif debian; then export DEBIAN_FRONTEND=noninteractive run_and_print_if_failed "apt-get -q -y install $*" RC=$? unset DEBIAN_FRONTEND return "$RC" elif suse; then run_and_print_if_failed "zypper --non-interactive install $*" fi } create_bridge() { if centos && [[ $DISTVER -le 8 ]]; then if [[ $1 = purge ]]; then rm -f /etc/sysconfig/network-scripts/ifcfg-"${BRIDGE_INTERFACE}" ip link set down dev "${BRIDGE_INTERFACE}" || true ip link del "${BRIDGE_INTERFACE}" || true else cat >/etc/sysconfig/network-scripts/ifcfg-"${BRIDGE_INTERFACE}" </etc/sysconfig/network/ifcfg-"${BRIDGE_INTERFACE}" </etc/systemd/network/"${BRIDGE_INTERFACE}"-nic.netdev </etc/netplan/minione.yaml </dev/null cat >/etc/network/interfaces.d/tap.cfg </etc/network/interfaces.d/"${BRIDGE_INTERFACE}".cfg <>"$FILE" fi fi fi fi } ifup_bridge() { if suse && [[ "$DISTVER" -lt 16 ]]; then wicked ifup "${BRIDGE_INTERFACE}" elif suse; then true # not needed when NM is used elif netplan_on; then netplan apply else debian && ifup tap0 if type -t ifup >/dev/null; then ifup "${BRIDGE_INTERFACE}" else true # not needed when NM is used fi fi } configure_firewalld() { if [[ "${1:-}" = 'purge' ]]; then if networking; then firewall-cmd --permanent --remove-masquerade >/dev/null 2>&1 || true firewall-cmd --permanent --zone=trusted \ --remove-interface="${BRIDGE_INTERFACE}" >/dev/null 2>&1 || true fi for P in "${FIREWALL_PORTS[@]}"; do firewall-cmd --permanent --remove-port="$P" >/dev/null 2>&1 || true done firewall-cmd --reload >/dev/null 2>&1 || true return 0 fi # Open OpenNebula management ports (SSH, Sunstone/FireEdge, OneGate, OneFlow) for P in "${FIREWALL_PORTS[@]}"; do firewall-cmd --permanent --add-port="$P" >/dev/null || return 1 done # Private VM network: trust the bridge so VMs reach host services # (DNS, OneGate, ...) and masquerade their egress over the NAT interface. if networking; then firewall-cmd --permanent --zone=trusted \ --change-interface="${BRIDGE_INTERFACE}" >/dev/null || return 1 firewall-cmd --permanent --add-masquerade >/dev/null || return 1 fi firewall-cmd --reload >/dev/null || return 1 } apt_locks_free() { local LOCK for LOCK in /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock \ /var/lib/apt/lists/lock /var/cache/apt/archives/lock; do [[ -e "$LOCK" ]] || continue fuser "$LOCK" >/dev/null 2>&1 && return 1 done return 0 } # Stop and mask everything which may run apt/dpkg on the background. disable_unattended_upgrades() { local UNIT I for UNIT in "${APT_PERIODIC_UNITS[@]}"; do systemctl cat "$UNIT" >/dev/null 2>&1 || continue if systemctl -q is-enabled "$UNIT" 2>/dev/null; then ENABLED_APT_UNITS+=("$UNIT") fi MASKED_APT_UNITS+=("$UNIT") systemctl stop "$UNIT" >/dev/null 2>&1 systemctl mask "$UNIT" >/dev/null 2>&1 done # nothing new can start now, let any already running upgrade finish for I in $(seq 120); do apt_locks_free && return 0 sleep 5 done echo "Timed out waiting for the APT/dpkg locks to be released" >&2 return 1 } enable_unattended_upgrades() { local UNIT for UNIT in "${MASKED_APT_UNITS[@]}"; do systemctl unmask "$UNIT" >/dev/null 2>&1 done for UNIT in "${ENABLED_APT_UNITS[@]}"; do # --now on the oneshot .service units would block until apt-daily runs if [[ "$UNIT" == *.timer ]]; then systemctl enable --now "$UNIT" >/dev/null 2>&1 else systemctl enable "$UNIT" >/dev/null 2>&1 fi done MASKED_APT_UNITS=() ENABLED_APT_UNITS=() } configure_nat() { if el10; then if [[ $1 != 'purge' ]]; then run_and_print_if_failed "nft add table ip nat" run_and_print_if_failed "nft 'add chain ip nat postrouting { type nat hook postrouting priority 100 ; }'" run_and_print_if_failed "nft add rule ip nat postrouting ip saddr ${VNET_ADDRESS}/${NETMASK_BITS} ip daddr != ${VNET_ADDRESS}/${NETMASK_BITS} masquerade" else run_and_print_if_failed "nft delete table ip nat" fi else ACTION='-A' [[ $1 = 'purge' ]] && ACTION='-D' IPTABLES_COMMAND=$( cat </dev/null cat </etc/dnsmasq.conf || return 1 interface=${BRIDGE_INTERFACE},lo bind-interfaces EOT systemctl start dnsmasq systemctl enable dnsmasq fi } configure_repos() { if centos; then if [[ $1 = 'purge' ]]; then rm -f /etc/yum.repos.d/opennebula.repo else cat </etc/yum.repos.d/opennebula.repo [opennebula] name=opennebula baseurl=${REPO_BASE}/${DISTNAME}/${DISTVER}/$(arch) enabled=1 gpgkey=https://downloads.opennebula.io/repo/${REPO_KEY} gpgcheck=1 EOT fi elif debian; then if [[ $1 = 'purge' ]]; then rm -f /etc/apt/sources.list.d/opennebula.list rm -f /etc/apt/preferences.d/opennebula-nodejs else # Check if /etc/apt/keyrings directory exists, if not, create it if [ ! -d /etc/apt/keyrings ]; then install -m 0755 -d /etc/apt/keyrings fi (wget -q -O- https://downloads.opennebula.io/repo/"${REPO_KEY}" | gpg --dearmor --yes --output /etc/apt/keyrings/opennebula.gpg) || return 1 echo "deb [signed-by=/etc/apt/keyrings/opennebula.gpg] ${REPO_BASE}/${DISTNAME}/${DISTVER} stable opennebula" \ >/etc/apt/sources.list.d/opennebula.list || return 1 # Ubuntu 26.04 ships Node.js 22 in its base repos, but FireEdge needs # Node.js 20 from the OpenNebula repo. Pin it so apt prefers our v20. if [[ "${DISTNAME}" == Ubuntu ]] && verlte 26.04 "${DISTVER}"; then cat </etc/apt/preferences.d/opennebula-nodejs || return 1 Package: nodejs Pin: release o=OpenNebula Pin-Priority: 600 EOT fi fi elif suse; then if [[ $1 = 'purge' ]]; then zypper removerepo opennebula 2>/dev/null || true if opensuse; then zypper removerepo science 2>/dev/null || true; fi rm -f /etc/zypp/credentials.d/opennebula.conf else if [[ $EE == yes ]]; then local SUSE_EE_BASE # shellcheck disable=SC2001 SUSE_EE_BASE=$(echo "$REPO_BASE" | sed 's|//[^@]*@|//|') mkdir -p /etc/zypp/credentials.d cat </etc/zypp/credentials.d/opennebula.conf username=${EE_TOKEN%%:*} password=${EE_TOKEN#*:} EOT chmod 600 /etc/zypp/credentials.d/opennebula.conf zypper ar -f "${SUSE_EE_BASE}/${DISTNAME}/${DISTVER}/$(arch)?credentials=opennebula.conf" opennebula || true else zypper ar -f "${REPO_BASE}/${DISTNAME}/${DISTVER}/$(arch)" opennebula || true fi if opensuse; then local LEAP_VER LEAP_VER=$(grep ^VERSION_ID= /etc/os-release | cut -d\" -f2) zypper ar -f "https://download.opensuse.org/repositories/science/${LEAP_VER}/" science || true fi rpm --import "https://downloads.opennebula.io/repo/${REPO_KEY}" || return 1 zypper --gpg-auto-import-keys refresh || return 1 fi fi } enable_sles_modules() { local SP_VER SP_VER=$(grep ^VERSION_ID= /etc/os-release | cut -d\" -f2) local ARCH ARCH=$(uname -m) SUSEConnect -p "PackageHub/${SP_VER}/${ARCH}" || return 1 SUSEConnect -p "sle-module-public-cloud/${SP_VER}/${ARCH}" || return 1 SUSEConnect -p "sle-module-desktop-applications/${SP_VER}/${ARCH}" || return 1 } enable_epel() { if redhat; then dnf install -y "https://dl.fedoraproject.org/pub/epel/epel-release-latest-${DISTVER}.noarch.rpm" elif centos; then if [[ "${DISTNAME}" =~ AlmaLinux ]] && [[ "${DISTVER}" -ge 9 ]]; then dnf config-manager --set-enabled crb || return 1 fi install_pkg "epel-release" fi } install_opennebula_pkgs() { install_pkg "${ONE_FE_PKGS[@]}" || return 1 systemctl daemon-reload } install_opennebula_kvm_pkgs() { install_pkg "$NODE_KVM_PKG" || return 1 if el10; then install_pkg "kernel-modules-extra-$(uname -r)" || return 1 fi } install_opennebula_lxc_pkgs() { install_pkg opennebula-node-lxc || return 1 } uninstall_opennebula_pkgs() { if centos; then yum --quiet -y remove "$NODE_KVM_PKG" "${ONE_FE_PKGS[@]}" systemctl restart "${LIBVIRTD}" || true if redhat; then yum --quiet -y remove qemu-kvm-rhev || true else yum --quiet -y remove qemu-kvm-ev >/dev/null || true fi elif debian; then apt-get purge -q -y "${ONE_FE_PKGS[@]}" >/dev/null apt-get purge -q -y "$NODE_KVM_PKG" >/dev/null systemctl restart "${LIBVIRTD}" || true dpkg-statoverride --remove /var/lib/one || true elif suse; then zypper --non-interactive remove "$NODE_KVM_PKG" "${ONE_FE_PKGS[@]}" || true systemctl restart "${LIBVIRTD}" || true fi } detect_installed_version() { if centos || suse; then if RPM_STR=$(rpm -q opennebula 2>/dev/null); then VERSION=$(echo "$RPM_STR" | awk -F- '{print $3}' | cut -d. -f1,2) else return 1 fi else if DEB_STR=$(dpkg -s opennebula 2>/dev/null | grep '^Version:'); then VERSION=$(echo "$DEB_STR" | awk '{print $2}' | cut -d. -f1,2) else return 1 fi fi } gen_ssh_key() { local KEY=${1:-$HOME/.ssh/id_rsa} mkdir -p "$(dirname "$KEY")" chmod 0700 "$(dirname "$KEY")" if [ ! -f "$KEY" ]; then ssh-keygen -t rsa -P "" -f "$KEY" >/dev/null fi } install_terraform() { curl --retry 3 -s -o /tmp/terraform.zip "$TERRAFORM_URL" || return 1 unzip /tmp/terraform.zip -d /usr/bin || return 1 rm /tmp/terraform.zip } # Run the MariaDB client as root reading SQL from stdin # (unix_socket auth works for root on a fresh server) mariadb_root() { if type -t mariadb >/dev/null; then mariadb else mysql fi } install_mariadb() { install_pkg "$MARIADB_PKG" || return 1 systemctl enable "$MARIADB_SERVICE" >/dev/null 2>&1 systemctl start "$MARIADB_SERVICE" || return 1 } configure_mysql_db() { mariadb_root </dev/null || type -t mysql >/dev/null || return 0 systemctl -q is-active "$MARIADB_SERVICE" || return 0 mariadb_root </dev/null 2>&1 || true } # Initialize some usefull vars NETMASK_BITS=$(mask2cidr "${VNET_NETMASK}") read -r DISTNAME DISTVER <<<"$(get_distname_and_version)" (centos || suse) && AUGEAS_PKG='augeas' libvirt_modular && LIBVIRTD='virtqemud' # Database backend package/service (MariaDB) MARIADB_SERVICE='mariadb' if suse; then MARIADB_PKG='mariadb' else MARIADB_PKG='mariadb-server' fi # OpenNebula management ports opened in firewalld: # SSH, Sunstone/FireEdge, OneGate, OneFlow FIREWALL_PORTS=(22/tcp "${SUNSTONE_PORT}/tcp" 5030/tcp 2474/tcp) # Units able to run apt/dpkg on the background, paused during the installation APT_PERIODIC_UNITS=( unattended-upgrades.service apt-daily-upgrade.timer apt-daily-upgrade.service apt-daily.timer apt-daily.service ) MASKED_APT_UNITS=() ENABLED_APT_UNITS=() if [[ "$PURGE" = "yes" ]] && [[ "$VERSION_GIVEN" != "yes" ]]; then check "detect_installed_version" "Detecting ONE installed version" 1 \ "SKIP Will try to uninstall default $VERSION" fi PUBLIC_IP=$(get_public_ip) ETH0_IP=$(get_my_ip) # FRONTEND + KVM NODE if node; then ONEGATE_ENDPOINT=$VNET_GATEWAY ONEGATE_SERVER=$VNET_GATEWAY REPORT_IP=$ETH0_IP # FRONTEND + PREPARE FIREEDGE, ONEPROVISION else ONEGATE_ENDPOINT=${PUBLIC_IP:-$ETH0_IP} ONEGATE_SERVER=$ETH0_IP REPORT_IP=${PUBLIC_IP:-$ETH0_IP} fi # On AWS always report PUBLIC_IP, if exists if [[ -n "$PUBLIC_IP" ]] && aws; then REPORT_IP=$PUBLIC_IP fi # OpenNebula 7.3+ ships the Kubernetes backend (opennebula-ks) also in CE. # VMs then reach OneGate (and oned) through a transparent proxy listening # on the link-local address 169.254.16.9 inside the virtual network. KS='no' if verlte 7.2.80 "$VERSION"; then KS='yes' node && ONEGATE_ENDPOINT='169.254.16.9' fi lxc && MARKET_APP_NAME='alpine' if [[ "${DISTNAME}" == Ubuntu ]] && verlte 24.04 "${DISTVER}"; then SSHD_SERVICE='ssh' fi #------------------------------------------------------------------------------- # Uninstall #------------------------------------------------------------------------------- purge() { echo "Really uninstall? [yes/no]:" [[ "${ASK}" = 'yes' ]] && yes_no FORCE='yes' title "Uninstalling" check "systemctl stop opennebula" "Stopping OpenNebula" 1 "SKIP" db_mysql && check "drop_mysql_db" "Dropping MySQL database" 1 "SKIP" [[ "${ENABLED_APPARMOR}" = 'yes' ]] && check "modify_apparmor purge" "Restoring AppArmor" check "uninstall_opennebula_pkgs" "Uninstalling OpenNebula packages" 1 "SKIP" check "start_dnsmasq purge" "Stopping DNSMasq" 1 "SKIP" check "configure_repos purge" "Unconfiguring repositories" if (centos || suse) && firewalld_running; then check "configure_firewalld purge" "Reverting firewalld configuration" 1 "SKIP" else check "configure_nat purge" "Unconfiguring NAT" 1 "SKIP" fi check "create_bridge purge" "Deleting bridge interface ${BRIDGE_INTERFACE}" check "rm -rf /etc/one $HOME/.one >/dev/null" "Deleting /etc/one" check "rm -rf /var/log/one >/dev/null" "Deleting /var/log/one" if [[ "${DELETE_ONEADMIN}" = 'yes' ]]; then check "userdel -r -f oneadmin>/dev/null" "Deleting oneadmin user" 1 "SKIP" check "rm -rf /var/lib/one >/dev/null" "Deleting /var/lib/one" fi } if [[ $PURGE = 'yes' ]]; then VERBOSE='yes' purge exit 0 fi clean() { [[ -d /var/lib/one/.one ]] && check \ 'rm -rf /var/lib/one/.one.old; mv /var/lib/one/.one /var/lib/one/.one.old' \ 'Move old oneadmin auth-dir away' [[ -f /var/lib/one/one.db ]] && check \ 'mv /var/lib/one/one.db /var/lib/one/one.db.old' \ "Move old db away" db_mysql && check "drop_mysql_db" "Dropping old MySQL database" 1 "SKIP" } #------------------------------------------------------------------------------- # Checks & detection #------------------------------------------------------------------------------- title "Checks & detection" # check Opennebula version & distribution & version check "supported_dist_ver" \ "Checking distribution and version [${DISTNAME} ${DISTVER} ${VERSION}]" \ 1 "IGNORED Will try to install if repository exists" # check if repository exists check "repo_exists" "Checking if OpenNebula repository exists" 3 # check cpu flgas for virtualizaton capabilities node && { check 'grep flags /proc/cpuinfo | grep vmx\\\|svm > /dev/null' \ "Checking cpu virtualization capabilities" 1 \ "SKIP QEMU will be used" || LOCALHOST_VM_MAD='qemu' } check "type -t augtool >/dev/null" "Checking augeas is installed" 1 \ "SKIP will try to install" || MISSING_PKGS="${MISSING_PKGS} $AUGEAS_PKG" check "type -t curl >/dev/null" "Checking curl is installed" 1 \ "SKIP will try to install" || MISSING_PKGS="${MISSING_PKGS} curl" # check available disk space on /var check 'disk_free 20 /var' 'Checking free disk space' 1 'IGNORE' # check existing directories from previous installation check "[[ ! -e /etc/one && ! -e /var/lib/one ]]" \ "Checking directories from previous installation" 1 \ "IGNORED will be cleaned" || CLEAN='yes' # check existing user from previous installation check "! id oneadmin >/dev/null" \ "Checking user from previous installation" 1 "IGNORE" # check if sshd service is running check "systemctl status -n0 ${SSHD_SERVICE} >/dev/null" \ "Checking ${SSHD_SERVICE} service is running" # check if we have networking tools networking && { if el10; then check "type -t nft >/dev/null" "Checking nftables are installed" 1 \ "SKIP will try to install" || MISSING_PKGS="${MISSING_PKGS} nftables iptables" else check "type -t iptables >/dev/null" "Checking iptables are installed" 1 \ "SKIP will try to install" || MISSING_PKGS="${MISSING_PKGS} iptables" fi if [[ "${DISTNAME}${DISTVER}" =~ RedHat8 ]]; then check "rpm -q network-scripts >/dev/null" "Checking network-scripts are installed" 1 \ "SKIP will try to install" || MISSING_PKGS="${MISSING_PKGS} network-scripts" fi } debian && { # check if we have apt-transport-https check "dpkg -L apt-transport-https >/dev/null 2>&1" \ "Checking apt-transport-https is installed" 1 \ "SKIP will try to install" || MISSING_PKGS="${MISSING_PKGS} apt-transport-https" # check if gnupg is installed check "dpkg -L gnupg >/dev/null 2>&1" \ "Checking if gnupg is installed" 1 \ "SKIP will try to install" || MISSING_PKGS="${MISSING_PKGS} gnupg" # check if ca-certificates is up-to-date check "[[ -z \"$(apt list --upgradable 2>&1 | grep ca-certificates)\" ]]" \ "Checking if ca-certificates is up-to-date" 1 \ "SKIP will try to update" || MISSING_PKGS="${MISSING_PKGS} ca-certificates" } # Check SELinux or AppArmor # SUSE 16+ uses SELinux, SUSE 15 uses AppArmor SELINUX=$(getenforce 2>/dev/null) if centos || (suse && [[ "$DISTVER" -ge 16 ]]); then # Keep SELinux enforcing and configure the contexts OpenNebula needs # instead of disabling it. if [[ "${SELINUX}" = 'Enforcing' ]]; then CONFIGURE_SELINUX='yes' # semanage lives in policycoreutils-python-utils on RHEL-family; # only auto-install there to avoid a wrong package name on SUSE. centos && { check "type -t semanage >/dev/null" \ "Checking semanage is installed" 1 "SKIP will try to install" || MISSING_PKGS="${MISSING_PKGS} policycoreutils-python-utils"; } fi fi (debian || (suse && [[ "$DISTVER" -lt 16 ]])) && { check "! aa-status >/dev/null 2>&1" \ "Checking AppArmor" 1 "SKIP will try to modify" || ENABLED_APPARMOR='yes'; } # check for given ssh key if [[ -n "${SSH_PUBKEY}" ]]; then check "[[ -f \"${SSH_PUBKEY}\" ]]" \ "Checking ssh pub key ${SSH_PUBKEY} exists" # or take the first founc, or generate else SSH_PUBKEY=$(get_first_ssh_key) if ! check "[[ -f \"${SSH_PUBKEY}\" ]]" "Checking for present ssh key" 1 \ "SKIP"; then check "gen_ssh_key" "Generating ssh keypair in $HOME/.ssh/id_rsa" SSH_PUBKEY="$HOME/.ssh/id_rsa.pub" fi fi if [[ "${DISTNAME}${DISTVER}" = "RedHat8" ]]; then check "verlte 1.8.5 $(rpm -q --qf '%{VERSION}' libgcrypt)" \ "Checking libgcrypt version" 1 \ "SKIP will try to update" || MISSING_PKGS="${MISSING_PKGS} libgcrypt" fi networking && { # check if given interface exists if [[ -n "${NAT_INTERFACE}" ]]; then check "interface_exists ${NAT_INTERFACE}" \ "Checking [${NAT_INTERFACE}] net device exists" else NAT_INTERFACE=$(get_interface_name) check "[[ -n \"${NAT_INTERFACE}\" ]]" \ "Checking local interface [${NAT_INTERFACE}]" fi # check if we have iptables-persistent and netfilter-persistent if debian; then check "type -t iptables-persistent > /dev/null" \ "Checking iptables-persistent is installed" 1 \ "SKIP will try to install" || MISSING_PKGS="${MISSING_PKGS} iptables-persistent" check "type -t netfilter-persistent > /dev/null" \ "Checking netfilter-persistent is installed" 1 \ "SKIP will try to install" || MISSING_PKGS="${MISSING_PKGS} netfilter-persistent" fi #check if birdge iface is not already present check "! interface_exists ${BRIDGE_INTERFACE}" \ "Checking $BRIDGE_INTERFACE interface is not present" 1 "IGNORED" # check if given virtual network is already in routing table check "! ip route show ${VNET_ADDRESS}/${NETMASK_BITS} | grep dev >/dev/null" \ "Checking virtual network ${VNET_ADDRESS}/${NETMASK_BITS} is not routed" } # check if the requested app name exists on market place if kvm; then if $HAVE_CURL; then check "curl -s -H \"${JSON_HEADERS}\" ${APPS_URL} \ | grep '\"name\":\"${MARKET_APP_NAME}\"' >/dev/null" \ "Checking presence of the market app: \"$MARKET_APP_NAME\"" 3 "Not found" elif $HAVE_WGET; then check "wget --quiet -O - --header \"${JSON_HEADERS}\" ${APPS_URL} \ | grep '\"name\":\"${MARKET_APP_NAME}\"' >/dev/null" \ "Checking presence of the market app: \"$MARKET_APP_NAME\"" 3 "Not found" else # Always fail, but continue with info when --force was given check "false" "Missing curl/wget to check market app" 1 "IGNORE Can't check" fi fi # Terraform and Ansible are not needed since OpenNebula 7.2 if ! verlte 7.2 "$VERSION"; then # Install newer then system ansible using PIP for some distros or for <=6.8 if [[ "${DISTNAME}${DISTVER}" =~ Ubuntu2204|Debian10 ]] || verlte "$VERSION" 6.8; then check "type -t $PIP >/dev/null" "Checking ${PYTHON_PIP} is installed" 1 \ "SKIP will try to install" || MISSING_PKGS="${MISSING_PKGS} ${PYTHON_PIP}" if type -t ansible >/dev/null; then ANSIBLE_VERSION=$(ansible --version | head -1 | tr -cd '[:digit:]' | cut -c 1-3) check "[ $ANSIBLE_VERSION -ge 215 ]" "Checking ansible version (2.15+)" 1 \ "SKIP will try to install" || MISSING_PIP_PKGS="'ansible==8.7.0'" else check "false" "Checking ansible" 1 "SKIP will try to install" || MISSING_PIP_PKGS="'ansible==8.7.0'" fi fi if type -t terraform >/dev/null; then TERRAFORM_VERSION=$(terraform --version | head -1 | cut -d ' ' -f 2) check "verlte v0.13.6 $TERRAFORM_VERSION" \ "Checking terraform version (>= v0.13.6)" else check "false" "Checking terraform" 1 "SKIP will try to install" INSTALL_TERRAFORM=true check "type -t unzip >/dev/null" "Checking unzip is installed" 1 \ "SKIP will try to install" || MISSING_PKGS="${MISSING_PKGS} unzip" fi fi #------------------------------------------------------------------------------- # Pre-installation report #------------------------------------------------------------------------------- title "Main deployment steps:" echo "Install OpenNebula frontend version ${VERSION}" $INSTALL_TERRAFORM && echo "Install Terraform" db_mysql && echo "Install and configure MariaDB (MySQL backend)" (centos || suse) && firewalld_running && echo "Configure firewalld (kept enabled)" debian && unattended_upgrades_present && echo "Pause unattended-upgrades during install (re-enabled afterwards)" node && { echo "Configure bridge ${BRIDGE_INTERFACE} with IP ${VNET_GATEWAY}/${NETMASK_BITS}" echo "Enable NAT over ${NAT_INTERFACE}" [[ "${ENABLED_APPARMOR}" = yes ]] && echo "Modify AppArmor" kvm && echo "Install OpenNebula KVM node" lxc && echo "Install OpenNebula LXC node" echo "Export appliance and update VM template" } [[ "${CLEAN}" = yes ]] && echo "Clean oneadmin home" [[ "${CONFIGURE_SELINUX}" = yes ]] && echo "Configure SELinux (kept enforcing)" [[ -n "${MISSING_PKGS}" ]] && echo "Install ${MISSING_PKGS}" [[ -n "${MISSING_PIP_PKGS}" ]] && echo "Install pip ${MISSING_PIP_PKGS}" echo "" echo "Do you agree? [yes/no]:" [[ "${ASK}" = 'yes' ]] && yes_no #------------------------------------------------------------------------------- # Installation #------------------------------------------------------------------------------- title "Installation" VERBOSE='yes' [[ "${CLEAN}" = yes ]] && clean if debian && unattended_upgrades_present; then # restore the units even if the installation is aborted in the middle trap enable_unattended_upgrades EXIT check "disable_unattended_upgrades" "Pausing unattended-upgrades during install" fi debian && check "apt-get -q -y update >/dev/null" "Updating APT cache" [[ -n "${MISSING_PKGS}" ]] && check "install_pkg ${MISSING_PKGS}" "Install ${MISSING_PKGS}" 3 if [[ -n "${MISSING_PIP_PKGS}" ]]; then # it's evaluation tool, we can do ugly things export PIP_BREAK_SYSTEM_PACKAGES=1 check "$PIP install --upgrade pip || true" "Updating PIP" check "$PIP install ${MISSING_PIP_PKGS}" \ "Install from PyPI ${MISSING_PIP_PKGS}" 3 fi networking && { check "create_bridge" "Creating bridge interface ${BRIDGE_INTERFACE}" check "ifup_bridge" "Bring bridge interfaces up" [[ "$FORWARD" = 0 ]] && { check "sysctl -w net.ipv4.ip_forward=1 >/dev/null" "Enabling IPv4 forward" check "grep -q \"^net.ipv4.ip_forward=1$\" /etc/sysctl.conf || \ echo \"net.ipv4.ip_forward=1\" >> /etc/sysctl.conf" "Persisting IPv4 forward" } # When firewalld is in charge, NAT/masquerade is configured through it # (see configure_firewalld below); otherwise fall back to raw iptables/nft. if (centos || suse) && firewalld_running; then true else check "configure_nat" "Configuring NAT" (centos || suse) && check "iptables-save > /etc/sysconfig/iptables" "Saving iptables changes" debian && check "netfilter-persistent save" "Saving iptables changes" fi check "install_pkg dnsmasq" "Installing DNSMasq" 3 check "start_dnsmasq" "Starting DNSMasq" } # Keep firewalld enabled: open management ports and (for node installs) # trust the VM bridge and masquerade its egress. (centos || suse) && firewalld_running && check "configure_firewalld" "Configuring firewalld" check "configure_repos" "Configuring repositories" debian && check "apt-get -q -y update >/dev/null" "Updating APT cache" centos && check "enable_epel" "Installing EPEL" [[ "$DISTNAME" = "SLES" ]] && check "enable_sles_modules" "Enabling SLES modules" verlte 7.1.80 "$VERSION" && ONE_FE_PKGS+=(opennebula-form) if [[ "${KS}" = 'yes' ]]; then ONE_FE_PKGS+=(opennebula-ks) ONE_SERVICES+=(opennebula-ks) fi check "install_opennebula_pkgs" "Installing OpenNebula packages" 3 $INSTALL_TERRAFORM && check "install_terraform" "Installing TerraForm" if kvm; then check "install_opennebula_kvm_pkgs" "Installing OpenNebula kvm node packages" 3 [[ "${ENABLED_APPARMOR}" = 'yes' ]] && check "modify_apparmor" "Updating AppArmor" check "rm -f /etc/libvirt/qemu/networks/autostart/default.xml" \ "Disable default libvirtd networking" check "systemctl restart ${LIBVIRTD}" "Restart libvirt" elif lxc; then check "install_opennebula_lxc_pkgs" "Installing OpenNebula lxc node packages" 3 fi db_mysql && check "install_mariadb" "Installing and starting MariaDB" 3 [[ "${CONFIGURE_SELINUX}" = 'yes' ]] && check "configure_selinux" "Configuring SELinux contexts for OpenNebula" 1 \ "SKIP review SELinux denials if VMs fail to start" #------------------------------------------------------------------------------- # Configuration functions #------------------------------------------------------------------------------- sed_subst() { local KEY=$1 local VALUE=$2 if [ -z "$3" ]; then local CONF='/etc/one/oned.conf' else local CONF=$3 fi sed -i -e "s/^${KEY}/${VALUE}/" "${CONF}" >/dev/null } # OpenNebula 7.3 moved the OneGate bind address from the top level ':host:' # into a ':bind:' entry nested in the ':server:' block. set_onegate_bind() { local CONF='/etc/one/onegate-server.conf' if grep -q '^:server:' "${CONF}"; then sed -i -e "s/^\(\s*\):bind:.*/\1:bind: ${ONEGATE_SERVER}/" "${CONF}" >/dev/null else sed -i -e "s/^:host:.*/:host: ${ONEGATE_SERVER}/" "${CONF}" >/dev/null fi grep -qE "^[[:space:]]*(:bind:|:host:)[[:space:]]+${ONEGATE_SERVER}[[:space:]]*$" "${CONF}" } # Forward connections from the VM-side transparent proxy (169.254.16.9) to # the real OneGate and oned XML-RPC addresses. configure_tproxy() { local CONF='/var/lib/one/remotes/etc/vnm/OpenNebulaNetwork.conf' local ONEGATE_IP ONEGATE_IP=$(awk '$1 == ":bind:" || $1 == ":host:" { print $2; exit }' \ /etc/one/onegate-server.conf) [ -n "${ONEGATE_IP}" ] || return 1 grep -q '^:tproxy:' "${CONF}" && return 0 cat >>"${CONF}" <&2 return 1 } augtool -s set "/files$CONF/$KEY" "'$VALUE'" >/dev/null } set_init_password() { [[ ! -d $HOME/.one ]] && { mkdir "$HOME"/.one || return 1; } echo "oneadmin:$PASSWORD" >"$HOME"/.one/one_auth || return 1 chmod 0600 "$HOME"/.one/one_auth echo "oneadmin:$PASSWORD" >/var/lib/one/.one/one_auth chmod 0600 /var/lib/one/.one/one_auth } set_fireedge_port() { local FIREEDGE_PORT="$1" sed_subst "port:.*" "port: ${FIREEDGE_PORT}" /etc/one/fireedge-server.conf } one_is_ready() { for I in $(seq "$ONE_WAIT_TIMEOUT"); do onehost list >/dev/null 2>&1 && return 0 sleep 1 done echo "OpenNebula did not start within the timeout" >&2 return 1 } deny_ssh_from_vnet() { # If sshd_config doesn't exist (e.g. openSUSE 16), use a drop-in file # to avoid overriding distro compiled-in defaults (like UsePAM, Subsystem sftp). if [[ ! -f /etc/ssh/sshd_config ]] && suse; then mkdir -p /etc/ssh/sshd_config.d echo "DenyUsers ${STAR_NET}" >/etc/ssh/sshd_config.d/minione.conf else if ! grep -q "DenyUsers ${STAR_NET}" /etc/ssh/sshd_config; then echo "" >>/etc/ssh/sshd_config echo "DenyUsers ${STAR_NET}" >>/etc/ssh/sshd_config fi fi systemctl restart "${SSHD_SERVICE}" } add_keys_to_known_hosts() { su oneadmin -c 'ssh-keyscan localhost > ~/.ssh/known_hosts' || return 1 HOSTNAME=$(hostname) su oneadmin -c "ssh-keyscan ${HOSTNAME} >> ~/.ssh/known_hosts" || return 1 FQDN=$(hostname -f 2>/dev/null) if [[ -n "$FQDN" && "$HOSTNAME" != "${FQDN}" ]]; then su oneadmin -c "ssh-keyscan ${FQDN} >> ~/.ssh/known_hosts" || return 1 fi } test_ssh_connection() { sudo -u oneadmin ssh localhost true /dev/null || return 1 } add_ssh_keys_to_oneadmin() { local TMP_FILE TMP_FILE=$(mktemp) || return 1 # put current template to the tempfile oneuser show 0 | sed '/USER TEMPLATE/,/VMS USAGE/!d;//d' >"${TMP_FILE}" ONEADMIN_OS_USER_KEY=$(cat /var/lib/one/.ssh/id*pub 2>/dev/null) SSH_PUBKEY_CONTENT=$(cat "${SSH_PUBKEY}" 2>/dev/null) cat >>"${TMP_FILE}" <>/var/lib/one/.ssh/config <>~/.ssh/config </dev/null; then exit 0 fi if [ ! -f /usr/share/augeas/lenses/oned.aug ]; then echo "Missing oned.aug" >&2 exit 1 fi MATCH=$(augtool match '/files/etc/hosts/*/ipaddr' "$IP") if [ -n "$MATCH" ]; then # IP already in /etc/hosts augtool -s set "/files/etc/hosts/*[ipaddr=\"$IP\"]/alias[.=\"$HOSTNAME\"] \"$HOSTNAME\"" else # IP not in /etc/hosts yet LAST=$(augtool ls '/files/etc/hosts' | tail -1 | awk -F/ '{print $1}') LAST=$((LAST + 1)) AUG_CMD=$(mktemp) echo "set /files/etc/hosts/$LAST/ipaddr \"$IP\"" >"$AUG_CMD" echo "set /files/etc/hosts/$LAST/canonical \"$HOSTNAME\"" >>"$AUG_CMD" augtool -s -f "$AUG_CMD" rm "$AUG_CMD" fi ) } update_network_hooks() { for VN_MAD in 802.1Q bridge dummy ebtables fw ovswitch ovswitch_vxlan vxlan; do cp /var/lib/one/remotes/vnm/hooks/pre/firecracker /var/lib/one/remotes/vnm/"${VN_MAD}"/pre.d/firecracker cp /var/lib/one/remotes/vnm/hooks/clean/firecracker /var/lib/one/remotes/vnm/"${VN_MAD}"/clean.d/firecracker chown oneadmin:oneadmin /var/lib/one/remotes/vnm/"${VN_MAD}"/pre.d/firecracker chown oneadmin:oneadmin /var/lib/one/remotes/vnm/"${VN_MAD}"/clean.d/firecracker done } #------------------------------------------------------------------------------- # Configuration #------------------------------------------------------------------------------- title "Configuration" if verlte "$VERSION" 6.8.99; then check "update_network_hooks" "Update network hooks" fi check "aug_set ONEGATE_ENDPOINT '\"http://${ONEGATE_ENDPOINT}:5030\"'" \ "Switching OneGate endpoint in oned.conf" check "set_onegate_bind" \ "Switching OneGate endpoint in onegate-server.conf" check "sed_subst \":keep_empty_bridge: .*\" \":keep_empty_bridge: true\" \"/var/lib/one/remotes/etc/vnm/OpenNebulaNetwork.conf\"" \ "Switching keep_empty_bridge on in OpenNebulaNetwork.conf" [[ "${KS}" = 'yes' ]] && node && check "configure_tproxy" \ "Configuring OneGate proxy in OpenNebulaNetwork.conf" # new scheduler in 6.99+ if verlte "$VERSION" 6.10.99; then check "aug_set SCHED_INTERVAL 10 /etc/one/sched.conf" \ "Switching scheduler interval in sched.conf" else check "aug_set SCHED_RETRY_TIME 10 /etc/one/oned.conf" \ "Switching scheduler interval in oned.conf" fi check "set_init_password" "Setting initial password for current user and oneadmin" check "aug_set DEFAULT_CDROM_DEVICE_PREFIX '\"sd\"'" \ "Switching DEFAULT_CDROM_DEVICE_PREFIX in oned.conf" [[ ${SUNSTONE_PORT} != 2616 ]] && check "set_fireedge_port ${SUNSTONE_PORT}" "Switching FireEdge port" db_mysql && { check "configure_mysql_db" "Creating OpenNebula MySQL database and user" check "set_db_mysql" "Configuring MySQL backend in oned.conf" } check "systemctl start ${ONE_SERVICES[*]}" "Starting OpenNebula services" check "systemctl enable ${ONE_SERVICES[*]}" "Enabling OpenNebula services" check "add_ssh_keys_to_oneadmin" "Add ssh key to oneadmin user" check "update_ssh_configs" "Update ssh configs to allow VM addresses reusing" check "ensure_hostname_resolvable" "Ensure own hostname is resolvable" check "one_is_ready" "Checking OpenNebula is working" networking && { check "deny_ssh_from_vnet" "Disabling ssh from virtual network" check "add_keys_to_known_hosts" "Adding localhost ssh key to known_hosts" check "test_ssh_connection" "Testing ssh connection to localhost" } #------------------------------------------------------------------------------- # Bootstrap functions #------------------------------------------------------------------------------- onecli_cmd_tmpl() { local COMMAND=$1 local DATA=$2 local TMP_FILE TMP_FILE=$(mktemp) || return 1 cat >"${TMP_FILE}" < $MAD FILE EOF ) onecli_cmd_tmpl "onedatastore update 0" "${TEMPLATE}" >/dev/null || return 1 onecli_cmd_tmpl "onedatastore update 1" "${TEMPLATE}" >/dev/null } create_vnet() { SIZE=${VNET_AR_IP_COUNT} TEMPLATE=$( cat </dev/null 2>&1 } poll_for_marketplace() { APP_COUNT=$(onemarketapp list | wc -l) for I in $(seq 30); do sleep 5 NEW_APP_COUNT=$(onemarketapp list | wc -l) if [[ "${NEW_APP_COUNT}" = "${APP_COUNT}" && "${APP_COUNT}" -gt 20 ]]; then return 0 fi APP_COUNT=${NEW_APP_COUNT} done return 1 } export_marketapp() { local APP_NAME="$1" local DS_ID="${2:-1}" local SUFFIX="$3" local NEW_NAME="${4:-$APP_NAME$SUFFIX}" poll_for_marketplace ID=$(onemarketapp list --filter NAME="${APP_NAME}" \ --csv | tail -1 | awk -F, '{print $1}') || return 1 # onemarketapp always return 0 and prints to STDOUT OUT=$(mktemp) onemarketapp export "${ID}" "${NEW_NAME}" --datastore "$DS_ID" >"$OUT" if grep -q -i error <"$OUT"; then cat "$OUT" >&2 rm "$OUT" return 1 fi rm "$OUT" } # opennebula-ks fails to start until the marketplace is monitored, # hitting its systemd start limit; reset it and start the service again recover_ks() { local I systemctl is-active opennebula-ks >/dev/null 2>&1 && return 0 systemctl reset-failed opennebula-ks 2>/dev/null systemctl restart opennebula-ks || return 1 for I in $(seq 6); do sleep 5 systemctl is-active opennebula-ks >/dev/null 2>&1 && return 0 done return 1 } image_is_ready() { for I in $(seq "$IMAGE_WAIT_TIMEOUT"); do STATES=$(oneimage list --csv --no-header -l stat) # some error occurs echo "$STATES" | grep err && { echo "Image download error" return 1 } # all images are ready echo "$STATES" | grep -q -v rdy || return 0 sleep 1 done echo "Image download reached timeout" >&2 return 1 } update_template() { local ID local TMP_FILE # the last template ID=$(onetemplate list --no-header -l ID | head -1 | tr -d '[:space:]') TMP_FILE=$(mktemp) 2>/dev/null || return 1 # Add root password, report_ready and token setting to context onetemplate show "$ID" | grep CONTEXT -A1000 | sed -e "s/CONTEXT=\[/CONTEXT=[ PASSWORD=\"${VM_PASSWORD}\",/" | sed -e "s/CONTEXT=\[/CONTEXT=[ REPORT_READY=\"YES\",/" | sed -e "s/CONTEXT=\[/CONTEXT=[ TOKEN=\"YES\",/" \ >"${TMP_FILE}" # Add network node && echo 'NIC=[ NETWORK="vnet", NETWORK_UNAME="oneadmin", SECURITY_GROUPS="0" ]' >>"${TMP_FILE}" onetemplate update "$ID" "${TMP_FILE}" >/dev/null RC=$? rm "${TMP_FILE}" return "${RC}" } update_kvm_host() { TMP_FILE=$(mktemp) 2>/dev/null || return 1 cat >"$TMP_FILE" </dev/null 2>&1" \ "Creating $LOCALHOST_VM_MAD host" if [ "$(arch)" = "aarch64" ]; then check "update_kvm_host" "Updating kvm host with ARM64 specifics" fi } lxc && { check "onehost create -i lxc -v lxc localhost >/dev/null 2>&1" \ "Creating LXC host" } node && { check "systemctl restart opennebula" "Restarting OpenNebula" check "sudo -u oneadmin onehost sync -f" "Ensure host is synced" } networking && check "create_vnet" "Creating virtual network" check "export_marketapp \"$MARKET_APP_NAME\"" "Exporting [${MARKET_APP_NAME}] from Marketplace to local datastore" [[ "${KS}" = 'yes' ]] && check "recover_ks" "Ensuring OpenNebula KS service is running" check "image_is_ready" "Waiting until the image is ready" check "update_template" "Updating VM template" if [[ ${#MASKED_APT_UNITS[@]} -gt 0 ]]; then trap - EXIT check "enable_unattended_upgrades" "Re-enabling unattended-upgrades" 1 "SKIP" fi #------------------------------------------------------------------------------- # Report #------------------------------------------------------------------------------- [[ $SUNSTONE_PORT != 80 ]] && PORT_STR=":$SUNSTONE_PORT" title 'Report' echo "OpenNebula ${VERSION} was installed" db_mysql && echo "Database backend: MySQL (MariaDB), database 'opennebula'" echo "Sunstone is running on:" echo " http://${REPORT_IP}${PORT_STR}/" echo "Use following to login:" echo " user: oneadmin" echo " password: ${PASSWORD}" exit 0