diff --git a/dracut/99emergency-timeout/module-setup.sh b/dracut/99emergency-timeout/module-setup.sh new file mode 100755 index 0000000..26ac01a --- /dev/null +++ b/dracut/99emergency-timeout/module-setup.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh + +install() { + inst_multiple \ + cut \ + date + + inst_hook emergency 99 "${moddir}/timeout.sh" +} diff --git a/dracut/99emergency-timeout/timeout.sh b/dracut/99emergency-timeout/timeout.sh new file mode 100644 index 0000000..e95ac16 --- /dev/null +++ b/dracut/99emergency-timeout/timeout.sh @@ -0,0 +1,91 @@ +# Before starting the emergency shell, prompt the user to press Enter. +# If they don't, reboot the system. +# +# Assumes /bin/sh is bash. + +# _wait_for_journalctl_to_stop will block until either: +# - no messages have appeared in journalctl for the past 5 seconds +# - 15 seconds have elapsed +_wait_for_journalctl_to_stop() { + local time_since_last_log=0 + + local time_started="$(date '+%s')" + local now="$(date '+%s')" + + while [ ${time_since_last_log} -lt 5 -a $((now-time_started)) -lt 15 ]; do + sleep 1 + + local last_log_timestamp="$(journalctl -e -n 1 -q -o short-unix | cut -d '.' -f 1)" + local now="$(date '+%s')" + + local time_since_last_log=$((now-last_log_timestamp)) + done +} + +_prompt_for_timeout() { + local timeout=300 + local interval=15 + + if [[ -e /.emergency-shell-confirmed ]]; then + return + fi + ignition_units="ignition-disks.service ignition-files.service" + if systemctl show $ignition_units | grep -q "^ActiveState=failed$"; then + # Ignition has failed, suppress kernel logs so that Ignition logs stay + # on the screen + dmesg --console-off + + # There's a couple straggler systemd messages. Wait until it's been 5 + # seconds since something was written to the journal. + _wait_for_journalctl_to_stop + + # Print Ignition logs + cat < 0 ]]; do + local m=$(( $timeout / 60 )) + local s=$(( $timeout % 60 )) + local m_label="minutes" + if [[ $m = 1 ]]; then + m_label="minute" + fi + + if [[ $s != 0 ]]; then + echo -n -e "Press Enter for emergency shell or wait $m $m_label $s seconds for reboot. \r" + else + echo -n -e "Press Enter for emergency shell or wait $m $m_label for reboot. \r" + fi + + local anything + if read -t $interval anything; then + > /.emergency-shell-confirmed + return + fi + timeout=$(( $timeout - $interval )) + done + + echo -e "\nRebooting." + # This is not very nice, but since reboot.target likely conflicts with + # the existing goal target wrt the desired state of shutdown.target, + # there doesn't seem to be a better option. + systemctl reboot --force + exit 0 +} + +# If we're invoked from a dracut breakpoint rather than +# dracut-emergency.service, we won't have a controlling terminal and stdio +# won't be connected to it. Explicitly read/write /dev/console. +_prompt_for_timeout < /dev/console > /dev/console