#!/bin/bash
###
### This file is covered by the GNU General Public License
### version 3 or later.
###
### Copyright (C) 2021-2025, ALT Linux Team

###################
### Main script ###
###################

# Safety first
set -o errexit
set -o noglob
set -o nounset
set -o errtrace

# Temporary directory
export TMPDIR="${TMPDIR:-/tmp}"

# Full path to this script
readonly scriptname="$(realpath -- "$0")"

# Short name of this program
readonly progname="${scriptname##*/}"

# Supplemental sources: we need the ability to launch on site
if [ "$scriptname" = "/usr/bin/$progname" ]; then
	readonly libdir="/usr/libexec/$progname"
else
	readonly libdir="${scriptname%/*}"/sysrest
fi

# Bootstrap
. "$libdir"/common.sh
. "$libdir"/restore/defaults.sh
. "$libdir"/restore/internal.sh
[ ! -s "/etc/$progname"/restore.conf ] ||
	. "/etc/$progname"/restore.conf
. "$libdir"/restore/parser.sh
. "$libdir"/restore/target.sh
. "$libdir"/restore/restore.sh

# Catch all unexpected errors
trap 'unexpected_error "${BASH_SOURCE[0]##*/}" "$LINENO"' ERR

# Parsing command line arguments
parse_cmdline "$@"

# Determine the target platform and its default parameters
detect_hw_platform

# This program requires root privileges to run
[ "$(id -u)" = 0 ] ||
	fatal "To run this program you need to be a superuser."

# Enable logging and debugging
. "$libdir"/logger.sh
setup_logger "$@"

# Start checking the backup
if [ -n "$use_backup" ]; then
	. "$libdir"/restore/backup.sh
	. "$libdir"/profile/hardware.sh

	set_recovery_options
fi

# Show banner
welcome_text

# Checking files and metadata of the backup
[ -z "$use_backup" ] ||
	check_backup_files
unset use_backup

# Determine a list of protected disks that cannot be targets
protect_boot_devices

# Checking the validity of the specified target disk
if [ -n "$target" ] && [ "$action" != scandisk ]; then
	dummy="$target"
	[ -b "$target" ] ||
		fatal "Target device not found: '%s'." "$target"
	get_whole_disk dummy "$target" && [ "$dummy" = "$target" ] ||
		fatal "Target device must be a whole disk: '%s'." "$target"
	! in_array "$target" $protected_devices ||
		fatal "The target is on the list of write-protected devices."
	unset dummy
else
	# the target disk is not relevant for the disk scan operation
	[ -z "$target" ] ||
		warn "The target will be ignored with the --scan-only action."
	target=

	# automatically detect the target disk or select it manually
	search_target_drive
fi

# No longer needed
unset target_model
unset choose_target
unset min_target_size
unset max_target_size
unset protected_devices
unset protected_mpoints

# Defining the final deployment configuration
deployment_config

# Create a disk partitioning scheme and define partition device names
layout="$workdir/disk-layout.$pt_scheme"
make_pt_scheme >"$layout"
define_parts

# The root partition is required in any case
if [ -z "$rootpart" ]; then
	fatal "Internal error: root partition required."
fi

# Set future hostname
setup_hostname

# Intermediate diagnostics
show_diag_msg

# No longer needed
unset prepsize
unset esp_size
unset bbp_size
unset swapsize
unset bootsize
unset rootsize

# Ignore interrupts
trap : INT TERM QUIT HUP USR1 USR2

# Clean the target disk
msg "Initializing disk(s)..."
{ swapoff -a ||:
  vgchange -a n ||:
  mdadm --stop --scan ||:
  dummy="$(list_partitions)"
  [ -z "$dummy" ] ||
	wipefs -a -- $dummy ||:
  dd if=/dev/zero bs=1M count=4 of="$target"
  wipefs -a -- "$target"
  [ -z "$dummy" ] ||
	rm -f -- $dummy
  unset dummy
} >/dev/null 2>&1

