Support systemd socket activation #2927

Closed
opened 2026-02-21 16:29:16 -05:00 by deekerman · 5 comments
Owner

Originally created by @fsateler on GitHub (Aug 11, 2015).

As noted in #554, currently qbittorrent-nox can be currently exited from the web ui, but this means that one needs to ssh in to the server to restart the service if I exit the program.

This could be improved by leveraging the socket activation for the web ui port. Systemd can start a service when a connection is received on a given port, and then passes the service the open socket. This would enable starting the program by just accessing the Web UI url.

See this post, example 3 for a simple example implementation with support for both socket and normal activation.

I suggest setting a special parameter to --webui-port (eg, inherit) to allow qbittorrent to detect if it should try to use the socket activation, and to fail if it is not received (to prevent qbittorrent from racing with systemd for the port).

Originally created by @fsateler on GitHub (Aug 11, 2015). As noted in #554, currently qbittorrent-nox can be currently exited from the web ui, but this means that one needs to ssh in to the server to restart the service if I exit the program. This could be improved by leveraging the socket activation for the web ui port. Systemd can start a service when a connection is received on a given port, and then passes the service the open socket. This would enable starting the program by just accessing the Web UI url. See [this post](http://0pointer.de/blog/projects/socket-activation.html), example 3 for a simple example implementation with support for both socket and normal activation. I suggest setting a special parameter to `--webui-port` (eg, `inherit`) to allow qbittorrent to detect if it should try to use the socket activation, and to fail if it is not received (to prevent qbittorrent from racing with systemd for the port).
Author
Owner

@depau commented on GitHub (Aug 26, 2015):

I would like this feature added too. It works this way for CUPS and other headless servers as well.

@depau commented on GitHub (Aug 26, 2015): I would like this feature added too. It works this way for CUPS and other headless servers as well.
Author
Owner

@fsateler commented on GitHub (Nov 15, 2015):

I have taken a stab at implementing this. Unfortunately, I cannot present a full PR because I did not manage to navigate qmake to conditionally link to libsystemd. But the code change itself is very small:

diff --git a/src/webui/webui.cpp b/src/webui/webui.cpp
index f384c57..f409b6e 100644
--- a/src/webui/webui.cpp
+++ b/src/webui/webui.cpp
@@ -33,6 +33,9 @@
 #include "core/net/portforwarder.h"
 #include "webapplication.h"
 #include "webui.h"
+// ifdef here
+#include <systemd/sd-daemon.h>
+// endif

 WebUI::WebUI(QObject *parent)
     : QObject(parent)
@@ -79,11 +82,26 @@ void WebUI::init()
 #endif

         if (!httpServer_->isListening()) {
-            bool success = httpServer_->listen(QHostAddress::Any, m_port);
-            if (success)
-                logger->addMessage(tr("The Web UI is listening on port %1").arg(m_port));
-            else
-                logger->addMessage(tr("Web UI Error - Unable to bind Web UI to port %1").arg(m_port), Log::CRITICAL);
+            int n = 0;
+            bool success;
+            // ifdef here
+            n = sd_listen_fds(0);
+            // endif
+            if (n == 1) {
+                success = httpServer_->setSocketDescriptor(SD_LISTEN_FDS_START);
+                m_port = httpServer_->serverPort();
+                if (success)
+                    logger->addMessage(tr("The Web UI is listening systemd-inherited port %1").arg(m_port));
+                else
+                    logger->addMessage(tr("Web UI Error - Unable to bind Web UI to systemd-inherited socket"), Log::CRITICAL);
+            }
+            else {
+                success = httpServer_->listen(QHostAddress::Any, m_port);
+                if (success)
+                    logger->addMessage(tr("The Web UI is listening on port %1").arg(m_port));
+                else
+                    logger->addMessage(tr("Web UI Error - Unable to bind Web UI to port %1").arg(m_port), Log::CRITICAL);
+            }
         }

         // DynDNS

A bit of explanation.

sd_listen_fds returns the number of file descriptors systemd is listening on for us. If we have exactly 1 socket, then use that instead of opening our own port. This effectively ignores the webui port configuration. The parameter is 0 so that if a WebUI class is reinstantiated we still get the systemd-activated fd. This can be changed so that only the first instance inherits the fd by setting it to 1, then future calls will return 0.

SD_LISTEN_FDS_START is always the first socket passed via activation. Other sockets would be pased as SD_LISTEN_FDS_START+1, etc, but we only care about one.

Systemd does not care if the socket is closed when the program exits.

To use, you can add the following socket file:

# qbittorrent-nox.socket
[Unit]
Description=qBittorrent WebUI listen stream

[Socket]
ListenStream=0.0.0.0:8080

