The http6d service in the regular services collection is an example of a typical and commonly used TCP service.
Its start, stop, and restart programs are fairly trivial and straightforward.
The meat of the service is its run and service programs.
The service could be arranged as a single run program.
However, that does not permit adjustment of the service program, including its command-line arguments, whilst the service is up.
Having a separate service program, that is chained to for each new TCP connection, permits an administrator to adjust the main service program without taking the service down.
Of course, this has to be done carefully, because a live service could run the service program at any time, including halfway through when a text editor is updating the script.
So always edit a "service.new" script and then, after setting execute permission, atomically rename it to service.
The run and service programs are as follows:
#!/bin/nosh #Run file generated from build/http6d.socket tcp-socket-listen --systemd-compatibility --backlog 2 0 80 softlimit -o 20 -d 50000 envuidgid publicfile tcp-socket-accept --connection-limit 16 --no-delay ucspi-socket-rules-check ./service
#!/bin/nosh #Service file generated from build/http6d@.service #HTTP service over IP4/IP6 using Bernstein's publicfile httpd /home/publicfile/public
Like many of the pre-packaged services, these scripts are in fact automatically generated from a description file that is in the source package.
(In fact, that description is a slight variant on a systemd unit file.)
Both invoke nosh as their script interpreter, and comprise a series of utility commands, each chain-loading to the next, passing along what program arguments it has not itself absorbed.
In the run program:
nosh chain loads tcp-socket-listen.
tcp-socket-listen opens a TCP socket, binds it to a wildcard IP address and the well-known TCP port for HTTP, and then chain loads softlimit.
softlimit adjusts some kernel resource limits for the current process, and chain loads envuidgid.
envuidgid reads the system's account database for the user "publicfile", sets up some environment variables for later use, and chain loads tcp-socket-accept.
tcp-socket-accept sits waiting for TCP connections on the listening socket that was opened for it, forking a new process for each accepted connection and chain loading to ucspi-socket-rules-check in that process.
ucspi-socket-rules-check checks a local database, in the service directory, for access controls on the connecting client IP address, and if the client is permitted access chains to the service program.
In the service program:
nosh chain loads the service program proper, which is Bernstein's publicfile.
publicfile picks up the environment variables earlier set by envuidgid, changes to the data root directory, drops superuser privileges, and serves HTTP requests over the TCP connection.
The amount of setup in the run and service scripts depends from what the service program itself does, of course.
If publicfile did not change working directory and root directory, for example, one could do so with the chain-loading chdir and chroot commands.
Because the service's run program was generated from a systemd unit file, the run program passes tcp-socket-listen the --systemd-compatibility option.
This isn't strictly necessary here, since the program that accepts the socket connections doesn't require
systemd's LISTEN_FDS protocol
for knowing which open file descriptor is the listening TCP socket.
Were it a systemd-specific program, however, it would need this; and the option does no harm to non-systemd-specific server programs.
Looking at the rest of the service bundle, one finds the standard things for a "server" service. The service is auto-started (when enabled) by the "server" system target. It is stopped by the "shutdown" system target. It requires for correct operation that all services in the "basic" system target be started, before it itself is started. Its output is sent to a logger service.