1

This question is perhaps related to the one I am asking here.

This is in the context of https://github.com/bstarynk/helpcovid/ (a GPLv3+ software for Linux, currently -april 2020- in active development, which is a specialized C++ multithreaded web application for Linux, so work in progress on April 8, 2020)

I am renting a VPS on host b-star-y.tech running Linux/Debian/Buster.

I am developing with others helpcovid, a multi-threaded GPLv3+ web application software coded for Linux in C++17.

I am not familiar with systemd, which is used by Debian/Buster on that host, or with docker

I would like to implement the following infinite loop in a git cloned file tree.

  • git pull
  • make
  • run ./helpcovid -D -T2 with both stderr and stdout redirected to some file.
  • sleep 5

and repeat indefinitely.

I am tempted to use a crontab job, or perhaps atd with batch

Is there a better way with systemd ?

You may email me to basile@starynkevitch.net

1 Answers1

3

For about a quarter of a century, now, we've had toolsets that allow unprivileged users to run nonce services ad hoc. Daniel J. Bernstein's daemontools is one of the earliest. One doesn't have to set up scan directories and other infrastructure. One can just run a program under supervise directly. M. Bernstein's UCSPI-TCP toolset, furthermore, is an example of the several toolsets that we've had for a similar length of time that handle services that need to accept TCP connections and do stuff.

There are other toolsets that do the same, without need for systemd, and of course with systemd one can also set up ad hoc per-user services.

It is 2020. There is no good reason to write new programs that use the rickety and dangerous PID file mechanism. There's no need for any of that PID file code that you've written.

Similarly, it's a good idea to write a TCP server so that it can inherit its listening socket, as an already open file descriptor. This is in fact the way that system-wide services, run from inted, conventionally have operated since the 1980s. It can be done with per-user services, too. The s6 and nosh toolsets, and indeed systemd, all provide ways in which a service can be given its listening socket. There's no need for any of that nonce URL parsing code that you have written, which like many off-the-cuff parsers doesn't handle all of the forms that a URL can take.

Unfortunately, as I said, your cpp-httplib library does not have a constructor or function member where it can be given the file descriptor for the listening socket. This is a major oversight in the design of that library, given that inheriting socket file descriptors has been a normal practice for three and a bit decades.

something other than systemd

Both my nosh toolset and Laurent Bercot's s6 toolset provide ways to run nonce programs as ad hoc per-user services.

Under the nosh toolset, one would have a helpcovid subdirectory, with a service subdirectory beneath that containing the various programs for handling the service.

helpcovid/service/start and helpcovid/service/stop are fairly minimal:

#!/bin/nosh                                                         
#Start file generated from ./helpcovid.socketand ./helpcovid.service
true
#!/bin/nosh
#Stop file generated from ./helpcovid.socketand ./helpcovid.service
true

The meat is in helpcovid/service/run and helpcovid/service/service:

#!/bin/nosh
#Run file generated from ./helpcovid.socketand ./helpcovid.service
#Starynkevitch helpcovid listening socket
tcp-socket-listen --systemd-compatibility ::0 50002
envdir env
setenv LC_ALL fr_FR.UTF-8
chdir /home/basile/dev/helpcovid/test
./service
#!/bin/nosh
#Service file generated from ./helpcovid.service
#Starynkevitch helpcovid service
sh -c 'exec /home/basile/dev/helpcovid/work/helpcovid ${flags}'

And helpcovid/service/restart controls the restart logic:

#!/bin/sh
#Restart file generated from ./helpcovid.service
sleep 5
exec true   # ignore script arguments

This one would run by giving it to a per-user instance of service-manager. The toolset does the binding and listening on the socket, reads environment variable configuration from an envdir, and passes the open file descriptor to the eventual program using the LISTEN_FDS protocol, having changed working directory to the place where your test webroot/ directory lives.

A similar structure is employed by s6, although the tool names are different (e.g. s6-tcpserver6-socketbinder rather than tcp-socket-listen) and the restart logic is differently handled. s6 does not require a service manager. Like the original daemontools, one can just run s6-supervise by hand to invoke a service:

s6-supervise ./helpcovid/service/

I'm not going to go into a lot of detail, as the main point is that not having systemd is no excuse for bad dæmon design (indeed good dæmon design principles originated with much older non-systemd mechanisms) and that the mechanisms for passing open file descriptors for listening sockets apply to and can be used with more than just systemd.

systemd

The aforegiven programs were actually converted from a systemd socket unit and service unit that I quickly threw together. They show how one would set up a per-user service on systemd, with files in ~/.config/systemd/user/:

# helpcovid.socket
[Unit]
Description=Starynkevitch helpcovid listening socket
[Socket]
ListenStream=50002
Accept=No
[Install]
Wanted-By=default.target
# helpcovid.service
[Unit]
Description=Starynkevitch helpcovid service
[Service]
Environment=LC_ALL=fr_FR.UTF-8
Restart=always
RestartSec=5
ExecStart=/home/basile/dev/helpcovid/work/helpcovid ${flags}
WorkingDirectory=/home/basile/dev/helpcovid/test
#This has no meaning for systemd.
EnvironmentDirectory=env

This is controlled with the likes of systemctl --user start helpcovid.socket.

In the world of systemd, these are the configuration files. The user wants to change the TCP port number? The user changes the ListenStream= setting. No need for redundant extra environment variables for setting the locale. The user just sets the actual LC_ALL (or whatever) variable in the manner shown.

logging

Logging is also handled by service management. The nosh toolset and s6 way is to feed the standard output and standard error through a pipe to a secondary service running cyclog, s6-log, or something. systemd stuffs log output into the user part of the centralized systemd journal, which you use journalctl --user to read.

your program

Your program needs to log to std::clog (i.e. standard error), call setlocale() with NULL to read the standard environment variables, and have code that handles the LISTEN_FDS mechanism. There are plenty of choices for that last, from a non-portable helper function library that comes with systemd to other people's more portable workalikes.

It's definitely far less code to put in than all of that TCP port number parsing code, rickety and dangerous PID file code, and HELPCOVID_LOCALE and --locale code that can be taken out this way. ☺

Further reading

JdeBP
  • 68,745