sdtab
Manage systemd user timers and services like crontab.
# Add a timer (cron syntax)
# Add a long-running service
# List all managed units
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
@serviceto create always-on daemons with restart policies - Resource limits — set memory, CPU, and I/O constraints per unit
- Export/Import —
sdtab exportdumps config to TOML,sdtab applyrestores 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
Or build from source:
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. Ifloginctl enable-lingerfails, ask your system administrator to enable it for your user.
Quick Start
# Initialize sdtab (enables linger, creates config directory)
# Optional: set up Slack failure notifications
# Add a daily task at 9:00 AM
# Add a service that runs continuously
# Check status
# View logs
# Export config to file
# Apply config from file (on another machine)
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 removestops and disables the unit before deleting files.sdtab apply --pruneonly removes units with thesdtab-prefix — manually created systemd units are never touched.
sdtab applyupdates 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 initalso installs a Claude Code skill file to~/.claude/commands/sdtab.md, enabling/sdtabcommands 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:
# With user mention
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:
In Sdtabfile.toml, use no_notify = true:
[]
= "0 9 * * *"
= "./quiet-task.sh"
= "/home/user"
= true
Export Format
sdtab export produces a TOML file:
[]
= "0 3 * * *"
= "./backup.sh"
= "/home/user/project"
= "512M"
[]
= "node dist/index.js"
= "/home/user/app"
= "Web Server"
= "on-failure"
= "/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:
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
License
MIT