# Create partitions on the target disk and reread the partition table
run sfdisk -q -f --no-reread -W always -X "$pt_scheme" -- "$target" <"$layout"
run rm -f $verbose -- "$layout"
unset layout
rereadpt

# Write the partition names into the GUID/GPT table
set_gpt_part_names

# Wipe signatures on all partitions except the extended one
run wipefs -a -- $(list_partitions -x) >/dev/null ||:

# Format partitions
if [ -n "$preppart" ]; then
	msg "Formatting PReP..."
	will_be_run dd if=/dev/zero bs=4k status=none of="$preppart"
	dd if=/dev/zero bs=4k status=none of="$preppart" &>/dev/null ||:
fi
if [ -n "$esp_part" ]; then
	msg "Formatting ESP..."
	run mkfs.fat -F32 -f2 \
		${esp__fs_label:+-n "$esp__fs_label"} -- "$esp_part"
fi
if [ -n "$bbp_part" ]; then
	msg "Formatting BBP..."
	will_be_run dd if=/dev/zero bs=4k status=none of="$bbp_part"
	dd if=/dev/zero bs=4k status=none of="$bbp_part" &>/dev/null ||:
fi
if [ -n "$bootpart" ]; then
	msg "Formatting BOOT..."
	run mkfs.ext2 -q ${verbose:+-v} \
		${boot_fs_label:+-L "$boot_fs_label"} -- "$bootpart"
fi
if [ -n "$swappart" ]; then
	msg "Formatting SWAP..."
	run mkswap $verbose \
		${swap_fs_label:+-L "$swap_fs_label"} -- "$swappart"
	swapuuid="$(get_fs_uuid "$swappart")"
fi
if [ -n "$rootpart" ]; then
	msg "Formatting ROOT..."
	[ -z "$bootpart" ] && [ -n "$old_ext4_boot" ] && \
		opts=" -O ^64bit" || opts=
	run mkfs.ext4 -q ${verbose:+-v} -j$opts \
		${root_fs_label:+-L "$root_fs_label"} -- "$rootpart"
	bootuuid="$(get_fs_uuid "${bootpart:-$rootpart}")"
	unset opts
fi
if [ -n "$datapart" ]; then
	msg "Formatting DATA..."
	run mkfs.ext4 -q ${verbose:+-v} \
		-j${data_fs_label:+ -L "$data_fs_label"} -- "$datapart"
fi

# Recovering the root partition
if [ -n "$rootpart" ]; then
	msg "Restoring / (root) partition..."
	[ -d "$destdir" ] ||
		run mkdir -p -m 0755 $verbose -- "$destdir"
	run mount -t ext4 -o "$rootopts" $verbose -- "$rootpart" "$destdir"

	# Перенос содержимого /boot в отдельный раздел
	if [ -n "$bootpart" ] && ! file_exists boot.tgz; then
		run mkdir -m 0700 $verbose -- "$destdir"/boot
		run mount -t ext2 -o "$bootopts" $verbose -- \
					"$bootpart" "$destdir"/boot
	fi

	unpack_image root
fi

# Recovering the originally separate /boot
if [ -n "$bootpart" ] && file_exists boot.tgz; then
	msg "Restoring /boot partition..."
	[ -d "$destdir"/boot ] ||
		run mkdir -m 0700 $verbose -- "$destdir"/boot
	[ -z "$bootpart" ] ||
		run mount -t ext2 -o "$bootopts" $verbose -- \
					"$bootpart" "$destdir"/boot
	unpack_image boot /boot
fi

# Recovering ESP
if [ -n "$esp_part" ] || file_exists esp.tgz; then
	msg "Restoring /boot/efi (ESP) partition..."
	[ -d "$destdir"/boot/efi ] ||
		run mkdir -p -m 0755 $verbose -- "$destdir"/boot/efi
	[ -z "$esp_part" ] ||
		run mount -t vfat -o "$esp_opts" $verbose -- \
					"$esp_part" "$destdir"/boot/efi
	! file_exists esp.tgz ||
		unpack_image esp /boot/efi
	run mkdir -p -m 0755 $verbose -- "$destdir"/boot/efi/EFI
