10

I have a deployment script, it must add something to a user crontab (trigger a script that clean logs every XXX days), however this must only be done during the first deployment, or when it needs to be updated.

(I can run xxx.py deploy env or xxx.py update env)

so I have to do this:

Check if my cronJob already exist
Put my cronJob if it does not already exist
or
update my cronjob if one of the parameter of the command is different

I don't see how to add/check/remove something to the crontab without using crontab -e or editing the crontab file (download it, rewrite it, re-upload it)

PS : this is a user specific cronjob, "webadmin" is going to do it and he should not use sudo to do it.

  • 1
    Does it have to be in a user-specific crontab? Most prepackaged cron jobs go into one of the /etc/cron.* directories. – a CVn Jul 21 '16 at 13:26
  • Does CentOS have /etc/cron.d? If so, put your script in there using a name unique to your application – roaima Jul 21 '16 at 13:26
  • yes it's user specific. I can't add it to /etc/cron.d because it's a root file, thus only root's job are alowed inside (I could sudo but that a bad practice I've been told) – sliders_alpha Jul 21 '16 at 13:28
  • 1
    same as /etc/crontab, files in /etc/cron.d/ have an extra field for the username, immediately after the schedule spec. e.g. * * * * * username /path/to/script. See man 5 crontab and search for SYSTEM CRON. – cas Jul 21 '16 at 13:30
  • See also stackoverflow.com/questions/610839/… – rogerdpack Apr 17 '18 at 19:29
12

my best idea so far

to check first if the content matches what should be in there and only update if it doesn't:

if [[ $(crontab -l | egrep -v "^(#|$)" | grep -q 'some_command'; echo $?) == 1 ]]
then
    echo $(crontab -l ; echo '* 1 * * * some_command') | crontab -
fi

but this gets complicated enough to build a separate script around that cron task.

other ideas

you could send the string via stdin to crontab (beware, this clears out any previous crontab entries):

echo "* 1 * * * some_command" | crontab -

this should even work right through ssh:

echo "* 1 * * * some_command" | ssh user@host "crontab -"

if you want to append to the file you could use this:

# on the machine itself
echo "$(echo '* 1 * * * some_command' ; crontab -l)" | crontab -
# via ssh
echo "$(echo '* 1 * * * some_command' ; ssh user@host crontab -l)" | ssh user@host "crontab -"
  • that depends: do you need what would have been in there? :) – Phillip -Zyan K Lee- Stockmann Jul 21 '16 at 13:18
  • awww ... not running as root? ... I'll rewrite it ... – Phillip -Zyan K Lee- Stockmann Jul 21 '16 at 13:30
  • eh, I like that :D – sliders_alpha Jul 21 '16 at 15:39
  • I had two issues with this solution: 1) echo '*... expanded the * to a list of files. 2) the line endings in the crontab got deleted. – Heath Raftery Sep 8 '19 at 1:05
  • I was able to fix these issues by: 1) change to echo "*... and 2) remove echo $ from the start of the line. – Heath Raftery Sep 8 '19 at 2:03
3

I highly recommend using Ansible* for this rather than rolling your own. Or Puppet or Chef — but Ansible is well-suited for zero-infrastructure deploy scripts like this.

That's because there are already modules meant to solve problems like this, and config management tools have have idempotence as a basic design goal — that's the property of only changing when it needs to even if you accidentally (or intentionally) run it again.

In particular, Ansible's cron module can modify user crontabs. As a bonus, if you want to later adjust to use system crontabs, it'll be a very easy tweak rather than a rewrite.


* disclaimer: I work for Red Hat, and Ansible is a Red Hat sponsored project.

  • Yup, thing is, I didn't knew about ansible 2 month ago, and now we have a massive python deployer script (but he is MAGNIFICIENT, readable, maintanable, ;)) Next time, I'll use ansible but right now going back is impossible (money money money) – sliders_alpha Jul 21 '16 at 15:38
2

