2

To get regular weather updates into my Redis database the scheme I am trying to use is as follows

I have a PHP script that fetches the weather from the relevant weather API. It goes something like this

<?php
 function getWeather()
 {
  if (weatherupdaterequired)
  {
   //weather API call
   //parse and store to database
  }
 }

 while (true)
 {
  getWeather();
  sleep(30);
 }
?>

which is stored in my /usr/local/bin folder. In the same folder I have a shell script, runweather which does just this:

#!/bin/sh
nohup php /usr/local/bin/echoweather.php  >/dev/null 2>&1 &

I normally tend to use #!/bin/bash but in this instance I found that when run at startup - as you will see below - only #!/bin/sh works. I assume that this has something to do with the bash shell not yet being available.

I then created a symlink to runweather

ln -s /usr/local/bin/runweather /etc/init.d/runweather

and then another symlink

ln -s /etc/init.d/runweather /etc/rc2.d/S99runweather

A few explanatory notes

  1. It is /usr/local/bin/echoweather.php that is doing all the real work. It runs at 30s intervals and sleeps when not working
  2. Just prior to terminating each run it places an ephemeral Redis key $redis-setEx("weatherreport",29,$echoCount)` which I can use to keep tabs on its health
  3. Placing the shell script that gets echoweather.php running at startup in /usr/local/bin, then symlinking it in /etc/init.d only to then symlink it again in /etc/rc2.d might look convoluted. I did this since I found that if I place the actual shell script in /etc/init.d and then symlink it to /etc/rc2.d it does not execute.

This scheme is working. I rebooted my server several times and checked on the health of echoweather.php by looking for the weatherreport key in Redis via redis-cli - always present and correct. However, I am a rank amateur when it comes to dealing with Ubuntu startup scripts. Perhaps there is a simpler way to do things? I'd be much obliged to anyone who might be able to comment.

DroidOS
  • 497
  • 1
  • 8
  • 21
  • I have rephrased my entire question and sketched out my own, working, solution in the interests of more clarity. I have got things working but I suspect that there might well be an easier way to go about all this. – DroidOS Feb 21 '20 at 08:12
  • Just symlinking it is not enough. etc/init.d/ and nowadays systemd needs info when to start it, if it is related to othter service (dns, networking, etc.). This could be a double... try this solution mentioned here https://askubuntu.com/questions/919054/how-do-i-run-a-single-command-at-startup-using-systemd – s1mmel Feb 24 '20 at 08:35
  • It is not clear why you are saying symlinking is not enough. I have not implied in my question that my solution is NOT working. It does in fact work. However, it is based on my, very, incomplete knowledge of the issues involved. I want to know if there is a better + easier way. I read through the solution you mention above. It will almost certainly work too. Is it more robust than what I have done? – DroidOS Feb 24 '20 at 12:35
  • 1
    /etc/init.d/ won't be around forever, it is still there but rather old. Take a look at systemd, this will be the future tool to use. You are lucky that it works, normally you need to make sure that certain other services like network, dns, etc. are started, before your script should be started. This relationship should be part of your service script. so in other words, if you follow my link and build a systemd service, it will most certainly last longer then your solution now. If e.g. the developers decide to obsolete /etc/init.d/ for the next release, your script won't work anymore. – s1mmel Feb 25 '20 at 15:09

1 Answers1

4

If you're not running a EOL version of Ubuntu, your init system is already systemd based, so using it is the right way to go. Some people might tell you to use Type=idle for your service unit, but please: (from the systemd man page):

Note that using any type other than simple possibly delays the boot process, as the service manager needs to wait for service initialization to complete. It is hence recommended not to needlessly use any types other than simple. (Also note it is generally not recommended to use idle or oneshot for long-running services.)

Taking that into consideration, we should manage our service ordering by setting targets and dependencies. You have at least three important requirements for your script to work:

  1. Your network stack needs to be functioning;
  2. Your script should run late in the boot process, and;
  3. Your script should run after Redis is fully started.

Having identified those requirements, let's take a look at the systemd targets so we know what we want, require and should depend or wait on:

  1. Units that strictly requires a configured network connection should pull in network-online.target, so our unit Wants this and should only run After this target;
  2. multi-user.target sets up a multi-user system and finishes up once everything required for that is done, so the unit probably Wants and is WantedBy it. As we really want our unit to run late into the boot process, running it After this target is what we want too;
  3. Redis is part of the multi-user system we are configuring, and our unit can only produce its desired effects if Redis up and running so it Requires Redis to be running and we should only load After it.

With that in mind, we can write a echoweather.service file into the /etc/systemd/system/ that takes all of this into account:

[Unit]
Description=Gets regular weather updates into my Redis database
Wants=network-online.target multi-user.target
Requires=redis-server.service
After=network-online.target multi-user.target redis-server.service

[Service]
PIDFile=/var/run/echoweather.pid
ExecStart=/usr/bin/php /usr/local/bin/echoweather.php  >/dev/null 2>&1 &
Type=forking
KillMode=process

[Install]
WantedBy=multi-user.target

Once it's done, reload your daemon files, enable your service unit and run your process:

$ sudo systemctl daemon-reload
$ sudo systemctl enable echoweather.service
$ sudo systemctl start echoweather.service
  • Very comprehensive answer with clearly explained steps. Much appreciated. – DroidOS Feb 26 '20 at 08:13
  • 1
    you probably want Type=simple since echoweather.php will not really fork. Thought it could fork via pcntl_fork(), it's probably not what OP wants to get into. – istepaniuk Jan 14 '21 at 22:45
  • @istepaniuk Fair and probably correct. It should be noted that services using the simple type will not propagate start-up errors and doesn't allow ordering of other units against its completion. – Alexandre Teles Jan 18 '21 at 20:04