You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1249 lines
42 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

#!/bin/bash
# Description: EL to MSVSphere migration script.
# License: GPLv3.
# Environment variables:
# ALMA_RELEASE_URL - msvsphere-release package download URL.
# ALMA_PUBKEY_URL - RPM-GPG-KEY-MSVSphere download URL.
set -euo pipefail
BASE_TMP_DIR='/root'
OS_RELEASE_PATH='/etc/os-release'
REDHAT_RELEASE_PATH='/etc/redhat-release'
STAGE_STATUSES_DIR='/var/run/msvsphere-deploy-statuses'
ALT_ADM_DIR="/var/lib/alternatives"
BAK_DIR="/tmp/alternatives_backup"
ALT_DIR="/etc/alternatives"
# MSVSphere OS 8.5
MINIMAL_SUPPORTED_VERSION='8.4'
VERSION='0.1.13'
DOWNGRADE='NO'
BRANDING_PKGS=("centos-backgrounds" "centos-logos" "centos-indexhtml" \
"centos-logos-ipa" "centos-logos-httpd" \
"almalinux-backgrounds" "almalinux-logos" "almalinux-indexhtml" \
"almalinux-logos-ipa" "almalinux-logos-httpd" \
"oracle-backgrounds" "oracle-logos" "oracle-indexhtml" \
"oracle-logos-ipa" "oracle-logos-httpd" \
"oracle-epel-release-el8" "oracle-epel-release-el9" \
"redhat-backgrounds" "redhat-logos" "redhat-indexhtml" \
"redhat-logos-ipa" "redhat-logos-httpd" \
"rocky-backgrounds" "rocky-logos" "rocky-indexhtml" \
"rocky-logos-ipa" "rocky-logos-httpd" \
"vzlinux-backgrounds" "vzlinux-logos" "vzlinux-indexhtml" \
"vzlinux-logos-ipa" "vzlinux-logos-httpd")
REMOVE_PKGS=("centos-linux-release" "centos-gpg-keys" "centos-linux-repos" \
"centos-stream-release" "centos-stream-repos" \
"kernel-uek" "kernel-uek-core" "kernel-uek-modules" \
"libreport-plugin-rhtsupport" "libreport-rhel" "insights-client" \
"libreport-rhel-anaconda-bugzilla" "libreport-rhel-bugzilla" \
"linux-firmware-core" "iwlax2xx-firmware" \
"almalinux-release" "almalinux-gpg-keys" "almalinux-repos" \
"almalinux-obsolete-packages" "libblockdev-btrfs" \
"oraclelinux-release" "oraclelinux-release-el8" \
"oraclelinux-release-el9" "python3-gobject-base-noarch" \
"redhat-release" "redhat-release-eula" \
"rocky-release" "rocky-gpg-keys" "rocky-repos" \
"rocky-obsolete-packages" "libblockdev-btrfs" \
"vzlinux-release" "vzlinux-gpg-keys" "vzlinux-repos" "vzlinux-obsolete-packages" \
"evolution-data-server-ui" "epel-next-release")
REDHAT_DNF_PLUGINS=("product-id" "subscription-manager" "upload-profile")
REDHAT_REPO_FILES=("/etc/yum.repos.d/redhat.repo" "/etc/yum.repos.d/ubi.repo")
REDHAT_RHSM_RPMS=("subscription-manager" "subscription-manager-cockpit" "cockpit" "rhc")
module_list_enabled=""
module_list_installed=""
is_container=0
setup_log_files() {
exec > >(tee /var/log/msvsphere-deploy.log) 2>&1
exec 5> /var/log/msvsphere-deploy.debug.log
BASH_XTRACEFD=5
}
# Save the successful status of a stage for future continue of it
# $1 - name of a stage
save_status_of_stage() {
if [[ 0 != "$(id -u)" ]]; then
# the function is called in tests and should be skipped
return 0
fi
local -r stage_name="${1}"
if [[ ! -d "${STAGE_STATUSES_DIR}" ]]; then
mkdir -p "${STAGE_STATUSES_DIR}"
fi
touch "${STAGE_STATUSES_DIR}/${stage_name}"
}
# Get a status of a stage for continue of it
# $1 - name of a stage
# The function returns 1 if stage isn't completed and 0 if it's completed
get_status_of_stage() {
if [[ 0 != "$(id -u)" ]]; then
# the function is called in tests and should be skipped
return 1
fi
local -r stage_name="${1}"
if [[ ! -d "${STAGE_STATUSES_DIR}" ]]; then
return 1
fi
if [[ ! -f "${STAGE_STATUSES_DIR}/${stage_name}" ]]; then
return 1
fi
return 0
}
is_migration_completed() {
if get_status_of_stage "completed"; then
printf '\n\033[0;32mMigration to MSVSphere was already completed\033[0m\n'
exit 0
fi
}
# Reports a completed step using a green color.
#
# $1 - Message to print.
report_step_done() {
local -r message="${1}"
printf '\033[0;32m%-70sOK\033[0m\n' "${message}"
}
# Reports a failed step using a red color.
#
# $1 - Message to print.
# $2 - Additional information to show (optional).
report_step_error() {
local -r message="${1}"
local -r trace="${2:-}"
printf '\033[0;31m%-70sERROR\033[0m\n' "${message}" 1>&2
if [[ -n "${trace}" ]]; then
echo "${trace}" | while read -r line; do
printf ' %s\n' "${line}" 1>&2
done
fi
}
# Prints program usage information.
show_usage() {
echo -e 'Migrates an EL system to MSVSphere\n'
echo -e 'Usage: msvsphere-deploy.sh [OPTION]...\n'
echo ' -h, --help show this message and exit'
echo ' -f, --full perform yum upgrade to 8.5 if necessary'
echo ' -d, --downgrade option to allow downgrade from CentOS Stream'
echo ' -v, --version print version information and exit'
}
# Terminates the program if it is not run with root privileges
assert_run_as_root() {
if [[ $(id -u) -ne 0 ]]; then
report_step_error 'Check root privileges' \
'Migration tool must be run as root'
exit 2
fi
report_step_done 'Check root privileges'
}
# Terminates the program if UEFI Secure Boot is enabled
assert_secureboot_disabled() {
local -r message='Check Secure Boot disabled'
if LC_ALL='C' mokutil --sb-state 2>/dev/null | grep -P '^SecureBoot\s+enabled' 1>/dev/null; then
report_step_error "${message}" 'Secure Boot is not supported yet'
exit 1
fi
report_step_done "${message}"
}
# Prints a system architecture.
get_system_arch() {
uname -i
}
# Reads a variable value from /etc/os-release.
#
# $1 - variable name.
#
# Returns the variable value.
get_os_release_var() {
local -r var="${1}"
local val
if ! val="$(grep -oP "^${var}=\"\K.*?(?=\")" "${OS_RELEASE_PATH}")"; then
echo "Error: ${var} is not found in ${OS_RELEASE_PATH}" >&2
exit 1
fi
echo "${val}"
}
# Detects an operational system version.
#
# $1 - operational system type.
#
# Prints OS version.
get_os_version() {
local -r os_type="${1}"
local os_version
if [[ "${os_type}" == 'centos' ]]; then
if [[ "$(get_os_release_var 'NAME')" == 'CentOS Stream' ]]; then
if [[ ! "${DOWNGRADE}" == 'YES' ]]; then
report_step_error "Use '-d or --downgrade' option to allow downgrade from CentOS Stream"
exit 1
fi
os_version="$(get_os_release_var 'VERSION_ID')"
else
if ! os_version="$(grep -oP 'CentOS\s+Linux\s+release\s+\K(\d+\.\d+)' \
"${REDHAT_RELEASE_PATH}" 2>/dev/null)"; then
report_step_error "Detect ${os_type} version"
fi
fi
elif [[ "${os_type}" == 'virtuozzo' ]]; then
if ! os_version="$(grep -oP 'Virtuozzo\s+Linux\s+release\s+\K(\d+\.\d+)' \
"${REDHAT_RELEASE_PATH}" 2>/dev/null)"; then
report_step_error "Detect ${os_type} version"
fi
else
os_version="$(get_os_release_var 'VERSION_ID')"
fi
echo "${os_version}"
}
# Prints control type and version.
get_panel_info() {
local panel_type=''
local panel_version=''
local -r cpanel_file='/usr/local/cpanel/cpanel'
local -r plesk_file='/usr/local/psa/version'
if [[ -x "${cpanel_file}" ]]; then
panel_type='cpanel'
panel_version=$("${cpanel_file}" -V 2>/dev/null | grep -oP '^[\d.]+')
elif [[ -f "${plesk_file}" ]]; then
panel_type='plesk'
panel_version=$(grep -oP '^[\d.]+' "${plesk_file}" 2>/dev/null)
fi
echo "${panel_type} ${panel_version}"
}
# Terminates the program if a platform is not supported by MSVSphere.
#
# $1 - Operational system id (ID).
# $2 - Operational system version (e.g. 8 or 8.3).
# $3 - System architecture (e.g. x86_64).
assert_supported_system() {
if get_status_of_stage "assert_supported_system"; then
return 0
fi
local -r os_type="${1}"
local -r os_version="${2:0:1}"
local -r arch="${3}"
case "${arch}" in
x86_64|aarch64|ppc64le|s390x)
;;
*)
report_step_error "Check ${arch} architecture is supported"
exit 1
;;
esac
if [[ ${os_version} -lt ${MINIMAL_SUPPORTED_VERSION:0:1} ]]; then
report_step_error "Check EL${os_version} is supported"
exit 1
fi
os_types=("centos" "almalinux" "msvsphere" "ol" "rhel" "rocky" "virtuozzo")
if [[ ! " ${os_types[*]} " =~ ${os_type} ]]; then
report_step_error "Check ${os_type} operating system is supported"
exit 1
fi
report_step_done "Check ${os_type}-${os_version}.${arch} is supported"
save_status_of_stage "assert_supported_system"
return 0
}
# Terminates the program if a control panel is not supported by MSVSphere.
#
# $1 - Control panel type.
# $2 - Control panel version.
assert_supported_panel() {
if get_status_of_stage "assert_supported_panel"; then
return 0
fi
local -r panel_type="${1}"
local -r panel_version="${2}"
local plesk_min_major=18
local plesk_min_minor=0
local plesk_min_micro=35
local major
local minor
local micro
local error_msg="${panel_type} version \"${panel_version}\" is not supported. Please update the control panel to version \"${plesk_min_major}.${plesk_min_minor}.${plesk_min_micro}\"."
if [[ "${panel_type}" == 'plesk' ]]; then
local IFS=.
read -r major minor micro << EOF
${panel_version}
EOF
if [[ -z ${micro} ]]; then
micro=0
fi
if [[ -z ${minor} ]]; then
minor=0
fi
if [[ ${major} -lt ${plesk_min_major} ]]; then
report_step_error "${error_msg}"
exit 1
elif [[ ${major} -eq ${plesk_min_major} && ${minor} -lt ${plesk_min_minor} ]]; then
report_step_error "${error_msg}"
exit 1
elif [[ ${major} -eq ${plesk_min_major} && ${minor} -eq ${plesk_min_minor} && ${micro} -lt ${plesk_min_micro} ]]; then
report_step_error "${error_msg}"
exit 1
fi
fi
save_status_of_stage "assert_supported_panel"
}
assert_supported_filesystem() {
if get_status_of_stage "assert_supported_filesystem"; then
return 0
fi
local result
if result=$(df -Th | awk '{print $2}' | grep btrfs); then
report_step_error "${result} is not supported filesystem"
exit 1
fi
save_status_of_stage "assert_supported_filesystem"
}
# Returns a latest msvsphere-release RPM package download URL.
#
# $1 - MSVSphere major version (e.g. 8).
# $2 - System architecture (e.g. x86_64).
#
# Prints msvsphere-release RPM package download URL.
get_release_file_url() {
local -r os_version="${1:0:1}"
local -r arch="${2}"
echo "${ALMA_RELEASE_URL:-https://rsync.inferitos.ru/msvsphere/sphere-release-server-latest-${os_version}.${arch}.rpm https://rsync.inferitos.ru/msvsphere/sphere-release-identity-server-latest-${os_version}.${arch}.rpm https://rsync.inferitos.ru/msvsphere/sphere-release-common-latest-${os_version}.${arch}.rpm https://rsync.inferitos.ru/msvsphere/sphere-release-latest-${os_version}.${arch}.rpm}"
}
# Returns a latest msvsphere-repos RPM package download URL.
#
# $1 - MSVSphere major version (e.g. 9).
# $2 - System architecture (e.g. x86_64).
#
# Prints msvsphere-release RPM package download URL.
get_repos_file_url() {
local -r os_version="${1:0:1}"
local -r arch="${2}"
echo "${ALMA_REPOS_URL:-https://rsync.inferitos.ru/msvsphere/sphere-repos-latest-${os_version}.${arch}.rpm}"
}
# Returns a latest msvsphere-gpg-keys RPM package download URL.
#
# $1 - MSVSphere major version (e.g. 9).
# $2 - System architecture (e.g. x86_64).
#
# Prints msvsphere-gpg-keys RPM package download URL.
get_gpg_keys_file_url() {
local -r os_version="${1:0:1}"
local -r arch="${2}"
echo "${ALMA_REPOS_URL:-https://rsync.inferitos.ru/msvsphere/sphere-gpg-keys-latest-${os_version}.${arch}.rpm}"
}
# Downloads and installs the MSVSphere public PGP key.
#
# $1 - Temporary directory path.
# $2 - MSVSphere major version (e.g. 8).
install_rpm_pubkey() {
if get_status_of_stage "install_rpm_pubkey"; then
return 0
fi
local -r tmp_dir="${1}"
local -r os_version="${2:0:1}"
local -r ALMA_PUBKEY_URL="https://rsync.inferitos.ru/msvsphere/RPM-GPG-KEY-MSVSphere-${os_version}"
local -r pubkey_url="${ALMA_PUBKEY_URL:-https://rsync.inferitos.ru/msvsphere/RPM-GPG-KEY-MSVSphere-8}"
local -r pubkey_path="${tmp_dir}/RPM-GPG-KEY-MSVSphere"
local -r step='Download RPM-GPG-KEY-MSVSphere'
local output
if ! output=$(curl -f -s -S -o "${pubkey_path}" "${pubkey_url}" 2>&1); then
report_step_error "${step}" "${output}"
exit 1
else
report_step_done "${step}"
fi
rpm --import "${pubkey_path}"
report_step_done 'Import RPM-GPG-KEY-MSVSphere to RPM DB'
rm -f "${pubkey_path}"
save_status_of_stage "install_rpm_pubkey"
}
# Downloads msvsphere-release package.
#
# $1 - Temporary directory path.
# $2 - msvsphere-release package download URL.
#
# Prints downloaded file path.
download_release_files() {
local -r tmp_dir="${1}"
local -r release_url="${2}"
local -r release_path="${tmp_dir}/sphere-release-server-latest.rpm ${tmp_dir}/sphere-release-identity-server-latest.rpm ${tmp_dir}/sphere-release-common-latest.rpm ${tmp_dir}/sphere-release-latest.rpm"
local output
if ! output=$(for url in ${release_url}; do curl -f -s -S -o "${tmp_dir}/$(echo $url | awk -F/ '{ print $NF }' | sed 's@-[0-9].*$@.rpm@')" "${url}"; done 2>&1); then
report_step_error 'Download msvsphere packages - release' "${output}"
exit 1
fi
echo "${release_path}"
}
# Downloads msvsphere-repos package.
#
# $1 - Temporary directory path.
# $2 - msvsphere-repos package download URL.
#
# Prints downloaded file path.
download_repos_files() {
local -r tmp_dir="${1}"
local -r repos_url="${2}"
local -r repos_path="${tmp_dir}/msvsphere-repos.rpm"
local output
if ! output=$(curl -f -s -S -o "${repos_path}" "${repos_url}" 2>&1); then
report_step_error 'Download msvsphere packages - repos' "${output}"
exit 1
fi
echo "${repos_path}"
}
# Downloads msvsphere-gpg-keys package.
#
# $1 - Temporary directory path.
# $2 - msvsphere-gpg-keys package download URL.
#
# Prints downloaded file path.
download_gpg_keys_files() {
local -r tmp_dir="${1}"
local -r gpg_keys_url="${2}"
local -r gpg_keys_path="${tmp_dir}/msvsphere-gpg-keys.rpm"
local output
if ! output=$(curl -f -s -S -o "${gpg_keys_path}" "${gpg_keys_url}" 2>&1); then
report_step_error 'Download msvsphere packages - gpg-keys' "${output}"
exit 1
fi
echo "${gpg_keys_path}"
}
# Terminates the program if a given RPM package checksum/signature is invalid.
#
# $1 - RPM package path.
assert_valid_package() {
local -r pkg_path="${1}"
local output
rpm -K $pkg_path 2>&1
if ! output=$(rpm -K ${pkg_path} 2>&1); then
report_step_error "Verify ${pkg_path} package" \
"${output}"
exit 1
fi
report_step_done 'Verify msvsphere-release package'
}
#
# Change repo file to point to last CentOS 8 release in vault
#
# $1 - Repo id.
# $2 - Repo name.
# $3 - Repo name for earlier releases if different from name.
# $4 - Repo subdir if different from name.
fix_repo_file() {
local -r repo_id="${1}"
local -r repo_name="${2}"
local -r repo_name_alt="${3-$2}"
local -r repo_subdir="${4:-$2}"
if [ -f "/etc/yum.repos.d/CentOS-Linux-${repo_name}.repo" ]; then
sed -i -e "/mirrorlist=http:\/\/mirrorlist.centos.org\/?release=\$releasever&arch=\$basearch&repo=/ s/^#*/#/" -e "/baseurl=http:\/\/mirror.centos.org\/\$contentdir\/\$releasever\// s/^#*/#/" -e "/^\[${repo_id}\]/a baseurl=https://mirror.rackspace.com/centos-vault/8.5.2111/${repo_subdir}/\$basearch/os" "/etc/yum.repos.d/CentOS-Linux-${repo_name}.repo"
elif [ -f "/etc/yum.repos.d/CentOS-${repo_name_alt}.repo" ]; then
sed -i -e "/mirrorlist=http:\/\/mirrorlist.centos.org\/?release=\$releasever&arch=\$basearch&repo=/ s/^#*/#/" -e "/baseurl=http:\/\/mirror.centos.org\/\$contentdir\/\$releasever\// s/^#*/#/" -e "/^\[\(${repo_id}\|${repo_name}\)\]/a baseurl=https://mirror.rackspace.com/centos-vault/8.5.2111/${repo_subdir}/\$basearch/os" "/etc/yum.repos.d/CentOS-${repo_name_alt}.repo"
elif [ -n "${repo_name_alt}" ]; then
echo "Error: No repo file found for ${repo_name}" >&2
return 1
fi
}
#
# Perform dnf upgrade (and fix repos since CentOS mirrorlist is offline as of 2022-01-31)
#
dnf_upgrade() {
local -r step='Fix DNF repositories to live mirror and run dnf upgrade -y'
if [[ "$(get_os_release_var 'NAME')" != 'CentOS Stream' ]]; then
fix_repo_file baseos BaseOS Base
fix_repo_file appstream AppStream
fix_repo_file cr ContinuousRelease CR
fix_repo_file devel Devel ''
fix_repo_file extras Extras Extras extras
fix_repo_file fasttrack FastTrack fasttrack fasttrack
fix_repo_file ha HighAvailability ''
fix_repo_file plus Plus centosplus
fix_repo_file powertools PowerTools
fix_repo_file crb CRB
fi
dnf upgrade -y || {
ret_code=${?}
if [[ ${ret_code} -ne 0 ]]; then
report_step_error "${step}. Exit code: ${ret_code}"
exit ${ret_code}
fi
}
# Make sure RPM DB is clean for gpg key installation in a subsequent step
dnf clean dbcache
report_step_done 'DNF upgrade'
}
# Terminates the program if OS version doesn't match MSVSphere version.
#
# $1 - OS version.
# $2 - msvsphere-release package file path.
assert_compatible_os_version() {
if get_status_of_stage "assert_compatible_os_version"; then
return 0
fi
local -r os_version="${1}"
local -r release_path="${2}"
local alma_version
alma_version=$(rpm -qp --queryformat '%{version}' ${release_path})
if [[ "$(get_os_release_var 'NAME')" != 'CentOS Stream' ]]; then
if [[ "${os_version:0:1}" -lt "${MINIMAL_SUPPORTED_VERSION:0:1}" ]] && [[ "${os_version:2:3}" -lt "${MINIMAL_SUPPORTED_VERSION:2:3}" ]]; then
report_step_error "Please upgrade your OS from ${os_version} to" \
"at least ${MINIMAL_SUPPORTED_VERSION} and try again"
exit 1
fi
fi
if [[ "${os_version:0:1}" -lt "${alma_version:0:1}" ]] && [[ "${os_version:2:3}" -gt "${alma_version:2:3}" ]]; then
report_step_error "Version of your OS ${os_version} is not supported yet"
exit 1
fi
report_step_done 'Your OS is supported'
save_status_of_stage "assert_compatible_os_version"
}
# Backup /etc/issue* files
backup_issue() {
if get_status_of_stage "backup_issue"; then
return 0
fi
for file in $(rpm -Vf /etc/issue | cut -d' ' -f4); do
if [[ ${file} =~ "/etc/issue" ]]; then
cp "${file}" "${file}.bak"
fi
done
save_status_of_stage "backup_issue"
}
# Restore /etc/issue* files
restore_issue() {
if get_status_of_stage "restore_issue"; then
return 0
fi
for file in /etc/issue /etc/issue.net; do
[ ! -f "${file}.bak" ] || mv -f ${file}.bak ${file}
done
save_status_of_stage "restore_issue"
}
# Cleanup sssd(8) cache
cleanup_sss_cache() {
if get_status_of_stage "cleanup_sss_cache"; then
return 0
fi
if [[ "${DOWNGRADE}" == 'YES' ]]; then
for file in /var/lib/sss/db/cache_*.ldb; do
if [ -f "${file}" ]; then
mv -f "${file}" "${file}.bak"
fi
done
fi
save_status_of_stage "cleanup_sss_cache"
}
# Recursively removes a given directory.
#
# $1 - Directory path.
cleanup_tmp_dir() {
rm -fr "${1:?}"
}
# Remove OS specific packages
remove_os_specific_packages_before_migration() {
if get_status_of_stage "remove_os_specific_packages_before_migration"; then
return 0
fi
for i in "${!REMOVE_PKGS[@]}"; do
if ! rpm -q "${REMOVE_PKGS[i]}" &> /dev/null; then
# remove an erased package from the list if it isn't installed
unset "REMOVE_PKGS[i]"
fi
done
if [[ "${#REMOVE_PKGS[@]}" -ne 0 ]]; then
rpm -e --nodeps --allmatches "${REMOVE_PKGS[@]}"
fi
report_step_done 'Remove OS specific rpm packages'
save_status_of_stage "remove_os_specific_packages_before_migration"
}
# Remove not needed Red Hat directories
remove_not_needed_redhat_dirs() {
if get_status_of_stage "remove_not_needed_redhat_dirs"; then
return 0
fi
[ -d /usr/share/doc/redhat-release ] && rm -r /usr/share/doc/redhat-release
[ -d /usr/share/redhat-release ] && rm -r /usr/share/redhat-release
save_status_of_stage "remove_not_needed_redhat_dirs"
}
# Install package msvsphere-release
install_msvsphere_release_package() {
if get_status_of_stage "install_msvsphere_release_package"; then
return 0
fi
local -r release_path="${1}"
rpm -Uvh --nodeps ${release_path}
report_step_done 'Install msvsphere-release package'
save_status_of_stage "install_msvsphere_release_package"
}
# Install package msvsphere-repos
install_msvsphere_repos_package() {
if get_status_of_stage "install_msvsphere_repos_package"; then
return 0
fi
local -r repos_path="${1}"
rpm -Uvh --nodeps "${repos_path}"
report_step_done 'Install msvsphere-repos package'
save_status_of_stage "install_msvsphere_repos_package"
}
# Install package msvsphere-gpg-keys
#
# $1 - full path (including package filename) to the msvsphere-gpg-keys package
install_msvsphere_gpg_keys_package() {
if get_status_of_stage "install_msvsphere_gpg_keys_package"; then
return 0
fi
local -r gpg_keys_path="${1}"
rpm -Uvh --nodeps "${gpg_keys_path}"
report_step_done 'Install msvsphere-gpg-keys package'
save_status_of_stage "install_msvsphere_gpg_keys_package"
}
# Remove brand packages and install the same MSVSphere packages
replace_brand_packages() {
if get_status_of_stage "replace_brand_packages"; then
return 0
fi
local alma_pkgs=()
local alma_pkg
local output
local pkg_name
# replace GUI packages
for i in "${!BRANDING_PKGS[@]}"; do
pkg_name="${BRANDING_PKGS[i]}"
if rpm -q "${pkg_name}" &>/dev/null; then
# shellcheck disable=SC2001
case "${pkg_name}" in
oracle-epel-release-el*)
alma_pkg="epel-release"
;;
*)
# shellcheck disable=SC2001
alma_pkg="$(echo "${pkg_name}" | sed 's#centos\|oracle\|redhat\|rocky\|almalinux\|vzlinux#sphere#')"
;;
esac
alma_pkgs+=("${alma_pkg}")
else
unset "BRANDING_PKGS[i]"
fi
done
if [[ "${#BRANDING_PKGS[@]}" -ne 0 ]]; then
rpm -e --nodeps --allmatches "${BRANDING_PKGS[@]}"
report_step_done "Remove ${BRANDING_PKGS[*]} packages"
fi
if [[ "${#alma_pkgs[@]}" -ne 0 ]]; then
if ! output=$(dnf install -y "${alma_pkgs[@]}" 2>&1); then
report_step_error "Install ${alma_pkgs[*]} packages" "${output}"
fi
report_step_done "Install ${alma_pkgs[*]} packages"
fi
save_status_of_stage "replace_brand_packages"
}
# Converts a CentOS like system to MSVSphere
#
# $1 - msvsphere-release RPM package path.
# $2 - msvsphere-repos RPM package path.
# $3 - msvsphere-gpg-keys RPM package path
migrate_from_centos() {
if get_status_of_stage "migrate_from_centos"; then
return 0
fi
local -r release_path="${1}"
local -r repos_path="${2}"
local -r gpg_keys_path="${3}"
# replace OS packages with msvsphere-release
# and OS centos-specific packages
remove_os_specific_packages_before_migration
remove_not_needed_redhat_dirs
install_msvsphere_release_package "${release_path}"
install_msvsphere_repos_package "${repos_path}"
install_msvsphere_gpg_keys_package "${gpg_keys_path}"
replace_brand_packages
save_status_of_stage "migrate_from_centos"
}
# Reset modules stream ol8 for OracleLinux
reset_wrong_module_streams() {
module_list_enabled=$(dnf -e 1 module list --enabled | awk '$2 == "ol8" {printf $1" "}')
module_list_installed=$(dnf -e 1 module list --installed | awk '$2 == "ol8" {printf $1" "}')
if [ -n "${module_list_enabled}" ]; then
# shellcheck disable=SC2086
dnf module reset -y ${module_list_enabled}
echo "reset modules: ${module_list_enabled}"
report_step_done "Reset modules"
fi
}
# Executes the 'dnf distro-sync -y' command.
#
# $1 - operational system type.
#
distro_sync() {
if get_status_of_stage "distro_sync"; then
return 0
fi
local -r step='Run dnf distro-sync -y'
local ret_code=0
local -r os_version="${1}"
case "${os_version}" in
8*)
local dnf_repos="--enablerepo=powertools"
;;
9*)
local dnf_repos="--enablerepo=crb"
;;
esac
local exclude_pkgs="--exclude="
# create needed repo
if [ "${panel_type}" == "plesk" ]; then
plesk installer --select-release-current --show-components --skip-cleanup
dnf_repos+=",PLESK_*-dist"
fi
dnf check-update || {
ret_code=${?}
if [[ ${ret_code} -ne 0 ]] && [[ ${ret_code} -ne 100 ]]; then
report_step_error "${step}. Exit code: ${ret_code}"
exit ${ret_code}
fi
}
# check if we inside lxc container
if [ $is_container -eq 1 ]; then
exclude_pkgs+="filesystem*,grub*"
fi
dnf distro-sync -y "${dnf_repos}" "${exclude_pkgs}" || {
ret_code=${?}
report_step_error "${step}. Exit code: ${ret_code}"
exit ${ret_code}
}
# remove unnecessary repo
if [ "${panel_type}" == "plesk" ]; then
plesk installer --select-release-current --show-components
fi
report_step_done "${step}"
save_status_of_stage "distro_sync"
}
# Enable reseted modules with new stream rhel8
restore_module_streams() {
if [ -n "${module_list_enabled}" ]; then
# shellcheck disable=SC2001,2086
module_enabled_rhel=$(echo ${module_list_enabled} | sed -e 's# #:rhel8 #g')
# shellcheck disable=SC2086
dnf module enable -y ${module_enabled_rhel}
echo "re-enabled modules: ${module_enabled_rhel}"
report_step_done "Enable modules"
fi
if [ -n "${module_list_installed}" ]; then
# shellcheck disable=SC2001,2086
module_installed_rhel=$(echo ${module_list_installed} | sed -e 's# #:rhel8 #g')
# shellcheck disable=SC2086
dnf module install -y ${module_installed_rhel}
echo "installed modules: ${module_installed_rhel}"
report_step_done "Install modules"
fi
}
install_kernel() {
if get_status_of_stage "install_kernel"; then
return 0
fi
if ! output=$(rpm -q kernel 2>&1); then
if output=$(dnf -y install kernel 2>&1); then
report_step_done "Install MSVSphere kernel"
else
report_step_error "Install MSVSphere kernel"
fi
fi
save_status_of_stage "install_kernel"
}
grub_update() {
if get_status_of_stage "grub_update"; then
return 0
fi
if [ "$(which grub2-mkconfig)" ] ; then
if [ -d /sys/firmware/efi ]; then
grub2-mkconfig -o /etc/grub2-efi.cfg
else
grub2-mkconfig -o /etc/grub2.cfg
fi
fi
save_status_of_stage "grub_update"
}
# Check do we have custom kernel (e.g. kernel-uek) and print warning
check_custom_kernel() {
if get_status_of_stage "check_custom_kernel"; then
return 0
fi
local output
output=$(rpm -qa | grep kernel-uek) || :
if [ -n "${output}" ]; then
if [ -x /usr/bin/mokutil ] && /usr/bin/mokutil --sb-state 2>&1 | grep -q enabled; then
echo -ne "\n!! There are kernels left from previous operating system
that won't boot in Secure Boot mode anymore:\n"
else
echo "There are kernels left from previous operating system:"
fi
# shellcheck disable=SC2001,SC2086
echo "$output" | sed 's# #\n#'
echo ""
echo "If you don't need them, you can remove them by using the 'dnf remove" \
"${output}' command"
fi
save_status_of_stage "check_custom_kernel"
}
_backup_alternative() {
local path="${1}"
local bak_dir="${2}"
local bak_prefix="current_point"
local alt_name=
local alt_dest=
local alt_link=
alt_name="$(basename "${path}")"
alt_link="${ALT_DIR}/${alt_name}"
alt_dest="$(readlink "${alt_link}")"
mkdir -p "${bak_dir}"
# backup the current state of an alternative
echo "${alt_dest} ${alt_link}" > "${bak_dir}/${bak_prefix}.${alt_name}"
}
_restore_alternative() {
local path="${1}"
local bak_dir="${2}"
local bak_point="${bak_dir}/current_point"
local alt_name=
local shift_begin=3
local shift_middle=2
local main_link=
local alternatives=()
local priorities=()
local links=()
local dests=()
local names=()
local line=
local _alt_link=
local _alt_dest=
local alt_link=
local alt_dest=
local i=
local empty_num=
local len=
local begin_index=
local end_index=
len="$(wc -l < "${path}")"
# name of a backup alternatives, e.g. python or java
alt_name="$(basename "${path}")"
# system link which points to an alternative, e.g.
# /usr/bin/unversioned-python -> /etc/alternatives/python
main_link="$(sed -n -e 2p "${path}")"
i=0
# this cycle saves info about slave's links
# and slave's names of an alternative
# e.g.
# unversioned-python - name
# /usr/bin/python - link
# python2 - name
# /usr/bin/python2 - link
# unversioned-python-man - name
# /usr/share/man/man1/python.1.gz - link
while read -r line; do
i=$(("${i}" + 1))
if [[ -z $line ]]; then
empty_num=$(("${shift_begin}" + "${i}"))
break
fi
if [[ $(( "${i}" % 2 )) -eq 0 ]]; then
links+=("$line")
else
names+=("$line")
fi
done < <(tail -n +${shift_begin} "${path}")
# this cycle saves info about available alternatives and them priorites
# e.g.
# /usr/libexec/no-python alternative with priority 404
# /usr/bin/python3 alternative with priority 300
while [[ "${len}" -gt $(("${#names[@]}" + "${empty_num}")) ]]; do
alternatives+=("$(sed -n -e "${empty_num}"p "${path}")")
priorities+=("$(sed -n -e $(("${empty_num}" + 1))p "${path}")")
begin_index="$(("${empty_num}" + "${shift_middle}"))"
end_index="$(("${#names[@]}" + "${shift_middle}" + "${empty_num}"))"
# this cycle saves info about slave's dests for an each alternative,
# e.g.
# /usr/bin/python3 - dest for /usr/bin/python
#/usr/share/man/man1/python3.1.gz - dest for /usr/share/man/man1/python.1.gz
# destination can be empty and in this case we don't create symlink
if [[ "${begin_index}" -ne "${end_index}" ]]; then
while read -r line; do
dests+=("$line")
done < <(sed -n -e "${begin_index}","${end_index}"p "${path}")
fi
empty_num=$(("${empty_num}" + "${#names[@]}" + "${shift_middle}"))
done
# read and restore current state of an alternative
# e.g.
# /etc/alternatives/python -> /usr/libexec/no-python
while read -r _alt_dest _alt_link; do
alt_link="${_alt_link}"
alt_dest="${_alt_dest}"
if [[ ! -e "${alt_link}" ]]; then
ln -sf "${alt_dest}" "${alt_link}"
fi
done < "${bak_point}.${alt_name}"
for i in "${!alternatives[@]}"; do
if [[ ! -e "${main_link}" ]]; then
# restore system symlink for alternative, e.g.
# /usr/bin/unversioned-python -> /etc/alternatives/python
ln -sf "${alt_link}" "${main_link}"
fi
for j in "${!names[@]}"; do
if [[ "${alt_dest}" == "${alternatives[$i]}" ]]; then
if [[ -e "${dests[$(( "${j}" + "${#links[@]}" * "${i}"))]}" && ! -e "${ALT_DIR}/${names[$j]}" ]]; then
# restore system slave link to an alternative, e.g.
# /etc/alternatives/unversioned-python-man -> /usr/share/man/man1/unversioned-python.1.gz
ln -sf "${dests[$(( "${j}" + "${#links[@]}" * "${i}"))]}" "${ALT_DIR}/${names[$j]}"
fi
if [[ -e "${ALT_DIR}/${names[$j]}" && ! -e "${links[$j]}" ]]; then
# restore slave link for an alternative
# e.g. /usr/share/man/man1/python.1.gz -> /etc/alternatives/unversioned-python-man
ln -sf "${ALT_DIR}/${names[$j]}" "${links[$j]}"
fi
fi
done
done
}
# backup existing alternatives, including the current states of alternatives
backup_alternatives() {
if get_status_of_stage "backup_alternatives"; then
return 0
fi
for alt_file in "${ALT_ADM_DIR}"/*; do
_backup_alternative "${alt_file}" "${BAK_DIR}"
done
/usr/bin/cp -rf "${ALT_ADM_DIR}" "${BAK_DIR}"/.
report_step_done "Backup of alternatives is done"
save_status_of_stage "backup_alternatives"
}
# restore existing alternatives, including the current states of alternatives
restore_alternatives() {
if get_status_of_stage "restore_alternatives"; then
return 0
fi
for alt_file in "${BAK_DIR}/alternatives/"*; do
_restore_alternative "${alt_file}" "${BAK_DIR}"
done
/usr/bin/cp -rn "${BAK_DIR}/alternatives/"* "${ALT_ADM_DIR}/"
report_step_done "Restoring of alternatives is done"
save_status_of_stage "restore_alternatives"
}
add_efi_boot_record() {
if get_status_of_stage "add_efi_boot_record"; then
return 0
fi
if [[ ! -d /sys/firmware/efi ]]; then
return
fi
local device
local disk_name
local disk_num
local soft_dev1
local soft_dev2
local disk_name1
local disk_name2
local disk_num1
local disk_num2
local arch
arch="$(get_system_arch)"
device="$(df -T /boot/efi | sed -n 2p | awk '{ print $1}')"
if [[ $device == *"/dev/md"* ]]; then
echo "ESP Partition on software raid detected:"
soft_dev1="$(lsblk -f -l -p | grep boot_efi |sed -n 1p | awk '{ print $1}')"
soft_dev2="$(lsblk -f -l -p | grep boot_efi |sed -n 2p | awk '{ print $1}')"
echo "$soft_dev1"
echo "$soft_dev2"
disk_name1="$(echo "${soft_dev1}" | sed -re 's/(p|)[0-9]$//g')"
disk_name2="$(echo "${soft_dev2}" | sed -re 's/(p|)[0-9]$//g')"
disk_num1="$(echo "${soft_dev1}" | tail -c 2|sed 's|[^0-9]||g')"
disk_num2="$(echo "${soft_dev2}" | tail -c 2|sed 's|[^0-9]||g')"
else
disk_name="$(echo "${device}" | sed -re 's/(p|)[0-9]$//g')"
disk_num="$(echo "${device}" | tail -c 2|sed 's|[^0-9]||g')"
fi
if [[ ${arch} == "x86_64" && $device == *"/dev/md"* ]]; then
efibootmgr -c -L "MSVSphere" -l "\EFI\msvsphere\shimx64.efi" -d "${disk_name1}" -p "${disk_num1}"
efibootmgr -c -L "MSVSphere" -l "\EFI\msvsphere\shimx64.efi" -d "${disk_name2}" -p "${disk_num2}"
elif [[ ${arch} == "aarch64" && $device == *"/dev/md"* ]]; then
efibootmgr -c -L "MSVSphere" -l "\EFI\msvsphere\shimaa64.efi" -d "${disk_name1}" -p "${disk_num1}"
efibootmgr -c -L "MSVSphere" -l "\EFI\msvsphere\shimaa64.efi" -d "${disk_name2}" -p "${disk_num2}"
elif [[ ${arch} == "x86_64" ]]; then
efibootmgr -c -L "MSVSphere" -l "\EFI\msvsphere\shimx64.efi" -d "${disk_name}" -p "${disk_num}"
elif [[ ${arch} == "aarch64" ]]; then
efibootmgr -c -L "MSVSphere" -l "\EFI\msvsphere\shimaa64.efi" -d "${disk_name}" -p "${disk_num}"
fi
report_step_done "The new EFI boot record for MSVSphere is added"
save_status_of_stage "add_efi_boot_record"
}
reinstall_secure_boot_packages() {
if get_status_of_stage "reinstall_secure_boot_packages"; then
return 0
fi
if [ "$(which grubby)" ] ; then #if this happens on a system with GRUB and related tools, rather than on RPi
local kernel_package
for pkg in $(rpm -qa | grep -E 'shim|fwupd|grub2'); do
if [[ "MSVSphere" != "$(rpm -q --queryformat '%{vendor}' "$pkg")" ]]; then
yum reinstall "${pkg}" -y
fi
done
kernel_package="$(rpm -qf "$(grubby --default-kernel)")"
if [[ "MSVSphere" != "$(rpm -q --queryformat '%{vendor}' "${kernel_package}")" ]]; then
yum reinstall "${kernel_package}" -y
fi
fi
report_step_done "All Secure Boot related packages which were not released by MSVSphere are reinstalled"
save_status_of_stage "reinstall_secure_boot_packages"
}
disable_dnf_plugin() {
local plugin="$1"
local conf
conf="/etc/dnf/plugins/${plugin}.conf"
if [ -f "${conf}" ]; then
sed -i -e 's/^enabled\s*=\s*1/enabled=0/g' "${conf}"
else
printf '[main]\nenabled=0\n' > "${conf}"
fi
}
disable_redhat_dnf_plugins() {
local plugin
for plugin in "${REDHAT_DNF_PLUGINS[@]}"; do
disable_dnf_plugin "${plugin}"
done
}
subscription_manager_unregister() {
if get_status_of_stage "subscription_manager_unregister"; then
return 0
fi
if subscription-manager status >/dev/null 2>&1; then
subscription-manager remove --all
subscription-manager unregister
subscription-manager clean
fi
report_step_done "Red Hat Subscription Manager deactivated"
save_status_of_stage "subscription_manager_unregister"
}
remove_redhat_rhsm_rpms() {
if get_status_of_stage "remove_redhat_rhsm_rpms"; then
return 0
fi
rpm -e --nodeps "${REDHAT_RHSM_RPMS[@]}" >/dev/null 2>&1 || true
report_step_done "Red Hat Subscription Manager packages are removed (with rpm --nodeps)"
save_status_of_stage "remove_redhat_rhsm_rpms"
}
remove_redhat_repo_files() {
if get_status_of_stage "remove_redhat_repo_files"; then
return 0
fi
local repo
for repo in "${REDHAT_REPO_FILES[@]}"; do
if [ -e "${repo}" ]; then
mv -f "${repo}" "${repo}.$(date -u '+%s')"
fi
done
report_step_done "Remove RHEL repositories' files if any"
save_status_of_stage "remove_redhat_repo_files"
}
main() {
is_migration_completed
local arch
local os_version
local os_type
local release_url
local tmp_dir
local release_path
local panel_type
local panel_version
assert_run_as_root
arch="$(get_system_arch)"
os_type="$(get_os_release_var 'ID')"
os_version="$(get_os_version "${os_type}")"
#os_version="$(get_os_release_var 'VERSION_ID')"
#os_version="${os_version:0:1}"
assert_supported_system "${os_type}" "${os_version}" "${arch}"
assert_supported_filesystem
read -r panel_type panel_version < <(get_panel_info)
assert_supported_panel "${panel_type}" "${panel_version}"
release_url=$(get_release_file_url "${os_version}" "${arch}")
tmp_dir=$(mktemp -d --tmpdir="${BASE_TMP_DIR}" .alma.XXXXXX)
# shellcheck disable=SC2064
trap "cleanup_tmp_dir ${tmp_dir}" EXIT
install_rpm_pubkey "${tmp_dir}" "${os_version}"
release_path=$(download_release_files "${tmp_dir}" "${release_url}")
report_step_done 'Download msvsphere-release package'
if mount 2>/dev/null | grep -q fuse.lxcfs ||
env | grep -q 'container=lxc' ||
awk '{print $1}' /proc/vz/veinfo 2>/dev/null ||
grep docker '/proc/1/cgroup' >/dev/null ||
test -e /.dockerenv; then
printf '\033[0;32mDeploying inside container\033[0m\n'
is_container=1
fi
assert_valid_package "${release_path}"
assert_compatible_os_version "${os_version}" "${release_path}"
case "${os_type}" in
almalinux|centos|msvsphere|ol|rhel|rocky|virtuozzo)
backup_issue
if [[ "${os_type}" == "rhel" ]]; then
subscription_manager_unregister
remove_redhat_rhsm_rpms
remove_redhat_repo_files
fi
case "${os_version}" in
8*)
repos_url=$(get_repos_file_url "${os_version}" "${arch}")
repos_path=$(download_repos_files "${tmp_dir}" "${repos_url}")
gpg_keys_url=$(get_gpg_keys_file_url "${os_version}" "${arch}")
gpg_keys_path=$(download_gpg_keys_files "${tmp_dir}" "${gpg_keys_url}")
migrate_from_centos "${release_path}" "${repos_path}" "${gpg_keys_path}"
;;
9*)
repos_url=$(get_repos_file_url "${os_version}" "${arch}")
repos_path=$(download_repos_files "${tmp_dir}" "${repos_url}")
gpg_keys_url=$(get_gpg_keys_file_url "${os_version}" "${arch}")
gpg_keys_path=$(download_gpg_keys_files "${tmp_dir}" "${gpg_keys_url}")
migrate_from_centos "${release_path}" "${repos_path}" "${gpg_keys_path}"
;;
esac
;;
*)
report_step_error "Migrate ${os_type}: not supported"
exit 1
;;
esac
backup_alternatives
reset_wrong_module_streams
distro_sync "${os_version}"
restore_module_streams
restore_alternatives
restore_issue
cleanup_sss_cache
# don't do this steps if we inside the lxc container
if [ $is_container -eq 0 ]; then
install_kernel
grub_update
reinstall_secure_boot_packages
add_efi_boot_record
fi
check_custom_kernel
save_status_of_stage "completed"
printf '\n\033[0;32mMigration to MSVSphere is completed\033[0m\n'
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
for opt in "$@"; do
case ${opt} in
-h | --help)
show_usage
exit 0
;;
-f | --full)
dnf_upgrade
;;
-v | --version)
echo "${VERSION}"
exit 0
;;
-d | --downgrade)
DOWNGRADE='YES'
;;
-t | --tests)
exit 0
;;
*)
echo "Error: unknown option ${opt}" >&2
exit 2
;;
esac
done
setup_log_files
set -x
main
set +x
fi