Skip to main content

Zabbix-cRontroller

Monitor cron jobs in Zabbix with crontab as the single source of truth

Introduction

Keep job schedules and metadata in the user's crontab. The zc execution wrapper records each run's exit status, duration, stdout and stderr in private per-user storage. It forwards output to cron or the entry's existing redirections.

This implementation provides local run evidence. Zabbix integration and direct file, syslog and journal collection are not implemented yet.

Installation & Integration

Use Python 3.14 and uv on Linux with cron installed. From a checkout, install for the current user and create the short-name symlink:

UV_TOOL_BIN_DIR="$HOME/.local/bin" uv tool install --python 3.14 .
ln -s zabbix-crontroller "$HOME/.local/bin/zc"

For a system installation with sudo:

sudo env UV_TOOL_DIR=/opt/zabbix-crontroller/tools \
  UV_PYTHON_INSTALL_DIR=/opt/zabbix-crontroller/python \
  UV_TOOL_BIN_DIR=/usr/local/bin uv tool install --python 3.14 .
sudo ln -s zabbix-crontroller /usr/local/bin/zc

These are local installations, not release builds. Release packages are produced only by tagged GitHub workflows. The executable is zabbix-crontroller; zc must be a symlink to it. Use its absolute path in cron, or explicitly set cron's PATH to include its directory. A non-root example is PATH=/home/batch/.local/bin:/usr/bin:/bin; cron does not expand $HOME in PATH. For system installations, the uv tool environment and interpreter must be readable and executable by the job owners; the shared /opt locations avoid placing them inside root's private home. Validate from each job owner's account.

Validate a candidate file, or the current user's installed crontab:

zc validate example.crontab
zc validate

Validation checks the complete syntax and then wrapper and shell availability as the invoking user. It never executes a job. Success returns 0; invalid input, acquisition failures and unavailable executables return 1. Missing crontabs are failures, not successful empty configurations.

Syntax

Keep schedules and commands in your normal user crontab. The Syntax Specification defines the frozen 1.0 notation supported by the parser.

  1. Declare # zc:version = "1.0" exactly once, before # zc:start.
  2. Enclose all monitored jobs in one section, from # zc:start to # zc:end.
  3. Start each job declaration with job: a stable, unique identifier using lowercase ASCII letters, digits, underscores or hyphens, beginning with a letter.
  4. Add a required name and, if useful, an optional description. These can contain Unicode; the description may appear before or after the name.
  5. Put exactly one cron entry immediately after its metadata. It completes the job declaration. # zc:end closes the entire section, not an individual job.
  6. Begin the command with zc run JOB -- 'COMMAND', repeating the exact job identifier. Use one literally quoted shell command. Both executable names and absolute paths ending in zc or zabbix-crontroller are accepted.

For example, a description is not required:

# zc:version = "1.0"
PATH=/usr/local/bin:/usr/bin:/bin
# zc:start
# zc:job = "backup-postgres"
# zc:name = "Orders database backup"
30 1 * * * zc run backup-postgres -- '/opt/company/maintenance/bin/backup-postgres --database orders'
# zc:end

Write one property per line using a quoted TOML string. Keep each metadata header and its cron entry together. Blank lines, ordinary comments and environment assignments may appear between completed jobs. Delimiters may have trailing # comments, but take no value. Changing a name or command does not require changing the job identifier.

The wrapper executes the quoted command using cron's SHELL, or /bin/sh when unset. Put pipelines, command chains and substitutions inside single quotes so they execute within the monitored shell. Keep existing output redirections outside the quotes if they should receive forwarded output while capture stays separate. Use '"'"' to include an apostrophe in a single-quoted command. Cron still requires escaping literal percentages as \%, even inside quotes. See runtime behaviour and evidence for limits and failure handling.

Every cron entry inside the section needs its own metadata. An additional entry without metadata is an error, including after the final job. Unannotated entries are allowed before and after the section. Job metadata outside the section is an error. An empty section is valid, but both delimiters remain required.

Commenting out only a job's cron entry is an error: its metadata will not attach to a later entry. To stop discovering a job while keeping it running, remove its metadata header and move its cron entry outside the monitored section. Preserve the relevant environment settings when moving it; section boundaries do not reset the cron environment. Removing only the header inside the section is an error. Remove the entire declaration to remove both monitoring and execution. Malformed configurations prevent publication of the discovery snapshot.

Syntax example

The example crontab contains five monitored jobs and a separate unmonitored entry:

# Production batch jobs — svc_batch@lon-batch-01
# Owner: Platform Operations
# Host timezone: Europe/London. Change requests must reference an approved ticket.

# zc:version = "1.0"

SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=platform-operations@example.com
HOME=/var/lib/batch

# zc:start

# zc:job = "supplier-sync"
# zc:name = "Supplier stock synchronisation"
# zc:description = "Collect supplier stock feeds during business hours, Monday–Friday."
12 7-19 * * 1-5 zc run supplier-sync -- '/usr/bin/flock -n /var/lib/batch/locks/supplier-sync.lock /opt/company/integrations/bin/supplier-sync --config /etc/company/supplier-sync.toml' >> /var/log/batch/supplier-sync.log 2>&1

