From f5bcfa2259a921b25c975b2b073f216bc352b63f Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 23 Mar 2026 16:03:32 +0800 Subject: [PATCH] feat: finish bilingual linux learning lab --- COURSE_TASKS.json | 148 ++++++- index.html | 1079 +++++++++++++++++++++++++++++++++++++++------ sandbox.py | 6 +- server.py | 456 ++++++++++++++++++- 4 files changed, 1537 insertions(+), 152 deletions(-) diff --git a/COURSE_TASKS.json b/COURSE_TASKS.json index 81ea0ad..b8c8b2d 100644 --- a/COURSE_TASKS.json +++ b/COURSE_TASKS.json @@ -5,9 +5,9 @@ "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": 8, - "total_lessons": 24, - "total_exercises": 82, + "module_count": 10, + "total_lessons": 30, + "total_exercises": 106, "pedagogy": "learning-first", "orientation": "ops-workflow" }, @@ -581,6 +581,148 @@ ] } ] + }, + { + "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." } + ] + } + ] } ] } diff --git a/index.html b/index.html index c157a3c..07922d9 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ - + @@ -269,7 +269,7 @@ } .module-summary-grid { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; margin-top: 14px; } @@ -290,7 +290,7 @@ } .stage-grid { display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; margin-top: 14px; } @@ -319,21 +319,71 @@ color: var(--muted); line-height: 1.9; } + .guide-stack { + display: flex; + flex-direction: column; + gap: 12px; + } + .guide-card { + padding: 14px; + border-radius: 16px; + border: 1px solid var(--line); + background: rgba(255,255,255,0.16); + } + .guide-card h4 { margin: 0 0 8px; font-size: 17px; } + .guide-card p { margin: 6px 0 0; } + .guide-meta { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; + margin-top: 12px; + } + .guide-meta .mini-stat strong { + font-size: 15px; + line-height: 1.6; + } + .variant-list { + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 12px; + } + .variant { + padding: 10px 12px; + border-radius: 14px; + border: 1px solid var(--line); + background: rgba(255,255,255,0.18); + } + .variant code { + display: inline-block; + margin-bottom: 6px; + padding: 4px 8px; + border-radius: 999px; + background: var(--terminal); + color: #dceaff; + font-family: Consolas, "Courier New", monospace; + font-size: 13px; + } + .variant strong { + display: block; + font-size: 14px; + margin-bottom: 4px; + } + .span-2 { grid-column: span 2; } .empty { padding: 12px; border: 1px dashed var(--line); border-radius: 14px; color: var(--muted); text-align: center; } @media (max-width: 1180px) { .layout { grid-template-columns: 1fr; } .sidebar { position: static; } .hero-grid, .detail-grid { grid-template-columns: 1fr; } .mini-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .module-summary-grid { grid-template-columns: 1fr; } - .stage-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .span-2 { grid-column: span 1; } } @media (max-width: 720px) { .shell { padding: 14px; } .topbar, .hero-head, .search, .terminal-input { flex-direction: column; align-items: stretch; } .stats { grid-template-columns: 1fr; } .mini-stats { grid-template-columns: 1fr; } - .stage-grid { grid-template-columns: 1fr; } + .guide-meta { grid-template-columns: 1fr; } } @@ -341,59 +391,60 @@
-
Linux Ops Learning Lab
+
Linux Ops Learning Lab

Linux Learning Lab

Search Linux lessons, practice commands in a sandbox, and build better troubleshooting habits.

- - Privacy + + + Privacy