README
ΒΆ
timertab
Cron jobs done right β one YAML file, real systemd timers under the hood.
If you've ever wished crontab -e gave you systemd timers instead of 1970s cron, this is that tool. You write a simple YAML config, timertab turns it into proper .service and .timer units. No daemon, no lock-in β just plain systemd.
See what your crontab looks like as systemd timers
No install needed. Pipe your crontab in, get rendered systemd units out:
crontab -l | docker run --network none --rm -i -v "$PWD/output:/output" ghcr.io/ginden/timertab-import
This produces a full review bundle β timertab.yaml, rendered .service and .timer files, and a REPORT.md with migration notes β all without touching your system. Runs sandboxed with --network none.
Why timertab?
π True zero lock-in
Generated units are standard, human-readable systemd files. They work without timertab installed β your timers keep running whether timertab is present or not. When you outgrow YAML and want to manage units directly:
timertab eject <id>
This removes ownership markers and leaves a standalone systemd timer. No proprietary format, no runtime dependency.
πͺ Success and failure hooks
Run a command when a job succeeds or fails β send a notification, dump logs, trigger another script. Hooks are first-class, not an afterthought:
on_success:
command: "echo ok"
on_failure:
command: 'notify-send "Backup failed"'
Hooks receive rich context: TIMERTAB_JOB_ID, TIMERTAB_UNIT, SERVICE_RESULT, EXIT_CODE, EXIT_STATUS.
π‘οΈ Atomic reconcile
If your config is invalid, nothing gets written or pruned. No partial state, no orphaned units. Ever.
π Multiple schedules per job
One job can fire at different times β no need to duplicate entries:
when:
- "0 9 * * *"
- "0 18 * * *"
π¦ Multiple instances per user
Run separate job namespaces (work, personal, project-specific) on the same machine. Each instance manages its own units and never touches the others:
instance_id: work
βοΈ Raw systemd when you need it
Sensible defaults for everything, full systemd control when you want it:
systemd:
service:
Restart: "on-failure"
RestartSec: "30s"
timer:
AccuracySec: "1m"
π Git auto-commit
Every successful edit is automatically committed to a local git repo. Full audit trail of every change, with no extra effort. Disable with --no-commit or in config.
All features
- One YAML file β all your scheduled jobs in one place, version-control friendly.
- Cron syntax you already know β
@hourly,@daily, or standard 5-field cron expressions. - Shell shorthand or explicit argv β use a string for
/bin/sh -lc, or a YAML list for direct execution. - Multiline scripts β these just work in string mode, run multiple commands without
&&abuse. - Per-user isolation β units are scoped to your UID;
timertabnever touches units it didn't create. - JSON Schema β get autocomplete and validation in editors that support it.
- Shell completions β bash, zsh, and fish.
Quick start
Install
go install github.com/ginden/timertab/cmd/timertab@latest
Or from a local clone:
make install
Create your first job
timertab -e
This opens your $EDITOR with the config file. Add a job:
$schema: "https://raw.githubusercontent.com/ginden/timertab/v1.0.0/schema/v1.json"
version: 1
instance_id: work
jobs:
- name: clean temp files
when: "@daily"
run: "find /tmp -user $USER -mtime +7 -delete"
Save and close β timertab validates the config, generates the systemd units, and starts the timer. That's it.
A more complete example
$schema: "https://raw.githubusercontent.com/ginden/timertab/v1.0.0/schema/v1.json"
version: 1
jobs:
- name: NPM cache verify
when: "@hourly"
run: "npm --global cache verify"
env:
NPM_CONFIG_PREFIX: "/home/user/.npm-global"
on_success:
command: "echo ok"
on_failure:
command: 'journalctl -u "$TIMERTAB_UNIT" -n 100 --no-pager'
- name: backup documents
when:
- "0 9 * * *"
- "0 18 * * *"
run:
- /usr/bin/rsync
- -a
- /home/user/Documents/
- /mnt/backup/
cwd: "/home/user"
systemd:
service:
Restart: "on-failure"
RestartSec: "30s"
timer:
AccuracySec: "1m"
on_failure:
command: 'notify-send "Backup failed"'
String run values are shorthand for ["/bin/sh", "-lc", "..."]. Use the list form when you want exact argv execution without an extra shell.
Usage
| Command | What it does |
|---|---|
timertab edit (or timertab -e) |
Edit config, validate, and apply (generate and start timers) |
timertab edit --no-apply (or timertab -e --no-apply) |
Edit and validate only, don't touch systemd |
timertab edit --no-commit |
Apply without creating/updating the git history entry for that edit run |
timertab list / timertab print-config (or timertab -l) |
Print current config |
timertab status / timertab status --json |
Show last run, next trigger, and result for each job |
timertab status <id> |
Show detailed runtime state, generated unit definitions, file locations, and diagnostic commands |
timertab eject <id> |
Stop managing a job β its units stay and keep running |
timertab enable <id> / timertab disable <id> |
Toggle one job on/off without removing it |
timertab logs <id> |
Tail/query journald logs for one job |
timertab diff |
Preview create/modify/delete reconcile operations |
timertab import |
Convert crontab entries into timertab YAML |
timertab render |
Convert crontab input into a review bundle with timertab.yaml, rendered units, and REPORT.md |
timertab validate --config <path> |
Validate a config file without applying |
timertab print-path (or timertab --print-path) |
Show where the config file lives |
Config file location:
--config <path>if provided${TIMERTAB_CONFIG_DIR}/timertab.yamlifTIMERTAB_CONFIG_DIRis set- otherwise
${XDG_CONFIG_HOME:-$HOME/.config}/timertab/timertab.yaml
Import or Review an Existing Crontab
To convert your current crontab into timertab YAML without applying anything:
timertab import --stdout > timertab.yaml
To generate a review bundle with rendered systemd units:
crontab -l | timertab render --stdin --output output
render writes:
output/timertab.yaml- one
.serviceand one.timerfile per imported job output/REPORT.mdwith imported jobs, warnings, and cron-vs-systemd caveats
Unlike edit, render never touches ~/.config/systemd/user, never calls systemctl, and does not require systemd to be installed.
If you want the same review flow without installing timertab locally, use the published container image:
crontab -l | docker run --network none --rm -i -v "$PWD/output:/output" ghcr.io/ginden/timertab-import
Shell Completions
Generate completions with timertab completion <shell>.
bash
timertab completion bash > ~/.local/share/bash-completion/completions/timertab
zsh
timertab completion zsh > ~/.zfunc/_timertab
Then make sure ~/.zfunc is on your fpath.
fish
timertab completion fish > ~/.config/fish/completions/timertab.fish
Requirements
- Linux with systemd β₯ 247
- A running user session (
systemctl --usermust work)
If you need timers to fire while you're logged out:
loginctl enable-linger "$USER"
timertab will automatically detect this and print instructions if it's not set up.
How it works
When you run timertab edit (or timertab -e), here's what happens:
- Your editor opens the YAML config.
- On save,
timertabvalidates the config against the schema and semantic rules. - Missing job
idfields are auto-generated and persisted back to the file. - For each job, a
.serviceand.timerunit is rendered. - Stale units (from removed jobs) are stopped, disabled, and deleted.
- New/changed units are written,
daemon-reloadis called, and timers are started.
If validation fails at step 2, nothing else happens β no partial writes, no orphaned units.
Successful timertab edit apply runs also auto-commit the config file by default. If the config directory is not already inside a git work tree, timertab initializes one first, then stages and commits the config change. Disable that once with timertab edit --no-commit, or persistently in config:
git:
auto_commit: false
Spec and schema
- v1 Specification β spec created for LLMs
- JSON Schema β for editor integration and validation
- CLI Reference β command-by-command behavior and flags
- Technical Details β implementation, release, and maintenance notes
- Caveats and Design Choices β weird edges and opinionated behavior
- Libraries β third-party dependencies
License
MIT Β© MichaΕ Wadas