fi

# Recovering the user data partition
if [ -n "$datapart" ] || file_exists home.tgz || file_exists var.tgz; then
	msg "Restoring %s (user data) partition..." "$datapart_mp"

	if [ "$datapart_mp" = /home ]; then
		run mkdir -p -m 0700 $verbose -- "$destdir"/home
	else
		run mkdir -p -m 0755 $verbose -- "$destdir"/var
	fi

	if [ -n "$datapart" ]; then
		run mount -t ext4 -o "$dataopts" $verbose -- \
				"$datapart" "${destdir}${datapart_mp}"
	fi

	if file_exists home.tgz; then
		unpack_image home "$datapart_mp"
	elif file_exists var.tgz; then
		unpack_image var "$datapart_mp"
	fi
fi

# Create a temporary directory for chroot
run mkdir -p -m 0755 $verbose -- "$destdir"/tmp/DEPLOY

# Ensure uniqueness
msg "Personification..."
run dbus-uuidgen >"$destdir"/etc/machine-id
[ ! -d "$destdir"/var/lib/dbus ] ||
	run cp -Lf $verbose -- "$destdir"/etc/machine-id \
				"$destdir"/var/lib/dbus/machine-id
[ "$template" = "$computer" ] || [ ! -f "$destdir"/etc/sysconfig/network ] ||
	run sed -i "s,$template,$computer," "$destdir"/etc/sysconfig/network
printf "%s\n" "$computer" >"$destdir"/etc/hostname
if [ -s "$workdir"/RNDSEED ]; then
	dummy="${destdir}$(run head -n1 -- "$workdir"/RNDSEED)"
	if [ -d "${dummy%/*}" ]; then
		run head -c512 -- /dev/urandom >"$dummy"
		run chmod $quiet $verbose -- 0600 "$dummy"
	fi
	unset dummy
fi

# Recreate the /etc/fstab file
mkfstab >"$destdir"/etc/fstab
run chmod $quiet $verbose -- 0644 "$destdir"/etc/fstab
fdump "$destdir"/etc/fstab

# Recreate GRUB early boot config
if [ -d "$destdir/boot/efi/EFI/$bootldr_id" ]; then
	[ -n "$bootpart" ] && dummy="" || dummy="/boot"
	cat >"$destdir/boot/efi/EFI/$bootldr_id"/grub.cfg <<-EOF
	search.fs_uuid $bootuuid root 
	set prefix=(\$root)'$dummy/grub'
	configfile \$prefix/grub.cfg
	EOF
	unset dummy
	run chmod $quiet $verbose -- 0644 \
		"$destdir/boot/efi/EFI/$bootldr_id"/grub.cfg
	fdump "$destdir/boot/efi/EFI/$bootldr_id"/grub.cfg
fi

# Update the OEM Setup wizard step list
if [ -n "$oem_setup_steps" ]; then
	( for step in $oem_setup_steps setup-finish; do
		printf '%s\n' "$step"
	  done
	  unset step
	) >"$destdir"/tmp/DEPLOY/OEM-steps
	fdump "$destdir"/tmp/DEPLOY/OEM-steps
fi

# Define the boot parameters of the target system
[ -n "$bootargs" ] || [ ! -s "$destdir"/etc/sysconfig/grub2 ] || {
	bootargs="$(read_kernel_args "$destdir"/etc/sysconfig/grub2)"
	e2kadd_bootargs=
}
[ -z "$swappart" ] || [ -n "$no_hibernate" ] ||
	bootargs="resume=UUID=$swapuuid $bootargs"
[ "$platform" != elbrus ] || [ -z "$e2kadd_bootargs" ] ||
	bootargs="$e2kadd_bootargs $bootargs"
[ -z "$oem_setup_steps" ] && [ ! -s "$destdir"/etc/alterator-setup/steps ] ||
	bootargs="$bootargs systemd.unit=setup.target"

