logoalt Hacker News

jchwyesterday at 12:49 AM1 replyview on HN

Regardless of exactly how clean the environment is, my favorite part of systemd is the fact that there is only one regardless of how something was triggered. Whether a unit is triggered via a mount unit, timer unit, udev rule, it's the same units at the end, so it's the same environment.

The same problems that could be caused by a polluted environment in cron can be caused in reverse by a polluted environment elsewhere, when you unwittingly copy a command that depends on some environment being set. If you are using systemd as the service manager, this necessarily doesn't happen because it's all units. (Well, you could still copy something from outside of systemd and run into a similar problem, but at least there's essentially only one set of caveats you have to learn for whatever thing you want executed in the background.)

So I guess this isn't so much cron vs systemd timers, but more cron + other init and service supervisors vs systemd init in general.


Replies

simoncionyesterday at 7:12 AM

> Regardless of exactly how clean the environment is, my favorite part of systemd is the fact that there is only one regardless of how something was triggered. Whether a unit is triggered via a mount unit, timer unit, udev rule, it's the same units at the end, so it's the same environment.

>

> The same problems that could be caused by a polluted environment in cron can be caused in reverse by a polluted environment elsewhere, when you unwittingly copy a command that depends on some environment being set.

I'm confused about what you need this for? Are you running some utility command that needs the same environment provided by the daemon's service file? If so, any competent init system lets you extend upstream-provided service files. In OpenRC:

  # tail -n 1000 /etc/*/test-service
  ==> /etc/conf.d/test-service <==
  extra_commands="${extra_commands} maintenance"
  
  OTHER_THING="overriden other-thing"
  
  maintenance () {
    ebegin "doing maintenance. IV='$INIT_VAR' OT='$OTHER_THING'"
    set > /tmp/maintenance-set.txt
    eend 0
  }
  
  ==> /etc/init.d/test-service <==
  #!/sbin/openrc-run
  
  name="test-service daemon"
  command=/usr/bin/socat
  command_user=nobody:nobody
  command_args="UDP-RECVFROM:6666,fork SYSTEM:'/bin/true'"
  supervisor=supervise-daemon
  extra_commands="rebuild"
  
  INIT_VAR=${INIT_VAR:-"init var"}
  OTHER_THING=${OTHER_THING:-"stock other-thing"}
  
  depend() {
    use net
  }
  
  start_pre() {
    set > /tmp/start-set.txt
  }
  
  rebuild () {
    ebegin "doing rebuild. IV='$INIT_VAR' OT='$OTHER_THING'"
    eend 0
  }
  # /etc/init.d/test-service start
  test-service              | * Starting test-service daemon ...                                [ ok ]
  # /etc/init.d/test-service maintenance
  test-service              | * doing maintenance. IV='init var' OT='overriden other-thing' ... [ ok ]
  # /etc/init.d/test-service rebuild
  test-service              | * doing rebuild. IV='init var' OT='overriden other-thing' ...     [ ok ]
  # pgrep --list-full socat
  133705 /usr/bin/socat UDP-RECVFROM:6666,fork SYSTEM:/bin/true
The environment when the service is starting is effectively identical to the one when our custom function is being called:

  # diff -u0 /tmp/*-set.txt
  --- /tmp/maintenance-set.txt 2026-06-02 23:53:19.703048431 -0700
  +++ /tmp/start-set.txt 2026-06-02 23:53:15.265094855 -0700
  @@ -9 +9 @@
  -BASH_LINENO=([0]="410" [1]="0")
  +BASH_LINENO=([0]="409" [1]="0")
  @@ -11 +11 @@
  -BASH_SOURCE=([0]="/etc/init.d/../conf.d/test-service" [1]="/usr/libexec/rc/sh/openrc-run.sh")
  +BASH_SOURCE=([0]="/etc/init.d/test-service" [1]="/usr/libexec/rc/sh/openrc-run.sh")
  @@ -30 +30 @@
  -FUNCNAME=([0]="maintenance" [1]="main")
  +FUNCNAME=([0]="start_pre" [1]="main")
  @@ -64 +64 @@
  -PPID=133712
  +PPID=133702
  @@ -69 +69 @@
  -RC_CMD=maintenance
  +RC_CMD=start
  @@ -73 +73 @@
  -RC_OPENRC_PID=133710
  +RC_OPENRC_PID=133700
  @@ -75 +75 @@
  -RC_RUNSCRIPT_PID=133711
  +RC_RUNSCRIPT_PID=133701
  @@ -93 +93 @@
  -_='doing maintenance. IV='\''init var'\'' OT='\''overriden other-thing'\'''
  +_=']'
So, if you need to do maintenance for a service on a schedule in the same environment that is provided for starting that service, you can simply extend the service script and use cron to execute that functionality.

But. Another thing that confuses me is why you think that SystemD [0] provides anything special here? If you were to create a service file in most any other service manager and start it with cron, you'd get exactly the same environment sanitization as you get for all other services. Given your testimony, I expect that prior to SystemD, you'd have refused to create service files for things like one-off jobs that weren't system services... so why are you okay with it now that you're using SystemD?

[0] I spell it "SystemD" not to mock it -as I understand some do- but to distinguish The Systemd Project from systemd(1). It sucks minor ass that the two share the same name, but what can you do?