Systemd timers replacement for cron jobs, plus Weechat LetsEncrypt certificate renewal and distribution.

systemadm

Systemd is here.  Like it or not, we’re being given a new tool and it’s time to learn how to use it.  Today, we address the systemd timers feature and it’s ability to implement the functions of cron.

This isn’t a beginner topic, this blog assumes you know the command line well and have at least some idea of how Linux boots up.  In addition to covering how to implement a timer, an example script will implement renewal and distribution of an updated LetsEncrypt certificate to multiple Weechat users running on a remote host or VM.

Digging right in: Systemd keeps it’s local configuration in /etc/systemd/system, so we’re going to work from that folder.

cd /etc/systemd/system

The first file we’ll create is the .timer file.  In my case, semidaily-timer.timer because I need to trigger twice a day.  If your timer might be used by multiple tasks, a generic name describing the period is appropriate.   My semidaily-timer.timer file contains:

[Unit]
Description=Semi Daily Timer (runs twice a day at 4:44am and 4:44pm)

[Timer]
OnCalendar=*-*-* 04,16:44:00
Unit=semidaily-timer.target

[Install]
WantedBy=multi-user.target

Hopefully the Description field is self explanatory.

The  [Timer] OnCalendar= field is a bit like crontab but simpler.  It’s made up of date fields separated by – and time fields separated by :.  There is a space between the date and time blocks.  The overall format is <year>-<month>-<day> <hour>:<minute>:<second>.

There can be an optional day of the week before the date, and seconds can include a decimal point to handle smaller time units.  The comma separated values in the hour field of the time block are indicating that our timer will trigger at both of the listed hours, 4am and 4pm.  The odd minute was chosen by a random number generator that rolled a “44”. A .. (dot dot) allows entering a range in a single field, and a / can be used to indicate a repeat interval.

Like cron, * is the wildcard and matches anything in that field as shown in the year, month, and day of my file. In addition, you can trigger based on day of the week.  For example OnCalendar=Mon *-*-* 01:00:00 would trigger at 1AM every Monday.  As usual, more details are in the man page. [1]

[Timer] Unit=semidaily-timer.target links in our .target file (described next.)

Something to note is that AccuracySec= defaults to 1 minute so the timing is only accurate to the minute by default.[2]

The [Install] WantedBy=multi-user.target field indicates that this timer can be started only when the multi-user.target has been reached.  The man page has a lengthy list of possible targets [3] and your distro may create more as they desire.  multi-user.target doesn’t guarantee networking is functioning so ideally we’d verify any connectivity needed in our script at run time.

Up next is the Target file.  Mine is called semidaily-timer.target and contains:

[Unit]
Description=Semi Daily Timer Target
StopWhenUnneeded=yes

Again, the Description field is self explanatory.  the [Unit] StopWhenUnneeded= field simply says that systemd can not run this target if nothing uses it.  This file is just a bit of glue and we’re not going to use anything else that isn’t defaults so it’s short and sweet[4].

The final file is our service file.  Since mine is going to be used to run the LetsEncrypt certificate update, I call mine letsencrypt.service.  The contents are:

[Unit]
Description=checks letsencrypt cert and updates if needed.
Wants=semidaily-timer.timer

[Service]
Type=simple
ExecStart=/usr/local/sbin/ssl-key-update.sh

[Install]
WantedBy=semidaily-timer.target

[Unit] Wants= and [Install] WantedBy= are your .timer and .target files from above. [Service] Type=simple means the ExecStart= is the main process of the service and that there is no need to delay after launching it before running other systemd processes. ExecStart=/usr/local/sbin/ssl-key-update.sh specifies our script to be run[5].

Lastly, we need to enable and start everything.

systemctl enable semidaily-timer.timer
systemctl enable letsencrypt.service
systemctl start semidaily-timer.timer

You can verify the setup started successfully with:

systemctl status semidaily-timer.timer

The output should be similar to:

● semidaily-timer.timer - Semi Daily Timer (runs twice a day at 4:44am and 4:44pm)
 Loaded: loaded (/etc/systemd/system/semidaily-timer.timer; enabled; vendor preset: enabled)
 Active: active (waiting) since Tue 2016-10-04 18:26:36 CDT; 43min ago

Oct 04 18:26:36 elm systemd[1]: Started Semi Daily Timer (runs twice a day at 4:44am and 4:44pm).

You can monitor that the timer triggers with:

journalctl -f -u letsencrypt.service