For the record I'm going to suggest using /etc/cron.d/. Only root can write files here but the entries can be run as any user (without need for sudo).

echo '0 0 * * 0 webadmin /usr/local/bin/tidy_logfiles' > ~/webadmin.cron
scp -p ~/webadmin.cron root@remote_host:/etc/cron.d/webadmin

This can be applied multiple times, updating the local webadmin.cron file as necessary before copying it across.

You can even remove the provisioning:

ssh -q root@remote_host rm -f /etc/cron.d/webadmin

Note that in many cases you cannot provide root's password for the scp/ssh commands. Instead you need to have set up public/private key certificates. Also, by implication the local account (whatever it is) will have full root access to the remote server. It's unclear at the moment whether that would be show-stopper for your specific scenario.

  • It's my client server, I cannot log as root, I can sudo into it BUT, if I do that they'll kill me. This is a webadlmin job so it should only be in the webadmin stuff, that's what the sysadmin told me. – sliders_alpha Jul 21 '16 at 15:40
  • @sliders_alpha the job only runs as webadmin. It's the provisioning that requires root equivalence. However, I'll look for a non-root solution too. – roaima Jul 21 '16 at 15:44
  • 1
    +1. /etc/cron.d/ exists for exactly this purpose - so that packages / deployments can just drop a crontab file in here. – cas Jul 22 '16 at 0:20
1

If you want to add a cron job via the target account, run crontab -e. This command passes the crontab through an editor. Tell it to use an editor command that modifies the crontab as you desire. The editor command is executed as a shell snippet with the name of a temporary file appended.

unset VISUAL
EDITOR='update_crontab () {
  set -e
  new=$(mktemp)
  if <"$1" grep -v "^#" | grep -w do_stuff; then
    # Remove existing entries containing do_stuff
    grep -v -w do_stuff "$1" >"$new"
  else
    cp "$1" "$new"
  fi
  # Add the new entry
  echo "1 2 3 4 5 do_stuff --new-options" >>"$new"
  mv "$new" "$1"
}
update_crontab' crontab -e

This approach is more reliable than the native crontab -l | … | crontab - because this one is vulnerable to a race condition if the crontab is edited concurrently: modifications made between the call to crontab -l and the call to crontab - would be undone.

1

This is an adaptation of what @phillip-zyan-k-lee-stockmann offered, based on his "Best idea so far" code.

My changes from his (excellent and helpful snippet) are basically:

  • Regex for not just the command name, but also the whole entry including time strings. That way it could support adding a command even if there are same-named or overlapping-named commands in other entries. (It still won't add the same command on the same schedule twice.)
  • A bit of logging
  • I switched (and named) mine to hourly for various reasons; easy to adjust it back per crontab syntax

And so here's my code for what I called crontab-add-hourly.sh:

#!/bin/bash

# PURPOSE:
# To allow simple, programmatic addition of commands/entries into the crontab (if not already present)

cmd=$1
entry="0 * * * * $cmd"
printf "we want to add this entry:\n$entry\n\n" 
escapedEntry=$(printf '%s\n' "$entry" | sed 's:[][\/.^$*]:\\&:g') #from: https://unix.stackexchange.com/a/129063/320236
printf "but first we'll see if it's already in there using this regex pattern:\n$escapedEntry\n\n"

if [[ $(crontab -l | egrep -v '^(#|$)' | grep -q "$escapedEntry"; echo $?) == 1 ]] # from: https://unix.stackexchange.com/a/297377/320236
then
    printf "all clear; pattern was not already present; adding command to crontab hourly:\n$cmd\n\n"
    (crontab -l ; printf "$entry\n\n") | crontab -
else
    printf "pattern already present; no action taken\n\n"
fi

Example usage and output:

$ ./crontab-add-hourly.sh my-script.bash

we want to add this entry:
0 * * * * my-script.bash

but first we'll see if it's already in there using this regex pattern:
0 \* \* \* \* my-script\.bash

all clear; pattern was not already present; adding command to crontab hourly:
my-script.bash

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.