# Changing GRUB device names
f="$destdir/etc/sysconfig/grub2"
if [ -s "$f" ]; then
	k="GRUB_CMDLINE_LINUX_DEFAULT"
	run sed -i -E "s|^$k=.*$|$k='$bootargs '|" "$f"
	k="GRUB_AUTOUPDATE_DEVICE"
	v="$(run mountpoint -x -- "$target")"
	v="$(run grep -s "S:disk/by-id/" "/run/udev/data/b$v" |
					head -n1 |cut -f2- -d:)"
	run sed -i -E "s|^#?$k=.*$|$k='/dev/$v '|" "$f"
	run chmod $quiet $verbose -- 0644 "$f"
	fdump "$f"
	unset v k
fi
unset f

# Renaming the only wired network interface
if [ -n "$oldifname" ] && [ -d "$destdir/etc/net/ifaces/$oldifname" ] &&
	[ -n "$newifname" ] && [ "$newifname" != "$oldifname" ] &&
	[ ! -d "$destdir/etc/net/ifaces/$newifname" ]
then
	run mv -f $verbose -- "$destdir/etc/net/ifaces/$oldifname" \
				"$destdir/etc/net/ifaces/$newifname"
	[ ! -s "$destdir/etc/net/ifaces/$newifname"/options ] ||
		fdump "$destdir/etc/net/ifaces/$newifname"/options
fi

# Create a file with the version of the
# deployed system and the deployment date
#
if [ -n "$release_file" ]; then
	printf "%s %s\n" "$release" "$(env LC_TIME=C date +'%F')" \
					>"${destdir}${release_file}"
fi

# Preparing a temporary directory for chroot
#
if [ -n "$logfile" ]; then
	:> "$destdir/tmp/DEPLOY/$progname.log"
	run mount --bind $verbose -- "$logfile" \
			"$destdir/tmp/DEPLOY/$progname.log"
fi
if [ -n "$frontend_pipe" ]; then
	:> "$destdir"/tmp/DEPLOY/frontend-sock
	run mount --bind $verbose -- "$frontend_pipe" \
			"$destdir"/tmp/DEPLOY/frontend.sock
fi
if [ "$backup_proto" = file ] && dir_exists rootfs; then
	run cp -aTRf $verbose -- "$backup"/rootfs "$destdir"
elif file_exists update.tgz; then
	read_file update.tgz |run unpigz -qnc |
		run tar -xpSf - --overwrite $verbose -C "$destdir"
fi
#
! file_exists sysrest.ini ||
	read_file sysrest.ini >"$destdir"/tmp/DEPLOY/sysrest.ini
run cp -Lf $verbose -- "$libdir"/restore/defaults.sh "$destdir"/tmp/DEPLOY/
run cp -Lf $verbose -- "$libdir"/restore/internal.sh "$destdir"/tmp/DEPLOY/
run cp -Lf $verbose -- "$libdir"/restore/chroot.sh "$destdir"/tmp/DEPLOY/
run cp -Lf $verbose -- "$libdir"/common.sh "$destdir"/tmp/DEPLOY/
run cp -Lf $verbose -- "$libdir"/logger.sh "$destdir"/tmp/DEPLOY/
run chmod $quiet $verbose -- 0755 "$destdir"/tmp/DEPLOY/chroot.sh

# Add global hooks to chroot
if [ -z "$disable_hooks" ]; then
	! file_exists chroot-pre.sh ||
		read_file chroot-pre.sh >"$destdir"/tmp/DEPLOY/chroot-pre.sh
	! file_exists chroot-post.sh ||
		read_file chroot-post.sh >"$destdir"/tmp/DEPLOY/chroot-post.sh
fi

