Real-World Bash Automation Projects for Sysadmins and Developers
Build four production-grade automation projects in Bash: database backup with retention, system health monitor with alerts, zero-downtime deployment, and log analysis pipeline. Lesson 10 of the Linux & Bash Scripting course.

The fastest way to consolidate scripting skills is to build things you would actually use. This lesson presents four complete, production-ready automation projects that apply every concept from this course: error handling, traps, functions, arrays, process management, scheduling, and API integration.
Previous: Lesson 9 — Bash in CI/CD Pipelines
Project 1: Database Backup with Retention
A complete PostgreSQL backup script with compression, remote transfer, retention policy, and alerting.
#!/usr/bin/env bash
# pg_backup.sh — PostgreSQL backup with S3 upload and retention
set -euo pipefail
# ---------- Configuration ----------
readonly DB_NAME="${1:?'Usage: pg_backup.sh <database_name>'}"
readonly BACKUP_DIR="/var/backups/postgresql"
readonly S3_BUCKET="${S3_BUCKET:?'S3_BUCKET must be set'}"
readonly RETENTION_DAYS=30
readonly TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
readonly BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}_${TIMESTAMP}.sql.gz"
readonly LOG_FILE="/var/log/pg_backup.log"
readonly SLACK_WEBHOOK="${SLACK_WEBHOOK:-}"
# ---------- Logging ----------
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO $*" | tee -a "$LOG_FILE"; }
error() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR $*" | tee -a "$LOG_FILE" >&2; }
# ---------- Alerting ----------
alert() {
local message="$1"
log "ALERT: $message"
if [[ -n "$SLACK_WEBHOOK" ]]; then
curl -sf -X POST "$SLACK_WEBHOOK" \
-H 'Content-type: application/json' \
-d "{\"text\":\":rotating_light: Backup alert: ${message}\"}" \
|| true
fi
}
# ---------- Cleanup ----------
cleanup() {
local exit_code=$?
if [[ -f "${BACKUP_FILE}.tmp" ]]; then
rm -f "${BACKUP_FILE}.tmp"
fi
if [[ $exit_code -ne 0 ]]; then
alert "Backup of ${DB_NAME} failed (exit code: ${exit_code})"
fi
}
trap cleanup EXIT
# ---------- Main ----------
main() {
log "Starting backup: ${DB_NAME}"
mkdir -p "$BACKUP_DIR"
# Dump and compress atomically
pg_dump "$DB_NAME" | gzip > "${BACKUP_FILE}.tmp"
mv "${BACKUP_FILE}.tmp" "$BACKUP_FILE"
local size
size="$(du -sh "$BACKUP_FILE" | cut -f1)"
log "Backup created: ${BACKUP_FILE} (${size})"
# Upload to S3
aws s3 cp "$BACKUP_FILE" "s3://${S3_BUCKET}/postgresql/${DB_NAME}/"
log "Uploaded to s3://${S3_BUCKET}/postgresql/${DB_NAME}/"
# Remove local file after successful upload
rm -f "$BACKUP_FILE"
# Apply retention: remove backups older than RETENTION_DAYS
local deleted
deleted=$(aws s3 ls "s3://${S3_BUCKET}/postgresql/${DB_NAME}/" \
| awk '{print $4}' \
| while read -r key; do
local ts="${key%%_*}"
local age=$(( ( $(date +%s) - $(date -d "${ts:0:8}" +%s) ) / 86400 ))
if (( age > RETENTION_DAYS )); then
aws s3 rm "s3://${S3_BUCKET}/postgresql/${DB_NAME}/${key}"
echo "$key"
fi
done | wc -l)
log "Retention policy applied: ${deleted} old backup(s) removed"
log "Backup complete"
}
mainProject 2: System Health Monitor with Webhook Alerts
A script that checks CPU, memory, disk, and service status — and sends a Slack alert when thresholds are breached.
#!/usr/bin/env bash
# health_monitor.sh — System health check with Slack alerts
set -euo pipefail
readonly SLACK_WEBHOOK="${SLACK_WEBHOOK:?'SLACK_WEBHOOK must be set'}"
readonly CPU_THRESHOLD=80
readonly MEM_THRESHOLD=90
readonly DISK_THRESHOLD=85
readonly SERVICES=("nginx" "postgresql" "redis")
alerts=()
# ---------- Checks ----------
check_cpu() {
local cpu_idle
cpu_idle=$(top -bn1 | grep "Cpu(s)" | awk '{print $8}' | cut -d. -f1)
local cpu_used=$(( 100 - cpu_idle ))
if (( cpu_used > CPU_THRESHOLD )); then
alerts+=("CPU usage is ${cpu_used}% (threshold: ${CPU_THRESHOLD}%)")
fi
}
check_memory() {
local mem_used_pct
mem_used_pct=$(free | awk '/^Mem:/{printf "%.0f", $3/$2*100}')
if (( mem_used_pct > MEM_THRESHOLD )); then
alerts+=("Memory usage is ${mem_used_pct}% (threshold: ${MEM_THRESHOLD}%)")
fi
}
check_disk() {
while IFS= read -r line; do
local use_pct mount
use_pct=$(echo "$line" | awk '{print $5}' | tr -d '%')
mount=$(echo "$line" | awk '{print $6}')
if (( use_pct > DISK_THRESHOLD )); then
alerts+=("Disk ${mount} is ${use_pct}% full (threshold: ${DISK_THRESHOLD}%)")
fi
done < <(df -h | tail -n +2 | grep -v tmpfs)
}
check_services() {
for service in "${SERVICES[@]}"; do
if ! systemctl is-active --quiet "$service"; then
alerts+=("Service '${service}' is not running")
fi
done
}
# ---------- Alert ----------
send_slack_alert() {
local hostname
hostname="$(hostname -f)"
local message="*Health alert on ${hostname}*\n"
for alert in "${alerts[@]}"; do
message+="• ${alert}\n"
done
curl -sf -X POST "$SLACK_WEBHOOK" \
-H 'Content-type: application/json' \
-d "{\"text\":\"${message}\"}"
}
# ---------- Main ----------
check_cpu
check_memory
check_disk
check_services
if (( ${#alerts[@]} > 0 )); then
send_slack_alert
echo "ALERT: ${#alerts[@]} issue(s) found"
exit 1
else
echo "OK: All checks passed"
fiProject 3: Zero-Downtime Application Deployment
A deployment script that performs health-checked rolling updates with automatic rollback on failure.
#!/usr/bin/env bash
# deploy_app.sh — Zero-downtime deploy with rollback
set -euo pipefail
readonly APP="${1:?'Usage: deploy_app.sh <app_name> <image_tag>'}"
readonly IMAGE_TAG="${2:?'Usage: deploy_app.sh <app_name> <image_tag>'}"
readonly NAMESPACE="${NAMESPACE:-production}"
readonly HEALTH_URL="${HEALTH_URL:-http://localhost:8080/health}"
readonly ROLLOUT_TIMEOUT=300
log() { echo "[$(date '+%H:%M:%S')] $*"; }
error() { echo "[$(date '+%H:%M:%S')] ERROR: $*" >&2; }
get_current_tag() {
kubectl get deployment "$APP" -n "$NAMESPACE" \
-o jsonpath='{.spec.template.spec.containers[0].image}' \
2>/dev/null | awk -F: '{print $NF}' || echo "none"
}
rollback() {
local previous_tag="$1"
error "Deployment failed — rolling back to ${previous_tag}"
kubectl set image "deployment/${APP}" \
"${APP}=${DOCKER_REGISTRY}/${APP}:${previous_tag}" \
-n "$NAMESPACE"
kubectl rollout status "deployment/${APP}" -n "$NAMESPACE" --timeout=120s
error "Rollback complete"
}
wait_for_health() {
local attempts=0
local max=30
log "Waiting for health check at ${HEALTH_URL}..."
until curl -sf "$HEALTH_URL" &>/dev/null; do
(( attempts++ ))
if (( attempts >= max )); then
error "Health check failed after ${max} attempts"
return 1
fi
sleep 10
done
log "Health check passed"
}
main() {
local previous_tag
previous_tag="$(get_current_tag)"
if [[ "$previous_tag" == "$IMAGE_TAG" ]]; then
log "Already at ${IMAGE_TAG} — nothing to do"
exit 0
fi
log "Deploying ${APP}: ${previous_tag} → ${IMAGE_TAG}"
# Register rollback in case anything fails from here
trap "rollback '${previous_tag}'" ERR
kubectl set image "deployment/${APP}" \
"${APP}=${DOCKER_REGISTRY}/${APP}:${IMAGE_TAG}" \
-n "$NAMESPACE"
kubectl rollout status "deployment/${APP}" \
-n "$NAMESPACE" --timeout="${ROLLOUT_TIMEOUT}s"
wait_for_health
# Remove the rollback trap — deployment succeeded
trap - ERR
log "Deployment complete: ${APP} is running ${IMAGE_TAG}"
}
mainProject 4: Log Analysis Pipeline
A daily log analysis script that parses nginx access logs, extracts key metrics, and writes a summary report.
#!/usr/bin/env bash
# log_analysis.sh — Daily nginx access log analysis
set -euo pipefail
readonly LOG_FILE="${1:-/var/log/nginx/access.log}"
readonly REPORT_DIR="/var/reports/nginx"
readonly DATE="$(date +%Y-%m-%d)"
readonly REPORT="${REPORT_DIR}/report_${DATE}.txt"
mkdir -p "$REPORT_DIR"
{
echo "======================================"
echo " Nginx Access Log Report: ${DATE}"
echo "======================================"
echo
echo "## Total Requests"
wc -l < "$LOG_FILE"
echo
echo "## HTTP Status Code Breakdown"
awk '{print $9}' "$LOG_FILE" \
| sort | uniq -c | sort -rn \
| awk '{printf " %-6s %d\n", $2, $1}'
echo
echo "## Top 10 Requested URLs"
awk '{print $7}' "$LOG_FILE" \
| cut -d'?' -f1 \
| sort | uniq -c | sort -rn | head -10 \
| awk '{printf " %6d %s\n", $1, $2}'
echo
echo "## Top 10 Client IPs"
awk '{print $1}' "$LOG_FILE" \
| sort | uniq -c | sort -rn | head -10 \
| awk '{printf " %6d %s\n", $1, $2}'
echo
echo "## Top 10 Slowest Requests (request time field)"
awk '{print $NF, $7}' "$LOG_FILE" \
| sort -rn | head -10 \
| awk '{printf " %8ss %s\n", $1, $2}'
echo
echo "## 5xx Errors"
grep '" 5[0-9][0-9] ' "$LOG_FILE" \
| awk '{print $9, $7}' \
| sort | uniq -c | sort -rn | head -20 \
| awk '{printf " %4d %s %s\n", $1, $2, $3}'
echo
echo "Report generated: $(date)"
} > "$REPORT"
echo "Report written to: $REPORT"
cat "$REPORT"Schedule it daily:
# /etc/cron.d/nginx-log-analysis
0 1 * * * root /usr/local/bin/log_analysis.sh >> /var/log/log_analysis.log 2>&1What to Build Next
These four projects cover the core patterns. Here are natural extensions to deepen your skills:
- Add
bats-coreunit tests to the backup and health monitor scripts - Port the health monitor to post metrics to Prometheus Pushgateway instead of Slack
- Extend the deploy script to support blue-green deployment (two live deployments, traffic switch via load balancer)
- Build a self-service script that new engineers run to bootstrap their local dev environment — databases, config files, dependencies — in a single idempotent run
