systemdtab 0.1.0

Manage systemd timers and services like crontab
systemdtab-0.1.0 is not a library.

sdtab

日本語

Manage systemd user timers and services like crontab.

# Add a timer (cron syntax)
sdtab add "0 9 * * *" "uv run ./report.py"

# Add a long-running service
sdtab add "@service" "node server.js" --restart on-failure

# List all managed units
sdtab list
NAME    TYPE     SCHEDULE     COMMAND               STATUS
report  timer    0 9 * * *    uv run ./report.py    Tue 2026-03-03 09:00:00 JST
web     service  @service     node server.js        ● active
  Web Server

Features

  • cron syntax — familiar * * * * * schedule format, automatically converted to systemd OnCalendar
  • Long-running services — use @service to create always-on daemons with restart policies
  • Resource limits — set memory, CPU, and I/O constraints per unit
  • Export/Importsdtab export dumps config to TOML, sdtab apply restores it on another machine
  • Failure notifications — Slack webhook alerts when a unit fails (OnFailure= mechanism)
  • Colored status● active (green), ● failed (red), ○ inactive (yellow) at a glance; descriptions shown as gray subtitles; auto-disabled when piped
  • Zero dependencies beyond systemd — no database, no daemon, just unit files

Install

cargo install --git https://github.com/kok1eee/systemdtab

Or build from source:

git clone https://github.com/kok1eee/systemdtab
cd systemdtab
cargo build --release
cp target/release/sdtab ~/.local/bin/

Requirements

  • Linux with systemd (user session only — system-wide units are not supported)
  • Rust 1.70+

Note: sdtab manages user-level units only (systemctl --user). It cannot create or manage system-wide services that require root privileges. If loginctl enable-linger fails, ask your system administrator to enable it for your user.

Quick Start

# Initialize sdtab (enables linger, creates config directory)
sdtab init

# Optional: set up Slack failure notifications
sdtab init --slack-webhook "https://hooks.slack.com/services/T.../B.../xxx"

# Add a daily task at 9:00 AM
sdtab add "0 9 * * *" "./backup.sh" --name backup --memory-max 512M

# Add a service that runs continuously
sdtab add "@service" "node dist/index.js" --name web --restart on-failure --env-file .env

# Check status
sdtab list
sdtab status backup

# View logs
sdtab logs web -f

# Export config to file
sdtab export -o Sdtabfile.toml

# Apply config from file (on another machine)
sdtab apply Sdtabfile.toml --dry-run
sdtab apply Sdtabfile.toml

Commands

Command Description
sdtab init [--slack-webhook URL] [--slack-mention USER_ID] Enable linger, create directories, set up notifications
sdtab add "<schedule>" "<command>" [--dry-run] Add a timer
sdtab add "@service" "<command>" [--dry-run] Add a long-running service
sdtab list [--json] List all managed timers and services (long commands are truncated)
sdtab status <name> Show detailed status with next 5 run times
sdtab edit <name> Edit unit file with $EDITOR
sdtab logs <name> [-f] [-n N] View logs (journalctl)
sdtab restart <name> Restart a service
sdtab enable <name> Enable a timer or service
sdtab disable <name> Disable (keep files)
sdtab remove <name> Stop, disable, and remove unit files
sdtab export [-o <file>] Export config as TOML
sdtab apply <file> [--prune] [--dry-run] Apply config from TOML

sdtab remove stops and disables the unit before deleting files. sdtab apply --prune only removes units with the sdtab- prefix — manually created systemd units are never touched.

sdtab apply updates changed units in-place (overwrite → daemon-reload) without stopping them first. Only units that actually need a restart are restarted: for services, description-only changes skip restart; for timers, service-side changes (command, env, etc.) take effect on the next trigger without restarting the timer.

sdtab init also installs a Claude Code skill file to ~/.claude/commands/sdtab.md, enabling /sdtab commands in any project.

Schedule Syntax

Standard cron expressions and convenient shortcuts:

Expression Meaning
*/5 * * * * Every 5 minutes
0 9 * * * Daily at 9:00
0 9 * * Mon-Fri Weekdays at 9:00
@daily Once a day (midnight)
@hourly Once an hour
@reboot On system boot
@daily/3 Daily at 3:00
@weekly/Mon,Wed Every Monday and Wednesday
@service Long-running service (not a timer)

Add Options

