Package: runit
Version: 2.2.0-6
Severity: wishlist
Tags: patch
Hi,
runit's shutdown(8) doesn't support scheduled shutdowns. This isn't normally
a problem (if you want, you can use at(1) to schedule a shutdown); but it
breaks the "Unattended-Upgrade::Automatic-Reboot-Time" feature of
unattended-upgrades.
I wrote a wrapper (in plain Bourne shell instead of zsh this time) for
runit's shutdown to support more of the features of systemd shutdown(8) more
or less transparently. This includes scheduled shutdowns (which are
implemented via at(1)). In my so far admittedly fairly minimal testing it
worked as intended.
Please consider including the wrapper in the runit package and enabling it
by default.
(I'd appreciate quick feedback on whether you will do this, because if not,
I'll build my own package, for my own use, that diverts runit's shutdown and
replaces it with my wrapper.)
Thanks!
András
-- System Information:
Init: runit (via /run/runit.stopit)
--
When you starve with a tiger, the tiger starves last.
#!/bin/sh
#
# This is a wrapper around the /usr/lib/runit/shutdown that's shipped by the
# Debian runit package. Its purpose is to add support for reboot times
# other than "now", by way of scheduling the future reboot as an at(1) job;
# but while I'm at it I might as well add support for all the other systemd
# shutdown options as well.
#
# Copyright (c) 2025 András Korn.
#
# Licensed under the GPL v3, or, at your option, and only if it's included
# in the runit package, under the BSD-3-clause license.
real_shutdown=/usr/lib/runit/shutdown.distrib
rundir=/run/runit/scheduled-shutdown
[ $# -eq 0 ] && exec "$real_shutdown"
wallmessage=""
timespec=""
wallonly=0
wall=1
shutdown_args=""
cancel_shutdown() {
if cd "$rundir" 2>/dev/null; then
j=$(echo *)
if ! [ "$j" = "*" ]; then
atrm $j
ret=$?
rm $j
cd / # currently superfluous as nothing that is run
after calling cancel_shutdown is sensitive to PWD, but it's good hygiene
return $ret
else
return 0
fi
else
return 0
fi
}
show_shutdown() {
if cd "$rundir" 2>/dev/null; then
j=$(echo *)
if ! [ "$j" = "*" ]; then
timespec=$(cat $j | sort -t: -n -k1,1 -k2,2 | head -1)
echo "shutdown or reboot scheduled for $timespec, use
'shutdown -c' to cancel."
fi
fi
exit 0
}
while [ -n "$1" ]; do
case "$1" in
-[hrfFHP]) shutdown_args="$shutdown_args
$1";;
now) shift; [ $# -gt 0 ] &&
wallmessage="${*}"; break;;
-k) wallonly=1;;
--no-wall) wall=0;;
-c) cancel_shutdown; exit $?;;
--show) show_shutdown;; #
doesn't return
+[0-9]*|[012][0-9]:[0-5][0-9]) timespec="$1"; shift; [ $# -gt
0 ] && wallmessage="${*}"; break;;
*) echo "$0: WARNING: argument
'$1' is unknown. Ignoring";; # Ignore unknown arguments. Reasoning: it's
better to be able to shut down without supporting some bell or whistle than to
prevent shutdown.
esac
shift
done
case "$timespec" in
"") [ -n "$wallmessage" ] && [ "$wall" = 1 ] && wall "The
system is going down NOW for: $wallmessage";;
+[0-9]*) timespec="$(date --date "now $timespec minutes"
'+%H:%M')";; # needs GNU date(1)
esac
if [ -n "$timespec" ]; then
if [ -x /usr/bin/at ]; then
# possible improvements: 1. include kind of shutdown in wall
message (reboot or halt); 2. schedule some wall messages a few minutes before
the actual shutdown.
cd / # make sure the pwd of the atjob is /, not some
directory on e.g. a network fs
if atoutput=$({
if [ "$wall" = 1 ]; then
if [ -n "$wallmessage" ]; then
echo "wall 'The system is going down
NOW for: $wallmessage'"
else
echo "wall 'The system is going down
NOW!'"
fi
[ "$wallonly" = 0 ] && echo "exec
\"$real_shutdown\" $shutdown_args"
fi
} | at "$timespec" 2>&1); then
jobnum=$(echo "$atoutput" | sed -n '/^job /{s/^job
//;s/ .*//;p}')
case "$jobnum" in # Is $jobnum an integer?
''|*[!0-9]*)
echo "$0: WARNING: I tried to schedule
an at(1) job for $timespec to perform a 'shutdown $shutdown_args', and the job
number is apparently '$jobnum', which is not an integer. I don't know what
happened, or whether the shutdown will take place at the scheduled time. Please
investigate." >&2;;
*)
mkdir -p "$rundir"
cancel_shutdown
# if there was a shutdown scheduled, cancel it -- we just scheduled a new one
echo "$timespec" >"$rundir/$jobnum"
# this is needed by show_shutdown and cancel_shutdown
;;
esac
else
echo "$0: WARNING: I tried to schedule an at(1) job for
$timespec to perform a 'shutdown $shutdown_args', and at(1) returned an error.
The system will probably not shut down at the scheduled time. For your
reference, here is the entire output of at(1):" >&2
echo "$atoutput" >&2
exit 2
fi
else
echo "FATAL: can't schedule shutdown for $timespec because
at(1) is apparently not available (/usr/bin/at can't be executed)." >&2
exit 1
fi
else
if [ -n "$wallmessage" ] && [ "$wall" = 1 ]; then
echo "wall 'The system is going down NOW for: $wallmessage'"
else
echo "wall 'The system is going down NOW!'"
fi
[ "$wallonly" = 0 ] && exec "$real_shutdown" $shutdown_args
fi
exit 0