In the final script of my S3 backup series, I build a restore tool that pulls photos back down from the cloud with a single aws s3 sync command — then run into a testing surprise and a security confession worth sitting with.
Why a restore script, and not just a backup script
For four scripts now, this series has been about getting photos up into S3 reliably — checking for a bucket, uploading new files, keeping things tidy. But a backup you can't restore from isn't really a backup, it's just a very expensive place to lose track of your files. So the fifth and final script in this series, restorePhotos.sh, closes the loop: it pulls everything back down from the bucket to a local machine.
I think about this the same way I think about Dropbox. Dropbox's whole value proposition isn't the upload — it's that the folder on your desktop and the copy in the cloud stay in sync in both directions, so if you lose your laptop, you get a new one, sign in, and your files just reappear. That's the experience I wanted here, minus the always-on background daemon. Instead of a service quietly watching a folder, I have a script I run on demand: when I want a fresh local copy of everything in the bucket, whether that's because I got a new machine, wiped a drive, or accidentally deleted something I shouldn't have, I run this and my photos come back.
It's a simple idea, but it's the piece that actually makes the rest of the series worth doing. An upload script without a restore script is a one-way trip. This is the return leg.
Writing the script
I started the same way I've started every script in this series: setting the shell to exit immediately if any command fails. It's a small habit, but it means if something goes wrong partway through — a bad AWS credential, a typo in the bucket name — the script stops instead of plowing ahead and potentially doing something unexpected with a half-finished state.
From there, the setup is just two variables: the name of the S3 bucket I've been using throughout the series, and a local restore directory, which I pointed at a folder on my desktop. I like keeping the restore location separate and clearly labeled rather than restoring on top of an existing photos folder — it makes it obvious what came from the cloud versus what was already on the machine, and it avoids any ambiguity about which files are the "source of truth" during a restore.
The actual work happens in a single command: aws s3 sync, pointed from the bucket to that local directory. Sync is the right tool here rather than cp or a manual loop, because it only pulls down what's different — new or changed files — rather than re-downloading everything every time you run it. For a one-off restore that distinction barely matters, but it means the same script works just as well if I run it repeatedly, say to keep a secondary machine roughly current with the bucket. One command, and the whole restore logic is done. That's honestly the appeal of the AWS CLI for something like this — you don't need to hand-roll file comparison or retry logic, sync already does it.
Testing the failure case — and getting a surprise
Before trusting a script, I like to see it fail on purpose, not just succeed. So I deliberately ran restorePhotos.sh before creating the target directory on the desktop, expecting the script to error out and stop, given that I'd set it to exit on any failure.
That's not quite what happened. Instead of throwing an error, the script ran through without complaint. I'll be honest that this caught me a little off guard in the moment — it wasn't the failure mode I was expecting to demonstrate. I want to flag this rather than paper over it: I didn't fully chase down the underlying reason on screen, so if you're following along and building something similar, don't take it as settled that a missing destination directory is always a non-issue for your setup. Test it yourself before you depend on that behavior, especially if your script does anything more elaborate than a straight sync afterward.
What I can tell you with confidence is what I did next: I created the restore directory on the desktop and reran the script. That time, it worked exactly as intended, pulling the full contents of the bucket down into the new folder. If you're building your own version of this, I'd treat that first test as a reminder to verify assumptions about error handling explicitly, rather than trusting that "set -e" or its equivalent will catch every case you expect it to. Shell scripts are full of small surprises like this, and the only reliable fix is testing the actual failure path, not just imagining it.
Verifying the restore, and the security habit I should have led with
Once the script completed cleanly, I didn't just trust the green checkmark — I went into the restore folder on the desktop and looked. The JPEGs were there, matching what I'd expect from the bucket. That's a step I'd encourage anyone to keep in their own version of this workflow: a script exiting without an error tells you it didn't crash, not that it did the right thing. Actually opening the folder and eyeballing the files is a cheap, fast way to close that gap.
Worth noting: at this point I had three copies of these photos floating around — the original local set, this newly restored set in a separate folder, and the copy sitting in S3. That's not a problem in itself; redundancy is kind of the point of a backup strategy. But it's worth being deliberate about it rather than accumulating duplicate folders by accident, especially if you're going to keep running this restore script on a schedule or across multiple machines.
I want to close on something I flagged during the demo and think is worth repeating clearly here: I ran this entire series, including this final script, using an admin-level AWS account. I did that purely for convenience, so I didn't have to stop and debug permission errors on camera. It's not what I'd recommend for real use, and it's not what I actually use day to day outside of a demo like this. If you're adapting any of these scripts — the backup ones or this restore one — for your own photos or for anything resembling production, set up an IAM policy scoped to exactly what the script needs: read and write access to that one bucket, nothing broader. An admin account can do far more damage than a sync script ever needs to be capable of, and there's no good reason to hand a script credentials it doesn't need. This is one of those things that's easy to defer "just for now" and then never circle back to, so I'd rather say it plainly at the end of the series than leave it as a footnote.
Conclusion
That's the fifth and final script in this series — a small, one-command tool that turns an S3 bucket into something closer to a real backup you can actually recover from, not just a place files go to sit. If you've been following along, the natural next step is to go back through the four earlier scripts with this one in mind, and then take an afternoon to swap your own admin credentials out for a least-privilege IAM policy before you run any of them for real.
Official AWS references
KEEP PRACTICING THE DECISION
Learn the concept, then test the edge case.
The Solutions Architect Associate course in TutorialRepo combines guided lessons, scenario questions, review, and course-aware explanations.