Option Description
--name <name> Unit name (auto-generated from command if omitted)
--workdir <path> Working directory (defaults to current)
--description <text> Description
--env-file <path> Environment file
--restart <policy> always / on-failure / no (services only, default: always)
--memory-max <size> Memory limit (e.g. 512M, 1G)
--cpu-quota <percent> CPU limit (e.g. 50%, 200%)
--io-weight <N> I/O priority: 1-10000 (default: 100)
--timeout-stop <duration> Stop timeout (e.g. 30s)
--exec-start-pre <cmd> Command to run before ExecStart
--exec-stop-post <cmd> Command to run after process stops
--log-level-max <level> Max log level to store (e.g. warning, err)
--random-delay <duration> Random delay for timer firing (e.g. 5m)
--env <KEY=VALUE> Environment variable (repeatable)
--no-notify Disable failure notification for this unit
--dry-run Preview generated unit files without creating them

Failure Notifications

Set up Slack notifications for when any unit fails:

sdtab init --slack-webhook "https://hooks.slack.com/services/T.../B.../xxx"

# With user mention
sdtab init --slack-webhook "https://hooks.slack.com/services/T.../B.../xxx" --slack-mention "U0700J8MN3W"

This creates a template unit sdtab-notify@.service and adds OnFailure=sdtab-notify@%n.service to all subsequently created units. When a timer or service fails, systemd triggers the notification unit, which sends a message to Slack via curl.

To opt out of notifications for a specific unit:

sdtab add "0 9 * * *" "./quiet-task.sh" --no-notify

In Sdtabfile.toml, use no_notify = true:

[timers.quiet-task]
schedule = "0 9 * * *"
command = "./quiet-task.sh"
workdir = "/home/user"
no_notify = true

Export Format

sdtab export produces a TOML file:

[timers.backup]
schedule = "0 3 * * *"
command = "./backup.sh"
workdir = "/home/user/project"
memory_max = "512M"

[services.web]
command = "node dist/index.js"
workdir = "/home/user/app"
description = "Web Server"
restart = "on-failure"
env_file = "/home/user/.env"

Use sdtab apply Sdtabfile.toml to recreate all units from this file. Add --prune to remove sdtab-managed units not in the file.

How It Works

sdtab generates standard systemd unit files under ~/.config/systemd/user/ with a sdtab- prefix. No custom daemon or database — everything is plain systemd.

~/.config/systemd/user/
├── sdtab-backup.service    # [Service] definition
├── sdtab-backup.timer      # [Timer] with OnCalendar
├── sdtab-web.service       # Long-running service
├── sdtab-notify@.service   # Failure notification template (if webhook configured)

Metadata is stored as comments in the service file (# sdtab:type=, # sdtab:cron=, etc.), so sdtab can reconstruct the original configuration without an external database.

Comparison with Alternatives

sdtab crontab systemd-cron fcron jobber
One command to create timer Yes Yes No (uses crontab files) Yes Yes
Long-running services Yes (@service) No No (oneshot only) No No
Resource limits (memory/CPU) --memory-max, --cpu-quota No Manual (edit unit files) No No
Export/import config Yes (TOML) crontab -l (text) No No No
Machine-readable output --json No No No No
Backend systemd native crond systemd (generated) own daemon own daemon
User-level without root Yes Yes System-level Needs root Needs root

Testing

The cron parser, unit file generation, and TOML serialization are covered by 93 unit tests:

cargo test

AI Agent Support

sdtab init installs a Claude Code skill file to ~/.claude/commands/sdtab.md. After that, you can manage systemd timers from any project with natural language:

You> /sdtab run report.py every day at 9am

Claude Code interprets the intent, runs sdtab add "0 9 * * *" "uv run ./report.py" --dry-run for confirmation, then creates the timer.

The skill works globally — not just inside the sdtab repository. Once installed, any Claude Code session can create, list, and manage timers and services through /sdtab.

Also included:

  • CLAUDE.md — project instructions with full command reference (auto-loaded by AI agents)
  • --dry-run — preview generated unit files before committing
  • --json — machine-readable output for programmatic use
sdtab list --json
[
  {"name":"backup","type":"timer","schedule":"0 3 * * *","command":"./backup.sh","status":"Mon 2026-03-02 03:00:00 JST"},
  {"name":"web","type":"service","schedule":"@service","command":"node index.js","status":"active"}
]

License

MIT