729 lines
74 KiB
JSON
729 lines
74 KiB
JSON
{
|
|
"meta": {
|
|
"version": "5.1",
|
|
"title": "Linux Operations Learning Lab",
|
|
"author": "Codex",
|
|
"updated": "2026-03-19",
|
|
"description": "A rebuilt Linux learning path that moves from command basics to real operations troubleshooting with sandbox exercises.",
|
|
"module_count": 10,
|
|
"total_lessons": 30,
|
|
"total_exercises": 106,
|
|
"pedagogy": "learning-first",
|
|
"orientation": "ops-workflow"
|
|
},
|
|
"modules": [
|
|
{
|
|
"id": "module_1_foundation",
|
|
"title": "Module 1: Build Linux spatial awareness",
|
|
"summary": "Start with location and listing so every later action has context.",
|
|
"lessons": [
|
|
{
|
|
"id": "m1_l1_pwd",
|
|
"title": "Locate yourself with pwd",
|
|
"goal": "Understand the current working directory before changing files or running scripts.",
|
|
"why_it_matters": "Many Linux mistakes come from running the right command in the wrong path.",
|
|
"concepts": ["Current working directory", "Absolute vs relative path", "Context before action"],
|
|
"command": "pwd",
|
|
"examples": ["pwd", "cd /tmp && pwd"],
|
|
"pitfalls": ["Assuming every shell opens in the same place", "Running a file command before checking the path"],
|
|
"scenarios": ["Verify where you are before editing a file", "Confirm location before launching a script"],
|
|
"troubleshooting_flow": ["Confirm the working context", "Run pwd", "Use the output to choose the next command"],
|
|
"related_commands": ["pwd", "cd", "ls"],
|
|
"classic_view": "Treat pwd as the command that restores orientation before every other action.",
|
|
"takeaways": ["Know your location before acting", "Read the full path instead of guessing", "Use location to decide the next safe step"],
|
|
"after_class": "Repeat pwd before every directory-changing command until it becomes automatic.",
|
|
"exercises": [
|
|
{ "id": "m1_l1_e1", "type": "understanding", "question": "Which command prints the full current working directory?", "answer": "pwd" },
|
|
{ "id": "m1_l1_e2", "type": "operation", "title": "Print the current working directory", "hint": "Run pwd directly.", "success_test": "cmd == 'pwd'", "solution": ["pwd"], "success_msg": "You verified the shell location correctly." },
|
|
{ "id": "m1_l1_e3", "type": "scenario", "question": "If a script fails with 'file not found', what should you check first?", "answer": "Run pwd and verify the current path before anything else." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m1_l2_ls",
|
|
"title": "Inspect a directory with ls",
|
|
"goal": "Observe directory content and switch to detail views when needed.",
|
|
"why_it_matters": "Linux exploration usually begins with ls because you need to know what exists before you inspect more deeply.",
|
|
"concepts": ["Directory contents", "Hidden files", "Long listing output"],
|
|
"command": "ls",
|
|
"examples": ["ls", "ls -la", "ls -l /etc"],
|
|
"pitfalls": ["Thinking a hidden file does not exist because plain ls missed it", "Reading long output without understanding permissions or size"],
|
|
"scenarios": ["Check what files exist before deleting anything", "Inspect a config directory for hidden dotfiles"],
|
|
"troubleshooting_flow": ["List the directory first", "Switch to ls -la if details matter", "Use the listing to choose the next file command"],
|
|
"related_commands": ["ls", "find", "stat"],
|
|
"classic_view": "Observation comes before diagnosis, and ls is usually the first observation tool.",
|
|
"takeaways": ["Use ls for broad visibility", "Use ls -la for hidden files and metadata", "Treat the listing as evidence"],
|
|
"after_class": "Practice comparing ls and ls -la until you can explain why the outputs differ.",
|
|
"exercises": [
|
|
{ "id": "m1_l2_e1", "type": "understanding", "question": "Why does ls -a reveal more than plain ls?", "answer": "Because it includes hidden dotfiles." },
|
|
{ "id": "m1_l2_e2", "type": "operation", "title": "List the current directory", "hint": "Start with ls.", "success_test": "cmd == 'ls'", "solution": ["ls"], "success_msg": "You listed the current directory." },
|
|
{ "id": "m1_l2_e3", "type": "operation", "title": "Show hidden files and details", "hint": "Use ls -la.", "success_test": "cmd == 'ls -la' or cmd == 'ls -al'", "solution": ["ls -la", "ls -al"], "success_msg": "You switched from a simple listing to a full inspection view." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m1_l3_cd_cat_echo",
|
|
"title": "Move, read, and confirm with cd, cat, and echo",
|
|
"goal": "Use navigation, file reads, and quick output together as one everyday workflow.",
|
|
"why_it_matters": "Many Linux tasks are just a chain of navigate, inspect, and confirm.",
|
|
"concepts": ["Changing directories", "Reading text files", "Using echo for quick verification"],
|
|
"command": "cd / cat / echo",
|
|
"examples": ["cd /tmp", "cat /etc/hosts", "echo Hello Linux"],
|
|
"pitfalls": ["Using cat on a directory", "Changing directories without verifying where you landed", "Ignoring echo as a debugging aid"],
|
|
"scenarios": ["Enter a workspace and inspect a config file", "Print quick checkpoints in a shell session"],
|
|
"troubleshooting_flow": ["Change to the right location", "Read the right file", "Echo the key assumption you want to verify"],
|
|
"related_commands": ["cd", "cat", "echo", "pwd"],
|
|
"classic_view": "These commands make context and state visible before you do more powerful operations.",
|
|
"takeaways": ["Use cd intentionally", "Use cat for quick text inspection", "Use echo to make invisible state explicit"],
|
|
"after_class": "Practice saying out loud what each command changed in your working context.",
|
|
"exercises": [
|
|
{ "id": "m1_l3_e1", "type": "operation", "title": "Enter /tmp", "hint": "Use cd /tmp.", "success_test": "cmd == 'cd /tmp' and cwd == '/tmp'", "solution": ["cd /tmp"], "success_msg": "You moved to the target working directory." },
|
|
{ "id": "m1_l3_e2", "type": "operation", "title": "Read the hosts file", "hint": "Run cat /etc/hosts.", "success_test": "cmd == 'cat /etc/hosts' and 'localhost' in output", "solution": ["cat /etc/hosts"], "success_msg": "You inspected a real text file in the sandbox." },
|
|
{ "id": "m1_l3_e3", "type": "operation", "title": "Print a confirmation message", "hint": "Run echo Hello Linux.", "success_test": "cmd == 'echo Hello Linux' and 'Hello Linux' in output", "solution": ["echo Hello Linux"], "success_msg": "You used echo to make the current step explicit." }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "module_2_filesystem",
|
|
"title": "Module 2: Manipulate files safely",
|
|
"summary": "Learn to create, copy, move, and permission files without losing context.",
|
|
"lessons": [
|
|
{
|
|
"id": "m2_l1_create",
|
|
"title": "Create structure with mkdir and touch",
|
|
"goal": "Build directories and empty files quickly so you can prepare workspaces or examples.",
|
|
"why_it_matters": "Project setup and environment preparation often begin with directory scaffolding.",
|
|
"concepts": ["Directory creation", "Recursive directory creation", "Empty file creation"],
|
|
"command": "mkdir / touch",
|
|
"examples": ["mkdir /tmp/lab", "mkdir -p /tmp/lab/data/raw", "touch /tmp/lab/notes.txt"],
|
|
"pitfalls": ["Forgetting -p for nested paths", "Trying to touch a file under a missing directory"],
|
|
"scenarios": ["Prepare a clean lab directory", "Create a placeholder log or notes file"],
|
|
"troubleshooting_flow": ["Check the target path", "Create directories first", "Create files after the path exists"],
|
|
"related_commands": ["mkdir", "touch", "ls"],
|
|
"classic_view": "File-system work is safer when you change one layer at a time: path first, file second.",
|
|
"takeaways": ["Use mkdir for structure", "Use touch for placeholders", "Think about parent directories before file creation"],
|
|
"after_class": "Rebuild the same directory tree twice until the path feels natural.",
|
|
"exercises": [
|
|
{ "id": "m2_l1_e1", "type": "operation", "title": "Create a lab directory", "hint": "Run mkdir /tmp/lab.", "success_test": "cmd == 'mkdir /tmp/lab' and exists('/tmp/lab')", "solution": ["mkdir /tmp/lab"], "success_msg": "Your lab directory now exists." },
|
|
{ "id": "m2_l1_e2", "type": "operation", "title": "Create a nested directory tree", "hint": "Run mkdir -p /tmp/lab/data/raw.", "success_test": "cmd == 'mkdir -p /tmp/lab/data/raw' and exists('/tmp/lab/data/raw')", "solution": ["mkdir -p /tmp/lab/data/raw"], "success_msg": "You created the nested path correctly." },
|
|
{ "id": "m2_l1_e3", "type": "operation", "title": "Create a note file", "hint": "Run touch /tmp/lab/notes.txt.", "success_test": "cmd == 'touch /tmp/lab/notes.txt' and exists('/tmp/lab/notes.txt')", "solution": ["touch /tmp/lab/notes.txt"], "success_msg": "You created a new empty file in the lab path." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m2_l2_copy_move_permission",
|
|
"title": "Copy, move, and permission files with intent",
|
|
"goal": "Manipulate files carefully and connect permission changes to execution behavior.",
|
|
"why_it_matters": "Safe Linux work depends on knowing when you are backing up, renaming, or changing access.",
|
|
"concepts": ["Backup copies", "Rename vs move", "Permission modes"],
|
|
"command": "cp / mv / chmod / stat",
|
|
"examples": ["cp /etc/hosts /tmp/hosts.bak", "mv /tmp/hosts.bak /tmp/hosts.backup", "chmod 755 /tmp/lab", "stat /etc/hosts"],
|
|
"pitfalls": ["Deleting before backing up", "Changing permissions without checking metadata first"],
|
|
"scenarios": ["Back up a config before editing", "Make a script or directory executable when needed"],
|
|
"troubleshooting_flow": ["Copy before editing", "Rename with a purpose", "Inspect metadata before permission changes"],
|
|
"related_commands": ["cp", "mv", "chmod", "stat"],
|
|
"classic_view": "File operations are safe when you can clearly name the step: backup, rename, inspect, or permission change.",
|
|
"takeaways": ["Back up first", "Rename with purpose", "Inspect before changing permissions"],
|
|
"after_class": "Repeat the flow copy -> rename -> stat -> chmod on a temporary file until the sequence feels obvious.",
|
|
"exercises": [
|
|
{ "id": "m2_l2_e1", "type": "operation", "title": "Back up the hosts file", "hint": "Run cp /etc/hosts /tmp/hosts.bak.", "success_test": "cmd == 'cp /etc/hosts /tmp/hosts.bak' and exists('/tmp/hosts.bak')", "solution": ["cp /etc/hosts /tmp/hosts.bak"], "success_msg": "You created a backup copy before any change." },
|
|
{ "id": "m2_l2_e2", "type": "operation", "title": "Rename the backup file", "hint": "Run mv /tmp/hosts.bak /tmp/hosts.backup.", "success_test": "cmd == 'mv /tmp/hosts.bak /tmp/hosts.backup' and exists('/tmp/hosts.backup')", "solution": ["mv /tmp/hosts.bak /tmp/hosts.backup"], "success_msg": "You renamed the file successfully." },
|
|
{ "id": "m2_l2_e3", "type": "operation", "title": "Inspect hosts metadata", "hint": "Run stat /etc/hosts.", "success_test": "cmd == 'stat /etc/hosts' and 'File:' in output", "solution": ["stat /etc/hosts"], "success_msg": "You inspected the file metadata successfully." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m2_l3_cleanup_and_modes",
|
|
"title": "Clean up and change modes with rm and chmod",
|
|
"goal": "Delete temporary artifacts safely and connect permission changes to execution behavior.",
|
|
"why_it_matters": "Cleanup and permission changes are common, but both can create damage if you skip inspection.",
|
|
"concepts": ["Cleanup order", "Symbolic vs numeric modes", "Execution permission"],
|
|
"command": "rm / chmod",
|
|
"examples": ["rm /tmp/hosts.backup", "chmod 755 /tmp/lab", "chmod +x /tmp/lab/notes.txt"],
|
|
"pitfalls": ["Deleting before confirming the file is disposable", "Changing permissions without knowing why"],
|
|
"scenarios": ["Remove temporary files after verification", "Make a script or directory executable when needed"],
|
|
"troubleshooting_flow": ["Verify the target path", "Remove only temporary files", "Inspect and then change permissions with intent"],
|
|
"related_commands": ["rm", "chmod", "stat", "ls"],
|
|
"classic_view": "Cleanup and permission work should be deliberate, not reactive.",
|
|
"takeaways": ["Delete last", "Use numeric and symbolic modes intentionally", "Connect permissions to a real need"],
|
|
"after_class": "Repeat the sequence inspect -> remove or inspect -> chmod until it feels like a safe habit.",
|
|
"exercises": [
|
|
{ "id": "m2_l3_e1", "type": "operation", "title": "Remove the renamed backup", "hint": "Run rm /tmp/hosts.backup.", "success_test": "cmd == 'rm /tmp/hosts.backup' and not exists('/tmp/hosts.backup')", "solution": ["rm /tmp/hosts.backup"], "success_msg": "You cleaned up the temporary backup safely." },
|
|
{ "id": "m2_l3_e2", "type": "operation", "title": "Set the lab directory mode to 755", "hint": "Run chmod 755 /tmp/lab.", "success_test": "cmd == 'chmod 755 /tmp/lab'", "solution": ["chmod 755 /tmp/lab"], "success_msg": "You applied a numeric permission change." },
|
|
{ "id": "m2_l3_e3", "type": "operation", "title": "Add execute permission to notes.txt", "hint": "Run chmod +x /tmp/lab/notes.txt.", "success_test": "cmd == 'chmod +x /tmp/lab/notes.txt'", "solution": ["chmod +x /tmp/lab/notes.txt"], "success_msg": "You used a symbolic permission change." }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "module_3_search",
|
|
"title": "Module 3: Search and preview with evidence",
|
|
"summary": "Move from a broad search to a precise answer using filters and short file views.",
|
|
"lessons": [
|
|
{
|
|
"id": "m3_l1_grep",
|
|
"title": "Search file content with grep",
|
|
"goal": "Find relevant lines quickly instead of reading an entire file every time.",
|
|
"why_it_matters": "Operations work often means locating one useful line inside a noisy file.",
|
|
"concepts": ["Pattern search", "Case-insensitive search", "Line-oriented inspection"],
|
|
"command": "grep",
|
|
"examples": ["grep sandbox_user /etc/passwd", "grep -i error /var/log/syslog"],
|
|
"pitfalls": ["Searching too broadly and ignoring context", "Forgetting case-insensitive mode"],
|
|
"scenarios": ["Find one user entry in passwd", "Locate error lines in a log"],
|
|
"troubleshooting_flow": ["Choose the right file first", "Search for the smallest useful keyword", "Use the matched lines to decide the next command"],
|
|
"related_commands": ["grep", "cat", "tail"],
|
|
"classic_view": "grep turns a large text space into a small set of signals you can reason about.",
|
|
"takeaways": ["Search deliberately", "Narrow the signal before changing anything", "Use matched lines as evidence"],
|
|
"after_class": "Practice moving from a broad keyword to a more precise keyword in the same file.",
|
|
"exercises": [
|
|
{ "id": "m3_l1_e1", "type": "operation", "title": "Find sandbox_user in passwd", "hint": "Run grep sandbox_user /etc/passwd.", "success_test": "cmd == 'grep sandbox_user /etc/passwd' and 'sandbox_user' in output", "solution": ["grep sandbox_user /etc/passwd"], "success_msg": "You extracted the relevant passwd line." },
|
|
{ "id": "m3_l1_e2", "type": "operation", "title": "Find error lines in syslog", "hint": "Run grep -i error /var/log/syslog.", "success_test": "cmd == 'grep -i error /var/log/syslog' and 'error' in output.lower()", "solution": ["grep -i error /var/log/syslog"], "success_msg": "You filtered the log down to the error lines." },
|
|
{ "id": "m3_l1_e3", "type": "scenario", "question": "Why is grep usually faster than reading the whole log manually?", "answer": "Because it narrows the file to only the lines that match the signal you care about." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m3_l2_find_preview",
|
|
"title": "Locate files with find and preview them with tail",
|
|
"goal": "Move from path discovery to a focused log or config preview.",
|
|
"why_it_matters": "You cannot inspect the right file until you locate it precisely, and you do not need to open the whole file first.",
|
|
"concepts": ["Name-based search", "Search roots", "Tail as a recent-state preview"],
|
|
"command": "find / tail",
|
|
"examples": ["find /etc -name '*.conf'", "find /var/log -type f", "tail -n 1 /var/log/auth.log"],
|
|
"pitfalls": ["Searching from too high in the tree", "Using cat on a large file when tail would answer faster"],
|
|
"scenarios": ["Find config files under /etc", "Inspect the newest auth log line"],
|
|
"troubleshooting_flow": ["Pick the smallest reasonable root", "Add a filter", "Preview the most relevant file segment"],
|
|
"related_commands": ["find", "tail", "head"],
|
|
"classic_view": "A good search ends with an exact path and a small, readable preview.",
|
|
"takeaways": ["Use find for precise location", "Use tail for fresh log evidence", "Reduce noise early"],
|
|
"after_class": "Practice explaining the root path and every filter before you run find.",
|
|
"exercises": [
|
|
{ "id": "m3_l2_e1", "type": "operation", "title": "Find configuration files under /etc", "hint": "Run find /etc -name '*.conf'.", "success_test": "cmd == \"find /etc -name '*.conf'\" and '.conf' in output", "solution": ["find /etc -name '*.conf'"], "success_msg": "You located configuration files recursively." },
|
|
{ "id": "m3_l2_e2", "type": "operation", "title": "Find log files under /var/log", "hint": "Run find /var/log -type f.", "success_test": "cmd == 'find /var/log -type f' and '/var/log' in output", "solution": ["find /var/log -type f"], "success_msg": "You filtered the search to files under the log tree." },
|
|
{ "id": "m3_l2_e3", "type": "operation", "title": "Read the newest auth log line", "hint": "Run tail -n 1 /var/log/auth.log.", "success_test": "cmd == 'tail -n 1 /var/log/auth.log' and 'sandbox_user' in output", "solution": ["tail -n 1 /var/log/auth.log"], "success_msg": "You previewed the most recent auth log line." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m3_l3_preview_and_pipeline",
|
|
"title": "Preview files with head, tail, and a simple pipeline",
|
|
"goal": "Read only the part of a file that answers your current question.",
|
|
"why_it_matters": "Large files are easier to scan when you only open the beginning or end first.",
|
|
"concepts": ["Top lines", "Bottom lines", "Simple pipelines for narrowing output"],
|
|
"command": "head / tail / cat | tail",
|
|
"examples": ["head /etc/passwd", "tail -n 1 /var/log/auth.log", "cat /var/log/auth.log | tail -n 1"],
|
|
"pitfalls": ["Using cat on a large file when a preview is enough", "Forgetting to change the line count for a focused question"],
|
|
"scenarios": ["Check whether a file starts with the right structure", "Inspect the last auth event in a log"],
|
|
"troubleshooting_flow": ["Choose the right end of the file", "Preview a small number of lines", "Switch to grep or full reads only if needed"],
|
|
"related_commands": ["head", "tail", "cat", "grep"],
|
|
"classic_view": "Preview commands reduce noise so you can decide whether the file deserves deeper inspection.",
|
|
"takeaways": ["Use head for structure", "Use tail for recent activity", "Use a pipeline when one command is not enough"],
|
|
"after_class": "Practice explaining why you chose head or tail before you run the command.",
|
|
"exercises": [
|
|
{ "id": "m3_l3_e1", "type": "operation", "title": "Preview the top of passwd", "hint": "Run head /etc/passwd.", "success_test": "cmd == 'head /etc/passwd' and 'root' in output", "solution": ["head /etc/passwd"], "success_msg": "You previewed the beginning of the file." },
|
|
{ "id": "m3_l3_e2", "type": "operation", "title": "Inspect the newest auth log line", "hint": "Run tail -n 1 /var/log/auth.log.", "success_test": "cmd == 'tail -n 1 /var/log/auth.log' and 'sandbox_user' in output", "solution": ["tail -n 1 /var/log/auth.log"], "success_msg": "You narrowed the file to the most recent line." },
|
|
{ "id": "m3_l3_e3", "type": "operation", "title": "Use a simple pipeline to preview auth.log", "hint": "Run cat /var/log/auth.log | tail -n 1.", "success_test": "cmd == 'cat /var/log/auth.log | tail -n 1' and 'sandbox_user' in output", "solution": ["cat /var/log/auth.log | tail -n 1"], "success_msg": "You combined two commands to narrow the log quickly." }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "module_4_operations",
|
|
"title": "Module 4: Turn commands into operations workflows",
|
|
"summary": "Use process and network commands as parts of a real troubleshooting chain instead of isolated tools.",
|
|
"lessons": [
|
|
{
|
|
"id": "m4_l1_process",
|
|
"title": "Read system pressure with top and ps",
|
|
"goal": "Spot busy processes and connect process views to system pressure.",
|
|
"why_it_matters": "Incidents often begin with 'the system feels slow', and process views turn that feeling into evidence.",
|
|
"concepts": ["Process snapshots", "CPU-heavy processes", "Live system views"],
|
|
"command": "top / ps",
|
|
"examples": ["top", "ps aux --sort=-%cpu | head"],
|
|
"pitfalls": ["Jumping straight to kill before understanding the process role", "Looking at one snapshot without connecting it to symptoms"],
|
|
"scenarios": ["Find the hottest process when CPU looks high", "Explain which command gives the faster broad view"],
|
|
"troubleshooting_flow": ["Look at top for the broad picture", "Use ps sorting to confirm the hottest process", "Only then decide the next action"],
|
|
"related_commands": ["top", "ps", "kill"],
|
|
"classic_view": "System pressure is easier to reason about when you move from a broad live view to a specific process list.",
|
|
"takeaways": ["Use top for the live picture", "Use ps for a sortable snapshot", "Diagnose before you terminate anything"],
|
|
"after_class": "Compare top and ps side by side and explain what each one is better at.",
|
|
"exercises": [
|
|
{ "id": "m4_l1_e1", "type": "operation", "title": "Show a live overview", "hint": "Run top.", "success_test": "cmd == 'top' and 'load average' in output", "solution": ["top"], "success_msg": "You captured a live system overview." },
|
|
{ "id": "m4_l1_e2", "type": "operation", "title": "List processes by CPU usage", "hint": "Run ps aux --sort=-%cpu | head.", "success_test": "cmd == 'ps aux --sort=-%cpu | head' and '%CPU' in output", "solution": ["ps aux --sort=-%cpu | head"], "success_msg": "You ranked processes by CPU usage." },
|
|
{ "id": "m4_l1_e3", "type": "understanding", "question": "Why should kill come after observation rather than before it?", "answer": "Because you need evidence about what the process is doing and whether it is safe to stop." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m4_l2_network",
|
|
"title": "Check the network path with ip, ping, ss, and curl",
|
|
"goal": "Diagnose connectivity step by step from interface state to application response.",
|
|
"why_it_matters": "Network incidents feel confusing when you mix layers; they become clearer when you inspect one layer at a time.",
|
|
"concepts": ["Interface address", "Basic connectivity", "Listening ports", "Application-level validation"],
|
|
"command": "ip / ping / ss / curl",
|
|
"examples": ["ip addr", "ping 127.0.0.1", "ss -ltnp", "curl http://127.0.0.1"],
|
|
"pitfalls": ["Treating DNS, port, and app failures as one category", "Checking a port without making a real request"],
|
|
"scenarios": ["A local service looks up but the app still feels unavailable", "You need to confirm whether the issue is address, reachability, port, or HTTP behavior"],
|
|
"troubleshooting_flow": ["Check interface state with ip addr", "Test connectivity with ping", "Check sockets with ss", "Validate the app layer with curl"],
|
|
"related_commands": ["ip", "ping", "ss", "curl", "dig"],
|
|
"classic_view": "The cleanest network diagnosis moves from low-level reachability to the final application response.",
|
|
"takeaways": ["Do not skip layers", "Confirm a port before blaming the app", "Use curl to verify user-facing behavior"],
|
|
"after_class": "Repeat the sequence ip -> ping -> ss -> curl until the layer transitions feel natural.",
|
|
"exercises": [
|
|
{ "id": "m4_l2_e1", "type": "operation", "title": "Inspect interface addresses", "hint": "Run ip addr.", "success_test": "cmd == 'ip addr' and 'inet' in output", "solution": ["ip addr"], "success_msg": "You confirmed interface and address information." },
|
|
{ "id": "m4_l2_e2", "type": "operation", "title": "Test local connectivity", "hint": "Run ping 127.0.0.1.", "success_test": "cmd == 'ping 127.0.0.1' and 'packet loss' in output", "solution": ["ping 127.0.0.1"], "success_msg": "You verified the local connectivity layer." },
|
|
{ "id": "m4_l2_e3", "type": "operation", "title": "Validate the application response", "hint": "Run curl http://127.0.0.1.", "success_test": "cmd == 'curl http://127.0.0.1' and 'hello localhost' in output", "solution": ["curl http://127.0.0.1"], "success_msg": "You completed the network path all the way to the application layer." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m4_l3_service",
|
|
"title": "Trace service health with systemctl and journalctl",
|
|
"goal": "Move from service state to recent logs in a deliberate sequence.",
|
|
"why_it_matters": "Service incidents are easier to solve when you separate service state from application logs.",
|
|
"concepts": ["Service unit state", "Recent journal lines", "From status to explanation"],
|
|
"command": "systemctl / journalctl",
|
|
"examples": ["systemctl status nginx", "journalctl -n 20", "systemctl restart nginx"],
|
|
"pitfalls": ["Restarting a service without checking why it failed", "Reading status without following the logs"],
|
|
"scenarios": ["A service is reported down or unstable", "You need the last error before considering a restart"],
|
|
"troubleshooting_flow": ["Read service state with systemctl status", "Open the recent journal lines", "Use the message to guide the next repair"],
|
|
"related_commands": ["systemctl", "journalctl", "ss"],
|
|
"classic_view": "Service status tells you what is happening; logs tell you why.",
|
|
"takeaways": ["Start with status", "Use logs for explanation", "Restart only after you understand the failure"],
|
|
"after_class": "Say out loud what systemctl tells you that journalctl does not, and vice versa.",
|
|
"exercises": [
|
|
{ "id": "m4_l3_e1", "type": "operation", "title": "Check nginx service status", "hint": "Run systemctl status nginx.", "success_test": "cmd == 'systemctl status nginx' and 'Active:' in output", "solution": ["systemctl status nginx"], "success_msg": "You inspected the service state." },
|
|
{ "id": "m4_l3_e2", "type": "operation", "title": "Read recent journal lines", "hint": "Run journalctl -n 20.", "success_test": "cmd == 'journalctl -n 20' and 'Started nginx.service' in output", "solution": ["journalctl -n 20"], "success_msg": "You pulled recent journal evidence." },
|
|
{ "id": "m4_l3_e3", "type": "operation", "title": "Simulate a service restart command", "hint": "Run systemctl restart nginx.", "success_test": "cmd == 'systemctl restart nginx' and 'completed for nginx' in output", "solution": ["systemctl restart nginx"], "success_msg": "You practiced the restart command after inspection." }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "module_5_incidents",
|
|
"title": "Module 5: Practice incident playbooks",
|
|
"summary": "Use the commands you learned in layered incident drills so the tools feel connected to real operations work.",
|
|
"lessons": [
|
|
{
|
|
"id": "m5_l1_disk",
|
|
"title": "Incident drill: investigate disk pressure",
|
|
"goal": "Move from broad disk visibility to specific directory evidence.",
|
|
"why_it_matters": "Disk pressure can break writes, logs, and services, so you need a repeatable inspection path.",
|
|
"concepts": ["Filesystem usage", "Directory usage", "Large-file hunting"],
|
|
"command": "df / du / find",
|
|
"examples": ["df -h", "du -sh /var/log", "find /var/log -type f"],
|
|
"pitfalls": ["Stopping at df without drilling into the heavy directory", "Deleting files before confirming what they are for"],
|
|
"scenarios": ["A host reports that disk usage is full", "A log directory appears to be growing too quickly"],
|
|
"troubleshooting_flow": ["Confirm which filesystem is full", "Measure the likely heavy directory", "Locate concrete files before deciding cleanup"],
|
|
"related_commands": ["df", "du", "find", "sort"],
|
|
"classic_view": "Disk incidents are easier when you move from filesystem view to directory view to file view in order.",
|
|
"takeaways": ["Use df for the broad picture", "Use du for directory evidence", "Use find before deleting anything"],
|
|
"after_class": "Practice saying which layer each command belongs to: filesystem, directory, or file.",
|
|
"exercises": [
|
|
{ "id": "m5_l1_e1", "type": "operation", "title": "Check filesystem usage", "hint": "Run df -h.", "success_test": "cmd == 'df -h' and 'Filesystem' in output", "solution": ["df -h"], "success_msg": "You captured the broad disk usage picture." },
|
|
{ "id": "m5_l1_e2", "type": "operation", "title": "Measure /var/log usage", "hint": "Run du -sh /var/log.", "success_test": "cmd == 'du -sh /var/log' and '/var/log' in output", "solution": ["du -sh /var/log"], "success_msg": "You narrowed the issue to a directory-level view." },
|
|
{ "id": "m5_l1_e3", "type": "operation", "title": "Locate log files under /var/log", "hint": "Run find /var/log -type f.", "success_test": "cmd == 'find /var/log -type f' and '/var/log' in output", "solution": ["find /var/log -type f"], "success_msg": "You drilled down to the file layer." },
|
|
{ "id": "m5_l1_e4", "type": "scenario", "question": "Why is deleting files before directory and file inspection risky?", "answer": "Because you can remove useful evidence or the wrong data before you understand what is consuming space." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m5_l2_auth",
|
|
"title": "Incident drill: investigate login or permission failures",
|
|
"goal": "Link identity, account records, and recent auth logs into one diagnosis sequence.",
|
|
"why_it_matters": "Access problems often mix user identity, account state, and authentication evidence.",
|
|
"concepts": ["Current identity", "Account presence", "Recent authentication history"],
|
|
"command": "whoami / id / grep / tail",
|
|
"examples": ["whoami", "id", "grep sandbox_user /etc/passwd", "cat /var/log/auth.log | tail -n 1"],
|
|
"pitfalls": ["Guessing a password issue without checking logs", "Ignoring account records and group membership"],
|
|
"scenarios": ["A login fails repeatedly", "A script exists but still cannot be used by the current user"],
|
|
"troubleshooting_flow": ["Confirm the current identity", "Verify the target account exists", "Read the newest auth evidence"],
|
|
"related_commands": ["whoami", "id", "grep", "tail"],
|
|
"classic_view": "Access issues are easier when you inspect identity, account records, and logs as one chain.",
|
|
"takeaways": ["Confirm the user context first", "Use account files as evidence", "Trust logs more than guesses"],
|
|
"after_class": "Repeat the identity -> account -> log sequence until it feels like one habit.",
|
|
"exercises": [
|
|
{ "id": "m5_l2_e1", "type": "operation", "title": "Confirm your current identity", "hint": "Run whoami.", "success_test": "cmd == 'whoami' and 'sandbox_user' in output", "solution": ["whoami"], "success_msg": "You confirmed the current shell identity." },
|
|
{ "id": "m5_l2_e2", "type": "operation", "title": "Inspect uid and group information", "hint": "Run id.", "success_test": "cmd == 'id' and 'uid=' in output", "solution": ["id"], "success_msg": "You inspected the uid and group context." },
|
|
{ "id": "m5_l2_e3", "type": "operation", "title": "Check whether sandbox_user exists in passwd", "hint": "Run grep sandbox_user /etc/passwd.", "success_test": "cmd == 'grep sandbox_user /etc/passwd' and 'sandbox_user' in output", "solution": ["grep sandbox_user /etc/passwd"], "success_msg": "You verified the account record." },
|
|
{ "id": "m5_l2_e4", "type": "operation", "title": "Read the newest auth log line", "hint": "Run cat /var/log/auth.log | tail -n 1.", "success_test": "cmd == 'cat /var/log/auth.log | tail -n 1' and 'sandbox_user' in output", "solution": ["cat /var/log/auth.log | tail -n 1"], "success_msg": "You retrieved recent authentication evidence." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m5_l3_service_path",
|
|
"title": "Incident drill: service is up, but the application still feels broken",
|
|
"goal": "Check service state, listening sockets, and application output in one guided drill.",
|
|
"why_it_matters": "A service can look healthy at one layer but still fail at another layer.",
|
|
"concepts": ["Service state", "Socket listening state", "Application response validation"],
|
|
"command": "systemctl / ss / curl / journalctl",
|
|
"examples": ["systemctl status nginx", "ss -ltnp", "curl http://127.0.0.1", "journalctl -n 20"],
|
|
"pitfalls": ["Trusting systemctl alone without checking sockets or a real request", "Stopping at ss without making an application-layer call"],
|
|
"scenarios": ["A team says the service is up but the page still feels unavailable", "You need to prove whether the problem is service state, port state, or HTTP behavior"],
|
|
"troubleshooting_flow": ["Check systemctl for service state", "Check ss for listening ports", "Use curl to verify the real response", "Open journal logs if behavior still looks wrong"],
|
|
"related_commands": ["systemctl", "ss", "curl", "journalctl"],
|
|
"classic_view": "Healthy service state does not guarantee healthy application behavior, so finish the chain with a real request.",
|
|
"takeaways": ["Check multiple layers", "Do not confuse a listening port with a healthy app", "End the chain with a real request and logs when needed"],
|
|
"after_class": "Practice explaining which layer each command validates: service, socket, application, or logs.",
|
|
"exercises": [
|
|
{ "id": "m5_l3_e1", "type": "operation", "title": "Check nginx service state", "hint": "Run systemctl status nginx.", "success_test": "cmd == 'systemctl status nginx' and 'Active:' in output", "solution": ["systemctl status nginx"], "success_msg": "You confirmed the service layer state." },
|
|
{ "id": "m5_l3_e2", "type": "operation", "title": "Check listening sockets", "hint": "Run ss -ltnp.", "success_test": "cmd == 'ss -ltnp' and 'LISTEN' in output", "solution": ["ss -ltnp"], "success_msg": "You confirmed the socket layer." },
|
|
{ "id": "m5_l3_e3", "type": "operation", "title": "Make a real application request", "hint": "Run curl http://127.0.0.1.", "success_test": "cmd == 'curl http://127.0.0.1' and 'hello localhost' in output", "solution": ["curl http://127.0.0.1"], "success_msg": "You verified the final application response layer." },
|
|
{ "id": "m5_l3_e4", "type": "operation", "title": "Open recent journal lines", "hint": "Run journalctl -n 20.", "success_test": "cmd == 'journalctl -n 20' and 'Started nginx.service' in output", "solution": ["journalctl -n 20"], "success_msg": "You added log evidence to the service drill." }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "module_6_shell_environment",
|
|
"title": "Module 6: Work faster in the shell",
|
|
"summary": "Use environment variables, aliases, history, and simple text pipelines to make Linux work feel repeatable instead of manual.",
|
|
"lessons": [
|
|
{
|
|
"id": "m6_l1_env_alias",
|
|
"title": "Control shell context with env, export, and alias",
|
|
"goal": "Understand how shell state changes affect later commands and sessions.",
|
|
"why_it_matters": "A lot of Linux productivity comes from shaping the shell so common work is easier to repeat safely.",
|
|
"concepts": ["Environment variables", "Exported shell state", "Command aliases"],
|
|
"command": "env / export / alias",
|
|
"examples": ["env", "export APP_MODE=lab", "alias ll='ls -l'"],
|
|
"pitfalls": ["Assuming an echoed value is the same as an exported variable", "Creating aliases without understanding the original command"],
|
|
"scenarios": ["Prepare a temporary runtime flag", "Shorten a repeated command pattern"],
|
|
"troubleshooting_flow": ["Inspect the current shell state", "Change one variable or alias", "Verify the shell reflects the change"],
|
|
"related_commands": ["env", "export", "alias", "echo"],
|
|
"classic_view": "The shell is not just a place to type commands; it is a working environment you can shape.",
|
|
"takeaways": ["Use env to inspect state", "Use export to persist a shell variable for child processes", "Use aliases carefully and intentionally"],
|
|
"after_class": "Practice changing one variable and one alias, then explain what effect each change has.",
|
|
"exercises": [
|
|
{ "id": "m6_l1_e1", "type": "operation", "title": "Inspect the current environment", "hint": "Run env.", "success_test": "cmd == 'env' and 'HOME=' in output", "solution": ["env"], "success_msg": "You inspected the current shell environment." },
|
|
{ "id": "m6_l1_e2", "type": "operation", "title": "Export a temporary variable", "hint": "Run export APP_MODE=lab.", "success_test": "cmd == 'export APP_MODE=lab' and 'APP_MODE=lab' in output", "solution": ["export APP_MODE=lab"], "success_msg": "You exported a shell variable successfully." },
|
|
{ "id": "m6_l1_e3", "type": "operation", "title": "Create a listing alias", "hint": "Run alias ll='ls -l'.", "success_test": "cmd == \"alias ll='ls -l'\" and \"alias ll='ls -l'\" in output", "solution": ["alias ll='ls -l'"], "success_msg": "You created a shell alias." },
|
|
{ "id": "m6_l1_e4", "type": "scenario", "question": "Why is export different from simply printing a value with echo?", "answer": "Because export changes the shell environment for later commands, while echo only prints text once." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m6_l2_history_replay",
|
|
"title": "Use history, date, and clear to manage command sessions",
|
|
"goal": "Review what you ran, time-stamp your work, and keep the terminal readable during longer sessions.",
|
|
"why_it_matters": "Operations work often depends on replaying what happened and keeping a clean command narrative.",
|
|
"concepts": ["Command history", "Session readability", "Time awareness"],
|
|
"command": "history / date / clear",
|
|
"examples": ["history", "date", "clear"],
|
|
"pitfalls": ["Running many commands without keeping track of sequence", "Treating a terminal as disposable when the command history is useful evidence"],
|
|
"scenarios": ["Reconstruct what you tried during an incident", "Record the time before a change or restart"],
|
|
"troubleshooting_flow": ["Review your recent commands", "Record or inspect the current time", "Clear the screen only after capturing what matters"],
|
|
"related_commands": ["history", "date", "clear", "echo"],
|
|
"classic_view": "A strong operator can explain not just what they ran, but in what order and when.",
|
|
"takeaways": ["Use history as lightweight evidence", "Use date to anchor actions in time", "Clear the screen only when it helps readability"],
|
|
"after_class": "Practice summarizing a small command session from history without looking at your notes.",
|
|
"exercises": [
|
|
{ "id": "m6_l2_e1", "type": "operation", "title": "Show your recent command history", "hint": "Run history.", "success_test": "cmd == 'history' and len(output) >= 0", "solution": ["history"], "success_msg": "You reviewed the shell history." },
|
|
{ "id": "m6_l2_e2", "type": "operation", "title": "Print the current date and time", "hint": "Run date.", "success_test": "cmd == 'date' and 'CST' in output", "solution": ["date"], "success_msg": "You time-stamped the session." },
|
|
{ "id": "m6_l2_e3", "type": "operation", "title": "Clear the terminal view", "hint": "Run clear.", "success_test": "cmd == 'clear' and 'screen cleared' in output", "solution": ["clear"], "success_msg": "You reset the visible terminal output." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m6_l3_text_pipeline",
|
|
"title": "Reduce noise with sort, uniq, cut, awk, and sed",
|
|
"goal": "Use small text-processing tools to transform output instead of reading everything manually.",
|
|
"why_it_matters": "Linux operations is easier when you can shape output into something small enough to reason about.",
|
|
"concepts": ["Pipeline thinking", "Sorting and deduplication", "Field extraction", "Quick text transforms"],
|
|
"command": "sort / uniq / cut / awk / sed",
|
|
"examples": ["cat /var/log/auth.log | sort", "cat /var/log/auth.log | uniq", "cat /etc/passwd | cut -d: -f1"],
|
|
"pitfalls": ["Running multiple commands manually when a pipeline would be clearer", "Transforming output without understanding what field you are extracting"],
|
|
"scenarios": ["Normalize repeated lines before inspection", "Extract usernames from passwd"],
|
|
"troubleshooting_flow": ["Start with raw output", "Apply one transform at a time", "Stop when the output is small enough to answer the question"],
|
|
"related_commands": ["sort", "uniq", "cut", "awk", "sed", "grep"],
|
|
"classic_view": "Pipelines are how Linux turns a large question into a sequence of smaller, readable steps.",
|
|
"takeaways": ["Use sort and uniq to reduce repetition", "Use cut for simple field extraction", "Treat awk and sed as focused shaping tools rather than magic"],
|
|
"after_class": "Practice explaining what each pipeline stage contributes before you run the full chain.",
|
|
"exercises": [
|
|
{ "id": "m6_l3_e1", "type": "operation", "title": "Sort auth log lines", "hint": "Run cat /var/log/auth.log | sort.", "success_test": "cmd == 'cat /var/log/auth.log | sort' and len(output) > 0", "solution": ["cat /var/log/auth.log | sort"], "success_msg": "You sorted the auth log lines." },
|
|
{ "id": "m6_l3_e2", "type": "operation", "title": "Deduplicate auth log lines", "hint": "Run cat /var/log/auth.log | uniq.", "success_test": "cmd == 'cat /var/log/auth.log | uniq' and len(output) > 0", "solution": ["cat /var/log/auth.log | uniq"], "success_msg": "You reduced duplicate lines with uniq." },
|
|
{ "id": "m6_l3_e3", "type": "operation", "title": "Extract the first passwd field", "hint": "Run cat /etc/passwd | cut -d: -f1.", "success_test": "cmd == 'cat /etc/passwd | cut -d: -f1' and len(output) > 0", "solution": ["cat /etc/passwd | cut -d: -f1"], "success_msg": "You extracted a single field from passwd." },
|
|
{ "id": "m6_l3_e4", "type": "scenario", "question": "Why do pipelines help more than manually copying output into another tool?", "answer": "Because they preserve a clear, repeatable transformation chain directly in the shell." }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "module_7_packages_and_delivery",
|
|
"title": "Module 7: Locate tools, inspect packages, and move artifacts",
|
|
"summary": "Learn where commands live, how package managers differ, and how archives or background jobs support basic delivery work.",
|
|
"lessons": [
|
|
{
|
|
"id": "m7_l1_locate_tools",
|
|
"title": "Locate binaries with which and whereis",
|
|
"goal": "Understand where commands come from before changing PATHs or package assumptions.",
|
|
"why_it_matters": "Operators often need to prove which binary is running and where supporting docs or manpages live.",
|
|
"concepts": ["Binary location", "PATH lookup", "Tool discovery"],
|
|
"command": "which / whereis",
|
|
"examples": ["which curl", "which ls", "whereis grep"],
|
|
"pitfalls": ["Assuming the same command path on every server", "Changing PATH without first proving what is currently used"],
|
|
"scenarios": ["Check where curl is installed", "Find the shell-visible ls binary"],
|
|
"troubleshooting_flow": ["Ask which binary the shell resolves", "Use whereis for broader discovery", "Only then adjust your assumptions or PATH"],
|
|
"related_commands": ["which", "whereis", "env"],
|
|
"classic_view": "Before you replace a binary or adjust PATH, prove what the shell is already resolving.",
|
|
"takeaways": ["Use which for shell resolution", "Use whereis for broader lookup", "Do not guess command paths"],
|
|
"after_class": "Compare which and whereis on two commands and explain why their outputs differ.",
|
|
"exercises": [
|
|
{ "id": "m7_l1_e1", "type": "operation", "title": "Locate curl", "hint": "Run which curl.", "success_test": "cmd == 'which curl' and '/usr/bin/curl' in output", "solution": ["which curl"], "success_msg": "You located the curl binary." },
|
|
{ "id": "m7_l1_e2", "type": "operation", "title": "Locate ls", "hint": "Run which ls.", "success_test": "cmd == 'which ls' and '/bin/ls' in output", "solution": ["which ls"], "success_msg": "You located the ls binary." },
|
|
{ "id": "m7_l1_e3", "type": "operation", "title": "Broaden the search for grep", "hint": "Run whereis grep.", "success_test": "cmd == 'whereis grep' and 'grep:' in output", "solution": ["whereis grep"], "success_msg": "You used whereis for broader command discovery." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m7_l2_package_basics",
|
|
"title": "Read package tool output with apt, yum, rpm, and dpkg",
|
|
"goal": "Recognize the common package tools you will encounter across distributions.",
|
|
"why_it_matters": "Even if you automate package installs later, you still need to understand the native package vocabulary of the host.",
|
|
"concepts": ["Debian vs RPM ecosystems", "Package listing output", "Distribution-specific tooling"],
|
|
"command": "apt / yum / rpm / dpkg",
|
|
"examples": ["apt", "yum", "rpm", "dpkg"],
|
|
"pitfalls": ["Assuming apt exists on every Linux server", "Reading package output without identifying the distro context first"],
|
|
"scenarios": ["Check which package tool family a server likely uses", "Read enough package output to orient yourself before change work"],
|
|
"troubleshooting_flow": ["Identify the package family", "Read listing output", "Only then plan installs or version checks"],
|
|
"related_commands": ["apt", "yum", "rpm", "dpkg", "which"],
|
|
"classic_view": "Package commands are not interchangeable, so distribution context matters before action.",
|
|
"takeaways": ["Recognize Debian-style and RPM-style tooling", "Read package output without guessing", "Use package family as part of host context"],
|
|
"after_class": "Practice explaining which systems are more likely to use apt vs yum or rpm.",
|
|
"exercises": [
|
|
{ "id": "m7_l2_e1", "type": "operation", "title": "Inspect apt output", "hint": "Run apt.", "success_test": "cmd == 'apt' and 'Reading package lists' in output", "solution": ["apt"], "success_msg": "You inspected Debian-style package manager output." },
|
|
{ "id": "m7_l2_e2", "type": "operation", "title": "Inspect yum output", "hint": "Run yum.", "success_test": "cmd == 'yum' and 'Installed Packages' in output", "solution": ["yum"], "success_msg": "You inspected RPM-style package manager output." },
|
|
{ "id": "m7_l2_e3", "type": "operation", "title": "Inspect rpm output", "hint": "Run rpm.", "success_test": "cmd == 'rpm' and 'bash-' in output", "solution": ["rpm"], "success_msg": "You inspected a package-level rpm response." },
|
|
{ "id": "m7_l2_e4", "type": "operation", "title": "Inspect dpkg output", "hint": "Run dpkg.", "success_test": "cmd == 'dpkg' and 'ii bash' in output", "solution": ["dpkg"], "success_msg": "You inspected a Debian package-level response." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m7_l3_archives_and_background",
|
|
"title": "Handle archives and background jobs with tar and nohup",
|
|
"goal": "Prepare simple deployment artifacts and understand why background execution changes how you observe a job.",
|
|
"why_it_matters": "Basic delivery work often includes packaging files, extracting them, and running something outside the current terminal.",
|
|
"concepts": ["Archive creation", "Archive extraction", "Background job output"],
|
|
"command": "tar / nohup",
|
|
"examples": ["tar -czf /tmp/lab.tar.gz /tmp/lab", "tar -xzf /tmp/lab.tar.gz -C /tmp/lab_restore", "nohup python3 /projects/backend/app.py"],
|
|
"pitfalls": ["Treating an archive like a backup without verifying extraction", "Starting a job with nohup without thinking about its output destination"],
|
|
"scenarios": ["Bundle a small workspace", "Extract an artifact into a staging path", "Run a long task after disconnecting"],
|
|
"troubleshooting_flow": ["Create the artifact", "Extract it into a clean target", "Use nohup only when the process should survive the terminal"],
|
|
"related_commands": ["tar", "nohup", "ps", "ls"],
|
|
"classic_view": "Packaging and background execution are useful, but only when you stay aware of where files and output go.",
|
|
"takeaways": ["Use tar for simple packaging", "Verify extraction paths", "Use nohup deliberately, not by habit"],
|
|
"after_class": "Practice explaining what changed in the filesystem after create and extract operations.",
|
|
"exercises": [
|
|
{ "id": "m7_l3_e1", "type": "operation", "title": "Create a tar archive", "hint": "Run tar -czf /tmp/lab.tar.gz /tmp/lab.", "success_test": "cmd == 'tar -czf /tmp/lab.tar.gz /tmp/lab' and exists('/tmp/lab.tar.gz')", "solution": ["tar -czf /tmp/lab.tar.gz /tmp/lab"], "success_msg": "You created an archive artifact." },
|
|
{ "id": "m7_l3_e2", "type": "operation", "title": "Extract the tar archive", "hint": "Run tar -xzf /tmp/lab.tar.gz -C /tmp/lab_restore.", "success_test": "cmd == 'tar -xzf /tmp/lab.tar.gz -C /tmp/lab_restore' and exists('/tmp/lab_restore')", "solution": ["tar -xzf /tmp/lab.tar.gz -C /tmp/lab_restore"], "success_msg": "You extracted the archive into a clean target directory." },
|
|
{ "id": "m7_l3_e3", "type": "operation", "title": "Start a background-friendly command", "hint": "Run nohup python3 /projects/backend/app.py.", "success_test": "cmd == 'nohup python3 /projects/backend/app.py' and 'nohup.out' in output", "solution": ["nohup python3 /projects/backend/app.py"], "success_msg": "You practiced a nohup-style launch." },
|
|
{ "id": "m7_l3_e4", "type": "scenario", "question": "Why should you still verify extraction or output paths after using tar or nohup?", "answer": "Because packaging or backgrounding can succeed syntactically while still writing to the wrong place." }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "module_8_monitoring_and_scheduling",
|
|
"title": "Module 8: Read system health and time-based execution",
|
|
"summary": "Learn the commands that help you interpret a host snapshot and think about recurring work.",
|
|
"lessons": [
|
|
{
|
|
"id": "m8_l1_system_snapshot",
|
|
"title": "Take a quick host snapshot with uptime, free, w, and last",
|
|
"goal": "Build a fast first-pass host summary before deeper debugging.",
|
|
"why_it_matters": "A good operator can summarize load, memory, active sessions, and recent logins in under a minute.",
|
|
"concepts": ["Load averages", "Memory pressure", "Active users", "Login history"],
|
|
"command": "uptime / free / w / last",
|
|
"examples": ["uptime", "free", "w", "last"],
|
|
"pitfalls": ["Reading one metric in isolation", "Ignoring user/session context during troubleshooting"],
|
|
"scenarios": ["A host feels slow but you need a broad snapshot first", "You need to know whether other users are active on the system"],
|
|
"troubleshooting_flow": ["Check load with uptime", "Check memory with free", "Check active sessions with w", "Review recent logins with last"],
|
|
"related_commands": ["uptime", "free", "w", "last", "top"],
|
|
"classic_view": "Before deep investigation, collect a fast baseline of load, memory, users, and logins.",
|
|
"takeaways": ["Use multiple quick metrics together", "Include session context in your diagnosis", "Build a snapshot before diving deeper"],
|
|
"after_class": "Practice summarizing the host in one sentence using all four commands together.",
|
|
"exercises": [
|
|
{ "id": "m8_l1_e1", "type": "operation", "title": "Check uptime", "hint": "Run uptime.", "success_test": "cmd == 'uptime' and 'load average' in output", "solution": ["uptime"], "success_msg": "You read the broad load snapshot." },
|
|
{ "id": "m8_l1_e2", "type": "operation", "title": "Check memory usage", "hint": "Run free.", "success_test": "cmd == 'free' and 'Mem:' in output", "solution": ["free"], "success_msg": "You checked memory pressure." },
|
|
{ "id": "m8_l1_e3", "type": "operation", "title": "Check active users", "hint": "Run w.", "success_test": "cmd == 'w' and 'USER' in output", "solution": ["w"], "success_msg": "You reviewed active shell users." },
|
|
{ "id": "m8_l1_e4", "type": "operation", "title": "Check recent login history", "hint": "Run last.", "success_test": "cmd == 'last' and 'sandbox_user' in output", "solution": ["last"], "success_msg": "You reviewed recent login history." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m8_l2_ports_and_paths",
|
|
"title": "Inspect open paths with lsof, netstat, traceroute, and dig",
|
|
"goal": "Use deeper network tools when the simple service or socket checks are not enough.",
|
|
"why_it_matters": "Real network debugging often requires more than ping and curl.",
|
|
"concepts": ["Open file and socket ownership", "Legacy vs modern network listing", "Route tracing", "DNS lookup"],
|
|
"command": "lsof / netstat / traceroute / dig",
|
|
"examples": ["lsof", "netstat", "traceroute 8.8.8.8", "dig example.com"],
|
|
"pitfalls": ["Treating all network questions as port-only problems", "Ignoring route or DNS evidence when connectivity looks inconsistent"],
|
|
"scenarios": ["Confirm which process owns a listening socket", "Trace whether the path to a remote target looks reachable", "Check if DNS is the real failure point"],
|
|
"troubleshooting_flow": ["Use lsof or netstat to inspect local ownership", "Use traceroute to reason about path", "Use dig to isolate DNS problems"],
|
|
"related_commands": ["lsof", "netstat", "traceroute", "dig", "ss"],
|
|
"classic_view": "When simple checks stop being enough, move to ownership, route, and DNS visibility.",
|
|
"takeaways": ["Use lsof for ownership clues", "Use traceroute for route intuition", "Use dig to separate DNS from transport issues"],
|
|
"after_class": "Practice describing which question each command answers before you run it.",
|
|
"exercises": [
|
|
{ "id": "m8_l2_e1", "type": "operation", "title": "Inspect open files and sockets", "hint": "Run lsof.", "success_test": "cmd == 'lsof' and 'LISTEN' in output", "solution": ["lsof"], "success_msg": "You inspected file and socket ownership." },
|
|
{ "id": "m8_l2_e2", "type": "operation", "title": "Inspect listening network state", "hint": "Run netstat.", "success_test": "cmd == 'netstat' and 'LISTEN' in output", "solution": ["netstat"], "success_msg": "You inspected network listening state with netstat." },
|
|
{ "id": "m8_l2_e3", "type": "operation", "title": "Trace a network path", "hint": "Run traceroute 8.8.8.8.", "success_test": "cmd == 'traceroute 8.8.8.8' and 'traceroute' in output", "solution": ["traceroute 8.8.8.8"], "success_msg": "You traced a network path." },
|
|
{ "id": "m8_l2_e4", "type": "operation", "title": "Check DNS resolution", "hint": "Run dig example.com.", "success_test": "cmd == 'dig example.com' and 'ANSWER SECTION' in output", "solution": ["dig example.com"], "success_msg": "You separated DNS lookup from the rest of the network path." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m8_l3_time_and_schedule",
|
|
"title": "Read scheduling context with date, cal, and crontab",
|
|
"goal": "Think about when work runs, not just what runs.",
|
|
"why_it_matters": "A lot of operations work is time-based: jobs, cutovers, maintenance windows, and recurring checks.",
|
|
"concepts": ["Current system time", "Calendar context", "Recurring jobs"],
|
|
"command": "date / cal / crontab",
|
|
"examples": ["date", "cal", "crontab -l"],
|
|
"pitfalls": ["Ignoring system time when reading logs or scheduling changes", "Treating cron like magic instead of planned automation"],
|
|
"scenarios": ["Confirm the current host time before correlating logs", "Check whether a user has scheduled jobs", "Think about maintenance windows"],
|
|
"troubleshooting_flow": ["Read current time first", "Check the calendar context", "Inspect scheduled jobs before assuming nothing runs automatically"],
|
|
"related_commands": ["date", "cal", "crontab", "history"],
|
|
"classic_view": "Time is part of system state, and operations mistakes often come from forgetting that.",
|
|
"takeaways": ["Time-stamp your thinking", "Use calendar context during planning", "Check cron before assuming a task is manual"],
|
|
"after_class": "Explain how wrong time assumptions can distort a log or incident timeline.",
|
|
"exercises": [
|
|
{ "id": "m8_l3_e1", "type": "operation", "title": "Read the current system time", "hint": "Run date.", "success_test": "cmd == 'date' and 'CST' in output", "solution": ["date"], "success_msg": "You checked the system time." },
|
|
{ "id": "m8_l3_e2", "type": "operation", "title": "Open the calendar view", "hint": "Run cal.", "success_test": "cmd == 'cal' and 'March' in output", "solution": ["cal"], "success_msg": "You opened a calendar view for planning context." },
|
|
{ "id": "m8_l3_e3", "type": "operation", "title": "Inspect scheduled jobs", "hint": "Run crontab -l.", "success_test": "cmd == 'crontab -l' and 'crontab' in output", "solution": ["crontab -l"], "success_msg": "You checked for scheduled cron jobs." },
|
|
{ "id": "m8_l3_e4", "type": "scenario", "question": "Why should you check date before correlating logs from an incident?", "answer": "Because a wrong time assumption can make the event sequence look incorrect even if the commands are right." }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "module_9_web_delivery",
|
|
"title": "Module 9: Web delivery and runtime verification",
|
|
"summary": "Connect service state, sockets, configs, and HTTP requests into a simple web delivery workflow.",
|
|
"lessons": [
|
|
{
|
|
"id": "m9_l1_service_to_http",
|
|
"title": "From service state to HTTP verification",
|
|
"goal": "Learn how to verify a web service from the process layer up to the HTTP layer.",
|
|
"why_it_matters": "A web service is not truly healthy until the application responds correctly, not just when the process exists.",
|
|
"concepts": ["Service state", "Socket readiness", "HTTP verification"],
|
|
"command": "systemctl / ss / curl",
|
|
"examples": ["systemctl status nginx", "ss -ltnp", "curl http://127.0.0.1"],
|
|
"pitfalls": ["Treating a running service as proof of healthy delivery", "Stopping at a listening socket without making a request"],
|
|
"scenarios": ["A service was restarted after deployment but the site still looks broken", "You need to prove whether the issue is process, port, or response body"],
|
|
"troubleshooting_flow": ["Check the service state", "Check listening sockets", "Send a real HTTP request", "Use the result to decide whether you need logs next"],
|
|
"related_commands": ["systemctl", "ss", "curl", "journalctl"],
|
|
"classic_view": "Healthy delivery means the request path works end to end, not just that one process is running.",
|
|
"takeaways": ["Check multiple layers", "Do not confuse a listening port with a correct response", "Always include a real request in a delivery check"],
|
|
"after_class": "Practice saying which layer each command verifies: service, port, or HTTP response.",
|
|
"exercises": [
|
|
{ "id": "m9_l1_e1", "type": "operation", "title": "Check nginx service state", "hint": "Run systemctl status nginx.", "success_test": "cmd == 'systemctl status nginx' and 'Active:' in output", "solution": ["systemctl status nginx"], "success_msg": "You verified the service layer." },
|
|
{ "id": "m9_l1_e2", "type": "operation", "title": "Check listening sockets", "hint": "Run ss -ltnp.", "success_test": "cmd == 'ss -ltnp' and 'LISTEN' in output", "solution": ["ss -ltnp"], "success_msg": "You verified the socket layer." },
|
|
{ "id": "m9_l1_e3", "type": "operation", "title": "Check the HTTP response", "hint": "Run curl http://127.0.0.1.", "success_test": "cmd == 'curl http://127.0.0.1' and 'hello localhost' in output", "solution": ["curl http://127.0.0.1"], "success_msg": "You verified the HTTP layer." },
|
|
{ "id": "m9_l1_e4", "type": "scenario", "question": "If systemctl and ss look fine but curl still fails, which layer deserves your attention next?", "answer": "The application response path and logs, because process and port checks alone do not prove healthy delivery." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m9_l2_fetch_and_compare",
|
|
"title": "Fetch and compare remote content with curl and wget",
|
|
"goal": "Understand the difference between checking a response and saving an artifact.",
|
|
"why_it_matters": "Operations work often mixes quick request verification with actual file retrieval during delivery or recovery.",
|
|
"concepts": ["Response inspection", "Artifact download", "Saved output validation"],
|
|
"command": "curl / wget",
|
|
"examples": ["curl http://127.0.0.1", "wget https://example.com/file.tar.gz"],
|
|
"pitfalls": ["Using wget when you only need a quick response check", "Downloading a file without checking where it was saved"],
|
|
"scenarios": ["Confirm a page is reachable", "Fetch a package or static asset into a working directory"],
|
|
"troubleshooting_flow": ["Use curl for quick HTTP inspection", "Use wget when you need a saved file", "Verify the saved path or body immediately"],
|
|
"related_commands": ["curl", "wget", "ls", "cat"],
|
|
"classic_view": "Choose between inspect and download deliberately instead of treating all HTTP tools as the same.",
|
|
"takeaways": ["Use curl for response checks", "Use wget for saved artifacts", "Verify destination and content after retrieval"],
|
|
"after_class": "Explain why curl and wget answer different operational questions even when they both touch HTTP.",
|
|
"exercises": [
|
|
{ "id": "m9_l2_e1", "type": "operation", "title": "Inspect the local web response", "hint": "Run curl http://127.0.0.1.", "success_test": "cmd == 'curl http://127.0.0.1' and 'hello localhost' in output", "solution": ["curl http://127.0.0.1"], "success_msg": "You used curl as a response-checking tool." },
|
|
{ "id": "m9_l2_e2", "type": "operation", "title": "Simulate downloading an artifact", "hint": "Run wget https://example.com/file.tar.gz.", "success_test": "cmd == 'wget https://example.com/file.tar.gz' and 'saved' in output.lower()", "solution": ["wget https://example.com/file.tar.gz"], "success_msg": "You used wget as an artifact download tool." },
|
|
{ "id": "m9_l2_e3", "type": "understanding", "question": "Why is curl often the better first tool during a health check?", "answer": "Because it quickly shows the response without introducing file-placement concerns." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m9_l3_config_runtime",
|
|
"title": "Inspect runtime config clues with cat, grep, and journalctl",
|
|
"goal": "Correlate config files, log output, and service behavior during a web issue.",
|
|
"why_it_matters": "Web incidents often require you to connect what the config says with what the logs report.",
|
|
"concepts": ["Config inspection", "Log correlation", "Runtime mismatch"],
|
|
"command": "cat / grep / journalctl",
|
|
"examples": ["cat /etc/nginx.conf", "grep error /var/log/syslog", "journalctl -n 20"],
|
|
"pitfalls": ["Changing config before reading the current file", "Reading logs without checking the active config context"],
|
|
"scenarios": ["An app starts but still shows misbehavior", "You suspect a config mismatch or startup error"],
|
|
"troubleshooting_flow": ["Read the config file", "Search logs for the matching signal", "Compare the config expectation with the runtime evidence"],
|
|
"related_commands": ["cat", "grep", "journalctl", "systemctl"],
|
|
"classic_view": "Config and logs make more sense when you read them as one story instead of separate artifacts.",
|
|
"takeaways": ["Read before changing", "Search logs for matching symptoms", "Use config and logs together"],
|
|
"after_class": "Practice explaining how one config clue should influence what log pattern you search for.",
|
|
"exercises": [
|
|
{ "id": "m9_l3_e1", "type": "operation", "title": "Read nginx.conf", "hint": "Run cat /etc/nginx.conf.", "success_test": "cmd == 'cat /etc/nginx.conf' and 'worker_processes' in output", "solution": ["cat /etc/nginx.conf"], "success_msg": "You inspected the active config file." },
|
|
{ "id": "m9_l3_e2", "type": "operation", "title": "Search syslog for errors", "hint": "Run grep error /var/log/syslog.", "success_test": "cmd == 'grep error /var/log/syslog' and 'error' in output.lower()", "solution": ["grep error /var/log/syslog"], "success_msg": "You filtered log output down to the error signal." },
|
|
{ "id": "m9_l3_e3", "type": "operation", "title": "Read recent journal entries", "hint": "Run journalctl -n 20.", "success_test": "cmd == 'journalctl -n 20' and 'nginx' in output.lower()", "solution": ["journalctl -n 20"], "success_msg": "You correlated service behavior with recent log evidence." }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "module_10_backup_and_recovery",
|
|
"title": "Module 10: Backup, recovery, and safe change patterns",
|
|
"summary": "Practice the habits that make change safer: copy, archive, inspect, restore, and verify.",
|
|
"lessons": [
|
|
{
|
|
"id": "m10_l1_backup_patterns",
|
|
"title": "Create rollback options with cp and tar",
|
|
"goal": "Understand simple rollback preparation before changing files or directories.",
|
|
"why_it_matters": "A safe change is easier when you can return to a known-good file or archive quickly.",
|
|
"concepts": ["Point-in-time copy", "Archive backup", "Rollback preparation"],
|
|
"command": "cp / tar",
|
|
"examples": ["cp /etc/hosts /tmp/hosts.rollback", "tar -czf /tmp/etc-snapshot.tar.gz /etc"],
|
|
"pitfalls": ["Changing a file before creating any rollback artifact", "Treating a backup as valid without verifying it exists"],
|
|
"scenarios": ["Prepare for a risky config edit", "Bundle a directory tree for later recovery"],
|
|
"troubleshooting_flow": ["Create a copy or archive first", "Verify the backup exists", "Only then proceed with the risky change"],
|
|
"related_commands": ["cp", "tar", "ls", "stat"],
|
|
"classic_view": "Backups are what gives you permission to change safely.",
|
|
"takeaways": ["Make rollback options first", "Verify that the backup artifact exists", "Choose copy vs archive based on scope"],
|
|
"after_class": "Before any file edit, say what your rollback object is and where it lives.",
|
|
"exercises": [
|
|
{ "id": "m10_l1_e1", "type": "operation", "title": "Create a rollback copy", "hint": "Run cp /etc/hosts /tmp/hosts.rollback.", "success_test": "cmd == 'cp /etc/hosts /tmp/hosts.rollback' and exists('/tmp/hosts.rollback')", "solution": ["cp /etc/hosts /tmp/hosts.rollback"], "success_msg": "You created a rollback copy before change." },
|
|
{ "id": "m10_l1_e2", "type": "operation", "title": "Create an archive snapshot", "hint": "Run tar -czf /tmp/etc-snapshot.tar.gz /etc.", "success_test": "cmd == 'tar -czf /tmp/etc-snapshot.tar.gz /etc' and exists('/tmp/etc-snapshot.tar.gz')", "solution": ["tar -czf /tmp/etc-snapshot.tar.gz /etc"], "success_msg": "You created an archive-based snapshot." },
|
|
{ "id": "m10_l1_e3", "type": "scenario", "question": "Why is a verified backup more valuable than just planning to remember the old state?", "answer": "Because rollback depends on an actual artifact, not memory or assumptions." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m10_l2_restore_path",
|
|
"title": "Restore into a clean path and inspect the result",
|
|
"goal": "Practice extracting or copying into a clean recovery target instead of overwriting blindly.",
|
|
"why_it_matters": "Safe recovery often means restoring to a staging path first so you can inspect what you got back.",
|
|
"concepts": ["Clean restore target", "Artifact extraction", "Verification after recovery"],
|
|
"command": "mkdir / tar / ls",
|
|
"examples": ["mkdir -p /tmp/restore/etc", "tar -xzf /tmp/etc-snapshot.tar.gz -C /tmp/restore/etc", "ls /tmp/restore/etc"],
|
|
"pitfalls": ["Extracting directly into a live path without inspection", "Skipping path validation after a restore"],
|
|
"scenarios": ["Recover a config tree into a staging directory", "Inspect a restored artifact before using it"],
|
|
"troubleshooting_flow": ["Create a clean target path", "Extract into the clean path", "List the restored result before using it"],
|
|
"related_commands": ["mkdir", "tar", "ls", "find"],
|
|
"classic_view": "Recovery is safer when you restore into a clean inspection space first.",
|
|
"takeaways": ["Restore into a staging path when possible", "Inspect restored content before reuse", "Use ls or find to verify the recovery target"],
|
|
"after_class": "Practice saying where you would restore first and why that path is safer than the live target.",
|
|
"exercises": [
|
|
{ "id": "m10_l2_e1", "type": "operation", "title": "Create a clean restore path", "hint": "Run mkdir -p /tmp/restore/etc.", "success_test": "cmd == 'mkdir -p /tmp/restore/etc' and exists('/tmp/restore/etc')", "solution": ["mkdir -p /tmp/restore/etc"], "success_msg": "You created a clean restore target." },
|
|
{ "id": "m10_l2_e2", "type": "operation", "title": "Extract the snapshot into the restore path", "hint": "Run tar -xzf /tmp/etc-snapshot.tar.gz -C /tmp/restore/etc.", "success_test": "cmd == 'tar -xzf /tmp/etc-snapshot.tar.gz -C /tmp/restore/etc' and exists('/tmp/restore/etc')", "solution": ["tar -xzf /tmp/etc-snapshot.tar.gz -C /tmp/restore/etc"], "success_msg": "You restored the archive into a clean path." },
|
|
{ "id": "m10_l2_e3", "type": "operation", "title": "List the restore target", "hint": "Run ls /tmp/restore/etc.", "success_test": "cmd == 'ls /tmp/restore/etc' and len(output) >= 0", "solution": ["ls /tmp/restore/etc"], "success_msg": "You inspected the restore target after extraction." }
|
|
]
|
|
},
|
|
{
|
|
"id": "m10_l3_change_window",
|
|
"title": "Plan a safe change window with date, cal, history, and journalctl",
|
|
"goal": "Think about change timing, traceability, and rollback evidence as part of routine operations work.",
|
|
"why_it_matters": "A safe operator tracks when a change happened and what happened immediately before or after it.",
|
|
"concepts": ["Time anchoring", "Change timeline", "Quick rollback evidence"],
|
|
"command": "date / cal / history / journalctl",
|
|
"examples": ["date", "cal", "history", "journalctl -n 20"],
|
|
"pitfalls": ["Making a change without checking maintenance timing", "Losing the command narrative that explains what happened"],
|
|
"scenarios": ["Prepare for a maintenance window", "Reconstruct the sequence around a recent restart or config change"],
|
|
"troubleshooting_flow": ["Anchor the time", "Check the calendar or window context", "Review your command narrative", "Read recent logs around the change"],
|
|
"related_commands": ["date", "cal", "history", "journalctl"],
|
|
"classic_view": "Operational safety improves when every change has time context, command context, and log context.",
|
|
"takeaways": ["Time-stamp your changes", "Keep a readable command narrative", "Pair history with logs for recovery thinking"],
|
|
"after_class": "Practice explaining a change using time, commands, and logs as one connected story.",
|
|
"exercises": [
|
|
{ "id": "m10_l3_e1", "type": "operation", "title": "Anchor the current time", "hint": "Run date.", "success_test": "cmd == 'date' and 'CST' in output", "solution": ["date"], "success_msg": "You anchored the current time." },
|
|
{ "id": "m10_l3_e2", "type": "operation", "title": "Review the calendar context", "hint": "Run cal.", "success_test": "cmd == 'cal' and 'March' in output", "solution": ["cal"], "success_msg": "You reviewed the calendar context." },
|
|
{ "id": "m10_l3_e3", "type": "operation", "title": "Review your command narrative", "hint": "Run history.", "success_test": "cmd == 'history' and len(output) >= 0", "solution": ["history"], "success_msg": "You reviewed the shell command timeline." },
|
|
{ "id": "m10_l3_e4", "type": "operation", "title": "Review recent journal lines", "hint": "Run journalctl -n 20.", "success_test": "cmd == 'journalctl -n 20' and 'nginx' in output.lower()", "solution": ["journalctl -n 20"], "success_msg": "You linked the change window to recent service logs." }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|