Folders and files
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Repository files navigation
NOTE: this README is a brief overview.
Best source of truth is =dron --help=.
#+begin_src python :results drawer replace :exports results
from dron.cli import cli
return cli.help.replace('\b', '')
#+end_src
#+RESULTS:
:results:
dron -- simple frontend for Systemd, inspired by cron.
- *d* stands for 'Systemd'
- *ron* stands for 'cron'
dron is my attempt to overcome things that make working with Systemd tedious
:end:
* What does it do?
=dron= is a small Python CLI for defining scheduled jobs in Python while letting
the operating system do the actual running and scheduling:
- on Linux it manages user-level =systemd= =.service= and =.timer= units
- on macOS it manages user-level =launchd= LaunchAgents
Jobs live in a Python module, defaulting to =drontab.<hostname>=. The module must
define a =jobs()= function that yields =dron.api.job(...)= objects.
#+begin_src python :results drawer replace :exports results
from dron.cli import _drontab_example
return _drontab_example()
#+end_src
#+RESULTS:
:results:
from dron.api import job
# at the moment you're expected to define jobs() function that yields jobs
# in the future I might add more mechanisms
def jobs():
# simple job that doesn't do much
yield job(
'daily',
'/home/user/scripts/run-borg /home/user',
unit_name='borg-backup-home',
)
yield job(
'daily',
'linkchecker https://beepb00p.xyz',
unit_name='linkchecker-beepb00p',
)
# drontab is simply python code!
# so if you're annoyed by having to remember Systemd syntax, you can use a helper function
def every(*, mins: int) -> str:
return f'*:0/{mins}'
# make sure my website is alive, it will send local email on failure
yield job(
every(mins=10),
'ping https://beepb00p.xyz',
unit_name='ping-beepb00p',
)
:end:
Run =dron apply= after editing the module. =dron= imports the jobs, builds the
desired backend units, compares them with currently managed units, and applies
the add/update/delete plan.
* Why?
In short, because I want to benefit from the heavy lifting that Systemd does: timeouts, resource management, restart policies, powerful scheduling specs and logging,
while not having to manually manipulate numerous unit files and restart the daemon all over.
I elaborate on what led me to implement it and motivation [[https://beepb00p.xyz/scheduler.html#what_do_i_want][here]]. Also:
- why not just use [[https://beepb00p.xyz/scheduler.html#cron][cron]]?
- why not just use [[https://beepb00p.xyz/scheduler.html#systemd][systemd]]?
* Setting up
1. install system dependencies if needed; on Linux, =dbus-python= may require
DBus development libraries
2. install =dron=: =pip3 install --user git+https://github.com/karlicoss/dron=
3. install =sendmail= from your package manager if you want local job failure
emails
* Using
#+begin_src python :results output replace :exports results
from click.testing import CliRunner
from dron.cli import cli
print(CliRunner().invoke(cli, ['--help'], prog_name='dron').output)
#+end_src
#+RESULTS:
#+begin_example
Usage: dron [OPTIONS] COMMAND [ARGS]...
dron -- simple frontend for Systemd, inspired by cron.
- *d* stands for 'Systemd'
- *ron* stands for 'cron'
dron is my attempt to overcome things that make working with Systemd tedious
Options:
--marker TEXT Use custom marker instead of default `(MANAGED BY DRON)`.
Useful for developing/testing.
--help Show this message and exit.
Commands:
apply Apply drontab (like 'crontab' with no args)
debug Print some debug info
job Actions on individual jobs
monitor Monitor services/timers managed by dron
print Parse and print drontab
uninstall Remove all managed jobs (will ask for confirmation)
,* Why?
In short, because I want to benefit from the heavy lifting that Systemd does:
timeouts, resource management, restart policies, powerful scheduling specs and
logging, while not having to manually manipulate numerous unit files and
restart the daemon all over.
I elaborate on what led me to implement it and motivation
[[https://beepb00p.xyz/scheduler.html#what_do_i_want][here]]. Also:
- why not just use [[https://beepb00p.xyz/scheduler.html#cron][cron]]?
- why not just use [[https://beepb00p.xyz/scheduler.html#systemd][systemd]]?
#+end_example
Common commands:
- =dron apply --dry-run= imports the drontab module, verifies and generates
backend units, and shows the add/update/delete plan without applying it
- =dron apply= applies that plan
- =dron print= shows parsed jobs, with =--pretty= for a table
- =dron monitor= opens the Textual monitor; =dron monitor --once= prints a
one-shot table
- =dron job past <unit>= shows previous runs for one unit
- =dron job run <unit>= runs one job immediately, ignoring the timer
- =dron uninstall= removes all managed jobs after confirmation
Use =--module= with =apply= and =print= to point at a different drontab module.
* Job syntax
The idea is that it's a simple Python DSL that lets you define jobs with minimal
friction.
#+begin_src python :eval no
job(when, command, *, unit_name=None, on_failure=(notify.email_local,), **kwargs)
#+end_src
=when= is a backend schedule string such as =daily= or =*:0/10=. Passing =None=
creates a service without a timer, so it can still be run manually with
=dron job run=. =command= can be a shell string or a sequence of command parts.
Any extra keyword arguments are passed to the backend unit generator, which is
useful when you need systemd-specific service properties.
=load_profile=True= passes =--load-profile= to the macOS launchd wrapper, which sources =~/.profile= in noninteractive =/bin/bash= when launching jobs and failure notifications.
The job and its failure notification commands inherit the exported variables, including Python's startup settings.
This runs on every invocation and does not depend on the launchd session environment being initialized first.
This is opt-in and only supported by the launchd backend.
To enable it by default in a shared macOS drontab module:
#+begin_src python :eval no
from functools import partial
from dron.api import job as dron_job
job = partial(dron_job, load_profile=True)
#+end_src
* Caveats
- =launchd= support intentionally has a smaller schedule surface than =systemd=
- older =systemd= versions only accepted absolute paths for =ExecStart=; generated
units are verified before =apply= installs them
* Potential improvements
- make applying changes more atomic, for example rolling back all changes until
daemon reload succeeds
- more failure report mechanisms