timertab

module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 17, 2026 License: MIT

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; timertab never 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.yaml if TIMERTAB_CONFIG_DIR is 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 .service and one .timer file per imported job
  • output/REPORT.md with 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 --user must 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:

  1. Your editor opens the YAML config.
  2. On save, timertab validates the config against the schema and semantic rules.
  3. Missing job id fields are auto-generated and persisted back to the file.
  4. For each job, a .service and .timer unit is rendered.
  5. Stale units (from removed jobs) are stopped, disabled, and deleted.
  6. New/changed units are written, daemon-reload is 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

License

MIT Β© MichaΕ‚ Wadas

Directories ΒΆ

Path Synopsis
cmd
timertab command
internal
cli

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL