Systemd: Difference between revisions
(→Automatically starting VDR when the storage is plugged in: Separate section "Shutting down VDR") |
(→Shutting down VDR: Clarify the unmounting and remounting logic) |
||
(10 intermediate revisions by the same user not shown) | |||
Line 3: | Line 3: | ||
Since 2015, the majority of Linux distributions have adopted systemd, having replaced other init systems such as SysV init. |
Since 2015, the majority of Linux distributions have adopted systemd, having replaced other init systems such as SysV init. |
||
==Preventing shutdown and reboot while VDR is running== |
|||
By default, the power button on a [[remote control]] that is managed by a kernel [[LIRC]] driver will be mapped to <code>systemd-logind</code>. Unless you have overridden the default <code>HandlePowerKey=poweroff</code> in <code>/etc/systemd/logind.conf</code>, the system would be shut down immediately when you hit a power button on any input device (remote control, keyboard, or power button on the computer case). |
|||
Often, VDR is the main application on the system. You would not want the system to be accidentally shut down or rebooted while someone is watching TV, or a recording is in progress. System shutdown would be initiated by a script that would be invoked by VDR itself; see the <code>-s</code> parameter of <code>ExecStart</code> below. |
|||
To disable or enable the normal handling of shutdown and reboot, you can use the following script, say, <code>/etc/systemd/system/vdr-keep-alive.sh</code>. Executing it will require super user privileges. |
|||
<pre> |
|||
#!/bin/sh |
|||
TARGETS=" |
|||
/lib/systemd/system/poweroff.target.d |
|||
/lib/systemd/system/reboot.target.d |
|||
/lib/systemd/system/halt.target.d |
|||
" |
|||
CONF=vdr-keep-alive.conf |
|||
case "$1" in |
|||
start) |
|||
for t in $TARGETS |
|||
do |
|||
if [ ! -f "$t/$CONF" ] |
|||
then |
|||
if [ ! -d "$t/" ] |
|||
then |
|||
mkdir "$t" |
|||
fi |
|||
echo "[Unit]\nRefuseManualStart=yes" > "$t/$CONF" |
|||
fi |
|||
done |
|||
;; |
|||
stop) |
|||
for t in $TARGETS |
|||
do |
|||
rm -f "$t/$CONF" |
|||
done |
|||
;; |
|||
esac |
|||
exec systemctl daemon-reload |
|||
</pre> |
|||
You can execute this script as follows: |
|||
<pre> |
|||
sudo /etc/systemd/system/vdr-keep-alive.sh start |
|||
sudo /etc/systemd/system/vdr-keep-alive.sh stop |
|||
</pre> |
|||
You might want to make the <code>TARGETS</code> above to include <code>suspend</code>, <code>hibernate</code>, or others mentioned in <code>man 7 systemd.special</code>. |
|||
==Making Systemd start up VDR== |
==Making Systemd start up VDR== |
||
Before systemd, you might have edited <code>/etc/gettydefs</code> to prevent a virtual console from being associated with a normal login prompt, and then have <code>init</code> invoke a shell script that would invoke VDR, often named <code>runvdr</code>. With systemd, |
Before systemd, you might have edited <code>/etc/gettydefs</code> to prevent a virtual console from being associated with a normal login prompt, and then have <code>init</code> invoke a shell script that would invoke VDR, often named <code>runvdr</code>. With systemd, you would execute <code>sudo systemctl disable getty@tty1</code> and write a systemd unit configuration file, say, <code>/etc/systemd/system/vdr.service</code>, with contents like the following: |
||
<pre> |
<pre> |
||
[Unit] |
[Unit] |
||
Description=Video Disk Recorder |
Description=Video Disk Recorder |
||
After=systemd-user-sessions.service plymouth-quit-wait.service |
#After=systemd-user-sessions.service plymouth-quit-wait.service rc-local.service |
||
After=rc-local.service |
|||
After=getty@tty1.service |
|||
Conflicts=getty@tty1.service |
Conflicts=getty@tty1.service |
||
#BindsTo=dev-disk-by\x2dlabel-VDR.device |
|||
#After=video.mount |
|||
Conflicts=shutdown.target |
Conflicts=shutdown.target |
||
ConditionPathExists=/video/video |
ConditionPathExists=/video/video |
||
Line 65: | Line 18: | ||
[Service] |
[Service] |
||
User=pi |
User=pi |
||
ExecStartPre=+/etc/systemd/system/vdr-keep-alive.sh start |
#ExecStartPre=+/etc/systemd/system/vdr-keep-alive.sh start |
||
ExecStart=/usr/local/bin/vdr --no-kbd --lirc=/dev/lirc0 -Prpihddevice -v /video/video -s /var/lib/vdr/vdr-shutdown.sh |
ExecStart=/usr/local/bin/vdr --no-kbd --lirc=/dev/lirc0 -Prpihddevice -v /video/video -s /var/lib/vdr/vdr-shutdown.sh |
||
#ExecStart=systemd-inhibit --what=idle:sleep:shutdown:handle-power-key:handle-suspend-key:handle-lid-switch --who=VDR --mode=block /usr/local/bin/vdr --no-kbd --lirc=/dev/lirc0 -Prpihddevice -v /video/video -s /var/lib/vdr/vdr-shutdown.sh |
|||
TimeoutStartSec=infinity |
TimeoutStartSec=infinity |
||
Type=idle |
Type=idle |
||
Line 72: | Line 26: | ||
RestartSec=1s |
RestartSec=1s |
||
TTYVTDisallocate=yes |
TTYVTDisallocate=yes |
||
[Install] |
[Install] |
||
WantedBy=display-manager.service |
WantedBy=display-manager.service |
||
Line 79: | Line 34: | ||
*<code>ExecStart=</code> must refer to the full VDR invocation. |
*<code>ExecStart=</code> must refer to the full VDR invocation. |
||
*<code>ConditionPathExists=</code> is for the directory that contains the recordings, matching the <code>-v</code> parameter in <code>ExecStart</code>. If the recordings cannot be mounted, the service would not start up. |
*<code>ConditionPathExists=</code> is for the directory that contains the recordings, matching the <code>-v</code> parameter in <code>ExecStart</code>. If the recordings cannot be mounted, the service would not start up. |
||
*<code>After=</code> specifies services that need to be started first. See <code>man 5 systemd.unit</code> for a description of various dependencies. |
|||
*The commented-out <code>BindsTo</code> and <code>After=video.mount</code> are for automatically starting up VDR when video storage is plugged in. |
|||
The shutdown script (named <code>/var/lib/vdr/vdr-shutdown.sh</code> in the above example) could do at least one of the following: |
The shutdown script (named <code>/var/lib/vdr/vdr-shutdown.sh</code> in the above example) could do at least one of the following: |
||
*Invoke <code>sudo /etc/systemd/system/vdr-keep-alive.sh stop</code> to allow normal reboot and shutdown. |
|||
*Adjust [[rtcwake]] (or [[Nvram_wakeup]]) to have the system start up automatically on the next scheduled recording. |
*Adjust [[rtcwake]] (or [[Nvram_wakeup]]) to have the system start up automatically on the next scheduled recording. |
||
*Shut down VDR or the entire system. |
|||
*Invoke <code>sudo systemctl stop vdr</code> or <code>sudo systemctl halt</code> to shut down VDR or the entire system. This will also terminate the shell that runs the script! |
|||
===Installation=== |
===Installation=== |
||
<pre> |
<pre> |
||
sudo systemctl disable getty@tty1 |
|||
sudo systemctl enable vdr |
sudo systemctl enable vdr |
||
sudo systemctl start vdr |
sudo systemctl start vdr |
||
Line 91: | Line 48: | ||
or just (according to <code>man systemctl</code>): |
or just (according to <code>man systemctl</code>): |
||
<pre>sudo systemctl enable --now vdr</pre> |
<pre>sudo systemctl enable --now vdr</pre> |
||
Note: If you do not disable <code>getty@tty1.service</code>, VDR may fail to start up when the system starts. Disabling or enabling services persists across system restart. |
|||
==Example: Auto-starting VDR with swappable video storage == |
==Example: Auto-starting VDR with swappable video storage == |
||
Line 144: | Line 103: | ||
journalctl -xe</pre> |
journalctl -xe</pre> |
||
===Automatically starting VDR when the storage is plugged in=== |
===Automatically starting VDR when the storage is plugged in=== |
||
We also want VDR to start automatically once the file system has been mounted. To do that, |
We also want VDR to start automatically once the file system has been mounted. To do that, some <code>BindsTo</code> and <code>After</code> directives have to be present in the <code>[Unit]</code> section and a <code>WantedBy</code> directive in the <code>[Install]</code> section of <code>/etc/systemd/system/vdr.service</code>: |
||
<pre> |
<pre> |
||
[Unit] |
|||
Description=Video Disk Recorder |
|||
BindsTo=dev-disk-by\x2dlabel-VDR.device |
|||
After=video.mount |
|||
Conflicts=getty@tty1.service |
|||
Conflicts=shutdown.target |
|||
[Service] |
|||
User=pi |
|||
#ExecStartPre=+/etc/systemd/system/vdr-keep-alive.sh start |
|||
ExecStart=/usr/local/bin/vdr --no-kbd --lirc=/dev/lirc0 -Prpihddevice -v /video/video -s /var/lib/vdr/vdr-shutdown.sh |
|||
TimeoutStartSec=infinity |
|||
Type=idle |
|||
Restart=on-failure |
|||
RestartSec=1s |
|||
TTYVTDisallocate=yes |
|||
[Install] |
[Install] |
||
WantedBy= |
WantedBy=video.mount |
||
</pre> |
</pre> |
||
If you had the following lines at the end of the file, they can be removed: |
If you had the following lines at the end of the file, they can be removed: |
||
Line 158: | Line 134: | ||
<pre>sudo systemctl reenable vdr</pre> |
<pre>sudo systemctl reenable vdr</pre> |
||
==Preventing shutdown and reboot while VDR is running== |
|||
===Shutting down VDR=== |
|||
By default, the power button on a [[remote control]] that is managed by a kernel [[LIRC]] driver will be mapped to <code>systemd-logind</code>. Unless you have overridden the default <code>HandlePowerKey=poweroff</code> in <code>/etc/systemd/logind.conf</code>, the system would be shut down immediately when you hit a power button on any input device (remote control, keyboard, or power button on the computer case). |
|||
Often, VDR is the main application on the system. You would not want the system to be accidentally shut down or rebooted while someone is watching TV, or a recording is in progress. System shutdown would be initiated by a script that would be invoked by VDR itself; see the <code>-s</code> parameter of <code>ExecStart</code> above. |
|||
===Policy Kit script (work in progress)=== |
|||
Recent versions of <code>polkitd</code> (formerly called Policy Kit daemon) support Javascript based rules. Older versions (up to version 0.105) only support a <code>.pkla</code> format. The new place for custom user rules is the directory <code>/etc/polkit-1/rules.d/</code>. The following has been tested with <code>polkitd</code> version 122. |
|||
Create the file <code>/etc/polkit-1/rules.d/50-vdr-prevent-shutdown.rules</code> with the following contents: |
|||
<pre> |
|||
polkit.addRule (function (action, subject) { |
|||
if (action.id == "org.freedesktop.login1.reboot" || |
|||
action.id == "org.freedesktop.login1.power-off" || |
|||
action.id == "org.freedesktop.login1.suspend" || |
|||
action.id == "org.freedesktop.login1.hibernate" || |
|||
action.id == "org.freedesktop.login1.reboot-multiple-sessions" || |
|||
action.id == "org.freedesktop.login1.power-off-multiple-sessions" || |
|||
action.id == "org.freedesktop.login1.suspend-multiple-sessions" || |
|||
action.id == "org.freedesktop.login1.hibernate-multiple-sessions" || |
|||
action.id == "org.freedesktop.login1.reboot-ignore-inhibit" || |
|||
action.id == "org.freedesktop.login1.power-off-ignore-inhibit" || |
|||
action.id == "org.freedesktop.login1.suspend-ignore-inhibit" || |
|||
action.id == "org.freedesktop.login1.hibernate-ignore-inhibit" || |
|||
action.id == "org.freedesktop.login1.set-reboot-parameter" || |
|||
action.id == "org.freedesktop.login1.set-reboot-to-firmware-setup" || |
|||
action.id == "org.freedesktop.login1.set-reboot-to-boot-loader-menu" || |
|||
action.id == "org.freedesktop.login1.set-reboot-to-boot-loader-entry") { |
|||
try { |
|||
polkit.spawn(["/usr/bin/test", "!", "-d", "/video/video"]); |
|||
return polkit.Result.YES; |
|||
} |
|||
catch (error) { |
|||
return polkit.Result.NO; |
|||
} |
|||
} |
|||
else if (action.id == "org.freedesktop.login1.inhibit-block-idle" || |
|||
action.id == "org.freedesktop.login1.inhibit-block-shutdown" || |
|||
action.id == "org.freedesktop.login1.inhibit-block-sleep" || |
|||
action.id == "org.freedesktop.login1.inhibit-handle-power-key" || |
|||
action.id == "org.freedesktop.login1.inhibit-handle-reboot-key" || |
|||
action.id == "org.freedesktop.login1.inhibit-handle-suspend-key") |
|||
return polkit.Result.YES; |
|||
}); |
|||
</pre> |
|||
You may want to replace the <code>polkit.spawn</code> call with something else. |
|||
The <code>systemd-logind</code> ''power button handling by will be unaffected by this'', that is, you may want to replace <code>HandlePowerKey=poweroff</code> in <code>/etc/systemd/logind.conf</code> or block it by other means. The <code>else if</code> part enables the Systemd way of blocking the power key: |
|||
<pre> |
|||
systemd-inhibit --what=idle:sleep:shutdown:handle-power-key:handle-suspend-key:handle-lid-switch --who=VDR --mode=block vdr --no-kbd … |
|||
</pre> |
|||
The above script will allow ''normal users'' to execute any power actions unless the directory <code>/video/video</code> exists, which could mean that a detachable video storage is plugged in. For example, <code>systemctl reboot</code> and <code>systemctl power-off</code> may be blocked, but <code>sudo systemctl reboot</code> or <code>sudo reboot</code> will succeed. |
|||
It could be convenient to use the Javascript array member function <code>includes()</code>, but that is not recognized by <code>polkitd</code> version 122. |
|||
When an action is allowed, commands like <code>pkcheck -u -p $$ -a org.freedesktop.login1.reboot</code> should return 0. To learn more about the rules, invoke <code>pkaction -va org.freedesktop.login1.reboot</code>. |
|||
To update the policy rules, <code>polkitd</code> must be restarted. The command <code>sudo invoke-rc.d dbus restart</code> does that. Beware that it may kill GUI sessions and confuse graphical environments. |
|||
You may also want to check <code>sudo journalctl -u polkit</code> for any syntax errors. |
|||
===Old way: dynamically reconfiguring systemd=== |
|||
You might use the following script, say, <code>/etc/systemd/system/vdr-keep-alive.sh</code>. You could edit the <code>TARGETS</code> below to include <code>suspend</code>, <code>hibernate</code>, or others mentioned in <code>man 7 systemd.special</code>. Executing the script will require super user privileges. |
|||
<pre> |
|||
#!/bin/sh |
|||
TARGETS=" |
|||
/lib/systemd/system/poweroff.target.d |
|||
/lib/systemd/system/reboot.target.d |
|||
/lib/systemd/system/halt.target.d |
|||
" |
|||
CONF=vdr-keep-alive.conf |
|||
case "$1" in |
|||
start) |
|||
for t in $TARGETS |
|||
do |
|||
if [ ! -f "$t/$CONF" ] |
|||
then |
|||
if [ ! -d "$t/" ] |
|||
then |
|||
mkdir "$t" |
|||
fi |
|||
echo "[Unit]\nRefuseManualStart=yes" > "$t/$CONF" |
|||
fi |
|||
done |
|||
;; |
|||
stop) |
|||
for t in $TARGETS |
|||
do |
|||
rm -f "$t/$CONF" |
|||
done |
|||
;; |
|||
esac |
|||
exec systemctl daemon-reload |
|||
</pre> |
|||
You can execute this script as follows: |
|||
<pre> |
|||
sudo /etc/systemd/system/vdr-keep-alive.sh start |
|||
sudo /etc/systemd/system/vdr-keep-alive.sh stop |
|||
</pre> |
|||
Another option might be to dynamically create or remove a file <code>/etc/systemd/logind.conf.d/00-buttons.conf</code> with content like the following: |
|||
<pre> |
|||
[Login] |
|||
HandlePowerKey=ignore |
|||
HandleSuspendKey=ignore |
|||
HandleHibernateKey=ignore |
|||
HandleLidSwitch=ignore |
|||
HandleRebootKey=ignore |
|||
</pre> |
|||
For the changes to take effect, <code>systemd-logind</code> would have to be restarted or told to reload its configuration. Unlike the what the above script does, these definitions would not prevent commands like <code>sudo reboot</code> from working. |
|||
==Shutting down VDR== |
|||
Last, we need a VDR shutdown script <code>/var/lib/vdr/vdr-shutdown.sh</code> that will power off the storage so that it can be safely detached, or shut down the system and schedule a restart for the next timer: |
Last, we need a VDR shutdown script <code>/var/lib/vdr/vdr-shutdown.sh</code> that will power off the storage so that it can be safely detached, or shut down the system and schedule a restart for the next timer: |
||
<pre> |
<pre> |
||
Line 167: | Line 257: | ||
#FALLBACK_SHUTDOWN_DELAY=86400 # seconds |
#FALLBACK_SHUTDOWN_DELAY=86400 # seconds |
||
#STORAGE=/dev/disk/by-label/VDR |
#STORAGE=/dev/disk/by-label/VDR |
||
#STORAGE_MOUNT=video.mount |
|||
[ -n "${STORAGE:-}" ] && sudo systemd-mount -u "$STORAGE" |
[ -n "${STORAGE:-}" ] && sudo systemd-mount -u "$STORAGE" |
||
if [ "$5" = 1 -a -n "${STORAGE:-}" ] |
if [ "$5" = 1 -a -n "${STORAGE:-}" ] |
||
then |
then |
||
sudo rtcwake -m no -s "$2" || : |
sudo rtcwake -m no -s "$2" || : |
||
sudo sh /etc/systemd/system/vdr-keep-alive.sh stop |
#sudo sh /etc/systemd/system/vdr-keep-alive.sh stop |
||
exec sudo udisksctl power-off -b "$STORAGE" |
exec sudo udisksctl power-off -b "$STORAGE" |
||
elif [ "$2" = 0 ] |
elif [ "$2" = 0 ] |
||
then |
then |
||
sudo sh /etc/systemd/system/vdr-keep-alive.sh stop |
|||
sudo rtcwake -m disable || : |
sudo rtcwake -m disable || : |
||
#sudo sh /etc/systemd/system/vdr-keep-alive.sh stop |
|||
exec sudo systemctl poweroff |
exec sudo systemctl poweroff |
||
elif [ "$2" -ge $HIBERN ] && sudo rtcwake -m disk -s $(($2 - $HIBERN / 2)) |
elif [ "$2" -ge $HIBERN ] && sudo rtcwake -m disk -s $(($2 - $HIBERN / 2)) |
||
Line 186: | Line 277: | ||
elif [ "$2" -ge "${FALLBACK_SHUTDOWN_DELAY:-}" ] |
elif [ "$2" -ge "${FALLBACK_SHUTDOWN_DELAY:-}" ] |
||
then |
then |
||
sudo sh /etc/systemd/system/vdr-keep-alive.sh stop |
#sudo sh /etc/systemd/system/vdr-keep-alive.sh stop |
||
sudo systemctl poweroff |
exec sudo systemctl poweroff |
||
fi |
fi |
||
[ -n "${ |
[ -n "${STORAGE_MOUNT:-}" ] && exec sudo systemctl start "${STORAGE_MOUNT}" |
||
[ -n "${STORAGE:-}" ] && exec sudo systemd-mount "$STORAGE" |
|||
</pre> |
</pre> |
||
===Wake on timer=== |
|||
The script relies on the versatile command <code>rtcwake</code> for wake-on-timer control. |
|||
You may want to replace the <code>#exec</code> line with <code>exec</code> to enable debug output to the file <code>/var/tmp/vdr-shutdown.txt</code>. This file should survive system shutdown and restart. |
|||
===Notes=== |
|||
Adjust <code>HIBERN</code> to the time it takes to suspend to disk and resume from it. If this fails because no swap space has been configured, the script will fall back to suspend-to-RAM. |
|||
If there is no real-time clock device for scheduling wake-up (say, on a Raspberry Pi), all [[rtcwake]] commands will fail. It would be the user's responsibility to shut down or wake up the system between recordings (or leave it running all the time). You might want to set <code>FALLBACK_SHUTDOWN_DELAY</code> at the start of the script to have an automatic shutdown (followed by manual wakeup) if the next scheduled recording is in distant enough future. |
|||
Some systems are unable to wake up after power-off; see [[rtcwake]] for details. On an affected system, you might want to remove the section starting with <code>if [ "$5" = 1</code> to let the power button press lead directly to suspend-to-disk or suspend-to-RAM, just like an inactivity timeout would. |
|||
===Detachable storage=== |
|||
When the lines starting with <code>#STORAGE=</code> and <code>#STORAGE_MOUNT=</code> are commented out, there will be no difference between the user pressing the Power button, and the system being shut down due to an inactivity timeout. |
|||
If you want to use detachable video storage, uncomment the line(s) and adjust the directory name if needed. The rest of this section assumes that you have done so. |
|||
If any process is using the <code>STORAGE</code> mount point, the system will refuse to shut down for any reason (inactivity timeout, or the Power button being pressed). In VDR, an active recording or the playback of a recording (even a paused playback) counts as using the mount point. |
|||
If unmounting the <code>STORAGE</code> succeeds, and the Power button was pressed, the line starting with <code>if [ "$5" = 1</code> will power off the storage using <code>udisksctl</code>. This will also shut down VDR if <code>vdr.service</code> defines the following: |
|||
<pre> |
|||
[Unit] |
|||
BindsTo=dev-disk-by\x2dlabel-VDR.device |
|||
</pre> |
|||
Even though VDR will be shut down, the rest of the system will remain powered on. The power LED indicator on a USB storage device will become an indicator of two things: |
|||
* whether it is safe to disconnect the cable; you can plug it in again to have VDR start up (on same or different drive) |
|||
* whether shutdown is possible, by pressing the Power button one more time |
|||
If there is no recording scheduled in the future (<code>"$2" = 0</code>), the system will be shut down. |
|||
Otherwise, the script will try to suspend or hibernate the system until the next timed recording is scheduled. Finally, the storage will be mounted. In this way, the storage may be temporarily removed from a system while it is suspended or hibernated by <code>rtcwake</code>. The variable <code>STORAGE_MOUNT</code> is needed, because when <code>systemd-mount</code> is invoked with one argument, it may mount the block device under <code>/run/media/system/</code> instead of using a custom mount unit that is associated with the device. |
|||
==Notes== |
|||
You can still execute the following commands to stop or start the VDR service, for example, for upgrading VDR or plugins: |
You can still execute the following commands to stop or start the VDR service, for example, for upgrading VDR or plugins: |
||
<pre> |
<pre> |
||
Line 199: | Line 321: | ||
</pre> |
</pre> |
||
If you use these commands, the storage device will remain mounted in the file system, and the status of the reboot and shutdown targets will not change. |
If you use these commands, the storage device will remain mounted in the file system, and the status of the reboot and shutdown targets will not change. |
||
At the start of the <code>/var/lib/vdr/vdr-shutdown.sh</code> script, you can configure a few things. |
|||
# Replace the <code>#exec</code> with <code>exec</code> to enable debug output to the file <code>/var/tmp/vdr-shutdown.txt</code>. This file should survive system shutdown and restart. |
|||
# Adjust <code>BOOT_DELAY</code> to the time it takes to shutdown and restart your system, or to suspend to disk and resume from it. |
|||
# If you do not want to use detachable storage for the video storage, remove or comment out the line starting with <code>STORAGE=</code>. |
|||
If the power button is pressed while VDR is not running, the system will be shut down. On the first press (while VDR is running), the lines after <code>elif [ "$5" = 1 ]</code> will detach the storage and shut down VDR. Some systems do not support wake-on-timer after power-off; see [[rtcwake]] for details. On such systems, you might remove the section <code>elif [ "$5" = 1 ]</code> to let the power button press to lead to proceed to suspend-to-disk or suspend-to-RAM instead. |
|||
If the <code>STORAGE</code> variable has been set, and any process (VDR or anything else) is using that mount point, the system will refuse to shut down. The power LED indicator on a USB storage device will become an indicator on whether shutdown is possible. If VDR is the only user of <code>STORAGE</code>, an extremely slow "double click" of the power button on the remote control unit will power off the system. Note: An active recording or the playback of a recording (even a paused playback) will prevent shutdown. |
|||
The <code>/var/lib/vdr/vdr-shutdown.sh</code> script relies on the versatile command [[rtcwake]] for wake-on-timer control. Suspending to disk could fail because no swap space has been configured. The script can fall back to suspend-to-RAM. If there is no real-time clock device for scheduling wake-up (say, on a Raspberry Pi), it would then be the user's responsibility to shut down or wake up the system between recordings. You might want to set <code>FALLBACK_SHUTDOWN_DELAY</code> at the start of the script to have an automatic shutdown (followed by manual wakeup) if the next scheduled recording is in distant enough future. |
Latest revision as of 06:33, 12 July 2023
Introduction
The main aim of systemd (a "system and service manager") is to unify service configuration and behavior across Linux distributions. It bootstraps the user space and manages user processes. It also provides replacements for various daemons and utilities, including device management, login management, network connection management, and event logging.
Since 2015, the majority of Linux distributions have adopted systemd, having replaced other init systems such as SysV init.
Making Systemd start up VDR
Before systemd, you might have edited /etc/gettydefs
to prevent a virtual console from being associated with a normal login prompt, and then have init
invoke a shell script that would invoke VDR, often named runvdr
. With systemd, you would execute sudo systemctl disable getty@tty1
and write a systemd unit configuration file, say, /etc/systemd/system/vdr.service
, with contents like the following:
[Unit] Description=Video Disk Recorder #After=systemd-user-sessions.service plymouth-quit-wait.service rc-local.service Conflicts=getty@tty1.service #BindsTo=dev-disk-by\x2dlabel-VDR.device #After=video.mount Conflicts=shutdown.target ConditionPathExists=/video/video [Service] User=pi #ExecStartPre=+/etc/systemd/system/vdr-keep-alive.sh start ExecStart=/usr/local/bin/vdr --no-kbd --lirc=/dev/lirc0 -Prpihddevice -v /video/video -s /var/lib/vdr/vdr-shutdown.sh #ExecStart=systemd-inhibit --what=idle:sleep:shutdown:handle-power-key:handle-suspend-key:handle-lid-switch --who=VDR --mode=block /usr/local/bin/vdr --no-kbd --lirc=/dev/lirc0 -Prpihddevice -v /video/video -s /var/lib/vdr/vdr-shutdown.sh TimeoutStartSec=infinity Type=idle Restart=on-failure RestartSec=1s TTYVTDisallocate=yes [Install] WantedBy=display-manager.service
Adjustments
User=
refers to the user account that will run the VDR service.ExecStart=
must refer to the full VDR invocation.ConditionPathExists=
is for the directory that contains the recordings, matching the-v
parameter inExecStart
. If the recordings cannot be mounted, the service would not start up.After=
specifies services that need to be started first. Seeman 5 systemd.unit
for a description of various dependencies.- The commented-out
BindsTo
andAfter=video.mount
are for automatically starting up VDR when video storage is plugged in.
The shutdown script (named /var/lib/vdr/vdr-shutdown.sh
in the above example) could do at least one of the following:
- Adjust rtcwake (or Nvram_wakeup) to have the system start up automatically on the next scheduled recording.
- Shut down VDR or the entire system.
Installation
sudo systemctl disable getty@tty1 sudo systemctl enable vdr sudo systemctl start vdr
or just (according to man systemctl
):
sudo systemctl enable --now vdr
Note: If you do not disable getty@tty1.service
, VDR may fail to start up when the system starts. Disabling or enabling services persists across system restart.
Example: Auto-starting VDR with swappable video storage
On a small system like the Raspberry_Pi, the internal storage is just large enough for the basic installation and system configuration. For recordings, you might want to use a USB-powered HDD or SSD.
You might make a virtue out of necessity and implement swappable video directories. One USB SSD might not be large enough for your entire collection of recordings. So, why not implement swappable storage, with the following user interface?
- When you plug in the USB cable (or it is attached on system startup), VDR will start up automatically.
- When you press the power button, VDR will shut down and the USB storage will be powered off, so that it can be safely unplugged.
- When you plug in the USB cable again, VDR will start up again. This could be a different drive.
Preparations
Create a file system and label it VDR
. Create the subdirectory video
inside it.
Below, we assume that the storage devices that you want to initialize is /dev/sdd. Substitute the correct name for sdd
below, and ensure that you are accessing the right device!
sudo mkfs.ext4 /dev/sdd1 sudo tune2fs -L /dev/sdd1 sudo mount /dev/sdd1 /mnt mkdir /mnt/video sudo umount /mnt
On the VDR system, create a mount point for the storage devices that have been prepared as above.
sudo mkdir -m 000 /video
Automatically mounting storage when it is plugged in
You might think of a simple /etc/fstab
entry like this:
LABEL=VDR /video ext4 defaults,noatime,nofail 0 1
But, we want the file system to be mounted automatically when it is labeled VDR. To achieve that, we create a file /etc/systemd/system/video.mount
(the name must match the mount point /video
):
[Unit] BindsTo=dev-disk-by\x2dlabel-VDR.device After=dev-disk-by\x2dlabel-VDR.device Requires=systemd-fsck@dev-disk-by\x2dlabel-VDR.service After=systemd-fsck@dev-disk-by\x2dlabel-VDR.service [Mount] Where=/video What=/dev/disk/by-label/VDR Type=ext4 Options=defaults,noatime,nofail [Install] WantedBy=dev-disk-by\x2dlabel-VDR.device
The magic .device
unit refers to the path /dev/disk/by-label/VDR
, which will become available when a device containing a file system labeled VDR
is attached. We use that name also in the What=
directive.
Now we are ready to enable the unit:
sudo systemctl enable video.mount
To check the status, you may find the following commands useful:
systemctl status journalctl -u video.mount journalctl -xe
Automatically starting VDR when the storage is plugged in
We also want VDR to start automatically once the file system has been mounted. To do that, some BindsTo
and After
directives have to be present in the [Unit]
section and a WantedBy
directive in the [Install]
section of /etc/systemd/system/vdr.service
:
[Unit] Description=Video Disk Recorder BindsTo=dev-disk-by\x2dlabel-VDR.device After=video.mount Conflicts=getty@tty1.service Conflicts=shutdown.target [Service] User=pi #ExecStartPre=+/etc/systemd/system/vdr-keep-alive.sh start ExecStart=/usr/local/bin/vdr --no-kbd --lirc=/dev/lirc0 -Prpihddevice -v /video/video -s /var/lib/vdr/vdr-shutdown.sh TimeoutStartSec=infinity Type=idle Restart=on-failure RestartSec=1s TTYVTDisallocate=yes [Install] WantedBy=video.mount
If you had the following lines at the end of the file, they can be removed:
[Install] WantedBy=display-manager.service
To notify systemd of the update:
sudo systemctl reenable vdr
Preventing shutdown and reboot while VDR is running
By default, the power button on a remote control that is managed by a kernel LIRC driver will be mapped to systemd-logind
. Unless you have overridden the default HandlePowerKey=poweroff
in /etc/systemd/logind.conf
, the system would be shut down immediately when you hit a power button on any input device (remote control, keyboard, or power button on the computer case).
Often, VDR is the main application on the system. You would not want the system to be accidentally shut down or rebooted while someone is watching TV, or a recording is in progress. System shutdown would be initiated by a script that would be invoked by VDR itself; see the -s
parameter of ExecStart
above.
Policy Kit script (work in progress)
Recent versions of polkitd
(formerly called Policy Kit daemon) support Javascript based rules. Older versions (up to version 0.105) only support a .pkla
format. The new place for custom user rules is the directory /etc/polkit-1/rules.d/
. The following has been tested with polkitd
version 122.
Create the file /etc/polkit-1/rules.d/50-vdr-prevent-shutdown.rules
with the following contents:
polkit.addRule (function (action, subject) { if (action.id == "org.freedesktop.login1.reboot" || action.id == "org.freedesktop.login1.power-off" || action.id == "org.freedesktop.login1.suspend" || action.id == "org.freedesktop.login1.hibernate" || action.id == "org.freedesktop.login1.reboot-multiple-sessions" || action.id == "org.freedesktop.login1.power-off-multiple-sessions" || action.id == "org.freedesktop.login1.suspend-multiple-sessions" || action.id == "org.freedesktop.login1.hibernate-multiple-sessions" || action.id == "org.freedesktop.login1.reboot-ignore-inhibit" || action.id == "org.freedesktop.login1.power-off-ignore-inhibit" || action.id == "org.freedesktop.login1.suspend-ignore-inhibit" || action.id == "org.freedesktop.login1.hibernate-ignore-inhibit" || action.id == "org.freedesktop.login1.set-reboot-parameter" || action.id == "org.freedesktop.login1.set-reboot-to-firmware-setup" || action.id == "org.freedesktop.login1.set-reboot-to-boot-loader-menu" || action.id == "org.freedesktop.login1.set-reboot-to-boot-loader-entry") { try { polkit.spawn(["/usr/bin/test", "!", "-d", "/video/video"]); return polkit.Result.YES; } catch (error) { return polkit.Result.NO; } } else if (action.id == "org.freedesktop.login1.inhibit-block-idle" || action.id == "org.freedesktop.login1.inhibit-block-shutdown" || action.id == "org.freedesktop.login1.inhibit-block-sleep" || action.id == "org.freedesktop.login1.inhibit-handle-power-key" || action.id == "org.freedesktop.login1.inhibit-handle-reboot-key" || action.id == "org.freedesktop.login1.inhibit-handle-suspend-key") return polkit.Result.YES; });
You may want to replace the polkit.spawn
call with something else.
The systemd-logind
power button handling by will be unaffected by this, that is, you may want to replace HandlePowerKey=poweroff
in /etc/systemd/logind.conf
or block it by other means. The else if
part enables the Systemd way of blocking the power key:
systemd-inhibit --what=idle:sleep:shutdown:handle-power-key:handle-suspend-key:handle-lid-switch --who=VDR --mode=block vdr --no-kbd …
The above script will allow normal users to execute any power actions unless the directory /video/video
exists, which could mean that a detachable video storage is plugged in. For example, systemctl reboot
and systemctl power-off
may be blocked, but sudo systemctl reboot
or sudo reboot
will succeed.
It could be convenient to use the Javascript array member function includes()
, but that is not recognized by polkitd
version 122.
When an action is allowed, commands like pkcheck -u -p $$ -a org.freedesktop.login1.reboot
should return 0. To learn more about the rules, invoke pkaction -va org.freedesktop.login1.reboot
.
To update the policy rules, polkitd
must be restarted. The command sudo invoke-rc.d dbus restart
does that. Beware that it may kill GUI sessions and confuse graphical environments.
You may also want to check sudo journalctl -u polkit
for any syntax errors.
Old way: dynamically reconfiguring systemd
You might use the following script, say, /etc/systemd/system/vdr-keep-alive.sh
. You could edit the TARGETS
below to include suspend
, hibernate
, or others mentioned in man 7 systemd.special
. Executing the script will require super user privileges.
#!/bin/sh TARGETS=" /lib/systemd/system/poweroff.target.d /lib/systemd/system/reboot.target.d /lib/systemd/system/halt.target.d " CONF=vdr-keep-alive.conf case "$1" in start) for t in $TARGETS do if [ ! -f "$t/$CONF" ] then if [ ! -d "$t/" ] then mkdir "$t" fi echo "[Unit]\nRefuseManualStart=yes" > "$t/$CONF" fi done ;; stop) for t in $TARGETS do rm -f "$t/$CONF" done ;; esac exec systemctl daemon-reload
You can execute this script as follows:
sudo /etc/systemd/system/vdr-keep-alive.sh start sudo /etc/systemd/system/vdr-keep-alive.sh stop
Another option might be to dynamically create or remove a file /etc/systemd/logind.conf.d/00-buttons.conf
with content like the following:
[Login] HandlePowerKey=ignore HandleSuspendKey=ignore HandleHibernateKey=ignore HandleLidSwitch=ignore HandleRebootKey=ignore
For the changes to take effect, systemd-logind
would have to be restarted or told to reload its configuration. Unlike the what the above script does, these definitions would not prevent commands like sudo reboot
from working.
Shutting down VDR
Last, we need a VDR shutdown script /var/lib/vdr/vdr-shutdown.sh
that will power off the storage so that it can be safely detached, or shut down the system and schedule a restart for the next timer:
#!/bin/sh #exec >> /var/tmp/vdr-shutdown.txt 2>&1; set -x; : "$@" set -eu HIBERN=120 # time in seconds to suspend to disk + resume from it #FALLBACK_SHUTDOWN_DELAY=86400 # seconds #STORAGE=/dev/disk/by-label/VDR #STORAGE_MOUNT=video.mount [ -n "${STORAGE:-}" ] && sudo systemd-mount -u "$STORAGE" if [ "$5" = 1 -a -n "${STORAGE:-}" ] then sudo rtcwake -m no -s "$2" || : #sudo sh /etc/systemd/system/vdr-keep-alive.sh stop exec sudo udisksctl power-off -b "$STORAGE" elif [ "$2" = 0 ] then sudo rtcwake -m disable || : #sudo sh /etc/systemd/system/vdr-keep-alive.sh stop exec sudo systemctl poweroff elif [ "$2" -ge $HIBERN ] && sudo rtcwake -m disk -s $(($2 - $HIBERN / 2)) then : elif sudo rtcwake -m mem -s "$2" then : elif [ "$2" -ge "${FALLBACK_SHUTDOWN_DELAY:-}" ] then #sudo sh /etc/systemd/system/vdr-keep-alive.sh stop exec sudo systemctl poweroff fi [ -n "${STORAGE_MOUNT:-}" ] && exec sudo systemctl start "${STORAGE_MOUNT}" [ -n "${STORAGE:-}" ] && exec sudo systemd-mount "$STORAGE"
Wake on timer
The script relies on the versatile command rtcwake
for wake-on-timer control.
You may want to replace the #exec
line with exec
to enable debug output to the file /var/tmp/vdr-shutdown.txt
. This file should survive system shutdown and restart.
Adjust HIBERN
to the time it takes to suspend to disk and resume from it. If this fails because no swap space has been configured, the script will fall back to suspend-to-RAM.
If there is no real-time clock device for scheduling wake-up (say, on a Raspberry Pi), all rtcwake commands will fail. It would be the user's responsibility to shut down or wake up the system between recordings (or leave it running all the time). You might want to set FALLBACK_SHUTDOWN_DELAY
at the start of the script to have an automatic shutdown (followed by manual wakeup) if the next scheduled recording is in distant enough future.
Some systems are unable to wake up after power-off; see rtcwake for details. On an affected system, you might want to remove the section starting with if [ "$5" = 1
to let the power button press lead directly to suspend-to-disk or suspend-to-RAM, just like an inactivity timeout would.
Detachable storage
When the lines starting with #STORAGE=
and #STORAGE_MOUNT=
are commented out, there will be no difference between the user pressing the Power button, and the system being shut down due to an inactivity timeout.
If you want to use detachable video storage, uncomment the line(s) and adjust the directory name if needed. The rest of this section assumes that you have done so.
If any process is using the STORAGE
mount point, the system will refuse to shut down for any reason (inactivity timeout, or the Power button being pressed). In VDR, an active recording or the playback of a recording (even a paused playback) counts as using the mount point.
If unmounting the STORAGE
succeeds, and the Power button was pressed, the line starting with if [ "$5" = 1
will power off the storage using udisksctl
. This will also shut down VDR if vdr.service
defines the following:
[Unit] BindsTo=dev-disk-by\x2dlabel-VDR.device
Even though VDR will be shut down, the rest of the system will remain powered on. The power LED indicator on a USB storage device will become an indicator of two things:
- whether it is safe to disconnect the cable; you can plug it in again to have VDR start up (on same or different drive)
- whether shutdown is possible, by pressing the Power button one more time
If there is no recording scheduled in the future ("$2" = 0
), the system will be shut down.
Otherwise, the script will try to suspend or hibernate the system until the next timed recording is scheduled. Finally, the storage will be mounted. In this way, the storage may be temporarily removed from a system while it is suspended or hibernated by rtcwake
. The variable STORAGE_MOUNT
is needed, because when systemd-mount
is invoked with one argument, it may mount the block device under /run/media/system/
instead of using a custom mount unit that is associated with the device.
Notes
You can still execute the following commands to stop or start the VDR service, for example, for upgrading VDR or plugins:
sudo systemctl stop vdr sudo systemctl start vdr
If you use these commands, the storage device will remain mounted in the file system, and the status of the reboot and shutdown targets will not change.