#!/bin/sh # # Copyleft 2012 Gui Iribarren # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # ### ### Usage: $0 [ [-w time] | now ] [-f time] ### ### $0 falls back to a last-known-good config after an ill-fated reboot. ### ### It makes a backup of /etc, and after the grace time reboots the system. ### At the next boot, it waits again for a fallback timeout; if you can't ### login and delete the backup, it will restore it and reboot. ### This mechanism makes it possible to run $0, try risky operations (like ### restarting the network), and if something goes wrong so you can't ### login, just wait for the fallback timeout. ### Even more, you can make actual changes to /etc config and reboot. If it ### comes up right, you can login and simply delete the backup ### /.etc.last-good.tgz . But if something went wrong with the new /etc, ### again just wait for the revert timeout. ### ### -w, --wait, --in TIME ### After backing up /overlay/upper/etc, wait for TIME before reboot. ### Default: 5min ### -f, --fallback-after TIME ### After boot, wait for TIME before reverting /overlay/upper/etc from ### backup found in /overlay/upper/.etc.last-good.tgz ### Default: 10min ### now ### Do not make /overlay/upper/etc backup; instead check that there's ### one already in place (/overlay/upper/.etc.last-good.tgz), then reboot ### and wait for fallback timeout. ### cancel ### Remove /overlay/upper/.etc.last-good.tgz ### (useful after a successful reboot) ### discard ### Restores /overlay/upper/etc from /overlay/upper/.etc.last-good.tgz. ### (useful to discard changes) ### ### TIME examples: 1hour 60min 60m 3600sec 3600 ### (all of them are equivalent) ### # # PIDFILE="/tmp/run/safe-reboot.pid" fallback_timeout=600 grace_period=300 file_etc_lastgood="/overlay/upper/.etc.last-good.tgz" dir_etc="/overlay/upper/etc/" safe_fallback_script="/overlay/upper/etc/init.d/safe-fallback" safe_fallback_script_enable="ln -sf ../init.d/safe-fallback /overlay/upper/etc/rc.d/S11safe-fallback" cmd_force_reboot="echo b > /proc/sysrq-trigger" # Immediately reboot the system without syncing or unmounting disks. usage () { reason="$*" [ -n "$reason" ] && echo "$reason" sed -n "/^### /{s///;s|\$0|$(basename "$0")|g;p}" "$0" exit 1 } timetoseconds() { # valid and equivalent input examples: 3600 3600s 3600sec 60minutes 1ho local time="$1" ; local seconds { [ "${time%s*}" -ge 0 ] 2> /dev/null && seconds="${time%s*}"; } \ ||{ [ "${time%m*}" -ge 0 ] 2> /dev/null && seconds="$((${time%m*}*60))"; } \ ||{ [ "${time%h*}" -ge 0 ] 2> /dev/null && seconds="$((${time%h*}*3600))"; } echo $seconds } backup_etc_exists () { [ -s "$file_etc_lastgood" ] } backup_etc () { rm -f "$file_etc_lastgood" tar -czf "$file_etc_lastgood" -C "$dir_etc" . } write_safe_fallback_script () { mkdir -p "$(dirname "$safe_fallback_script")" rm -f "$safe_fallback_script" touch "$safe_fallback_script" chmod +x "$safe_fallback_script" cat < "$safe_fallback_script" #!/bin/sh /etc/rc.common START=11 start() { ( [ -s "$file_etc_lastgood" ] \ && sleep $fallback_timeout & trap 'kill \$! ; exit' TERM ; wait \ && if [ -s $file_etc_lastgood ] ; then safe-reboot discard fi ) & echo \$! > "$PIDFILE" } EOF mkdir -p "$dir_etc"/rc.d/ $safe_fallback_script_enable } reboot_after_grace_period () { sleep $grace_period & trap 'kill $! ; exit' TERM ; wait write_safe_fallback_script reboot ; sleep 10 ; eval "$cmd_force_reboot" } action_cancel () { rm -f "$file_etc_lastgood" rm -f "$safe_fallback_script" echo -n "Removed $file_etc_lastgood, " if [ -s "$PIDFILE" ] ; then kill "$(cat "$PIDFILE")" rm -f "$PIDFILE" echo "and killed running timeout." else echo "no running timeout found." fi } action_discard () { if backup_etc_exists ; then rm -f "$safe_fallback_script" rm -rf $dir_etc ; mkdir -p $dir_etc tar -xzf $file_etc_lastgood -C $dir_etc rm -f $file_etc_lastgood reboot ; sleep 10 ; eval "$cmd_force_reboot" else echo "STOP! Backup file $file_etc_lastgood not found or 0 sized! Refusing to continue." exit 2 fi } while [ -n "$1" ]; do arg="$1" shift case "$arg" in --in | in | --wait | wait | -w ) grace_period=$(timetoseconds "$1") ; shift [ -z "$grace_period" ] && usage "Error with grace period value." ;; --fallback-after | -f ) fallback_timeout=$(timetoseconds "$1") ; shift [ -z "$fallback_timeout" ] && usage "Error with fallback timeout value." ;; --discard | discard ) action_discard ; exit ;; --cancel | cancel ) action_cancel ; exit ;; now ) grace_period=0 ;; --help | -h ) usage ;; esac done if [ "$grace_period" -eq 0 ] ; then backup_etc_exists || { echo "STOP! Backup file $file_etc_lastgood not found! Refusing to continue." ; exit 2 ; } else backup_etc_exists && { echo "STOP! Backup file $file_etc_lastgood exists! You probably want to \`$0 now\`" ; exit 4 ; } backup_etc || { echo "STOP! Backup failed. Refusing to continue." ; exit 8 ; } fi write_safe_fallback_script || { echo "STOP! Writing fallback script failed. Refusing to continue." ; exit 16 ; } reboot_after_grace_period & { echo "Rebooting in $grace_period seconds..." ; } echo $! > "$PIDFILE"