{"id":18151300,"url":"https://github.com/msantos/runcron","last_synced_at":"2025-04-28T17:50:03.670Z","repository":{"id":136621504,"uuid":"203606713","full_name":"msantos/runcron","owner":"msantos","description":"simple, safe, container-friendly cron alternative","archived":false,"fork":false,"pushed_at":"2025-02-03T12:30:32.000Z","size":105,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-30T11:41:40.845Z","etag":null,"topics":["capsicum","cron","daemontools","exec","fork","pledge","prctl","procctl","seccomp","stdio"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/msantos.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-08-21T14:56:28.000Z","updated_at":"2025-02-03T12:30:36.000Z","dependencies_parsed_at":"2024-12-28T14:19:20.892Z","dependency_job_id":"0c980d23-a64f-4089-972a-082e86b09a5b","html_url":"https://github.com/msantos/runcron","commit_stats":{"total_commits":117,"total_committers":1,"mean_commits":117.0,"dds":0.0,"last_synced_commit":"7651d0805996d7102ffa3a79b991d07dc1d7fb8e"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msantos%2Fruncron","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msantos%2Fruncron/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msantos%2Fruncron/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msantos%2Fruncron/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/msantos","download_url":"https://codeload.github.com/msantos/runcron/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251360421,"owners_count":21577268,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["capsicum","cron","daemontools","exec","fork","pledge","prctl","procctl","seccomp","stdio"],"created_at":"2024-11-02T01:07:17.398Z","updated_at":"2025-04-28T17:50:03.662Z","avatar_url":"https://github.com/msantos.png","language":"C","readme":"runcron - simple, safe, container-friendly cron alternative\n\n# SYNOPSIS\n\nruncron [*options*] *crontab expression* *command* *arg* *...*\n\n# DESCRIPTION\n\n`runcron` is a minimal cron running as part of a process supervision\ntree for automated environments. runcron is intended to be simple,\nsafe and container-friendly.\n\n`runcron` runs under a supervisor like\n[daemontools](https://cr.yp.to/daemontools.html) and exits after task\ncompletion. The supervisor restarts runcron, taking any action based on\nthe task exit status:\n\n```\nsvscan,17276,17276 service/\n  |-supervise,17277,17276 date17\n  |   `-runcron,17308,17276 */17 * * * * * sh -c echo 17: $(date)\n  |-supervise,17279,17276 date33\n  |   `-runcron,17303,17276 */33 * * * * * sh -c echo 33: $(date)\n  `-supervise,17280,17276 sleep\n      `-runcron,17282,17276 @reboot sleep inf\n          `-sleep,17288,17288 inf\n```\n\n`runcron` supervises tasks:\n\n* only allows a single instance of a job to run\n\n* job runtime is limited to the next cron interval\n\n* when the task is complete, exits with value set to the task exit status\n\n* periodically retries the job if it exits non-0\n\n* if the tasks succeeds (exits 0), when restarted, sleeps until the next\n  cron interval\n\n* terminates any background subprocesses when the foreground process exits\n\n* attempts to prevent running unkillable (setuid) subprocesses\n\n* standard input is forwarded to the task\n\ncron expressions are parsed using\n[ccronexpr](https://github.com/staticlibs/ccronexpr).\n\nThe standard crontab(5) expressions (and some additional expressions)\nare supported. The seconds field is optional:\n\n```\n\t\t\tfield          allowed values\n\t\t\t-----          --------------\n\t\t\tsecond         0-59 (optional)\n\t\t\tminute         0-59\n\t\t\thour           0-23\n\t\t\tday of month   1-31\n\t\t\tmonth          1-12 (or names, see below)\n\t\t\tday of week    0-7 (0 or 7 is Sun, or use names)\n```\n\ncrontab(5) aliases pseudorandomly assign a run time from the alias\ninterval. To run exactly at the start of the interval, use the \"=\"\nalias variant:\n\n```\n\t\t\tstring         meaning\n\t\t\t------         -------\n\t\t\t@reboot        Run once, at startup (see below).\n\t\t\t@yearly        Run once a year, \"0~59 0~59 0~23 1~28 1~12 *\".\n\t\t\t@annually      (same as @yearly)\n\t\t\t@monthly       Run once a month, \"0~59 0~59 0~23 1~28 * *\".\n\t\t\t@weekly        Run once a week, \"0~59 0~59 0~23 * * 1~7\".\n\t\t\t@daily         Run once a day, \"0~59 0~59 0~23 * * *\".\n\t\t\t@midnight      (same as =daily)\n\t\t\t@hourly        Run once an hour, \"0~59 0~59 * * * *\".\n\n\t\t\t=reboot        (same as @reboot)\n\t\t\t=yearly        Run once a year, \"0 0 1 1 *\".\n\t\t\t=annually      (same as =yearly)\n\t\t\t=monthly       Run once a month, \"0 0 1 * *\".\n\t\t\t=weekly        Run once a week, \"0 0 * * 0\".\n\t\t\t=daily         Run once a day, \"0 0 * * *\".\n\t\t\t=midnight      (same as =daily)\n\t\t\t=hourly        Run once an hour, \"0 * * * *\".\n```\n\n## Handling stdin\n\nStandard input is forwarded to the subprocess:\n\n```\n$ echo test | runcron '0~59 * * * * *' sed 's/e/3/g'\nt3st\n```\n\n## crontab Expressions\n\n### Randomized Intervals\n\n`runcron` supports random values in intervals inspired by\n[OpenBSD crontab](https://man.openbsd.org/crontab.5).\n\nThe `~` character in an interval field will pseudorandomly choose an\noffset:\n\n```\n# run once, from Monday to Friday, between 12am and 8am\n0 0~8 * * 1~5\n```\n\nThe random offset is predictable, using the system hostname as the seed\nby default. Use the `-t` option to change the seed or set it to an empty\nstring (\"\") to use the current time as the seed.\n\n```\n# runs: Tuesday at 7am\nruncron -t \"www1.example.com\" -vvv -p -n '0 0~8 * * 1~5' echo test\n\n# runs: Friday at 5am\nruncron -t \"www2.example.com\" -vvv -p -n '0 0~8 * * 1~5' echo test\n```\n\n## @reboot\n\nThe `@reboot` alias runs the task immediately. The behaviour of subsequent\nattempts to run the task depends on the exit status of the previous run:\n\n* 0: runcron will not run the task and sleep indefinitely\n* non-0: runcron will rerun the task after `--retry-interval` seconds\n  (default: 3600)\n\nSince the runcron state is written to a file (see `-f` option), the\nstate can persist between reboots.\n\n```\numask 077\nmkdir -p /tmp/reboot\nruncron -f /tmp/reboot/runcron.lock ...\n```\n\n# EXAMPLES\n\n```\n    # Attempt to connect to google daily\n    # If the connection fails, the task will be retried hourly.\n    runcron \"43 7 * * *\" nc -z google.com 80\n\n    # Run at 9:03am on the 20th of each month.\n    # After the first run, the job will be terminated before the\n    # next scheduled run.\n    runcron \"3 9 20 * *\" sleep inf\n\n    # schedule a task randomly between nodes from Monday-Sunday\n    # each node will choose the same offset based on the hostname\n    runcron \"11 * * * 1~7\" echo test\n\n    # specify a string to use as a seed\n    runcron -t \"foo.example.com\" \"11 * * * 1~7\" echo test\n\n    # or non-deterministically based on the time\n    #\n    # Since the next run interval will be randomly chosen, manually\n    # set a timeout\n    runcron -t \"\" -T 3600 \"11 * * * 1~7\" echo test\n```\n\n## daemontools run script\n\n```\n    #!/bin/sh\n\n    # Run daily at 8:15am\n    exec runcron \"15 8 * * *\" echo Running job\n```\n\n# OPTIONS\n\n-f, --file\n: lock file path (default: .runcron.lock)\n\n-C, --chdir\n: change working directory before running command\n\n-T, --timeout\n: specify command timeout in seconds (-1 to disable, default: next\ncron interval)\n\n-R, --retry-interval\n: interval to retry failed commands (default: 3600s)\n\n-n, --dryrun\n: do nothing\n\n-p, --print\n: output seconds to next timespec\n\n-s, --signal\n: signal sent on command timeout\n\nThe signal is also sent on job completion to clean up any background\ntasks (use --disable-signal-on-exit to disable).\n\nDefault: 15 (SIGTERM)\n\n-t, --tag\n: Seed used for for generating a pseudorandom offset for cron expressions\nwith random intervals. The offset used in a job is constant between\nruns.\n\nSetting the tag to an empty string (\"\") will cause the offset to be\npseudorandomly chosen based on the current time. The job timeout will\nalso be random.\n\nDefault: hostname (see `RUNCRON_TAG`)\n\n-v, --verbose\n: verbose mode\n\n--timestamp *YY-MM-DD hh-mm-ss|@epoch*\n: provide an initial time\n\n--limit-cpu\n: restrict cpu usage of cron expression parsing (default: 10 seconds)\n\n--limit-as\n: restrict memory (address space) of cron expression parsing (default: 1 Mb)\n\n--allow-setuid-subprocess\n: allow running potentially unkillable subprocesses\n\n--disable-process-restrictions\n: do not fork cron expression processing\n\n--disable-signal-on-exit\n: By default, any background subprocesses are terminated when the\nforeground process is terminated. Use this option to disable signalling\nbackground jobs on exit.\n\n# SIGNALS\n\n## Waiting for Job\n\nWhile the task is waiting to run, signals sent to runcron are ignored\nexcept for:\n\nSIGUSR1/SIGALRM\n: Run the job immediately\n\nSIGUSR2\n: Print the remaining number of seconds to stderr\n\nSIGINT\n: Exit with status 111\n\nSIGTERM\n: Exit with status 111\n\n## Running Job\n\nWhen the task is running, signals (excluding SIGKILL, SIGALRM, SIGUSR1\nand SIGUSR2) received by runcron are forwarded to the task process group.\n\n# ENVIRONMENT VARIABLES\n\nRUNCRON_TAG\n: Sets the default value for the `-t/--tag` option: if unset, the hostname\nis used\n\n## Read-only\n\nruncron sets these values before executing the subprocess:\n\nRUNCRON_EXITSTATUS\n: Task exit status of previous run\n\nRUNCRON_TIMEOUT\n: Number of seconds before task is terminated\n\n# BUILDING\n\n## Quick Install\n\n```\nmake\n\n# to run tests: requires bats(1)\nmake clean all test\n\n# selecting method for restricting cron expression parsing\nRESTRICT_PROCESS=seccomp make\n\n#### using musl\n# sudo apt install musl-dev musl-tools\n\nRESTRICT_PROCESS=rlimit ./musl-make clean all test\n\n## linux seccomp sandbox: requires kernel headers\n\n# clone the kernel headers somewhere\ncd /path/to/dir\ngit clone https://github.com/sabotage-linux/kernel-headers.git\n\n# then compile\nMUSL_INCLUDE=/path/to/dir ./musl-make clean all test\n```\n\n# ALTERNATIVES\n\n* [pseudocron](https://github.com/msantos/pseudocron)\n\n* [snooze](https://github.com/leahneukirchen/snooze)\n\n* [runwhen](http://code.dogmap.org/runwhen/)\n\n* [supercronic](https://github.com/aptible/supercronic)\n\n* [uschedule](https://ohse.de/uwe/uschedule.html)\n\n# SEE ALSO\n\n*crontab*(5)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmsantos%2Fruncron","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmsantos%2Fruncron","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmsantos%2Fruncron/lists"}