# Add files from profile to chroot
if [ -n "$profile_subdir" ]; then
	if [ "$backup_proto" = file ] &&
	   dir_exists "$profile_subdir"/rootfs
	then
		run cp -aTRf $verbose -- \
			"$backup/$profile_subdir"/rootfs "$destdir"
	elif file_exists "$profile_subdir"/update.tgz; then
		read_file "$profile_subdir"/update.tgz |run unpigz -qnc |
			run tar -xpSf - --overwrite $verbose -C "$destdir"
	fi

	if file_exists "$profile_subdir"/sysrest.ini; then
		read_file "$profile_subdir"/sysrest.ini \
			>>"$destdir"/tmp/DEPLOY/sysrest.ini
	fi

	if [ -z "$disable_hooks" ]; then
		! file_exists "$profile_subdir"/chroot-pre.sh ||
			read_file "$profile_subdir"/chroot-pre.sh \
				>"$destdir"/tmp/DEPLOY/chroot-pre.sh
		! file_exists "$profile_subdir"/chroot-post.sh ||
			read_file "$profile_subdir"/chroot-post.sh \
				>"$destdir"/tmp/DEPLOY/chroot-post.sh
	fi
fi

# Determining whether grub-pc bootloader needs to be installed
if [ "$platform" != x86_64 ] && [ "$platform" != i586 ]; then
	dummy=
elif [ -z "$uefiboot" ] || [ -n "$biosboot_too" ]; then
	dummy=1
elif [ -n "$bbp_part" ] || [ -z "$esp_part" ]; then
	dummy=1
else
	dummy=
fi

# Preparing the chroot parameters file
cat >"$destdir"/tmp/DEPLOY/chroot.ini <<-EOF
HAVE_ESP=${esp_part:+1}
HAVE_BIOS=$dummy
HAVE_PReP=${preppart:+1}
quiet="$quiet"
verbose="$verbose"
SYSREST_DEBUG=${SYSREST_DEBUG:+1}
frontend_pipe="${frontend_pipe:+/tmp/DEPLOY/frontend.sock}"
alterator=
logfile="${logfile:+/tmp/DEPLOY/$progname.log}"
logprio="$logprio"
progname="$progname"
target="$target"
action="$action"
EOF
unset dummy
fdump "$destdir"/tmp/DEPLOY/chroot.ini

# Performing other post-installation actions before entering chroot
track_hook before_chroot

# No longer needed
unset release_file
unset oldifname
unset newifname
unset bootargs
unset template

# Clear all entries from NVRAM: UEFI boot mode only
if [ -z "$no_nvram" ] && [ -n "$uefiboot" ]; then
	[ -n "$no_clear_nvram" ] || clear_nvram
fi

# One-time chroot into the target system
run mount --bind $verbose -- /dev "$destdir"/dev
run env -i PATH="$PATH" TERM=linux USER=root \
	LANG=C LC_ALL=C TMPDIR=/tmp/DEPLOY   \
	chroot "$destdir" /tmp/DEPLOY/chroot.sh
will_be_run umount $quiet $verbose -- "$destdir"/dev
umount $quiet $verbose -- "$destdir"/dev 2>/dev/null ||
	run umount -fl $quiet $verbose -- "$destdir"/dev ||:
[ -z "$logfile" ] ||
	run umount $quiet $verbose -- "$destdir/tmp/DEPLOY/$progname.log"
[ -z "$frontend_pipe" ] ||
	run umount $quiet $verbose -- "$destdir"/tmp/DEPLOY/frontend-sock
run rm -rf $verbose --one-file-system -- "$destdir"/tmp/DEPLOY