[Install]
WantedBy=sockets.target
@fsateler commented on GitHub (Nov 15, 2015): I have taken a stab at implementing this. Unfortunately, I cannot present a full PR because I did not manage to navigate qmake to conditionally link to libsystemd. But the code change itself is very small: ``` diff diff --git a/src/webui/webui.cpp b/src/webui/webui.cpp index f384c57..f409b6e 100644 --- a/src/webui/webui.cpp +++ b/src/webui/webui.cpp @@ -33,6 +33,9 @@ #include "core/net/portforwarder.h" #include "webapplication.h" #include "webui.h" +// ifdef here +#include <systemd/sd-daemon.h> +// endif WebUI::WebUI(QObject *parent) : QObject(parent) @@ -79,11 +82,26 @@ void WebUI::init() #endif if (!httpServer_->isListening()) { - bool success = httpServer_->listen(QHostAddress::Any, m_port); - if (success) - logger->addMessage(tr("The Web UI is listening on port %1").arg(m_port)); - else - logger->addMessage(tr("Web UI Error - Unable to bind Web UI to port %1").arg(m_port), Log::CRITICAL); + int n = 0; + bool success; + // ifdef here + n = sd_listen_fds(0); + // endif + if (n == 1) { + success = httpServer_->setSocketDescriptor(SD_LISTEN_FDS_START); + m_port = httpServer_->serverPort(); + if (success) + logger->addMessage(tr("The Web UI is listening systemd-inherited port %1").arg(m_port)); + else + logger->addMessage(tr("Web UI Error - Unable to bind Web UI to systemd-inherited socket"), Log::CRITICAL); + } + else { + success = httpServer_->listen(QHostAddress::Any, m_port); + if (success) + logger->addMessage(tr("The Web UI is listening on port %1").arg(m_port)); + else + logger->addMessage(tr("Web UI Error - Unable to bind Web UI to port %1").arg(m_port), Log::CRITICAL); + } } // DynDNS ``` A bit of explanation. `sd_listen_fds` returns the number of file descriptors systemd is listening on for us. If we have exactly 1 socket, then use that instead of opening our own port. This effectively ignores the webui port configuration. The parameter is 0 so that if a WebUI class is reinstantiated we still get the systemd-activated fd. This can be changed so that only the first instance inherits the fd by setting it to 1, then future calls will return 0. `SD_LISTEN_FDS_START` is always the first socket passed via activation. Other sockets would be pased as `SD_LISTEN_FDS_START+1`, etc, but we only care about one. Systemd does not care if the socket is closed when the program exits. To use, you can add the following socket file: ``` # qbittorrent-nox.socket [Unit] Description=qBittorrent WebUI listen stream [Socket] ListenStream=0.0.0.0:8080 [Install] WantedBy=sockets.target ```
Author
Owner

@fsateler commented on GitHub (Feb 18, 2016):

ping. If you can give me some guidance on how to make qmake detect if a library is available and add appropriate defines in that case, I can prepare a PR for this.

thanks

@fsateler commented on GitHub (Feb 18, 2016): ping. If you can give me some guidance on how to make qmake detect if a library is available and add appropriate defines in that case, I can prepare a PR for this. thanks
Author
Owner

@zeule commented on GitHub (Jan 11, 2017):

@fsateler qmake can not do that, that is why it is used with autotools.
You can add a switch to configure.ac or cmake files, or use the existing one for systemd. Then in configure.ac append library to LIBS variable (see boost example).

@zeule commented on GitHub (Jan 11, 2017): @fsateler qmake can not do that, that is why it is used with autotools. You can add a switch to `configure.ac` or cmake files, or use the existing one for systemd. Then in `configure.ac` append library to `LIBS` variable (see boost example).
Author
Owner

@sledgehammer999 commented on GitHub (Oct 29, 2020):

This issue has been closed and locked for being too old, and thus either most likely resolved in recent versions or no longer applicable.
If you experience the reported problem or similar in the latest version, please open a new issue report with the requested information in the issue template.

A new issue report with relevant updated data gathered from the latest version is preferable to necroing an old report with a comment like "still happens in version x.y.z", even if you think the bug is the same, or suspect of a regression.
Due to the changes made to the qBittorrent code and its dependencies over time, the exact cause of your problem could be totally different than the original one, despite the visible symptoms of the bug being similar.
Thus, providing relevant updated information is crucial to find and fix the root cause of a recurrent problem or regression.

Thank you for your contributions.

@sledgehammer999 commented on GitHub (Oct 29, 2020): This issue has been closed and locked for being too old, and thus either most likely resolved in recent versions or no longer applicable. If you experience the reported problem or similar in the **latest** version, please open a new issue report with the requested information in the issue template. A new issue report with relevant updated data gathered from the latest version is preferable to necroing an old report with a comment like "still happens in version x.y.z", even if you think the bug is the same, or suspect of a regression. Due to the changes made to the qBittorrent code and its dependencies over time, the exact cause of your problem could be totally different than the original one, despite the visible symptoms of the bug being similar. Thus, providing relevant updated information is crucial to find and fix the root cause of a recurrent problem or regression. Thank you for your contributions.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/qBittorrent#2927
No description provided.