Network Services Automation
Objective
Write a suite of Bash shell scripts to automate common server administration tasks: service health monitoring with automatic restart on failure, batch user provisioning from a CSV file, log rotation and archival, and a nightly system report emailed to the administrator. The scripts were designed to run as systemd timer units rather than raw cron jobs for better logging and dependency management.
Tools & Technologies
Bash 5.x— scripting languagesystemd— service and timer unit managementsystemctl— querying and controlling servicesjournalctl— reading systemd journal logsmailutils / sendmail— sending email reportsawk / sed / cut— text processing in scriptsuseradd / passwd / chage— user provisioning commandslogger— writing custom entries to syslogdf / free / top / ss— system resource reporting
Architecture Overview
Step-by-Step Process
The core monitoring script checks a predefined list of services, logs their state, and restarts any that are inactive. It uses systemctl is-active and writes structured entries via logger.
#!/usr/bin/env bash
# /usr/local/bin/service_monitor.sh
set -euo pipefail
SERVICES=("ssh" "apache2" "bind9" "fail2ban")
LOGFILE="/var/log/service-monitor.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
for svc in "${SERVICES[@]}"; do
STATUS=$(systemctl is-active "$svc" 2>/dev/null || echo "unknown")
if [[ "$STATUS" != "active" ]]; then
echo "[$TIMESTAMP] WARN: $svc is $STATUS — restarting" | tee -a "$LOGFILE"
systemctl restart "$svc" && \
echo "[$TIMESTAMP] INFO: $svc restarted successfully" | tee -a "$LOGFILE" || \
echo "[$TIMESTAMP] ERROR: failed to restart $svc" | tee -a "$LOGFILE"
logger -t service-monitor "Restarted $svc (was $STATUS)"
else
echo "[$TIMESTAMP] OK: $svc is active" >> "$LOGFILE"
fi
done
Created service and timer unit files so the monitor runs every 5 minutes under systemd supervision with automatic logging to the journal.
# /etc/systemd/system/service-monitor.service
[Unit]
Description=Service Health Monitor
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/service_monitor.sh
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/service-monitor.timer
[Unit]
Description=Run service monitor every 5 minutes
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
AccuracySec=30s
[Install]
WantedBy=timers.target
sudo chmod +x /usr/local/bin/service_monitor.sh
sudo systemctl daemon-reload
sudo systemctl enable --now service-monitor.timer
sudo systemctl list-timers --all | grep service-monitor
The provisioning script reads a CSV file (username,fullname,group,expiry), creates each user with the correct settings, assigns them to their group, and forces a password change on first login.
#!/usr/bin/env bash
# /usr/local/bin/provision_users.sh
# CSV format: username,fullname,group,expiry_days
INPUT="$1"
[[ -z "$INPUT" ]] && { echo "Usage: $0 users.csv"; exit 1; }
while IFS=',' read -r username fullname group expiry; do
[[ "$username" == "username" ]] && continue # skip header
# Create group if it doesn't exist
getent group "$group" &>/dev/null || groupadd "$group"
# Create user
useradd -m -c "$fullname" -g "$group" -s /bin/bash "$username" 2>/dev/null || {
echo "WARN: user $username already exists"
continue
}
# Set temporary password (username123) and force change
echo "$username:${username}123" | chpasswd
chage -d 0 "$username" # force password change on login
chage -M "$expiry" "$username" # set max password age
logger -t user-provision "Created user $username in group $group"
echo "Created: $username ($fullname) → group $group, expiry ${expiry}d"
done < "$INPUT"
Collects disk usage, memory stats, failed services, recent auth failures, and top CPU processes, then formats a plain-text report and sends it via mail.
#!/usr/bin/env bash
# /usr/local/bin/nightly_report.sh
REPORT=$(mktemp)
HOST=$(hostname)
DATE=$(date '+%A, %B %d %Y %H:%M')
cat >> "$REPORT" <
Applied best practices: set -euo pipefail, input validation, trap handlers for cleanup, and dry-run mode flags. Tested all scripts with bash -n (syntax check) and shellcheck.
# Syntax check all scripts
bash -n /usr/local/bin/service_monitor.sh
bash -n /usr/local/bin/provision_users.sh
bash -n /usr/local/bin/nightly_report.sh
# Static analysis
shellcheck /usr/local/bin/*.sh
# Test provisioning with sample CSV
cat > /tmp/test_users.csv <
Complete Workflow
Challenges & Solutions
- Script failing silently on unset variables — Added
set -uto catch all unset variable references at runtime, converting silent bugs into explicit errors. - Timer not triggering after reboot — Missed adding
WantedBy=timers.targetin the[Install]section. Without this,systemctl enablehad no effect on boot persistence. - CSV parsing breaking on names with spaces — Fixed by setting
IFS=','in thewhile readloop rather than relying on default word splitting. - Mail not sending from server — The lab environment lacked an MTA. Installed
postfixin local-only mode and usedmailutilsto route reports to the local admin mailbox.
Key Takeaways
set -euo pipefailat the top of every production Bash script is non-negotiable — it transforms subtle bugs into loud failures that are easy to diagnose.- systemd timers are superior to cron for managed services — they log to the journal, support dependency ordering, and can be inspected with standard
systemctlcommands. - Batch provisioning scripts must handle idempotency — running the same script twice should not create duplicate users or destroy existing accounts.
- Always validate external inputs (CSV files, command-line arguments) before processing to prevent script failures or unintended system changes.