From 42ca69a43f590e5233b541327b3161ab2f69964f Mon Sep 17 00:00:00 2001 From: "Scott C. MacCallum" Date: Tue, 16 Jun 2026 10:40:35 -0400 Subject: [PATCH] Adding files --- 06-linuxplus-quiz-mode.el | 954 ++++++++++++++++++++++++++++---- 101-systemd-service-quiz.el | 49 ++ 102-network-tools-lesson.el | 123 ++++ 103-network-tools-quiz.el | 52 ++ 30-auth-log-lesson.el | 56 ++ 31-auth-log-quiz.el | 16 + 40-package-management-lesson.el | 55 ++ 41-package-management-quiz.el | 16 + 60-file-permissions-lesson.el | 74 +++ 9 files changed, 1272 insertions(+), 123 deletions(-) create mode 100644 101-systemd-service-quiz.el create mode 100644 102-network-tools-lesson.el create mode 100644 103-network-tools-quiz.el create mode 100644 30-auth-log-lesson.el create mode 100644 31-auth-log-quiz.el create mode 100644 40-package-management-lesson.el create mode 100644 41-package-management-quiz.el create mode 100644 60-file-permissions-lesson.el diff --git a/06-linuxplus-quiz-mode.el b/06-linuxplus-quiz-mode.el index fb82d0b..8c6393b 100644 --- a/06-linuxplus-quiz-mode.el +++ b/06-linuxplus-quiz-mode.el @@ -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 diff --git a/101-systemd-service-quiz.el b/101-systemd-service-quiz.el new file mode 100644 index 0000000..dd74fe0 --- /dev/null +++ b/101-systemd-service-quiz.el @@ -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 ` is useful when troubleshooting a failed service." + :answer true) + + (:type true-false + :prompt "`journalctl -u ` 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 diff --git a/102-network-tools-lesson.el b/102-network-tools-lesson.el new file mode 100644 index 0000000..8bddfa9 --- /dev/null +++ b/102-network-tools-lesson.el @@ -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 diff --git a/103-network-tools-quiz.el b/103-network-tools-quiz.el new file mode 100644 index 0000000..cbd34b5 --- /dev/null +++ b/103-network-tools-quiz.el @@ -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 diff --git a/30-auth-log-lesson.el b/30-auth-log-lesson.el new file mode 100644 index 0000000..c95f4d5 --- /dev/null +++ b/30-auth-log-lesson.el @@ -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 | wc -l\n\n" + "Replace 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 diff --git a/31-auth-log-quiz.el b/31-auth-log-quiz.el new file mode 100644 index 0000000..e4207c3 --- /dev/null +++ b/31-auth-log-quiz.el @@ -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 diff --git a/40-package-management-lesson.el b/40-package-management-lesson.el new file mode 100644 index 0000000..a51ebd5 --- /dev/null +++ b/40-package-management-lesson.el @@ -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 diff --git a/41-package-management-quiz.el b/41-package-management-quiz.el new file mode 100644 index 0000000..ea2eb29 --- /dev/null +++ b/41-package-management-quiz.el @@ -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 diff --git a/60-file-permissions-lesson.el b/60-file-permissions-lesson.el new file mode 100644 index 0000000..353e737 --- /dev/null +++ b/60-file-permissions-lesson.el @@ -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