/run/user/1000
, which of course does not exist until user #1000 logs in or explicitly starts up xyr per-user service management, is a red herring. The entire mechanism that uses it should not be there.
Bug #215 for this program runs a lot deeper than you think. This service unit file is very wrong, as is the operation of the program itself. There is a lot of cargo cult programming, based upon not actually understanding the fundamentals of systemd service units.
- Service units are not shell script. The systemd manual does explain this. The
ExecStart
setting here causes the service program to be run with some extra arguments, 2>&1>
and /dev/null
.
- The service manager already ensures that only one service runs. All of this code added here is unnecessary junk.
- The rickety and dangerous PID file mechanism should not be used. It has no place in proper service management.
- The service manager also handles invoking the service in a dæmon context. A lot of the other code in
main()
is also unnecessary junk, based upon the dæmonization fallacy.
- Three service units is unnecessary maintenance overhead. They only differ in their
After
settings, and those can just be merged into one.
- Graceless termination is not success. Given that there was already one problem with cleaning up files upon termination,
SuccessExitStatus=SIGKILL
is wrongheaded. Normal termination should be graceful, via SIGTERM
, and SIGKILL
should be considered abnormal. (Of course, the whole output
file mechanism is a badly implemented home-grown logging mechanism that should not be used under service management, as already explained.) This is the systemd default.
- Destructors of the database objects and other stuff should run. Do not leave
main()
with exit()
.
A dæmon program implemented properly for running under a service manager, be it from daemontools, runit, s6, nosh, systemd, or something else, is a lot shorter:
… // the same until this point
void pvo_upload(void)
{
std::clog << "Starting Daemon..." << std::endl;
CommonServiceCode();
std::clog << "Stopping Daemon..." << std::endl;
}
int main(int argc, char *argv[])
{
int c;
const char *config_file = "";
/* parse commandline */
while(1)
{
static struct option long_options[] =
{
{ "config-file", required_argument, 0, 'c' },
{ 0, 0, 0, 0 }
};
int option_index = 0;
c = getopt_long (argc, argv, "c:", long_options, &option_index);
if (c == -1) break;
switch (c)
{
case 'c':
config_file = optarg;
break;
default:
return EXIT_FAILURE;
break;
}
}
if (cfg.readSettings(argv[0], config_file) != Configuration::CFG_OK)
return EXIT_FAILURE;
std::clog << "Starting SBFspotUploadDaemon Version " << VERSION << std::endl;
// Check if DB is accessible
db_SQL_Base db = db_SQL_Base();
db.open(cfg.getSqlHostname(), cfg.getSqlUsername(), cfg.getSqlPassword(), cfg.getSqlDatabase());
if (!db.isopen())
{
std::clog << "Unable to open database. Check configuration." << std::endl;
return EXIT_FAILURE;
}
// Check DB Version
int schema_version = 0;
db.get_config(SQL_SCHEMAVERSION, schema_version);
db.close();
if (schema_version < SQL_MINIMUM_SCHEMA_VERSION)
{
std::clog << "Upgrade your database to version " << SQL_MINIMUM_SCHEMA_VERSION << std::endl;
return EXIT_FAILURE;
}
// Install our signal handler.
// This responds to the service manager signalling the service to stop.
signal(SIGTERM, handler);
// Start daemon loop
pvo_upload();
return EXIT_SUCCESS;
}
And the service unit is shorter, too:
[Unit]
Description=SBFspot upload daemon
After=mysql.service mariadb.service network.target
[Service]
Type=simple
TimeoutStopSec=10
ExecStart=/usr/local/bin/sbfspot.3/SBFspotUploadDaemon
Restart=on-success
[Install]
WantedBy=multi-user.target
The log output is viewable with systemctl status
and journalctl
(with the -u
option and the service name, if desired).
Further reading
User=pi
or daemon will run as root – SBF Jun 29 '18 at 22:17