hzeller / gmrender-resurrect

Resource efficient UPnP/DLNA renderer, optimal for Raspberry Pi, CuBox or a general MediaServer. Fork of GMediaRenderer to add some features to make it usable.
GNU General Public License v2.0
832 stars 202 forks source link

Use systemd service instead of the deprecated SysV init script #230

Open ingo-h opened 3 years ago

ingo-h commented 3 years ago

Because emulation of SysV init scripts by systemd will be less supported step by step, I think it's time to migrate to a systemd service to manage gmrender-resurrect. Here is a suggestion for a unit file that should do the same as the old init script, including use of parameter in /etc/default/gmediarender. I assume the init script and its emulation is removed like this:

~$ sudo systemctl stop gmediarender
~$ sudo systemctl disable gmedidarender
~$ sudo mv /etc/init.d/gmediarender .
~$ sudo update-rc.d gmediarender remove
~$ sudo rm /etc/systemd/system/gmediarender.service
~$ sudo systemctl daemon-reload
~$ sudo systemctl reset-failed

Then I create a new unit file:

~$ sudo systemctl edit --force --full gemediarender.service

In the empty editor I inserted these statements:

[Unit]
Description=Media Renderer
After=network.target

[Service]
Environment=OPENSSL_CONF=""
EnvironmentFile=/etc/default/gmediarender
ExecStart=/usr/sbin/runuser -u $DAEMON_USER -g $DAEMON_GROUP -- /usr/bin/bash -c "UUID=$(</etc/machine-id); [ -n \"$ALSA_DEVICE\" ] && ALSA=\"--gstout-audiosink alsasink --gstout-audiodevice $ALSA_DEVICE\"; /usr/bin/gmediarender --friendly-name $UPNP_DEVICE_NAME --gstout-initial-volume-db $INITIAL_VOLUME_DB --uuid $UUID $ALSA $DAEMON_EXTRA_ARGS"

[Install]
WantedBy=network.target

There are only two things diferrent from the old SysV script.

The config file would look like this:

~$ cat /etc/default/gmediarender
# Configuration for gmediarender

# User and group the daemon will be running as.
DAEMON_USER="nobody"
DAEMON_GROUP="audio"

# Device name as it will be advertised to and shown in the UPnP controller UI.
# Some string that helps you recognize the player, e.g. "Livingroom Player"
UPNP_DEVICE_NAME="raspberrypi"

# Initial volume in decibel. 0.0 is 'full volume', -10 correspondents to '75' on
# the exported volume scale (Note, this does not change the ALSA volume, only
# internal to gmrender. So make sure to leave the ALSA volume always to 100%).
INITIAL_VOLUME_DB="-20"

# If you explicitly choose a specific ALSA device here (find them with 'aplay -L'), then
# gmediarenderer will use that ALSA device to play audio.
# Otherwise, whatever default is configured for gstreamer for the '$DAEMON_USER' is
# used.
ALSA_DEVICE="sysdefault"
#ALSA_DEVICE="iec958"

# You can pass extra arguments to the daemon
# For example, you can specify a logfile with this line
# (though you need to ensure correct permissions for a log file yourself.)
# DAEMON_EXTRA_ARGS="--logfile /var/log/gmediarender.log"

This has only one little disadvantage. Variable expansion like UPNP_DEVICE_NAME="gmediarender on $(hostname)" will not work. You will see the string $(hostname) and not the expanded hostname. To check settings of the running service look at:

