Automation 6 min read

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

The 5 Automations I Actually Use Every Day (And the Filter I Ran Them Through)

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.

Comments

No comments yet — be the first.

Leave a comment

Links aren't allowed. Short thanks are posted right away; everything else is reviewed first.

related

More from the blog.

Weekly · Free · Unsubscribe any time

The Operator's Brief

Every week: something I've automated, a tool I've found useful, and whatever I'm thinking about. Short, practical, no sales pitch.

✓

You're in.

Check your inbox to confirm — first issue arrives next week.