Active timers can be listed with:

systemctl list-timers

The LetsEncrypt certificate renewal script needs the various parameters at the top set, and to be saved in  /usr/local/sbin/ssl-key-update.sh.  It assumes your weechat users are members of the group weechat on the remote host and that you’re running nginx locally on your Ubuntu Xenial computer and have followed the EFF certbot instructions linked in the references [6] to get your certificate. This script assumes you have passwordless key based ssh login on the remote host/VM while running as the unprivileged user. Weechat does not add users to a group by default, any mechanism that gets a list of users could be substituted for the group member list.

Of course the above systemd timer instructions work for any other task that you wish to automate on a schedule as well.

Update: Weechat 1.7 changes the fifo name from weechat_fifo_<pid> to just weechat_fifo.  Script revised.

The script is:

#!/bin/bash
##run twice daily as root

UNPRIVILEGEDUSER=me
REMOTEUSER=weechat
REMOTEHOST=vm17
REMOTEMOUNT=/home/${UNPRIVILEGEDUSER}/${REMOTEHOST}
WEECHATGROUP=weechat
DOMIAN=mydomain.com

#renew cert
letsencrypt renew

#test cert newer than flag?  yes, process, no, exit
if [[ /etc/letsencrypt/live/${DOMIAN}/flag -ot /etc/letsencrypt/live/${DOMIAN}/cert.pem ]]; then

  echo "Certificate updated."
  #update flag
  touch -r /etc/letsencrypt/live/${DOMIAN}/cert.pem /etc/letsencrypt/live/${DOMIAN}/flag

  #restart nginx
  systemctl restart nginx

  #copy keys to a place unprivileged user can read them
  cp /etc/letsencrypt/live/${DOMIAN}/fullchain.pem /home/${UNPRIVILEGEDUSER}/
  cp /etc/letsencrypt/live/${DOMIAN}/privkey.pem /home/${UNPRIVILEGEDUSER}/

  #temporarily drop privileges to ${UNPRIVILEGEDUSER}
  su ${UNPRIVILEGEDUSER} <<'EOF'

  #mount ${REMOTEHOST} as ${UNPRIVILEGEDUSER} if needed (for sshkey)
  if [ -d ${REMOTEMOUNT}/home ]; then
    mounted=true
  else
    mounted=false
    sshfs ${REMOTEUSER}@${REMOTEHOST}:/ ${REMOTEMOUNT}
  fi

  #for each user
  for u in $(ssh ${REMOTEUSER}@${REMOTEHOST} "getent group ${WEECHATGROUP}" | cut -f4 -d:| tr ',' ' '); do

    echo "Processing $u..."

    #concatenate the key into the file, overwriting the old file.
    cat /home/${UNPRIVILEGEDUSER}/fullchain.pem /home/${UNPRIVILEGEDUSER}/privkey.pem > ~/${REMOTEUSER}/home/$u/.weechat/ssl/relay.pem

    #use fifo to set relay.network.ssl_cert_key to a dummy value then back to relay.pem to force a re-read
    ssh ${REMOTEUSER}@${REMOTEHOST} "sudo bash -c \"echo \\\"*/set relay.network.ssl_cert_key \\\"%h/ssl/relay.self.pem\\\"\\\" > /home/$u/.weechat/weechat_fifo\""
    ssh ${REMOTEUSER}@${REMOTEHOST} "sudo bash -c \"echo \\\"*/set relay.network.ssl_cert_key \\\"%h/ssl/relay.pem\\\"\\\" > /home/$u/.weechat/weechat_fifo\""

  done

  #dismount ${REMOTEHOST}i if needed
  if [ "$mounted" = "false ]; then
    fusermount -u ${REMOTEMOUNT}
  fi

  #return to root (EOF must be at character 1)
EOF

  #cleanup temp files
  rm /home/${UNPRIVILEGEDUSER}/fullchain.pem
  rm /home/${UNPRIVILEGEDUSER}/privkey.pem

fi

Reference:

man systemd.unit(5)
^[1] man systemd.time(5)
^[2] man systemd.timer(7)
^[3] man systemd.special(5)
^[4] man systemd.target(5)
^[5] man systemd.service(5)
^[6] https://certbot.eff.org/#ubuntuxenial-nginx

The above directions have been tested on Ubuntu 16.04 with systemd 229.

 

Advertisements

2 thoughts on “Systemd timers replacement for cron jobs, plus Weechat LetsEncrypt certificate renewal and distribution.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s