I automated routine Linux administration with Python, using psutil for resource metrics and subprocess for safe command execution. Scheduled health and cleanup scripts replaced manual checks and surfaced disk and memory pressure before it caused outages.

Objective & Context

Repetitive admin tasks are error-prone when done by hand. This lab builds idempotent Python scripts that gather metrics, act on thresholds, and run under systemd timers, the toolkit that underpins the IR automation and vulnerability labs.

Learning Objectives

  • Read CPU, memory, and disk metrics with psutil.
  • Run shell commands safely with subprocess (no shell=True).
  • Write alerting logic with thresholds and notifications.
  • Schedule scripts with systemd timer and service units.
  • Handle permissions correctly for privileged checks.

Environment & Prerequisites

Prerequisites

  • A Linux host with Python 3.11 and psutil (pip install psutil)
  • systemd for scheduling (timer + service unit); root for enabling the timer
  • Permissions appropriate to the resources being managed
  • Assumed skill: basic Linux commands and Python functions

Step-by-Step Execution

1. Gather metrics with psutil

import psutil

def alert(disk, mem):
    print(f"ALERT: disk {disk}% mem {mem}%")

disk = psutil.disk_usage("/").percent
mem  = psutil.virtual_memory().percent
if disk > 85 or mem > 90:
    alert(disk, mem)

Expected output:

(no output; alert fires only over threshold)

What this teaches: psutil reads from /proc, so the check is cheap and dependency-free; thresholds turn raw metrics into actionable signals. The alert function is a placeholder, wire it to your real notification path (mail, webhook, pager). Save this as /opt/health/health.py for the timer step below.

2. Run a command safely with subprocess

python -c "import subprocess; print(subprocess.run(['df','-h','/'],capture_output=True,text=True).stdout)"

Expected output:

Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1 ...

What this teaches: Passing the command as a list avoids the shell entirely, which eliminates shell-injection risk from interpolated arguments.

3. Schedule via systemd timer [ROOT REQUIRED]

sudo tee /etc/systemd/system/health.service > /dev/null <<'EOF' [Unit] Description=System health check [Service] Type=oneshot ExecStart=/usr/bin/python3 /opt/health/health.py WorkingDirectory=/opt/health User=root EOF sudo tee /etc/systemd/system/health.timer > /dev/null <<'EOF' [Unit] Description=Run health check hourly [Timer] OnCalendar=hourly Persistent=true [Install] WantedBy=timers.target EOF

Expected output:

(no output; unit files written; needs sudo or a root shell)

What this teaches: The service unit pins the interpreter path, working directory, and user so the script runs the same from the timer as it does by hand. Writing under /etc/systemd/system requires root; use a ~/.config/systemd/user/ timer instead if you have no sudo on the box.

sudo systemctl daemon-reload && sudo systemctl enable --now health.timer && systemctl list-timers health.timer

Expected output:

NEXT                        UNIT          ACTIVATES
2026-06-17 10:00:00 UTC     health.timer  health.service

What this teaches: daemon-reload makes systemd pick up the new units, then the timer appearing in list-timers proves systemd accepted the schedule; enable --now both enables and starts it in one command.

How It Works

How It Works

psutil is a cross-platform /proc reader: it parses the same kernel interfaces that top and df use, but returns them as Python objects. That makes health checks a few lines of Python instead of fragile text parsing of command output.

subprocess.run with a list argument bypasses the shell, so arguments are passed literally to the program. shell=True reintroduces parsing, quoting, and injection hazards; the list form is the safe default for automation.

systemd timers replace cron for service-style scheduling: they support calendar and monotonic triggers, jitter, persistent catch-up after downtime, and their runs appear in the journal, which makes scheduled Python observable like any other service.

Validation & Testing

Fill a test filesystem past the threshold and confirm the script alerts and runs cleanup, then verify the timer fires on schedule. Pass criteria: threshold logic triggers correctly, subprocess uses a list (no shell injection), and the timer runs reliably.

Advanced: Troubleshooting
  • Shell injection risk: pass args as a list, never shell=True with user input.
  • Timer not firing: check systemctl status health.timer and journal logs.
  • Permission denied: run the service as a user with the needed access, not blanket root.

Troubleshooting

SymptomLikely causeFix
psutil raises AccessDeniedreading another user's process info without permissionrun as the owning user or root; scope checks to system-wide metrics instead
subprocess FileNotFoundErrorthe program is not on PATH in the timer's environmentuse absolute paths in scheduled scripts; systemd units get a minimal PATH
timer never firesthe unit was enabled but not started, or the calendar expression is wronguse enable --now and verify with systemctl list-timers; check systemd-analyze calendar
script works manually but fails from the timerenvironment differences: cwd, env vars, or userset WorkingDirectory= and User= in the service unit; log stderr to the journal
alerts fire constantly (flapping)threshold crossed repeatedly around the boundaryadd hysteresis or require N consecutive breaches before alerting

Key Results

  • Replaced manual checks with scheduled health monitoring.
  • Surfaced disk/memory pressure before it caused service impact.
  • Eliminated shell-injection risk via list-form subprocess calls.
  • Ran idempotent cleanup automatically under systemd timers.

Key Takeaways

  • psutil turns /proc into Python objects: no text parsing needed.
  • Always pass subprocess commands as lists; avoid shell=True.
  • Thresholds plus alerting turn metrics into action.
  • systemd timers make scheduled scripts observable via the journal.
  • Scheduled environments are minimal: use absolute paths and explicit units.

Practice Challenges

Challenge 1: Hysteresis alerter

Goal: Extend the disk check to alert only after three consecutive breaches and clear after three consecutive recoveries.

Hint: Keep a small state file or counter between runs; the timer invokes the script each time.

Challenge 2: Journal-friendly script

Goal: Make the script log structured key=value lines so journalctl queries can filter by metric.

Hint: Print to stdout from the service unit; systemd captures it into the journal automatically.