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.1.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.1.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 live systemd unit directories, 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
- For non-root use: a running user session (
systemctl --usermust work) - For root use: access to the system manager (
systemctlwithout--user)
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