What You'll Build
In this exercise you will build `cricket_inventory.sh` — a production-grade system inventory script that an operations engineer can run on any unfamiliar Linux server to produce a structured, timestamped report about the server's identity, resource state, and application health. This is exactly the kind of script that DevOps and SRE teams maintain for onboarding new servers and performing pre-deployment checks.
The script will identify the Linux distribution and kernel version, report CPU and memory utilisation, enumerate mounted filesystems with threshold-based warnings, list listening network ports, and check for CricketPulse application artifacts in their FHS-correct locations. Every section uses the commands and concepts from Lessons 01–03 in a realistic operational context.
By the end of this exercise you will have practised filesystem navigation, environment variable inspection, process checking, and threshold logic — not as isolated commands but as components of a coherent diagnostic tool that solves a real problem that every DevOps team faces.
Prerequisites
- Completion of Lessons 01–03: understanding of Linux purpose, shell types, FHS directory structure, and navigation commands.
- A running Linux system — Ubuntu 20.04 or later recommended — either a local VM, WSL2, or a cloud instance (AWS/GCP/Azure free tier works).
- Basic ability to open a terminal and run simple commands like `ls`, `pwd`, and `echo`.
- A text editor accessible from the terminal — `nano` is sufficient; `vim` or VS Code remote also work.
- Sudo access on the target system for commands requiring elevated privileges such as network port inspection.
Setup & Project Structure
This exercise requires only a text editor and a Linux shell — no additional software needs to be installed. You will create a single script file called `cricket_inventory.sh` in a dedicated exercise directory, make it executable, and run it directly. The script is built incrementally across four steps — each step adds one report section and introduces the specific commands used to gather that information.
After each step, run the partial script and verify its output before moving to the next step. This incremental approach builds confidence that each section works correctly before adding complexity — the same practice used when writing production scripts, where you validate each component before composing them.
# Create the project directory and script file
mkdir -p ~/devops_exercises/linux_module1
cd ~/devops_exercises/linux_module1
# Create the script with correct permissions from the start
touch cricket_inventory.sh
chmod 755 cricket_inventory.sh
# Verify executable bit is set
ls -la cricket_inventory.sh
# Expected: -rwxr-xr-x 1 youruser yourgroup 0 [date] cricket_inventory.sh
# Open in your editor
nano cricket_inventory.sh
Step 1 — Foundation: Identity and Distribution
Step 1 establishes the script's structure and collects the most fundamental information: who the server is and what it runs. Every subsequent diagnostic decision depends on knowing the distribution — which package manager is available, which init system manages services, and which paths to expect for configuration and logs.
This step introduces the script's defensive header (`set -euo pipefail`), the output formatting functions that will be reused throughout, and the commands for reading system identity from `/etc/os-release` and `uname`. Understanding `uname -r` (kernel version) matters because kernel bugs and security patches are version-specific — documenting it is the first thing a support engineer asks for during a kernel-level issue.
#!/bin/bash
# cricket_inventory.sh — Step 1: System Identity
set -euo pipefail
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
REPORT_FILE="/tmp/inventory_$(hostname)_$(date '+%Y%m%d_%H%M%S').txt"
# Reusable formatting functions
section() { echo; echo "════════════════════════"; echo " $1"; echo "════════════════════════"; }
info() { echo " ✓ $1"; }
warn() { echo " ⚠ WARNING: $1"; }
{
echo "CricketPulse Server Inventory — $TIMESTAMP"
echo "Hostname: $(hostname -f)"
section "SYSTEM IDENTITY"
if [ -f /etc/os-release ]; then
. /etc/os-release # Source to get variables
info "Distribution: ${PRETTY_NAME}"
info "Version ID: ${VERSION_ID:-unknown}"
else
warn "/etc/os-release not found — cannot identify distribution"
fi
info "Kernel: $(uname -r)"
info "Architecture: $(uname -m)"
info "Boot time: $(uptime -s 2>/dev/null || echo 'unavailable')"
info "Uptime: $(uptime -p 2>/dev/null || uptime | awk '{print $3, $4}' | tr -d ',')"
} | tee "$REPORT_FILE"
echo "Report: $REPORT_FILE"
Step 2 — Core Logic: Resources and Filesystem
Step 2 extends the script to collect resource utilisation and filesystem state. CPU and memory information are drawn from `/proc/cpuinfo` and `/proc/meminfo` — virtual files created by the kernel that expose system state as readable text. Nearly every metric that Prometheus node_exporter exposes ultimately comes from reading files in `/proc`.
Filesystem usage is collected with `df -h`, filtered to real filesystems, with a threshold check that marks any filesystem above 80% utilisation as a warning. This threshold logic is the same pattern that production monitoring systems like Grafana alerting implement — by building it into the inventory script, you practise thinking in operational thresholds rather than raw values.
# Step 2 additions — append inside the report block
section "CPU & MEMORY"
CPU_COUNT=$(grep -c '^processor' /proc/cpuinfo)
CPU_MODEL=$(grep 'model name' /proc/cpuinfo | head -1 | cut -d: -f2 | xargs)
info "CPU cores: $CPU_COUNT"
info "CPU model: $CPU_MODEL"
MEM_TOTAL_KB=$(grep '^MemTotal:' /proc/meminfo | awk '{print $2}')
MEM_AVAIL_KB=$(grep '^MemAvailable:' /proc/meminfo | awk '{print $2}')
MEM_TOTAL_GB=$(echo "scale=1; $MEM_TOTAL_KB/1048576" | bc)
MEM_USED_PCT=$(echo "scale=0; (($MEM_TOTAL_KB - $MEM_AVAIL_KB) * 100) / $MEM_TOTAL_KB" | bc)
info "Total RAM: ${MEM_TOTAL_GB}GB"
if [ "$MEM_USED_PCT" -gt 85 ]; then
warn "Memory usage ${MEM_USED_PCT}% — HIGH"
else
info "Memory usage: ${MEM_USED_PCT}%"
fi
section "FILESYSTEM USAGE"
while IFS= read -r line; do
USE_PCT=$(echo "$line" | awk '{print $5}' | tr -d '%')
if [ "${USE_PCT:-0}" -gt 80 ]; then
warn "$line ← ABOVE 80%"
else
info "$line"
fi
done < <(df -h --output=source,size,used,avail,pcent,target | \
grep -v -E '^(tmpfs|devtmpfs|udev|Filesystem)')
section "INODE USAGE"
df -i --output=source,ipcent,target | grep -v -E '^(tmpfs|devtmpfs|Filesystem)' | \
while read -r line; do
PCT=$(echo "$line" | awk '{print $2}' | tr -d '%-')
if [ "${PCT:-0}" -gt 80 ] 2>/dev/null; then warn "$line ← HIGH INODES"
else info "$line"; fi
done