commit d449f2338cdeae504d01e14c8a91e2318769fcca Author: zlei9 Date: Sun Mar 29 09:43:23 2026 +0800 Initial commit with translated description diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..f61c348 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,422 @@ +--- +name: cron-scheduling +description: "使用cron和systemd定时器安排和管理重复任务。在设置cron作业、编写systemd定时器单元、处理时区感知调度、监控失败作业、实现重试模式或调试计划任务未运行的原因时使用。" +metadata: {"clawdbot":{"emoji":"⏰","requires":{"anyBins":["crontab","systemctl","at"]},"os":["linux","darwin"]}} +--- + +# Cron & Scheduling + +Schedule and manage recurring tasks. Covers cron syntax, crontab management, systemd timers, one-off scheduling, timezone handling, monitoring, and common failure patterns. + +## When to Use + +- Running scripts on a schedule (backups, reports, cleanup) +- Setting up systemd timers (modern cron alternative) +- Debugging why a scheduled job didn't run +- Handling timezones in scheduled tasks +- Monitoring and alerting on job failures +- Running one-off delayed commands + +## Cron Syntax + +### The five fields + +``` +┌───────── minute (0-59) +│ ┌─────── hour (0-23) +│ │ ┌───── day of month (1-31) +│ │ │ ┌─── month (1-12 or JAN-DEC) +│ │ │ │ ┌─ day of week (0-7, 0 and 7 = Sunday, or SUN-SAT) +│ │ │ │ │ +* * * * * command +``` + +### Common schedules + +```bash +# Every minute +* * * * * /path/to/script.sh + +# Every 5 minutes +*/5 * * * * /path/to/script.sh + +# Every hour at :00 +0 * * * * /path/to/script.sh + +# Every day at 2:30 AM +30 2 * * * /path/to/script.sh + +# Every Monday at 9:00 AM +0 9 * * 1 /path/to/script.sh + +# Every weekday at 8:00 AM +0 8 * * 1-5 /path/to/script.sh + +# First day of every month at midnight +0 0 1 * * /path/to/script.sh + +# Every 15 minutes during business hours (Mon-Fri 9-17) +*/15 9-17 * * 1-5 /path/to/script.sh + +# Twice a day (9 AM and 5 PM) +0 9,17 * * * /path/to/script.sh + +# Every quarter (Jan, Apr, Jul, Oct) on the 1st at midnight +0 0 1 1,4,7,10 * /path/to/script.sh + +# Every Sunday at 3 AM +0 3 * * 0 /path/to/script.sh +``` + +### Special strings (shorthand) + +```bash +@reboot /path/to/script.sh # Run once at startup +@yearly /path/to/script.sh # 0 0 1 1 * +@monthly /path/to/script.sh # 0 0 1 * * +@weekly /path/to/script.sh # 0 0 * * 0 +@daily /path/to/script.sh # 0 0 * * * +@hourly /path/to/script.sh # 0 * * * * +``` + +## Crontab Management + +```bash +# Edit current user's crontab +crontab -e + +# List current crontab +crontab -l + +# Edit another user's crontab (root) +sudo crontab -u www-data -e + +# Remove all cron jobs (be careful!) +crontab -r + +# Install crontab from file +crontab mycrontab.txt + +# Backup crontab +crontab -l > crontab-backup-$(date +%Y%m%d).txt +``` + +### Crontab best practices + +```bash +# Set PATH explicitly (cron has minimal PATH) +PATH=/usr/local/bin:/usr/bin:/bin + +# Set MAILTO for error notifications +MAILTO=admin@example.com + +# Set shell explicitly +SHELL=/bin/bash + +# Full crontab example +PATH=/usr/local/bin:/usr/bin:/bin +MAILTO=admin@example.com +SHELL=/bin/bash + +# Backups +0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1 + +# Cleanup old logs +0 3 * * 0 find /var/log/myapp -name "*.log" -mtime +30 -delete + +# Health check +*/5 * * * * /opt/scripts/healthcheck.sh || /opt/scripts/alert.sh "Health check failed" +``` + +## Systemd Timers + +### Create a timer (modern cron replacement) + +```ini +# /etc/systemd/system/backup.service +[Unit] +Description=Daily backup + +[Service] +Type=oneshot +ExecStart=/opt/scripts/backup.sh +User=backup +StandardOutput=journal +StandardError=journal +``` + +```ini +# /etc/systemd/system/backup.timer +[Unit] +Description=Run backup daily at 2 AM + +[Timer] +OnCalendar=*-*-* 02:00:00 +Persistent=true +RandomizedDelaySec=300 + +[Install] +WantedBy=timers.target +``` + +```bash +# Enable and start the timer +sudo systemctl daemon-reload +sudo systemctl enable --now backup.timer + +# Check timer status +systemctl list-timers +systemctl list-timers --all + +# Check last run +systemctl status backup.service +journalctl -u backup.service --since today + +# Run manually (for testing) +sudo systemctl start backup.service + +# Disable timer +sudo systemctl disable --now backup.timer +``` + +### OnCalendar syntax + +```ini +# Systemd calendar expressions + +# Daily at midnight +OnCalendar=daily +# or: OnCalendar=*-*-* 00:00:00 + +# Every Monday at 9 AM +OnCalendar=Mon *-*-* 09:00:00 + +# Every 15 minutes +OnCalendar=*:0/15 + +# Weekdays at 8 AM +OnCalendar=Mon..Fri *-*-* 08:00:00 + +# First of every month +OnCalendar=*-*-01 00:00:00 + +# Every 6 hours +OnCalendar=0/6:00:00 + +# Specific dates +OnCalendar=2026-02-03 12:00:00 + +# Test calendar expressions +systemd-analyze calendar "Mon *-*-* 09:00:00" +systemd-analyze calendar "*:0/15" +systemd-analyze calendar --iterations=5 "Mon..Fri *-*-* 08:00:00" +``` + +### Advantages over cron + +``` +Systemd timers vs cron: ++ Logs in journald (journalctl -u service-name) ++ Persistent: catches up on missed runs after reboot ++ RandomizedDelaySec: prevents thundering herd ++ Dependencies: can depend on network, mounts, etc. ++ Resource limits: CPUQuota, MemoryMax, etc. ++ No lost-email problem (MAILTO often misconfigured) +- More files to create (service + timer) +- More verbose configuration +``` + +## One-Off Scheduling + +### at (run once at a specific time) + +```bash +# Schedule a command +echo "/opt/scripts/deploy.sh" | at 2:00 AM tomorrow +echo "reboot" | at now + 30 minutes +echo "/opt/scripts/report.sh" | at 5:00 PM Friday + +# Interactive (type commands, Ctrl+D to finish) +at 10:00 AM +> /opt/scripts/task.sh +> echo "Done" | mail -s "Task complete" admin@example.com +> + +# List pending jobs +atq + +# View job details +at -c + +# Remove a job +atrm +``` + +### sleep-based (simplest) + +```bash +# Run something after a delay +(sleep 3600 && /opt/scripts/task.sh) & + +# With nohup (survives logout) +nohup bash -c "sleep 7200 && /opt/scripts/task.sh" & +``` + +## Timezone Handling + +```bash +# Cron runs in the system timezone by default +# Check system timezone +timedatectl +date +%Z + +# Set timezone for a specific cron job +# Method 1: TZ variable in crontab +TZ=America/New_York +0 9 * * * /opt/scripts/report.sh + +# Method 2: In the script itself +#!/bin/bash +export TZ=UTC +# All date operations now use UTC + +# Method 3: Wrapper +TZ=Europe/London date '+%Y-%m-%d %H:%M:%S' + +# List available timezones +timedatectl list-timezones +timedatectl list-timezones | grep America +``` + +### DST pitfalls + +``` +Problem: A job scheduled for 2:30 AM may run twice or not at all +during DST transitions. + +"Spring forward": 2:30 AM doesn't exist (clock jumps 2:00 → 3:00) +"Fall back": 2:30 AM happens twice + +Mitigation: +1. Schedule critical jobs outside 1:00-3:00 AM +2. Use UTC for the schedule: TZ=UTC in crontab +3. Make jobs idempotent (safe to run twice) +4. Systemd timers handle DST correctly +``` + +## Monitoring and Debugging + +### Why didn't my cron job run? + +```bash +# 1. Check cron daemon is running +systemctl status cron # Debian/Ubuntu +systemctl status crond # CentOS/RHEL + +# 2. Check cron logs +grep CRON /var/log/syslog # Debian/Ubuntu +grep CRON /var/log/cron # CentOS/RHEL +journalctl -u cron --since today # systemd + +# 3. Check crontab actually exists +crontab -l + +# 4. Test the command manually (with cron's environment) +env -i HOME=$HOME SHELL=/bin/sh PATH=/usr/bin:/bin /opt/scripts/backup.sh +# If it fails here but works normally → PATH or env issue + +# 5. Check permissions +ls -la /opt/scripts/backup.sh # Must be executable +ls -la /var/spool/cron/ # Crontab file permissions + +# 6. Check for syntax errors in crontab +# cron silently ignores lines with errors + +# 7. Check if output is being discarded +# By default, cron emails output. If no MTA, output is lost. +# Always redirect: >> /var/log/myjob.log 2>&1 +``` + +### Job wrapper with logging and alerting + +```bash +#!/bin/bash +# cron-wrapper.sh — Run a command with logging, timing, and error alerting +# Usage: cron-wrapper.sh [args...] + +set -euo pipefail + +JOB_NAME="${1:?Usage: cron-wrapper.sh [args...]}" +shift +COMMAND=("$@") + +LOG_DIR="/var/log/cron-jobs" +mkdir -p "$LOG_DIR" +LOG_FILE="$LOG_DIR/$JOB_NAME.log" + +log() { echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] $*" >> "$LOG_FILE"; } + +log "START: ${COMMAND[*]}" +START_TIME=$(date +%s) + +if "${COMMAND[@]}" >> "$LOG_FILE" 2>&1; then + ELAPSED=$(( $(date +%s) - START_TIME )) + log "SUCCESS (${ELAPSED}s)" +else + EXIT_CODE=$? + ELAPSED=$(( $(date +%s) - START_TIME )) + log "FAILED with exit code $EXIT_CODE (${ELAPSED}s)" + # Alert (customize as needed) + echo "Cron job '$JOB_NAME' failed with exit $EXIT_CODE" | \ + mail -s "CRON FAIL: $JOB_NAME" admin@example.com 2>/dev/null || true + exit $EXIT_CODE +fi +``` + +```bash +# Use in crontab: +0 2 * * * /opt/scripts/cron-wrapper.sh daily-backup /opt/scripts/backup.sh +*/5 * * * * /opt/scripts/cron-wrapper.sh health-check /opt/scripts/healthcheck.sh +``` + +### Lock to prevent overlap + +```bash +# Prevent concurrent runs (job takes longer than interval) +# Method 1: flock +* * * * * flock -n /tmp/myjob.lock /opt/scripts/slow-job.sh + +# Method 2: In the script +LOCKFILE="/tmp/myjob.lock" +exec 200>"$LOCKFILE" +flock -n 200 || { echo "Already running"; exit 0; } +# ... do work ... +``` + +## Idempotent Job Patterns + +```bash +# Idempotent backup (only creates if newer than last backup) +#!/bin/bash +BACKUP_DIR="/backups/$(date +%Y%m%d)" +[[ -d "$BACKUP_DIR" ]] && { echo "Backup already exists"; exit 0; } +mkdir -p "$BACKUP_DIR" +pg_dump mydb > "$BACKUP_DIR/mydb.sql" + +# Idempotent cleanup (safe to run multiple times) +find /tmp/uploads -mtime +7 -type f -delete 2>/dev/null || true + +# Idempotent sync (rsync only transfers changes) +rsync -az /data/ backup-server:/backups/data/ +``` + +## Tips + +- Always redirect output in cron jobs: `>> /var/log/job.log 2>&1`. Without this, output goes to mail (if configured) or is silently lost. +- Test cron jobs by running them with `env -i` to simulate cron's minimal environment. Most failures are caused by missing `PATH` or environment variables. +- Use `flock` to prevent overlapping runs when a job might take longer than its schedule interval. +- Make all scheduled jobs idempotent. If a job runs twice (DST, manual trigger, crash recovery), it should produce the same result. +- `systemd-analyze calendar` is invaluable for verifying timer schedules before deploying. +- Never schedule critical jobs between 1:00 AM and 3:00 AM if DST applies. Use UTC schedules instead. +- Log the start time, end time, and exit code of every cron job. Without this, debugging failures after the fact is guesswork. +- Prefer systemd timers over cron for production services: you get journald logging, missed-run catchup (`Persistent=true`), and resource limits for free. diff --git a/_meta.json b/_meta.json new file mode 100644 index 0000000..ccebf79 --- /dev/null +++ b/_meta.json @@ -0,0 +1,6 @@ +{ + "ownerId": "kn7f6g2r31qsb1ts8cf5x7rpk180fn9j", + "slug": "cron-scheduling", + "version": "1.0.0", + "publishedAt": 1770158609900 +} \ No newline at end of file