~$ systemctl status gmediarender.service
● gmediarender.service - Media Renderer
   Loaded: loaded (/etc/systemd/system/gmediarender.service; enabled; vendor preset: enabled)
   Active: active (running) since Wed 2021-02-03 13:13:31 CET; 1min 44s ago
 Main PID: 1446 (runuser)
    Tasks: 15 (limit: 4304)
   CGroup: /system.slice/gmediarender.service
           ├─1446 /usr/sbin/runuser -u nobody -g audio -- /usr/bin/bash -c UUID=$(</etc/machine-id); [ -n "$ALSA_DEVICE" ] && ALSA="--gstout-audiosink alsasink --gstout-audiodevice $ALSA_DEVICE"; /usr/bin/gmediarender --friendly-name $UPNP_DEVICE_NAME --gstout-initial-volume-db $INITIAL_VOLUME_DB --uuid $UUID $ALSA $DAEMON_EXTRA_ARGS
           ├─1447 /usr/bin/bash -c UUID=$(</etc/machine-id); [ -n "$ALSA_DEVICE" ] && ALSA="--gstout-audiosink alsasink --gstout-audiodevice $ALSA_DEVICE"; /usr/bin/gmediarender --friendly-name $UPNP_DEVICE_NAME --gstout-initial-volume-db $INITIAL_VOLUME_DB --uuid $UUID $ALSA $DAEMON_EXTRA_ARGS
           └─1449 /usr/bin/gmediarender --friendly-name raspberrypi --gstout-initial-volume-db -20 --uuid a92a4d095de221dd9c6a5d386012a112 --gstout-audiosink alsasink --gstout-audiodevice sysdefault

Feb 03 13:13:31 rpi05-10 systemd[1]: Started Media Renderer.
Feb 03 13:13:31 rpi05-10 runuser[1446]: pam_unix(runuser:session): session opened for user nobody by (uid=0)
Feb 03 13:13:34 rpi05-10 runuser[1446]: gmediarender 0.0.8 started [ gmediarender 0.0.8 (libupnp-1.8.4; glib-2.58.3; gstreamer-1.14.4) ].
Feb 03 13:13:34 rpi05-10 runuser[1446]: Logging switched off. Enable with --logfile=<filename> (or --logfile=stdout for console)
Feb 03 13:13:42 rpi05-10 runuser[1446]: Ready for rendering.

Have a look at the CGroup:. There you can follow how the parameter are expanded step by step and with PID 1449 the final running service.

coldtobi commented 3 years ago

