The 5 Automations I Actually Use Every Day (And the Filter I Ran Them Through)
The 5 automations from Live Life Automated I use every day — with actual code
Writing a book about automation means I'm obligated to actually live by its principles. These aren't scripts from the book — Live Life Automated isn't a script library — but every one of them passes the book's Five Criteria for Good Automation: high frequency, genuinely repetitive, low stakes of failure (or a solid rollback), fast payback on setup cost, and easy to reverse. Here are the five I run every single day on my own infrastructure.
1. Morning System Summary
Every morning at 7:00 AM, I get an email summary of my infrastructure's overnight state. Not a wall of monitoring noise — a curated digest.
#!/bin/bash
# /usr/local/bin/morning-summary.sh
# Runs via cron: 0 7 * * * root /usr/local/bin/morning-summary.sh
set -euo pipefail
REPORT_DATE=$(date '+%Y-%m-%d')
ZABBIX_URL="http://zabbix.internal"
ZABBIX_USER="report-user"
ZABBIX_PASS="${ZABBIX_REPORT_PASSWORD}"
# Pull active problems from Zabbix API
PROBLEMS=$(curl -s -X POST "${ZABBIX_URL}/api_jsonrpc.php" \
-H 'Content-Type: application/json' \
-d "{
\"jsonrpc\": \"2.0\",
\"method\": \"problem.get\",
\"params\": {
\"output\": [\"eventid\", \"name\", \"severity\", \"clock\"],
\"severities\": [3, 4, 5],
\"recent\": true
},
\"auth\": \"${ZABBIX_AUTH_TOKEN}\",
\"id\": 1
}" | python3 -c "
import sys, json
data = json.load(sys.stdin)
problems = data.get('result', [])
if not problems:
print('No high/critical problems active.')
else:
for p in problems:
print(f\" [{p['severity']}] {p['name']}\")
")
# Backup status
FAILED_BACKUPS=$(find /var/log/backup-verify/ -name "*.log" -newer /var/log/backup-verify/last-check -exec grep -l "FAILED" {} \; 2>/dev/null | wc -l)
# Disk space alerts (any filesystem over 80%)
DISK_ALERTS=$(df -h --output=pcent,target | awk 'NR>1 {gsub(/%/,""); if ($1+0 > 80) print $2 " at " $1 "%"}')
# Compose and send
{
echo "Subject: Morning Summary ${REPORT_DATE}"
echo "From: automation@fitzgeraldtech.com"
echo "To: matt@fitzgeraldtech.com"
echo ""
echo "=== MORNING SUMMARY ${REPORT_DATE} ==="
echo ""
echo "--- ACTIVE PROBLEMS ---"
echo "${PROBLEMS}"
echo ""
echo "--- BACKUP STATUS ---"
echo "Failed backup jobs (last 24h): ${FAILED_BACKUPS}"
echo ""
echo "--- DISK SPACE ---"
if [ -z "${DISK_ALERTS}" ]; then
echo "All filesystems under 80%"
else
echo "${DISK_ALERTS}"
fi
} | sendmail -t
The key design decisions: only high/critical Zabbix problems (severity 3+), backup failures, and disk over 80%. No noise. If the email is empty except for "No problems," I know overnight was clean.
2. Backup Verification
Backing up without verifying is superstition, not data protection. This script runs every night after backups complete and logs the result:
#!/bin/bash
# /usr/local/bin/verify-backups.sh
# Runs after PBS backup job completes
set -euo pipefail
LOG_FILE="/var/log/backup-verify/$(date +%Y%m%d).log"
BACKUP_REPO="https://pbs.internal:8007"
NAMESPACE="client-backups"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "${LOG_FILE}"
}
log "Starting backup verification"
# List snapshots from the last 24 hours
RECENT_SNAPSHOTS=$(proxmox-backup-client list \
--repository "${BACKUP_REPO}" \
--output-format json 2>/dev/null | \
python3 -c "
import sys, json
from datetime import datetime, timedelta
data = json.load(sys.stdin)
cutoff = datetime.now() - timedelta(hours=24)
recent = [s for s in data if datetime.fromisoformat(s['backup-time']) > cutoff]
print(len(recent))
")
EXPECTED_SNAPSHOTS=12 # adjust to your client count
if [ "${RECENT_SNAPSHOTS}" -lt "${EXPECTED_SNAPSHOTS}" ]; then
log "FAILED: Expected ${EXPECTED_SNAPSHOTS} snapshots, found ${RECENT_SNAPSHOTS}"
touch "${LOG_FILE}.FAILED"
exit 1
fi
log "OK: ${RECENT_SNAPSHOTS} snapshots found in last 24h"
touch /var/log/backup-verify/last-check
Anything that creates a .FAILED marker gets caught by the morning summary. No manual checking.
3. Client Offboarding Checklist Runner
When an employee leaves a client organization, I run a script that walks through every access revocation step and requires confirmation at each:
#!/bin/bash
# /usr/local/bin/offboard-user.sh
# Usage: offboard-user.sh <client_id> <username>
set -euo pipefail
CLIENT_ID="${1:?Usage: $0 <client_id> <username>}"
USERNAME="${2:?Usage: $0 <client_id> <username>}"
TIMESTAMP=$(date '+%Y%m%d-%H%M%S')
LOG_FILE="/var/log/offboarding/${CLIENT_ID}-${USERNAME}-${TIMESTAMP}.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "${LOG_FILE}"
}
confirm() {
read -r -p "$1 [y/N]: " response
[[ "${response}" =~ ^[Yy]$ ]]
}
log "Starting offboarding: ${CLIENT_ID} / ${USERNAME}"
# Step 1: Disable AD account
if confirm "Disable AD account for ${USERNAME}?"; then
samba-tool user disable "${USERNAME}" \
-H ldaps://${CLIENT_ID}.clients.internal \
-U administrator%"${SAMBA_ADMIN_PASS}" && \
log "AD account disabled" || log "FAILED: AD account disable"
fi
# Step 2: Revoke VPN cert if applicable
if confirm "Revoke VPN certificate for ${USERNAME}?"; then
# OPNsense API call to revoke cert
log "VPN certificate revocation: manual step required - documented in log"
fi
# Step 3: Remove from email distribution lists
log "MANUAL STEP: Review email distribution list membership for ${USERNAME}"
# Generate completion report
log "Offboarding complete. Review log at ${LOG_FILE}"
echo "Offboarding log: ${LOG_FILE}"
Every action is logged with timestamps. The log file is the paper trail if questions come up later.
4. Pre-Patch Snapshot Script
Before any patching window, I snapshot everything:
#!/bin/bash
# /usr/local/bin/pre-patch-snapshot.sh
# Run before any patching on a Proxmox node
NODE="${1:?Provide node name}"
SNAPSHOT_NAME="pre-patch-$(date +%Y%m%d)"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
# Get all running VMs on the node
VMIDS=$(pvesh get /nodes/${NODE}/qemu --output-format json | \
python3 -c "import sys,json; vms=json.load(sys.stdin); print(' '.join(str(v['vmid']) for v in vms if v['status']=='running'))")
log "Creating pre-patch snapshots on ${NODE} for VMs: ${VMIDS}"
for VMID in ${VMIDS}; do
log "Snapshotting VM ${VMID}..."
pvesh create /nodes/${NODE}/qemu/${VMID}/snapshot \
-snapname "${SNAPSHOT_NAME}" \
-description "Pre-patch snapshot $(date)" && \
log "VM ${VMID}: snapshot OK" || \
log "VM ${VMID}: snapshot FAILED"
done
log "Snapshot pass complete. Proceed with patching."
If patching breaks something, rollback is one command per VM. This has saved me three times.
5. New Client Environment Bootstrap
The automation I'm most proud of — not the most complex, but the one that changed the economics of my MSP:
#!/bin/bash
# /usr/local/bin/bootstrap-client.sh
# Usage: bootstrap-client.sh <client_id> <client_name> <contact_email>
set -euo pipefail
CLIENT_ID="${1:?}"
CLIENT_NAME="${2:?}"
CONTACT_EMAIL="${3:?}"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${CLIENT_ID}] $*"; }
log "Bootstrapping new client: ${CLIENT_NAME}"
# 1. Create documentation directory
mkdir -p "/opt/docs/clients/${CLIENT_ID}"/{runbooks,credentials,network,contacts}
cat > "/opt/docs/clients/${CLIENT_ID}/README.md" << EOF
# ${CLIENT_NAME}
Client ID: ${CLIENT_ID}
Created: $(date)
Contact: ${CONTACT_EMAIL}
EOF
log "Documentation structure created"
# 2. Generate WireGuard keys
WG_PRIVATE=$(wg genkey)
WG_PUBLIC=$(echo "${WG_PRIVATE}" | wg pubkey)
echo "PrivateKey = ${WG_PRIVATE}" > "/opt/docs/clients/${CLIENT_ID}/credentials/wg-server.key"
log "WireGuard keys generated"
# 3. Create Zabbix host group via API
ZABBIX_RESULT=$(curl -s -X POST "http://zabbix.internal/api_jsonrpc.php" \
-H "Content-Type: application/json" \
-d "{\"jsonrpc\":\"2.0\",\"method\":\"hostgroup.create\",\"params\":{\"name\":\"${CLIENT_NAME}\"},\"auth\":\"${ZABBIX_AUTH}\",\"id\":1}")
log "Zabbix host group created"
# 4. Send welcome email
sendmail -t << EOF
To: ${CONTACT_EMAIL}
From: matt@fitzgeraldtech.com
Subject: Welcome to Fitzgerald Tech Solutions — ${CLIENT_NAME}
Hi,
Your IT environment is being configured. You'll receive credentials
and setup instructions within 24 hours.
— Matt Fitzgerald
Fitzgerald Tech Solutions
EOF
log "Welcome email sent to ${CONTACT_EMAIL}"
log "Bootstrap complete for ${CLIENT_ID}"
This runs in about 30 seconds and handles everything that doesn't require physical access. The remaining setup (actual server provisioning, on-site network config) is documented in the client's runbook folder before I touch anything.
None of these are "from the book" — Live Life Automated doesn't ship a script library. They're FTS production tooling that happens to be a clean illustration of the book's filter for what's actually worth automating. The full versions I run have more error handling, more logging, and more edge case coverage — these are the readable versions.
What's an automation you run every day that I haven't covered?
Matt Fitzgerald runs Fitzgerald Tech Solutions and writes The Operator's Edge newsletter.