Small bash and Python utilities that print a patch window like w2d6h23 — week 2 of the month, Saturday, 23:00 — so you can drive cron jobs by the first/second/third/fourth Monday or Tuesday of every month. Useful for Patch-Tuesday-style staged rollouts where leg A and leg B of a redundant system have to update on consecutive but predictable days, every month, without the spacing drift that plain 0 9 1-7 * 1 (first Monday) gives you. Companion project: cronie-patchtime — a fork of cronie with this idea built into the daemon as an @patch crontab keyword.
You want Leg A of a redundant system patched on the first Tuesday of the month and Leg B on the first Wednesday. Plain cron makes this awkward — 0 9 1-7 * 2 and 0 9 1-7 * 3 work in some months and misfire in others depending on the day the month starts on, and the spacing between the two firings changes monthly.
Count weeks from a repeatable anchor day in the month — the first Monday, first Tuesday, or any other ISO weekday 1-7. That's how Microsoft defines "Patch Tuesday" (second Tuesday of the month at 10:00 PST), and it's how patchtime works.
patchtime.sh and patchtime.py print the current patch window as w2d6h23: w for week (1-4), d for day (1-7, ISO Monday=1), h for hour (00-23).
The script can count weeks from the first Monday of Tuesday of the month. When counting from Tuesdays, the whole week including the Monday before Tuesday is considered part of the week. This is when you take out a calendar and have a look. The last week of the month will sometimes not be a complete week, the script will then "steal" days from next month as they are not otherwise used in next month's week one.
This means you can use this to sanely schedule w1d1h00 - w4d7h23. Some months will have a Week 5 and it will be printed as expected but as it does not always exist, it's not the best of ideas to use it.
This is great to use for scheduling monthly patching, lets say you want your repo to sync and publish to your test-environment at w1d1h00. You might then want your test-environment to patch leg A of your redundant solution at w1d1h10 and leg B at w1d2h10. Giving you a day to notice any issues in between.
Lets then say you want to publish the updated repo to prod, at w3d1h00, a good week later running and evaluating your patched test-environment. You could the patch prod leg A at w3d1h20 and B at w3d1h21.
You now probably understand these scripts are written with cron in mind.
This idea came up when running numerous systems with a plethora of customers, where each customer could define what patchwindow their servers should use via an interface. The Automation Management Tool of your choice would be asked to read members of the patchwindow every hour and patch via a simple cron, check out the scheaduled examples below. When running large systems you often have monitoring of some sort, and patching with reboot is always an issue. So you want to notify the monitoring system beforehand. This little script do not handle delays or preemtive strikes like that but will combine nicely with faketime, https://www.rpmfind.net/linux/rpm2html/search.php?query=libfaketime.
$ date "+%Y-%m-%d %H:%M"
2022-11-26 09:40
$ ./patchtime.sh
w3d6h09
#Faketime is mearly to produce the example
$ faketime "2023-07-31 08:00:00" ./patchtime.sh
w5d1h08
$ faketime "2023-07-31 08:00:00" ./patchtime.sh -t
w1d1h08
$ faketime "2023-08-7 08:00:00" ./patchtime.sh
w1d1h08
$ faketime "2023-08-7 08:00:00" ./patchtime.sh -t
w2d1h08
0 * * * * [ $(/opt/scripts/patchtime.sh -t) == "w1d1h00" ] && /opt/scripts/sync_repos.sh && /opt/scripts/publish_phase1.sh
0 * * * * [ $(/opt/scripts/patchtime.sh -t) == "w1d1h10" ] && /opt/scripts/patch_test_leg-A.sh
0 * * * * [ $(/opt/scripts/patchtime.sh -t) == "w1d2h10" ] && /opt/scripts/patch_test_leg-B.sh
0 * * * * [ $(/opt/scripts/patchtime.sh -t) == "w3d1h00" ] && /opt/scripts/publish_phase2.sh
0 * * * * [ $(/opt/scripts/patchtime.sh -t) == "w3d1h20" ] && /opt/scripts/patch_prod_leg-A.sh
0 * * * * [ $(/opt/scripts/patchtime.sh -t) == "w3d2h21" ] && /opt/scripts/patch_prod_leg-B.sh
Combining with cron and AWX/Tower/Ansible, the inventory in AWX/Ansible is pulled from Netbox where each host has a defined patchwindow.
0 * * * * bash -c '/usr/bin/awx job_templates launch "Notify patch stakeholders" --extra_vars "survey_hosts: patchwindow_$(/usr/bin/faketime -f "+24h" /opt/scripts/patchtime.sh -t)"'
0 * * * * bash -c '/usr/bin/awx job_templates launch "Monitoring Schedule Downtime" --extra_vars "survey_hosts: patchwindow_$(/usr/bin/faketime -f "+2h" /opt/scripts/patchtime.sh -t)"'
0 * * * * bash -c '/usr/bin/awx job_templates launch "Patch OS" --extra_vars "survey_hosts: patchwindow_$(/opt/scripts/patchtime.sh -t)"'
A fork of cronie with a built-in @patch keyword lives at
https://github.com/nashways/cronie-patchtime (branch patchtime).
It teaches cron the same week-of-month math, so you can drop the
shell-gating shown above and write entries like:
@patch w1 d1 h00 /opt/scripts/sync_repos.sh
@patch w1 d1 h10 /opt/scripts/patch_test_leg-A.sh
@patch w3 d1 h00 /opt/scripts/publish_phase2.sh
Syntax: @patch [a<N>] w<list> d<list> [h<list>] [m<list>] command,
where a is the anchor weekday 1..7 (default 1=Monday), and w/d/h/m
accept the same list/range/* syntax as ordinary cron fields. Build
with ./configure --enable-patchtime (or --disable-patchtime for
vanilla cronie). See the crontab(5) man page in the fork for full
docs and examples.
It would be cool to rewrite this is something other than bash and package it in a rpm. Or even extend a version of cron with this stuff. Cron extension done — see cronie-patchtime above.
Yes, please!, Just say hi and I will add you as a collaborator if you want.
Google is supposedly your friend.
GNU GPLv3
Not sure how much energy I have to push this further, if you want to fork or just grab the idea, go ahead.