3

How can I tell if a script was called from systemd or from a user?

I created a service for an old school daemon. We just switched to systemd and some of my admin like to call the rc-script directly. This won't work very well in the case of machine reboot. Systemd won't honor the PIDFile= because the new started daemon is not part of it's cgroup for the service.

I added the service file and the rc_script. I can't differ by user id, because to use the script the user has to be foobaruser.

The stop service is now problem, because systemd will recognize this from the deleted pid file.

So how do I find out if the script was called from systemd?

foobar.service

[Unit]
Description=Foobar Service
After=syslog.target network.target

[Service]
Type=forking
User=foobaruser
LimitNOFILE=60000
LimitNPROC=8000
TasksMax=8000
PIDFile=/local/foobar/foo.pid
ExecStart=/opt/foobar/rc_script start
ExecStop=/opt/foobar/rc_script stop
TimeoutSec=100
TimeoutStopSec=300
KillMode=none
RemainAfterExit=no
Environment=LANG=de_DE.UTF-8

[Install]
WantedBy=multi-user.target

/opt/foobar/rc_script skeleton

#!/bin/bash
case $1 in
start)
  VAR_IS_SYSTEMD=$( ... script to check if bash is run from systemd ... )
  if [ "$VAR_IS_SYSTEMD" = false ] ; then
    sudo systemctl start foobar.service
    exit
  fi
  # start service within systemd
  ;;
stop)
  # stop service
  ;;
esac
  • 2
    This is really not a problem you should aim to solve; better to make a clean break. Work on developing an understanding of what systemd (and journalctl) can do and after getting over the initial hump your users will develop their muscle memory and have a better mental model of how things work. Publish a conversion cheat-sheet within your team. – Cameron Kerr Aug 15 '18 at 8:30
  • @CameronKerr I agree with you for shorter scripts. Unfortunately this script grew to 2500 lines and has 36 option for calling. SO I decided to leave it that way it is and filter out the user/other script calls. – notes-jj Aug 17 '18 at 15:54
  • Just get rid of the old rc script and put the appropriate logic to start the service in the systemd unit. – Michael Hampton Aug 17 '18 at 17:47
6

I agree with the other posters that you should try to rewrite the service to not call the script at all (it’s fine if your admins want to keep using it, but does the systemd service need all the 36 options?) – but it’s also not hard to detect if the script was called from systemd.

  • systemd v232 added the concept of an invocation ID of a unit, which is passed to the unit in the $INVOCATION_ID environment variable. You can check if that’s set or not.

  • systemd v231+ sets the $JOURNAL_STREAM variable for services whose stdout or stderr is connected to the journal, which seems to be the case for your service, so you could also check for that variable if you’re on systemd v231. (On v232+, $INVOCATION_ID is definitely the better choice.)

  • On older systemd versions, I don’t think there’s an environment variable that’s always present, but you can of course define one yourself by adding something like this to your service:

    Environment=LAUNCHED_BY_SYSTEMD=1
    
2

A simple solution is to use a different script for systemd to start. The /opt/foobar/rc_script should just call systemctl start foobar.service and systemctl stop foobar.service.

Suse uses $SYSTEMD_NO_WRAP, but I don't know whether that is systemd standard or Suse specific.

if test -z "$SYSTEMD_NO_WRAP"; then
...
    /usr/bin/systemctl [start|stop] xxx.service
  • Unfortunately this does not work for "Amazon Linux 2" nor "Red Hat Enterprise Linux 7" – notes-jj Aug 17 '18 at 15:20
0

For "Amazon Linux 2" and "Red Hat Enterprise Linux 7" I found a solution.

On these linux systems system process PID=1 is comm=systemd. The parent ID PPID of the systemd started script is 1.

There may be other linux systems where this is not the case.

#!/bin/bash
VAR_IS_SYSTEMD_SYSTEM=$( [[ $(ps -o comm -p 1 --no-headers | tr -d ' ') == "systemd" ]] && echo true )
echo VAR_IS_SYSTEMD_SYSTEM = ${VAR_IS_SYSTEMD_SYSTEM}

VAR_IS_DAEMON_SCRIPT=$( [[ "${PPID}" == "1" ]] && echo true )
echo VAR_IS_DAEMON_SCRIPT = ${VAR_IS_DAEMON_SCRIPT}

if [[ "${VAR_IS_SYSTEMD_SYSTEM}" = true ]] && [[ "${VAR_IS_DAEMON_SCRIPT}" = true ]]; then
  echo "You are a systemd Daemon Script"
else
  echo "You are a normal Script"
fi

Elements/Commands used:

  • ps -o comm -p 1 --no-headers

    Get command name of process 1

  • tr -d ' '

    Remove formatting whitespace from ps command

  • "${PPID}"

    Parent PID of bash process

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.