Adding files

This commit is contained in:
2026-06-16 10:40:35 -04:00
parent f9ae4d3763
commit 42ca69a43f
9 changed files with 1272 additions and 123 deletions

View File

@@ -1,156 +1,864 @@
;;; 06-linuxplus-quiz-mode.el --- linuxplus-quiz-mode
(defvar-local linuxplus-quiz-questions nil
"Questions for the current Linux+ quiz buffer.")
(defvar-local linuxplus-quiz-lesson-name nil)
(defvar-local linuxplus-quiz-questions nil)
(defvar-local linuxplus-quiz-user-answers nil)
(defvar-local linuxplus-quiz-current-question 0)
(defvar-local linuxplus-quiz-current-choice 0)
(defvar-local linuxplus-quiz-return-window-configuration nil)
(defvar-local linuxplus-quiz-return-buffer nil)
(defvar-local linuxplus-quiz-answers nil
"Correct answers for the current Linux+ quiz buffer.")
(defun linuxplus-quiz--question-type (question)
(plist-get question :type))
(defvar-local linuxplus-quiz-user-answers nil
"User answers for the current Linux+ quiz buffer.")
(defun linuxplus-quiz--question-prompt (question)
(plist-get question :prompt))
(defvar-local linuxplus-quiz-current-index 0
"Current question index in the Linux+ quiz buffer.")
(defun linuxplus-quiz--question-answer (question)
(plist-get question :answer))
(defvar-local linuxplus-quiz-lesson-name nil
"Lesson name associated with the current Linux+ quiz buffer.")
(defun linuxplus-quiz--question-answers (question)
(plist-get question :answers))
(defun linuxplus-quiz-answer-marker (answer)
"Return a display marker string for ANSWER."
(defun linuxplus-quiz--question-choices (question)
(plist-get question :choices))
(defun linuxplus-quiz--current-question ()
(nth linuxplus-quiz-current-question
linuxplus-quiz-questions))
(defun linuxplus-quiz--current-user-answer ()
(nth linuxplus-quiz-current-question
linuxplus-quiz-user-answers))
(defun linuxplus-quiz--set-current-user-answer (answer)
(setf
(nth linuxplus-quiz-current-question
linuxplus-quiz-user-answers)
answer))
(defun linuxplus-quiz--answer-marker (answer)
(cond
((eq answer 'y) "[y]")
((eq answer 'n) "[n]")
(t "[ ]")))
((eq answer 'true) "[t]")
((eq answer 'false) "[f]")
((eq answer 'yes) "[y]")
((eq answer 'no) "[n]")
((eq answer 'selected) "[*]")
(t "[]")))
(defun linuxplus-quiz-answer-string (answer)
"Return a readable string for ANSWER."
(defun linuxplus-quiz--answer-string (answer)
(cond
((eq answer 'y) "y")
((eq answer 'n) "n")
(t "unanswered")))
((eq answer 'true) "true")
((eq answer 'false) "false")
((eq answer 'yes) "yes")
((eq answer 'no) "no")
((listp answer) (format "%S" answer))
((null answer) "unanswered")
(t (format "%S" answer))))
(defun linuxplus-quiz-result-string (user-answer correct-answer)
"Return a readable result string comparing USER-ANSWER to CORRECT-ANSWER."
(if (eq user-answer correct-answer)
"correct"
"incorrect"))
(defun linuxplus-quiz--correct-answer-for-display
(question)
(let ((type
(linuxplus-quiz--question-type question)))
(cond
((memq type '(true-false yes-no))
(linuxplus-quiz--answer-string
(linuxplus-quiz--question-answer question)))
((eq type 'multiple)
(format "%S"
(linuxplus-quiz--question-answers
question)))
(t "unknown"))))
(defun linuxplus-quiz--advance-question ()
(when (< linuxplus-quiz-current-question
(1- (length linuxplus-quiz-questions)))
(setq linuxplus-quiz-current-question
(1+ linuxplus-quiz-current-question))
(setq linuxplus-quiz-current-choice 0)))
(defun linuxplus-quiz--previous-question ()
(when (> linuxplus-quiz-current-question 0)
(setq linuxplus-quiz-current-question
(1- linuxplus-quiz-current-question))
(setq linuxplus-quiz-current-choice 0)))
(defun linuxplus-quiz--multiple-selected-p
(answer choice-index)
(and (listp answer)
(memq choice-index answer)))
(defun linuxplus-quiz--advance-choice ()
"Advance to next multiple-choice bracket."
(let* ((question
(linuxplus-quiz--current-question))
(choices
(linuxplus-quiz--question-choices
question)))
(when (and choices
(< linuxplus-quiz-current-choice
(1- (length choices))))
(setq linuxplus-quiz-current-choice
(1+ linuxplus-quiz-current-choice)))))
(defun linuxplus-quiz--toggle-current-choice ()
"Toggle current multiple-choice selection."
(let* ((answer
(copy-sequence
(or
(linuxplus-quiz--current-user-answer)
nil)))
(choice linuxplus-quiz-current-choice)
(was-selected
(memq choice answer)))
(if was-selected
;; Remove selection and stay.
(setq answer
(delq choice answer))
;; Add selection and advance.
(push choice answer)
(setq answer
(sort answer #'<))
(linuxplus-quiz--set-current-user-answer
answer)
(linuxplus-quiz--advance-choice)
;; Stop removal logic.
(setq answer nil))
;; Removal path only.
(when answer
(setq answer
(sort answer #'<))
(linuxplus-quiz--set-current-user-answer
answer))))
(defun linuxplus-quiz--answer-correct-p
(question user-answer)
(let ((type
(linuxplus-quiz--question-type question)))
(cond
((memq type '(true-false yes-no))
(eq user-answer
(linuxplus-quiz--question-answer
question)))
((eq type 'multiple)
(equal
(sort
(copy-sequence
(or user-answer nil))
#'<)
(sort
(copy-sequence
(linuxplus-quiz--question-answers
question))
#'<)))
(t nil))))
(defun linuxplus-quiz--question-index-at-point ()
"Return zero-based question index at point."
(save-excursion
(let ((found nil))
(when
(re-search-backward
"^\\([0-9]+\\)\\.$"
nil t)
(setq found
(1-
(string-to-number
(match-string 1)))))
(when
(and found
(>= found 0)
(< found
(length
linuxplus-quiz-questions)))
found))))
(defun linuxplus-quiz--choice-index-at-point ()
"Return zero-based multiple-choice bracket index at point."
(save-excursion
(let ((current-line
(line-beginning-position))
(question-start nil)
(choice-index 0)
result)
;; Find question start.
(when
(re-search-backward
"^\\([0-9]+\\)\\.$"
nil t)
(setq question-start (point)))
(when question-start
;; Start searching after question header.
(forward-line 1)
;; Walk brackets.
(while
(and
(null result)
(re-search-forward
"\\[[^]]*\\]"
nil t))
;; Did we hit current line?
(when
(= (line-beginning-position)
current-line)
(setq result choice-index))
;; Next bracket index.
(setq choice-index
(1+ choice-index))))
result)))
(defun linuxplus-quiz--sync-current-position-from-point ()
"Update current question and multiple-choice bracket from cursor position."
(let ((question-index
(linuxplus-quiz--question-index-at-point)))
(when question-index
(setq linuxplus-quiz-current-question
question-index)
(if
(eq
(linuxplus-quiz--question-type
(linuxplus-quiz--current-question))
'multiple)
(setq linuxplus-quiz-current-choice
(or
(linuxplus-quiz--choice-index-at-point)
0))
(setq linuxplus-quiz-current-choice 0)))))
(defun linuxplus-quiz--invalid-input-message ()
(linuxplus-quiz--sync-current-position-from-point)
(let ((type
(linuxplus-quiz--question-type
(linuxplus-quiz--current-question))))
(cond
((eq type 'true-false)
(message
"Valid input: t for true, f for false."))
((eq type 'yes-no)
(message
"Valid input: y for yes, n for no."))
((eq type 'multiple)
(message
"Use the space bar to add/remove the * character."))
(t
(message "Invalid input.")))))
(defun linuxplus-quiz--set-answer-and-maybe-advance
(answer)
"Set ANSWER.
If unanswered, advance.
If replacing, stay."
(linuxplus-quiz--sync-current-position-from-point)
(let ((old-answer
(linuxplus-quiz--current-user-answer)))
(linuxplus-quiz--set-current-user-answer
answer)
(unless old-answer
(linuxplus-quiz--advance-question))
(linuxplus-quiz-render)))
(defun linuxplus-quiz-render ()
"Render the current Linux+ quiz buffer."
(let ((inhibit-read-only t))
(erase-buffer)
(insert "Linux+ Quiz\n")
(insert "===========\n\n")
(insert "y = yes, n = no, b = back, f = forward, x = finish, q = quit\n\n")
(dotimes (i (length linuxplus-quiz-questions))
(let ((prefix (if (= i linuxplus-quiz-current-index) ">" " "))
(marker (linuxplus-quiz-answer-marker
(nth i linuxplus-quiz-user-answers)))
(question (nth i linuxplus-quiz-questions)))
(insert (format "%s %s %d. %s\n"
prefix marker (1+ i) question)))))
(goto-char (point-min)))
(defun linuxplus-quiz-finish ()
"Finish the current quiz, record the score, and show detailed results."
(interactive)
(let ((score 0)
(total (length linuxplus-quiz-questions)))
(dotimes (i total)
(when (eq (nth i linuxplus-quiz-user-answers)
(nth i linuxplus-quiz-answers))
(setq score (1+ score))))
(linuxplus-record-score linuxplus-quiz-lesson-name score)
(let ((inhibit-read-only t))
(erase-buffer)
(insert "Linux+ Quiz Results\n")
(insert "===================\n\n")
(insert (format "Lesson: %s\n" linuxplus-quiz-lesson-name))
(insert (format "Score: %d/%d\n\n" score total))
(insert "Detailed review:\n\n")
(dotimes (i total)
(let ((question (nth i linuxplus-quiz-questions))
(user-answer (nth i linuxplus-quiz-user-answers))
(correct-answer (nth i linuxplus-quiz-answers)))
(insert (format "%d. %s\n" (1+ i) question))
(insert (format " Your answer: %s\n"
(linuxplus-quiz-answer-string user-answer)))
(insert (format " Correct answer: %s\n"
(linuxplus-quiz-answer-string correct-answer)))
(insert (format " Result: %s\n\n"
(linuxplus-quiz-result-string
user-answer correct-answer))))))
(goto-char (point-min))
(view-mode 1)
(message "Quiz complete. Score: %d/%d" score total)))
(let ((current-type
(linuxplus-quiz--question-type
(linuxplus-quiz--current-question))))
(defun linuxplus-quiz-answer-y ()
"Answer yes to the current Linux+ quiz question."
(interactive)
(setf (nth linuxplus-quiz-current-index linuxplus-quiz-user-answers) 'y)
(if (< linuxplus-quiz-current-index
(1- (length linuxplus-quiz-questions)))
(setq linuxplus-quiz-current-index
(1+ linuxplus-quiz-current-index))
(linuxplus-quiz-render))
(when (derived-mode-p 'linuxplus-quiz-mode)
(linuxplus-quiz-render)))
(cond
((eq current-type 'true-false)
(insert
"Type 't' for true or 'f' for false inside the brackets.\n"))
((eq current-type 'yes-no)
(insert
"Type 'y' for yes or 'n' for no inside the brackets.\n"))
((eq current-type 'multiple)
(insert
"Use the space bar to add/remove the * character for multiple choice questions.\n"))
(t
(insert "Answer the question.\n"))))
(insert
"b = back, TAB = forward, x = finish, q = quit\n\n")
(dotimes
(i (length linuxplus-quiz-questions))
(let* ((question
(nth i linuxplus-quiz-questions))
(type
(linuxplus-quiz--question-type
question))
(prompt
(linuxplus-quiz--question-prompt
question))
(user-answer
(nth i linuxplus-quiz-user-answers))
(current-question-p
(= i
linuxplus-quiz-current-question)))
(insert
(format "%d.\n" (1+ i)))
(insert
(format " %s\n" prompt))
(cond
((eq type 'true-false)
(insert
(format
"%s %s True or False\n\n"
(if current-question-p
">"
" ")
(linuxplus-quiz--answer-marker
user-answer))))
((eq type 'yes-no)
(insert
(format
"%s %s Yes or No\n\n"
(if current-question-p
">"
" ")
(linuxplus-quiz--answer-marker
user-answer))))
((eq type 'multiple)
(let ((choices
(linuxplus-quiz--question-choices
question)))
(dotimes
(choice-index
(length choices))
(let*
((selected
(if
(linuxplus-quiz--multiple-selected-p
user-answer
choice-index)
'selected
nil))
(choice-current-p
(and
current-question-p
(= choice-index
linuxplus-quiz-current-choice))))
(insert
(format
"%s %s %s\n"
(if choice-current-p
">"
" ")
(linuxplus-quiz--answer-marker
selected)
(nth choice-index
choices))))))
(insert "\n")))))
(insert
"\nReminder: x = finish quiz, q = quit/return to lesson.\n"))
(linuxplus-quiz-goto-current-answer))
(defun linuxplus-quiz-goto-current-answer ()
(goto-char (point-min))
(let ((target-question
(format "^%d\\.$"
(1+
linuxplus-quiz-current-question))))
(when
(re-search-forward
target-question
nil t)
(let ((type
(linuxplus-quiz--question-type
(linuxplus-quiz--current-question))))
(cond
((memq type '(true-false yes-no))
(when
(re-search-forward
"\\[[^]]*\\]"
nil t)
(backward-char 1)))
((eq type 'multiple)
(dotimes
(_ linuxplus-quiz-current-choice)
(re-search-forward
"\\[[^]]*\\]"
nil t))
(when
(re-search-forward
"\\[[^]]*\\]"
nil t)
(backward-char 1))))))))
(defun linuxplus-quiz-answer-true ()
(defun linuxplus-quiz-answer-n ()
"Answer no to the current Linux+ quiz question."
(interactive)
(setf (nth linuxplus-quiz-current-index linuxplus-quiz-user-answers) 'n)
(if (< linuxplus-quiz-current-index
(1- (length linuxplus-quiz-questions)))
(setq linuxplus-quiz-current-index
(1+ linuxplus-quiz-current-index))
(linuxplus-quiz-render))
(when (derived-mode-p 'linuxplus-quiz-mode)
(linuxplus-quiz-render)))
(linuxplus-quiz--sync-current-position-from-point)
(if
(eq
(linuxplus-quiz--question-type
(linuxplus-quiz--current-question))
'true-false)
(linuxplus-quiz--set-answer-and-maybe-advance
'true)
(linuxplus-quiz--invalid-input-message)))
(defun linuxplus-quiz-answer-false ()
(interactive)
(linuxplus-quiz--sync-current-position-from-point)
(if
(eq
(linuxplus-quiz--question-type
(linuxplus-quiz--current-question))
'true-false)
(linuxplus-quiz--set-answer-and-maybe-advance
'false)
(linuxplus-quiz--invalid-input-message)))
(defun linuxplus-quiz-answer-yes ()
(interactive)
(linuxplus-quiz--sync-current-position-from-point)
(if
(eq
(linuxplus-quiz--question-type
(linuxplus-quiz--current-question))
'yes-no)
(linuxplus-quiz--set-answer-and-maybe-advance
'yes)
(linuxplus-quiz--invalid-input-message)))
(defun linuxplus-quiz-answer-no ()
(interactive)
(linuxplus-quiz--sync-current-position-from-point)
(if
(eq
(linuxplus-quiz--question-type
(linuxplus-quiz--current-question))
'yes-no)
(linuxplus-quiz--set-answer-and-maybe-advance
'no)
(linuxplus-quiz--invalid-input-message)))
(defun linuxplus-quiz-toggle-multiple ()
(interactive)
(linuxplus-quiz--sync-current-position-from-point)
(if
(eq
(linuxplus-quiz--question-type
(linuxplus-quiz--current-question))
'multiple)
(progn
(linuxplus-quiz--toggle-current-choice)
(linuxplus-quiz-render))
(linuxplus-quiz--invalid-input-message)))
(defun linuxplus-quiz-next ()
"Move to next question without changing answer."
(interactive)
(when (< linuxplus-quiz-current-index
(1- (length linuxplus-quiz-questions)))
(setq linuxplus-quiz-current-index
(1+ linuxplus-quiz-current-index))
(linuxplus-quiz-render)))
(linuxplus-quiz--sync-current-position-from-point)
(let ((type
(linuxplus-quiz--question-type
(linuxplus-quiz--current-question))))
(if
(and
(eq type 'multiple)
(< linuxplus-quiz-current-choice
(1-
(length
(linuxplus-quiz--question-choices
(linuxplus-quiz--current-question))))))
(setq linuxplus-quiz-current-choice
(1+
linuxplus-quiz-current-choice))
(linuxplus-quiz--advance-question)))
(linuxplus-quiz-render))
(defun linuxplus-quiz-prev ()
"Move to previous question."
(interactive)
(when (> linuxplus-quiz-current-index 0)
(setq linuxplus-quiz-current-index
(1- linuxplus-quiz-current-index))
(linuxplus-quiz-render)))
(defvar linuxplus-quiz-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "y") #'linuxplus-quiz-answer-y)
(define-key map (kbd "n") #'linuxplus-quiz-answer-n)
(define-key map (kbd "f") #'linuxplus-quiz-next)
(define-key map (kbd "b") #'linuxplus-quiz-prev)
(define-key map (kbd "x") #'linuxplus-quiz-finish)
(define-key map (kbd "q") #'quit-window)
map)
"Keymap for `linuxplus-quiz-mode`.")
(linuxplus-quiz--sync-current-position-from-point)
(let ((type
(linuxplus-quiz--question-type
(linuxplus-quiz--current-question))))
(if
(and
(eq type 'multiple)
(> linuxplus-quiz-current-choice 0))
(setq linuxplus-quiz-current-choice
(1-
linuxplus-quiz-current-choice))
(linuxplus-quiz--previous-question)))
(linuxplus-quiz-render))
(defun linuxplus-quiz-invalid-key ()
(interactive)
(linuxplus-quiz--invalid-input-message))
(defun linuxplus-quiz-return-to-lesson ()
"Return to lesson/window layout."
(interactive)
(let ((config
linuxplus-quiz-return-window-configuration)
(buffer
linuxplus-quiz-return-buffer))
(when
(window-configuration-p config)
(set-window-configuration config))
(when
(buffer-live-p buffer)
(switch-to-buffer buffer))))
(defun linuxplus-quiz-finish ()
(interactive)
(let ((score 0)
(total
(length linuxplus-quiz-questions)))
(dotimes (i total)
(when
(linuxplus-quiz--answer-correct-p
(nth i linuxplus-quiz-questions)
(nth i linuxplus-quiz-user-answers))
(setq score (1+ score))))
(linuxplus-record-score
linuxplus-quiz-lesson-name
score)
(let ((inhibit-read-only t))
(erase-buffer)
(insert "Linux+ Quiz Results\n")
(insert "===================\n\n")
(insert
(format
"Lesson: %s\n"
linuxplus-quiz-lesson-name))
(insert
(format
"Score: %d/%d\n\n"
score total))
(insert "Detailed review:\n\n")
(dotimes (i total)
(let*
((question
(nth i linuxplus-quiz-questions))
(prompt
(linuxplus-quiz--question-prompt
question))
(user-answer
(nth i linuxplus-quiz-user-answers)))
(insert
(format
"%d. %s\n"
(1+ i)
prompt))
(insert
(format
" Your answer: %s\n"
(linuxplus-quiz--answer-string
user-answer)))
(insert
(format
" Correct answer: %s\n"
(linuxplus-quiz--correct-answer-for-display
question)))
(insert
(format
" Result: %s\n\n"
(if
(linuxplus-quiz--answer-correct-p
question
user-answer)
"correct"
"incorrect")))))
(insert
"Press q to return to the lesson.\n"))
(goto-char (point-min))
(linuxplus-quiz-mode)
(message
"Quiz complete. Score: %d/%d"
score total)))
(setq linuxplus-quiz-mode-map
(let ((map
(make-sparse-keymap)))
;; invalid printable input
(dotimes (i 95)
(define-key
map
(char-to-string (+ i 32))
#'linuxplus-quiz-invalid-key))
;; valid answers
(define-key
map
(kbd "t")
#'linuxplus-quiz-answer-true)
(define-key
map
(kbd "f")
#'linuxplus-quiz-answer-false)
(define-key
map
(kbd "y")
#'linuxplus-quiz-answer-yes)
(define-key
map
(kbd "n")
#'linuxplus-quiz-answer-no)
(define-key
map
(kbd "SPC")
#'linuxplus-quiz-toggle-multiple)
;; navigation
(define-key
map
(kbd "b")
#'linuxplus-quiz-prev)
(define-key
map
(kbd "TAB")
#'linuxplus-quiz-next)
(define-key
map
(kbd "x")
#'linuxplus-quiz-finish)
(define-key
map
(kbd "q")
#'linuxplus-quiz-return-to-lesson)
map))
(define-derived-mode
linuxplus-quiz-mode
special-mode
"LinuxPlus-Quiz"
"Major mode for Linux+ quizzes."
(define-derived-mode linuxplus-quiz-mode special-mode "LinuxPlus-Quiz"
"Major mode for Linux+ quiz buffers."
(setq buffer-read-only t))
(defun linuxplus-start-quiz (lesson questions answers buffer)
"Start a Linux+ quiz for LESSON using QUESTIONS and ANSWERS in BUFFER."
(let ((buf (get-buffer-create buffer)))
(with-current-buffer buf
(linuxplus-quiz-mode)
(setq linuxplus-quiz-lesson-name lesson)
(setq linuxplus-quiz-questions questions)
(setq linuxplus-quiz-answers answers)
(setq linuxplus-quiz-user-answers (make-list (length questions) nil))
(setq linuxplus-quiz-current-index 0)
(linuxplus-quiz-render))
(pop-to-buffer buf)))
(defun linuxplus-start-quiz
(lesson questions buffer)
"Start Linux+ quiz in full window."
(let ((return-config
(current-window-configuration))
(return-buffer
(current-buffer))
(buf
(get-buffer-create buffer)))
(delete-other-windows)
(switch-to-buffer buf)
(linuxplus-quiz-mode)
(setq linuxplus-quiz-return-window-configuration
return-config)
(setq linuxplus-quiz-return-buffer
return-buffer)
(setq linuxplus-quiz-lesson-name
lesson)
(setq linuxplus-quiz-questions
questions)
(setq linuxplus-quiz-user-answers
(make-list
(length questions)
nil))
(setq linuxplus-quiz-current-question 0)
(setq linuxplus-quiz-current-choice 0)
(linuxplus-quiz-render)))
;;; 06-linuxplus-quiz-mode.el ends here

View File

@@ -0,0 +1,49 @@
;;; 101-systemd-service-quiz.el --- systemd-service-quiz
(defun systemd-service-quiz ()
"Quiz for systemd service management."
(interactive)
(linuxplus-start-quiz
'systemd-service-lesson
'((:type true-false
:prompt "`systemctl start ssh` starts the service immediately but does not enable it at boot."
:answer true)
(:type true-false
:prompt "`systemctl enable ssh` starts the service immediately and enables it at boot."
:answer false)
(:type true-false
:prompt "`systemctl disable ssh` prevents startup at boot but does not necessarily stop a currently running service."
:answer true)
(:type true-false
:prompt "`systemctl mask ssh` is stronger than `systemctl disable ssh` because it prevents normal starting."
:answer true)
(:type true-false
:prompt "`systemctl reload ssh` and `systemctl restart ssh` always do exactly the same thing."
:answer false)
(:type true-false
:prompt "`systemctl status <service>` is useful when troubleshooting a failed service."
:answer true)
(:type true-false
:prompt "`journalctl -u <service>` can show logs for a specific systemd service."
:answer true)
(:type true-false
:prompt "`systemctl --failed` can help identify failed units."
:answer true)
(:type yes-no
:prompt "On many Red Hat-family systems, is the SSH service commonly named `sshd`?"
:answer yes)
(:type yes-no
:prompt "On many Debian-family systems, is the SSH service commonly named `sshd` instead of `ssh`?"
:answer no))
"*systemd Service Quiz*"))
;;; 101-systemd-service-quiz.el ends here

123
102-network-tools-lesson.el Normal file
View File

@@ -0,0 +1,123 @@
;;; 102-network-tools-lesson.el --- network-tools-lesson
(defun network-tools-lesson ()
"Linux+ lesson for network troubleshooting tools."
(interactive)
(delete-other-windows)
(let ((buf (get-buffer-create "*Network Tools Lesson*"))
(shell (or shell-file-name
(getenv "SHELL")
"/bin/sh")))
(switch-to-buffer buf)
(read-only-mode -1)
(erase-buffer)
(insert
"Linux+ Lesson: Network Troubleshooting Tools\n"
"============================================\n\n"
"Concept:\n"
"Linux administrators must diagnose network problems quickly.\n"
"CompTIA Linux+ expects familiarity with modern networking tools,\n"
"basic connectivity testing, interface inspection, DNS testing,\n"
"and socket/process inspection.\n\n"
"Core commands:\n\n"
"ip addr\n"
" Display IP address information.\n\n"
"ip route\n"
" Display routing table.\n\n"
"ping\n"
" Test ICMP connectivity.\n\n"
"ss -tulpn\n"
" Show listening TCP/UDP sockets and associated processes.\n\n"
"hostnamectl\n"
" Show or change persistent hostname.\n\n"
"dig\n"
" Query DNS servers.\n\n"
"traceroute\n"
" Show network path hops.\n\n"
"curl\n"
" Retrieve data from URLs and test HTTP services.\n\n"
"wget\n"
" Download files from remote systems.\n\n"
"nmap\n"
" Network discovery and port scanning.\n\n"
"Hands-on tasks:\n\n"
"1. Display IP addresses:\n"
" ip addr\n\n"
"2. Display routes:\n"
" ip route\n\n"
"3. Test connectivity:\n"
" ping -c 4 google.com\n\n"
"4. Inspect listening services:\n"
" ss -tulpn\n\n"
"5. Query DNS:\n"
" dig google.com\n\n"
"6. Download a webpage:\n"
" curl https://example.com\n\n"
"7. Download a file:\n"
" wget https://example.com/index.html\n\n"
"8. Display hostname:\n"
" hostnamectl\n\n"
"9. Run a localhost scan:\n"
" sudo nmap localhost\n\n"
"Important Linux+ exam traps:\n\n"
"- `ip` replaces many older networking tools.\n"
"- `ss` is preferred over `netstat` on modern systems.\n"
"- `dig` is for DNS lookups.\n"
"- `ping` uses ICMP.\n"
"- `curl` is commonly used for API and HTTP testing.\n"
"- `wget` is commonly used for recursive or file downloads.\n"
"- `nmap` is heavily associated with network discovery and auditing.\n"
"- `hostnamectl` changes persistent hostnames.\n\n"
"Debian/Rocky notes:\n\n"
"- `dig` may require package installation.\n"
"- Debian package: dnsutils\n"
"- Rocky package: bind-utils\n\n"
"When finished, run:\n\n"
"M-x network-tools-quiz\n")
(goto-char (point-min))
(view-mode 1)
(split-window-below)
(other-window 1)
(let ((term (get-buffer "*term*")))
(if (buffer-live-p term)
(switch-to-buffer term)
(ansi-term shell "term")))
(other-window -1)))
;;; 102-network-tools-lesson.el ends here

52
103-network-tools-quiz.el Normal file
View File

@@ -0,0 +1,52 @@
;;; 103-network-tools-quiz.el --- network-tools-quiz
(defun network-tools-quiz ()
"Quiz for Linux network troubleshooting tools."
(interactive)
(linuxplus-start-quiz
'network-tools-lesson
'((:type true-false
:prompt "`ss -tulpn` can display listening sockets and processes."
:answer true)
(:type true-false
:prompt "`dig` is commonly used for DNS lookups."
:answer true)
(:type true-false
:prompt "`ping` primarily tests DNS zone transfers."
:answer false)
(:type true-false
:prompt "`ip addr` displays interface addressing information."
:answer true)
(:type true-false
:prompt "`hostnamectl` can manage persistent hostnames."
:answer true)
(:type true-false
:prompt "`wget` is primarily associated with downloading remote files."
:answer true)
(:type true-false
:prompt "`curl` is commonly used for HTTP/API testing."
:answer true)
(:type true-false
:prompt "`netstat` is generally preferred over `ss` on modern Linux systems."
:answer false)
(:type yes-no
:prompt "Does `ping` use ICMP packets?"
:answer yes)
(:type yes-no
:prompt "On Debian-family systems, is the `dig` command commonly provided by the `dnsutils` package?"
:answer yes))
"*Network Tools Quiz*"))
;;; 103-network-tools-quiz.el ends here

56
30-auth-log-lesson.el Normal file
View File

@@ -0,0 +1,56 @@
;;; 30-auth-log-lesson.el --- auth-log-lesson
(defun auth-log-lesson ()
"Linux+ lesson for authentication logs."
(interactive)
(delete-other-windows)
(let ((buf (get-buffer-create "*Auth Log Lesson*"))
(shell (or shell-file-name (getenv "SHELL") "/bin/sh")))
(switch-to-buffer buf)
(read-only-mode -1)
(erase-buffer)
(insert
"Linux+ Lesson: Authentication Logs\n"
"=================================\n\n"
"Concept:\n"
"Different Linux distributions store authentication logs in different locations.\n\n"
"You MUST know this for Linux+:\n\n"
" Red Hat / RHEL / Rocky / Alma:\n"
" /var/log/secure\n\n"
" Debian / Ubuntu:\n"
" /var/log/auth.log\n\n"
"Hands-on tasks (run in the terminal below):\n\n"
"1. Try viewing both files:\n"
" sudo less /var/log/auth.log\n"
" sudo less /var/log/secure\n\n"
" One of these will exist, depending on your distro.\n\n"
"2. Search for failed logins:\n"
" sudo grep -i fail /var/log/auth.log\n"
" sudo grep -i fail /var/log/secure\n\n"
"3. Search for ssh login activity:\n"
" sudo grep ssh /var/log/auth.log\n"
" sudo grep ssh /var/log/secure\n\n"
"4. Count failed login attempts:\n"
" sudo grep -i fail <file> | wc -l\n\n"
"Replace <file> with the correct log file for your system.\n\n"
"What to notice:\n"
"- Which file exists\n"
"- What failed login entries look like\n"
"- How grep helps filter logs\n\n"
"Linux+ exam trap:\n"
"If the question mentions failed login attempts on a Red Hat system,\n"
"the answer is:\n\n"
" /var/log/secure\n\n"
"When finished, run:\n"
" M-x auth-log-quiz\n")
(goto-char (point-min))
(view-mode 1)
(split-window-below)
(other-window 1)
(let ((term (get-buffer "*term*")))
(if (buffer-live-p term)
(switch-to-buffer term)
(ansi-term shell "term")))
(other-window -1)))
;;; 30-auth-log-lesson.el ends here

16
31-auth-log-quiz.el Normal file
View File

@@ -0,0 +1,16 @@
;;; 31-auth-log-quiz.el --- auth-log-quiz
(defun auth-log-quiz ()
"Quiz for authentication log knowledge."
(interactive)
(linuxplus-start-quiz
'auth-log-lesson
'("Is /var/log/secure used on Red Hat systems?"
"Is /var/log/auth.log used on Debian-based systems?"
"Does grep help filter log entries?"
"Is /var/log/messages used for authentication logs?"
"Can failed logins be found using grep fail?")
'(y y y n y)
"*Auth Log Quiz*"))
;;; 31-auth-log-quiz.el ends here

View File

@@ -0,0 +1,55 @@
;;; 40-package-management-lesson.el --- package-management-lesson
(defun package-management-lesson ()
"Linux+ lesson for apt vs dnf package management."
(interactive)
(delete-other-windows)
(let ((buf (get-buffer-create "*Package Management Lesson*"))
(shell (or shell-file-name (getenv "SHELL") "/bin/sh")))
(switch-to-buffer buf)
(read-only-mode -1)
(erase-buffer)
(insert
"Linux+ Lesson: Package Management (Debian vs Red Hat)\n"
"=====================================================\n\n"
"You MUST know both ecosystems.\n\n"
"Debian-based systems (Debian, Ubuntu):\n"
" apt update → refresh package list\n"
" apt upgrade → upgrade installed packages\n"
" apt install pkg → install package\n"
" apt remove pkg → remove package\n\n"
"Red Hat-based systems (RHEL, Rocky, Alma):\n"
" dnf check-update → check for updates\n"
" dnf upgrade → upgrade system\n"
" dnf install pkg → install package\n"
" dnf remove pkg → remove package\n\n"
"Hands-on tasks:\n\n"
"DEBIAN VM:\n"
" sudo apt update\n"
" sudo apt install tree\n"
" tree\n"
" sudo apt remove tree\n\n"
"ROCKY VM:\n"
" sudo dnf check-update\n"
" sudo dnf install tree\n"
" tree\n"
" sudo dnf remove tree\n\n"
"What to notice:\n"
"- Command differences (apt vs dnf)\n"
"- Same concepts, different syntax\n\n"
"Linux+ exam traps:\n"
"- apt is NOT used on Red Hat\n"
"- dnf/yum are NOT used on Debian\n\n"
"When finished, run:\n"
" M-x package-management-quiz\n")
(goto-char (point-min))
(view-mode 1)
(split-window-below)
(other-window 1)
(let ((term (get-buffer "*term*")))
(if (buffer-live-p term)
(switch-to-buffer term)
(ansi-term shell "term")))
(other-window -1)))
;;; 40-package-management-lesson.el ends here

View File

@@ -0,0 +1,16 @@
;;; 41-package-management-quiz.el --- package-management-quiz
(defun package-management-quiz ()
"Quiz for apt vs dnf knowledge."
(interactive)
(linuxplus-start-quiz
'package-management-lesson
'("Is apt used on Debian-based systems?"
"Is dnf used on Red Hat-based systems?"
"Does apt update refresh package lists?"
"Does dnf install install packages?"
"Can apt be used on Rocky Linux?")
'(y y y y n)
"*Package Management Quiz*"))
;;; 41-package-management-quiz.el ends here

View File

@@ -0,0 +1,74 @@
;;; 60-file-permissions-lesson.el --- file-permissions-lesson
(defun file-permissions-lesson ()
"Linux+ lesson for file permissions and ownership."
(interactive)
(delete-other-windows)
(let ((buf (get-buffer-create "*File Permissions Lesson*"))
(shell (or shell-file-name (getenv "SHELL") "/bin/sh")))
(switch-to-buffer buf)
(read-only-mode -1)
(erase-buffer)
(insert
"Linux+ Lesson: File Permissions and Ownership\n"
"============================================\n\n"
"Concepts:\n"
"- chmod changes permissions\n"
"- chown changes ownership\n"
"- rwx permissions apply to:\n"
" user / group / others\n\n"
"Permission values:\n"
" r = 4\n"
" w = 2\n"
" x = 1\n\n"
"Common octal permissions:\n"
" 644 = rw-r--r--\n"
" 755 = rwxr-xr-x\n"
" 600 = rw-------\n"
" 700 = rwx------\n\n"
"Hands-on tasks:\n\n"
"1. Create a practice directory:\n"
" mkdir ~/linuxplus-permissions\n\n"
"2. Enter it:\n"
" cd ~/linuxplus-permissions\n\n"
"3. Create a file:\n"
" touch testfile\n\n"
"4. View permissions:\n"
" ls -l\n\n"
"5. Change permissions to 644:\n"
" chmod 644 testfile\n"
" ls -l\n\n"
"6. Change permissions to 755:\n"
" chmod 755 testfile\n"
" ls -l\n\n"
"7. Use symbolic mode:\n"
" chmod u+x testfile\n"
" chmod g-w testfile\n"
" ls -l\n\n"
"8. View ownership:\n"
" ls -l\n\n"
"9. Change ownership (requires sudo):\n"
" sudo chown root:root testfile\n"
" ls -l\n\n"
"10. Return ownership to yourself:\n"
" sudo chown $USER:$USER testfile\n"
" ls -l\n\n"
"Linux+ exam traps:\n"
"- chmod changes permissions, NOT ownership\n"
"- chown changes ownership\n"
"- x on directories means traversal\n"
"- 755 is common for executable files/directories\n"
"- 644 is common for regular files\n\n"
"When finished, run:\n"
" M-x file-permissions-quiz\n")
(goto-char (point-min))
(view-mode 1)
(split-window-below)
(other-window 1)
(let ((term (get-buffer "*term*")))
(if (buffer-live-p term)
(switch-to-buffer term)
(ansi-term shell "term")))
(other-window -1)))
;;; 60-file-permissions-lesson.el ends here