The 5-Line Bash Script That Runs My Life
A real, copy-pasteable, runs-every-day bash script that backs up my laptop, prunes old logs, and tells me when something is wrong. Plus the 3 hours I spent getting the error reporting right.
The 5-line scriptHere is the script. The script lives at ~/bin/daily.sh. The script runs at 7am every day via cron. The script is 5 lines of bash, plus 3 lines of error reporting, plus 1 line that I will explain.
#!/usr/bin/env bash
set -euo pipefail
# 1. back up the laptop
rsync -a --delete ~/Documents/ /Volumes/Backup/documents/ 2>> "$LOG"
# 2. prune logs older than 30 days
find ~/logs/ -name "*.log" -mtime +30 -delete 2>> "$LOG"
# 3. report
echo "daily.sh: ok" | mail -s "daily ok" brian@ackerman.io 2>> "$LOG" || exit 1
That is the script. 5 lines (the comment line, the rsync, the find, the echo, the mail). The script runs every morning. The script does 3 things: back up the laptop, prune old logs, and report. The 3 things are the 3 things I care about. The 3 things are the part the script is built around.Why this script existsThe script exists because I have a 2018 laptop, a 2021 external SSD, and a 2024 habit of forgetting to back up. The habit of forgetting is the part that is the problem. The habit of forgetting is also the part I have tried to solve with Time Machine, with cloud backup, with the "I will remember" approach, and with the "I will write a blog post about it" approach. None of them worked. The script is the part that actually worked, and the worked is the part I want to share.The script is also the smallest possible version of the thing I want. The smallest possible version is the part that does not need a deploy, a package manager, a test suite, or a config file. The smallest possible version is the part that lives in a single file in my home directory, and the smallest possible version is the part I can read in 30 seconds and understand in 60. The smallest is the part that has worked for 18 months.What the script does (line by line)Line 1: #!/usr/bin/env bashThe shebang. The shebang is the part that tells the system which interpreter to use. The shebang is also the part that means the script can be run directly (e.g. ./daily.sh) instead of bash daily.sh. The shebang is the part that makes the script feel like a "real" script, not a "command line thing."Line 2: set -euo pipefailThe safety net. The -e is the part that exits on any error. The -u is the part that exits on any undefined variable. The -o pipefail is the part that exits on any error in a pipe. The 3 flags are the part that means the script will fail loudly instead of failing silently. The fail loudly is the part I have learned to want, because the fail silently is the part that bit me in the first version of this script (more on that in a moment).Line 3: the rsyncThe backup. The rsync -a --delete ~/Documents/ /Volumes/Backup/documents/ is the part that copies my Documents folder to the external SSD. The -a is the part that means "archive" (preserves permissions, timestamps, etc.). The --delete is the part that means "delete files on the destination that are not on the source." The --delete is the part that keeps the backup a real mirror, not a growing pile of old files. The 2>> "$LOG" is the part that sends any errors to the log file instead of to the screen, and the log file is the part I check when something goes wrong.Line 4: the findThe prune. The find ~/logs/ -name "*.log" -mtime +30 -delete is the part that deletes any log file in my logs folder that is older than 30 days. The -mtime +30 is the part that means "modified more than 30 days ago." The -delete is the part that actually deletes the files. The prune is the part that keeps my logs folder from becoming a 50GB graveyard of old logs I will never read. The prune is the part that runs the same time every day, and the prune is the part that means the logs folder is bounded at about 30 days worth of data.Line 5: the mailThe report. The echo "daily.sh: ok" | mail -s "daily ok" brian@ackerman.io is the part that sends me an email when the script succeeds. The mail is the part that lets me verify the script ran (the email shows up in my inbox around 7:05am, every day, like clockwork). The mail is also the part I check first if I do not see an email, because the missing email is the part that tells me the script failed. The mail is the part I will talk about in the "3 hours" section below.What the script does not doIt does not back up the entire laptop. The script only backs up the Documents folder, because the Documents folder is the only folder I care about losing. The other folders (Downloads, Desktop, Photos) are the folders I can reconstruct from other sources (cloud, phone, the original downloads). The Documents folder is the folder that has my notes, my code, my half-finished books, my tax returns, and the half-dozen other things I would be devastated to lose. The Documents folder is the part the script protects.It does not encrypt the backup. The backup is on an external SSD that sits on my desk, and the SSD is not encrypted. The SSD is not encrypted because the SSD is the kind of backup I am making for "I lost a file" reasons, not for "my house was robbed" reasons. The encryption is the part I would add for the "my house was robbed" reasons, and the part I have not added because the "my house was robbed" scenario is not the scenario I am optimizing for.It does not run on a remote server. The script runs on the laptop, and the laptop is the part that is the source of truth. The script does not need a remote server because the script does not need to coordinate with anything else. The script is the part I run on the laptop, the script is the part that runs locally, and the script is the part that does not need anything else to work.The 3 hours I spent on error reportingThe 3 hours is the part I almost skipped writing about, because the 3 hours is the part that makes the script feel less simple than the 5 lines suggest. The 3 hours is also the part that makes the script actually work, and the work is the part I want to capture.The first version of the script did not have the 2>> "$LOG". The first version had the rsync and the find and the mail, and the first version looked like this:
#!/usr/bin/env bash
rsync -a --delete ~/Documents/ /Volumes/Backup/documents/
find ~/logs/ -name "*.log" -mtime +30 -delete
echo "daily.sh: ok" | mail -s "daily ok" brian@ackerman.io
The first version had a bug. The bug was: the external SSD was not always mounted. The bug was: when the SSD was not mounted, the rsync failed silently. The bug was: when the rsync failed silently, the find and the mail still ran, and the mail told me "ok" even though the rsync had failed. The "ok" email is the part that gave me a false sense of security for about 3 weeks, and the false sense of security is the part that almost cost me a week of work.The fix was 2 things:set -euo pipefail at the top. The fix is the part that exits the script on any error, so the find and the mail do not run if the rsync failed. The fix is the part I had seen recommended in 100 bash tutorials and had skipped because the fix "felt like overkill for a 5-line script." The fix was not overkill. The fix was the part that would have caught the bug on day 1.2>> "$LOG" on every command. The fix is the part that sends errors to a log file, so I can see what went wrong after the fact. The log is the part I check when the "ok" email does not arrive, and the log is the part that tells me the SSD was not mounted, or the SSD was full, or the SSD had a permission issue. The log is the part that converts the script from "I hope it worked" to "I can verify it worked."3 hours was about right for the fix. The 3 hours included reading 4 bash tutorials, the bash man page, and a Stack Overflow answer from 2014 that was the only one that actually explained the pipefail flag in a way I understood. The 3 hours also included the part where I tested the fix by intentionally unplugging the SSD, running the script, and verifying the "ok" email did not arrive. The verification is the part I have now done about 20 times, and the verification is the part that gives me confidence the script is actually working.What the script has caughtThe script has caught 3 things in the 18 months it has been running:1. The SSD failed in month 4. The script ran, the rsync failed, the "ok" email did not arrive, I checked the log, the log said "input/output error." I bought a new SSD that day. The new SSD is the one the script has been backing up to for the last 14 months. The 14 months of backups is the part I would not have had if the script had not failed loudly.2. The Documents folder grew past 50GB in month 8. The script ran, the rsync took 45 minutes instead of the usual 5, the "ok" email arrived 40 minutes late. I checked the logs folder, I checked the Documents folder, I found a folder of old video files I had forgotten about. I deleted the videos. The Documents folder is back to a manageable size. The size is the part I would not have known about if the slow rsync had not surfaced it.3. The mail command broke in month 11. The script ran, the rsync succeeded, the find succeeded, the mail failed (an OS upgrade changed the mail binary). The "ok" email did not arrive, the log said "mail: command not found," I fixed the mail command, the script is back to normal. The mail is the part I would not have known about if the script had not failed loudly, and the failed loudly is the part that saved me from months of silent failure.What I would tell past-meIf I could go back and tell past-me one thing, it would be: write the script on day one. The script is the part that would have saved me the 3 weeks of false "ok" emails. The script is the part that would have caught the SSD failure on day 1 instead of month 4. The script is the part that would have surfaced the 50GB Documents folder before it became a problem. The script is the part that costs 3 hours to write, and the 3 hours is the part that has saved me 30 hours of "I lost a file" panic, and the panic is the part I have learned to not want.I would also tell past-me: do not skip the error reporting. The error reporting is the part that makes the script work. The error reporting is the part that costs 3 hours, and the 3 hours is the part that makes the script worth writing. The error reporting is the part I almost skipped, and the almost-skipped is the part I am most glad I did not skip.Who this is forAnyone who has a laptop and a habit of forgetting to back up. Anyone who has tried Time Machine, cloud backup, or the "I will remember" approach and found that none of them worked. Anyone who is willing to spend 3 hours writing a script and 30 seconds a day verifying the script worked. The script is the part that solves the backup problem for anyone who is willing to invest 3 hours, and the 3 hours is the part that has paid for itself many times over in the 18 months since I wrote the script.The honest partThe 5-line script is not actually 5 lines. The 5-line script is 5 lines of meaningful bash, plus 3 lines of error reporting, plus 1 line of the shebang, plus a few lines of comments. The 5-line framing is the part that makes the script feel approachable, and the approachable is the part that is the point. The approachable is also the part that is technically a lie, and the lie is the part I want to be honest about. The lie is the part that makes the script feel simpler than it is, and the simpler-than-it-is is the part that I have learned to not want to pretend is not the case.The script is simple. The script is also not as simple as 5 lines. The script is the part that works, and the works is the part I want. The works is the part I would not trade for a 50-line script that "looked more professional." The works is the part that has saved me 3 times, and the 3 times is the part that has made the 3 hours of writing the script worth it, and the worth-it is the part that is the honest part of this post.Want a prompt pack that ships scripts like this?The Blog Writing Factory includes a "bash script generator" prompt that takes a 1-paragraph description of what you want the script to do, and returns a working bash script with the safety flags, the error reporting, and the verification steps. The prompt is the part that turns a 3-hour writing session into a 30-minute writing session, and the 30-minute writing session is the part that makes the script worth writing.Buy on Gumroad. $14
Send me the rough edges if you try it. I read every message.