1.8 Shell Scripting Practice
Practice Bash with realistic security and operations workflows
The previous lesson explained the building blocks. This lab turns those blocks into practical scripts, so try each scenario before opening the solution and treat the examples as starting points you can adapt.
Table of Contents
- 0. Best Practices Checklist
- 1. Failed SSH Login Monitor
- 2. Automated Backup with Integrity Verification
- 3. Bash Port Scanner
- 4. Log Rotation & Cleanup Daemon
- 5. Service Health Monitor with Auto-Recovery
- 6. Firewall Rule Auditor
- 7. User Account Security Auditor
- 8. SSL Certificate Expiry Checker
- 9. Incident Response Data Collector
- 10. CI/CD Deployment Script with Rollback
- 11. Real Use Cases
Welcome to the Shell Scripting Practice Lab. This page wraps up the shell scripting series with 10 realistic, scenario-based challenges designed for cybersecurity analysts and DevOps engineers.
Each scenario follows a structured format:
- Situation: a realistic problem you might see on the job
- Objective: what the script needs to accomplish
- Solution: a complete working script, hidden until you choose to open it
- Best practices: production habits to carry forward
Try to write your own solution before revealing the answer.
Shell Scripting Best Practices for Security & DevOps
- Always start scripts with
set -euo pipefailfor strict error handling - Quote variables like
"$var"to prevent word splitting and glob expansion - Use
[[ ]]over[ ]for safer, modern conditional tests - Validate inputs before using user-provided data in security scripts
- Log important actions with timestamps because audit trails matter during incident response
- Use
localvariables in functions to prevent namespace pollution - Implement proper exit codes:
0for success, non-zero for specific failure types - Use
shellcheckto lint your scripts before deploying to production - Never hardcode credentials; use environment variables or secret managers
- Add usage messages and
--helpflags to all scripts shared with your team
Failed SSH Login Monitor
/var/log/auth.log in real-time, counts failed attempts per IP, and triggers an alert when any single IP exceeds a threshold, optionally adding the IP to a blocklist.#!/bin/bash
# ssh_monitor.sh - Failed SSH Login Monitor
# Usage: ./ssh_monitor.sh [threshold] [logfile]
set -euo pipefail
THRESHOLD="${1:-5}"
LOGFILE="${2:-/var/log/auth.log}"
ALERT_LOG="/var/log/ssh_alerts.log"
log_alert() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] ALERT: $1"
echo "$msg" | tee -a "$ALERT_LOG"
}
check_failed_logins() {
echo "=== SSH Failed Login Report ==="
echo "Threshold: $THRESHOLD attempts"
echo "Log file: $LOGFILE"
echo "-------------------------------"
# Extract failed login IPs and count occurrences
grep "Failed password" "$LOGFILE" 2>/dev/null \
| grep -oP 'from \K[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' \
| sort | uniq -c | sort -rn \
| while read -r count ip; do
if (( count >= THRESHOLD )); then
log_alert "$ip has $count failed attempts, BLOCKING"
# Auto-block with iptables (uncomment in production)
# iptables -A INPUT -s "$ip" -j DROP
echo " [BLOCKED] $ip, $count attempts"
else
echo " [OK] $ip, $count attempts"
fi
done
echo "-------------------------------"
echo "Report generated: $(date)"
}
# Validate log file exists
if [[ ! -f "$LOGFILE" ]]; then
echo "Error: Log file '$LOGFILE' not found" >&2
exit 1
fi
check_failed_logins
- Always validate input files exist before processing
- Use
set -euo pipefailfor safer script execution - Log all security actions with timestamps for audit trails
- Test blocking rules in dry-run mode before enabling auto-block
Automated Backup with Integrity Verification
#!/bin/bash
# secure_backup.sh - Automated Backup with Integrity Check
# Usage: ./secure_backup.sh
set -euo pipefail
# Configuration
BACKUP_DIRS=("/etc" "/var/www" "/home")
BACKUP_DEST="/backup"
RETENTION_DAYS=30
DATE_STAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DEST}/backup_${DATE_STAMP}.tar.gz"
CHECKSUM_FILE="${BACKUP_FILE}.sha256"
LOG_FILE="/var/log/backup.log"
log() { echo "[$(date '+%F %T')] $1" | tee -a "$LOG_FILE"; }
# Ensure backup directory exists
mkdir -p "$BACKUP_DEST"
log "Starting backup..."
# Create compressed archive
if tar -czf "$BACKUP_FILE" "${BACKUP_DIRS[@]}" 2>>"$LOG_FILE"; then
log "Archive created: $BACKUP_FILE ($(du -h "$BACKUP_FILE" | cut -f1))"
else
log "ERROR: Backup failed!"
exit 1
fi
# Generate checksum
sha256sum "$BACKUP_FILE" > "$CHECKSUM_FILE"
log "Checksum generated: $(cat "$CHECKSUM_FILE")"
# Verify integrity immediately
if sha256sum -c "$CHECKSUM_FILE" &>/dev/null; then
log "Integrity check: PASSED [OK]"
else
log "ERROR: Integrity check FAILED!"
exit 2
fi
# Rotate old backups
deleted=$(find "$BACKUP_DEST" -name "backup_*.tar.gz" \
-mtime +"$RETENTION_DAYS" -delete -print | wc -l)
log "Rotation: Removed $deleted backups older than ${RETENTION_DAYS} days"
log "Backup completed successfully"
- Always verify backup integrity immediately after creation
- Implement backup rotation to prevent disk space exhaustion
- Log every step for troubleshooting and audit compliance
- Store backups on a separate volume or remote location
Bash Port Scanner
/dev/tcp pseudo-device to scan a target host for open ports, with timeout handling and clean output.#!/bin/bash
# bash_scanner.sh - Lightweight Bash Port Scanner
# Usage: ./bash_scanner.sh <target> [start_port] [end_port]
set -uo pipefail
TARGET="${1:?Usage: $0 <target> [start_port] [end_port]}"
START_PORT="${2:-1}"
END_PORT="${3:-1024}"
TIMEOUT=1
OPEN_PORTS=()
echo "----------------------------------------"
echo " Bash Port Scanner v1.0"
echo " Target: $TARGET"
echo " Range: $START_PORT, $END_PORT"
echo "----------------------------------------"
echo ""
echo "Scanning..."
for (( port=START_PORT; port<=END_PORT; port++ )); do
# Use /dev/tcp to attempt connection with timeout
(echo >/dev/tcp/"$TARGET"/"$port") 2>/dev/null &
pid=$!
# Wait with timeout
( sleep "$TIMEOUT"; kill "$pid" 2>/dev/null ) &
waiter=$!
wait "$pid" 2>/dev/null
result=$?
kill "$waiter" 2>/dev/null
wait "$waiter" 2>/dev/null
if [[ "$result" -eq 0 ]]; then
OPEN_PORTS+=("$port")
printf " [OPEN] Port %-5d\n" "$port"
fi
# Progress indicator every 100 ports
(( port % 100 == 0 )) && echo " ... scanned $port/$END_PORT"
done
echo ""
echo "----------------------------------------"
echo " Scan Complete"
echo " Open ports: ${#OPEN_PORTS[@]}"
for p in "${OPEN_PORTS[@]}"; do
printf " -> %d\n" "$p"
done
echo "----------------------------------------"
- Only use in authorized penetration testing engagements
- Implement timeouts to avoid hanging on filtered ports
- This is slower than nmap; use it only when no tools are available
/dev/tcpis a bash-specific feature and is not available in all shells
Log Rotation & Cleanup Daemon
#!/bin/bash
# log_manager.sh - Log Rotation & Cleanup
# Run via cron: 0 2 * * * /opt/scripts/log_manager.sh
set -euo pipefail
LOG_DIRS=("/var/log/nginx" "/var/log/app" "/var/log/syslog")
COMPRESS_AGE=1 # Compress logs older than N days
DELETE_AGE=30 # Delete compressed logs older than N days
DISK_THRESHOLD=80 # Alert if disk usage exceeds N%
REPORT="/var/log/log_manager_report.log"
log() { echo "[$(date '+%F %T')] $1" | tee -a "$REPORT"; }
log "=== Log Management Started ==="
total_compressed=0
total_deleted=0
space_saved=0
for dir in "${LOG_DIRS[@]}"; do
[[ -d "$dir" ]] || continue
log "Processing: $dir"
# Compress old logs (skip already compressed)
while IFS= read -r -d '' file; do
size_before=$(stat -c%s "$file")
if gzip "$file" 2>/dev/null; then
size_after=$(stat -c%s "${file}.gz")
saved=$(( size_before - size_after ))
space_saved=$(( space_saved + saved ))
((total_compressed++))
fi
done < <(find "$dir" -name "*.log" -mtime +"$COMPRESS_AGE" \
-not -name "*.gz" -print0)
# Delete old compressed logs
count=$(find "$dir" -name "*.gz" -mtime +"$DELETE_AGE" \
-delete -print | wc -l)
total_deleted=$((total_deleted + count))
done
# Check disk usage
disk_usage=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if (( disk_usage >= DISK_THRESHOLD )); then
log "WARNING: Disk usage at ${disk_usage}% (threshold: ${DISK_THRESHOLD}%)"
fi
log "Compressed: $total_compressed files"
log "Deleted: $total_deleted old archives"
log "Saved: $(( space_saved / 1024 / 1024 )) MB"
log "Disk usage: ${disk_usage}%"
log "=== Log Management Complete ==="
- Schedule via cron during low-traffic hours
- Always check if directories exist before processing
- Use
-print0withread -d ''for filenames with spaces - Monitor disk usage proactively; don't wait for outages
Service Health Monitor with Auto-Recovery
#!/bin/bash
# health_monitor.sh - Service Health Monitor
# Usage: ./health_monitor.sh (run via cron every 1-5 min)
set -uo pipefail
# Service definitions: "name|url|service_name"
SERVICES=(
"Payment API|http://localhost:8001/health|payment-api"
"Auth Service|http://localhost:8002/health|auth-service"
"Web Frontend|http://localhost:3000|nginx"
)
MAX_RETRIES=3
TIMEOUT=5
WEBHOOK_URL="${SLACK_WEBHOOK:-}"
LOG="/var/log/health_monitor.log"
log() { echo "[$(date '+%F %T')] $1" | tee -a "$LOG"; }
send_alert() {
local msg="$1"
log "ALERT: $msg"
if [[ -n "$WEBHOOK_URL" ]]; then
curl -sf -X POST "$WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"ALERT: $msg\"}" &>/dev/null || true
fi
}
check_service() {
local name="$1" url="$2" svc="$3"
local attempt=1
while (( attempt <= MAX_RETRIES )); do
status=$(curl -sf -o /dev/null -w "%{http_code}" \
--max-time "$TIMEOUT" "$url" 2>/dev/null || echo "000")
if [[ "$status" == "200" ]]; then
log "$name: UP (attempt $attempt)"
return 0
fi
log "$name: attempt $attempt failed (HTTP $status)"
((attempt++))
sleep 2
done
# All retries failed; attempt restart
log "$name: DOWN, attempting restart of $svc"
if systemctl restart "$svc" 2>/dev/null; then
send_alert "$name was down, auto-restarted $svc"
else
send_alert "$name is DOWN and restart FAILED! Manual intervention needed."
fi
return 1
}
log "=== Health Check Started ==="
failures=0
for entry in "${SERVICES[@]}"; do
IFS='|' read -r name url svc <<< "$entry"
check_service "$name" "$url" "$svc" || ((failures++))
done
log "=== Health Check Complete: $failures failures ==="
- Implement retry logic before declaring a service down
- Use timeouts on all HTTP requests to prevent hanging
- Log all actions for post-incident analysis
- Set up escalation: auto-restart first, then alert humans if that fails
Firewall Rule Auditor
#!/bin/bash
# firewall_audit.sh - Firewall Rule Auditor
# Usage: ./firewall_audit.sh
set -uo pipefail
REPORT="/tmp/firewall_audit_$(date +%Y%m%d).txt"
DANGEROUS_PORTS=(22 3306 5432 6379 27017 9200)
ISSUES=0
header() { echo -e "\n$1" | tee -a "$REPORT"; }
finding() { echo " [WARN] $1" | tee -a "$REPORT"; ((ISSUES++)); }
ok() { echo " [OK] $1" | tee -a "$REPORT"; }
echo "Firewall Security Audit, $(date)" | tee "$REPORT"
echo "Host: $(hostname)" | tee -a "$REPORT"
# Check if firewall is active
header "1. Firewall Status"
if command -v ufw &>/dev/null; then
status=$(ufw status 2>/dev/null | head -1)
if [[ "$status" == *"inactive"* ]]; then
finding "UFW is INACTIVE, no firewall protection!"
else
ok "UFW is active"
fi
fi
# Check default policies
header "2. Default Policies"
iptables -L -n 2>/dev/null | grep -E "^Chain (INPUT|FORWARD)" | \
while read -r line; do
chain=$(echo "$line" | awk '{print $2}')
policy=$(echo "$line" | grep -oP '\(policy \K\w+')
if [[ "$policy" == "ACCEPT" ]]; then
finding "$chain default policy is ACCEPT (should be DROP)"
else
ok "$chain default policy is $policy"
fi
done
# Check for wide-open rules
header "3. Wide-Open Rules (source: 0.0.0.0/0)"
iptables -L INPUT -n 2>/dev/null | grep "ACCEPT" | \
while read -r rule; do
for port in "${DANGEROUS_PORTS[@]}"; do
if echo "$rule" | grep -qE "dpt:$port.*0\.0\.0\.0/0|0\.0\.0\.0/0.*dpt:$port"; then
finding "Port $port is open to ANY source: $rule"
fi
done
done
# Check for rules without source restriction
header "4. Unrestricted ACCEPT Rules"
iptables -L INPUT -n --line-numbers 2>/dev/null | \
grep "ACCEPT" | grep "0.0.0.0/0.*0.0.0.0/0" | \
while read -r rule; do
finding "Unrestricted ACCEPT: $rule"
done
header "=== Audit Complete ==="
echo "Total issues found: $ISSUES" | tee -a "$REPORT"
echo "Report saved: $REPORT" | tee -a "$REPORT"
- Default INPUT policy should be DROP, not ACCEPT
- Never expose database ports (3306, 5432, 6379) to 0.0.0.0/0
- Restrict SSH access to specific IP ranges or use a VPN
- Run firewall audits regularly as part of compliance checks
User Account Security Auditor
/etc/passwd, /etc/shadow, and login records to identify security risks in user accounts and generate an actionable report.#!/bin/bash
# user_audit.sh - User Account Security Auditor
# Usage: sudo ./user_audit.sh
set -uo pipefail
[[ $EUID -eq 0 ]] || { echo "Run as root"; exit 1; }
REPORT="/tmp/user_audit_$(date +%Y%m%d).txt"
STALE_DAYS=90
ISSUES=0
log() { echo "$1" | tee -a "$REPORT"; }
warn() { echo " [WARN] $1" | tee -a "$REPORT"; ((ISSUES++)); }
ok() { echo " [OK] $1" | tee -a "$REPORT"; }
log "User Account Audit, $(date)"
log "Host: $(hostname)"
log ""
# Check for UID 0 accounts (root equivalents)
log "=== UID 0 Accounts (Root Equivalents) ==="
while IFS=: read -r user _ uid _; do
if [[ "$uid" == "0" && "$user" != "root" ]]; then
warn "$user has UID 0, root-equivalent access!"
fi
done < /etc/passwd
# Check for accounts with no password
log ""
log "=== Password Status ==="
while IFS=: read -r user pass _; do
case "$pass" in
"") warn "$user has NO password set" ;;
"!"|"!!"|"*") ;; # Locked or system account
*) ok "$user has a password set" ;;
esac
done < /etc/shadow
# Check for stale accounts
log ""
log "=== Stale Accounts (no login in ${STALE_DAYS}+ days) ==="
cutoff=$(date -d "-${STALE_DAYS} days" +%s 2>/dev/null || \
date -v-${STALE_DAYS}d +%s)
while IFS=: read -r user _ uid _ _ _ shell; do
(( uid < 1000 )) && continue # Skip system accounts
[[ "$shell" == */nologin || "$shell" == */false ]] && continue
last_login=$(lastlog -u "$user" 2>/dev/null | tail -1 | awk '{print $4,$5,$6,$7}')
if [[ "$last_login" == *"Never"* ]]; then
warn "$user has NEVER logged in (shell: $shell)"
fi
done < /etc/passwd
log ""
log "=== Audit Complete: $ISSUES issues found ==="
log "Report: $REPORT"
- Run account audits quarterly for compliance frameworks
- Disable accounts that haven't been used in 90+ days
- Only root should have UID 0; any other UID 0 account is a red flag
- Service accounts should use
/sbin/nologinas their shell
SSL Certificate Expiry Checker
#!/bin/bash
# ssl_checker.sh - SSL Certificate Expiry Checker
# Usage: ./ssl_checker.sh [warn_days]
set -uo pipefail
WARN_DAYS="${1:-30}"
DOMAINS=(
"example.com"
"api.example.com"
"mail.example.com"
"staging.example.com"
)
RED='\033[0;31m' YEL='\033[0;33m'
GRN='\033[0;32m' NC='\033[0m'
echo "--------------------------------------------"
echo " SSL Certificate Expiry Report"
echo " Warning threshold: $WARN_DAYS days"
echo " Checked: $(date)"
echo "--------------------------------------------"
echo ""
expiring=0
for domain in "${DOMAINS[@]}"; do
# Get certificate expiry date
expiry=$(echo | openssl s_client -servername "$domain" \
-connect "$domain:443" 2>/dev/null \
| openssl x509 -noout -enddate 2>/dev/null \
| cut -d= -f2)
if [[ -z "$expiry" ]]; then
printf "${RED} [ERROR] %-30s UNREACHABLE${NC}\n" "$domain"
((expiring++))
continue
fi
# Calculate days until expiry
expiry_epoch=$(date -d "$expiry" +%s 2>/dev/null || \
date -jf "%b %d %T %Y %Z" "$expiry" +%s)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
if (( days_left <= 0 )); then
printf "${RED} [ERROR] %-30s EXPIRED (%d days ago)${NC}\n" \
"$domain" "$(( -days_left ))"
((expiring++))
elif (( days_left <= WARN_DAYS )); then
printf "${YEL} [WARN] %-30s EXPIRING in %d days${NC}\n" \
"$domain" "$days_left"
((expiring++))
else
printf "${GRN} [OK] %-30s OK (%d days remaining)${NC}\n" \
"$domain" "$days_left"
fi
done
echo ""
echo "--------------------------------------------"
if (( expiring > 0 )); then
echo -e "${RED} $expiring certificate(s) need attention!${NC}"
else
echo -e "${GRN} All certificates healthy [OK]${NC}"
fi
echo "--------------------------------------------"
- Check certificates daily via cron and alert at 30-, 14-, and 7-day marks
- Automate renewal with Let's Encrypt / certbot where possible
- Include internal services and APIs, not just public-facing domains
- Always test with
openssl s_client; browser checks aren't enough
Incident Response Data Collector
#!/bin/bash
# ir_collector.sh - Incident Response Data Collector
# Usage: sudo ./ir_collector.sh
set -uo pipefail
[[ $EUID -eq 0 ]] || { echo "Must run as root!"; exit 1; }
CASE_ID="IR-$(date +%Y%m%d-%H%M%S)"
EVIDENCE_DIR="/tmp/${CASE_ID}"
mkdir -p "$EVIDENCE_DIR"
collect() {
local name="$1" desc="$2"
shift 2
echo " Collecting: $desc..."
"$@" > "${EVIDENCE_DIR}/${name}.txt" 2>&1 || true
}
echo "----------------------------------------"
echo "Incident Response Data Collector"
echo "Case: $CASE_ID"
echo "----------------------------------------"
echo ""
# System information
collect "01_hostname" "Hostname & OS info" hostnamectl
collect "02_uptime" "System uptime" uptime
collect "03_uname" "Kernel information" uname -a
collect "04_date" "Current timestamp" date -u '+%Y-%m-%d %H:%M:%S UTC'
# User activity
collect "10_who" "Currently logged users" who -a
collect "11_last" "Recent logins" last -n 50
collect "12_lastb" "Failed logins" lastb -n 50
collect "13_auth_log" "Auth log (last 500)" tail -n 500 /var/log/auth.log
# Process information
collect "20_processes" "Running processes" ps auxww
collect "21_pstree" "Process tree" pstree -pa
# Network state
collect "30_connections" "Network connections" ss -tulpn
collect "31_established" "Established conns" ss -tnp state established
collect "32_routes" "Routing table" ip route
collect "33_arp" "ARP cache" ip neigh
collect "34_dns" "DNS config" cat /etc/resolv.conf
collect "35_iptables" "Firewall rules" iptables -L -n -v
# File system
collect "40_mounts" "Mounted filesystems" mount
collect "41_recent_files" "Files modified <24h" \
find /tmp /var/tmp /dev/shm -mtime -1 -ls
# Scheduled tasks
collect "50_crontab" "Root crontab" crontab -l
collect "51_cron_dirs" "Cron directories" ls -la /etc/cron.*
echo ""
echo "Generating checksums for chain of custody..."
cd "$EVIDENCE_DIR"
sha256sum *.txt > checksums.sha256
echo " [OK] $(wc -l < checksums.sha256) files hashed"
# Package evidence
tar -czf "/tmp/${CASE_ID}.tar.gz" -C /tmp "$CASE_ID"
echo ""
echo "Evidence package: /tmp/${CASE_ID}.tar.gz"
echo "SHA256: $(sha256sum "/tmp/${CASE_ID}.tar.gz" | cut -d' ' -f1)"
echo ""
echo "=== Collection Complete ==="
- Collect volatile data first (memory, processes, connections) before disk data
- Never run tools from the compromised system; use trusted binaries from a USB
- Hash everything for chain-of-custody and legal admissibility
- Document your actions with timestamps; you may need to testify
CI/CD Deployment Script with Rollback
#!/bin/bash
# deploy.sh - CI/CD Deployment with Rollback
# Usage: ./deploy.sh <version>
set -uo pipefail
VERSION="${1:?Usage: $0 <version>}"
APP_DIR="/var/www/app"
BACKUP_DIR="/var/www/backups"
HEALTH_URL="http://localhost:3000/health"
MAX_HEALTH_CHECKS=10
LOG="/var/log/deploy.log"
log() { echo "[$(date '+%F %T')] $1" | tee -a "$LOG"; }
fail() { log "FATAL: $1"; exit 1; }
# Pre-flight Checks
log "=== Deploying version $VERSION ==="
[[ -d "$APP_DIR" ]] || fail "App directory not found"
[[ -f "releases/${VERSION}.tar.gz" ]] || fail "Release artifact not found"
# Backup Current Version
current_ver=$(cat "$APP_DIR/VERSION" 2>/dev/null || echo "unknown")
log "Backing up current version: $current_ver"
mkdir -p "$BACKUP_DIR"
cp -a "$APP_DIR" "${BACKUP_DIR}/rollback_${current_ver}_$(date +%s)"
# Deploy New Version
log "Deploying version $VERSION..."
tar -xzf "releases/${VERSION}.tar.gz" -C "$APP_DIR" --strip-components=1
echo "$VERSION" > "$APP_DIR/VERSION"
# Restart application
log "Restarting application..."
systemctl restart app 2>/dev/null || pm2 restart app 2>/dev/null
# Health Check
log "Running health checks..."
healthy=false
for ((i=1; i<=MAX_HEALTH_CHECKS; i++)); do
sleep 3
status=$(curl -sf -o /dev/null -w "%{http_code}" \
--max-time 5 "$HEALTH_URL" 2>/dev/null || echo "000")
if [[ "$status" == "200" ]]; then
healthy=true
log "Health check $i/$MAX_HEALTH_CHECKS: PASS"
break
fi
log "Health check $i/$MAX_HEALTH_CHECKS: FAIL (HTTP $status)"
done
if $healthy; then
log "[OK] Deployment successful! Version $VERSION is live."
# Clean old backups (keep last 5)
ls -dt "${BACKUP_DIR}"/rollback_* 2>/dev/null | \
tail -n +6 | xargs rm -rf
exit 0
fi
# Rollback
log "[ERROR] Health checks failed, initiating rollback."
latest_backup=$(ls -dt "${BACKUP_DIR}"/rollback_* | head -1)
if [[ -d "$latest_backup" ]]; then
rm -rf "$APP_DIR"
cp -a "$latest_backup" "$APP_DIR"
systemctl restart app 2>/dev/null || pm2 restart app 2>/dev/null
log "Rolled back to: $current_ver"
else
fail "No backup found for rollback!"
fi
exit 1
- Always backup before deploying; never skip this step
- Implement health checks with retry logic after every deployment
- Automatic rollback prevents extended outages from bad deploys
- Keep deployment logs for audit and debugging purposes
Real Use Cases
The scripts above map directly to real responsibilities in cybersecurity and DevOps roles:
SOC Analyst
Automate log monitoring, detect anomalies, collect incident response data, and generate security reports.
Penetration Tester
Build lightweight recon tools for restricted environments where standard toolkits aren't available.
DevOps Engineer
Automate deployments with health checks and rollback, manage infrastructure, and monitor services.
Compliance Officer
Audit user accounts, firewall rules, and SSL certificates for ISO 27001 and SOC 2 compliance.
SRE / Platform Engineer
Build self-healing systems with automated health monitoring, log rotation, and backup verification.
Cybersecurity Student
Practice real-world scripting challenges that mirror actual job responsibilities and interview questions.