Shore Up
Ketan AagjaDirector of IT · 22+ years in infrastructure, security & mail systems

Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.

Inventory Installed Packages and Services Across Servers over SSH

This guide gives you a small bash script that logs into a list of servers over SSH, and on each one dumps two things to a text file on your control machine: the list of installed packages (via dpkg-query on Debian/Ubuntu or rpm on RHEL/Alma) and the list of systemd service unit files. The purpose is a point-in-time software and service inventory you can diff, audit, or archive.

9 min read

Sync Two Directories in Near-Real-Time with inotifywait and rsync

This guide builds a small daemon that watches a source directory with inotifywait and, whenever a file there changes, runs rsync to mirror those changes into a destination directory. The result is one-way, near-real-time replication: destination follows source, never the other way around.

8 min read

Automate SSL Certificate Expiry Monitoring Across Many Domains

This guide builds a small bash script that connects to a list of hostnames over TLS, reads the expiry date from each certificate, and emails you a summary of anything expiring within a threshold you choose (30 days by default). It is a read-only monitor : it makes outbound TLS connections and sends mail. It does not touch, renew, or modify any certificate, and it changes nothing on the servers it checks.

10 min read

Automate DNS Record Checks and Alert on Drift with dig

This is a read-only monitor . The script queries DNS with dig , compares each answer against a baseline file you control, and prints (and by cron, emails) a line for every record that no longer matches. It changes no DNS records, no zone files, and no configuration — the worst it can do is send you an email or write to a file you point it at. Because of that there is nothing to roll back on the DNS side.

10 min read

Automate Debian/Ubuntu Package Updates With a Safe Reboot

This guide sets up a small script, run by a systemd timer, that does three things on a schedule: refreshes the package lists ( apt-get update ), installs available upgrades non-interactively, and — only if the upgrade left the system needing a reboot — reboots the machine during a maintenance window.

9 min read

Detect and Report Failed SSH Login Attempts with a Log-Parsing Script

This guide builds a read-only bash script that parses your SSH log, counts failed password attempts, and prints a summary of the busiest source IP addresses and the usernames they tried. Its purpose is visibility — spotting brute-force patterns — not blocking. It does not change firewall rules, ban anyone, edit config, or delete anything. Running it and re-running it leaves your system exactly as it was.

9 min read

Automate UFW Firewall Rules From a Config File

This guide gives you a small bash script that reads a plain-text config file and passes each line to ufw , so your firewall rules live in one readable, version-controllable file instead of your shell history. It adds the rules you list; it does not remove rules that aren't in the file.

8 min read

Replace a Cron Job with a systemd Timer, Logging, and Failure Alerts

This guide replaces a cron job with three small systemd units: a service that runs your task, a timer that schedules it, and a small notification service that emails you when the task fails. The point is to get the two things cron does not give you for free — the task's output captured in the journal, and an alert when it exits non-zero.

8 min read

Harden a Fresh Ubuntu Server with a First-Boot Bash Script

This script applies a standard first-boot baseline to a fresh Ubuntu server: it creates a non-root sudo user with your SSH key, turns on the UFW firewall (allowing only SSH), disables direct root login and SSH password authentication, enables automatic security updates, and installs fail2ban to throttle SSH brute-forcing. Its purpose is to take a default cloud or VM image from "wide open with a root password" to a sane, keys-only baseline in one pass.

10 min read

Automate MySQL/MariaDB Backups With Rotation and a Restore Test

Two scripts here. The first ( mysql-backup.sh ) makes a gzipped mysqldump of every database on the server, drops the file in /var/backups/mysql , and deletes any dump older than a retention window. The second ( mysql-restore-test.sh ) proves a backup is usable by loading the newest dump for one database into a throwaway database, counting the tables, and dropping that throwaway again.

9 min read

Automate Dovecot Mailbox Quota Reports with a Shell Script

This guide builds a small shell script that runs doveadm quota get for every mailbox on a Dovecot server, writes a plain-text usage report to a file, and emails you a summary that flags anyone at or above a threshold (90% by default). It is a read-only reporting script. It queries quota figures that Dovecot already tracks; it does not create, resize, recalculate, or delete anything in a mailbox.

9 min read

Schedule and Verify PostgreSQL Backups with pg_dump and Retention

This guide sets up a bash script that runs pg_dump (and pg_dumpall for cluster-wide roles) on a schedule, writes each backup to a directory with a timestamped filename, and then deletes any backup older than a retention window. The purpose is a hands-off nightly logical backup you can restore from.

8 min read

Bulk-Create Zimbra Accounts from a CSV with zmprov

This procedure reads a CSV of new users and generates a batch file of createAccount commands that zmprov executes to create real, live mailboxes on your Zimbra server. Creating accounts is a provisioning action that touches your directory and mail store. It is not destructive on its own — but the rollback (deleting an account) is irreversible: deleteAccount removes the mailbox and all of its mail with no undo. Treat the delete step accordingly.

8 min read