# Deploy cron jobs to many servers from one script folder

A lightweight way to manage scheduled jobs on a handful of Linux servers without Ansible. Keep one folder of scripts and cron files on an admin box, copy it to every server, and let each server install the cron files meant for it.

## Folder layout
```
/opt/scripts/
  backup_scripts/...
  crontab_scripts/
    cron0hourly            # all servers
    cronloadmonitor        # all servers
    web01_croncertbot      # only on the host whose short name is "web01"
    db01_cronbackupmysql   # only on db01
  profile.d/*.sh           # login scripts for every server
```
- **Naming rule:** files starting with `cron` go to every server. Files named `<shorthost>_<name>` go only to that host.
- Every file uses `/etc/cron.d` format, which includes the user field:
  ```
  */5 * * * * root /opt/scripts/loadmon.sh >/dev/null 2>&1
  ```
- Put shell logic in scripts, never in a cron file. Cronie skips the **whole file** if one line isn't a valid cron entry.

## The installer (runs on each server)
Install only files with your own prefix, and remove only your own stale files. Don't wipe `/etc/cron.d`, because packages put their own jobs there (`0hourly`, `raid-check`, monitoring agents).
```
#!/bin/bash
set -euo pipefail
[[ $EUID -eq 0 ]] || exit 1
SRC=/opt/scripts/crontab_scripts
host=$(hostname -s | tr '[:upper:]' '[:lower:]')
want=()
for f in "$SRC"/cron* "$SRC"/"${host}"_*; do
  [[ -f $f ]] || continue
  name="optscripts-$(basename "$f")"
  install -m 644 "$f" "/etc/cron.d/$name"; want+=("$name")
done
for f in /etc/cron.d/optscripts-*; do
  [[ -e $f ]] || continue
  [[ " ${want[*]} " == *" $(basename "$f") "* ]] || rm -f "$f"
done
for f in /opt/scripts/profile.d/*.sh; do
  install -C -m 644 "$f" /etc/profile.d/
done
```
Run the installer from its own cron file, so you never overwrite root's personal crontab:
```
# /etc/cron.d/optscripts-installer
*/15 * * * * root /opt/scripts/crontab_scripts/install_cron.sh >/dev/null 2>&1
```
If you put a file in `/var/spool/cron/` instead, it must be mode 0600, or cronie refuses it ("BAD FILE MODE").

## Pushing the folder
From the admin box, loop over a host list:
```
while read -r h; do
  rsync -a --delete --chmod=D755,F755 --exclude 'secrets/' /opt/scripts/ "root@$h:/opt/scripts/" \
    || echo "push to $h failed"
done < /opt/scripts/hosts.txt
```
- Keep anything containing secrets **out** of the pushed folder.
- `--chmod` stops loose file modes from being copied everywhere.
- Fix ownership at the source: `chown -R root:root /opt/scripts`.

## Why cron0hourly?
The installer above leaves stock files alone. If you use a "replace everything" approach instead, remember that the RHEL `cronie` package's `/etc/cron.d/0hourly` is what runs `/etc/cron.hourly`. Put an equivalent back:
```
01 * * * * root run-parts /etc/cron.hourly
```

## Better long-term
Once you have more than a few servers, use Ansible (the `cron` and `copy` modules) or a git pull on each host.
