diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/helpers/run-config-migration.py | 7 | ||||
-rwxr-xr-x | src/init/vyos-router | 112 | ||||
-rwxr-xr-x | src/op_mode/image_info.py | 8 | ||||
-rwxr-xr-x | src/op_mode/image_installer.py | 8 |
4 files changed, 111 insertions, 24 deletions
diff --git a/src/helpers/run-config-migration.py b/src/helpers/run-config-migration.py index e6ce97363..8e0d56150 100755 --- a/src/helpers/run-config-migration.py +++ b/src/helpers/run-config-migration.py @@ -19,6 +19,7 @@ import sys import time from argparse import ArgumentParser from shutil import copyfile +from vyos.utils.file import read_file from vyos.migrate import ConfigMigrate from vyos.migrate import ConfigMigrateError @@ -76,3 +77,9 @@ except ConfigMigrateError as e: if backup is not None and not config_migrate.config_modified: os.unlink(backup) + +# T1771: add knob on Kernel command-line to simulate failed config migrator run +# used to test if the automatic image reboot works. +kernel_cmdline = read_file('/proc/cmdline') +if 'vyos-fail-migration' in kernel_cmdline.split(): + sys.exit(1) diff --git a/src/init/vyos-router b/src/init/vyos-router index 6f1d386d6..5c88c0665 100755 --- a/src/init/vyos-router +++ b/src/init/vyos-router @@ -67,37 +67,50 @@ disabled () { grep -q -w no-vyos-$1 /proc/cmdline } +motd_helper() { + MOTD_DIR="/run/motd.d" + MOTD_FILE="${MOTD_DIR}/99-vyos-update-failed" + + if [[ ! -d ${MOTD_DIR} ]]; then + mkdir -p ${MOTD_DIR} + fi + + echo "" > ${MOTD_FILE} + echo "WARNING: Image update to \"$1\" failed." >> ${MOTD_FILE} + echo "Please check the logs:" >> ${MOTD_FILE} + echo "/usr/lib/live/mount/persistence/boot/$1/rw/var/log" >> ${MOTD_FILE} + echo "Message is cleared on next reboot!" >> ${MOTD_FILE} + echo "" >> ${MOTD_FILE} +} + # Load encrypted config volume mount_encrypted_config() { persist_path=$(/opt/vyatta/sbin/vyos-persistpath) if [ $? == 0 ]; then if [ -e $persist_path/boot ]; then image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//') - if [ -z "$image_name" ]; then - return + return 0 fi if [ ! -f $persist_path/luks/$image_name ]; then - return + return 0 fi vyos_tpm_key=$(python3 -c 'from vyos.tpm import read_tpm_key; print(read_tpm_key().decode())' 2>/dev/null) - if [ $? -ne 0 ]; then echo "ERROR: Failed to fetch encryption key from TPM. Encrypted config volume has not been mounted" echo "Use 'encryption load' to load volume with recovery key" echo "or 'encryption disable' to decrypt volume with recovery key" - return + return 1 fi echo $vyos_tpm_key | tr -d '\r\n' | cryptsetup open $persist_path/luks/$image_name vyos_config --key-file=- - if [ $? -ne 0 ]; then echo "ERROR: Failed to decrypt config volume. Encrypted config volume has not been mounted" echo "Use 'encryption load' to load volume with recovery key" echo "or 'encryption disable' to decrypt volume with recovery key" - return + return 1 fi mount /dev/mapper/vyos_config /config @@ -106,6 +119,7 @@ mount_encrypted_config() { echo "Mounted encrypted config volume" fi fi + return 0 } unmount_encrypted_config() { @@ -160,11 +174,16 @@ migrate_bootfile () if [ -x $vyos_libexec_dir/run-config-migration.py ]; then log_progress_msg migrate sg ${GROUP} -c "$vyos_libexec_dir/run-config-migration.py $BOOTFILE" + STATUS=$? + if [[ "$STATUS" != "0" ]]; then + return 1 + fi # update vyconf copy after migration if [ -d $VYCONF_CONFIG_DIR ] ; then cp -f $BOOTFILE $VYCONF_CONFIG_DIR/config.boot fi fi + return 0 } # configure system-specific settings @@ -187,8 +206,13 @@ load_bootfile () fi if [ -x $vyos_libexec_dir/vyos-boot-config-loader.py ]; then sg ${GROUP} -c "$vyos_libexec_dir/vyos-boot-config-loader.py $BOOTFILE" + STATUS=$? + if [[ "$STATUS" != "0" ]]; then + return 1 + fi fi ) + return 0 } # restore if missing pre-config script @@ -289,10 +313,10 @@ clear_or_override_config_files () keepalived/keepalived.conf cron.d/vyos-crontab \ ipvsadm.rules default/ipvsadm resolv.conf do - if [ -s /etc/$conf ] ; then - empty /etc/$conf - chmod 0644 /etc/$conf - fi + if [ -s /etc/$conf ] ; then + empty /etc/$conf + chmod 0644 /etc/$conf + fi done } @@ -417,6 +441,8 @@ gen_duid () start () { + log_success_msg "Starting VyOS router" + # reset and clean config files security_reset || log_failure_msg "security reset failed" @@ -482,7 +508,7 @@ start () # enable some debugging before loading the configuration if grep -q vyos-debug /proc/cmdline; then - log_action_begin_msg "Enable runtime debugging options" + log_success_msg "Enable runtime debugging options" FRR_DEBUG=$(python3 -c "from vyos.defaults import frr_debug_enable; print(frr_debug_enable)") touch $FRR_DEBUG touch /tmp/vyos.container.debug @@ -509,7 +535,7 @@ start () && chgrp ${GROUP} ${vyatta_configdir} log_action_end_msg $? - mount_encrypted_config + mount_encrypted_config || overall_status=1 # T5239: early read of system hostname as this value is read-only once during # FRR initialisation @@ -525,8 +551,7 @@ start () cleanup_post_commit_hooks - log_daemon_msg "Starting VyOS router" - disabled migrate || migrate_bootfile + disabled migrate || migrate_bootfile || overall_status=1 restore_if_missing_preconfig_script @@ -534,27 +559,66 @@ start () run_postupgrade_script - update_interface_config + update_interface_config || overall_status=1 - disabled system_config || system_config + disabled system_config || system_config || overall_status=1 systemctl start vyconfd.service for s in ${subinit[@]} ; do - if ! disabled $s; then - log_progress_msg $s - if ! ${vyatta_sbindir}/${s}.init start - then log_failure_msg - exit 1 + if ! disabled $s; then + log_progress_msg $s + if ! ${vyatta_sbindir}/${s}.init start + then log_failure_msg + exit 1 + fi fi - fi done bind_mount_boot - disabled configure || load_bootfile + disabled configure || load_bootfile || overall_status=1 log_end_msg $? + FIRST_BOOT_FILE="/config/first_boot" + UPDATE_FAILED_BOOT_FILE="/config/update_failed" + AUTOMATIC_REBOOT_TMO=$(${vyos_libexec_dir}/read-saved-value.py --path "system option reboot-on-upgrade-failure") + # Image upgrade failed - get previous image name, re-set it as default image + # and perform an automatic reboot. Automatic reboot timeout can be set via CLI + if [[ -n $AUTOMATIC_REBOOT_TMO ]] && [[ -f ${FIRST_BOOT_FILE} ]] && [[ ${overall_status} -ne 0 ]]; then + previous_image=$(jq -r '.previous_image' ${FIRST_BOOT_FILE}) + + # If the image update failed, we need to inform the image we will revert + # to about this + running_image=$(${vyos_op_scripts_dir}/image_info.py show_images_current --raw | jq -r '.image_running') + echo "{\"failed_image_update\": \"${running_image}\"}" \ + > /usr/lib/live/mount/persistence/boot/${previous_image}/rw/${UPDATE_FAILED_BOOT_FILE} + + ${vyos_op_scripts_dir}/image_manager.py --action set --image-name "${previous_image}" >/dev/null 2>&1 + motd_helper "${running_image}" + + log_daemon_msg "Booting failed, reverting to previous image" + log_progress_msg ${previous_image} + log_end_msg 0 + log_daemon_msg "Automatic reboot in ${AUTOMATIC_REBOOT_TMO} minutes" + sync ; shutdown --reboot --no-wall ${AUTOMATIC_REBOOT_TMO} >/dev/null 2>&1 + log_progress_msg "Use \"reboot cancel\" to cancel" + log_end_msg 0 + fi + # After image upgrade failure and once booted into the previous working + # image, inform the user via MOTD about the failure + if [[ -n $AUTOMATIC_REBOOT_TMO ]] && [[ -f ${UPDATE_FAILED_BOOT_FILE} ]] ; then + failed_image_update=$(jq -r '.failed_image_update' ${UPDATE_FAILED_BOOT_FILE}) + motd_helper "${failed_image_update}" + fi + # Clear marker files used by automatic reboot on image upgrade mechanism + if [[ -f ${FIRST_BOOT_FILE} ]]; then + rm -f ${FIRST_BOOT_FILE} + fi + if [[ -f ${UPDATE_FAILED_BOOT_FILE} ]] ; then + rm -f ${UPDATE_FAILED_BOOT_FILE} + fi + telinit q chmod g-w,o-w / diff --git a/src/op_mode/image_info.py b/src/op_mode/image_info.py index 56aefcd6e..0ec930543 100755 --- a/src/op_mode/image_info.py +++ b/src/op_mode/image_info.py @@ -72,6 +72,14 @@ def _format_show_images_details( return tabulated +def show_images_current(raw: bool) -> Union[image.BootDetails, str]: + + images_summary = show_images_summary(raw=True) + if raw: + return {'image_running' : images_summary['image_running']} + else: + return images_summary['image_running'] + def show_images_summary(raw: bool) -> Union[image.BootDetails, str]: images_available: list[str] = grub.version_list() diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py index ac5a84419..27371a18f 100755 --- a/src/op_mode/image_installer.py +++ b/src/op_mode/image_installer.py @@ -27,6 +27,7 @@ from os import readlink from os import getpid from os import getppid from json import loads +from json import dumps from typing import Union from urllib.parse import urlparse from passlib.hosts import linux_context @@ -54,6 +55,7 @@ from vyos.utils.dict import dict_search from vyos.utils.io import ask_input, ask_yes_no, select_entry from vyos.utils.file import chmod_2775 from vyos.utils.file import read_file +from vyos.utils.file import write_file from vyos.utils.process import cmd, run, rc_cmd from vyos.version import get_version_data @@ -1040,6 +1042,12 @@ def add_image(image_path: str, vrf: str = None, username: str = '', chmod_2775(target_config_dir) copytree('/opt/vyatta/etc/config/', target_config_dir, symlinks=True, copy_function=copy_preserve_owner, dirs_exist_ok=True) + + # Record information from which image we upgraded to the new one. + # This can be used for a future automatic rollback into the old image. + tmp = {'previous_image' : image.get_running_image()} + write_file(f'{target_config_dir}/first_boot', dumps(tmp)) + else: Path(target_config_dir).mkdir(parents=True) chown(target_config_dir, group='vyattacfg') |