{ config, pkgs, ... }: with pkgs.lib; { jobs.shutdown = { name = "shutdown"; task = true; stopOn = ""; # must override the default ("starting shutdown") environment = { MODE = "poweroff"; }; extraConfig = "console owner"; script = '' set +e # continue in case of errors ${pkgs.kbd}/bin/chvt 1 exec < /dev/console > /dev/console 2>&1 echo "" if test "$MODE" = maintenance; then echo "<<< Entering maintenance mode >>>" else echo "<<< System shutdown >>>" fi echo "" ${config.powerManagement.powerDownCommands} export PATH=${pkgs.utillinux}/bin:${pkgs.utillinux}/sbin:$PATH # Do an initial sync just in case. sync # Kill all remaining processes except init, this one and any # Upstart jobs that don't stop on the "starting shutdown" # event, as these are necessary to complete the shutdown. omittedPids=$(initctl list | sed -e 's/.*process \([0-9]\+\)/-o \1/;t;d') #echo "saved PIDs: $omittedPids" echo "sending the TERM signal to all processes..." ${pkgs.sysvtools}/bin/killall5 -15 $job $omittedPids sleep 1 # wait briefly echo "sending the KILL signal to all processes..." ${pkgs.sysvtools}/bin/killall5 -9 $job $omittedPids # If maintenance mode is requested, start a root shell, and # afterwards emit the "startup" event to bring everything # back up. if test "$MODE" = maintenance; then echo "" echo "<<< Maintenance shell >>>" echo "" ${pkgs.shadow}/bin/login root initctl emit -n startup exit 0 fi # Write a shutdown record to wtmp while /var/log is still writable. reboot --wtmp-only # Set the hardware clock to the system time. echo "setting the hardware clock..." hwclock --systohc --utc # Stop all swap devices. swapoff -a # Unmount file systems. We repeat this until no more file systems # can be unmounted. This is to handle loopback devices, file # systems mounted on other file systems and so on. tryAgain=1 while test -n "$tryAgain"; do tryAgain= failed= # list of mount points that couldn't be unmounted/remounted # Get rid of loopback devices. loDevices=$(losetup -a | sed 's#^\(/dev/loop[0-9]\+\).*#\1#') if [ -n "$loDevices" ]; then echo "removing loopback devices $loDevices..." losetup -d $loDevices fi cp /proc/mounts /dev/.mounts # don't read /proc/mounts while it's changing exec 4< /dev/.mounts while read -u 4 device mp fstype options rest; do # Skip various special filesystems. Non-existent # mount points are typically tmpfs/aufs mounts from # the initrd. if [ "$mp" = /proc -o "$mp" = /sys -o "$mp" = /dev -o "$device" = "rootfs" -o "$mp" = /run -o "$mp" = /var/run -o "$mp" = /var/lock -o ! -e "$mp" ]; then continue; fi echo "unmounting $mp..." # We need to remount,ro before attempting any # umount, or bind mounts may get confused, with # the fs not being properly flushed at the end. # `-i' is to workaround a bug in mount.cifs (it # doesn't recognise the `remount' option, and # instead mounts the FS again). success= if mount -t "$fstype" -n -i -o remount,ro "device" "$mp"; then success=1; fi # Note: don't use `umount -f'; it's very buggy. # (For instance, when applied to a bind-mount it # unmounts the target of the bind-mount.) !!! But # we should use `-f' for NFS. if [ "$mp" != / -a "$mp" != /nix -a "$mp" != /nix/store ]; then if umount -n "$mp"; then success=1; tryAgain=1; fi fi if [ -z "$success" ]; then failed="$failed $mp"; fi done done # Warn about filesystems that could not be unmounted or # remounted read-only. if [ -n "$failed" ]; then echo "warning: the following filesystems could not be unmounted:" for mp in $failed; do echo " $mp"; done echo Enter 'i' to launch a shell, or wait 10 seconds to continue. read -t 10 A if [ "$A" == "i" ]; then ${pkgs.bashInteractive}/bin/bash -i < /dev/console &> /dev/console fi sleep 5 fi # Final sync. sync # Either reboot or power-off the system. if test "$MODE" = reboot; then echo "rebooting..." sleep 1 exec reboot -f else echo "powering off..." sleep 1 exec halt -f -p fi ''; }; }