Adding files

This commit is contained in:
2026-06-16 11:10:28 -04:00
parent 42ca69a43f
commit ba700543a0
12 changed files with 1503 additions and 669 deletions

View File

@@ -24,18 +24,14 @@
(plist-get question :choices))
(defun linuxplus-quiz--current-question ()
(nth linuxplus-quiz-current-question
linuxplus-quiz-questions))
(nth linuxplus-quiz-current-question linuxplus-quiz-questions))
(defun linuxplus-quiz--current-user-answer ()
(nth linuxplus-quiz-current-question
linuxplus-quiz-user-answers))
(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))
(setf (nth linuxplus-quiz-current-question linuxplus-quiz-user-answers)
answer))
(defun linuxplus-quiz--answer-marker (answer)
(cond
@@ -56,18 +52,14 @@
((null answer) "unanswered")
(t (format "%S" answer))))
(defun linuxplus-quiz--correct-answer-for-display
(question)
(let ((type
(linuxplus-quiz--question-type question)))
(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)))
(format "%S" (linuxplus-quiz--question-answers question)))
(t "unknown"))))
(defun linuxplus-quiz--advance-question ()
@@ -83,544 +75,235 @@
(1- linuxplus-quiz-current-question))
(setq linuxplus-quiz-current-choice 0)))
(defun linuxplus-quiz--multiple-selected-p
(answer choice-index)
(defun linuxplus-quiz--multiple-selected-p (answer choice-index)
(and (listp answer)
(memq choice-index answer)))
(defun linuxplus-quiz--toggle-current-choice ()
(let* ((answer (copy-sequence
(or (linuxplus-quiz--current-user-answer) nil)))
(choice linuxplus-quiz-current-choice))
(if (memq choice answer)
(setq answer (delq choice answer))
(push choice answer))
(linuxplus-quiz--set-current-user-answer
(sort answer #'<))))
(defun linuxplus-quiz--advance-choice ()
"Advance to next multiple-choice bracket."
(let* ((question
(linuxplus-quiz--current-question))
(choices
(linuxplus-quiz--question-choices
question)))
"Advance to the next multiple-choice bracket if one exists."
(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)))
(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 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))
#'<)))
(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)))
(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)))
"Update current question 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-question question-index)
(unless (eq (linuxplus-quiz--question-type
(linuxplus-quiz--current-question))
'multiple)
(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))))
(let ((type (linuxplus-quiz--question-type
(linuxplus-quiz--current-question))))
(cond
((eq type 'true-false)
(message
"Valid input: t for true, f for false."))
(message "Valid input: t for true, f for false."))
((eq type 'yes-no)
(message
"Valid input: y for yes, n for no."))
(message "Valid input: y for yes, n for no."))
((eq type 'multiple)
(message
"Use the space bar to add/remove the * character."))
(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."
(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)
(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 ()
(let ((inhibit-read-only t))
(erase-buffer)
(insert "Linux+ Quiz\n")
(insert "===========\n\n")
(let ((current-type
(linuxplus-quiz--question-type
(linuxplus-quiz--current-question))))
(cond
((eq current-type 'true-false)
(insert
"Type 't' for true or 'f' for false inside the brackets.\n"))
(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"))
(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"))
(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")
(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))
(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))))
(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))))
(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))))
(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"))
(format "%s %s %s\n"
(if choice-current-p ">" " ")
(linuxplus-quiz--answer-marker selected)
(nth choice-index choices))))))
(insert "\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))))
(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)
(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)
(dotimes (_ linuxplus-quiz-current-choice)
(re-search-forward "\\[[^]]*\\]" nil t))
(when (re-search-forward "\\[[^]]*\\]" nil t)
(backward-char 1))))))))
(defun linuxplus-quiz-answer-true ()
(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
'true)
(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)
(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)
(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)
(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)
(if (eq (linuxplus-quiz--question-type
(linuxplus-quiz--current-question))
'multiple)
(progn
(linuxplus-quiz--toggle-current-choice)
(linuxplus-quiz--advance-choice)
(linuxplus-quiz-render))
(linuxplus-quiz--invalid-input-message)))
(defun linuxplus-quiz-next ()
(interactive)
(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))))))
(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))
(1+ linuxplus-quiz-current-choice))
(linuxplus-quiz--advance-question)))
(linuxplus-quiz-render))
(defun linuxplus-quiz-prev ()
(interactive)
(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))
(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))
(1- linuxplus-quiz-current-choice))
(linuxplus-quiz--previous-question)))
(linuxplus-quiz-render))
(defun linuxplus-quiz-invalid-key ()
@@ -628,185 +311,71 @@ If replacing, stay."
(linuxplus-quiz--invalid-input-message))
(defun linuxplus-quiz-return-to-lesson ()
"Return to lesson/window layout."
"Return to the lesson/window layout from before the quiz."
(interactive)
(let ((config
linuxplus-quiz-return-window-configuration)
(buffer
linuxplus-quiz-return-buffer))
(when
(window-configuration-p config)
(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)
(when (buffer-live-p buffer)
(switch-to-buffer buffer))))
(defun linuxplus-quiz-finish ()
(interactive)
(let ((score 0)
(total
(length linuxplus-quiz-questions)))
(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))
(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)
(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 (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"))
(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)))
(message "Quiz complete. Score: %d/%d" score total)))
(setq linuxplus-quiz-mode-map
(let ((map
(make-sparse-keymap)))
;; invalid printable input
(let ((map (make-sparse-keymap)))
;; Invalid printable input by default.
(dotimes (i 95)
(define-key map
(char-to-string (+ i 32))
#'linuxplus-quiz-invalid-key))
(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)
;; 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)
;; Navigation / control.
(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))
@@ -815,50 +384,24 @@ If replacing, stay."
special-mode
"LinuxPlus-Quiz"
"Major mode for Linux+ quizzes."
(setq buffer-read-only t))
(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)))
(defun linuxplus-start-quiz (lesson questions buffer)
"Start a Linux+ quiz in a 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-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))
(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,70 @@
;;; 100-systemd-service-lesson.el --- systemd-service-lesson
(defun systemd-service-lesson ()
"Linux+ lesson for systemd service management."
(interactive)
(delete-other-windows)
(let ((buf (get-buffer-create "*systemd Service Lesson*"))
(shell (or shell-file-name (getenv "SHELL") "/bin/sh")))
(switch-to-buffer buf)
(read-only-mode -1)
(erase-buffer)
(insert
"Linux+ Lesson: systemd Service Management\n"
"=========================================\n\n"
"Concept:\n"
"Learn how to manage services with systemctl and understand\n"
"the difference between starting, enabling, disabling, and masking.\n\n"
"Use BOTH VMs:\n"
"- Debian 13 usually uses service name: ssh\n"
"- Rocky Linux usually uses service name: sshd\n\n"
"Core meanings:\n"
" systemctl start <service> = start now\n"
" systemctl stop <service> = stop now\n"
" systemctl restart <service> = stop then start\n"
" systemctl reload <service> = reload config if supported\n"
" systemctl enable <service> = start at boot\n"
" systemctl disable <service> = do not start at boot\n"
" systemctl enable --now <service> = start now and enable at boot\n"
" systemctl mask <service> = prevent normal starting\n"
" systemctl unmask <service> = remove mask\n\n"
"Hands-on tasks in Debian 13:\n"
" systemctl status ssh\n"
" systemctl is-enabled ssh\n"
" sudo systemctl restart ssh\n"
" journalctl -u ssh --since today\n\n"
"Hands-on tasks in Rocky Linux:\n"
" systemctl status sshd\n"
" systemctl is-enabled sshd\n"
" sudo systemctl restart sshd\n"
" journalctl -u sshd --since today\n\n"
"Optional lab on either disposable VM:\n"
" sudo systemctl stop <service>\n"
" systemctl status <service>\n"
" sudo systemctl start <service>\n"
" sudo systemctl disable <service>\n"
" systemctl is-enabled <service>\n"
" sudo systemctl enable <service>\n\n"
"Boot target review:\n"
" systemctl get-default\n"
" sudo systemctl set-default multi-user.target\n"
" sudo systemctl set-default graphical.target\n\n"
"Exam traps:\n"
"- enable does not start a service immediately.\n"
"- disable does not stop a currently running service.\n"
"- mask is stronger than disable.\n"
"- reload and restart are not the same.\n"
"- Debian commonly uses ssh; Red Hat/Rocky commonly uses sshd.\n\n"
"When finished, run:\n"
" M-x systemd-service-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)))
;;; 100-systemd-service-lesson.el ends here

View File

@@ -1,49 +1,764 @@
;;; 101-systemd-service-quiz.el --- systemd-service-quiz
;;; 06-linuxplus-quiz-mode.el --- linuxplus-quiz-mode
(defun systemd-service-quiz ()
"Quiz for systemd service management."
(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)
(defun linuxplus-quiz--question-type (question)
(plist-get question :type))
(defun linuxplus-quiz--question-prompt (question)
(plist-get question :prompt))
(defun linuxplus-quiz--question-answer (question)
(plist-get question :answer))
(defun linuxplus-quiz--question-answers (question)
(plist-get question :answers))
(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 'true) "[t]")
((eq answer 'false) "[f]")
((eq answer 'yes) "[y]")
((eq answer 'no) "[n]")
((eq answer 'selected) "[*]")
(t "[]")))
(defun linuxplus-quiz--answer-string (answer)
(cond
((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--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.
If selecting:
- add *
- advance to next bracket
If removing:
- clear *
- stay on current bracket."
(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)
;; Return immediately.
(setq answer nil))
;; Removal path only reaches here.
(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--sync-current-position-from-point ()
"Update current question from cursor position."
(let ((question-index
(linuxplus-quiz--question-index-at-point)))
(when question-index
(setq linuxplus-quiz-current-question
question-index)
(unless
(eq
(linuxplus-quiz--question-type
(linuxplus-quiz--current-question))
'multiple)
(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 ()
(let ((inhibit-read-only t))
(erase-buffer)
(insert "Linux+ Quiz\n")
(insert "===========\n\n")
(let ((current-type
(linuxplus-quiz--question-type
(linuxplus-quiz--current-question))))
(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 ()
(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)
(linuxplus-quiz--sync-current-position-from-point)
(:type true-false
:prompt "`systemctl disable ssh` prevents startup at boot but does not necessarily stop a currently running service."
:answer true)
(if
(eq
(linuxplus-quiz--question-type
(linuxplus-quiz--current-question))
'true-false)
(:type true-false
:prompt "`systemctl mask ssh` is stronger than `systemctl disable ssh` because it prevents normal starting."
:answer true)
(linuxplus-quiz--set-answer-and-maybe-advance
'true)
(:type true-false
:prompt "`systemctl reload ssh` and `systemctl restart ssh` always do exactly the same thing."
:answer false)
(linuxplus-quiz--invalid-input-message)))
(:type true-false
:prompt "`systemctl status <service>` is useful when troubleshooting a failed service."
:answer true)
(defun linuxplus-quiz-answer-false ()
(interactive)
(:type true-false
:prompt "`journalctl -u <service>` can show logs for a specific systemd service."
:answer true)
(linuxplus-quiz--sync-current-position-from-point)
(:type true-false
:prompt "`systemctl --failed` can help identify failed units."
:answer true)
(if
(eq
(linuxplus-quiz--question-type
(linuxplus-quiz--current-question))
'true-false)
(:type yes-no
:prompt "On many Red Hat-family systems, is the SSH service commonly named `sshd`?"
:answer yes)
(linuxplus-quiz--set-answer-and-maybe-advance
'false)
(:type yes-no
:prompt "On many Debian-family systems, is the SSH service commonly named `sshd` instead of `ssh`?"
:answer no))
"*systemd Service Quiz*"))
(linuxplus-quiz--invalid-input-message)))
;;; 101-systemd-service-quiz.el ends here
(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 ()
(interactive)
(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 ()
(interactive)
(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."
(setq buffer-read-only t))
(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,144 @@
(defun linuxplus-user-management-lesson ()
"CompTIA Linux+ lesson: user management with lesson window and ansi-term."
(interactive)
(let ((lesson-buffer (get-buffer-create "*Linux+ User Management Lesson*"))
(term-buffer-name "*Linux+ Terminal*")
(shell-program (or shell-file-name "/bin/bash")))
;; Prepare lesson buffer
(switch-to-buffer lesson-buffer)
(read-only-mode -1)
(erase-buffer)
(insert
"Linux+ XK0-005 Lesson: User and Group Management
Study objectives:
- Linux account files
- User defaults
- Group management
- Account troubleshooting
==================================================
/etc/passwd
==================================================
Stores:
- username
- UID
- primary GID
- home directory
- shell
Does NOT store:
- password hashes
- password aging
==================================================
/etc/shadow
==================================================
Stores:
- password hashes
- expiration
- password aging
Commands:
sudo chage -l $USER
==================================================
/etc/group
==================================================
Stores group information.
Commands:
groups $USER
id $USER
==================================================
User defaults
==================================================
Files:
/etc/default/useradd
/etc/login.defs
Try:
cat /etc/default/useradd
grep UID /etc/login.defs
==================================================
usermod exam trap
==================================================
SAFE:
sudo usermod -aG GROUP USER
DANGEROUS:
sudo usermod -G GROUP USER
Without -a:
Existing supplementary groups are replaced.
==================================================
Rename groups
==================================================
Syntax:
groupmod -n NEW OLD
Example:
sudo groupmod -n Development WIP
==================================================
When finished:
M-x linuxplus-user-management-quiz
")
(goto-char (point-min))
;; Create the study layout
(delete-other-windows)
;; Top = lesson
;; Bottom = terminal
(split-window-below)
(other-window 1)
;; Reuse terminal if already created
(if (get-buffer term-buffer-name)
(switch-to-buffer term-buffer-name)
(ansi-term shell-program term-buffer-name))
;; Put cursor back in lesson
(other-window -1)))

View File

@@ -0,0 +1,52 @@
;;; 103-user-management-quiz.el --- user-management-quiz
(defun linuxplus-user-management-quiz ()
"Quiz for Linux+ user and group management."
(interactive)
(linuxplus-start-quiz
'linuxplus-user-management-lesson
'((:type true-false
:prompt "The /etc/passwd file stores password expiration and aging information."
:answer false)
(:type true-false
:prompt "The /etc/group and /etc/passwd files are useful when troubleshooting group membership issues."
:answer true)
(:type true-false
:prompt "Running usermod -G developers bob always preserves existing supplementary groups."
:answer false)
(:type true-false
:prompt "The command usermod -aG wheel bob appends bob to the wheel group."
:answer true)
(:type true-false
:prompt "The file /etc/login.defs can define UID ranges and password aging defaults."
:answer true)
(:type true-false
:prompt "The command groupmod -n NewName OldName renames a Linux group."
:answer true)
(:type true-false
:prompt "The x shown in /etc/passwd means the account has no password."
:answer false)
(:type true-false
:prompt "The id command can display UID, GID, and group memberships."
:answer true)
(:type yes-no
:prompt "Is a mobile authenticator generating one-time passwords an example of a software token?"
:answer yes)
(:type yes-no
:prompt "Should /etc/profile be checked first when verifying UID and group membership problems?"
:answer no))
"*Linux+ User Management Quiz*"))
;;; 103-user-management-quiz.el ends here

View File

@@ -0,0 +1,16 @@
;;; 61-file-permissions-quiz.el --- file-permissions-quiz
(defun file-permissions-quiz ()
"Quiz for Linux file permissions and ownership."
(interactive)
(linuxplus-start-quiz
'file-permissions-lesson
'("Does chmod change file permissions?"
"Does chown change ownership?"
"Does 755 equal rwxr-xr-x?"
"Does 644 equal rw-r--r--?"
"Does execute permission on a directory allow traversal?")
'(y y y y y)
"*File Permissions Quiz*"))
;;; 61-file-permissions-quiz.el ends here

View File

@@ -0,0 +1,63 @@
;;; 70-process-management-lesson.el --- process-management-lesson
(defun process-management-lesson ()
"Linux+ lesson for process management and signals."
(interactive)
(delete-other-windows)
(let ((buf (get-buffer-create "*Process Management Lesson*"))
(shell (or shell-file-name (getenv "SHELL") "/bin/sh")))
(switch-to-buffer buf)
(read-only-mode -1)
(erase-buffer)
(insert
"Linux+ Lesson: Process Management and Signals\n"
"============================================\n\n"
"Concepts:\n"
"- ps shows processes\n"
"- top shows live processes\n"
"- kill sends signals to processes\n"
"- pkill kills by process name\n"
"- jobs shows shell background jobs\n\n"
"Important signals:\n"
" SIGTERM = 15 = graceful termination\n"
" SIGKILL = 9 = forced termination\n\n"
"Hands-on tasks:\n\n"
"1. View running processes:\n"
" ps aux | less\n\n"
"2. View live processes:\n"
" top\n"
" Press q to quit.\n\n"
"3. Start a background process:\n"
" sleep 300 &\n\n"
"4. View background jobs:\n"
" jobs\n\n"
"5. Find the sleep process:\n"
" ps aux | grep sleep\n\n"
"6. Kill the process gracefully:\n"
" kill PID\n\n"
"7. Start another sleep process:\n"
" sleep 300 &\n\n"
"8. Kill it by name:\n"
" pkill sleep\n\n"
"9. Start another sleep process:\n"
" sleep 300 &\n\n"
"10. Kill with SIGKILL:\n"
" kill -9 PID\n\n"
"Linux+ exam traps:\n"
"- kill sends signals, it does NOT always mean SIGKILL\n"
"- kill default signal is SIGTERM (15)\n"
"- pkill works by process name\n"
"- ps aux is VERY important\n\n"
"When finished, run:\n"
" M-x process-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)))
;;; 70-process-management-lesson.el ends here

View File

@@ -0,0 +1,16 @@
;;; 71-process-management-quiz.el --- process-management-quiz
(defun process-management-quiz ()
"Quiz for Linux process management and signals."
(interactive)
(linuxplus-start-quiz
'process-management-lesson
'("Does ps display processes?"
"Is SIGKILL signal 9?"
"Does kill default to SIGTERM?"
"Does pkill kill by process name?"
"Does top show live process activity?")
'(y y y y y)
"*Process Management Quiz*"))
;;; 71-process-management-quiz.el ends here

View File

@@ -0,0 +1,66 @@
;;; 80-log-filtering-lesson.el --- log-filtering-lesson
(defun log-filtering-lesson ()
"Linux+ lesson for log filtering and grep chaining."
(interactive)
(delete-other-windows)
(let ((buf (get-buffer-create "*Log Filtering Lesson*"))
(shell (or shell-file-name (getenv "SHELL") "/bin/sh")))
(switch-to-buffer buf)
(read-only-mode -1)
(erase-buffer)
(insert
"Linux+ Lesson: Log Filtering and grep Chaining\n"
"==============================================\n\n"
"Concepts:\n"
"- grep searches for patterns\n"
"- pipes combine filters\n"
"- chaining grep narrows results\n"
"- logs contain noise and useful signals\n\n"
"VERY IMPORTANT:\n"
"This is WRONG:\n\n"
" grep ssh fail logfile\n\n"
"Why?\n"
"- grep expects ONE pattern\n"
"- 'fail' becomes a filename\n\n"
"Correct approaches:\n\n"
" grep ssh logfile | grep fail\n\n"
"or:\n\n"
" grep 'ssh.*fail\\|fail.*ssh' logfile\n\n"
"Hands-on tasks:\n\n"
"1. View recent journal entries:\n"
" journalctl | head\n\n"
"2. Search for ssh activity:\n"
" journalctl | grep -i ssh\n\n"
"3. Search for failures:\n"
" journalctl | grep -i fail\n\n"
"4. Chain filters together:\n"
" journalctl | grep -i ssh | grep -i fail\n\n"
"5. Search for invalid users:\n"
" journalctl | grep -i invalid\n\n"
"6. Search for authentication issues:\n"
" journalctl | grep -i denied\n\n"
"7. Compare broad vs narrow filtering:\n"
" journalctl | grep -i ssh\n"
" journalctl | grep -i ssh | grep -i fail\n\n"
"What to notice:\n"
"- first command = noise\n"
"- second command = signal\n"
"- chained grep reduces output size\n\n"
"Linux+ exam traps:\n"
"- grep takes ONE pattern unless combined explicitly\n"
"- grep chaining is very common\n"
"- logs are often filtered progressively\n\n"
"When finished, run:\n"
" M-x log-filtering-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)))
;;; 80-log-filtering-lesson.el ends here

16
81-log-filtering-quiz.el Normal file
View File

@@ -0,0 +1,16 @@
;;; 81-log-filtering-quiz.el --- log-filtering-quiz
(defun log-filtering-quiz ()
"Quiz for log filtering and grep chaining."
(interactive)
(linuxplus-start-quiz
'log-filtering-lesson
'("Does grep normally take one pattern?"
"Does grep ssh fail logfile treat fail as a filename?"
"Can grep chaining reduce noisy output?"
"Does grep -i ignore case?"
"Is grep ssh logfile | grep fail a valid filter chain?")
'(y y y y y)
"*Log Filtering Quiz*"))
;;; 81-log-filtering-quiz.el ends here

83
90-file-search-lesson.el Normal file
View File

@@ -0,0 +1,83 @@
;;; 90-file-search-lesson.el --- file-search-lesson
(defun file-search-lesson ()
"Linux+ lesson for finding files and commands."
(interactive)
(delete-other-windows)
(let ((buf (get-buffer-create "*File Search Lesson*"))
(shell (or shell-file-name (getenv "SHELL") "/bin/sh")))
(switch-to-buffer buf)
(read-only-mode -1)
(erase-buffer)
(insert
"Linux+ Lesson: File Searching and Command Discovery\n"
"==================================================\n\n"
"Concepts:\n"
"- `find` searches the filesystem in real time\n"
"- `locate` searches a database\n"
"- `which` finds executable paths\n"
"- `grep` filters output\n\n"
"VERY IMPORTANT:\n"
"- `find` is slower but current\n"
"- `locate` is faster but may be outdated\n\n"
"Hands-on tasks:\n\n"
"1. Create a practice directory:\n"
" mkdir -p ~/linuxplus-search-test\n"
" cd ~/linuxplus-search-test\n\n"
"2. Create practice files:\n"
" touch alpha.txt beta.log gamma.conf\n\n"
"3. Find a specific file:\n"
" find . -name alpha.txt\n\n"
"4. Find all .txt files:\n"
" find . -name '*.txt'\n\n"
"5. Find directories only:\n"
" find . -type d\n\n"
"6. Find files only:\n"
" find . -type f\n\n"
"7. Search for a command location:\n"
" which grep\n"
" which systemctl\n\n"
"8. Use locate:\n"
" locate passwd\n\n"
"9. If locate gives poor results:\n"
" sudo updatedb\n\n"
"10. Combine find with grep:\n"
" find /etc -name '*.conf' | grep ssh\n\n"
"Linux+ exam traps:\n"
"- `find` searches live filesystem data\n"
"- `locate` uses a database\n"
"- `which` searches PATH for executables\n"
"- `find` is commonly combined with pipes\n\n"
"When finished, run:\n"
" M-x file-search-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)))
;;; 90-file-search-lesson.el ends here

50
91-file-search-quiz.el Normal file
View File

@@ -0,0 +1,50 @@
;;; 91-file-search-quiz.el --- file-search-quiz
(defun file-search-quiz ()
"Quiz for Linux file searching tools."
(interactive)
(linuxplus-start-quiz
'file-search-lesson
'((:type true-false
:prompt "`find` searches the live filesystem while `locate` uses a database."
:answer 'true)
(:type true-false
:prompt "`which grep` can display the path to the `grep` executable."
:answer 'true)
(:type true-false
:prompt "`locate` always reflects the most current filesystem state."
:answer 'false)
(:type true-false
:prompt "`find . -type f` searches only for files."
:answer 'true)
(:type true-false
:prompt "`find /etc -name '*.conf' | grep ssh` is a valid filter chain."
:answer 'true)
(:type multiple
:prompt "Which statements about Linux file-search tools are correct?"
:choices ("`find` searches the live filesystem."
"`locate` uses a database."
"`which` searches PATH for executables."
"All of the above.")
:answers (0 1 2 3))
(:type multiple
:prompt "Which statements are false?"
:choices ("`locate` can show stale results if its database is outdated."
"`which` searches the entire filesystem."
"`find` requires an `updatedb` database before it works."
"`find . -name '*.txt'` can search for text files below the current directory.")
:answers (1 2))
(:type yes-no
:prompt "Does `updatedb` refresh the database used by `locate`?"
:answer 'yes))
"*File Search Quiz*"))
;;; 91-file-search-quiz.el ends here