How can I determine, within a shell script, whether it is being called by systemd or not?

From Lucas Werkmeister's informative answer on Server Fault:

  • With systemd versions 231 and later, there's a JOURNAL_STREAM variable that is set for services whose stdout or stderr is connected to the journal.
  • With systemd versions 232 and later, there's an INVOCATION_ID variable that is set.

If you don't want to rely on those variables, or for systemd versions before 231, you can check if the parent PID is equal to 1:

if [[ $PPID -ne 1 ]]
then
  echo "Don't call me directly; instead, call 'systemctl start/stop service-name'"
  exit 1
fi >&2

The Short Answer

if ! grep -qEe '[.]service$' /proc/self/cgroup; then
    echo "This script should be started with systemctl" >&2
    exit 1
fi

...or, if you know the specific service name you're expected to run as, and want to be robust against misconfigurations that prevent a user session from being created:

if ! grep -qEe '/myservice[.]service$' /proc/self/cgroup; then
    echo "This service should be started with systemctl start myservice" >&2
    exit 1
fi

Why It Works

One way to determine which service -- if any -- started the current process is checking /proc/self/cgroup. For a systemd-triggered service, this will contain the service name; for example:

12:pids:/system.slice/dhcpcd.service
11:rdma:/
10:memory:/system.slice/dhcpcd.service
9:blkio:/system.slice/dhcpcd.service
8:devices:/system.slice/dhcpcd.service
7:hugetlb:/
6:cpuset:/
5:freezer:/
4:cpu,cpuacct:/system.slice/dhcpcd.service
3:net_cls,net_prio:/
2:perf_event:/
1:name=systemd:/system.slice/dhcpcd.service
0::/system.slice/dhcpcd.service

...whereas for a process associated with a user's session, the cgroup will be something more like /user.slice/user-1000.slice/session-337.scope (assuming that this is the user with UID 1000's 337th session on the system since its last reboot).


A Fancier Implementation

If one wants to detect the specific service being run as, this too can be extracted from /proc/self/cgroup. Consider, for example:

cgroup_full=$(awk -F: '$1 == 0 { print $3 }' /proc/self/cgroup)
cgroup_short=${cgroup_full##*/}
case $cgroup_full in
  /system.slice/*.service) echo "Run from system service ${cgroup_short%.*}";;
  /user.slice/*.service)   echo "Run from user service ${cgroup_short%.*}";;
  *.service)               echo "Service ${cgroup_short%.*} type unknown";;
  *)                       echo "Not run from a systemd service; in $cgroup_full";;
esac

Another obvious solution that comes to mind is to add something like

Environment=FROM_SYSTEMD=1

to the service file, and test on that envvar.