3

I would like to schedule a command to be run at a specific time, identified by a Unix timestamp. It seems like this might be doable with systemd, using systemd-run, but how would that work? And what would happen to the scheduled command if the machine is powered down or suspended at the time when the command is scheduled?

Related question: doing the same thing with at from atd (requires parsing the timestamp back into a date): Is it possible to use the at command to schedule a job to run at a given timestamp?

a3nm
  • 9,207
  • Possible? Of course it's possible. You may need to use a programming language such as PHP or C/C++ that takes the timestamp as input and outputs a date-time in a format readable by the at command. – UncaAlby Dec 31 '19 at 17:15
  • @UncaAlby: Thanks, but I know I can do this with at. This question is about doing it with systemd. – a3nm Jan 01 '20 at 18:17
  • OK -- (padded to meet SO's minimum comment length) -- Why? – UncaAlby Jan 02 '20 at 00:42
  • Well, it looks like it should be possible but I didn't find good documentation for it. Also, atd is an old tool whose functionality is supposed to be captured by systemd, so with more and more programs depending on systemd it makes sense for me to use systemd for this task too. – a3nm Jan 03 '20 at 17:40
  • 2
    OK, this is seriously only my humble opinion, no sarcasm intended -- if you can get it working using "at", then get it working using "at". Then move on to the next burning issue. Perhaps one day "at" will be deprecated -- obsolete -- but, so long as it's still an active part of probably every *nix distribution in existence, systemd or no systemd, you might as well use it. Research how to get it working with systemd when there aren't other fires to put out (there are almost always other fires to put out). – UncaAlby Jan 06 '20 at 01:14

1 Answers1

5

systemd-run can schedule a command to be run at a specific time, as long as:

  • the system is not powered down before that time, because it writes a transient timer & service file to /run, which is cleared at reboot, and
  • the system is not suspended during that specific time, unless you use the "WakeSystem" option (see below).

See even further below for a reboot-persistent option.

A simple example for systemd-run:

# generate an arbitrary timestamp, in seconds-since-the-epoch
timestamp=$(date -d 'now + 42 seconds' +%s)
systemd-run --on-calendar "$(date -d @"$timestamp" +'%F %T')" \
  --timer-property=AccuracySec=1us \
  touch /tmp/done

The important parts to get systemd-run to use a seconds-since-the-epoch timestamp are:

  • to use --on-calendar and
  • here, using GNU date to convert the seconds-since-the epoch timestamp into a format that systemd-run understands. You could input or convert the timestamp yourself, as long as the result is in a format that OnCalendar understands.

The current documentation for OnCalendar indicates that it directly supports input in a seconds-since-the-epoch timestamp. That '@seconds' support was added in systemd version 234 (search that page for the file src/basic/calendarspec.c) in commit d80e5b7. Your version of systemd may predate this change; for examples:

  • RHEL 7 started with systemd 208 and was later upgraded to version 219; RHEL 8 uses version 239 (ref).
  • Debian 8 used version 215, Debian 9 used version 232, and Debian 10 uses version 241 (ref).
  • Ubuntu 16.04 LTS had version 229, Ubuntu 17.10 had version 234, and Ubuntu 18.04 LTS had version 237 (ref).

With a new-enough version of systemd, you could use this invocation:

timestamp=$(date -d 'now + 42 seconds' +%s)
systemd-run --on-calendar "@${timestamp}" \
  --timer-property=AccuracySec=1us \
  touch /tmp/done

I've also shown a timer-property of AccuracySec, which defaults to 1 minute, and whose value you should adjust based on the documentation:

To optimize power consumption, make sure to set this value as high as possible and as low as necessary.

I haven't tested it, but there is also a timer option called WakeSystem which would:

cause the system to resume from suspend, should it be suspended and if the system supports this. Note that this option will only make sure the system resumes on the appropriate times, it will not take care of suspending it again after any work that is to be done is finished.

You would integrate it into the above like so:

timestamp=$(date -d 'now + 42 seconds' +%s)
systemd-run --on-calendar "$(date -d @"$timestamp" +'%F %T')" \
  --timer-property=AccuracySec=1us \
  --timer-property=WakeSystem=true \
  touch /tmp/done

To ask systemd to run a command at a certain time, in a way which would persist beyond a reboot, and which is independent of any particular login session, you would need to place timer and service unit files under /etc/systemd/system/. /etc/systemd/system is where local configuration is stored for systemd.

A sample timer file would be:

[Timer]
AccuracySec=1us
[Unit]
Description=2020-01-22 09:50:00 timer
[Timer]
OnCalendar=2020-01-22 09:50:00

and a sample service file would be:

[Unit]
Description=my 2020-01-22 09:50:00 service
[Service]
ExecStart=
ExecStart=@/usr/bin/bash "/usr/bin/bash" "-c" "echo 2020-01-22 09:50:00 timer and service ran > /tmp/done"

You would then inform systemd about the files by reloading it:

systemctl daemon-reload

... and then enabling the timer:

systemctl enable foo.timer

... and then starting the timer:

systemctl start foo.timer

You may want to periodically remove expired timer and service units from /etc/systemd/system, once their time has passed.

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255