CentOS 8 在线升级 AlmaLinux
众所周知,步入到2021年红帽已不在维护CentOS了,转而取代的是伪滚动发行版 CentOS Stream,但少有Centos的运维人员对其青睐,而许多第三方应用程序(如cPanel)均不支持Stream。为此,博主对红帽这一草率的决定感到惋惜,同时也对自己初学Linux时用过的Centos将逐渐淡出人们视野而表示同情。
不过,别担心,随后就有大佬们推出了Centos的替代分支Linux。第一个宣布的是 Rocky Linux,来自最初为我们带来 CentOS 的人。紧随其后的是 AlmaLinux 。两者都是当前 Red Hat Enterprise Linux 二进制兼容并得到社区支持的开源操作系统的替代品,并且已经在成为 CentOS 的事实上的替代品方面取得了长足的进步 。
博主在Github找到了AlmaLinux的工作人员提供的Centos在线升级更新AlmaLinux的方式,可以说为已在数据中心部署了大量CentOS的运维人员带来了福音,且过程很简便。
需求:
● CentOS 8 的运行实例
● 具有 sudo 运行权限的用户
一、如何将 CentOS 乔迁 AlmaLinux
首先是更新Centos服务器环境:
- 1| sudo dnf upgrade -y 很明显这是CentOS 8
- 2|
- 3| 不管是哪版本,当然也可用yum:
- 4| yum update && yum upgrade -y
创建升级更新时的脚本文件路径:
- 1| mkdir AlmaLinux
- 2| 博主是在/root下创建的
- 3|
- 4| 进入新建目录:
- 5| cd AlmaLinux
从Github上下载升级脚本:
- 1| curl -O https://raw.githubusercontent.com/AlmaLinux/almalinux-deploy/master/almalinux-deploy.sh
若有读者在测试过程中如下图所示被墙了,可通过添加github的ip到hosts文件中来解决:
- 1| 报错代码:
- 2| Failed to connect to raw.githubusercontent.com port 443: Connection refused
解决方法: 访问 https://www.ipaddress.com/ ,查询 raw.githubusercontent.com 的IP
添加到/etc/hosts文件中:
- 1| vim /etc/hosts
- 2|
- 3| 添加内容:
- 4| #Github
- 5| 199.232.68.133 raw.githubusercontent.com
再次执行curl下载:
- 1| curl -O https://raw.githubusercontent.com/AlmaLinux/almalinux-deploy/master/almalinux-deploy.sh
而已下载好文件的朋友,授权执行即可:
- 1| 给almalinux-deploy.sh脚本文件授权:
- 2| chmod u+x almalinux-deploy.sh
- 3|
- 4| 执行:
- 5| sudo ./almalinux-deploy.sh
等待,直到出现如下消息,即说明乔迁成功!
- 1| Complete!
- 2| Run dnf distro-sync -y OK
- 3| Restoring of alternatives is done OK
- 4| Generating grub configuration file ...
- 5| done
- 6| All Secure Boot related packages which were released by not AlmaLinux are reinstalledOK
- 7|
- 8| Migration to AlmaLinux is completed
重启!
- 1| reboot
同时,我们发现新增了 AlmaLinux 8.4 的内核
重启后发现已乔迁成功!!
若有读者仍旧无法获取 almalinux-deploy.sh 升级脚本,也莫担心,博主提供脚本内容:
#!/bin/bash
# Description: EL to AlmaLinux migration script.
# License: GPLv3.
# Environment variables:
# ALMA_RELEASE_URL - almalinux-release package download URL.
# ALMA_PUBKEY_URL - RPM-GPG-KEY-AlmaLinux 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/almalinux-deploy-statuses'
ALT_ADM_DIR="/var/lib/alternatives"
BAK_DIR="/tmp/alternatives_backup"
ALT_DIR="/etc/alternatives"
# AlmaLinux OS 8.3
MINIMAL_SUPPORTED_VERSION='8.3'
VERSION='0.1.12'
BRANDING_PKGS=("centos-backgrounds" "centos-logos" "centos-indexhtml" \
"centos-logos-ipa" "centos-logos-httpd" \
"oracle-backgrounds" "oracle-logos" "oracle-indexhtml" \
"oracle-logos-ipa" "oracle-logos-httpd" \
"oracle-epel-release-el8" \
"redhat-backgrounds" "redhat-logos" "redhat-indexhtml" \
"redhat-logos-ipa" "redhat-logos-httpd" \
"rocky-backgrounds" "rocky-logos" "rocky-indexhtml" \
"rocky-logos-ipa" "rocky-logos-httpd")
REMOVE_PKGS=("centos-linux-release" "centos-gpg-keys" "centos-linux-repos" \
"libreport-plugin-rhtsupport" "libreport-rhel" "insights-client" \
"libreport-rhel-anaconda-bugzilla" "libreport-rhel-bugzilla" \
"oraclelinux-release" "oraclelinux-release-el8" \
"redhat-release" "redhat-release-eula" \
"rocky-release" "rocky-gpg-keys" "rocky-repos" \
"rocky-obsolete-packages")
setup_log_files() {
exec > >(tee /var/log/almalinux-deploy.log)
exec 5> /var/log/almalinux-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 AlmaLinux 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 AlmaLinux\n'
echo -e 'Usage: almalinux-deploy.sh [OPTION]...\n'
echo ' -h, --help show this message and exit'
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 ! 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
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 AlmaLinux.
#
# $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)
;;
*)
report_step_error "Check ${arch} architecture is supported"
exit 1
;;
esac
if [[ ${os_version} -ne ${MINIMAL_SUPPORTED_VERSION:0:1} ]]; then
report_step_error "Check EL${os_version} is supported"
exit 1
fi
os_types=("centos" "almalinux" "ol" "rhel" "rocky")
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 AlmaLinux.
#
# $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"
}
# Returns a latest almalinux-release RPM package download URL.
#
# $1 - AlmaLinux major version (e.g. 8).
# $2 - System architecture (e.g. x86_64).
#
# Prints almalinux-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://repo.almalinux.org/almalinux/almalinux-release-latest-${os_version}.${arch}.rpm}"
}
# Downloads and installs the AlmaLinux public PGP key.
#
# $1 - Temporary directory path.
install_rpm_pubkey() {
if get_status_of_stage "install_rpm_pubkey"; then
return 0
fi
local -r tmp_dir="${1}"
local -r pubkey_url="${ALMA_PUBKEY_URL:-https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux}"
local -r pubkey_path="${tmp_dir}/RPM-GPG-KEY-AlmaLinux"
local -r step='Download RPM-GPG-KEY-AlmaLinux'
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-AlmaLinux to RPM DB'
rm -f "${pubkey_path}"
save_status_of_stage "install_rpm_pubkey"
}
# Downloads almalinux-release package.
#
# $1 - almalinux-release package download URL.
# $2 - Temporary directory path.
#
# Prints downloaded file path.
download_release_file() {
local -r release_url="${1}"
local -r tmp_dir="${2}"
local -r release_path="${tmp_dir}/almalinux-release-latest.rpm"
local output
if ! output=$(curl -f -s -S -o "${release_path}" "${release_url}" 2>&1); then
report_step_error 'Download almalinux-release package' "${output}"
exit 1
fi
echo "${release_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
if ! output=$(rpm -K "${pkg_path}" 2>&1); then
report_step_error "Verify $(basename "${pkg_path}") package" \
"${output}"
exit 1
fi
report_step_done 'Verify almalinux-release package'
}
# Terminates the program if OS version doesn't match AlmaLinux version.
#
# $1 - OS version.
# $2 - almalinux-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 [[ "${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
if [[ "${os_version:2:3}" -gt "${alma_version:2:3}" ]]; then
report_step_error "Version of you 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"
}
# 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 almalinux-release
install_almalinux_release_package() {
if get_status_of_stage "install_almalinux_release_package"; then
return 0
fi
local -r release_path="${1}"
rpm -Uvh "${release_path}"
report_step_done 'Install almalinux-release package'
save_status_of_stage "install_almalinux_release_package"
}
# Remove brand packages and install the same AlmaLinux 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-el8)
alma_pkg="epel-release"
;;
*)
# shellcheck disable=SC2001
alma_pkg="$(echo "${pkg_name}" | sed 's#centos\|oracle\|redhat\|rocky#almalinux#')"
;;
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 AlmaLinux
#
# $1 - almalinux-release RPM package path.
migrate_from_centos() {
if get_status_of_stage "migrate_from_centos"; then
return 0
fi
local -r release_path="${1}"
# replace OS packages with almalinux-release
# and OS centos-specific packages
remove_os_specific_packages_before_migration
remove_not_needed_redhat_dirs
install_almalinux_release_package "${release_path}"
replace_brand_packages
save_status_of_stage "migrate_from_centos"
}
# Executes the 'dnf distro-sync -y' command.
#
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 dnf_repos="--enablerepo=powertools"
# 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
}
dnf distro-sync -y "${dnf_repos}" || {
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"
}
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 AlmaLinux kernel"
else
report_step_error "Install AlmaLinux kernel"
fi
fi
save_status_of_stage "install_kernel"
}
grub_update() {
if get_status_of_stage "grub_update"; then
return 0
fi
if [ -d /sys/firmware/efi ]; then
if [ -d /boot/efi/EFI/almalinux ]; then
grub2-mkconfig -o /boot/efi/EFI/almalinux/grub.cfg
elif [ -d /boot/efi/EFI/centos ]; then
grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg
else
grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg
fi
else
grub2-mkconfig -o /boot/grub2/grub.cfg
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
device="$(df -T /boot/efi | sed -n 2p | awk '{ print $1}')"
disk_name="$(echo "${device}" | sed -re 's/(p|)[0-9]$//g')"
disk_num="$(echo "${device}" | tail -c 2|sed 's|[^0-9]||g')"
efibootmgr -c -L "AlmaLinux" -l "\EFI\almalinux\shimx64.efi" -d "${disk_name}" -p "${disk_num}"
report_step_done "The new EFI boot record for AlmaLinux 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
local kernel_package
for pkg in $(rpm -qa | grep -E 'shim|fwupd|grub2'); do
if [[ "AlmaLinux" != "$(rpm -q --queryformat '%{vendor}' "$pkg")" ]]; then
yum reinstall "${pkg}" -y
fi
done
kernel_package="$(rpm -qf "$(grubby --default-kernel)")"
if [[ "AlmaLinux" != "$(rpm -q --queryformat '%{vendor}' "${kernel_package}")" ]]; then
yum reinstall "${kernel_package}" -y
fi
report_step_done "All Secure Boot related packages which were released by not AlmaLinux are reinstalled"
save_status_of_stage "reinstall_secure_boot_packages"
}
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}"
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}"
release_path=$(download_release_file "${release_url}" "${tmp_dir}")
report_step_done 'Download almalinux-release package'
assert_valid_package "${release_path}"
assert_compatible_os_version "${os_version}" "${release_path}"
case "${os_type}" in
almalinux|centos|ol|rhel|rocky)
backup_issue
migrate_from_centos "${release_path}"
;;
*)
report_step_error "Migrate ${os_type}: not supported"
exit 1
;;
esac
backup_alternatives
distro_sync
restore_alternatives
restore_issue
install_kernel
grub_update
reinstall_secure_boot_packages
add_efi_boot_record
check_custom_kernel
save_status_of_stage "completed"
printf '\n\033[0;32mMigration to AlmaLinux is completed\033[0m\n'
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
for opt in "$@"; do
case ${opt} in
-h | --help)
show_usage
exit 0
;;
-v | --version)
echo "${VERSION}"
exit 0
;;
-t | --tests)
exit 0
;;
*)
echo "Error: unknown option ${opt}" >&2
exit 2
;;
esac
done
setup_log_files
set -x
main
set +x
fi
授权后执行即可!
二、如何将CentOS 乔迁 Rocky Linux
与乔迁 AlmaLinux 同理,我们采用Github上的脚本在线升级:
- 1| 创建脚本下载路径,并进入:
- 2| mkdir Rocky-Linux && cd Rocky-Linux
- 3|
- 4| 下载脚本:
- 5| curl -O https://raw.githubusercontent.com/rocky-linux/rocky-tools/main/migrate2rocky/migrate2rocky.sh
若遇到 " Failed to connect to raw.githubusercontent.com port 443 “的报错时,解决方法和前文一致。
- 1| 授权:
- 2| chmod +x migrate2rocky.sh
- 3|
- 4| 选 -r 运行 !!
- 5| sudo ./migrate2rocky.sh -r
若你的Centos 8 dnf包管理需更新:
- 1| dnf install dnf && dnf update -y
- 2| 更新后再 sudo ./migrate2rocky.sh -r
如下图,正式升级更换Rocky,耐心等候:
出现如下执行消息表示,升级完成:
- 1| Done, please reboot your system.
- 2| A log of this installation can be found at /var/log/migrate2rocky.log
若不在订阅 REDHAT(红帽) 则可执行卸载删除其已无用文件:
- 1| dnf remove subscription-manager && dnf-plugin-subscription-manager
重启!
- 1| reboot
博主同样为无法下Rocky 的 migrate2rocky.sh升级脚本的小伙伴提供脚本内容,自主创建授权就可运行:
#!/bin/bash
#
# migrate2rocky - Migrate another EL8 distribution to RockyLinux 8.
# By: Peter Ajamian <peter@pajamian.dhs.org>
# Adapted from centos2rocky.sh by label <label@rockylinux.org>
#
# The latest version of this script can be found at:
# https://github.com/rocky-linux/rocky-tools
#
# Copyright (c) 2021 Rocky Enterprise Software Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
## Using this script means you accept all risks of system instability.
# These checks need to be right at the top because we start with bash-isms right
# away in this script.
if [ -n "$POSIXLY_CORRECT" ] || [ -z "$BASH_VERSION" ]; then
printf '%s\n' "bash >= 4.2 is required for this script." >&2
exit 1
fi
# We need bash version >= 4.2 for associative arrays and other features.
if (( BASH_VERSINFO[0]*100 + BASH_VERSINFO[1] < 402 )); then
printf '%s\n' "bash >= 4.2 is required for this script." >&2
exit 1
fi
# Make sure we're root.
if (( EUID != 0 )); then
printf '%s\n' "You must run this script as root. Either use sudo or 'su -c ${0}'" >&2
exit 1
fi
# Path to logfile
logfile=/var/log/migrate2rocky.log
# Send all output to the logfile as well as stdout.
# After the following we get:
# Output to 1 goes to stdout and the logfile.
# Output to 2 goes to stderr and the logfile.
# Output to 3 just goes to stdout.
# Output to 4 just goes to stderr.
# Output to 5 just goes to the logfile.
truncate -s0 "$logfile"
# shellcheck disable=SC2094
exec \
3>&1 \
4>&2 \
5>> "$logfile" \
> >(tee -a "$logfile") \
2> >(tee -a "$logfile" >&2)
# List nocolor last here so that -x doesn't bork the display.
errcolor=$(tput setaf 1)
infocolor=$(tput setaf 6)
nocolor=$(tput op)
# Single arg just gets returned verbatim, multi arg gets formatted via printf.
# First arg is the name of a variable to store the results.
msg_format () {
local _var
_var="$1"
shift
if (( $# > 1 )); then
# shellcheck disable=SC2059
printf -v "$_var" "$@"
else
printf -v "$_var" "%s" "$1"
fi
}
# Send an info message to the log file and stdout (with color)
infomsg () {
local msg
msg_format msg "$@"
printf '%s' "$msg" >&5
printf '%s%s%s' "$infocolor" "$msg" "$nocolor" >&3
}
# Send an error message to the log file and stderr (with color)
errmsg () {
local msg
msg_format msg "$@"
printf '%s' "$msg" >&5
printf '%s%s%s' "$errcolor" "$msg" "$nocolor" >&4
}
export LC_ALL=en_US.UTF-8 LANGUAGE=en_US
shopt -s nullglob
SUPPORTED_MAJOR="8"
SUPPORTED_PLATFORM="platform:el$SUPPORTED_MAJOR"
ARCH=$(arch)
gpg_key_url="https://dl.rockylinux.org/pub/rocky/RPM-GPG-KEY-rockyofficial"
gpg_key_sha512="88fe66cf0a68648c2371120d56eb509835266d9efdf7c8b9ac8fc101bdf1f0e0197030d3ea65f4b5be89dc9d1ef08581adb068815c88d7b1dc40aa1c32990f6a"
sm_ca_dir=/etc/rhsm/ca
unset tmp_sm_ca_dir
# all repos must be signed with the same key given in $gpg_key_url
declare -A repo_urls
repo_urls=(
[rockybaseos]="https://dl.rockylinux.org/pub/rocky/${SUPPORTED_MAJOR}/BaseOS/$ARCH/os/"
[rockyappstream]="https://dl.rockylinux.org/pub/rocky/${SUPPORTED_MAJOR}/AppStream/$ARCH/os/"
)
unset CDPATH
exit_message() {
errmsg $'\n'"$1"$'\n\n'
final_message
exit 1
}
final_message() {
errmsg '%s ' \
"An error occurred while we were attempting to convert your system to" \
"Rocky Linux. Your system may be unstable. Script will now exit to" \
"prevent possible damage."$'\n\n'
logmessage
}
logmessage(){
printf '%s%s%s\n' "$infocolor" \
"A log of this installation can be found at $logfile" \
"$nocolor" >&3
}
# This just grabs a field from os-release and returns it.
os-release () (
. /etc/os-release
if ! [[ ${!1} ]]; then
return 1
fi
printf '%s\n' "${!1}"
)
# Check the version of a package against a supplied version number. Note that
# this uses sort -V to compare the versions which isn't perfect for rpm package
# versions, but to do a proper comparison we would need to use rpmdev-vercmp in
# the rpmdevtools package which we don't want to force-install. sort -V should
# be adequate for our needs here.
pkg_ver() (
ver=$(rpm -q --qf '%{VERSION}\n' "$1") || return 2
if [[ $(sort -V <<<"$ver"$'\n'"$2" | head -1) != "$2" ]]; then
return 1
fi
return 0
)
# Set up a temporary directory.
pre_setup () {
if ! tmp_dir=$(mktemp -d) || [[ ! -d "$tmp_dir" ]]; then
exit_message "Error creating temp dir"
fi
# failglob makes pathname expansion fail if empty, dotglob adds files
# starting with . to pathname expansion
if ( shopt -s failglob dotglob; : "$tmp_dir"/* ) 2>/dev/null ; then
exit_message "Temp dir not empty"
fi
}
# Cleanup function gets rid of the temporary directory.
exit_clean () {
if [[ -d "$tmp_dir" ]]; then
rm -rf "$tmp_dir"
fi
}
pre_check () {
if [[ -e /etc/rhsm/ca/katello-server-ca.pem ]]; then
exit_message "Migration from Katello-modified systems is not supported by migrate2rocky. See the README file for details."
fi
if [[ -e /etc/salt/minion.d/susemanager.conf ]]; then
exit_message "Migration from Uyuni/SUSE Manager-modified systems is not supported by migrate2rocky. See the README file for details."
fi
}
# All of the binaries used by this script are available in a EL8 minimal install
# and are in /bin, so we should not encounter a system where the script doesn't
# work unless it's severly broken. This is just a simple check that will cause
# the script to bail if any expected system utilities are missing.
bin_check() {
# Check the platform.
if [[ $(os-release PLATFORM_ID) != "$SUPPORTED_PLATFORM" ]]; then
exit_message "This script must be run on an EL8 distribution. Migration from other distributions is not supported."
fi
local -a missing bins
bins=(
rpm dnf awk column tee tput mkdir
cat arch sort uniq rmdir rm head
curl sha512sum mktemp
)
if [[ $update_efi ]]; then
bins+=(findmnt grub2-mkconfig efibootmgr grep mokutil lsblk)
fi
for bin in "${bins[@]}"; do
if ! type "$bin" >/dev/null 2>&1; then
missing+=("$bin")
fi
done
local -A pkgs
pkgs=(
[dnf]=4.2
[dnf-plugins-core]=0
)
for pkg in "${!pkgs[@]}"; do
ver=${pkgs[$pkg]}
if ! pkg_ver "$pkg" "$ver"; then
exit_message \
"$pkg >= $ver is required for this script. Please run "\
"\"dnf install $pkg; dnf update\" first."
fi
done;
if (( ${#missing[@]} )); then
exit_message "Commands not found: ${missing[*]}. Possible bad PATH setting or corrupt installation."
fi
}
# This function will overwrite the repoquery_results associative array with the
# info for the resulting package. Note that we explicitly disable the epel repo
# as a special-case below to avoid having the extras repository map to epel.
repoquery () {
local name val prev result
result=$(
dnf -q --setopt=epel.excludepkgs=epel-release repoquery -i "$1" ||
exit_message "Failed to fetch info for package $1."
)
if ! [[ $result ]]; then
# We didn't match this package, the repo could be disabled.
return 1
fi
declare -gA repoquery_results=()
while IFS=" :" read -r name val; do
if [[ -z $name ]]; then
repoquery_results[$prev]+=" $val"
else
prev=$name
repoquery_results[$name]=$val
fi
done <<<"$result"
}
# This function will overwrite the repoinfo_results associative array with the
# info for the resulting repository.
repoinfo () {
local name val result
result=$(dnf -q repoinfo "$1") ||
exit_message "Failed to fetch info for repository $1."
if [[ $result == 'Total packages: 0' ]]; then
# We didn't match this repo.
return 1
fi
declare -gA repoinfo_results=()
while IFS=" :" read -r name val; do
if [[ ! ( $name || $val) ]]; then
continue
fi
if [[ -z $name ]]; then
repoinfo_results[$prev]+=" $val"
else
prev=$name
repoinfo_results[$name]=$val
fi
done <<<"$result"
# dnf repoinfo doesn't return the gpgkey, but we need that so we have to get
# it from the repo file itself.
# "end_of_file" is a hack here. Since it is not a valid dnf setting we know
# it won't appear in a .repo file on a line by itself, so it's safe to
# search for the string to make the awk parser look all the way to the end
# of the file.
# shellcheck disable=SC2154
repoinfo_results[Repo-gpgkey]=$(
awk '
$0=="['"${repoinfo_results[Repo-id]}"']",$0=="end_of_file" {
if (l++ < 1) {next}
else if (/^\[.*\]$/) {nextfile}
else if (sub(/^gpgkey\s*=\s*file:\/\//,"")) {print; nextfile}
else {next}
}
' < "${repoinfo_results[Repo-filename]}"
)
# Add an indicator of whether this is a subscription-manager managed
# repository.
# shellcheck disable=SC2154
repoinfo_results[Repo-managed]=$(
awk '
BEGIN {FS="[)(]"}
/^# Managed by \(.*\) subscription-manager$/ {print $2}
' < "${repoinfo_results[Repo-filename]}"
)
}
provides_pkg () (
if [[ ! $1 ]]; then
return 0
fi
set -o pipefail
provides=$(dnf -q provides "$1" | awk '{print $1; nextfile}') ||
return 1
set +o pipefail
pkg=$(rpm -q --queryformat '%{NAME}\n' "$provides") ||
pkg=$(dnf -q repoquery --queryformat '%{NAME}\n' "$provides") ||
exit_message "Can't get package name for $provides."
printf '%s\n' "$pkg"
)
# If you pass an empty arg as one of the package specs to rpm it will match
# every package on the system. This funtion simply strips out any empty args
# and passes the rest to rpm to avoid this side-effect.
saferpm () (
args=()
for a in "$@"; do
if [[ $a ]]; then
args+=("$a")
fi
done
rpm "${args[@]}"
)
# And a similar function for dnf
safednf () (
args=()
for a in "$@"; do
if [[ $a ]]; then
args+=("$a")
fi
done
dnf "${args[@]}"
)
collect_system_info () {
# Dump the DNF cache first so we start with a clean slate.
infomsg $'\nRemoving dnf cache\n'
rm -rf /var/cache/{yum,dnf}
# Check the efi mount first, so we can bail before wasting time on all these
# other checks if it's not there.
if [[ $update_efi ]]; then
local efi_mount kname
declare -g -a efi_disk efi_partition
efi_mount=$(findmnt --mountpoint /boot/efi --output SOURCE \
--noheadings) ||
exit_message "Can't find EFI mount. No EFI boot detected."
kname=$(lsblk -dno kname "$efi_mount")
efi_disk=$(lsblk -dno pkname "/dev/$kname")
if [[ $efi_disk ]]; then
efi_partition=$(<"/sys/block/$efi_disk/$kname/partition")
else
# This is likely an md-raid or other type of virtual disk, we need
# to dig a little deeper to find the actual physical disks and
# partitions.
kname=$(lsblk -dno kname "$efi_mount")
cd "/sys/block/$kname/slaves" || exit_message \
"Unable to gather EFI data: Can't cd to /sys/block/$kname/slaves."
if ! (shopt -s failglob; : *) 2>/dev/null; then
exit_message \
"Unable to gather EFI data: No slaves found in /sys/block/$kname/slaves."
fi
efi_disk=()
for d in *; do
efi_disk+=("$(lsblk -dno pkname "/dev/$d")")
efi_partition+=("$(<"$d/partition")")
if [[ ! ${efi_disk[-1]} || ! ${efi_partition[-1]} ]]; then
exit_message \
"Unable to gather EFI data: Can't find disk name or partition number for $d."
fi
done
cd -
fi
fi
# check if EFI secure boot is enabled
if [[ $update_efi ]]; then
if mokutil --sb-state 2>&1 | grep -q "SecureBoot enabled"; then
exit_message "EFI Secure Boot is enabled but Rocky Linux doesn't provide a signed shim yet. Disable EFI Secure Boot and reboot."
fi
fi
# Don't enable these module streams, even if they are enabled in the source
# distro.
declare -g -a module_excludes
module_excludes=(
libselinux-python:2.8
)
# Some OracleLinux modules have stream names of ol8 instead of rhel8 and ol
# instead of rhel. This is a map that does a glob match and replacement.
local -A module_glob_map
module_glob_map=(
['%:ol8']=:rhel8
['%:ol']=:rhel
);
# We need to map rockylinux repository names to the equivalent repositories
# in the source distro. To do that we look for known packages in each
# repository and see what repo they came from. We need to use repoquery for
# this which requires downloading the package, so we pick relatively small
# packages for this.
declare -g -A repo_map pkg_repo_map
declare -g -a managed_repos
pkg_repo_map=(
[baseos]=rootfiles.noarch
[appstream]=apr-util-ldap.$ARCH
[ha]=pacemaker-doc.noarch
[powertools]=libaec-devel.$ARCH
[extras]=epel-release.noarch
)
# [devel]=quota-devel.$ARCH
PRETTY_NAME=$(os-release PRETTY_NAME)
infomsg '%s' \
"Preparing to migrate $PRETTY_NAME to Rocky Linux 8."$'\n\n' \
"Determining repository names for $PRETTY_NAME"
for r in "${!pkg_repo_map[@]}"; do
printf '.'
p=${pkg_repo_map[$r]}
repoquery "$p" || continue
repo_map[$r]=${repoquery_results[Repository]}
done
printf '%s\n' '' '' "Found the following repositories which map from $PRETTY_NAME to Rocky Linux 8:"
column -t -s $'\t' -N "$PRETTY_NAME,Rocky Linux 8" < <(for r in "${!repo_map[@]}"; do
printf '%s\t%s\n' "${repo_map[$r]}" "$r"
done)
infomsg $'\n'"Getting system package names for $PRETTY_NAME"
# We don't know what the names of these packages are, we have to discover
# them via various means. The most common means is to look for either a
# distro-agnostic provides or a filename. In a couple of cases we need to
# jump through hoops to get a filename that is provided specifically by the
# source distro.
# Get info for each repository to determine which ones are subscription
# managed.
# system-release here is a bit of a hack, but it ensures that the
# rocky-repos package will get installed.
for r in "${!repo_map[@]}"; do
repoinfo "${repo_map[$r]}"
if [[ $r == "baseos" ]]; then
local baseos_filename=system-release
if [[ ! ${repoinfo_results[Repo-managed]} ]]; then
baseos_filename="${repoinfo_results[Repo-filename]}"
fi
local baseos_gpgkey="${repoinfo_results[Repo-gpgkey]}"
fi
if [[ ${repoinfo_results[Repo-managed]} ]]; then
managed_repos+=("${repo_map[$r]}")
fi
done
# First get info for the baseos repo
repoinfo "${repo_map[baseos]}"
declare -g -A pkg_map provides_pkg_map
declare -g -a addl_provide_removes addl_pkg_removes
provides_pkg_map=(
[rocky-backgrounds]=system-backgrounds
[rocky-indexhtml]=redhat-indexhtml
[rocky-repos]="$baseos_filename"
[rocky-logos]=system-logos
[rocky-logos-httpd]=system-logos-httpd
[rocky-logos-ipa]=system-logos-ipa
[rocky-gpg-keys]="$baseos_gpgkey"
[rocky-release]=system-release
)
addl_provide_removes=(
redhat-release
redhat-release-eula
)
# Check to make sure that we don't already have a full or partial
# RockyLinux install.
if [[ $(rpm -qa "${!provides_pkg_map[@]}") ]]; then
exit_message \
$'Found a full or partial RockyLinux install already in place. Aborting\n'
$'because continuing with the migration could cause further damage to system.'
fi
for pkg in "${!provides_pkg_map[@]}"; do
printf '.'
prov=${provides_pkg_map[$pkg]}
pkg_map[$pkg]=$(provides_pkg "$prov") ||
exit_message "Can't get package that provides $prov."
done
for prov in "${addl_provide_removes[@]}"; do
printf '.'
local pkg;
pkg=$(provides_pkg "$prov") || continue
addl_pkg_removes+=("$pkg")
done
printf '%s\n' '' '' "Found the following system packages which map from $PRETTY_NAME to Rocky Linux 8:"
column -t -s $'\t' -N "$PRETTY_NAME,Rocky Linux 8" < <(for p in "${!pkg_map[@]}"; do
printf '%s\t%s\n' "${pkg_map[$p]}" "$p"
done)
infomsg $'\n'"Getting list of installed system packages."$'\n'
readarray -t installed_packages < <(saferpm -qa --queryformat="%{NAME}\n" "${pkg_map[@]}")
declare -g -A installed_pkg_check installed_pkg_map
for p in "${installed_packages[@]}"; do
installed_pkg_check[$p]=1
done
for p in "${!pkg_map[@]}"; do
if [[ ${pkg_map[$p]} && ${installed_pkg_check[${pkg_map[$p]}]} ]]; then
installed_pkg_map[$p]=${pkg_map[$p]}
fi
done;
printf '%s\n' '' "We will replace the following $PRETTY_NAME packages with their Rocky Linux 8 equivalents"
column -t -s $'\t' -N "Packages to be Removed,Packages to be Installed" < <(
for p in "${!installed_pkg_map[@]}"; do
printf '%s\t%s\n' "${installed_pkg_map[$p]}" "$p"
done
)
if (( ${#addl_pkg_removes[@]} )); then
printf '%s\n' '' "In addition to the above the following system packages will be removed:" \
"${addl_pkg_removes[@]}"
fi
# Release packages that are part of SIG's should be listed below when they
# are available.
# UPDATE: We may or may not do something with SIG's here, it could just be
# left as a separate excersize to swap out the sig repos.
#sigs_to_swap=()
infomsg '%s' $'\n' \
$'Getting a list of enabled modules for the system repositories.\n'
# Get a list of system enabled modules.
readarray -t enabled_modules < <(
set -e -o pipefail
safednf -q "${repo_map[@]/#/--repo=}" module list --enabled |
awk '
$1 == "@modulefailsafe", /^$/ {next}
$1 == "Name", /^$/ {if ($1!="Name" && !/^$/) print $1":"$2}
' | sort -u
set +e +o pipefail
)
# Map the known module name differences.
disable_modules=()
local i gl repl mod
for i in "${!enabled_modules[@]}"; do
mod=${enabled_modules[$i]}
for gl in "${!module_glob_map[@]}"; do
repl=${module_glob_map[$gl]}
mod=${mod/$gl/$repl}
done
if [[ $mod != ${enabled_modules[$i]} ]]; then
disable_modules+=(${enabled_modules[$i]})
enabled_modules[$i]=$mod
fi
done
# Remove entries matching any excluded modules.
if (( ${#module_excludes[@]} )); then
printf '%s\n' '' "Excluding modules:" "${module_excludes[@]}"
local -A module_check='()'
local -a tmparr='()'
for m in "${module_excludes[@]}"; do
module_check[$m]=1
done
for m in "${enabled_modules[@]}"; do
if [[ ! ${module_check[$m]} ]]; then
tmparr+=("$m")
fi
done
enabled_modules=("${tmparr[@]}")
fi
printf '%s\n' '' "Found the following modules to re-enable at completion:" \
"${enabled_modules[@]}" ''
if (( ${#managed_repos[@]} )); then
printf '%s\n' '' "In addition, since this system uses subscription-manger the following managed repos will be disabled:" \
"${managed_repos[@]}"
fi
}
convert_info_dir=/root/convert
unset convert_to_rocky reinstall_all_rpms verify_all_rpms update_efi
usage() {
printf '%s\n' \
"Usage: ${0##*/} [OPTIONS]" \
'' \
'Options:' \
'-h Display this help' \
'-r Convert to rocky' \
'-V Verify switch' \
' !! USE WITH CAUTION !!'
exit 1
} >&2
generate_rpm_info() {
mkdir /root/convert
infomsg "Creating a list of RPMs installed: $1"$'\n'
rpm -qa --qf "%{NAME}|%{VERSION}|%{RELEASE}|%{INSTALLTIME}|%{VENDOR}|%{BUILDTIME}|%{BUILDHOST}|%{SOURCERPM}|%{LICENSE}|%{PACKAGER}\n" | sort > "${convert_info_dir}/$HOSTNAME-rpm-list-$1.log"
infomsg "Verifying RPMs installed against RPM database: $1"$'\n\n'
rpm -Va | sort -k3 > "${convert_info_dir}/$HOSTNAME-rpm-list-verified-$1.log"
}
# Run a dnf update before the actual migration.
pre_update() {
infomsg '%s\n' "Running dnf update before we attempt the migration."
dnf -y update || exit_message \
$'Error running pre-update. Stopping now to avoid putting the system in an\n'\
$'unstable state. Please correct the issues shown here and try again.'
}
package_swaps() {
# Save off any subscription-manger keys, just in case.
if ( shopt -s failglob dotglob; : "$sm_ca_dir"/* ) 2>/dev/null ; then
tmp_sm_ca_dir=$tmp_dir/sm-certs
mkdir "$tmp_sm_ca_dir" ||
exit_message "Could not create directory: $tmp_sm_ca_dir"
cp -f -dR --preserve=all "$sm_ca_dir"/* "$tmp_sm_ca_dir/" ||
exit_message "Could not copy certs to $tmp_sm_ca_dir"
fi
# prepare repo parameters
local -a dnfparameters
for repo in "${!repo_urls[@]}"; do
dnfparameters+=( "--repofrompath=${repo},${repo_urls[${repo}]}" )
dnfparameters+=( "--setopt=${repo}.gpgcheck=1" )
dnfparameters+=( "--setopt=${repo}.gpgkey=file://${gpg_key_file}" )
done
# Use dnf shell to swap the system packages out.
safednf -y shell --disablerepo=\* --noautoremove \
--setopt=protected_packages= --setopt=keepcache=True \
"${dnfparameters[@]}" \
<<EOF
remove ${installed_pkg_map[@]} ${addl_pkg_removes[@]}
install ${!installed_pkg_map[@]}
run
exit
EOF
# rocky-repos and rocky-gpg-keys are now installed, so we don't need the key file anymore
rm -rf "$gpg_tmp_dir"
# We need to check to make sure that all of the original system packages
# have been removed and all of the new ones have been added. If a package
# was supposed to be removed and one with the same name added back then
# we're kind of screwed for this check, as we can't be certain, but all the
# packages we're adding start with "rocky-*" so this really shouldn't happen
# and we can safely not check for it. The worst that will happen is a rocky
# linux package will be removed and then installed again.
local -a check_removed check_installed
readarray -t check_removed < <(
saferpm -qa --qf '%{NAME}\n' "${installed_pkg_map[@]}" \
"${addl_pkg_removes[@]}" | sort -u
)
if (( ${#check_removed[@]} )); then
infomsg '%s' $'\n' \
"Packages found on system that should still be removed. Forcibly" \
" removing them with rpm:"$'\n'
# Removed packages still found on the system. Forcibly remove them.
for pkg in "${check_removed[@]}"; do
# Extra safety measure, skip if empty string
if [[ -z $pkg ]]; then
continue
fi
printf '%s\n' "$pkg"
saferpm -e --allmatches --nodeps "$pkg" ||
saferpm -e --allmatches --nodeps --noscripts --notriggers "$pkg"
done
fi
# Check to make sure we installed everything we were supposed to.
readarray -t check_installed < <(
{
printf '%s\n' "${!installed_pkg_map[@]}" | sort -u
saferpm -qa --qf '%{NAME}\n' "${!installed_pkg_map[@]}" | sort -u
} | sort | uniq -u
)
if (( ${#check_installed[@]} )); then
infomsg '%s' $'\n' \
"Some required packages were not installed by dnf. Attempting to" \
" force with rpm:"$'\n'
# Get a list of rpm packages to package names
local -A rpm_map
local -a file_list
for rpm in /var/cache/dnf/{rockybaseos,rockyappstream}-*/packages/*.rpm
do
rpm_map[$(
rpm -q --qf '%{NAME}\n' --nodigest "$rpm" 2>/dev/null
)]=$rpm
done
# Attempt to install.
for pkg in "${check_installed[@]}"; do
printf '%s\n' "$pkg"
if ! rpm -i --force --nodeps --nodigest "${rpm_map[$pkg]}" \
2>/dev/null; then
# Try to install the package in just the db, then clean it up.
rpm -i --force --justdb --nodeps --nodigest "${rpm_map[$pkg]}" \
2>/dev/null
# Get list of files that are still causing problems and donk
# them.
readarray -t file_list < <(
rpm -V "$pkg" 2>/dev/null | awk '$1!="missing" {print $2}'
)
for file in "${file_list[@]}"; do
rmdir "$file" ||
rm -f "$file" ||
rm -rf "$file"
done
# Now try re-installing the package to replace the missing
# files. Regardless of the outcome here we just accept it and
# move on and hope for the best.
rpm -i --reinstall --force --nodeps --nodigest \
"${rpm_map[$pkg]}" 2>/dev/null
fi
done
fi
# Distrosync
infomsg $'Ensuring repos are enabled before the package swap\n'
safednf -y --enableplugin=config-manager config-manager \
--set-enabled "${!repo_map[@]}" || {
printf '%s\n' 'Repo name missing?'
exit 25
}
if (( ${#managed_repos[@]} )); then
# Filter the managed repos for ones still in the system.
readarray -t managed_repos < <(
safednf -q repolist "${managed_repos[@]}" | awk '$1!="repo" {print $1}'
)
if (( ${#managed_repos[@]} )); then
infomsg $'\nDisabling subscription managed repos\n'
safednf -y --enableplugin=config-manager config-manager \
--disable "${managed_repos[@]}"
fi
fi
if (( ${#disable_modules[@]} )); then
infomsg $'Disabling modules\n\n'
safednf -y module disable "${disable_modules[@]}" ||
exit_message "Can't disable modules ${disable_modules[*]}"
fi
if (( ${#enabled_modules[@]} )); then
infomsg $'Enabling modules\n\n'
safednf -y module enable "${enabled_modules[@]}" ||
exit_message "Can't enable modules ${enabled_modules[*]}"
fi
# Make sure that excluded repos are disabled.
infomsg $'Disabling excluded modules\n\n'
safednf -y module disable "${module_excludes[@]}" ||
exit_message "Can't disable modules ${module_excludes[*]}"
infomsg $'\nSyncing packages\n\n'
dnf -y distro-sync || exit_message "Error during distro-sync."
if rpm --quiet -q subscription-manager; then
infomsg $'Subscription Manager found on system.\n'
cat <<EOF
If you're converting from a subscription-managed distribution such as RHEL then
you may no longer need subscription-manager or dnf-plugin-subscription-manager.
While it won't hurt anything to have it on your system you may be able to safely
remove it with:
"dnf remove subscription-manager dnf-plugin-subscription-manager".
Take care that it doesn't remove something that you want to keep.
The subscription-manager dnf plugin may be enabled for the benefit of
Subscription Management. If no longer desired, you can use
"subscription-manager config --rhsm.auto_enable_yum_plugins=0" to block this
behavior.
EOF
fi
if [[ $tmp_sm_ca_dir ]]; then
# Check to see if there's Subscription Manager certs which have been
# removed
local -a removed_certs
readarray -t removed_certs < <((
shopt -s nullglob dotglob
local -a certs
cd "$sm_ca_dir" && certs=(*)
cd "$tmp_sm_ca_dir" && certs+=(*)
IFS=$'\n'
printf '%s' "${certs[*]}"
) | sort | uniq -u)
if (( ${#removed_certs[@]} )); then
cp -n -dR --preserve=all "$tmp_sm_ca_dir"/* "$sm_ca_dir/" ||
exit_message "Could not copy certs back to $sm_ca_dir"
infomsg '%s' \
$'Some Subscription Manager certificates ' \
"were restored to $sm_ca_dir after"$'\n' \
$'migration so that the subscription-manager ' \
$'command will continue to work:\n\n'
printf '%s\n' "${removed_certs[@]}" ''
cat <<EOF
If you no longer need to use the subscription-manager command then you may
safely remove these files.
EOF
fi
fi
}
# Check if this system is running on EFI
# If yes, we'll need to run fix_efi() at the end of the conversion
efi_check () {
# Check if we have /sys mounted and it is looking sane
if ! [[ -d /sys/class/block ]]; then
exit_message "/sys is not accessible."
fi
# Now that we know /sys is reliable, use it to check if we are running on EFI or not
if [[ -d /sys/firmware/efi/ ]]; then
declare -g update_efi
update_efi=true
fi
}
# Called to update the EFI boot.
fix_efi () (
grub2-mkconfig -o /boot/efi/EFI/rocky/grub.cfg ||
exit_message "Error updating the grub config."
for i in "${!efi_disk[@]}"; do
efibootmgr -c -d "/dev/${efi_disk[$i]}" -p "${efi_partition[$i]}" \
-L "Rocky Linux" -l /EFI/rocky/grubx64.efi ||
exit_message "Error updating uEFI firmware."
done
)
# Download and verify the Rocky Linux package signing key
establish_gpg_trust () {
# create temp dir and verify it is really created and empty, so we are sure deleting it afterwards won't cause any harm
declare -g gpg_tmp_dir
gpg_tmp_dir=$tmp_dir/gpg
if ! mkdir "$gpg_tmp_dir" || [[ ! -d "$gpg_tmp_dir" ]]; then
exit_message "Error creating temp dir"
fi
# failglob makes pathname expansion fail if empty, dotglob adds files starting with . to pathname expansion
if ( shopt -s failglob dotglob; : "$gpg_tmp_dir"/* ) 2>/dev/null ; then
exit_message "Temp dir not empty"
fi
# extract the filename from the url, use the temp dir just created
declare -g gpg_key_file="$gpg_tmp_dir/${gpg_key_url##*/}"
if ! curl -L -o "$gpg_key_file" --silent --show-error "$gpg_key_url"; then
rm -rf "$gpg_tmp_dir"
exit_message "Error downloading the Rocky Linux signing key."
fi
if ! sha512sum --quiet -c <<<"$gpg_key_sha512 $gpg_key_file"; then
rm -rf "$gpg_tmp_dir"
exit_message "Error validating the signing key."
fi
}
## End actual work
noopts=0
while getopts "hrVR" option; do
(( noopts++ ))
case "$option" in
h)
usage
;;
r)
convert_to_rocky=true
;;
V)
verify_all_rpms=true
;;
*)
errmsg $'Invalid switch\n'
usage
;;
esac
done
if (( ! noopts )); then
usage
fi
pre_setup
trap exit_clean EXIT
pre_check
efi_check
bin_check
if [[ $verify_all_rpms ]]; then
generate_rpm_info begin
fi
if [[ $convert_to_rocky ]]; then
collect_system_info
establish_gpg_trust
pre_update
package_swaps
fi
if [[ $verify_all_rpms && $convert_to_rocky ]]; then
generate_rpm_info finish
infomsg $'You may review the following files:\n'
find /root/convert -type f -name "$HOSTNAME-rpms-*.log"
fi
if [[ $update_efi && $convert_to_rocky ]]; then
fix_efi
fi
printf '\n\n\n'
if [[ $convert_to_rocky ]]; then
infomsg $'\nDone, please reboot your system.\n'
fi
logmessage
好啦,CentOS 乔迁 AlmaLinux 和 Rocky Linux 的教程就到此为止啦,感谢您的阅读与支持!!