feat(root02): author Linux Administration & Networking

Adds all 6 ROOT 02 modules (Linux system administration, storage & filesystems, the
Networking Mental Model, Linux network inspection, DNS & HTTP, Linux troubleshooting) — 37
activities, each with a real lesson, demonstration/guided exercise, knowledge check, hands-on
Lab, and reflection. The Networking Mental Model module (M3) gets deliberately extra depth per
its "critical module" designation, with no lab by design — conceptual mastery via knowledge
check and guided exercise, matching root-05-m00's own precedent.

Adds 6 new hands-on Linux/networking scenarios plus 2 MCQs: linux-service-inspection (inspects
the platform's own real nginx without touching it — a genuinely safe, real service-inspection
target that avoids ever risking the Lab's own connectivity), linux-service-lifecycle (start/
verify/stop a fully controllable service), linux-storage-inspection, linux-network-inspection
(ip/ss/getent/curl, each framed as answering one specific diagnostic question),
linux-dns-http-diagnosis (a genuine DNS-resolves-but-wrong-port failure, diagnosed and fixed),
and linux-troubleshooting-framework (a real missing-config-file failure, resolved via the
RootNode Observe/Evidence/Hypothesis/Test/Repair/Validate/Prevent framework). All tested
end-to-end via the real scenario-validation API, including negative tests confirming they fail
when unsolved.