# Ensure reliable loading
if [ -n "$safe_uefi_boot" ]; then
	if [ -d "$destdir"/boot/efi/EFI/BOOT ]; then
		[ -n "$bootpart" ] && dummy="" || dummy="/boot"
		cat >"$destdir"/boot/efi/EFI/BOOT/grub.cfg <<-EOF
		search.fs_uuid $bootuuid root 
		set prefix=(\$root)'$dummy/grub'
		configfile \$prefix/grub.cfg
		EOF
		unset dummy
		run chmod $quiet $verbose -- 0644 \
				"$destdir"/boot/efi/EFI/BOOT/grub.cfg
		fdump "$destdir"/boot/efi/EFI/BOOT/grub.cfg
	elif [ -d "$destdir/boot/efi/EFI/$bootldr_id" ]; then
		run cp -aRf $verbose -- \
			"$destdir/boot/efi/EFI/$bootldr_id" \
			"$destdir"/boot/efi/EFI/BOOT
		dummy="BOOT$(echo "$efi_suffix" |
				tr '[[:lower:]]' '[[:upper:]]')"
		if [ -s "$destdir/boot/efi/EFI/BOOT/$dummy" ]; then
			: # Do nothing at this point
		elif [ -s "$destdir/boot/efi/EFI/BOOT/shim$efi_suffix" ]; then
			run mv -f $verbose -- \
				"$destdir/boot/efi/EFI/BOOT/shim$efi_suffix" \
				"$destdir/boot/efi/EFI/BOOT/$dummy"
		elif [ -s "$destdir/boot/efi/EFI/BOOT/grub$efi_suffix" ]; then
			run mv -f $verbose -- \
				"$destdir/boot/efi/EFI/BOOT/grub$efi_suffix" \
				"$destdir/boot/efi/EFI/BOOT/$dummy"
		fi
		unset dummy
	fi
fi

# Performing other post-installation actions after exiting the chroot
track_hook after_chroot

# Creating a boot entry in NVRAM: UEFI boot mode only
if [ -z "$no_nvram" ] && [ -n "$uefiboot" ]; then
	[ "$platform" = ppc64le ] || record_nvram
fi

# Unmount all partitions of the target disk
{ [ -z "$datapart" ] ||
	umount $quiet $verbose -- "${destdir}${datapart_mp}" ||:
  [ -z "$esp_part" ] ||
	umount $quiet $verbose -- "$destdir"/boot/efi ||:
  [ -z "$bootpart" ] ||
	umount $quiet $verbose -- "$destdir"/boot ||:
  umount $quiet $verbose -- "$destdir" ||
	umount -fl $quiet $verbose -- "$destdir" ||:
} 2>/dev/null

# No longer needed
unset datapart_mp
unset bootuuid
unset swapuuid
unset preppart
unset esp_part
unset bbp_part
unset bootpart
unset swappart
unset rootpart
unset datapart

# Create a file to turn off or reboot the computer and make it
# executable: this is necessary to unmount the /mnt/autorun
# partition from which the autorun script is launched
#
if [ -n "$finalact" ]; then
	cat >/tmp/finish.sh <<-EOF
	#!/bin/sh
	umount $quiet $verbose -- "\$1" 2>/dev/null ||
	    umount -fl $quiet $verbose -- "\$1"
	$finalact
	exit 1
	EOF

	if run mountpoint -q /tmp; then
		will_be_run mount -o remount,exec $verbose /tmp
		mount -o remount,exec $verbose /tmp 2>/dev/null ||:
	fi

	run chmod $quiet $verbose -- 0755 /tmp/finish.sh
fi

# Final cleaning and shutdown or restart the computer
#
if [ -z "$quiet" ]; then
	echo -e "$CLR_OK"
	echo "Computer '$computer' restored successfully!"
	echo "Enjoy! ;-)"
	echo -e "$CLR_NORM"
fi
#
if [ -n "$SYSREST_DEBUG" ]; then
	[ -z "$quiet" ] ||
		exec 1>&3 3>&-
	quiet=
	[ -n "$no_nvram" ] || [ -z "$uefiboot" ] || {
		run efibootmgr ||:
		echo
	}
	run lsblk -f -- "$target" ||:
	echo
	run sfdisk -l -- "$target" ||:
	echo
	dummy="Press Ctrl-D to exit the debug shell..."
	echo -e "${CLR_WARN}${dummy}${CLR_NORM}"
	PS1="(${CLR_LC2}DEBUG${CLR_NORM}) # " /bin/bash -i 2>&1
fi
#
if [ -n "$finalact" ]; then
	exec /tmp/finish.sh "$(pwd)"
	$finalact
	unexpected_error "$progname" "$LINENO"
fi

