Get Started

Make your scripts run themselves: nightly backups and free site-down alerts

A script you have to remember to run is a script you'll forget. By the end, your files back themselves up every night, GitHub checks your website every morning while your PC is off, and your phone buzzes the moment either one fails. Task Scheduler, cron, launchd and GitHub Actions, each with a clean off switch.

90 minIntermediateTools: FreeStep 1 free
A starter kit with a small backup script built for scheduling: it logs every run, exits with a clear code when it fails and only ever deletes its own old zips
A Windows Task Scheduler task made from four lines of PowerShell, that catches up after the PC was off, can wake it from sleep, and can run while you're signed out
The same job in cron on Linux and Mac, plus the launchd version Apple recommends
  • A daily check that runs on GitHub's servers while your PC is off, and emails you when something is down
  • Phone alerts through ntfy when anything fails, and the exact commands to pause or remove every schedule
Before you start
  • Done first: the Terminal survival kit. You'll copy commands and move between folders
  • uv, the Python tool used across this site. It brings its own Python, so there's nothing else to install. Check with uv --version; step 1 shows how to get it if that fails
  • Windows 10 or 11, macOS, or Linux. Steps 2 and 3 are Windows, step 4 is Mac and Linux, step 5 is any of them
  • For step 5: a free GitHub ↗ account. Scheduled runs are free in public repositories, and a free account includes 2,000 minutes a month for private ones. If you never add a payment method, GitHub blocks further runs when the minutes run out instead of charging you (GitHub Actions billing ↗)
  • Optional: the free ntfy app on your phone for failure alerts. No account needed. The paid ntfy plans (from $5 a month) are not needed here
  • Optional but sensible: a USB drive or second disk for the backups. A backup on the same disk as the original doesn't survive that disk dying
01/06Get the kit and run the backup by hand0 done
STEP 01 of 06~15 min

Get the kit and run the backup by hand

A scheduler only presses "run" for you. If the command doesn't work when you type it, it won't work at 8pm either, and you won't be there to see the error.

  1. First check uv is there:

    Terminal
    uv --version
  2. If you get "not recognised" or "command not found", install it with the official one-liner from the uv docs ↗, then close the terminal and open a new one:

    Windows
    powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
    Mac / Linux
    curl -LsSf https://astral.sh/uv/install.sh | sh
  3. Download the starter kit (11KB) and unzip it into your home folder. You get a schedule-anything folder with the backup script, the Windows, cron and launchd set-ups, and a github-actions folder for step 5. You can also read each file on its own first: daily_backup.py, register-windows-task.ps1, crontab-example.txt, the launchd file, site_check.py and daily-check.yml.

    Windows
    cd $HOME\Downloads
    Expand-Archive schedule-anything-kit.zip -DestinationPath $HOME
    cd $HOME\schedule-anything
    Mac / Linux
    cd ~/Downloads
    unzip schedule-anything-kit.zip -d ~
    cd ~/schedule-anything
  4. Make a practice folder to back up, with one file in it. Use a practice folder until the whole lesson works, then point the schedule at your real one.

    Windows
    mkdir $HOME\Important
    "Shopping list" | Out-File $HOME\Important\list.txt
    Mac / Linux
    mkdir -p ~/Important
    echo "Shopping list" > ~/Important/list.txt
  5. Now a dry run. --dry-run shows what the script would do and changes nothing. The first time, uv may take a few seconds to find or download a Python.

    Windows
    uv run daily_backup.py "$HOME\Important" "$HOME\Backups\Important" --dry-run
    uv run daily_backup.py "$HOME\Important" "$HOME\Backups\Important"
    $LASTEXITCODE
    Mac / Linux
    uv run daily_backup.py ~/Important ~/Backups/Important --dry-run
    uv run daily_backup.py ~/Important ~/Backups/Important
    echo $?
  6. On Windows it looks like this:

    Output
    2026-10-12 01:33:59 INFO    Backing up C:\Users\user\Important: 1 files, 0.0 MB
    2026-10-12 01:33:59 INFO    Dry run: would write a new zip to C:\Users\user\Backups\Important
    2026-10-12 01:34:00 INFO    Backing up C:\Users\user\Important: 1 files, 0.0 MB
    2026-10-12 01:34:00 INFO    Saved backup-2026-10-12_013400.zip (1 files, 0.0 MB)
    0
  7. That last 0 is the exit code, and it's the most important number in this mission. Every program hands one back when it finishes: 0 means it worked, anything else means it didn't. Task Scheduler, cron, launchd and GitHub Actions all read it. The kit's script returns 1 if the backup fails and 2 if the source folder is missing (a USB drive that isn't plugged in, say).

    PowerShell showing a dry run and a real run of daily_backup.py with exit code 0, then a run against a missing GoneSoon folder that logs Source folder not found and returns exit code 2
    Exit code 0 when the backup works, 2 when the folder isn't there. That one number is what every scheduler checks.
  8. What the script does, and why each part matters when nobody is watching:

    • It writes a log. Every run adds lines to backup-log.txt in the backup folder. A scheduled job has no window, so the log is the only place an error can go.
    • It uses full paths. A scheduler doesn't start in your folder, so Important on its own would mean nothing. Give it the whole path every time.
    • It writes the zip under a temporary name, then renames it. If the PC dies halfway, you're left with an obvious .partial file, never a broken zip that looks finished.
    • It keeps the newest 7 zips (change it with --keep) and only deletes files named exactly like its own, backup-YYYY-MM-DD_HHMMSS.zip. Anything else in that folder is never touched.
    • It only uses Python's standard library, so there's nothing to install and nothing to break when a package updates.
    TipThis is a simple zip copy, good for a folder of documents. For whole drives, versions going back months, encryption and protection from ransomware, use restic from Backups that survive ransomware. The scheduling in this mission works exactly the same for it.
    CarefulIf the folder holds other people's personal data (clients' details, invoices with names on), the backup is personal data too under UK GDPR. Keep it as safe as the original: an encrypted drive, not a spare USB stick in a drawer.
✓
CheckpointA backup-...zip file is in the Backups\Important folder, backup-log.txt ends with a Saved line, and the exit code is 0.
If it breaks"uv" is not recognised, or "command not found: uv" + 3 more
"uv" is not recognised, or "command not found: uv"
Close every terminal window and open a new one, so it picks up the new install. If it still fails, run the installer again and read the last few lines: they say which folder uv went into.
"No such file or directory: daily_backup.py"
You're in the wrong folder. Unzipping by double-click often makes schedule-anything-kit\schedule-anything. Run ls and cd into the folder that contains daily_backup.py.
"Source folder not found ... (is the drive plugged in?)" and exit code 2
The path is wrong or the drive isn't there. Copy the path from File Explorer's address bar (or drag the folder into a Mac terminal) and put it in quotes.
"Skipped ... (Permission denied)" lines in the log
A file was open and locked by another app, often an Outlook data file or a running database. The rest of the backup still happens. Close that app before the scheduled time, or leave those files out.
Still stuck? Ask the community
Next mission · Build things with codeMake a Reel-ready animated intro with code
→