feat(root01): author Computing & Linux Foundations, and the real 01->05 prerequisite chain
Adds all 6 ROOT 01 modules (how computers run software, the shell, filesystem navigation, files & text, processes, users & permissions foundations), each with a real lesson, demonstration/guided exercise, knowledge check where warranted, a hands-on Lab, and a reflection — 35 activities, 29 new content files. Adds 6 new hands-on Linux scenarios (linux-inspect-hardware, linux-shell-basics, linux-filesystem-navigation, linux-files-and-text, linux-process-management, linux-permissions-foundations) plus 2 MCQ scenarios, using the existing generic scenario/ validation engine (docs/ROOT_FOUNDATIONS_LAB_STRATEGY.md §1) — every one tested end-to-end via the real scenario setup/validate API, including negative tests confirming they correctly FAIL when not actually solved (e.g. the process-management scenario fails while the target process is still running; the permissions scenario is tested against a real `su - labuser` denial). Wires rootnode-linux-foundations-assessment (2 required + 4 random-draw scenarios) using the existing Phase 3B assessment engine. Adds a real, server-side ROOT 01->02->03->04->05 course prerequisite chain (courses.prerequisites) — verified live on two students: one who completed ROOT 01 sees ROOT 02 unlock; a fresh student sees ROOT 02 correctly locked while 03/04/05 stay vacuously satisfied (their own prerequisite courses are still empty). ROOT 06+ and the Capstone are unaffected. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
+66
@@ -0,0 +1,66 @@
|
||||
# How Computers Run Software
|
||||
|
||||
## Hardware, OS, and application: three different layers
|
||||
|
||||
When you run a program, three distinct layers cooperate:
|
||||
|
||||
- **Hardware** — the physical CPU, RAM, and storage. It has no idea what a "file" or a "process"
|
||||
is; it just executes instructions and stores bits.
|
||||
- **The operating system (kernel)** — the layer that turns raw hardware into something usable:
|
||||
it decides which program gets the CPU next, hands out memory, and organizes storage into
|
||||
files and directories. Linux is a kernel plus a large set of standard tools around it.
|
||||
- **Applications (userspace)** — the programs you actually run. They ask the kernel for
|
||||
resources (memory, files, network) through a well-defined interface (system calls) instead of
|
||||
touching hardware directly.
|
||||
|
||||
This module gives you just enough of this model to reason about what you're actually looking at
|
||||
for the rest of ROOT_ — not a full computer architecture course.
|
||||
|
||||
## CPU: the thing that actually executes instructions
|
||||
|
||||
A CPU has some number of **cores** — each core can run one stream of instructions at a time.
|
||||
`nproc` tells you how many cores/threads Linux believes it has available. When a system feels
|
||||
"slow," it's very often because more processes want CPU time than there are cores to give them.
|
||||
|
||||
## RAM: fast, temporary, and finite
|
||||
|
||||
RAM holds the data and code a running program is actively using. It's fast but **volatile** —
|
||||
its contents disappear when power is lost, which is exactly why persistent data lives on
|
||||
storage instead. `free -h` shows you total memory, how much is in use, and how much is free.
|
||||
|
||||
## Storage: slower, but persistent
|
||||
|
||||
Storage (an SSD/disk) keeps data even when the machine is off. It's organized into a
|
||||
**filesystem** — the tree of directories and files you already have some intuition for. `df -h`
|
||||
shows how much space exists and how much is used on each mounted filesystem.
|
||||
|
||||
## Processes: a running program
|
||||
|
||||
A **process** is a program actually being executed — code plus its own private memory, tracked
|
||||
by the kernel with a unique ID (a PID). One program can have many processes running at once
|
||||
(open two terminals running the same shell, and you have two separate processes). ROOT 01
|
||||
Module 5 goes much deeper into this; for now, just know: everything you run becomes a process
|
||||
the kernel is responsible for scheduling onto a CPU core.
|
||||
|
||||
## Why this matters for everything after this module
|
||||
|
||||
Every concept the rest of ROOT_ builds on is a refinement of this same idea. A container is
|
||||
(mostly) an isolated process with its own filesystem view. A Kubernetes Pod is a group of
|
||||
containers the scheduler places onto a specific machine's CPU/RAM. None of that will make sense
|
||||
as anything other than magic if you don't first have this base model of what a computer is
|
||||
actually managing.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Give students just enough of a mental model to stop treating "the computer" as
|
||||
an opaque box — without turning this into a full CS/architecture course.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live with discussion.
|
||||
|
||||
**Common misconception to watch for:** students often conflate RAM and storage ("my computer
|
||||
has 8GB" meaning disk space). Draw the volatile-vs-persistent distinction explicitly and early.
|
||||
|
||||
**Discussion question:** "Why can't a program just talk to the hardware directly instead of
|
||||
going through the kernel?" (Answer: the kernel is what lets many programs safely share one CPU/
|
||||
one block of RAM without stepping on each other — this is worth naming explicitly, as it's the
|
||||
root of why containers and Kubernetes are even possible.)
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# Guided Exercise: Predict Before You Look
|
||||
|
||||
Before you run the real inspection commands in the Lab, commit to a guess — this is what makes
|
||||
the real numbers meaningful instead of just scrolling past them.
|
||||
|
||||
## Your task
|
||||
|
||||
On paper or in a scratch file, write down your best GUESS for:
|
||||
|
||||
1. How many CPU cores does this machine have?
|
||||
2. How much total RAM, roughly, in GB?
|
||||
3. How much free disk space is on the root filesystem?
|
||||
|
||||
Then think through: which of these three would you expect a "student Lab" environment to be
|
||||
MOST constrained on, and why? (Hint: think about what happens if 50 students are each running
|
||||
their own Lab on one shared physical machine at the same time.)
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
There's no single right numeric answer here — the point is the habit of predicting first. A
|
||||
reasonable answer to the "most constrained" question: memory and CPU are typically the tightest
|
||||
shared resource on a multi-tenant host, since every additional Lab needs its own slice of both,
|
||||
while disk is comparatively cheap to over-provision.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Have students commit to a number verbally or on a shared doc BEFORE anyone
|
||||
runs the real commands — the value is in noticing the gap between guess and reality, not in
|
||||
getting it right.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
|
||||
**Optional extension:** ask what would happen to their guess if this were a "real" laptop
|
||||
they personally own vs. a shared cloud server — good primer for later resource-limit
|
||||
discussions (ROOT 06's resource requests/limits, ROOT 09's resource governance).
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# Reflection: You Just Looked Inside the Box
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. How close was your guess to the real numbers? What surprised you most?
|
||||
2. Name one thing you now understand about "the computer" that you couldn't have explained
|
||||
before this module.
|
||||
3. When you eventually run `kubectl describe pod` and see `requests`/`limits` for CPU and
|
||||
memory, what do you now expect those numbers to actually be constraining?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Every resource limit you'll ever configure — in Docker, in Kubernetes, in a systemd unit — is
|
||||
a promise about exactly the three things this module named: CPU, memory, and storage.** You
|
||||
can't reason well about "why did my container get killed" or "why is my Pod stuck Pending"
|
||||
without this base model. This module isn't computer-science trivia — it's the vocabulary the
|
||||
rest of ROOT_ assumes you have.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 3 is worth spending real time on — it's
|
||||
the first moment students connect this abstract lesson to something they'll type as a real
|
||||
command many courses from now. Naming that connection explicitly here pays off later.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
# Linux and the Shell
|
||||
|
||||
## What "Linux" actually means
|
||||
|
||||
Linux is a **kernel** — the core piece of software from Module 1 that manages CPU, memory, and
|
||||
storage. What most people call "a Linux system" (Ubuntu, Debian, Fedora, Alpine...) is a
|
||||
**distribution**: the Linux kernel plus a chosen set of standard tools, package manager, and
|
||||
configuration conventions layered on top. Different distributions, same kernel underneath —
|
||||
which is why a command you learn on Ubuntu mostly still works on Alpine.
|
||||
|
||||
## Shell vs. terminal
|
||||
|
||||
These get used interchangeably but are different things:
|
||||
|
||||
- The **terminal** is the window/program that displays text and sends your keystrokes somewhere.
|
||||
- The **shell** is the program actually interpreting what you type as commands — `bash` is the
|
||||
shell this Lab uses. The terminal is just the display; the shell is the brain.
|
||||
|
||||
## Command structure
|
||||
|
||||
Every command follows roughly the same shape:
|
||||
|
||||
```
|
||||
command [options] [arguments]
|
||||
```
|
||||
|
||||
`ls -la /var/log` — `ls` is the command, `-la` are options (flags) that change its behavior,
|
||||
`/var/log` is the argument (what to act on). Options can usually be combined (`-l -a` = `-la`).
|
||||
|
||||
## Exit codes: how a command reports success or failure
|
||||
|
||||
Every command returns a number when it finishes: `0` means success, anything else (1-255) means
|
||||
some kind of failure. This isn't a Linux quirk — it's the exact mechanism scripts, CI pipelines,
|
||||
and (much later) Kubernetes health checks all rely on to know whether something worked. You can
|
||||
read the last command's exit code with the special variable `$?`.
|
||||
|
||||
## Getting help without leaving the terminal
|
||||
|
||||
- **Tab completion** — press Tab while typing a command or path; the shell completes it for you
|
||||
or shows you the options. This is a genuine time-saver, not just a convenience.
|
||||
- **Command history** — press the Up arrow to recall previous commands; `history` lists them.
|
||||
- **`man <command>`** — the manual page for almost any command, with every option explained.
|
||||
`man ls`, `man grep`, `man chmod` are all real, useful references — reaching for `man` before
|
||||
a search engine is a habit worth building now.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Establish shell fluency vocabulary (shell vs. terminal, exit codes, options vs.
|
||||
arguments) that every later module assumes without re-explaining.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live with a short live demo of tab
|
||||
completion and `man`.
|
||||
|
||||
**Common misconception to watch for:** students often think "Linux" and "the terminal" are the
|
||||
same thing — clarify the kernel/distribution/shell/terminal layering explicitly.
|
||||
|
||||
**Demo idea:** live-type a command that fails (e.g. `ls /nonexistent`), then run `echo $?`
|
||||
immediately after to show the exit code in real time — more convincing than describing it.
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
# Demonstration: Watching Exit Codes in Real Time
|
||||
|
||||
## OBSERVE
|
||||
|
||||
Run a command you know will succeed, then immediately check its exit code:
|
||||
|
||||
```
|
||||
$ true
|
||||
$ echo $?
|
||||
0
|
||||
```
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Now predict: what will the exit code be if the command fails instead?
|
||||
|
||||
## INSPECT
|
||||
|
||||
Run a command that fails, and check immediately:
|
||||
|
||||
```
|
||||
$ ls /this-does-not-exist
|
||||
ls: cannot access '/this-does-not-exist': No such file or directory
|
||||
$ echo $?
|
||||
2
|
||||
```
|
||||
|
||||
## CHANGE
|
||||
|
||||
Try a few other failing commands and note their codes: `grep nomatch /etc/hostname` (exit 1 —
|
||||
"no matches found," a different kind of failure than "file not found").
|
||||
|
||||
## VERIFY
|
||||
|
||||
Notice: exit codes aren't just "0 or 1" — different failures often get different non-zero
|
||||
codes. `0` always means success; the exact non-zero value's MEANING varies by command, but ANY
|
||||
non-zero value reliably means "something went wrong," which is the part every script actually
|
||||
relies on (`if command; then ... else ...`).
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Make the abstract "exit code" concept concrete by actually watching `$?` change
|
||||
in real time, immediately after each command — this is far more convincing live than described
|
||||
in prose.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** running `echo $?` after ANY other command in between (even
|
||||
`ls`) silently checks the WRONG command's exit code — emphasize "check it immediately."
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# Knowledge Check: Linux and the Shell
|
||||
|
||||
---
|
||||
|
||||
**1. What's the difference between a shell and a terminal?**
|
||||
|
||||
*Answer:* The terminal is the window/program that displays text and captures keystrokes; the
|
||||
shell (e.g. `bash`) is the program actually interpreting what you type as commands. The terminal
|
||||
is the display, the shell is the brain.
|
||||
|
||||
---
|
||||
|
||||
**2. A command exits with code `0`. What does that tell you?**
|
||||
|
||||
*Answer:* It succeeded. `0` is the universal "success" convention every script and pipeline
|
||||
relies on; any non-zero value means some kind of failure occurred.
|
||||
|
||||
---
|
||||
|
||||
**3. You've typed half a long path and can't remember the rest. What should you press?**
|
||||
|
||||
*Answer:* Tab — tab completion finishes the path (or shows the options) without you needing to
|
||||
type or remember it exactly.
|
||||
|
||||
---
|
||||
|
||||
**4. Where would you look to learn every option a command supports, without leaving the
|
||||
terminal?**
|
||||
|
||||
*Answer:* Its manual page — `man <command>`.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 1 is the one students most often blur together — worth confirming
|
||||
verbally before moving on.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Guided Exercise: Predict the Exit Code Before You Run It
|
||||
|
||||
This is a paper exercise — commit to an answer before checking it in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
For each command below, predict whether its exit code will be `0` (success) or non-zero
|
||||
(failure), and why:
|
||||
|
||||
1. `cat /etc/hostname` (a file that exists)
|
||||
2. `cat /etc/does-not-exist-at-all` (a file that doesn't)
|
||||
3. `grep root /etc/passwd` (a pattern that's very likely present)
|
||||
4. `grep zzz-not-a-real-user /etc/passwd` (a pattern that's very likely absent)
|
||||
5. `mkdir /tmp/already-exists && mkdir /tmp/already-exists` (running the same `mkdir` twice)
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
1. `0` — the file exists and is readable.
|
||||
2. Non-zero — the file doesn't exist, `cat` fails.
|
||||
3. `0` — `grep` succeeds (exit 0) when it finds at least one match.
|
||||
4. Non-zero (specifically `1`) — `grep` "fails" when it finds NO matches, even though nothing
|
||||
went technically wrong. This is a common surprise: `grep`'s exit code means "did I find a
|
||||
match," not "did an error occur."
|
||||
5. The SECOND `mkdir` fails (non-zero) — the directory already exists. The first one succeeds.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Item 4 is the one worth pausing on — many students assume "no error message"
|
||||
means "exit code 0," and `grep`'s no-match convention is a good early counter-example.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Reflection: Exit Codes Are Everywhere After This
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before this module, did you know commands returned a machine-readable success/failure
|
||||
signal at all? What did you assume happened instead?
|
||||
2. Where do you think exit codes might matter later in ROOT_ — in a script, a CI pipeline, or
|
||||
a container?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Every automated system you'll ever build or operate checks exit codes, not error messages.**
|
||||
A script deciding whether to continue, a CI pipeline deciding whether to deploy, and (much
|
||||
later) a container orchestrator deciding whether a process is healthy, all reduce to the exact
|
||||
same question this module taught you to answer by hand: did this exit `0` or not? Getting
|
||||
comfortable checking `$?` now means none of those later systems will feel like magic.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2 previews Docker/Kubernetes health
|
||||
checks well before students get there — useful to name explicitly without going deep yet.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# Navigating the Filesystem
|
||||
|
||||
## Everything is one tree, starting at `/`
|
||||
|
||||
Linux has exactly one filesystem tree, rooted at `/` (pronounced "root," unrelated to your
|
||||
username). Every file and directory, no matter what physical disk it's actually on, appears
|
||||
somewhere under this one tree — there's no separate "C:\" and "D:\" the way Windows shows
|
||||
multiple drives.
|
||||
|
||||
## Absolute vs. relative paths
|
||||
|
||||
An **absolute path** starts with `/` and always means the same location no matter where you
|
||||
currently are: `/home/student/notes.txt`. A **relative path** is interpreted relative to your
|
||||
CURRENT directory: `notes.txt` means something different depending on where you're standing
|
||||
when you type it. `.` means "this directory," `..` means "one directory up" — `cd ..` moves up
|
||||
one level, `cd ../..` moves up two.
|
||||
|
||||
## Key commands
|
||||
|
||||
- `pwd` — print your current directory (where am I?)
|
||||
- `ls` — list what's in a directory (`ls -la` shows hidden files and details)
|
||||
- `cd <path>` — change directory (absolute or relative)
|
||||
|
||||
## Hidden files
|
||||
|
||||
A file or directory starting with `.` (like `.bashrc` or `.config`) is "hidden" — `ls` skips it
|
||||
by default, `ls -a` shows it. This isn't a security feature, just a convention to keep
|
||||
configuration/metadata files out of the way of normal directory listings.
|
||||
|
||||
## The important top-level directories (conceptually, not memorized)
|
||||
|
||||
You don't need to memorize the entire Filesystem Hierarchy Standard, but recognizing these by
|
||||
purpose will save you real confusion later:
|
||||
|
||||
- **`/etc`** — system-wide configuration files (nginx's config lives at `/etc/nginx/nginx.conf`)
|
||||
- **`/var`** — variable data that changes while the system runs: logs (`/var/log`), among others
|
||||
- **`/home`** — personal directories for regular users
|
||||
- **`/tmp`** — temporary files, safe to assume anything here can disappear on reboot
|
||||
- **`/usr`** — most installed programs and their supporting files live somewhere under here
|
||||
- **`/proc`** — not real files on disk at all — a live, kernel-generated view into running
|
||||
processes and system state (you'll use this again in Module 5)
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Give students a working mental model of "one tree, absolute vs. relative
|
||||
paths" plus just enough top-level-directory recognition to not be lost later — not FHS
|
||||
memorization.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** students coming from Windows sometimes expect separate
|
||||
drives/roots — emphasize the single unified tree explicitly.
|
||||
|
||||
**Demo idea:** `cd /` then `ls`, narrating what each top-level directory is for as you see it,
|
||||
rather than listing them in the abstract.
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
# Demonstration: The Same Word, Two Different Destinations
|
||||
|
||||
## OBSERVE
|
||||
|
||||
Starting from `/home/student`, run `cd project` — you land at `/home/student/project`.
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Now, from `/home/student/project`, what do you predict `cd project` will do this time?
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ pwd
|
||||
/home/student/project
|
||||
$ cd project
|
||||
bash: cd: project: No such file or directory
|
||||
```
|
||||
|
||||
It fails — there's no `project` directory INSIDE `project` (unless you happened to create one).
|
||||
The exact same relative path meant something completely different depending on where you stood.
|
||||
|
||||
## CHANGE
|
||||
|
||||
From `/home/student/project`, run `cd ../project` instead — this correctly goes back up one
|
||||
level, then into `project` again, landing you right back where you started.
|
||||
|
||||
## VERIFY
|
||||
|
||||
`pwd` after each `cd` to confirm exactly where you ended up — this habit (verify your location
|
||||
before assuming it) prevents a huge share of "why did my command run somewhere unexpected"
|
||||
confusion later.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Make "relative paths depend on where you are" concrete and slightly surprising,
|
||||
rather than an abstract rule to memorize.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** students assume a relative path always means the SAME thing
|
||||
once they've used it once — reinforce that it's re-evaluated fresh every time, from wherever
|
||||
they currently are.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# Knowledge Check: Navigating the Filesystem
|
||||
|
||||
---
|
||||
|
||||
**1. What does `..` mean in a path?**
|
||||
|
||||
*Answer:* One directory up from your current location.
|
||||
|
||||
---
|
||||
|
||||
**2. You're at `/home/student/project`. Does `cd project` do the same thing here as it did at
|
||||
`/home/student`?**
|
||||
|
||||
*Answer:* No — a relative path is re-evaluated from your CURRENT location every time. At
|
||||
`/home/student`, `project` refers to `/home/student/project`; from inside that same directory,
|
||||
`project` would refer to `/home/student/project/project`, which likely doesn't exist.
|
||||
|
||||
---
|
||||
|
||||
**3. A file is "hidden" from a normal `ls`. What actually makes it hidden?**
|
||||
|
||||
*Answer:* Its name starts with a `.` — it's purely a naming convention `ls` respects by
|
||||
default, not a real permission or security feature. `ls -a` shows it.
|
||||
|
||||
---
|
||||
|
||||
**4. Where would you expect to find a running system's log files?**
|
||||
|
||||
*Answer:* `/var/log` — `/var` holds data that changes while the system runs.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 2 is the one worth confirming out loud — it's the core "gotcha" of
|
||||
relative paths.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# Guided Exercise: Plan a Directory Structure on Paper First
|
||||
|
||||
Before you build anything in the Lab, plan it — this is the same discipline you'll use later
|
||||
when planning a Kubernetes namespace layout or a Git repository structure.
|
||||
|
||||
## Your task
|
||||
|
||||
You're setting up a small project called `weather-app`. On paper, sketch a directory tree
|
||||
containing:
|
||||
|
||||
- A place for source code
|
||||
- A place for documentation
|
||||
- A place for tests
|
||||
- A single hidden configuration file at the project's root
|
||||
|
||||
Write out the exact `mkdir` and file-creation commands you'd use, including whether each path
|
||||
you write is absolute or relative.
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
```
|
||||
mkdir -p weather-app/src weather-app/docs weather-app/tests
|
||||
echo "ready" > weather-app/.config
|
||||
```
|
||||
|
||||
(Or with an absolute path: `mkdir -p /srv/weather-app/src ...`.) The `-p` flag matters here —
|
||||
without it, `mkdir` refuses to create `weather-app/src` if `weather-app` doesn't already exist.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Have students commit to their command in writing before revealing the worked
|
||||
answer — the value is in noticing what `-p` actually buys them, not just copying a correct
|
||||
command.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
|
||||
**Common mistake to watch for:** forgetting `mkdir -p` and getting a "No such file or
|
||||
directory" error when creating a nested path in one command — a good moment to connect back to
|
||||
Module 2's exit-code lesson (that error IS a non-zero exit code).
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# Reflection: Structure Isn't Bureaucracy, It's Communication
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Why might a real engineering team insist on a consistent directory structure across every
|
||||
project, instead of everyone organizing files however they like?
|
||||
2. Which of `/etc`, `/var`, `/home`, `/tmp`, `/usr`, `/proc` do you feel most confident you could
|
||||
explain to someone else right now? Which one still feels fuzzy?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**A predictable filesystem layout is what lets tools (and other engineers) reason about a
|
||||
system without asking you first.** `/etc` always means configuration, `/var/log` always means
|
||||
logs — this convention is exactly why a monitoring tool can be pointed at "check `/var/log`" on
|
||||
any Linux machine and just work. Container images later inherit this same convention (an
|
||||
nginx container still keeps its config at `/etc/nginx/nginx.conf`) — you're not learning a
|
||||
one-off Linux fact, you're learning a convention that outlives this specific module.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 1 connects directly to why Kubernetes
|
||||
namespaces and Git repository conventions matter later — a good moment to name that connection
|
||||
without going deep yet.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# Files and Text
|
||||
|
||||
## Basic file operations
|
||||
|
||||
- `touch <file>` — create an empty file (or update its timestamp if it exists)
|
||||
- `mkdir <dir>` — create a directory (`-p` creates any missing parent directories too)
|
||||
- `cp <src> <dst>` — copy a file
|
||||
- `mv <src> <dst>` — move OR rename a file (Linux treats renaming as "moving to a new name in
|
||||
the same place")
|
||||
- `rm <file>` — delete a file (`-r` for a directory, `-f` to skip confirmation — dangerous
|
||||
combined: `rm -rf` deletes without asking, permanently)
|
||||
|
||||
## Reading text
|
||||
|
||||
- `cat <file>` — print the whole file at once
|
||||
- `less <file>` — page through a file interactively (press `q` to quit) — the right tool for
|
||||
anything too long to read in one screen
|
||||
- `head <file>` / `tail <file>` — the first/last 10 lines by default (`-n 20` for a different
|
||||
count); `tail -f` follows a file live as new lines are appended — exactly how you'd watch a
|
||||
log file update in real time
|
||||
|
||||
## Searching text: `grep`
|
||||
|
||||
`grep PATTERN file` prints only the lines matching PATTERN. This is one of the most-used
|
||||
commands in real operations work — "find the lines that matter" beats "read everything."
|
||||
|
||||
## Pipes and redirection: composing commands
|
||||
|
||||
- **A pipe (`|`)** sends one command's output as the next command's input:
|
||||
`cat app.log | grep ERROR` — read the file, then filter it.
|
||||
- **Redirection (`>`)** sends a command's output into a file instead of the screen (overwriting
|
||||
it): `grep ERROR app.log > errors.txt`. `>>` appends instead of overwriting.
|
||||
|
||||
## stdout vs. stderr
|
||||
|
||||
A command actually has TWO separate output streams: **stdout** (normal output) and **stderr**
|
||||
(error messages), even though both print to your screen by default. This is why
|
||||
`command 2>/dev/null` (redirecting stream `2`, stderr, to nowhere) can suppress error noise
|
||||
while still showing normal output — you'll use exactly this pattern in later Labs.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Build real fluency composing small commands together — this is the single
|
||||
highest-leverage Linux skill for troubleshooting work later in ROOT_.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** students often don't realize stdout/stderr are separate
|
||||
streams until they see `2>/dev/null` change what's suppressed — worth demonstrating live.
|
||||
|
||||
**Demo idea:** run a command that produces both real output and an error in the same call
|
||||
(e.g. `ls /etc /nonexistent`) and show how `2>/dev/null` changes what appears.
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
# Demonstration: Filtering a Log Down to What Matters
|
||||
|
||||
## OBSERVE
|
||||
|
||||
A raw log file has five lines mixing `INFO` and `ERROR`:
|
||||
|
||||
```
|
||||
$ cat app.log
|
||||
INFO: boot ok
|
||||
ERROR: disk full
|
||||
INFO: retry
|
||||
ERROR: timeout
|
||||
INFO: done
|
||||
```
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict what `grep ERROR app.log` will print, before running it.
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ grep ERROR app.log
|
||||
ERROR: disk full
|
||||
ERROR: timeout
|
||||
```
|
||||
|
||||
Only the matching lines — everything else is filtered out.
|
||||
|
||||
## CHANGE
|
||||
|
||||
Now redirect that filtered result into a new file instead of just viewing it:
|
||||
|
||||
```
|
||||
$ grep ERROR app.log > errors.txt
|
||||
$ cat errors.txt
|
||||
ERROR: disk full
|
||||
ERROR: timeout
|
||||
```
|
||||
|
||||
## VERIFY
|
||||
|
||||
Count how many lines actually matched, without eyeballing them: `wc -l < errors.txt` (or
|
||||
`grep -c ERROR app.log` directly). This composition — filter, then count, then act — is the
|
||||
exact shape of a huge share of real troubleshooting work.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Show the filter → redirect → verify chain as one continuous, composable
|
||||
workflow, not three unrelated commands.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** using `>` when they meant `>>` and accidentally overwriting a
|
||||
file they wanted to append to — worth calling out explicitly since it's a real, common mistake
|
||||
with no undo.
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# Knowledge Check: Files and Text
|
||||
|
||||
---
|
||||
|
||||
**1. What's the difference between `>` and `>>`?**
|
||||
|
||||
*Answer:* `>` overwrites the target file's contents entirely; `>>` appends to the end of it,
|
||||
preserving what was already there.
|
||||
|
||||
---
|
||||
|
||||
**2. A command prints both normal output and an error message. What are these two streams
|
||||
called?**
|
||||
|
||||
*Answer:* stdout (normal output) and stderr (error messages) — two separate streams that both
|
||||
happen to print to your screen by default.
|
||||
|
||||
---
|
||||
|
||||
**3. You want to watch a log file update live, as new lines are appended, without re-running a
|
||||
command repeatedly.**
|
||||
|
||||
*Answer:* `tail -f <file>` — follows the file and prints new lines as they're written.
|
||||
|
||||
---
|
||||
|
||||
**4. `cat app.log | grep ERROR`. What does the `|` actually do?**
|
||||
|
||||
*Answer:* It pipes `cat`'s output directly into `grep`'s input, so `grep` filters the file's
|
||||
contents without needing a separate temporary file.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 2 is worth a live show-of-hands check — many students haven't
|
||||
consciously registered that stdout/stderr are separate until this module.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Guided Exercise: Which Tool Actually Answers the Question?
|
||||
|
||||
This is a paper exercise — commit to an answer before checking it in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
For each situation, name the ONE command (or short pipeline) you'd reach for first:
|
||||
|
||||
1. You want to see just the last 20 lines of a 50,000-line log file.
|
||||
2. You want to know how many lines in a file contain the word "timeout."
|
||||
3. You want to watch a log file update live while a process is running.
|
||||
4. You want to see a file's full contents but it's too long for one screen and you want to
|
||||
scroll through it interactively.
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
1. `tail -n 20 file.log`
|
||||
2. `grep -c timeout file.log`
|
||||
3. `tail -f file.log`
|
||||
4. `less file.log`
|
||||
|
||||
Notice: `cat` would technically "work" for #4 too, but it dumps everything at once with no way
|
||||
to scroll back — `less` is the actual right tool once a file is longer than one screen.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Item 4 is worth discussing explicitly — "it works" isn't the same bar as
|
||||
"it's the right tool for this situation," and that distinction matters more as commands compose
|
||||
into real troubleshooting workflows.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Reflection: Filtering Is a Reasoning Skill, Not a Syntax Trick
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before this module, how would you have found "the important lines" in a huge log file?
|
||||
How does that compare to `grep`?
|
||||
2. Which combination (pipe, redirect, or a specific command like `tail -f`) do you expect to
|
||||
use the most once you're doing real troubleshooting work?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**"Read everything and eyeball it" does not scale — filtering does.** Every troubleshooting
|
||||
framework you'll learn later in ROOT_ (starting with ROOT 02's own version, then again more
|
||||
formally in ROOT 10) depends on being able to quickly narrow a huge amount of system output down
|
||||
to the handful of lines that actually matter. `grep`, pipes, and redirection are the first real
|
||||
tools you have for that — not a syntax exercise, a reasoning skill.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 1 often produces good answers ("I'd
|
||||
just scroll and read") that make the value of filtering obvious by contrast — let students
|
||||
articulate this themselves rather than stating it for them.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Processes
|
||||
|
||||
## What a process actually is
|
||||
|
||||
A **process** is a running instance of a program — its own code, its own private memory, and a
|
||||
unique identifier the kernel tracks called a **PID** (Process ID). Run the same program twice
|
||||
(open two terminals both running `bash`), and you get two separate processes with two different
|
||||
PIDs, even though they're running identical code.
|
||||
|
||||
## Foreground vs. background
|
||||
|
||||
A command you run normally occupies your terminal until it finishes (**foreground**) — you
|
||||
can't type anything else until it's done. Append `&` to run it in the **background** instead,
|
||||
freeing your terminal immediately while the process keeps running. `sleep 300 &` starts a
|
||||
300-second sleep in the background and hands your prompt right back.
|
||||
|
||||
## Inspecting processes: `ps` and `top`
|
||||
|
||||
- `ps aux` — a snapshot of every process currently running, including its PID, how much CPU/
|
||||
memory it's using, and the exact command that started it.
|
||||
- `top` (or `htop`, if installed) — the same idea, but live-updating and sorted by resource
|
||||
usage, useful for "what's actually eating CPU/memory right now."
|
||||
|
||||
## Stopping a process: `kill` and signals
|
||||
|
||||
`kill <PID>` doesn't instantly destroy a process — it sends it a **signal**, a message the
|
||||
process can (usually) choose how to respond to. The default signal, `SIGTERM` (15), politely
|
||||
asks a process to shut down, giving it a chance to clean up first. `kill -9 <PID>` sends
|
||||
`SIGKILL`, which the kernel enforces immediately with no chance for the process to react —
|
||||
appropriate only when a process is truly stuck and ignoring `SIGTERM`. `kill -0 <PID>` is a
|
||||
special case: it sends NO real signal at all, just checks whether the PID still exists —
|
||||
exactly how you confirm "did that process actually stop" without side effects.
|
||||
|
||||
## Process hierarchy
|
||||
|
||||
Every process (except the very first one at boot) has a **parent** — the process that started
|
||||
it. Your shell is the parent of every command you run from it. This matters later: a parent
|
||||
process's own lifecycle can affect its children (this is part of why a container, which is
|
||||
often just one process tree, behaves the way it does when its main process exits).
|
||||
|
||||
## Why this matters for containers and Kubernetes
|
||||
|
||||
A container is, underneath, mostly an isolated process (or small process tree) with its own
|
||||
view of the filesystem and network — when people say "a container exited," they mean its main
|
||||
process exited, exactly the same event you'll cause on purpose in this module's Lab. Kubernetes'
|
||||
entire job, at its core, is deciding which machine a process (inside a container) runs on and
|
||||
restarting it if it dies unexpectedly — none of that will feel intuitive without this module's
|
||||
model of what a process actually is.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Build a real process mental model (PID, foreground/background, signals,
|
||||
parent/child) that ROOT 04 (containers) and ROOT 06 (Kubernetes workloads) both directly build
|
||||
on without re-explaining.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** students often think `kill` always means "forcefully
|
||||
destroy" — clarify that plain `kill` sends a polite, ignorable request (SIGTERM), and `-9` is
|
||||
the forceful escalation, not the default.
|
||||
|
||||
**Discussion question:** "What do you think happens to a container if its main process exits on
|
||||
its own, versus if something outside kills it?" (Don't need a complete answer yet — just plant
|
||||
the question for later.)
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
# Demonstration: Watching a Process Actually Terminate
|
||||
|
||||
## OBSERVE
|
||||
|
||||
Start a long-running background process and note its PID:
|
||||
|
||||
```
|
||||
$ sleep 300 &
|
||||
[1] 1421
|
||||
```
|
||||
|
||||
`1421` is the PID (yours will differ every time).
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict: will `kill -0 1421` print anything if the process is still running?
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ kill -0 1421 && echo "still running"
|
||||
still running
|
||||
```
|
||||
|
||||
`kill -0` sends no real signal — silence (or, here, our own `echo`) means the process exists.
|
||||
|
||||
## CHANGE
|
||||
|
||||
Now actually terminate it:
|
||||
|
||||
```
|
||||
$ kill 1421
|
||||
```
|
||||
|
||||
## VERIFY
|
||||
|
||||
```
|
||||
$ kill -0 1421 2>/dev/null && echo "still running" || echo "gone"
|
||||
gone
|
||||
```
|
||||
|
||||
The exact same check now reports the opposite result — real, observable proof the process is
|
||||
gone, not an assumption.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Make "checking before and after" a visible habit, not just narrated — students
|
||||
should see the SAME command return a different real answer once state actually changed.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** reaching for `kill -9` by default "to be safe" — reinforce that
|
||||
plain `kill` (SIGTERM) is the correct first attempt almost always.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Knowledge Check: Processes
|
||||
|
||||
---
|
||||
|
||||
**1. What does a PID identify?**
|
||||
|
||||
*Answer:* One specific running instance of a program — the kernel's unique identifier for that
|
||||
process, distinct from every other running process, even other copies of the same program.
|
||||
|
||||
---
|
||||
|
||||
**2. What's the actual difference between `kill <pid>` and `kill -9 <pid>`?**
|
||||
|
||||
*Answer:* Plain `kill` sends SIGTERM, a polite request the process can choose to handle (clean
|
||||
up, then exit). `kill -9` sends SIGKILL, which the kernel enforces immediately with no chance
|
||||
for the process to react — an escalation for when SIGTERM is ignored, not the default choice.
|
||||
|
||||
---
|
||||
|
||||
**3. You want to confirm a process is still running WITHOUT affecting it in any way. What do
|
||||
you use?**
|
||||
|
||||
*Answer:* `kill -0 <pid>` — sends no real signal, only checks whether the PID still exists.
|
||||
|
||||
---
|
||||
|
||||
**4. Why might understanding processes matter for understanding containers later?**
|
||||
|
||||
*Answer:* A container is, underneath, mostly an isolated process (or small process tree) — a
|
||||
container "exiting" is really its main process exiting, the exact same event this module's Lab
|
||||
causes on purpose.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 2 is worth confirming explicitly — "kill -9 by default" is a common,
|
||||
worth-correcting habit.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# Guided Exercise: Choosing the Right Signal
|
||||
|
||||
This is a paper exercise — commit to an answer before checking it in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
For each situation, decide: plain `kill` (SIGTERM) first, or is `kill -9` (SIGKILL) actually
|
||||
justified?
|
||||
|
||||
1. A normal web server process you want to shut down for a routine restart.
|
||||
2. A process that received SIGTERM two minutes ago and is still running, showing no signs of
|
||||
responding.
|
||||
3. A script you just started by mistake, that hasn't done anything harmful yet.
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
1. Plain `kill` — give it the chance to shut down cleanly (close connections, flush data, etc).
|
||||
2. `kill -9` is now justified — you already gave it the polite chance, and it didn't respond.
|
||||
3. Plain `kill` first, still — "just started" doesn't change the default; SIGKILL is a last
|
||||
resort, not a shortcut for convenience.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Item 3 sometimes trips students who reach for `-9` out of impatience — worth
|
||||
reinforcing that SIGTERM-first is the discipline REGARDLESS of urgency, since a process that
|
||||
ignores SIGTERM is the actual signal that escalation is warranted.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Reflection: You Just Stopped a Real Process, Not a Simulation
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before checking `kill -0` after your `kill`, how confident were you the process was
|
||||
actually gone? What would you have done if it hadn't stopped?
|
||||
2. Why do you think SIGTERM-before-SIGKILL matters more for a real production service than for
|
||||
a throwaway script in a Lab?
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**"I ran the kill command" is not the same as "I confirmed the process is gone."** This module's
|
||||
Lab required you to verify state, not just take an action and assume it worked — that
|
||||
verification step is exactly what separates a confident operator from someone hoping for the
|
||||
best. You'll repeat this exact pattern (act, then verify) constantly for the rest of ROOT_,
|
||||
starting as soon as ROOT 04's containers and continuing all the way through Kubernetes
|
||||
troubleshooting.
|
||||
|
||||
This module is complete. Continue to the next module.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2 is a good bridge to ROOT 06's later
|
||||
discussion of graceful shutdown/`terminationGracePeriodSeconds` — plant the idea now without
|
||||
naming Kubernetes specifics yet.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
# Users and Permissions Foundations
|
||||
|
||||
## Users, groups, and ownership
|
||||
|
||||
Every file on Linux has an **owner** (a user) and a **group**. `ls -la` shows both: the third
|
||||
column is the owning user, the fourth is the owning group. Multiple users can belong to the same
|
||||
group, which is how permissions get shared across a team without giving every individual user
|
||||
direct ownership.
|
||||
|
||||
## Read, write, execute — for three different audiences
|
||||
|
||||
Every file has permissions for THREE separate audiences: the owner, the group, and everyone
|
||||
else ("other"). Each audience gets some combination of:
|
||||
|
||||
- **r**ead — can view the file's contents (or list a directory's contents)
|
||||
- **w**rite — can modify the file (or create/delete files inside a directory)
|
||||
- e**x**ecute — can run the file as a program (or enter/traverse into a directory)
|
||||
|
||||
`ls -la` shows this as a 10-character string like `-rw-r--r--`: the first character is the file
|
||||
type, then three groups of `rwx` for owner/group/other.
|
||||
|
||||
## Reading and setting permissions as octal numbers
|
||||
|
||||
Each `rwx` triplet can be written as one digit: read=4, write=2, execute=1, summed together.
|
||||
`rw-` = 4+2 = `6`. `r--` = `4`. So `644` means: owner can read+write (6), group can read-only
|
||||
(4), everyone else can read-only (4) — a very common, sensible default for a regular file.
|
||||
`chmod 644 <file>` sets exactly that.
|
||||
|
||||
## `sudo`: temporary elevated privilege
|
||||
|
||||
Rather than logging in AS the root user (with permanent full access to everything), `sudo
|
||||
<command>` runs just that one command with elevated privileges, after checking you're allowed
|
||||
to. This is deliberate: the less time you spend with full privileges, the smaller the window for
|
||||
a mistake (or a compromised session) to do serious damage — the exact same reasoning behind
|
||||
"least privilege," which this module introduces by name for the first time.
|
||||
|
||||
## Least privilege: the principle, not just the command
|
||||
|
||||
**Least privilege** means: grant exactly the access something needs to do its job, and nothing
|
||||
more. A user who only needs to read a file shouldn't also be able to write it. A process that
|
||||
only needs to read one directory shouldn't have write access to the whole filesystem. This
|
||||
principle isn't specific to Linux permissions — you'll meet it again, in a much more elaborate
|
||||
form, in ROOT 09's Kubernetes RBAC and SecurityContext content. This module is where it starts.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Lesson goal:** Build real fluency with rwx/octal permissions AND plant the least-privilege
|
||||
PRINCIPLE explicitly — not just command syntax, since ROOT 09 depends on students already
|
||||
having this vocabulary.
|
||||
|
||||
**Approximate timing:** 15 minutes read; 10 minutes live.
|
||||
|
||||
**Common misconception to watch for:** students often memorize "644 = read/write" without being
|
||||
able to derive it from 4+2+1 — walk through the arithmetic explicitly rather than treating the
|
||||
numbers as magic.
|
||||
|
||||
**Discussion question:** "Why not just make everything readable and writable by everyone, to
|
||||
avoid permission headaches?" (Good setup for the least-privilege principle — let students
|
||||
generate the security argument themselves before you confirm it.)
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# Demonstration: A Real Permission Denial, and the Real Fix
|
||||
|
||||
## OBSERVE
|
||||
|
||||
A file is owned by root with permissions `600` (owner read/write only):
|
||||
|
||||
```
|
||||
$ stat -c '%a %U' data.txt
|
||||
600 root
|
||||
```
|
||||
|
||||
## HYPOTHESIS
|
||||
|
||||
Predict: can a different, non-root user read this file?
|
||||
|
||||
## INSPECT
|
||||
|
||||
```
|
||||
$ su - labuser -c "cat data.txt"
|
||||
cat: data.txt: Permission denied
|
||||
```
|
||||
|
||||
Confirmed — `600` grants nothing to group or other, so `labuser` (neither the owner nor root)
|
||||
is correctly denied.
|
||||
|
||||
## CHANGE
|
||||
|
||||
Grant read access to everyone, while keeping write access limited to the owner:
|
||||
|
||||
```
|
||||
$ chmod 644 data.txt
|
||||
$ stat -c '%a' data.txt
|
||||
644
|
||||
```
|
||||
|
||||
## VERIFY
|
||||
|
||||
```
|
||||
$ su - labuser -c "cat data.txt"
|
||||
sensitive
|
||||
```
|
||||
|
||||
The exact same command that failed before now succeeds — the permission bits, not the file's
|
||||
actual content, were the only thing that changed.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Demo goal:** Show a permission denial as a real, reproducible fact (not just a described
|
||||
error message) and the exact minimal fix, not an overcorrection like `chmod 777`.
|
||||
|
||||
**Approximate timing:** 8 minutes live.
|
||||
|
||||
**Common mistake to watch for:** students reaching for `chmod 777` "to make the error go away"
|
||||
— explicitly name why that's wrong (it also grants WRITE to everyone, not just the read access
|
||||
actually needed) and connect it back to least privilege.
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
# Knowledge Check: Users and Permissions Foundations
|
||||
|
||||
---
|
||||
|
||||
**1. A colleague says "permission denied" and fixes it with `chmod 777`. What's wrong with
|
||||
that fix?**
|
||||
|
||||
*Answer:* `777` grants read, write, AND execute to the owner, group, AND everyone else — almost
|
||||
always far more access than the actual problem required. It "fixes" the symptom while violating
|
||||
least privilege, usually granting write access nobody asked for.
|
||||
|
||||
---
|
||||
|
||||
**2. What does the permission `644` actually mean, broken down?**
|
||||
|
||||
*Answer:* Owner: read+write (4+2=6). Group: read-only (4). Other: read-only (4). Nobody but the
|
||||
owner can modify the file; everyone can read it.
|
||||
|
||||
---
|
||||
|
||||
**3. Why does `sudo <command>` exist instead of just logging in as root permanently?**
|
||||
|
||||
*Answer:* It grants elevated privilege only for the one command that needs it, minimizing the
|
||||
time spent with full access — reducing the chance a mistake (or a compromised session) causes
|
||||
serious, wide damage.
|
||||
|
||||
---
|
||||
|
||||
**4. State the least-privilege principle in your own words.**
|
||||
|
||||
*Answer:* Grant exactly the access something needs to do its job, and nothing more.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Question 1 is worth discussing as a group — most students have seen `chmod
|
||||
777` somewhere online as "the fix," and unlearning that habit early matters.
|
||||
|
||||
**Approximate timing:** 5 minutes.
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# Guided Exercise: Pick the Minimal Permission, Not the Easy One
|
||||
|
||||
This is a paper exercise — commit to an answer before checking it in the Lab.
|
||||
|
||||
## Your task
|
||||
|
||||
For each situation, write the exact `chmod` octal value that grants the MINIMUM permission
|
||||
actually needed:
|
||||
|
||||
1. A private SSH key file — only the owner should ever read or write it, nobody else, no
|
||||
execute for anyone.
|
||||
2. A shared script every user on the system should be able to run, but only the owner should be
|
||||
able to edit.
|
||||
3. A log file every user should be able to read (for troubleshooting), but only a logging
|
||||
service (the owner) should write to it.
|
||||
|
||||
## A worked shape to check yourself against
|
||||
|
||||
1. `600` — owner read+write (6), nothing for group or other (0, 0). This is the real, standard
|
||||
permission for private SSH keys — SSH itself refuses to use a key file that's more open than
|
||||
this.
|
||||
2. `755` — owner read+write+execute (7), group and other read+execute (5, 5) so they can run it
|
||||
but not modify it.
|
||||
3. `644` — owner read+write (6), group and other read-only (4, 4).
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Facilitation:** Item 1 is a good moment to mention that this isn't a hypothetical rule — SSH
|
||||
genuinely refuses to use an overly-permissive private key file, a real-world consequence of
|
||||
exactly this principle.
|
||||
|
||||
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced.
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
# Reflection: Least Privilege Is a Habit, Not a One-Time Fix
|
||||
|
||||
## Reflection questions
|
||||
|
||||
1. Before this module, what would you have done the first time you hit a "permission denied"
|
||||
error? How does that compare to what you'd do now?
|
||||
2. Name one place outside of file permissions where you'd expect "grant only what's needed" to
|
||||
matter (a user account, an API key, a service's network access — anything).
|
||||
|
||||
## Real Ops takeaway
|
||||
|
||||
**Least privilege is the single idea from this entire module that reappears the most across
|
||||
the rest of ROOT_.** File permissions are just the first, most concrete place you'll apply it —
|
||||
ROOT 09 applies the exact same principle to Kubernetes RBAC (what can a user or workload
|
||||
actually DO) and SecurityContext (what can a container actually access). Getting comfortable
|
||||
asking "what's the minimum this actually needs" now, on a file you can see and touch, makes that
|
||||
same question much easier to ask later about something more abstract, like a ServiceAccount's
|
||||
permissions.
|
||||
|
||||
This module is complete. This is the end of ROOT 01 — continue to ROOT 02: Linux
|
||||
Administration & Networking.
|
||||
|
||||
## Instructor Notes
|
||||
|
||||
**Discussion questions for a live classroom:** question 2 works well as a round-robin — the
|
||||
variety of answers (API keys, database credentials, network firewall rules) is itself useful
|
||||
evidence of how far this one principle travels.
|
||||
|
||||
**Approximate timing:** 5-8 minutes live discussion; 5 minutes self-paced writing.
|
||||
|
||||
**Milestone note:** this is the last module of ROOT 01 — consider a short recap of the whole
|
||||
course's arc (hardware → shell → filesystem → files/text → processes → permissions) before
|
||||
moving on, so students see it as one coherent foundation rather than six separate topics.
|
||||
@@ -0,0 +1,148 @@
|
||||
-- Phase 6D — ROOT 01: Computing & Linux Foundations. Adds 6 modules/activities under the
|
||||
-- existing (PLANNED, zero-content) root-01-computing-linux-foundations course row seeded by
|
||||
-- Phase 6A's 022_course_content.sql. Every lab activity references a NEW scenario file
|
||||
-- (scenarios/data/linux-*.json) using the existing, generic scenario/validation engine — see
|
||||
-- docs/ROOT_FOUNDATIONS_LAB_STRATEGY.md §1 for why no new activity type or mechanism was
|
||||
-- needed. Every lesson/demonstration/knowledge_check/guided_exercise/reflection activity
|
||||
-- references a real content file under content/root/root-01-computing-linux-foundations/.
|
||||
|
||||
UPDATE courses SET
|
||||
status = 'ready',
|
||||
objectives = '{"Explain what the OS/kernel manages versus an application","Use a Linux shell confidently, including reading exit codes","Navigate the filesystem using absolute and relative paths","Manipulate files and text with pipes and redirection","Inspect and safely terminate a real process","Apply the least-privilege principle to file permissions"}',
|
||||
updated_at = now()
|
||||
WHERE course_id = 'root-01-computing-linux-foundations';
|
||||
|
||||
INSERT INTO course_modules (module_id, course_id, title, description, sequence, objectives, prerequisites, estimated_duration_minutes, status) VALUES
|
||||
('root-01-m01-how-computers-run-software', 'root-01-computing-linux-foundations', 'How Computers Run Software',
|
||||
'Hardware vs. OS vs. application, and the CPU/RAM/storage/process vocabulary the rest of ROOT_ assumes. Target learner: no assumed prior Linux/CLI experience.',
|
||||
1, '{"Explain the hardware/kernel/application layering","Describe what CPU, RAM, and storage each actually do","Inspect a real machine''s CPU, memory, and disk"}', '{}', 40, 'ready'),
|
||||
('root-01-m02-linux-and-the-shell', 'root-01-computing-linux-foundations', 'Linux and the Shell',
|
||||
'What Linux/a distribution/a shell actually are, command structure, and exit codes as the universal success/failure signal.',
|
||||
2, '{"Distinguish shell, terminal, and distribution","Read and interpret a command''s exit code","Use tab completion, history, and man pages"}', '{root-01-m01-how-computers-run-software}', 45, 'ready'),
|
||||
('root-01-m03-navigating-the-filesystem', 'root-01-computing-linux-foundations', 'Navigating the Filesystem',
|
||||
'Absolute vs. relative paths, and what the standard top-level directories are for.',
|
||||
3, '{"Navigate using absolute and relative paths correctly","Explain the purpose of /etc, /var, /home, /tmp, /usr, /proc","Build a real nested directory structure"}', '{root-01-m02-linux-and-the-shell}', 45, 'ready'),
|
||||
('root-01-m04-files-and-text', 'root-01-computing-linux-foundations', 'Files and Text',
|
||||
'File manipulation, reading text, and composing commands with pipes/redirection.',
|
||||
4, '{"Manipulate files and directories with the core file commands","Filter text with grep","Compose commands using pipes and redirection"}', '{root-01-m03-navigating-the-filesystem}', 45, 'ready'),
|
||||
('root-01-m05-processes', 'root-01-computing-linux-foundations', 'Processes',
|
||||
'The process concept, foreground/background, signals, and why this matters for containers later.',
|
||||
5, '{"Explain what a process and a PID are","Inspect running processes with ps","Terminate a process correctly, verifying the result"}', '{root-01-m04-files-and-text}', 40, 'ready'),
|
||||
('root-01-m06-users-and-permissions-foundations', 'root-01-computing-linux-foundations', 'Users & Permissions Foundations',
|
||||
'Ownership, rwx permissions, sudo, and the least-privilege principle this phase introduces for the first time in ROOT_.',
|
||||
6, '{"Read and set rwx permissions using octal notation","Explain why sudo exists instead of permanent root access","State and apply the least-privilege principle"}', '{root-01-m05-processes}', 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
|
||||
('root-01-m01-a01-lesson', 'root-01-m01-how-computers-run-software', 1, 'lesson', 'How Computers Run Software',
|
||||
'content/root/root-01-computing-linux-foundations/m01-how-computers-run-software/01-lesson-how-computers-run-software.md', NULL, NULL, 'manual_marker', 15, 'ready'),
|
||||
('root-01-m01-a02-guided-exercise', 'root-01-m01-how-computers-run-software', 2, 'guided_exercise', 'Guided Exercise: Predict Before You Look',
|
||||
'content/root/root-01-computing-linux-foundations/m01-how-computers-run-software/02-guided-exercise-inspect-hardware-on-paper.md', NULL, NULL, 'manual_marker', 5, 'ready'),
|
||||
('root-01-m01-a03-lab-inspect-hardware', 'root-01-m01-how-computers-run-software', 3, 'lab', 'Lab: Inspect What This Machine Actually Has',
|
||||
NULL, 'linux-inspect-hardware', NULL, 'auto_scenario', 10, 'ready'),
|
||||
('root-01-m01-a04-reflection', 'root-01-m01-how-computers-run-software', 4, 'reflection', 'Reflection: You Just Looked Inside the Box',
|
||||
'content/root/root-01-computing-linux-foundations/m01-how-computers-run-software/03-reflection.md', NULL, NULL, 'manual_marker', 8, 'ready'),
|
||||
-- M02
|
||||
('root-01-m02-a01-lesson', 'root-01-m02-linux-and-the-shell', 1, 'lesson', 'Linux and the Shell',
|
||||
'content/root/root-01-computing-linux-foundations/m02-linux-and-the-shell/01-lesson-linux-and-the-shell.md', NULL, NULL, 'manual_marker', 15, 'ready'),
|
||||
('root-01-m02-a02-demonstration', 'root-01-m02-linux-and-the-shell', 2, 'demonstration', 'Demonstration: Watching Exit Codes in Real Time',
|
||||
'content/root/root-01-computing-linux-foundations/m02-linux-and-the-shell/02-demonstration-exit-codes-in-action.md', NULL, NULL, 'manual_marker', 8, 'ready'),
|
||||
('root-01-m02-a03-knowledge-check', 'root-01-m02-linux-and-the-shell', 3, 'knowledge_check', 'Knowledge Check: Linux and the Shell',
|
||||
'content/root/root-01-computing-linux-foundations/m02-linux-and-the-shell/03-knowledge-check.md', NULL, NULL, 'manual_marker', 5, 'ready'),
|
||||
('root-01-m02-a04-guided-exercise', 'root-01-m02-linux-and-the-shell', 4, 'guided_exercise', 'Guided Exercise: Predict the Exit Code',
|
||||
'content/root/root-01-computing-linux-foundations/m02-linux-and-the-shell/04-guided-exercise-predicting-exit-codes.md', NULL, NULL, 'manual_marker', 8, 'ready'),
|
||||
('root-01-m02-a05-lab-shell-basics', 'root-01-m02-linux-and-the-shell', 5, 'lab', 'Lab: Reading Exit Codes Like an Engineer',
|
||||
NULL, 'linux-shell-basics', NULL, 'auto_scenario', 10, 'ready'),
|
||||
('root-01-m02-a06-reflection', 'root-01-m02-linux-and-the-shell', 6, 'reflection', 'Reflection: Exit Codes Are Everywhere After This',
|
||||
'content/root/root-01-computing-linux-foundations/m02-linux-and-the-shell/05-reflection.md', NULL, NULL, 'manual_marker', 8, 'ready'),
|
||||
-- M03
|
||||
('root-01-m03-a01-lesson', 'root-01-m03-navigating-the-filesystem', 1, 'lesson', 'Navigating the Filesystem',
|
||||
'content/root/root-01-computing-linux-foundations/m03-navigating-the-filesystem/01-lesson-filesystem-navigation.md', NULL, NULL, 'manual_marker', 15, 'ready'),
|
||||
('root-01-m03-a02-demonstration', 'root-01-m03-navigating-the-filesystem', 2, 'demonstration', 'Demonstration: The Same Word, Two Different Destinations',
|
||||
'content/root/root-01-computing-linux-foundations/m03-navigating-the-filesystem/02-demonstration-absolute-vs-relative.md', NULL, NULL, 'manual_marker', 8, 'ready'),
|
||||
('root-01-m03-a03-knowledge-check', 'root-01-m03-navigating-the-filesystem', 3, 'knowledge_check', 'Knowledge Check: Navigating the Filesystem',
|
||||
'content/root/root-01-computing-linux-foundations/m03-navigating-the-filesystem/03-knowledge-check.md', NULL, NULL, 'manual_marker', 5, 'ready'),
|
||||
('root-01-m03-a04-guided-exercise', 'root-01-m03-navigating-the-filesystem', 4, 'guided_exercise', 'Guided Exercise: Plan a Directory Structure on Paper First',
|
||||
'content/root/root-01-computing-linux-foundations/m03-navigating-the-filesystem/04-guided-exercise-planning-a-structure.md', NULL, NULL, 'manual_marker', 8, 'ready'),
|
||||
('root-01-m03-a05-lab-filesystem-navigation', 'root-01-m03-navigating-the-filesystem', 5, 'lab', 'Lab: Build a Real Project Directory Structure',
|
||||
NULL, 'linux-filesystem-navigation', NULL, 'auto_scenario', 10, 'ready'),
|
||||
('root-01-m03-a06-reflection', 'root-01-m03-navigating-the-filesystem', 6, 'reflection', 'Reflection: Structure Isn''t Bureaucracy, It''s Communication',
|
||||
'content/root/root-01-computing-linux-foundations/m03-navigating-the-filesystem/05-reflection.md', NULL, NULL, 'manual_marker', 8, 'ready'),
|
||||
-- M04
|
||||
('root-01-m04-a01-lesson', 'root-01-m04-files-and-text', 1, 'lesson', 'Files and Text',
|
||||
'content/root/root-01-computing-linux-foundations/m04-files-and-text/01-lesson-files-and-text.md', NULL, NULL, 'manual_marker', 15, 'ready'),
|
||||
('root-01-m04-a02-demonstration', 'root-01-m04-files-and-text', 2, 'demonstration', 'Demonstration: Filtering a Log Down to What Matters',
|
||||
'content/root/root-01-computing-linux-foundations/m04-files-and-text/02-demonstration-pipes-and-redirection.md', NULL, NULL, 'manual_marker', 8, 'ready'),
|
||||
('root-01-m04-a03-knowledge-check', 'root-01-m04-files-and-text', 3, 'knowledge_check', 'Knowledge Check: Files and Text',
|
||||
'content/root/root-01-computing-linux-foundations/m04-files-and-text/03-knowledge-check.md', NULL, NULL, 'manual_marker', 5, 'ready'),
|
||||
('root-01-m04-a04-guided-exercise', 'root-01-m04-files-and-text', 4, 'guided_exercise', 'Guided Exercise: Which Tool Actually Answers the Question?',
|
||||
'content/root/root-01-computing-linux-foundations/m04-files-and-text/04-guided-exercise-choosing-the-right-tool.md', NULL, NULL, 'manual_marker', 8, 'ready'),
|
||||
('root-01-m04-a05-lab-files-and-text', 'root-01-m04-files-and-text', 5, 'lab', 'Lab: Extract the Signal From a Log File',
|
||||
NULL, 'linux-files-and-text', NULL, 'auto_scenario', 10, 'ready'),
|
||||
('root-01-m04-a06-reflection', 'root-01-m04-files-and-text', 6, 'reflection', 'Reflection: Filtering Is a Reasoning Skill, Not a Syntax Trick',
|
||||
'content/root/root-01-computing-linux-foundations/m04-files-and-text/05-reflection.md', NULL, NULL, 'manual_marker', 8, 'ready'),
|
||||
-- M05
|
||||
('root-01-m05-a01-lesson', 'root-01-m05-processes', 1, 'lesson', 'Processes',
|
||||
'content/root/root-01-computing-linux-foundations/m05-processes/01-lesson-processes.md', NULL, NULL, 'manual_marker', 15, 'ready'),
|
||||
('root-01-m05-a02-demonstration', 'root-01-m05-processes', 2, 'demonstration', 'Demonstration: Watching a Process Actually Terminate',
|
||||
'content/root/root-01-computing-linux-foundations/m05-processes/02-demonstration-watching-a-process-die.md', NULL, NULL, 'manual_marker', 8, 'ready'),
|
||||
('root-01-m05-a03-knowledge-check', 'root-01-m05-processes', 3, 'knowledge_check', 'Knowledge Check: Processes',
|
||||
'content/root/root-01-computing-linux-foundations/m05-processes/03-knowledge-check.md', NULL, NULL, 'manual_marker', 5, 'ready'),
|
||||
('root-01-m05-a04-guided-exercise', 'root-01-m05-processes', 4, 'guided_exercise', 'Guided Exercise: Choosing the Right Signal',
|
||||
'content/root/root-01-computing-linux-foundations/m05-processes/04-guided-exercise-signals.md', NULL, NULL, 'manual_marker', 8, 'ready'),
|
||||
('root-01-m05-a05-lab-process-management', 'root-01-m05-processes', 5, 'lab', 'Lab: Find and Stop a Runaway Process',
|
||||
NULL, 'linux-process-management', NULL, 'auto_scenario', 10, 'ready'),
|
||||
('root-01-m05-a06-reflection', 'root-01-m05-processes', 6, 'reflection', 'Reflection: You Just Stopped a Real Process, Not a Simulation',
|
||||
'content/root/root-01-computing-linux-foundations/m05-processes/05-reflection.md', NULL, NULL, 'manual_marker', 8, 'ready'),
|
||||
-- M06
|
||||
('root-01-m06-a01-lesson', 'root-01-m06-users-and-permissions-foundations', 1, 'lesson', 'Users and Permissions Foundations',
|
||||
'content/root/root-01-computing-linux-foundations/m06-users-and-permissions-foundations/01-lesson-users-and-permissions.md', NULL, NULL, 'manual_marker', 15, 'ready'),
|
||||
('root-01-m06-a02-demonstration', 'root-01-m06-users-and-permissions-foundations', 2, 'demonstration', 'Demonstration: A Real Permission Denial, and the Real Fix',
|
||||
'content/root/root-01-computing-linux-foundations/m06-users-and-permissions-foundations/02-demonstration-permission-denied.md', NULL, NULL, 'manual_marker', 8, 'ready'),
|
||||
('root-01-m06-a03-knowledge-check', 'root-01-m06-users-and-permissions-foundations', 3, 'knowledge_check', 'Knowledge Check: Users and Permissions Foundations',
|
||||
'content/root/root-01-computing-linux-foundations/m06-users-and-permissions-foundations/03-knowledge-check.md', NULL, NULL, 'manual_marker', 5, 'ready'),
|
||||
('root-01-m06-a04-guided-exercise', 'root-01-m06-users-and-permissions-foundations', 4, 'guided_exercise', 'Guided Exercise: Pick the Minimal Permission, Not the Easy One',
|
||||
'content/root/root-01-computing-linux-foundations/m06-users-and-permissions-foundations/04-guided-exercise-least-privilege.md', NULL, NULL, 'manual_marker', 8, 'ready'),
|
||||
('root-01-m06-a05-lab-permissions', 'root-01-m06-users-and-permissions-foundations', 5, 'lab', 'Lab: Fix a Real Permission Denial',
|
||||
NULL, 'linux-permissions-foundations', NULL, 'auto_scenario', 10, 'ready'),
|
||||
('root-01-m06-a06-reflection', 'root-01-m06-users-and-permissions-foundations', 6, 'reflection', 'Reflection: Least Privilege Is a Habit, Not a One-Time Fix',
|
||||
'content/root/root-01-computing-linux-foundations/m06-users-and-permissions-foundations/05-reflection.md', NULL, NULL, 'manual_marker', 8, 'ready')
|
||||
ON CONFLICT (activity_id) DO NOTHING;
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════════════════════════════════════
|
||||
-- ROOT 01 course assessment — mixes real state-validation tasks (the same scenarios already
|
||||
-- used as labs above, plus two more) with knowledge-recall MCQs, via the existing Phase 3B
|
||||
-- assessment engine. No Kubernetes scenario is force-fit into this non-Kubernetes assessment.
|
||||
-- ═══════════════════════════════════════════════════════════════════════════════════════════
|
||||
INSERT INTO assessment_definitions (assessment_definition_id, type, title, description, duration_minutes, hint_policy, pool_spec, completion_rule)
|
||||
VALUES (
|
||||
'rootnode-linux-foundations-assessment', 'exam', 'ROOT 01 Linux Foundations Assessment',
|
||||
'A timed check across shell fluency, filesystem navigation, process management, and least-privilege permissions — combining real state-validation tasks with conceptual reasoning questions.',
|
||||
30, 'disabled',
|
||||
'{"required": ["linux-shell-basics", "linux-permissions-foundations"],
|
||||
"random": [
|
||||
{"count":2,"from":["linux-inspect-hardware","linux-filesystem-navigation","linux-files-and-text","linux-process-management"]},
|
||||
{"count":2,"from":["linux-exit-codes-mcq","linux-permissions-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-01-m06-a07-assessment', 'root-01-m06-users-and-permissions-foundations', 7, 'assessment', 'ROOT 01 Linux Foundations Assessment', NULL, NULL, 'rootnode-linux-foundations-assessment', 'auto_assessment', 30, 'ready')
|
||||
ON CONFLICT (activity_id) DO NOTHING;
|
||||
|
||||
-- ═══════════════════════════════════════════════════════════════════════════════════════════
|
||||
-- Step 19 — real ROOT 01→02→03→04→05 course prerequisite chain, server-side (courses.prerequisites),
|
||||
-- not UI-hidden alone. root-02/03/04 remain PLANNED with zero activities this phase, so per
|
||||
-- docs/COURSE_CONTENT_MODEL.md §5's "zero activities is vacuously satisfied" rule, this chain
|
||||
-- has no practical locking effect YET on 02/03/04 or on 05 (which already only real-gates once
|
||||
-- root-04 is eventually authored) — it correctly encodes the INTENDED sequence now, so a future
|
||||
-- phase authoring ROOT 02-04 doesn't need to reopen this decision, without breaking any current
|
||||
-- Kubernetes-track student's path today (root-05's real prerequisite content, root-04, is still
|
||||
-- honestly empty).
|
||||
-- ═══════════════════════════════════════════════════════════════════════════════════════════
|
||||
UPDATE courses SET prerequisites = '{root-01-computing-linux-foundations}', updated_at = now() WHERE course_id = 'root-02-linux-administration-networking';
|
||||
UPDATE courses SET prerequisites = '{root-02-linux-administration-networking}', updated_at = now() WHERE course_id = 'root-03-git-collaborative-development';
|
||||
UPDATE courses SET prerequisites = '{root-03-git-collaborative-development}', updated_at = now() WHERE course_id = 'root-04-containers-and-docker';
|
||||
UPDATE courses SET prerequisites = '{root-04-containers-and-docker}', updated_at = now() WHERE course_id = 'root-05-kubernetes-foundations';
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": "linux-exit-codes-mcq",
|
||||
"title": "Reading Exit Code Conventions",
|
||||
"category": "Linux Fundamentals",
|
||||
"isolation_type": "shared_namespace",
|
||||
"difficulty": "Easy",
|
||||
"type": "mcq",
|
||||
"weight": 3,
|
||||
"description": "## Reading Exit Code Conventions\n\nYou run `grep pattern file.txt` and it exits with code `1`, printing no error message at all. What actually happened?",
|
||||
"options": [
|
||||
{ "id": "a", "text": "The file doesn't exist" },
|
||||
{ "id": "b", "text": "grep found no lines matching the pattern — this is grep's normal way of reporting \"no match,\" not an error" },
|
||||
{ "id": "c", "text": "You don't have permission to read the file" },
|
||||
{ "id": "d", "text": "The command is broken and needs to be reinstalled" }
|
||||
],
|
||||
"correct_option": "b",
|
||||
"explanation": "grep's exit code means \"did I find at least one match,\" not \"did something go wrong.\" Exit 1 with no error message is grep's normal, expected way of reporting zero matches — a very common early surprise, since silence plus a non-zero code doesn't always mean a real failure occurred.",
|
||||
"hints": [],
|
||||
"setup_commands": [],
|
||||
"default_namespace": "default",
|
||||
"teardown_commands": []
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"id": "linux-files-and-text",
|
||||
"title": "Extract the Signal From a Log File",
|
||||
"category": "Linux Fundamentals",
|
||||
"isolation_type": "shared_namespace",
|
||||
"difficulty": "Easy",
|
||||
"type": "task",
|
||||
"weight": 4,
|
||||
"description": "## Extract the Signal From a Log File\n\nA real application log (`/srv/text-lab/app.log`) has been generated for you, mixing `INFO` and `ERROR` lines. Reading raw logs line-by-line doesn't scale — filtering does.\n\n**Your task:**\n\n1. Use `grep` to extract only the `ERROR` lines from `/srv/text-lab/app.log`\n2. Redirect the result into a new file: `/srv/text-lab/errors.txt`\n3. Count how many ERROR lines there were and save the count to `/srv/text-lab/error-count.txt`",
|
||||
"hints": [
|
||||
{
|
||||
"title": "Filtering and redirecting",
|
||||
"body": "`grep PATTERN file > newfile` writes only matching lines to a new file. Pipe to `wc -l` to count them.",
|
||||
"command": "grep ERROR /srv/text-lab/app.log > /srv/text-lab/errors.txt\ngrep -c ERROR /srv/text-lab/app.log > /srv/text-lab/error-count.txt"
|
||||
}
|
||||
],
|
||||
"setup_commands": [
|
||||
{ "command": "mkdir -p /srv/text-lab && printf 'INFO: boot ok\\nERROR: disk full\\nINFO: retry\\nERROR: timeout\\nINFO: done\\n' > /srv/text-lab/app.log" }
|
||||
],
|
||||
"validation": {
|
||||
"commands": [
|
||||
{ "description": "errors.txt contains only ERROR lines", "command": "grep -vc ERROR /srv/text-lab/errors.txt || true", "expected_output": "0", "match": "exact" },
|
||||
{ "description": "errors.txt has exactly the 2 real ERROR lines", "command": "wc -l < /srv/text-lab/errors.txt | tr -d ' '", "expected_output": "2", "match": "exact" },
|
||||
{ "description": "error-count.txt records the count", "command": "cat /srv/text-lab/error-count.txt", "expected_output": "2", "match": "exact" }
|
||||
]
|
||||
},
|
||||
"default_namespace": "default",
|
||||
"teardown_commands": [
|
||||
{ "command": "rm -rf /srv/text-lab" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"id": "linux-filesystem-navigation",
|
||||
"title": "Build a Real Project Directory Structure",
|
||||
"category": "Linux Fundamentals",
|
||||
"isolation_type": "shared_namespace",
|
||||
"difficulty": "Easy",
|
||||
"type": "task",
|
||||
"weight": 3,
|
||||
"description": "## Build a Real Project Directory Structure\n\nEvery real codebase has a directory structure that means something. Practice building and navigating one.\n\n**Your task:**\n\nUnder `/srv/fs-lab/`, create:\n\n1. A directory tree: `project/src`, `project/docs`, `project/tests`\n2. A hidden file at `project/.config` containing exactly the text `ready`\n3. Navigate into `project/src` using a RELATIVE path from `/srv/fs-lab` (not an absolute path) to confirm you understand `cd project/src` vs `cd /srv/fs-lab/project/src`",
|
||||
"hints": [
|
||||
{
|
||||
"title": "Creating the tree in one command",
|
||||
"body": "`mkdir -p` creates every intermediate directory in one call, and brace expansion lets you create three siblings at once.",
|
||||
"command": "mkdir -p /srv/fs-lab/project/{src,docs,tests}\necho -n 'ready' > /srv/fs-lab/project/.config"
|
||||
}
|
||||
],
|
||||
"setup_commands": [
|
||||
{ "command": "mkdir -p /srv/fs-lab" }
|
||||
],
|
||||
"validation": {
|
||||
"commands": [
|
||||
{ "description": "project/src exists", "command": "[ -d /srv/fs-lab/project/src ] && echo yes", "expected_output": "yes", "match": "exact" },
|
||||
{ "description": "project/docs exists", "command": "[ -d /srv/fs-lab/project/docs ] && echo yes", "expected_output": "yes", "match": "exact" },
|
||||
{ "description": "project/tests exists", "command": "[ -d /srv/fs-lab/project/tests ] && echo yes", "expected_output": "yes", "match": "exact" },
|
||||
{ "description": "hidden .config file has the right content", "command": "cat /srv/fs-lab/project/.config", "expected_output": "ready", "match": "exact" }
|
||||
]
|
||||
},
|
||||
"default_namespace": "default",
|
||||
"teardown_commands": [
|
||||
{ "command": "rm -rf /srv/fs-lab" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"id": "linux-inspect-hardware",
|
||||
"title": "Inspect What This Machine Actually Has",
|
||||
"category": "Linux Fundamentals",
|
||||
"isolation_type": "shared_namespace",
|
||||
"difficulty": "Easy",
|
||||
"type": "task",
|
||||
"weight": 3,
|
||||
"description": "## Inspect What This Machine Actually Has\n\nThe kernel manages CPU, memory, and storage on behalf of every process running here — before you go further, look at what's actually available.\n\n**Your task:**\n\n1. Find the number of CPUs available (`nproc`)\n2. Find the total memory in MB (`free -m`)\n3. Find the size of the root filesystem in GB (`df -BG /`)\n4. Create a file at `/srv/hardware-lab/report.txt` with exactly three lines in this format (replace `<n>` with the real numbers you found):\n\n```\nCPUS=<n>\nMEM_MB=<n>\nDISK_GB=<n>\n```",
|
||||
"hints": [
|
||||
{
|
||||
"title": "Getting each number",
|
||||
"body": "`nproc` alone prints the CPU count. `free -m` prints a table — the second column of the `Mem:` row is total memory in MB. `df -BG /` prints a table — the second column of the second row is the root filesystem size in GB (the number, not including the trailing G).",
|
||||
"command": "nproc; free -m; df -BG /"
|
||||
},
|
||||
{
|
||||
"title": "Writing the report",
|
||||
"body": "Use a heredoc or several `echo` lines with `>>` to build the file.",
|
||||
"command": "mkdir -p /srv/hardware-lab && cat <<EOF > /srv/hardware-lab/report.txt\nCPUS=$(nproc)\nMEM_MB=$(free -m | awk '/Mem:/ {print $2}')\nDISK_GB=$(df -BG / | awk 'NR==2{gsub(\"G\",\"\",$2); print $2}')\nEOF"
|
||||
}
|
||||
],
|
||||
"setup_commands": [
|
||||
{ "command": "mkdir -p /srv/hardware-lab" }
|
||||
],
|
||||
"validation": {
|
||||
"commands": [
|
||||
{ "description": "report.txt exists and has a CPUS line", "command": "grep -E '^CPUS=[0-9]+$' /srv/hardware-lab/report.txt", "expected_output": "^CPUS=[0-9]+$", "match": "regex" },
|
||||
{ "description": "report.txt has a MEM_MB line", "command": "grep -E '^MEM_MB=[0-9]+$' /srv/hardware-lab/report.txt", "expected_output": "^MEM_MB=[0-9]+$", "match": "regex" },
|
||||
{ "description": "report.txt has a DISK_GB line", "command": "grep -E '^DISK_GB=[0-9]+$' /srv/hardware-lab/report.txt", "expected_output": "^DISK_GB=[0-9]+$", "match": "regex" }
|
||||
]
|
||||
},
|
||||
"default_namespace": "default",
|
||||
"teardown_commands": [
|
||||
{ "command": "rm -rf /srv/hardware-lab" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"id": "linux-permissions-foundations",
|
||||
"title": "Fix a Real Permission Denial",
|
||||
"category": "Linux Fundamentals",
|
||||
"isolation_type": "shared_namespace",
|
||||
"difficulty": "Easy",
|
||||
"type": "task",
|
||||
"weight": 4,
|
||||
"description": "## Fix a Real Permission Denial\n\nA second user, `labuser`, already exists on this system. A file at `/srv/perm-lab/data.txt` currently has permissions `600` (owner read/write only) — `labuser` cannot read it. Confirm the denial is real, then fix it correctly.\n\n**Your task:**\n\n1. As `labuser`, try to read `/srv/perm-lab/data.txt` and see the permission denial for yourself: `su - labuser -c \"cat /srv/perm-lab/data.txt\"`\n2. Change the file's permissions so it's readable by everyone but still only writable by its owner (this is exactly what `644` means)\n3. Confirm `labuser` can now read it",
|
||||
"hints": [
|
||||
{
|
||||
"title": "Reading permissions as a number",
|
||||
"body": "Each of the three permission groups (owner/group/other) is one octal digit: read=4, write=2, execute=1, summed. `644` = owner read+write (6), group read-only (4), other read-only (4).",
|
||||
"command": "stat -c '%a %U' /srv/perm-lab/data.txt"
|
||||
},
|
||||
{
|
||||
"title": "Fixing it",
|
||||
"body": "`chmod 644 <file>` sets exactly that permission pattern.",
|
||||
"command": "chmod 644 /srv/perm-lab/data.txt"
|
||||
}
|
||||
],
|
||||
"setup_commands": [
|
||||
{ "command": "id labuser >/dev/null 2>&1 || useradd -m labuser" },
|
||||
{ "command": "mkdir -p /srv/perm-lab && chmod 755 /srv/perm-lab" },
|
||||
{ "command": "echo sensitive > /srv/perm-lab/data.txt && chmod 600 /srv/perm-lab/data.txt && chown root:root /srv/perm-lab/data.txt" }
|
||||
],
|
||||
"validation": {
|
||||
"commands": [
|
||||
{ "description": "data.txt is now exactly 644", "command": "stat -c '%a' /srv/perm-lab/data.txt", "expected_output": "644", "match": "exact" },
|
||||
{ "description": "labuser can actually read it now", "command": "su - labuser -c 'cat /srv/perm-lab/data.txt' 2>&1", "expected_output": "sensitive", "match": "exact" }
|
||||
]
|
||||
},
|
||||
"default_namespace": "default",
|
||||
"teardown_commands": [
|
||||
{ "command": "rm -rf /srv/perm-lab" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": "linux-permissions-mcq",
|
||||
"title": "Choosing the Minimal Permission",
|
||||
"category": "Linux Fundamentals",
|
||||
"isolation_type": "shared_namespace",
|
||||
"difficulty": "Easy",
|
||||
"type": "mcq",
|
||||
"weight": 3,
|
||||
"description": "## Choosing the Minimal Permission\n\nA private SSH key file should be readable and writable by its owner only — nobody else should be able to read, write, or execute it. Which `chmod` octal value is correct?",
|
||||
"options": [
|
||||
{ "id": "a", "text": "777" },
|
||||
{ "id": "b", "text": "644" },
|
||||
{ "id": "c", "text": "600" },
|
||||
{ "id": "d", "text": "444" }
|
||||
],
|
||||
"correct_option": "c",
|
||||
"explanation": "600 grants the owner read+write (4+2=6) and nothing at all to group or other (0, 0) — the minimum needed, and the actual permission SSH itself enforces for private key files. 644 incorrectly leaves the file readable by everyone; 777 grants full access to everyone, the opposite of least privilege.",
|
||||
"hints": [],
|
||||
"setup_commands": [],
|
||||
"default_namespace": "default",
|
||||
"teardown_commands": []
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"id": "linux-process-management",
|
||||
"title": "Find and Stop a Runaway Process",
|
||||
"category": "Linux Fundamentals",
|
||||
"isolation_type": "shared_namespace",
|
||||
"difficulty": "Easy",
|
||||
"type": "task",
|
||||
"weight": 4,
|
||||
"description": "## Find and Stop a Runaway Process\n\nA background process has been started for you and its PID recorded at `/srv/proc-lab/target.pid`. Every process in Kubernetes and Docker is, underneath, exactly this: a PID the kernel is tracking.\n\n**Your task:**\n\n1. Confirm the process is currently running (`kill -0 <pid>` succeeds silently if a process exists)\n2. Send it a termination signal: `kill <pid>` (using the PID recorded in the file, not a hardcoded number — it will be different every time)\n3. Confirm it is actually gone afterward",
|
||||
"hints": [
|
||||
{
|
||||
"title": "Reading the PID and checking it's alive",
|
||||
"body": "`kill -0` sends no real signal — it only checks whether the process exists, which is why it's the standard way to test \"is this PID still running\" without side effects.",
|
||||
"command": "cat /srv/proc-lab/target.pid\nkill -0 $(cat /srv/proc-lab/target.pid) && echo RUNNING"
|
||||
},
|
||||
{
|
||||
"title": "Terminating it",
|
||||
"body": "A plain `kill <pid>` sends SIGTERM, the polite \"please shut down\" signal — the correct first choice before ever reaching for `kill -9`.",
|
||||
"command": "kill $(cat /srv/proc-lab/target.pid)"
|
||||
}
|
||||
],
|
||||
"setup_commands": [
|
||||
{ "command": "mkdir -p /srv/proc-lab && (sleep 900 & echo $! > /srv/proc-lab/target.pid)" }
|
||||
],
|
||||
"validation": {
|
||||
"commands": [
|
||||
{ "description": "the target process has actually been terminated", "command": "kill -0 $(cat /srv/proc-lab/target.pid) 2>/dev/null && echo RUNNING || echo GONE", "expected_output": "GONE", "match": "exact" }
|
||||
]
|
||||
},
|
||||
"default_namespace": "default",
|
||||
"teardown_commands": [
|
||||
{ "command": "kill -9 $(cat /srv/proc-lab/target.pid) 2>/dev/null; rm -rf /srv/proc-lab" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"id": "linux-shell-basics",
|
||||
"title": "Reading Exit Codes Like an Engineer",
|
||||
"category": "Linux Fundamentals",
|
||||
"isolation_type": "shared_namespace",
|
||||
"difficulty": "Easy",
|
||||
"type": "task",
|
||||
"weight": 3,
|
||||
"description": "## Reading Exit Codes Like an Engineer\n\nEvery command you run returns an exit code — `0` means success, anything else means failure. Scripts, CI pipelines, and Kubernetes probes all rely on this exact convention.\n\n**Your task:**\n\n1. Run a command that succeeds (e.g. `true`, or `ls` on a directory that exists) and save its exit code to `/srv/shell-lab/success-exit-code.txt`\n2. Run a command that fails (e.g. `ls` on a path that does NOT exist) and save its exit code to `/srv/shell-lab/fail-exit-code.txt`\n3. Look up the manual page for `ls` (`man ls`) and find one option you didn't know about",
|
||||
"hints": [
|
||||
{
|
||||
"title": "Capturing an exit code",
|
||||
"body": "The special variable `$?` holds the exit code of the LAST command that ran — capture it immediately, before running anything else.",
|
||||
"command": "mkdir -p /srv/shell-lab\ntrue\necho $? > /srv/shell-lab/success-exit-code.txt\nls /this-path-does-not-exist 2>/dev/null\necho $? > /srv/shell-lab/fail-exit-code.txt"
|
||||
}
|
||||
],
|
||||
"setup_commands": [
|
||||
{ "command": "mkdir -p /srv/shell-lab" }
|
||||
],
|
||||
"validation": {
|
||||
"commands": [
|
||||
{ "description": "success-exit-code.txt records 0", "command": "cat /srv/shell-lab/success-exit-code.txt", "expected_output": "0", "match": "exact" },
|
||||
{ "description": "fail-exit-code.txt records a non-zero code", "command": "cat /srv/shell-lab/fail-exit-code.txt", "expected_output": "^[1-9][0-9]*$", "match": "regex" }
|
||||
]
|
||||
},
|
||||
"default_namespace": "default",
|
||||
"teardown_commands": [
|
||||
{ "command": "rm -rf /srv/shell-lab" }
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user