{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/misc/fixed-time-task-scheduling-with-at/",
  "description": "Schedule one-time commands with UNIX at command. Learn job queuing, remote scheduling via HTTP API, and managing atd/atrun daemons.",
  "path": "/misc/fixed-time-task-scheduling-with-at/",
  "publishedAt": "2023-05-14T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "tags": [
    "Shell",
    "Unix",
    "JavaScript",
    "Networking"
  ],
  "textContent": "This weekend, I was working on a fun project that required a fixed-time job scheduler to run\na curl command at a future timestamp. I was aiming to find the simplest solution that\ncould just get the job done. I've also been exploring [Google Bard] recently and wanted to\nsee how it stacks up against other LLM tools like ChatGPT, BingChat, or Anthropic's Claude\nin terms of resolving programming queries.\n\nSo, I asked Bard:\n\n> What's the simplest solution I could get away with to run a shell command at a future\n> datetime?\n\nIt introduced me to the UNIX at command that does exactly what I needed. Cron wouldn't be\na good fit for this particular use case, and I wasn't aware of the existence of at before.\nSo I started probing the model and wanted to document my findings for future reference.\nAlso, the final hacky solution that allowed me to schedule jobs remotely can be found at the\n[end of this post].\n\nThe insipid definition\n\nThe command at in UNIX is used to schedule one-time jobs or commands to be executed at a\nspecific time in the future. Internally, the system maintains a queue that adds a new entry\nwhen a job is scheduled, and once it gets executed, the job is removed from the queue.\n\n> _NOTE:_ _By default, the jobs will be scheduled using the targeted machine's local\n> timezone._\n\nPrerequisites\n\nThe command isn't included in GNU coreutils, so you might have to install it separately on\nyour machine.\n\nDebian-ish\n\nOn a Debian-flavored Linux machine, run:\n\nThen check the status of atd daemon. This daemon executes the scheduled jobs.\n\nIf the service isn't running, then you can start the daemon with this command:\n\nMacOS\n\nOn MacOS, scheduled jobs are carried out by atrun and it's disabled by default. I had to\nfiddle around quite a bit to make it work on my MacBook Pro running MacOS Ventura. First,\nyou'll need to launch the daemon with the following command:\n\nThis will start the atrun daemon. Or enable it for future bootups by modifying\n/System/Library/LaunchDaemons/com.apple.atrun.plist to have:\n\nOn modern MacOS like Ventura, unfortunately, this requires [disabling SIP]. Next, you'll\nneed to provide full disk access to atrun. To do so:\n\n- Open Spotlight and type in _Allow full disk access_.\n- On the left panel, click on _Allow applications to access all user files_.\n\n![macOS System Settings showing Full Disk Access panel for atrun][image_1]\n\n- On the right panel, add /usr/libexec/atrun to the list of allowed apps. Press\n  cmd + shift + g and type in the full path of atrun.\n\n![macOS file picker dialog adding /usr/libexec/atrun to Full Disk Access][image_2]\n\nYou can learn more about [making atrun work on MacOS]. Although I'm using MacOS for\ndevelopment, In my particular case, making at work on MacOS wasn't the first priority\nbecause I deployed the final solution to an Ubuntu container.\n\nA few examples\n\nThe following sections demonstrates some examples of scheduling commands to be executed in a\nfew different scenarios.\n\nSchedule at a specific time\n\nTo schedule a command to be executed at a specific time, use this command syntax:\n\nFor example, to schedule the command ls -lah >> foo.txt to be executed at 3:00 PM local\ntime, you'd use the following command:\n\nPressing <Ctrl-D> tells at that you have finished entering the command, and it should\nschedule the job to run at the specified time. You'll see that at 3.00PM local time, a\nfile named foo.txt containing the output of ls -l will be created.\n\nSchedule after a certain period of time\n\nTo schedule a command to run in a specific amount of time from now, use:\n\nFor example, to schedule ps aux >> foo.txt to run in 2 minutes from now, you'd use the\nfollowing command:\n\nThis will schedule the command to run in two minutes in the current local time.\n\nSchedule a script run\n\nYou can also run a script containing multiple commands at a specific time. To do this,\ncreate a script file that houses the commands you want to run, and then use at to schedule\nthe script to be executed at the desired time.\n\nFor example, suppose you have a script file called script.sh that contains a curl\ncommand which makes an API call and saves the output to a file. You can schedule it as such:\n\nThe script will be executed in a minute from now. You can check the content of foo.json 1\nminute later:\n\nSchedule in a non-interactive manner\n\nWhat if you don't want to create a new script file and also don't want to schedule a command\ninteractively as shown before? You can echo the desired command and pipe it to at like\nthis:\n\nWe can also run multi-line commands in a single go by taking advantage of the heredoc\nformat:\n\nIn either case, 1 minute later, you'll see that a foo.txt file will be created in your\nlocal directory with the following content:\n\nThis command above uses at to schedule the execution of a dig command for the domain\nname rednafi.com. In this case, dig performs a DNS lookup, and the scheduled time is set\nto be 1 minute from now in the current local time. The output of the command is then\nappended to the file foo.txt. The <<EOF syntax is used for input redirection, which\nallows the command to be specified in a heredoc format without requiring you to enter the\ncommand in interactive mode as before.\n\nSchedule with UNIX timestamp\n\nYou can schedule jobs using a UNIX timestamp with the -t flag. The at command requires a\ntimestamp in the format [[[mm]dd]HH]MM[[cc]yy][.ss]]. Here's an example that uses the\ndate command to generate the current datetime, adds a 30-second offset to it, formats it\nto the at's expected format, and schedules a job.\n\nOn Linux, run:\n\nOn MacOS, run:\n\nView and manage scheduled jobs\n\nTo view a list of scheduled jobs, use the following command:\n\nThis will display a list of all the tasks that are currently scheduled.\n\nTo remove a scheduled task, use the following command:\n\nThe job number is the number assigned to the task when it was scheduled. You can find the\njob number by running the atq command. If you need to clear all the pending jobs, use\nthis:\n\nA hacky way to schedule jobs remotely\n\nThis is a hacky and probably dangerous way to do remote job scheduling. However, the beauty\nof side projects is that nobody's here to tell you what to do and it's a fun way to play\nwith hazmats.\n\nI needed a way to quickly prop up a service that'd allow me to schedule webhook API calls at\na fixed point in time in the future. So I exposed a simple NodeJS server that'd allow me to\nschedule an API call with at and execute the command at the desired datetime. Here's the\ncomplete server:\n\nThis endpoint takes in a shell command and just runs it on the server; bad idea, right? But\nthe endpoint is secured behind a Bearer token and I'm the only one who's going to use this.\nSecurity by obscurity!\n\nBefore running the server, you'll need to install express and once you've done it, you can\nstart the server with the following command:\n\nNow, from a different console panel, you can schedule a remote task as follows:\n\nThis will return:\n\nIn my case, I needed to POST a payload at a certain time in the future:\n\nFurther reading\n\n- [at command in Linux]\n\n\n\n\n[google bard]:\n    https://bard.google.com/\n\n[end of this post]:\n    #a-hacky-way-to-schedule-jobs-remotely\n\n[disabling SIP]:\n    https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection\n\n[making atrun work on MacOS]:\n    https://unix.stackexchange.com/a/478840/383934\n\n[at command in Linux]:\n    https://linuxize.com/post/at-command-in-linux/\n\n[image_1]:\n    https://blob.rednafi.com/static/images/fixed_time_task_scheduling_with_at/img_1.png\n\n[image_2]:\n    https://blob.rednafi.com/static/images/fixed_time_task_scheduling_with_at/img_2.png",
  "title": "Fixed-time job scheduling with UNIX 'at' command"
}