Wires rootnode-linux-networking-assessment (2 required + 4 random-draw scenarios) using the
existing Phase 3B assessment engine.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-08-26 17:07:46 -05:00
co-authored by Claude Sonnet 5
parent b6516a74db
commit e007bcf000
39 changed files with 1752 additions and 0 deletions
@@ -0,0 +1,64 @@
# Linux System Administration
## Five questions every administrator asks
Whenever you're handed an unfamiliar running system, you're really always answering the same
five questions: **What is running? Who started it? Where is its configuration? Where are its
logs? How do I verify its state?** This module builds the vocabulary and tools for all five.
## Packages: how software gets installed
A **package manager** (`apt` on Debian/Ubuntu, `dnf`/`yum` on Fedora/RHEL) installs, updates, and
removes software along with tracking what depends on what — the alternative (manually downloading
and compiling everything) is exactly the kind of unreproducible mess package managers exist to
prevent. You won't install packages by hand often in this Lab, but recognizing `apt install` /
`apt list --installed` matters for reading real systems.
## Services: long-running background programs
A **service** is a program meant to run continuously in the background, outliving any single
terminal session — a web server, a database, a log collector. Unlike a command you run and wait
for, a service starts, keeps running, and is expected to still be there when you come back.
## systemd and systemctl (the concept — not available in this Lab)
On most real Linux servers, **systemd** is the process manager (PID 1) responsible for starting,
stopping, and supervising services, and `systemctl` is the command you'd use to control it
(`systemctl status nginx`, `systemctl restart nginx`, `journalctl -u nginx` for its logs).
**This disposable Lab container does not run systemd** — its PID 1 is a boot script that starts
k3s directly, the same way a real container almost never runs a full init system. This is worth
knowing honestly rather than pretending otherwise: the CONCEPTS below (a running process, its
config file, its logs, verifying its state) are identical whether the supervisor is systemd or
something else — this module's Labs teach those concepts against a service you CAN directly
inspect and control in this environment.
## Configuration files and environment variables
Most services read their settings from either a config file (a specific path, usually under
`/etc/`) or **environment variables** (`env` lists every variable in your current shell;
`export NAME=value` sets one). Knowing WHERE a service's configuration lives is exactly the
"where is its configuration" question from this lesson's opening — you'll answer it for real in
this module's first Lab.
## Logs: where a service tells you what happened
A well-behaved service writes what it's doing to a log — usually a file under `/var/log/`.
Reading logs (with `cat`, `tail`, `tail -f` — from ROOT 01) is almost always the first real step
in figuring out why something isn't working, which Module 6 builds into a full framework.
## Instructor Notes
**Lesson goal:** Establish the five-question mental model (running/started-by/config/logs/
verify) as the organizing frame for the rest of this module, and be explicit and honest that
systemd isn't available in this specific Lab environment rather than silently working around it.
**Approximate timing:** 15 minutes read; 10 minutes live.
**Common misconception to watch for:** students who've used systemd elsewhere may expect
`systemctl` to exist here — address this directly and early rather than letting them discover
the error on their own.
**Discussion question:** "If you SSH into an unfamiliar production server and need to figure out
what's running and whether it's healthy, what's the very first command you'd run?" (There's no
single right answer — the point is getting students to articulate their own process before this
module hands them formal tools for it.)
@@ -0,0 +1,56 @@
# Demonstration: Answering the Five Questions For Real
## OBSERVE
This Lab's own terminal is served by a real running service: nginx.
```
$ pgrep -x nginx
906
```
Something with PID 906 is running.
## HYPOTHESIS
Predict: where would nginx's configuration file be, and where would its logs be?
## INSPECT
```
$ cat /etc/nginx/nginx.conf | head -5
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
...
$ ls /var/log/nginx/
access.log error.log
```
## CHANGE
Confirm it's actually listening on the port you'd expect:
```
$ ss -lnt | grep :80
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
```
## VERIFY
All five questions answered with real evidence, not assumptions: it's running (PID 906),
listening on port 80, configured at `/etc/nginx/nginx.conf`, logging to `/var/log/nginx/`. This
exact sequence — process, port, config, logs — is what you'll repeat for almost any unfamiliar
service on any real system.
## Instructor Notes
**Demo goal:** Show the five-question checklist answered end-to-end against a real, already-
running service the students are themselves using right now — more convincing than an
abstract example.
**Approximate timing:** 8 minutes live.
**Common mistake to watch for:** stopping here at "PID exists" without also confirming it's
actually listening on the expected port — a process existing and a service actually being
reachable are two different facts, both worth checking.
@@ -0,0 +1,39 @@
# Knowledge Check: Linux System Administration
---
**1. What are the five questions this module opened with?**
*Answer:* What is running? Who started it? Where is its configuration? Where are its logs? How
do I verify its state?
---
**2. Why doesn't this Lab environment have `systemctl`?**
*Answer:* This Lab's container doesn't run systemd as PID 1 — its boot script starts k3s
directly instead, the same way most containers don't run a full init system. The concepts
(inspecting a running service's process/config/logs/state) are the same regardless of which
supervisor manages it.
---
**3. A process exists (`pgrep` finds a PID) but the service seems unreachable. What's the next
thing to check?**
*Answer:* Whether it's actually listening on the expected port (`ss -lnt`) — a running process
and a reachable service are two separate facts; the process could have started but failed to
bind its port.
---
**4. Where does a well-behaved Linux service typically write what it's doing?**
*Answer:* A log file, usually under `/var/log/`.
## Instructor Notes
**Facilitation:** Question 2 is worth confirming explicitly if any student has prior systemd
experience — set the expectation before the Lab, not after a confusing error.
**Approximate timing:** 5 minutes.
@@ -0,0 +1,32 @@
# Guided Exercise: Plan Your Inspection Before You Type Anything
This is a paper exercise — commit to a plan before running commands in the Lab.
## Your task
You're told "the payments service seems slow." Before touching a keyboard, write down, in
order, which of these five questions you'd check FIRST, SECOND, and so on, and the one command
you'd use for each:
- Is it running at all?
- Is it listening on its expected port?
- What does its configuration say?
- What do its recent logs say?
- Has anything changed recently?
## A worked shape to check yourself against
A reasonable order: (1) is it running — `pgrep`/`ps`; (2) is it listening — `ss -lnt`; (3) recent
logs — `tail -n 50 <logfile>` (recent activity is usually more informative than config for a
sudden "slow" complaint); (4) configuration — only if logs point at a config-driven cause; (5)
recent changes — depends on context (deployment history, `ls -la` timestamps on config files).
There's no single correct order, but "running, then listening, then logs" before jumping to
config is a defensible default — you're checking the cheapest, fastest-answering questions
first.
## Instructor Notes
**Facilitation:** There's genuine room for disagreement on ordering here — the value is in
students defending their reasoning, not converging on one "correct" sequence.
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
@@ -0,0 +1,27 @@
# Reflection: You Now Have a Repeatable Checklist
## Reflection questions
1. Before this module, if someone said "check whether the service is healthy," what would you
have actually done? How does that compare to the five-question checklist now?
2. You started and stopped a service you fully controlled this module. What's different about
doing the same thing to a service you DIDN'T start, on a machine you don't fully understand
yet?
## Real Ops takeaway
**The five-question checklist works on ANY unfamiliar Linux system, not just this Lab.** Running
process, listening port, config location, logs, and current state — this is genuinely how
experienced engineers approach a machine they've never touched before. You'll use this exact
checklist again in ROOT 04 when a container "doesn't seem to be working," and again in
Kubernetes when a Pod isn't behaving as expected — the tools change, the checklist doesn't.
This module is complete. Continue to the next module.
## Instructor Notes
**Discussion questions for a live classroom:** question 2 is worth real discussion time — the
honest answer is "you should be more careful and inspect more before acting," a good early
seed for the least-privilege/caution habits ROOT 09 formalizes much later.
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
@@ -0,0 +1,50 @@
# Storage and Filesystems
## Block devices and partitions, conceptually
A **block device** is the kernel's view of a storage device (a disk, an SSD, a cloud volume) —
`lsblk` lists them. A device can be split into **partitions**, separate sections each formatted
with their own filesystem, though a single-partition disk is common too. You won't partition
anything in this Lab, but recognizing `lsblk`'s output (`sda`, `sda1`, `nvme0n1`...) matters for
reading real systems.
## Filesystems and mount points
A **filesystem** is the format that organizes raw blocks into the files/directories you actually
interact with (ext4, xfs, and — inside this container — overlay, a filesystem UNIQUE to how
containers layer their storage). A filesystem becomes usable by being **mounted** at some
directory path — `/` itself is a mount point, and additional filesystems can be mounted at other
paths (a separate disk mounted at `/data`, for example).
## Persistent vs. temporary storage — a distinction that matters immediately
Not everything written to disk is meant to last. `/tmp` is conventionally temporary (ROOT 01);
a container's own writable layer is, by convention and by ROOT 04's design, meant to be
DISPOSABLE — data written inside a container that isn't explicitly persisted elsewhere disappears
when that container is destroyed. This distinction — is this data supposed to survive, or not —
is the single most important storage question you'll carry forward into ROOT 04's volumes and
eventually Kubernetes' PersistentVolumes.
## Measuring usage: `df` vs. `du`
These answer two DIFFERENT questions, a common point of confusion:
- **`df -h`** — how much space is used/free on an entire mounted FILESYSTEM.
- **`du -sh <path>`** — how much space one specific DIRECTORY's contents actually use.
A filesystem can show plenty of free space overall (`df`) while one specific directory has
unexpectedly ballooned (`du`) — you need both questions answered to diagnose "why is disk full."
## Instructor Notes
**Lesson goal:** Build the persistent-vs-temporary distinction explicitly, since it's the direct
conceptual root of ROOT 04's bind-mount/volume distinction and, later, Kubernetes storage.
**Approximate timing:** 12 minutes read; 8 minutes live.
**Common misconception to watch for:** conflating `df` and `du` — a student surprised that
`df` doesn't show them "which directory is huge" is a good moment to reinforce they answer
different questions.
**Demo idea:** `df -h /` then `du -sh /var/log/*` side by side, narrating the different
question each answers.
@@ -0,0 +1,53 @@
# Demonstration: Two Different Questions, Two Different Commands
## OBSERVE
```
$ df -h /
Filesystem Size Used Avail Use% Mounted on
overlay 937G 199G 691G 23% /
```
The root filesystem overall: 23% used, plenty free.
## HYPOTHESIS
Predict: does this tell you anything about which specific directory is using the most space?
## INSPECT
```
$ du -sh /var/log/* 2>/dev/null
1.2M /var/log/nginx
850K /var/log/k3s.log
```
No — `df` only ever answers "how full is this filesystem overall." Finding a SPECIFIC large
consumer requires `du`, pointed at the directories you suspect.
## CHANGE
Narrow further into a single suspicious directory:
```
$ du -sh /var/log/nginx/*
600K /var/log/nginx/access.log
600K /var/log/nginx/error.log
```
## VERIFY
You now know both the overall filesystem picture (`df`) AND exactly which files are contributing
to it (`du`, narrowed progressively) — two genuinely different tools for two genuinely different
questions, not interchangeable.
## Instructor Notes
**Demo goal:** Make the df/du distinction concrete by actually needing BOTH to answer a
realistic "what's using my disk" question.
**Approximate timing:** 8 minutes live.
**Common mistake to watch for:** running `du -sh /` directly (extremely slow on a large
filesystem, since it recurses everything) instead of narrowing progressively into suspected
directories first.
@@ -0,0 +1,31 @@
# Knowledge Check: Storage and Filesystems
---
**1. `df -h /` shows only 23% used, but you suspect one directory is unusually large. Does
`df` alone answer that?**
*Answer:* No — `df` reports overall filesystem usage, not per-directory. You need `du -sh <path>`
pointed at the suspect directory (or directories) to answer that.
---
**2. What makes data in a container's writable layer different from data in `/home`?**
*Answer:* By convention and design, a container's own writable layer is meant to be disposable —
data there disappears when the container is destroyed, unless explicitly persisted elsewhere
(a topic ROOT 04 covers directly). `/home` data is expected to persist normally.
---
**3. What does "mounting" a filesystem actually mean?**
*Answer:* Attaching a filesystem so it becomes accessible at a specific directory path — `/`
itself is a mount point, and additional filesystems can be mounted at other paths.
## Instructor Notes
**Facilitation:** Question 2 previews ROOT 04's volume/bind-mount content directly — worth
naming that connection explicitly.
**Approximate timing:** 5 minutes.
@@ -0,0 +1,29 @@
# Guided Exercise: Narrow Down a Disk Problem on Paper
This is a paper exercise — commit to a plan before running commands in the Lab.
## Your task
`df -h /` shows the root filesystem is 95% full. You have three suspect directories:
`/var/log`, `/srv/cache`, and `/home`. Write the exact sequence of `du` commands you'd run to
find the actual culprit, narrowing your search efficiently rather than checking everything with
maximum depth immediately.
## A worked shape to check yourself against
```
du -sh /var/log /srv/cache /home
```
— one command, comparing all three top-level suspects at once. Whichever is largest, THEN
narrow further into it specifically (e.g. `du -sh /srv/cache/*` if that one's the culprit) rather
than deep-diving into all three from the start — this mirrors Module 1's "cheapest, fastest-
answering question first" principle.
## Instructor Notes
**Facilitation:** Point out that running `du -sh` separately three times versus once with all
three paths produces the same information — the point isn't syntax, it's the "compare broadly,
then narrow" strategy.
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
@@ -0,0 +1,27 @@
# Reflection: "It's Probably the Logs" Is a Guess, Not a Diagnosis
## Reflection questions
1. Before measuring, which of the three directories in this module's Lab did you guess would
be largest? Were you right?
2. Why does "persistent vs. temporary" matter enough to be worth memorizing as a question you
ask about EVERY piece of data, not just during a storage module?
## Real Ops takeaway
**Guessing which directory is consuming disk space, without measuring, is exactly the kind of
assumption that wastes real incident-response time.** `du`, applied progressively (broad, then
narrow), replaces guessing with evidence in seconds. And the persistent-vs-temporary question
you just practiced is the exact same question you'll ask again in ROOT 04 about container data,
and again much later about Kubernetes storage — a question worth internalizing now, cheaply,
rather than learning the hard way after data you assumed would persist didn't.
This module is complete. Continue to the next module.
## Instructor Notes
**Discussion questions for a live classroom:** question 1 works well as a quick poll before
revealing the real answer — the mismatch between guesses and reality reinforces the lesson
better than stating it directly.
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
@@ -0,0 +1,104 @@
# Networking Mental Model
## Why this module gets extra attention
Nearly everything you'll do for the rest of ROOT_ — container networking, Kubernetes Services,
Ingress, NetworkPolicy, DNS, cloud infrastructure — is a variation on the SAME small set of
ideas this module introduces. Get this model solid now, and every later networking topic becomes
"the same thing again, one layer up." Skip it, and every later module will feel like memorizing
unrelated facts. The goal here is operational REASONING, not subnet arithmetic — you will not be
asked to calculate a subnet mask by hand.
## The chain, layer by layer
Think of reaching another machine as passing through a fixed sequence of layers, each of which
can independently succeed or fail:
```
Host
↓
Network interface (this machine's connection to a network)
↓
IP address (this machine's address on that network)
↓
Subnet (which other addresses are "local" to reach directly)
↓
Default gateway (where to send anything NOT local)
↓
Routing (the actual path traffic takes)
↓
DNS (translating a name into an IP address)
↓
Port (which specific service on that IP)
↓
TCP / UDP (how the connection itself behaves)
↓
Application protocol (HTTP, etc. — the actual conversation)
```
## Each layer, briefly
- **Network interface** — your machine's actual network connection (`eth0`, etc.). No interface,
no networking, full stop.
- **IP address** — this machine's address on a given network. IPv4 addresses look like
`172.17.0.2`. `127.0.0.1` (aka `localhost`) is special: it always means "this same machine,"
never anywhere else. `0.0.0.0` is different again — when a SERVICE binds to it, it means
"listen on every interface this machine has," not a real destination address.
- **Subnet** — a range of addresses considered "local" to each other, expressed as CIDR
(`172.17.0.0/16` means every address starting `172.17.`). You don't need to calculate subnet
math by hand — you need to recognize that two addresses in the same subnet can talk directly,
while addresses outside it need a gateway.
- **Default gateway** — the address your machine sends traffic to when the destination ISN'T in
its own subnet — effectively "ask this router to figure out the rest."
- **Routing** — the actual decision-making about which path traffic takes; `ip route` shows your
machine's routing table (Module 4 makes this hands-on).
- **DNS** — translates a human-readable name (`example.com`) into an IP address, since IP
addresses aren't what humans want to remember or type.
- **Port** — a single IP address can host MANY services simultaneously; the port number is what
distinguishes "which one." Port 80 conventionally means HTTP, 443 HTTPS, 22 SSH — conventions,
not hard rules.
- **TCP vs. UDP** — TCP is connection-oriented and guarantees delivery/order (most web traffic,
SSH); UDP is connectionless and faster but delivers no guarantees (DNS queries themselves,
video streaming). Client/server: one side (the server) listens on a fixed port waiting for
connections; the other (the client) initiates them.
## The reasoning skill: "where is the failure?"
Given this chain, a real diagnostic question decomposes into a sequence of yes/no checks, each
isolating ONE layer:
- Does the host have an IP address at all?
- Is the destination local (same subnet) or remote?
- Is there a valid route to it?
- Can the name resolve to an IP?
- Can the destination PORT actually be reached?
- Is a service actually listening there?
- Does the application itself respond correctly?
Module 4 makes each of these a real command. Module 5 walks the complete request path end to
end. Module 6 formalizes this into RootNode's troubleshooting framework, applied to networking
specifically.
## Instructor Notes
**Lesson goal:** This module gets outsized weight for a reason — it's the single highest-
leverage conceptual investment in ROOT 02. Do not rush it to "cover the topics"; depth here pays
off across ROOT 04, ROOT 05-10, and beyond.
**Approximate timing:** 25 minutes read; 15-20 minutes live with discussion — longer than a
typical lesson, deliberately.
**Common misconception to watch for:** treating `0.0.0.0` as a real destination address rather
than a "listen on every interface" bind convention — this genuinely confuses students later when
they see it in a container's own listening-port output.
**Discussion question:** Walk through a real failure narrative live: "You `curl` a URL and get
'Connection refused.' Which layers does that rule OUT, and which remain suspects?" (Answer:
rules out DNS entirely — you got FAR enough to attempt a TCP connection — and rules out "nothing
is listening at all" being the ENTIRE problem elsewhere; "refused" specifically means something
actively rejected the connection, most often nothing listening on that exact port.)
**Optional extension:** contrast "Connection refused" vs. "Connection timed out" vs. "Could not
resolve host" as three DIFFERENT failure signatures pointing at different layers — worth a full
discussion pass if time allows, since Module 5 depends on students already sensing this
distinction.
@@ -0,0 +1,56 @@
# Demonstration: Three Failures That Sound Similar But Aren't
## OBSERVE
All three of these LOOK like "it didn't work" — but each one rules out and rules in completely
different layers.
```
$ curl http://this-name-does-not-resolve.invalid/
curl: (6) Could not resolve host: this-name-does-not-resolve.invalid
```
## HYPOTHESIS
Which layer does THIS failure point at — DNS, routing, the port, or the application itself?
## INSPECT
"Could not resolve host" happens BEFORE any network connection is even attempted — it's purely a
DNS failure. No IP was ever found to connect to.
## CHANGE
Now a different failure, against a name that DOES resolve:
```
$ curl http://localhost:9999/
curl: (7) Failed to connect to localhost port 9999: Connection refused
```
DNS/name resolution already succeeded here (`localhost` always resolves). "Connection refused"
means a TCP packet reached the destination and was ACTIVELY rejected — almost always because
nothing is listening on that port.
## VERIFY
A third signature, distinct from both:
```
$ curl http://10.255.255.1:80/
curl: (28) Failed to connect to 10.255.255.1 port 80: Connection timed out
```
No response at all, not even a rejection — usually means the destination is unreachable
(no route, or blocked silently), not that something is running but declining you.
## Instructor Notes
**Demo goal:** Get students to viscerally distinguish these three error MESSAGES as three
different diagnostic starting points, not interchangeable "it's broken" signals.
**Approximate timing:** 10 minutes live.
**Common mistake to watch for:** treating every connection failure as "the network is down" —
each of these three messages should immediately narrow which layer to investigate next, per
Module 5's troubleshooting exercise.
@@ -0,0 +1,45 @@
# Knowledge Check: Networking Mental Model
---
**1. What's the actual difference between an IP address and a port?**
*Answer:* An IP address identifies a specific machine on a network; a port identifies WHICH
service on that machine, since one IP can host many services simultaneously.
---
**2. `curl` reports "Could not resolve host." Which layer failed?**
*Answer:* DNS — the name never got translated into an IP address, so no connection attempt was
even made.
---
**3. `curl` reports "Connection refused" instead. What does that specifically tell you?**
*Answer:* The name resolved fine and the destination was reachable, but nothing is listening on
the port you tried — the connection attempt was actively rejected, not silently ignored.
---
**4. What is a default gateway for?**
*Answer:* It's where your machine sends traffic whose destination ISN'T in its own local subnet
— effectively "ask this router to figure out how to get there."
---
**5. True or false: `0.0.0.0` is a real destination address you could `curl`.**
*Answer:* False (in the way most students initially assume) — when a SERVICE binds to
`0.0.0.0`, it means "listen on every interface," a bind convention, not a real host you'd
connect to as a destination.
## Instructor Notes
**Facilitation:** Questions 2-3 are worth spending real time on together — the point isn't
memorizing the exact error text, it's the underlying "which layer does this rule out" reasoning.
**Approximate timing:** 8 minutes (slightly longer than a typical knowledge check, given this
module's weight).
@@ -0,0 +1,33 @@
# Guided Exercise: Diagnose From the Symptom Alone
This is a paper exercise — reason through each case using only the mental model, before any
hands-on Lab work.
## Your task
For each symptom, name which layer(s) it rules OUT, and which remain genuine suspects:
1. `ping 8.8.8.8` succeeds, but `curl https://example.com` fails with "Could not resolve host."
2. `dig example.com` returns a correct-looking IP address, but `curl https://example.com` times
out with no response at all.
3. `curl http://internal-service:8080/` returns "Connection refused," but
`curl http://internal-service:9090/` (a different port, same host) succeeds.
## A worked shape to check yourself against
1. Rules out: basic connectivity/routing to the internet (ping succeeded). Remaining suspect:
DNS specifically — something is preventing name resolution even though raw IP connectivity
works.
2. Rules out: DNS (resolution clearly succeeded). Remaining suspects: routing/reachability to
that specific IP, or a firewall silently dropping the connection — "no response at all" is the
timeout signature, not the "actively rejected" signature.
3. Rules out: DNS AND general reachability to that host (port 9090 worked). Remaining suspect:
narrowed all the way down to "nothing is listening on port 8080 specifically" — the most
precisely isolated diagnosis of the three.
## Instructor Notes
**Facilitation:** Item 3 is the strongest example of "isolate one variable" reasoning (same
host, different port) — worth highlighting explicitly as the gold-standard diagnostic pattern.
**Approximate timing:** 10-12 minutes live discussion; 8 minutes self-paced.
@@ -0,0 +1,30 @@
# Reflection: One Model, Reused Everywhere From Here On
## Reflection questions
1. Before this module, how would you have described "the network is broken" to a colleague?
How would you describe it now, more precisely?
2. This module deliberately avoided subnet math. Do you feel that cost you anything real, given
the module's actual goal (reasoning about WHERE a failure is, not calculating addresses)?
## Real Ops takeaway
**Every networking topic for the rest of ROOT_ is this same chain, viewed from a different
altitude.** A container's published port is still "port," a Kubernetes Service is still "how do
I reach the right destination via a stable address," Ingress is still "routing decisions," and
NetworkPolicy is still "which subnets/ports are allowed to reach which." You are not about to
learn a new mental model in ROOT 04 or ROOT 05 — you're about to see this SAME one applied one
layer up, repeatedly. That's why this module got extra weight: the investment compounds.
This module is complete. Continue to Module 4, where these ideas become real commands.
## Instructor Notes
**Discussion questions for a live classroom:** question 1 is worth collecting several answers
before and after — the change in precision is itself the evidence this module worked.
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced writing.
**Milestone note:** this is the "critical module" this course was built around — consider
confirming as a group that everyone can walk the full chain (host→interface→IP→subnet→gateway→
routing→DNS→port→TCP/UDP→application) from memory before moving on.
@@ -0,0 +1,74 @@
# Linux Network Inspection
## Every command below is really a question
Module 3 gave you the model. This module gives you the commands — but treat each one as
ANSWERING a specific question, not as syntax to memorize.
## "What addresses does this machine have?" — `ip addr`
```
$ ip -4 addr show eth0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
```
This machine's IPv4 address is `172.17.0.2`, on a `/16` subnet.
## "Where will this machine send traffic?" — `ip route`
```
$ ip route
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2
```
The second line says: anything in `172.17.0.0/16` is reachable directly. The first line (the
`default` route) says: everything else goes through `172.17.0.1` — the default gateway from
Module 3, now a real line in a real table.
## "What TCP ports are listening?" — `ss -lnt`
```
$ ss -lnt
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
```
Something is listening on port 80, on every interface (`0.0.0.0` as a BIND address, exactly the
convention Module 3 flagged). `ss` replaced the older `netstat` on modern Linux — same idea.
## "What does DNS say this name resolves to?" — `getent hosts` / `dig`
```
$ getent hosts example.test
10.0.0.5 example.test
```
`getent hosts` checks the same resolution path the rest of the system uses (local `/etc/hosts`
entries first, then DNS) — a reliable first choice. `dig <name>` queries DNS directly and shows
more detail, useful once you specifically suspect a real DNS server issue rather than a local
override.
## "Can the application actually respond?" — `curl`
```
$ curl -sf http://localhost:80/ -o /dev/null -w '%{http_code}\n'
200
```
Everything before this point (address, route, port, DNS) can be perfect, and the APPLICATION
itself can still be broken — `curl` is the only one of these tools that actually tests the full
chain end to end, including the application's own response.
## Instructor Notes
**Lesson goal:** Convert Module 3's mental model into muscle-memory commands, each explicitly
framed as answering one specific diagnostic question.
**Approximate timing:** 15 minutes read; 10 minutes live.
**Common misconception to watch for:** treating all five commands as redundant ways to "check
the network" — reinforce that each answers a DIFFERENT layer's question, and a real diagnosis
usually needs more than one.
**Demo idea:** run all five in sequence against this Lab's own nginx, narrating which Module 3
layer each one answers.
@@ -0,0 +1,56 @@
# Demonstration: The Same Five Questions, Answered Live
## OBSERVE
```
$ ip -4 addr show eth0
inet 172.17.0.2/16 ...
```
"What address do I have?" — answered.
## HYPOTHESIS
Predict what `ip route` will show before running it — specifically, predict whether there will
be a `default` line.
## INSPECT
```
$ ip route
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 ...
```
Confirmed — a `default` route exists, meaning traffic to anywhere outside `172.17.0.0/16` has
somewhere to go.
## CHANGE
```
$ ss -lnt
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
```
"What's listening?" — port 80, confirmed.
## VERIFY
```
$ curl -sf http://localhost/ -o /dev/null -w '%{http_code}\n'
200
```
The full chain, verified end to end with real evidence at each layer — not "the network seems
fine," but five specific, falsifiable answers.
## Instructor Notes
**Demo goal:** Chain all five commands together as ONE continuous diagnostic narrative, not
five disconnected syntax examples.
**Approximate timing:** 8 minutes live.
**Common mistake to watch for:** running `curl` first and stopping at "it worked" without ever
having separately confirmed address/route/port — fine when things work, but the WRONG habit for
when they don't (Module 5's Lab requires the full sequence).
@@ -0,0 +1,39 @@
# Knowledge Check: Linux Network Inspection
---
**1. Which command answers "what TCP ports are listening on this machine"?**
*Answer:* `ss -lnt`.
---
**2. Which command answers "where will this machine send traffic that isn't local"?**
*Answer:* `ip route` — specifically, the `default` line in its output.
---
**3. Why is `curl` the only tool in this module that can confirm the APPLICATION itself is
working, not just the network path to it?**
*Answer:* Address, route, port, and DNS can all be perfectly correct while the application
behind them still returns an error or wrong content — `curl` is the only one of these tools that
actually completes an HTTP request and inspects the real response.
---
**4. `getent hosts` and `dig` both resolve names. When would you reach for `dig` specifically
instead of `getent hosts`?**
*Answer:* When you specifically suspect a DNS server issue and want to query DNS directly with
more detail — `getent hosts` checks the same path the rest of the system uses (including local
`/etc/hosts` overrides first), which is usually the right first choice, but doesn't show you as
much about what the actual DNS server itself said.
## Instructor Notes
**Facilitation:** Question 3 reinforces Module 3's "layers can each independently succeed or
fail" idea with a very concrete example.
**Approximate timing:** 5 minutes.
@@ -0,0 +1,28 @@
# Guided Exercise: Which Command Answers This Question?
This is a paper exercise — commit to an answer before checking it in the Lab.
## Your task
For each question, name the ONE command you'd reach for first:
1. "Does this machine have a working IP address at all?"
2. "Is anything listening on port 8080 right now?"
3. "Does `payments.internal` resolve to an address?"
4. "Is the web server actually returning a valid response, not just accepting connections?"
5. "Where does traffic to an address outside my local subnet actually go?"
## A worked shape to check yourself against
1. `ip -4 addr show`
2. `ss -lnt | grep 8080`
3. `getent hosts payments.internal`
4. `curl -sf http://.../ -o /dev/null -w '%{http_code}'`
5. `ip route`
## Instructor Notes
**Facilitation:** Have students justify WHY their chosen command answers the specific question
asked, not just that it's "a networking command" — the goal is precision, not familiarity.
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
@@ -0,0 +1,25 @@
# Reflection: Five Commands, Five Answers, One Diagnosis
## Reflection questions
1. Which of these five commands did you already know before this module? Which was
genuinely new?
2. You now have a real toolkit for Module 3's "where is the failure" question. What's still
missing before you'd feel fully confident diagnosing a real production networking issue?
## Real Ops takeaway
**Five specific commands, each answering one specific question, beat one vague instinct that
"something's wrong with the network."** This is the exact toolkit real engineers reach for
first, in this order, when handed an unfamiliar connectivity problem. Module 5 puts all five
into one continuous diagnostic story; Module 6 formalizes the discipline around using them.
This module is complete. Continue to the next module.
## Instructor Notes
**Discussion questions for a live classroom:** question 2's honest answer is usually
"experience with real, varied failures" — a good moment to set expectations that fluency
compounds with practice, not from one module.
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
@@ -0,0 +1,58 @@
# DNS and HTTP
## The complete request path
Every time a browser or `curl` fetches a URL, it walks the entire Module 3 chain in order:
```
Application (curl / browser)
↓
DNS lookup — translate the name to an IP
↓
IP address obtained
↓
Routing — figure out the path to that IP
↓
TCP connection established — a three-way handshake, underneath curl
↓
HTTP request sent
↓
Server processes it
↓
HTTP response returned
```
Nothing here is new — this is Module 3's chain and Module 4's commands, just walked start to
finish as ONE continuous story instead of five separate checks.
## Four distinct failure categories, not one
Given this path, "it doesn't work" always decomposes into exactly one of four categories:
1. **DNS failure** — the name never resolved to an IP at all (`curl`'s "Could not resolve
host").
2. **Routing/connection failure** — the IP was found, but no TCP connection could be established
— either refused (something's there but nothing's listening) or timed out (no response at
all, usually unreachable or silently blocked).
3. **TCP succeeded, but the application never responds correctly** — a connection was
established, but the server hangs, drops the connection, or the request never completes.
4. **HTTP/application failure** — a full HTTP response comes back, but it's WRONG (a 500 error,
a 404, unexpected content) — everything below the application layer worked fine.
These are genuinely different problems requiring genuinely different fixes — conflating them
(treating a 404 the same as a DNS failure) wastes real diagnostic time.
## Instructor Notes
**Lesson goal:** Make the four-category failure taxonomy explicit and distinct — this is the
direct foundation for this module's diagnostic Lab and for Module 6's formal framework.
**Approximate timing:** 15 minutes read; 10 minutes live.
**Common misconception to watch for:** treating an HTTP 500 error the same category as "the
network is down" — a 500 means the ENTIRE network path worked; the failure is purely in the
application's own logic.
**Discussion question:** "A request returns HTTP 404. Which of the four categories is that, and
what does it rule out?" (Answer: category 4, application/HTTP — it rules out DNS, routing, AND
TCP connectivity entirely, since a real HTTP response came back at all.)
@@ -0,0 +1,57 @@
# Demonstration: Walking the Complete Path, Layer by Layer
## OBSERVE
```
$ getent hosts lab-internal.test
10.99.99.42 lab-internal.test
```
DNS: succeeded.
## HYPOTHESIS
Predict: if the next step (TCP connection) fails, what would that specific error look like?
## INSPECT
```
$ curl http://lab-internal.test:9091/
curl: (7) Failed to connect to lab-internal.test port 9091: Connection refused
```
"Connection refused" — DNS already succeeded (ruled out), the failure is specifically that
nothing is listening on port 9091 at that address.
## CHANGE
Find what IS actually listening, on the correct address:
```
$ ss -lnt | grep LISTEN
LISTEN 0 5 0.0.0.0:9095 0.0.0.0:*
```
The real service is on 9095, not 9091 — a configuration/expectation mismatch, not a DNS or
routing problem.
## VERIFY
```
$ curl http://lab-internal.test:9095/
app running
```
The full path, once pointed at the CORRECT layer that was actually broken (the port, not DNS),
succeeds end to end.
## Instructor Notes
**Demo goal:** Show that a real, plausible-sounding complaint ("DNS must be broken") is
disproven by evidence in seconds, and the REAL cause is one layer downstream.
**Approximate timing:** 10 minutes live.
**Common mistake to watch for:** jumping straight to "restart everything" instead of walking
the chain layer by layer to find the SPECIFIC broken step — the whole point of this module's
Lab is resisting that shortcut.
@@ -0,0 +1,40 @@
# Knowledge Check: DNS and HTTP
---
**1. Name the four failure categories this module introduced.**
*Answer:* DNS failure, routing/connection failure, TCP-succeeds-but-application-never-responds,
and HTTP/application failure (a full response comes back but it's wrong).
---
**2. A request returns HTTP 500. What does that tell you about DNS and TCP connectivity?**
*Answer:* Both worked completely — a 500 means a full HTTP response was received, so the failure
is purely inside the application's own logic, not the network path to reach it.
---
**3. Why is "connection refused" a MORE specific diagnosis than "timed out"?**
*Answer:* "Refused" means a packet reached the destination and was actively rejected (almost
always: nothing listening on that port) — you know the destination is reachable. "Timed out"
means no response at all, which could mean unreachable, no route, or silently blocked —
genuinely less specific.
---
**4. A teammate says "DNS must be broken" because a request fails. What should you check
BEFORE agreeing?**
*Answer:* Whether the name actually resolves at all (`getent hosts` or `dig`) — many failures
that "feel like DNS" are actually one layer downstream (routing, port, or application), and
DNS itself may be working perfectly.
## Instructor Notes
**Facilitation:** Question 4 mirrors this module's own Lab almost exactly — a good moment to
preview it if not already started.
**Approximate timing:** 5 minutes.
@@ -0,0 +1,32 @@
# Guided Exercise: Categorize the Failure Before You Fix It
This is a paper exercise — commit to a category before checking it in the Lab.
## Your task
For each symptom, name which of the four failure categories (DNS / routing-connection /
TCP-hangs / HTTP-application) it belongs to:
1. `curl: (6) Could not resolve host: api.internal`
2. `curl: (7) Failed to connect to api.internal port 443: Connection refused`
3. A request eventually returns, but with HTTP 502 Bad Gateway
4. A request just hangs for 30 seconds with no response, then finally times out
## A worked shape to check yourself against
1. DNS — the name never resolved.
2. Routing/connection — DNS worked, but nothing accepted the connection on that port.
3. HTTP/application — a full HTTP response came back (502 is a real, complete response), so
DNS/routing/TCP all worked; the failure is in the application layer (often a proxy unable to
reach ITS OWN backend, which is itself worth investigating further).
4. TCP-hangs / ambiguous-timeout — this is the least specific signature; it could mean
unreachable, silently dropped by a firewall, or an application that accepted the connection
but never responds — worth narrowing further with `ss`/`ip route` before concluding.
## Instructor Notes
**Facilitation:** Item 3 (502) is a good one to slow down on — many students initially assume
any 5xx code means "the network is broken," when it actually proves the OPPOSITE for every
layer below the application.
**Approximate timing:** 10-12 minutes live discussion; 8 minutes self-paced.
@@ -0,0 +1,26 @@
# Reflection: You Fixed the Real Problem, Not the Suspected One
## Reflection questions
1. Before running any commands in this module's Lab, what would you have guessed was broken?
Was your guess right?
2. Why does correctly categorizing a failure (DNS vs. routing vs. TCP vs. application) matter
more than just "trying things until something works"?
## Real Ops takeaway
**"DNS must be broken" is one of the most common wrong first guesses in real incident response
— and disproving or confirming it takes seconds, not minutes.** The habit this module builds —
categorize the failure precisely before attempting a fix — is what separates a five-minute
diagnosis from an hour of trying unrelated things. This exact reasoning, applied to Kubernetes
Services/Ingress/DNS later, is one of the most valuable transferable skills in this entire
program.
This module is complete. Continue to the final module of this course.
## Instructor Notes
**Discussion questions for a live classroom:** question 1 works well as a live poll before
revealing the Lab's actual root cause — the gap between instinct and evidence is the lesson.
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
@@ -0,0 +1,63 @@
# Linux Troubleshooting
## The RootNode troubleshooting framework
Every module in ROOT 02 has been building toward this: a repeatable, disciplined process for
diagnosing ANY Linux-level failure, not just the specific ones you've already seen.
```
OBSERVE
↓
GATHER EVIDENCE
↓
FORM HYPOTHESIS
↓
TEST
↓
REPAIR
↓
VALIDATE
↓
PREVENT
```
## Each step, and why skipping it costs you later
- **Observe** — what's the actual, specific symptom? Not "it's broken" — the exact error, the
exact command that failed.
- **Gather evidence** — logs, process state, listening ports, config content — the raw facts,
collected BEFORE guessing at a cause.
- **Form hypothesis** — based on the evidence (not a guess made before looking), what's the most
likely cause?
- **Test** — check whether your hypothesis is actually true, ideally without yet making changes
that could mask the real problem.
- **Repair** — make the minimal change that addresses the CONFIRMED cause, not a broad "try
everything" fix.
- **Validate** — confirm the original symptom is actually gone, with real evidence, not
assumption.
- **Prevent recurrence** — what would stop this from happening again? Skipping this step is how
teams end up fixing the exact same incident repeatedly.
## Why "skip straight to fixing" costs you
Jumping straight from OBSERVE to REPAIR (skipping evidence and hypothesis) means you're guessing
— sometimes you'll get lucky, but you won't have LEARNED anything transferable, and you risk
"fixing" a symptom while leaving the actual cause untouched, guaranteeing a repeat. Every command
from Modules 1, 2, and 4 IS the "gather evidence" step, made concrete.
## Instructor Notes
**Lesson goal:** Consolidate every command this course taught into ONE disciplined process,
directly matching the identical framework `root-10-m03-troubleshooting-framework` teaches at the
Kubernetes level — introduced here first, at the Linux level, so it already feels familiar by
then.
**Approximate timing:** 15 minutes read; 10 minutes live.
**Common misconception to watch for:** treating this as a rigid checklist to recite rather than
an actual habit — the value only shows up when it's applied under a real, unfamiliar failure
(this module's Lab).
**Discussion question:** "Describe a time you fixed something without understanding WHY it
broke. Did it come back?" (Real personal examples land this lesson better than abstract
description.)
@@ -0,0 +1,64 @@
# Demonstration: Every Step, In Order, On a Real Failure
## OBSERVE
A startup script fails: `/srv/demo/start.sh` exits with an error. That's the specific, concrete
symptom — not "the service is down."
## GATHER EVIDENCE
```
$ /srv/demo/start.sh
FATAL: config file not found: /srv/demo/config/settings.conf
```
The script's own error message IS the evidence — no guessing required.
## FORM HYPOTHESIS
The config file at that exact path doesn't exist. That's the specific, testable hypothesis —
not "something's misconfigured."
## TEST
```
$ ls /srv/demo/config/
ls: cannot access '/srv/demo/config/': No such file or directory
```
Confirmed — the directory itself doesn't exist yet, not just the file.
## REPAIR
```
$ mkdir -p /srv/demo/config
$ echo "mode=production" > /srv/demo/config/settings.conf
```
The minimal fix for the CONFIRMED cause — not a broader "reinstall everything" overcorrection.
## VALIDATE
```
$ /srv/demo/start.sh
Started with config: mode=production
```
The original symptom is gone, confirmed with real output, not assumption.
## PREVENT RECURRENCE
A real prevention step here: the script should probably check for and create its config
directory on first run, or the deployment process should ensure the config exists before the
service ever starts — naming this explicitly is what separates a real fix from a lucky one.
## Instructor Notes
**Demo goal:** Show all seven steps applied in strict order to one concrete, realistic failure
— the exact structure this module's Lab requires students to repeat themselves.
**Approximate timing:** 10 minutes live.
**Common mistake to watch for:** skipping straight to REPAIR (creating the file) without first
articulating the hypothesis and testing it — even when the fix seems obvious, naming the
hypothesis explicitly is the habit this module is building.
@@ -0,0 +1,41 @@
# Knowledge Check: Linux Troubleshooting
---
**1. What's the difference between OBSERVE and GATHER EVIDENCE?**
*Answer:* OBSERVE names the specific symptom (what actually failed, precisely). GATHER EVIDENCE
collects the raw facts (logs, config, process/port state) needed to understand WHY, before
guessing at a cause.
---
**2. Why does FORM HYPOTHESIS come before TEST, rather than jumping straight to REPAIR?**
*Answer:* A hypothesis is a specific, falsifiable guess at the cause based on evidence already
gathered — testing it BEFORE repairing confirms you're about to fix the right thing, rather than
making a change that might not address the actual cause at all.
---
**3. A fix works, but the same failure happens again a week later. Which framework step was
most likely skipped?**
*Answer:* PREVENT RECURRENCE — the immediate symptom was resolved, but whatever allowed the
underlying cause to occur in the first place was never addressed.
---
**4. Why is "repair the minimal confirmed cause" better than "try several things until it
works"?**
*Answer:* A minimal, confirmed repair means you actually know what fixed it and can prevent
recurrence deliberately. Trying multiple things at once means even success leaves you unable to
say WHICH change mattered — and unable to reliably prevent the same failure next time.
## Instructor Notes
**Facilitation:** Question 3 is a good real-world check-in — ask if anyone has a personal
example of a fix that didn't actually stick.
**Approximate timing:** 5 minutes.
@@ -0,0 +1,30 @@
# Guided Exercise: Write Your Hypothesis Before You Look at the Answer
This is a paper exercise — commit to a full framework pass before touching the Lab.
## Your task
You're told: "A script that's supposed to write a `ready` marker file isn't creating it, and
exits with a non-zero code." Before running anything, write out, specifically:
1. What's your OBSERVE statement (the precise symptom, not "it's broken")?
2. What EVIDENCE would you gather first, and with what command?
3. What's ONE specific, testable hypothesis (not "something's wrong with it")?
## A worked shape to check yourself against
1. Observe: "Script X exits non-zero and does not create file Y."
2. Evidence: redirect both stdout and stderr to a file and read it —
`script.sh > log 2>&1; cat log` — the script's own error output is almost always the fastest
evidence source for a startup failure.
3. Hypothesis (once you've actually read the error): something SPECIFIC the error names —
e.g. "the script requires a file at path X that doesn't currently exist" — not a vague
restatement of the symptom.
## Instructor Notes
**Facilitation:** The value here is entirely in getting students to write a hypothesis
SPECIFIC and FALSIFIABLE enough that they could be proven wrong — "something's misconfigured"
fails that bar; "the config file at this exact path is missing" passes it.
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
@@ -0,0 +1,31 @@
# Reflection: This Is the Same Framework You'll Use for the Rest of ROOT_
## Reflection questions
1. Which framework step did you personally find easiest to skip or rush through in this
module's Lab? Why do you think that step specifically tempts shortcuts?
2. You've now used this exact seven-step framework — Observe, Gather Evidence, Form Hypothesis,
Test, Repair, Validate, Prevent Recurrence — at the Linux level. Where do you expect to use
it again?
## Real Ops takeaway
**This entire course, from Module 1's five-question service checklist through Module 5's
failure-category taxonomy, was building toward this one repeatable discipline.** You'll meet
this exact framework again, formally named, in ROOT 10 — applied to Kubernetes instead of raw
Linux. Having already practiced it here means ROOT 10 won't be teaching you a new skill, just
retargeting a skill you already have.
This module — and ROOT 02 — is complete. Continue to ROOT 03: Git & Collaborative Development.
## Instructor Notes
**Discussion questions for a live classroom:** question 1 tends to surface "Prevent Recurrence"
and "Form Hypothesis" (jumping straight to a guessed fix) as the two most commonly admitted
shortcuts — worth normalizing as a real, common temptation rather than a personal failing.
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced writing.
**Milestone note:** this is the last module of ROOT 02 — consider a short recap connecting all
six modules (services → storage → networking model → inspection tools → DNS/HTTP → framework)
before moving to ROOT 03.
@@ -0,0 +1,136 @@
-- Phase 6E — ROOT 02: Linux Administration & Networking. Adds 6 modules/activities under the
-- existing (PLANNED, zero-content) root-02-linux-administration-networking course row. Every
-- lab activity references a NEW scenario file using the existing generic scenario/validation
-- engine (docs/ROOT_FOUNDATIONS_LAB_STRATEGY.md §1) — no new mechanism. Module 3 (Networking
-- Mental Model) has no lab activity by design — a conceptual module earns READY through
-- demonstrated reasoning (its knowledge check and guided exercise), matching root-05-m00's own
-- precedent — not a forced lab.
UPDATE courses SET
status = 'ready',
objectives = '{"Answer what is running/who started it/where is its config/where are its logs for a real service","Distinguish persistent from temporary storage and measure real disk usage","Explain the host-to-application networking chain and reason about where a failure lives","Use ip/ss/getent/curl to answer specific diagnostic questions","Categorize a request failure as DNS, routing, TCP, or application","Apply the RootNode troubleshooting framework to a real Linux failure"}',
updated_at = now()
WHERE course_id = 'root-02-linux-administration-networking';
INSERT INTO course_modules (module_id, course_id, title, description, sequence, objectives, prerequisites, estimated_duration_minutes, status) VALUES
('root-02-m01-linux-system-administration', 'root-02-linux-administration-networking', 'Linux System Administration',
'The five-question service checklist (running/started-by/config/logs/verify), applied to a real running service. Target learner: has completed ROOT 01.',
1, '{"Inspect a real service''s process, config, and logs","Start, verify, and stop a service you control","Explain why this Lab has no systemd, and why the concepts transfer anyway"}', '{}', 45, 'ready'),
('root-02-m02-storage-and-filesystems', 'root-02-linux-administration-networking', 'Storage & Filesystems',
'Block devices, mounts, and the persistent-vs-temporary distinction that carries into ROOT 04''s volumes and Kubernetes storage.',
2, '{"Distinguish df from du and use both correctly","Explain persistent vs. temporary storage","Find the real consumer of disk space in a directory tree"}', '{root-02-m01-linux-system-administration}', 40, 'ready'),
('root-02-m03-networking-mental-model', 'root-02-linux-administration-networking', 'Networking Mental Model',
'THE critical module of ROOT 02: the host-to-application networking chain, and operational reasoning about where a failure lives. No lab — conceptual mastery via demonstrated reasoning.',
3, '{"Walk the full host-interface-IP-subnet-gateway-routing-DNS-port-TCP/UDP-application chain","Distinguish DNS, routing, TCP, and application failure signatures","Explain 0.0.0.0 and 127.0.0.1 correctly"}', '{root-02-m02-storage-and-filesystems}', 55, 'ready'),
('root-02-m04-linux-network-inspection', 'root-02-linux-administration-networking', 'Linux Network Inspection',
'The Networking Mental Model made hands-on: ip, ss, getent, curl, each framed as answering one specific question.',
4, '{"Use ip addr/ip route to answer address and routing questions","Use ss to find listening ports","Use getent hosts and curl to test resolution and application reachability"}', '{root-02-m03-networking-mental-model}', 45, 'ready'),
('root-02-m05-dns-and-http', 'root-02-linux-administration-networking', 'DNS & HTTP',
'The complete request path end to end, and the four-category failure taxonomy (DNS/routing/TCP/application).',
5, '{"Walk the complete DNS-to-HTTP-response request path","Categorize a failure as DNS, routing/connection, TCP, or application","Diagnose and fix a real port-mismatch failure"}', '{root-02-m04-linux-network-inspection}', 45, 'ready'),
('root-02-m06-linux-troubleshooting', 'root-02-linux-administration-networking', 'Linux Troubleshooting',
'The RootNode troubleshooting framework (Observe/Evidence/Hypothesis/Test/Repair/Validate/Prevent), applied to a real broken-startup-script failure.',
6, '{"Apply the 7-step troubleshooting framework in strict order","Diagnose a real failure from its own error output","Name a real prevention step, not just a fix"}', '{root-02-m05-dns-and-http}', 45, 'ready')
ON CONFLICT (module_id) DO NOTHING;
INSERT INTO module_activities (activity_id, module_id, sequence, activity_type, title, content_path, scenario_id, assessment_definition_id, completion_behavior, estimated_duration_minutes, status) VALUES
-- M01 (2 labs)
('root-02-m01-a01-lesson', 'root-02-m01-linux-system-administration', 1, 'lesson', 'Linux System Administration',
'content/root/root-02-linux-administration-networking/m01-linux-system-administration/01-lesson-linux-system-administration.md', NULL, NULL, 'manual_marker', 15, 'ready'),
('root-02-m01-a02-demonstration', 'root-02-m01-linux-system-administration', 2, 'demonstration', 'Demonstration: Answering the Five Questions For Real',
'content/root/root-02-linux-administration-networking/m01-linux-system-administration/02-demonstration-inspecting-a-real-service.md', NULL, NULL, 'manual_marker', 8, 'ready'),
('root-02-m01-a03-knowledge-check', 'root-02-m01-linux-system-administration', 3, 'knowledge_check', 'Knowledge Check: Linux System Administration',
'content/root/root-02-linux-administration-networking/m01-linux-system-administration/03-knowledge-check.md', NULL, NULL, 'manual_marker', 5, 'ready'),
('root-02-m01-a04-guided-exercise', 'root-02-m01-linux-system-administration', 4, 'guided_exercise', 'Guided Exercise: Plan Your Inspection Before You Type Anything',
'content/root/root-02-linux-administration-networking/m01-linux-system-administration/04-guided-exercise-planning-your-inspection.md', NULL, NULL, 'manual_marker', 8, 'ready'),
('root-02-m01-a05-lab-service-inspection', 'root-02-m01-linux-system-administration', 5, 'lab', 'Lab: Inspect a Real Running Service',
NULL, 'linux-service-inspection', NULL, 'auto_scenario', 10, 'ready'),
('root-02-m01-a06-lab-service-lifecycle', 'root-02-m01-linux-system-administration', 6, 'lab', 'Lab: Start, Verify, and Stop a Service',
NULL, 'linux-service-lifecycle', NULL, 'auto_scenario', 10, 'ready'),
('root-02-m01-a07-reflection', 'root-02-m01-linux-system-administration', 7, 'reflection', 'Reflection: You Now Have a Repeatable Checklist',
'content/root/root-02-linux-administration-networking/m01-linux-system-administration/05-reflection.md', NULL, NULL, 'manual_marker', 8, 'ready'),
-- M02
('root-02-m02-a01-lesson', 'root-02-m02-storage-and-filesystems', 1, 'lesson', 'Storage and Filesystems',
'content/root/root-02-linux-administration-networking/m02-storage-and-filesystems/01-lesson-storage-and-filesystems.md', NULL, NULL, 'manual_marker', 12, 'ready'),
('root-02-m02-a02-demonstration', 'root-02-m02-storage-and-filesystems', 2, 'demonstration', 'Demonstration: Two Different Questions, Two Different Commands',
'content/root/root-02-linux-administration-networking/m02-storage-and-filesystems/02-demonstration-df-vs-du.md', NULL, NULL, 'manual_marker', 8, 'ready'),
('root-02-m02-a03-knowledge-check', 'root-02-m02-storage-and-filesystems', 3, 'knowledge_check', 'Knowledge Check: Storage and Filesystems',
'content/root/root-02-linux-administration-networking/m02-storage-and-filesystems/03-knowledge-check.md', NULL, NULL, 'manual_marker', 5, 'ready'),
('root-02-m02-a04-guided-exercise', 'root-02-m02-storage-and-filesystems', 4, 'guided_exercise', 'Guided Exercise: Narrow Down a Disk Problem on Paper',
'content/root/root-02-linux-administration-networking/m02-storage-and-filesystems/04-guided-exercise-narrowing-down-usage.md', NULL, NULL, 'manual_marker', 8, 'ready'),
('root-02-m02-a05-lab-storage-inspection', 'root-02-m02-storage-and-filesystems', 5, 'lab', 'Lab: Find What''s Actually Using the Disk',
NULL, 'linux-storage-inspection', NULL, 'auto_scenario', 10, 'ready'),
('root-02-m02-a06-reflection', 'root-02-m02-storage-and-filesystems', 6, 'reflection', 'Reflection: "It''s Probably the Logs" Is a Guess, Not a Diagnosis',
'content/root/root-02-linux-administration-networking/m02-storage-and-filesystems/05-reflection.md', NULL, NULL, 'manual_marker', 8, 'ready'),
-- M03 (no lab)
('root-02-m03-a01-lesson', 'root-02-m03-networking-mental-model', 1, 'lesson', 'Networking Mental Model',
'content/root/root-02-linux-administration-networking/m03-networking-mental-model/01-lesson-networking-mental-model.md', NULL, NULL, 'manual_marker', 25, 'ready'),
('root-02-m03-a02-demonstration', 'root-02-m03-networking-mental-model', 2, 'demonstration', 'Demonstration: Three Failures That Sound Similar But Aren''t',
'content/root/root-02-linux-administration-networking/m03-networking-mental-model/02-demonstration-three-failure-signatures.md', NULL, NULL, 'manual_marker', 10, 'ready'),
('root-02-m03-a03-knowledge-check', 'root-02-m03-networking-mental-model', 3, 'knowledge_check', 'Knowledge Check: Networking Mental Model',
'content/root/root-02-linux-administration-networking/m03-networking-mental-model/03-knowledge-check.md', NULL, NULL, 'manual_marker', 8, 'ready'),
('root-02-m03-a04-guided-exercise', 'root-02-m03-networking-mental-model', 4, 'guided_exercise', 'Guided Exercise: Diagnose From the Symptom Alone',
'content/root/root-02-linux-administration-networking/m03-networking-mental-model/04-guided-exercise-diagnosing-from-symptoms-alone.md', NULL, NULL, 'manual_marker', 12, 'ready'),
('root-02-m03-a05-reflection', 'root-02-m03-networking-mental-model', 5, 'reflection', 'Reflection: One Model, Reused Everywhere From Here On',
'content/root/root-02-linux-administration-networking/m03-networking-mental-model/05-reflection.md', NULL, NULL, 'manual_marker', 8, 'ready'),
-- M04
('root-02-m04-a01-lesson', 'root-02-m04-linux-network-inspection', 1, 'lesson', 'Linux Network Inspection',
'content/root/root-02-linux-administration-networking/m04-linux-network-inspection/01-lesson-linux-network-inspection.md', NULL, NULL, 'manual_marker', 15, 'ready'),
('root-02-m04-a02-demonstration', 'root-02-m04-linux-network-inspection', 2, 'demonstration', 'Demonstration: The Same Five Questions, Answered Live',
'content/root/root-02-linux-administration-networking/m04-linux-network-inspection/02-demonstration-five-commands-five-questions.md', NULL, NULL, 'manual_marker', 8, 'ready'),
('root-02-m04-a03-knowledge-check', 'root-02-m04-linux-network-inspection', 3, 'knowledge_check', 'Knowledge Check: Linux Network Inspection',
'content/root/root-02-linux-administration-networking/m04-linux-network-inspection/03-knowledge-check.md', NULL, NULL, 'manual_marker', 5, 'ready'),
('root-02-m04-a04-guided-exercise', 'root-02-m04-linux-network-inspection', 4, 'guided_exercise', 'Guided Exercise: Which Command Answers This Question?',
'content/root/root-02-linux-administration-networking/m04-linux-network-inspection/04-guided-exercise-choosing-the-right-command.md', NULL, NULL, 'manual_marker', 8, 'ready'),
('root-02-m04-a05-lab-network-inspection', 'root-02-m04-linux-network-inspection', 5, 'lab', 'Lab: Answer Three Real Questions With Three Real Commands',
NULL, 'linux-network-inspection', NULL, 'auto_scenario', 10, 'ready'),
('root-02-m04-a06-reflection', 'root-02-m04-linux-network-inspection', 6, 'reflection', 'Reflection: Five Commands, Five Answers, One Diagnosis',
'content/root/root-02-linux-administration-networking/m04-linux-network-inspection/05-reflection.md', NULL, NULL, 'manual_marker', 8, 'ready'),
-- M05
('root-02-m05-a01-lesson', 'root-02-m05-dns-and-http', 1, 'lesson', 'DNS and HTTP',
'content/root/root-02-linux-administration-networking/m05-dns-and-http/01-lesson-dns-and-http.md', NULL, NULL, 'manual_marker', 15, 'ready'),
('root-02-m05-a02-demonstration', 'root-02-m05-dns-and-http', 2, 'demonstration', 'Demonstration: Walking the Complete Path, Layer by Layer',
'content/root/root-02-linux-administration-networking/m05-dns-and-http/02-demonstration-walking-the-full-path.md', NULL, NULL, 'manual_marker', 8, 'ready'),
('root-02-m05-a03-knowledge-check', 'root-02-m05-dns-and-http', 3, 'knowledge_check', 'Knowledge Check: DNS and HTTP',
'content/root/root-02-linux-administration-networking/m05-dns-and-http/03-knowledge-check.md', NULL, NULL, 'manual_marker', 5, 'ready'),
('root-02-m05-a04-guided-exercise', 'root-02-m05-dns-and-http', 4, 'guided_exercise', 'Guided Exercise: Categorize the Failure Before You Fix It',
'content/root/root-02-linux-administration-networking/m05-dns-and-http/04-guided-exercise-categorizing-real-failures.md', NULL, NULL, 'manual_marker', 10, 'ready'),
('root-02-m05-a05-lab-dns-http-diagnosis', 'root-02-m05-dns-and-http', 5, 'lab', 'Lab: DNS Resolves, But the Connection Still Fails — Why?',
NULL, 'linux-dns-http-diagnosis', NULL, 'auto_scenario', 12, 'ready'),
('root-02-m05-a06-reflection', 'root-02-m05-dns-and-http', 6, 'reflection', 'Reflection: You Fixed the Real Problem, Not the Suspected One',
'content/root/root-02-linux-administration-networking/m05-dns-and-http/05-reflection.md', NULL, NULL, 'manual_marker', 8, 'ready'),
-- M06
('root-02-m06-a01-lesson', 'root-02-m06-linux-troubleshooting', 1, 'lesson', 'Linux Troubleshooting',
'content/root/root-02-linux-administration-networking/m06-linux-troubleshooting/01-lesson-linux-troubleshooting.md', NULL, NULL, 'manual_marker', 15, 'ready'),
('root-02-m06-a02-demonstration', 'root-02-m06-linux-troubleshooting', 2, 'demonstration', 'Demonstration: Every Step, In Order, On a Real Failure',
'content/root/root-02-linux-administration-networking/m06-linux-troubleshooting/02-demonstration-the-framework-in-order.md', NULL, NULL, 'manual_marker', 10, 'ready'),
('root-02-m06-a03-knowledge-check', 'root-02-m06-linux-troubleshooting', 3, 'knowledge_check', 'Knowledge Check: Linux Troubleshooting',
'content/root/root-02-linux-administration-networking/m06-linux-troubleshooting/03-knowledge-check.md', NULL, NULL, 'manual_marker', 5, 'ready'),
('root-02-m06-a04-guided-exercise', 'root-02-m06-linux-troubleshooting', 4, 'guided_exercise', 'Guided Exercise: Write Your Hypothesis Before You Look at the Answer',
'content/root/root-02-linux-administration-networking/m06-linux-troubleshooting/04-guided-exercise-writing-your-hypothesis-first.md', NULL, NULL, 'manual_marker', 10, 'ready'),
('root-02-m06-a05-lab-troubleshooting-framework', 'root-02-m06-linux-troubleshooting', 5, 'lab', 'Lab: A Service Won''t Start — Apply the Framework, Don''t Just Guess',
NULL, 'linux-troubleshooting-framework', NULL, 'auto_scenario', 15, 'ready'),
('root-02-m06-a06-reflection', 'root-02-m06-linux-troubleshooting', 6, 'reflection', 'Reflection: This Is the Same Framework You''ll Use for the Rest of ROOT_',
'content/root/root-02-linux-administration-networking/m06-linux-troubleshooting/05-reflection.md', NULL, NULL, 'manual_marker', 8, 'ready')
ON CONFLICT (activity_id) DO NOTHING;
-- ═══════════════════════════════════════════════════════════════════════════════════════════
-- ROOT 02 course assessment — mixes real state-validation tasks with knowledge-recall MCQs
-- reasoning about service state and failure-layer diagnosis, via the existing Phase 3B engine.
-- ═══════════════════════════════════════════════════════════════════════════════════════════
INSERT INTO assessment_definitions (assessment_definition_id, type, title, description, duration_minutes, hint_policy, pool_spec, completion_rule)
VALUES (
'rootnode-linux-networking-assessment', 'exam', 'ROOT 02 Linux Administration & Networking Assessment',
'A timed check across service administration, storage, networking diagnosis, and the troubleshooting framework — combining real state-validation tasks with conceptual reasoning questions.',
35, 'disabled',
'{"required": ["linux-network-inspection", "linux-troubleshooting-framework"],
"random": [
{"count":2,"from":["linux-service-inspection","linux-service-lifecycle","linux-storage-inspection","linux-dns-http-diagnosis"]},
{"count":2,"from":["linux-troubleshooting-order-mcq","linux-service-state-mcq"]}
]}',
'{"type":"all_scenarios_passed"}'
) ON CONFLICT (assessment_definition_id) DO NOTHING;
INSERT INTO module_activities (activity_id, module_id, sequence, activity_type, title, content_path, scenario_id, assessment_definition_id, completion_behavior, estimated_duration_minutes, status) VALUES
('root-02-m06-a07-assessment', 'root-02-m06-linux-troubleshooting', 7, 'assessment', 'ROOT 02 Linux Administration & Networking Assessment', NULL, NULL, 'rootnode-linux-networking-assessment', 'auto_assessment', 35, 'ready')
ON CONFLICT (activity_id) DO NOTHING;
@@ -0,0 +1,42 @@
{
"id": "linux-dns-http-diagnosis",
"title": "DNS Resolves, But the Connection Still Fails — Why?",
"category": "Networking",
"isolation_type": "shared_namespace",
"difficulty": "Medium",
"type": "task",
"weight": 6,
"description": "## DNS Resolves, But the Connection Still Fails — Why?\n\nAn application is supposed to be reachable at `http://broken-app.test:9091/`. A teammate insists \"DNS must be broken.\" Prove or disprove that, then actually fix the problem.\n\n**Your task:**\n\n1. Confirm whether `broken-app.test` actually resolves (`getent hosts broken-app.test`) — record your finding\n2. Try `curl http://broken-app.test:9091/` and observe what actually happens\n3. Find out what's REALLY running and on which port (hint: something already answers on a different port)\n4. Fix it so `curl http://broken-app.test:9091/` succeeds with a 200 response\n\nDo not just change the app's advertised port in documentation — make the SERVICE actually listen on 9091.",
"hints": [
{
"title": "Separate DNS from connectivity",
"body": "`getent hosts broken-app.test` only tells you whether the NAME resolves — it says nothing about whether anything is listening on the port you're trying to reach. Test them as two separate questions.",
"command": "getent hosts broken-app.test"
},
{
"title": "Finding what's actually running",
"body": "`ss -lnt` lists every port something is actually listening on right now — compare that against 9091.",
"command": "ss -lnt"
},
{
"title": "The fix",
"body": "Stop whatever's running on the wrong port, then start the real service correctly on 9091.",
"command": "kill $(cat /srv/dns-http-lab/wrong.pid)\npython3 -m http.server 9091 --directory /srv/dns-http-lab &\necho $! > /srv/dns-http-lab/fixed.pid"
}
],
"setup_commands": [
{ "command": "mkdir -p /srv/dns-http-lab && echo 'app running' > /srv/dns-http-lab/index.html" },
{ "command": "grep -q broken-app.test /etc/hosts || echo '127.0.0.1 broken-app.test' >> /etc/hosts" },
{ "command": "python3 -m http.server 9095 --directory /srv/dns-http-lab >/srv/dns-http-lab/wrong.log 2>&1 & echo $! > /srv/dns-http-lab/wrong.pid" }
],
"validation": {
"commands": [
{ "description": "broken-app.test genuinely resolves (DNS was never the actual problem)", "command": "getent hosts broken-app.test | awk '{print $1}'", "expected_output": "127.0.0.1", "match": "exact" },
{ "description": "the service now actually answers on port 9091", "command": "curl -sf http://broken-app.test:9091/ -o /dev/null -w '%{http_code}'", "expected_output": "200", "match": "exact" }
]
},
"default_namespace": "default",
"teardown_commands": [
{ "command": "[ -f /srv/dns-http-lab/wrong.pid ] && kill $(cat /srv/dns-http-lab/wrong.pid) 2>/dev/null; [ -f /srv/dns-http-lab/fixed.pid ] && kill $(cat /srv/dns-http-lab/fixed.pid) 2>/dev/null; rm -rf /srv/dns-http-lab" }
]
}
@@ -0,0 +1,42 @@
{
"id": "linux-network-inspection",
"title": "Answer Three Real Questions With Three Real Commands",
"category": "Networking",
"isolation_type": "shared_namespace",
"difficulty": "Easy",
"type": "task",
"weight": 4,
"description": "## Answer Three Real Questions With Three Real Commands\n\nEach networking command answers a specific question. Practice treating them that way.\n\n**Your task:**\n\n1. \"What IP address does this machine have?\" — write your `eth0` IPv4 address to `/srv/network-lab/my-ip.txt`\n2. \"What's listening on port 80?\" — confirm something is, and write `80` to `/srv/network-lab/listening-port.txt`\n3. \"What does DNS say `lab-internal.test` resolves to?\" — a hosts entry for it already exists on this machine; write the resolved IP to `/srv/network-lab/resolved-ip.txt`",
"hints": [
{
"title": "Your own IP",
"body": "`ip -4 addr show eth0` prints your interface's IPv4 address in an `inet a.b.c.d/nn` line.",
"command": "ip -4 addr show eth0"
},
{
"title": "What's listening",
"body": "`ss -lnt` lists every TCP socket in LISTEN state and its port.",
"command": "ss -lnt"
},
{
"title": "Resolving a name",
"body": "`getent hosts <name>` resolves a hostname using the same lookup mechanism the rest of the system uses (checks /etc/hosts, then DNS) — a reliable choice when you're not sure which one will answer.",
"command": "getent hosts lab-internal.test"
}
],
"setup_commands": [
{ "command": "mkdir -p /srv/network-lab" },
{ "command": "grep -q lab-internal.test /etc/hosts || echo '10.99.99.42 lab-internal.test' >> /etc/hosts" }
],
"validation": {
"commands": [
{ "description": "correctly identified a real IPv4 address", "command": "cat /srv/network-lab/my-ip.txt", "expected_output": "^([0-9]{1,3}\\.){3}[0-9]{1,3}$", "match": "regex" },
{ "description": "correctly identified port 80 as listening", "command": "cat /srv/network-lab/listening-port.txt", "expected_output": "80", "match": "exact" },
{ "description": "correctly resolved lab-internal.test", "command": "cat /srv/network-lab/resolved-ip.txt", "expected_output": "10.99.99.42", "match": "exact" }
]
},
"default_namespace": "default",
"teardown_commands": [
{ "command": "rm -rf /srv/network-lab" }
]
}
@@ -0,0 +1,37 @@
{
"id": "linux-service-inspection",
"title": "Inspect a Real Running Service",
"category": "Linux Administration",
"isolation_type": "shared_namespace",
"difficulty": "Easy",
"type": "task",
"weight": 4,
"description": "## Inspect a Real Running Service\n\nThis Lab environment is itself served by a real running service: nginx, reverse-proxying the very terminal you're using right now. Answer the four questions every service-administration task starts with, using ONLY inspection commands (do not stop or restart it).\n\n**Your task:**\n\nCreate `/srv/service-lab/report.txt` with exactly three lines:\n\n```\nLISTENING_PORT=<the TCP port nginx is listening on>\nCONFIG_PATH=<the full path to its config file>\nLOG_DIR=<the directory its logs are written to>\n```",
"hints": [
{
"title": "Finding the listening port",
"body": "`ss -lnt` lists every TCP socket in LISTEN state and the port it's bound to.",
"command": "ss -lnt"
},
{
"title": "Finding the config and logs",
"body": "nginx's config path and log directives are visible right in its own config file.",
"command": "cat /etc/nginx/nginx.conf | head -10"
}
],
"setup_commands": [
{ "command": "mkdir -p /srv/service-lab" }
],
"validation": {
"commands": [
{ "description": "correctly identified the listening port (80)", "command": "cat /srv/service-lab/report.txt | grep '^LISTENING_PORT='", "expected_output": "LISTENING_PORT=80", "match": "exact" },
{ "description": "correctly identified the config path", "command": "cat /srv/service-lab/report.txt | grep '^CONFIG_PATH='", "expected_output": "CONFIG_PATH=/etc/nginx/nginx.conf", "match": "exact" },
{ "description": "correctly identified the log directory", "command": "cat /srv/service-lab/report.txt | grep '^LOG_DIR='", "expected_output": "LOG_DIR=/var/log/nginx", "match": "exact" },
{ "description": "nginx itself was never stopped during this exercise", "command": "pgrep -x nginx >/dev/null && echo still-running || echo stopped", "expected_output": "still-running", "match": "exact" }
]
},
"default_namespace": "default",
"teardown_commands": [
{ "command": "rm -rf /srv/service-lab" }
]
}
@@ -0,0 +1,37 @@
{
"id": "linux-service-lifecycle",
"title": "Start, Verify, and Stop a Service",
"category": "Linux Administration",
"isolation_type": "shared_namespace",
"difficulty": "Easy",
"type": "task",
"weight": 4,
"description": "## Start, Verify, and Stop a Service\n\nUnlike the previous exercise (inspecting a service you must never touch), this one is yours to fully control.\n\n**Your task:**\n\n1. Start a small HTTP service in the background, listening on port 8890: `python3 -m http.server 8890 --directory /srv/service-lab &`, and save its PID to `/srv/service-lab/service.pid`\n2. Confirm it's actually listening, and record the result to `/srv/service-lab/status-before.txt` (write exactly `listening`)\n3. Stop it using the PID you saved\n4. Confirm it's actually gone, and record the result to `/srv/service-lab/status-after.txt` (write exactly `stopped`)",
"hints": [
{
"title": "Starting and recording the PID",
"body": "`$!` after backgrounding a command holds its PID.",
"command": "mkdir -p /srv/service-lab\npython3 -m http.server 8890 --directory /srv/service-lab >/srv/service-lab/service.log 2>&1 &\necho $! > /srv/service-lab/service.pid"
},
{
"title": "Verifying and stopping",
"body": "`ss -lnt | grep 8890` confirms it's listening. `kill $(cat .../service.pid)` stops it using the PID you recorded, not a hardcoded number.",
"command": "ss -lnt | grep 8890 && echo listening > /srv/service-lab/status-before.txt\nkill $(cat /srv/service-lab/service.pid)\nsleep 1\nss -lnt | grep 8890 || echo stopped > /srv/service-lab/status-after.txt"
}
],
"setup_commands": [
{ "command": "mkdir -p /srv/service-lab" }
],
"validation": {
"commands": [
{ "description": "confirmed the service was actually listening before stopping it", "command": "cat /srv/service-lab/status-before.txt", "expected_output": "listening", "match": "exact" },
{ "description": "confirmed the service is actually stopped now", "command": "cat /srv/service-lab/status-after.txt", "expected_output": "stopped", "match": "exact" },
{ "description": "the process is genuinely no longer running", "command": "kill -0 $(cat /srv/service-lab/service.pid) 2>/dev/null && echo RUNNING || echo GONE", "expected_output": "GONE", "match": "exact" },
{ "description": "port 8890 is genuinely no longer bound", "command": "ss -lnt | grep -q 8890 && echo BOUND || echo FREE", "expected_output": "FREE", "match": "exact" }
]
},
"default_namespace": "default",
"teardown_commands": [
{ "command": "[ -f /srv/service-lab/service.pid ] && kill $(cat /srv/service-lab/service.pid) 2>/dev/null; rm -rf /srv/service-lab" }
]
}
@@ -0,0 +1,22 @@
{
"id": "linux-service-state-mcq",
"title": "Verifying a Service Is Actually Running",
"category": "Linux Administration",
"isolation_type": "shared_namespace",
"difficulty": "Easy",
"type": "mcq",
"weight": 3,
"description": "## Verifying a Service Is Actually Running\n\nA colleague says \"I restarted the service, it should be fine now.\" What's the single most reliable way to actually confirm the service is both running AND reachable on its expected port, without asking them?",
"options": [
{ "id": "a", "text": "Trust their statement — restarting almost always works" },
{ "id": "b", "text": "Check that its process exists (e.g. via ps/pgrep) AND that its expected port is in LISTEN state (e.g. via ss) — a running process alone doesn't guarantee it bound its port successfully" },
{ "id": "c", "text": "Check the service's source code for bugs" },
{ "id": "d", "text": "Wait 24 hours and see if anyone complains" }
],
"correct_option": "b",
"explanation": "A process can be running (visible in ps) while having failed to bind its expected port (e.g. it's already in use, or it crashed after startup logging but before binding) — checking both facts independently is what actually confirms \"running and reachable,\" not either one alone.",
"hints": [],
"setup_commands": [],
"default_namespace": "default",
"teardown_commands": []
}
@@ -0,0 +1,37 @@
{
"id": "linux-storage-inspection",
"title": "Find What's Actually Using the Disk",
"category": "Linux Administration",
"isolation_type": "shared_namespace",
"difficulty": "Easy",
"type": "task",
"weight": 4,
"description": "## Find What's Actually Using the Disk\n\nThree directories under `/srv/storage-lab/` (`logs`, `cache`, `data`) contain different amounts of data. \"It's probably the logs\" is a guess — find the real answer.\n\n**Your task:**\n\n1. Use `du` to measure the actual size of each of the three directories\n2. Use `df` to check overall filesystem usage on `/`\n3. Write the name of the directory (just `logs`, `cache`, or `data`) that uses the MOST space to `/srv/storage-lab/biggest.txt`",
"hints": [
{
"title": "Measuring each directory",
"body": "`du -sh <dir>` summarizes total size for one directory. Run it against all three, or use a wildcard to see them side by side.",
"command": "du -sh /srv/storage-lab/*/"
},
{
"title": "Overall filesystem usage",
"body": "`df -h /` shows total/used/available space for the filesystem root lives on — a different, complementary question from `du` (which measures a specific directory's contents).",
"command": "df -h /"
}
],
"setup_commands": [
{ "command": "mkdir -p /srv/storage-lab/logs /srv/storage-lab/cache /srv/storage-lab/data" },
{ "command": "dd if=/dev/zero of=/srv/storage-lab/logs/app.log bs=1M count=1 2>/dev/null" },
{ "command": "dd if=/dev/zero of=/srv/storage-lab/cache/blob.bin bs=1M count=10 2>/dev/null" },
{ "command": "dd if=/dev/zero of=/srv/storage-lab/data/small.txt bs=1K count=2 2>/dev/null" }
],
"validation": {
"commands": [
{ "description": "correctly identified cache/ as the largest consumer", "command": "cat /srv/storage-lab/biggest.txt", "expected_output": "cache", "match": "exact" }
]
},
"default_namespace": "default",
"teardown_commands": [
{ "command": "rm -rf /srv/storage-lab" }
]
}
@@ -0,0 +1,37 @@
{
"id": "linux-troubleshooting-framework",
"title": "A Service Won't Start — Apply the Framework, Don't Just Guess",
"category": "Troubleshooting",
"isolation_type": "shared_namespace",
"difficulty": "Medium",
"type": "task",
"weight": 6,
"description": "## A Service Won't Start — Apply the Framework, Don't Just Guess\n\nA startup script at `/srv/trouble-lab/start.sh` is failing. Don't just poke at it randomly — apply OBSERVE → GATHER EVIDENCE → FORM HYPOTHESIS → TEST → REPAIR → VALIDATE → PREVENT.\n\n**Your task:**\n\n1. **Observe**: run `/srv/trouble-lab/start.sh` and note that it fails\n2. **Gather evidence**: the script's own error output tells you exactly what's missing — read it\n3. **Form a hypothesis and repair**: create whatever the error says is missing, with the correct content\n4. **Validate**: rerun the script and confirm it now succeeds AND that `/srv/trouble-lab/service.ready` is created",
"hints": [
{
"title": "Reading the real error",
"body": "Redirect both stdout and stderr to a log file, then read it — the script's failure message names the exact missing path.",
"command": "/srv/trouble-lab/start.sh > /srv/trouble-lab/service.log 2>&1; cat /srv/trouble-lab/service.log"
},
{
"title": "The fix",
"body": "The script expects a config file at a specific path with SOME content — any non-empty content satisfies it, since the script only checks that the file exists.",
"command": "mkdir -p /srv/trouble-lab/config\necho 'mode=production' > /srv/trouble-lab/config/settings.conf\n/srv/trouble-lab/start.sh"
}
],
"setup_commands": [
{ "command": "mkdir -p /srv/trouble-lab" },
{ "command": "printf '#!/bin/bash\\nCONFIG=/srv/trouble-lab/config/settings.conf\\nif [ ! -f \"$CONFIG\" ]; then\\n echo \"FATAL: config file not found: $CONFIG\" >&2\\n exit 1\\nfi\\necho \"Started with config: $(cat $CONFIG)\"\\necho ready > /srv/trouble-lab/service.ready\\n' > /srv/trouble-lab/start.sh" },
{ "command": "chmod +x /srv/trouble-lab/start.sh" }
],
"validation": {
"commands": [
{ "description": "the service now starts successfully", "command": "/srv/trouble-lab/start.sh >/dev/null 2>&1; echo $?", "expected_output": "0", "match": "exact" },
{ "description": "the ready marker was actually created", "command": "cat /srv/trouble-lab/service.ready", "expected_output": "ready", "match": "exact" }
]
},
"default_namespace": "default",
"teardown_commands": [
{ "command": "rm -rf /srv/trouble-lab" }
]
}
@@ -0,0 +1,22 @@
{
"id": "linux-troubleshooting-order-mcq",
"title": "Where Does the Failure Actually Live?",
"category": "Troubleshooting",
"isolation_type": "shared_namespace",
"difficulty": "Medium",
"type": "mcq",
"weight": 4,
"description": "## Where Does the Failure Actually Live?\n\n`getent hosts myapp.internal` correctly returns an IP address. `curl http://myapp.internal:8080/` returns \"Connection refused.\" What does this combination actually tell you?",
"options": [
{ "id": "a", "text": "DNS is broken — fix the hosts file or DNS server first" },
{ "id": "b", "text": "The name resolves correctly, so the problem is downstream of DNS — most likely nothing is listening on port 8080 at that address" },
{ "id": "c", "text": "The application returned an error page" },
{ "id": "d", "text": "The network cable is unplugged" }
],
"correct_option": "b",
"explanation": "A successful DNS resolution proves the name-to-IP step works — it tells you nothing about whether anything is listening on the port you're trying to reach. \"Connection refused\" specifically means a TCP SYN reached the destination and was actively rejected, most commonly because no process is listening on that port. This is exactly the kind of layer-by-layer reasoning the module's framework teaches: never re-diagnose a layer that already gave you a clean answer.",
"hints": [],
"setup_commands": [],
"default_namespace": "default",
"teardown_commands": []
}