From cron
There are two ways to move cron jobs to RunWisp:
- Take over (Linux with systemd): RunWisp reads your crontabs where they
are and replaces the cron service.
crontab -ekeeps working. - Convert by hand (any system):
runwisp import cronturns a crontab into TOML, and you disable the old cron lines yourself.
Either way, you can later promote any job into your
own runwisp.toml.
Take over in one command
Section titled “Take over in one command”sudo runwisp takeoverYou don’t need a runwisp.toml first. takeover finds your crontabs, shows a
plan, and asks once. Nothing is written until you say yes.
What it does
Section titled “What it does”- Writes the config: a
runwisp.tomlthat reads your crontabs withinclude_cron. In an existing config it adds the key and leaves comments and formatting alone. - Installs the service:
/etc/systemd/system/runwisp.service, thensystemctl daemon-reload. - Stops and masks cron: stopped first, so nothing runs in between.
- Starts RunWisp:
systemctl enable --now runwisp.service.
Cron isn’t touched until the config and unit are on disk. If RunWisp fails to start, cron is unmasked (and restarted if it was running), and you see the error.
If a crontab sets MAILTO, the plan warns that cron mail stops. Set up
notifications to hear about failures; see
MAILTO below.
Your crontabs are never rewritten. Running takeover again on a finished
take-over does nothing and exits 0. Flags such as --dry-run and --yes are
in the CLI reference.
The prompt
Section titled “The prompt”Found 12 cron jobs on this box: /etc/crontab, /etc/cron.d
1. Write /etc/runwisp/runwisp.toml reads /etc/crontab, /etc/cron.d live via [daemon] include_cron 2. Install RunWisp as a system service and retire cron.service write /etc/systemd/system/runwisp.service systemctl daemon-reload stop cron.service mask cron.service systemctl enable --now runwisp.service
Resolved settings: Binary: /usr/local/bin/runwisp Config: /etc/runwisp/runwisp.toml Data dir: /var/lib/runwisp Host: 127.0.0.1 Port: 9477
Take over from cron.service? [Y/n]On a root box with cron jobs, running plain runwisp the first time offers the
same take-over. Only a command you run in a terminal ever masks cron;
runwisp daemon, systemd, and Docker never do.
Held jobs
Section titled “Held jobs”While cron is still running, RunWisp holds the jobs it reads from cron’s
files: they are listed with their schedule, but RunWisp doesn’t start them, so
nothing runs twice. At boot, a HELD: banner names them.
- Held tasks show
⏸inrunwisp statusand the TUI, aheldbadge in the Web UI, andheldBy: "cron"in the API. runwisp run <name>still runs a held job by hand, so you can try it under RunWisp first.sudo runwisp takeoverends the hold. If you retire cron some other way, stop it and disable it at boot (for examplesudo systemctl disable --now cron). RunWisp notices within a minute and starts running the jobs, no reload needed. A stopped cron that is still enabled keeps the jobs held, because it starts again on the next boot.
You never set a hold yourself. RunWisp checks for a running cron daemon on every load.
Undoing it
Section titled “Undoing it”runwisp service uninstallThis unmasks and restarts cron, but only if this RunWisp’s unit recorded the
take-over (a # runwisp-masked-cron: line in the unit). If something starts
cron again later, runwisp service status reports it and RunWisp holds the
jobs again within a minute. sudo runwisp takeover fixes the cron side.
sudo runwisp service install alone never retires cron. If cron still runs
jobs, it tells you to use takeover.
Convert by hand
Section titled “Convert by hand”runwisp import cron converts a crontab to TOML and prints it:
crontab -l | runwisp import cron # your own crontabrunwisp import cron /etc/crontab # a specific fileGiven this crontab:
SHELL=/bin/bashPATH=/usr/local/bin:/usr/bin
# Nightly database backup30 2 * * * /usr/local/bin/backup.sh --fullyou get:
[tasks.backup]description = "Nightly database backup"cron = "30 2 * * *"shell = "/bin/bash"env_base = "clean"working_dir = "~"catch_up = 0 # cron never re-fires a missed tickon_overlap = "queue" # cron would overlap instead of queueingrun = "/usr/local/bin/backup.sh --full"
[tasks.backup.env]PATH = "/usr/local/bin:/usr/bin"The extra keys keep cron’s behaviour; see How cron maps to RunWisp.
A summary goes to stderr, with one row per job:
| Mark | Meaning |
|---|---|
✓ |
Mapped cleanly. |
~ |
Imported, but something changed. The row says what. |
! |
Needs a fix from you before the config loads. |
- |
Skipped, because your runwisp.toml already defines it. |
Notes about the whole file (such as MAILTO) are listed under Notes:.
Anything that can’t be mapped gets a # TODO in the TOML. When it looks
right, save and check it:
crontab -l | runwisp import cron -o runwisp.tomlrunwisp validateIf you already have a runwisp.toml, use --write instead of -o; see
Staging and promoting. All flags are in the
CLI reference.
System crontabs and the user column
Section titled “System crontabs and the user column”/etc/crontab and files in /etc/cron.d have a user column between the
schedule and the command. The importer detects it from the path (or the
# ... user command header) and sets the task’s
user. For piped input, force it with
--system or --system=false.
Retire the crontab
Section titled “Retire the crontab”An import copies your jobs and never touches the crontab. If cron keeps running them, every job runs twice. Before you start RunWisp, remove the imported jobs from cron:
crontab -l > ~/crontab.backup # keep a copycrontab -e # comment out the imported linesFor a file in /etc/cron.d, move it out of the directory:
sudo mv /etc/cron.d/myjobs /root/myjobs.crontab.backupYou can move jobs one at a time. Just never leave a job enabled in both cron and RunWisp.
Staging and promoting
Section titled “Staging and promoting”import --write adds imported jobs next to a config you already have, without
editing your own tasks. It works the same for import supervisord and
import systemd.
crontab -l | runwisp import cron --write --dry-run # show what would changecrontab -l | runwisp import cron --write # do itIt uses two files:
runwisp.toml # yours: hand-written, keep it in gitrunwisp.d/imported.toml # written by the importer- Your
runwisp.tomlis created, or itsincludeis set to loadrunwisp.d/*.toml. If you already have your ownincludelist,--writedoesn’t change it; it prints the glob to add and writes nothing. - The importer only ever writes
runwisp.d/imported.toml, so you can run it again safely. - A job your config already defines with the same command is skipped (
-). A job with the same name but a different command is renamed (backupbecomesbackup-2). - Both files are written together. If anything fails, both are rolled back.
- If the result wouldn’t load (any
!row), nothing is written and the command exits non-zero. Fix the line and run it again, or use-oto get a file you can edit by hand.
Imported tasks show "source": "staged" in runwisp list --json and
runwisp status --json. They run like any other task.
Promote a job
Section titled “Promote a job”promote moves a job into your own runwisp.toml:
runwisp promote backup # one jobrunwisp promote --all # every staged and crontab-read job- From staging: the block moves from
runwisp.d/imported.tomlinto yourrunwisp.tomlas it is, comments and# TODOlines included. When the last job is promoted, the empty staging file is removed. - From a crontab read by
include_cron: the job is copied intorunwisp.toml, and its line in the crontab is commented out with a note saying where it went. That stops cron from running it if cron is still active. If the line changed since the config was loaded,promoterefuses and writes nothing; try again.
After promoting, the job is yours: it has no source, and a later import
doesn’t touch it.
Run runwisp reload afterwards, or pass --reload. If the job was already
running under RunWisp (staged, or cron already retired), the reload reports no
task changes. If it was held because cron was still running, the reload reports
it as changed, and RunWisp starts scheduling it.
How cron maps to RunWisp
Section titled “How cron maps to RunWisp”These rules apply to runwisp import cron and to jobs read live through
include_cron.
| crontab | runwisp.toml | Notes |
|---|---|---|
30 2 * * * cmd |
cron = "30 2 * * *" + run |
Copied as is. |
@daily, @hourly, @weekly |
cron = "@daily" … |
Same names. |
@midnight, @annually |
@daily, @yearly |
Renamed to RunWisp’s spelling. |
@reboot cmd |
run_on_start = "boot" |
Once per machine boot. |
| user column (system crontab) | user |
Detected automatically. |
SHELL=/bin/bash |
shell |
Must be an absolute path. |
PATH=…, TZ=…, other vars |
env |
TZ is not a schedule zone in cron. |
CRON_TZ=… |
timezone |
Only env on Debian/Ubuntu. An unknown zone gets a # TODO. |
# comment above a job |
description |
|
\% in a command |
a literal % |
|
cmd % input |
run = "cmd" + a # TODO |
See below. |
MAILTO=… |
a sendmail notifier, printed for you to add |
See below. |
Crontab-wide settings (SHELL, CRON_TZ, variables) are copied onto each
task from that file. An import never changes your [defaults] or [daemon].
Environment and working directory
Section titled “Environment and working directory”Every imported task gets env_base = "clean"
and working_dir = "~", because your jobs
were written for cron:
- Cron gives a job an almost empty environment. Without
env_base = "clean", the job would get the daemon’s environment. Your crontab’sPATH=is still added on top. - Cron runs a job in the user’s home directory. Without
working_dir = "~", the job would run in the directory the daemon started in, and relative paths would point somewhere else.~is the home of the user the task runs as.
Catch-up and overlap
Section titled “Catch-up and overlap”Both are written out, so you can delete the line to get RunWisp’s behaviour:
catch_up = 0: cron never runs a job it missed while it was down. RunWisp normally catches up. The missed run is still recorded.on_overlap = "queue": when a job is still running at its next time, cron starts a second copy. RunWisp waits for the first to finish instead. If your job must run in parallel, setmax_concurrent.
For jobs read live through include_cron, you can’t edit these lines;
promote the job first. RunWisp’s cron syntax also accepts a seconds field and
@every 30s; see cron.
What needs a human
Section titled “What needs a human”MAILTO. The summary prints asendmailnotifier for the address, to add to yourrunwisp.toml. It uses the same local mail setup cron used. Then add it to each task’snotify. Cron mailed any output; RunWisp mails on failure. Without a local MTA, usesmtpinstead.@reboot.run_on_start = "boot"runs once per boot, but only once the daemon is up. If the daemon starts late, or only after the boot, the job runs then.- A
%that feeds input. In cron, text after an unescaped%is sent to the job on stdin. RunWisp has no stdin setting, so the command is cut at the%and the input is quoted in a# TODO. Put it back into the command, for example withprintf '…' | cmd. - A system crontab line without a user. Cron skips it, and so does RunWisp.
The sixth field must name a user that exists on this machine, so
* * * * * echo "tick"is skipped (there is no userecho). Add the user and import again. A crontab from another machine is checked by its shape only. - Task names. Names come from the command (
/usr/local/bin/backup.shbecomesbackup). Rename any that aren’t clear.
Learn more
Section titled “Learn more”include_cron: which files are read, and which jobs are skipped- Scheduling
- CLI reference