213 lines
19 KiB
JSON
213 lines
19 KiB
JSON
{
|
|
"meta": {
|
|
"version": "5.0",
|
|
"title": "Linux Operations Learning Lab",
|
|
"author": "Codex",
|
|
"updated": "2026-03-19",
|
|
"description": "A rebuilt and valid Linux course focused on command purpose, troubleshooting flow, and sandbox repetition.",
|
|
"module_count": 4,
|
|
"total_lessons": 8,
|
|
"total_exercises": 24,
|
|
"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": "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": "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": "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." }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|