(I guess its better to discuss the issue here, not in #229)

(IMHOs, Written with my Debian hat on;)

* In `/etc/default/gmediarender` I have to use two parameter for `DAEMON_USER` and `DAEMON_GROUP`.

(First thought was: maybe a new variable name, to make it easier to have fall back code in the init script for users not updating their file on the update. (I want to avoid that the daemon suddenly runs as e.g root, e.g. on some unattended update)

I use the /etc/machine-id instead of a random generated UUID from a mac-address of an interface. This simplifies the script a lot and the media renderer is clearly assigned to the computer on which it is running.

I like that :) however, does that break users setup (is the UUID used for identifying the instance in upnp control points? .. I don't know that) PS: I just realized that machine-id is considered confidential, as per machine-id(5): "This ID uniquely identifies the host. It should be considered "confidential", and must not be exposed in untrusted environments, in particular on the network."

You will see the string $(hostname) and not the expanded hostname. To check settings of the running service look at:

Yeah, exactly this one makes the story complicated… This is the feature I want to preserve.

~$ sudo systemctl stop gmediarender (...)

(When there is a service file, systemd does not use the systemd-sysv-generator, so the files can coexists. Not sure if a systemctl daemon-reload will do the trick already)

ingo-h commented 3 years ago

@coldtobi OK, I understand. I will try to leave the configuration file untouched. But then we should define a goal. Debian followed the modern evolution with the implementation of systemd with its main advantage of asynchronous execution for multiprocessor systems. So a general goal should be, also for Debian, to remove deprecated init.d scripts. They are just cripple of an old system SysV that no longer exists and prevent systemd to work asynchronous.

If you can live without init scripts as many other Debian packages, then it makes sense to have a compatibility or wrapper script that preserves the old configuration but enables the full power of systemd to manage gmrender-resurrect as service, not as daemon.

With man systemd.exec I found:

EnvironmentFile=
... The files listed with this directive will be read shortly before the process is executed (more specifically, after all processes from a previous unit state terminated. This means you can generate these files in one unit state, and read it with this option in the next. The files are read from the file system of the service manager, before any file system changes like bind mounts take place).

This should be usable to create the needed environment variables on the fly before starting the service. Shall I try it?

coldtobi commented 3 years ago

EnvironmentFile= won't do shell expansion.

Tried that already. An I don't think we should have a second service only to generate the file.

Regarding your System V comment: Please. Avoid. Such. Statements. They are neither warranted nor useful.

Just to be clear: I will not drop init.d support without good reason. As I wrote earlier: (When there is a service file, systemd does not use the systemd-sysv-generator, so the files can coexists.)

coldtobi commented 3 years ago

@ingo-h Not sure why you have closed this bug; this is a valid bug report. I just want to emphasise that I'm with you that [at least the Debian package] gmedia-resurrect should have a systemd service file, but that must be -- and that is where I have you called you out for --- , orthogonal to an judgemental statements on other init systemd.

Reopen this bug, because this is something I'd really like to tackle sooner or later.

My currently best lead in tackling this is the thread in the debian bug, namely using some along

 EnvironmentFile=-/run/service-dynamic-vars
 ExecStartPre=-/usr/bin/env -i /bin/sh -a -c '. /etc/default/service-static-vars && env -uPWD >/run/service-dynamic-vars'
 ExecStart=/usr/bin/daemon --option ${NAME}

Its a bit ugly… But I had no brain cycles yet to tackle it.

ingo-h commented 3 years ago

@coldtobi Sorry, I'm also a bit busy with bug fixing pupnp, my entry to the issues with gmrender-resurrect.

Not sure why you have closed this bug; this is a valid bug report.

I have seen this more as a feature request than a bug. And I didn't saw a possibility to get a native systemd.service without executing /etc/init.d/gmediarender and forking a daemon. So I closed the issue.

This may have been a misjudgment. It may be possible to have the systemd-sysv-generator emulation still available in addition to a native systemd service and the user can select, what implementation he want to use. But how to manage the selection? And what should be used as default/startup setup?

EnvironmentFile=-/run/service-dynamic-vars ExecStartPre=-/usr/bin/env -i /bin/sh -a -c '. /etc/default/service-static-vars && env -uPWD >/run/service-dynamic-vars' ExecStart=/usr/bin/daemon --option ${NAME}

That's exactly what I had in mind too. But I'm not sure if the environment variables are already seen by the running service that executed its settings.

coldtobi commented 3 years ago

But how to manage the selection? And what should be used as default/startup setup?

systemd prefers native services files and in case both are there, its generator will ignore the init.d file. No need of any selection at the packaging side; it will be transparent: User has systemd -> service used. User has no systemd but SysV -> init.d used.

(Sorry for the verbosity, I'm not sure if we talking past each other, so I've put some redudancy in my answer. If you had something else in mind in your qurestion, please expand)

That's exactly what I had in mind too. But I'm not sure if the environment variables are already seen by the running service that executed its settings. (Not sure if i understood what you mean; posibly my answer is not matching your concerns, please correct me where I misunderstood.)

Of course this needs experimenting, but IIUC the EnvironmentalFile is loaded just before ExecStart, then ExecStartPre should already have done its job and created the file. Of course, changes are only propagated on service restart -- the same as current behavior.

ingo-h commented 3 years ago

@coldtobi All correct understood.

systemd prefers native services files and in case both are there, its generator will ignore the init.d file. No need of any selection at the packaging side; it will be transparent: User has systemd -> service used. User has no systemd but SysV -> init.d used.

We are talking about Debian and the latter case will never be true. That is why I don't see a reason to preserve init.d scripts. I don't believe that someone will take the effort to replace the initial running init file and downgrade it to SysV. So if you install native systemd services they will always run. The user does not have any choice. If you want that he can select either native systemd services or by systemd emulated init.d scripts you have to give him a choice. One possibility is to explain it in /usr/share/doc/gmediarender/README.Debian how to switch from native systemd to emulation of SysV. Do you know that the emulation never covers completely SysV and may cause problems? Compatibility with SysV