# zc:job = "backup-postgres"
# zc:name = "Orders database backup"
# zc:description = "Nightly database backup; credentials supplied via .pgpass."
30 1 * * * zc run backup-postgres -- '/opt/company/maintenance/bin/backup-postgres --database orders --retention-days 14' >> /var/log/batch/backup-postgres.log 2>&1

# zc:job = "reconcile-payments"
# zc:name = "Reconcile payments"
# zc:description = "Generate the previous business day's reconciliation report before Finance arrives."
15 6 * * 1-5 zc run reconcile-payments -- '/opt/company/finance/bin/reconcile-payments --period previous-business-day' >> /var/log/batch/reconcile-payments.log 2>&1

# zc:job = "staging-cleanup"
# zc:name = "Staging cleanup"
# zc:description = "Remove expired integration staging files during the Sunday maintenance window."
45 3 * * 0 zc run staging-cleanup -- '/usr/bin/find /var/lib/batch/staging -xdev -type f -mtime +30 -delete' >> /var/log/batch/staging-cleanup.log 2>&1

# zc:job = "sap-export"
# zc:name = "SAP export"
# zc:description = "Forward pending warehouse transactions to SAP; prevent overlapping runs."
*/5 * * * * zc run sap-export -- '/usr/bin/flock -n /var/lib/batch/locks/sap-export.lock /opt/company/integrations/bin/sap-export --env production' >> /var/log/batch/sap-export.log 2>&1

# zc:end

# This separate cron entry runs outside monitoring.
45 3 * * 0 /usr/bin/find /var/lib/batch/staging -xdev -type f -mtime +30 -delete >> /var/log/batch/staging-cleanup.log 2>&1

Library usage

Import the parser independently of the application. The caller supplies the crontab text; the parser does not run crontab -l, execute jobs, read files or contact Zabbix.

from pathlib import Path

from zabbix_crontroller.parser import parse_crontab

text = Path("example.crontab").read_text(encoding="utf-8")
crontab = parse_crontab(text)

for job in crontab.jobs:
    print(job.job_id, job.name, job.schedule, job.command)

parse_crontab(text: str) -> Crontab returns the declared version and a tuple of CronJob records in source order. Jobs contain the identifier, name, optional description, schedule, complete command, wrapper_executable, environment snapshot and source line numbers. The wrapper field contains its decoded name or absolute path; parsing does not check whether that executable is installed. Records and their environment mappings are read-only. Missing or blank descriptions become None. An explicitly empty monitored section returns an empty jobs tuple.

The parser extracts five-field schedules and @ nicknames. Schedule field values, nickname availability, calendar ranges, time zones and daemon-specific extensions are not validated. This is a metadata parser, not a replacement for the daemon's crontab validator. Input is interpreted as a user crontab, with no seconds or username column. Commands retain their internal and trailing whitespace, quoting and cron percent sequences without evaluation. Only the literal wrapper invocation is validated; the inner shell programme remains opaque. Unmonitored commands retain their previous parsing behaviour.

Each environment snapshot contains only preceding explicit assignments, in file order, with outer quotes removed. It does not add process environment variables or cron defaults, perform substitutions, or model daemon-specific overrides.

Invalid input raises ParseError, with ConfigurationError for version errors and CrontabSyntaxError for other parsing errors. Exceptions expose message, line_number, job_id, declaration_line_number and previous_line_number; inapplicable locations are None. Source lines are one-based. The exception text includes the relevant locations. Parsing returns a complete result or raises an error; it never returns partial discovery data. Non-string input raises TypeError.

Contributing

See CONTRIBUTE.md for development setup, tests and contribution guidelines.

Metadata

Release files for zabbix-crontroller 0.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zabbix-crontroller 0.0.1
File Size Uploaded
zabbix_crontroller-0.0.1.tar.gz 33.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for zabbix-crontroller 0.0.1
File Interpreter ABI Platform
zabbix_crontroller-0.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 54.1 kB

Release files / zabbix_crontroller-0.0.1.tar.gz

Download URL zabbix_crontroller-0.0.1.tar.gz
Size 33.5 kB
Tags Source
SHA-256 checksum
How to use checksums
96c111010dc8d772885c77e8b60c8a1a1ff71972a917cea42521f0d3ca3332a4
BLAKE2b-256 checksum
How to use checksums
57a4c3792e95c406d0d9437744b1585596e21e39be5c5d2b7cde6530ef827b26
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.20 {"installer":{"name":"uv","version":"0.12.20","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / zabbix_crontroller-0.0.1-py3-none-any.whl

Download URL zabbix_crontroller-0.0.1-py3-none-any.whl
Size 20.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fcfa60f17e9a455a140c66cb1c051838700abc2cffb4f6e3c55daccaefadba19
BLAKE2b-256 checksum
How to use checksums
1528757ad217d84bdeb1a8380ccc615b87a635f034645b03651ef9f0cb38cf45
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.20 {"installer":{"name":"uv","version":"0.12.20","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.0.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page