From 0cef8c3b5fa4e47006cc12c30fce90eaeda385f4 Mon Sep 17 00:00:00 2001 From: "Scott C. MacCallum" Date: Thu, 9 Jul 2026 14:49:56 -0400 Subject: [PATCH] Add project file update helpers --- 07-linuxplus-update-files.el | 279 +++++++++++++++++++++++++++++++++++ RECOVERY.md | 32 ++++ STUDY.md | 20 +++ 3 files changed, 331 insertions(+) create mode 100644 07-linuxplus-update-files.el diff --git a/07-linuxplus-update-files.el b/07-linuxplus-update-files.el new file mode 100644 index 0000000..637ab04 --- /dev/null +++ b/07-linuxplus-update-files.el @@ -0,0 +1,279 @@ +;;; 07-linuxplus-update-files.el --- linuxplus-update-files +;;; +;;; Provide safe helpers for updating existing project files. +;;; +;;; This file is part of the Linux+ Learning project. +;;; +;;; Copyright (c) 2026, Scott C. MacCallum (scm@sdf.org). +;;; +;;; This program is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU Affero General Public License as +;;; published by the Free Software Foundation, either version 3 of the +;;; License, or (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU Affero General Public License for more details. +;;; +;;; You should have received a copy of the GNU Affero General +;;; Public License along with this program. If not, see +;;; . + +;;; Commentary: + +;; This file provides small update helpers for existing project files. +;; +;; The main command is: +;; +;; M-x linuxplus-apply-firewall-score-update +;; +;; It updates STUDY.md by marking the firewall review section as Solid +;; and by recording the 20/20 firewall quiz score. +;; +;; The helper functions are intentionally simple. They are meant to +;; avoid fragile hand-written patch hunks when making repeated project +;; documentation updates. + +;;; Code: + +(require 'subr-x) + +(defvar linuxplus-update-base-directory + (file-name-directory + (or load-file-name buffer-file-name default-directory)) + "Base directory for Linux+ project update helpers.") + +(defun linuxplus-update--file (name) + "Return the full project path for file NAME." + (expand-file-name name linuxplus-update-base-directory)) + +(defun linuxplus-update--read-file (file) + "Return the contents of FILE as a string." + (with-temp-buffer + (insert-file-contents file) + (buffer-string))) + +(defun linuxplus-update--write-file (file text) + "Write TEXT to FILE." + (with-temp-file file + (insert text))) + +(defun linuxplus-update--backup-file (file) + "Create a numbered backup copy of FILE." + (let* ((index 1) + (backup (format "%s.bak.%d" file index))) + (while (file-exists-p backup) + (setq index (1+ index)) + (setq backup (format "%s.bak.%d" file index))) + (copy-file file backup) + backup)) + +(defun linuxplus-update--section-bounds (text heading) + "Return section bounds for HEADING in TEXT. +The result is a cons cell whose car is the start position and whose +cdr is the end position. The end is the next Markdown level two +heading or the end of TEXT." + (let ((start (string-match (concat "^" (regexp-quote heading) "$") + text))) + (unless start + (error "Could not find heading: %s" heading)) + (let* ((search-start (+ start (length heading))) + (next (string-match "^## " text search-start))) + (cons start (or next (length text)))))) + +(defun linuxplus-update-replace-section (file heading new-section) + "In FILE, replace Markdown section HEADING with NEW-SECTION." + (let* ((text (linuxplus-update--read-file file)) + (bounds (linuxplus-update--section-bounds text heading)) + (start (car bounds)) + (end (cdr bounds)) + (backup (linuxplus-update--backup-file file)) + (updated (concat (substring text 0 start) + (string-trim-right new-section) + "\n\n" + (substring text end)))) + (linuxplus-update--write-file file updated) + (message "Updated %s; backup saved as %s" file backup))) + +(defun linuxplus-update-insert-before-heading (file heading text-to-insert) + "In FILE, insert TEXT-TO-INSERT before Markdown HEADING." + (let* ((text (linuxplus-update--read-file file)) + (marker (concat "\n" heading)) + (pos (string-match (regexp-quote marker) text)) + (backup (linuxplus-update--backup-file file))) + (unless pos + (error "Could not find heading: %s" heading)) + (linuxplus-update--write-file + file + (concat (substring text 0 (1+ pos)) + (string-trim-right text-to-insert) + "\n\n" + (substring text (1+ pos)))) + (message "Updated %s; backup saved as %s" file backup))) + +(defun linuxplus-update-replace-section-with-region + (file heading beg end) + "Replace Markdown section HEADING in FILE with region BEG to END." + (interactive + (list (read-file-name "File to update: " + linuxplus-update-base-directory) + (read-string "Section heading: ") + (region-beginning) + (region-end))) + (linuxplus-update-replace-section + file heading (buffer-substring-no-properties beg end))) + +(defun linuxplus-update-insert-region-before-heading + (file heading beg end) + "Insert region BEG to END before Markdown HEADING in FILE." + (interactive + (list (read-file-name "File to update: " + linuxplus-update-base-directory) + (read-string "Insert before heading: ") + (region-beginning) + (region-end))) + (linuxplus-update-insert-before-heading + file heading (buffer-substring-no-properties beg end))) + +(defun linuxplus-update--firewall-section () + "Return the corrected Firewall Review STUDY.md section." + "## Weak Area: Firewall Review + +Missed area: + + Firewall basics, stateful firewall behavior, firewalld persistence, + UFW command syntax, and DenyHosts/Fail2Ban recognition + +Correct concept: + + Firewalls use ACLs, inspect network packets, and use configuration + files for persistence. + + ACL means access control list. Firewall ACL rules allow or deny + traffic based on details such as source address, destination + address, protocol, port, or interface. + + A stateless firewall checks packets against rules without remembering + the state of a connection. + + A stateful firewall tracks connection state. For this book review, + remember that a stateful firewall can operate faster for established + connections and can determine if packets are fragmented. + + firewalld can use runtime configuration and permanent configuration. + After testing firewalld runtime rules, make them permanent with: + + sudo firewall-cmd --runtime-to-permanent + + To view numbered UFW rules, use: + + sudo ufw status numbered + + To block SSH using simple UFW syntax, use: + + sudo ufw deny 22/tcp + + For this book item, remember DenyHosts and Fail2Ban as IDS tools. + The expected answer associates them with /etc/hosts.deny. + +Exam wording trap: + + \"Make firewalld runtime rules permanent\" points to: + + firewall-cmd --runtime-to-permanent + + \"Numbered UFW rules\" points to: + + sudo ufw status numbered + + \"Deny SSH with simple UFW syntax\" points to: + + sudo ufw deny 22/tcp + + DenyHosts is strongly associated with /etc/hosts.deny. + + Fail2Ban commonly blocks through firewall actions in real-world + configurations, but for the book question, memorize the book's + expected wording. + +Commands/files to remember: + + sudo firewall-cmd --runtime-to-permanent + sudo ufw status numbered + sudo ufw deny 22/tcp + /etc/hosts.deny + +Future lesson topic: + + 104-firewall-review-lesson.el + 105-firewall-review-quiz.el + +Status: + + Solid") + +(defun linuxplus-update--firewall-score-note () + "Return the firewall score note for STUDY.md." + "Date: + + 2026-07-09 + +Lesson: + + 104-firewall-review-lesson.el + +Quiz: + + 105-firewall-review-quiz.el + +Score: + + 20/20 + +Missed: + + None + +Next action: + + Do a quick review later to keep the commands fresh: + + sudo firewall-cmd --runtime-to-permanent + sudo ufw status numbered + sudo ufw deny 22/tcp + /etc/hosts.deny") + +(defun linuxplus-apply-firewall-score-update () + "Update STUDY.md with the firewall review score and Solid status." + (interactive) + (let ((study-file (linuxplus-update--file "STUDY.md"))) + (linuxplus-update-replace-section + study-file + "## Weak Area: Firewall Review" + (linuxplus-update--firewall-section)) + (let ((text (linuxplus-update--read-file study-file))) + (unless (string-match-p "Score:\n\n 20/20" text) + (linuxplus-update-insert-before-heading + study-file + "## Current Notes" + (linuxplus-update--firewall-score-note))))) + (message "Firewall review score update complete.")) + +(defun linuxplus-update-help () + "Show help for Linux+ project update helpers." + (interactive) + (with-help-window "*Linux+ Update Help*" + (princ "Linux+ project update helpers\n\n") + (princ "Useful commands:\n\n") + (princ " M-x linuxplus-apply-firewall-score-update\n") + (princ " Update STUDY.md for the 20/20 firewall quiz score.\n\n") + (princ " M-x linuxplus-update-replace-section-with-region\n") + (princ " Replace a Markdown section with selected text.\n\n") + (princ " M-x linuxplus-update-insert-region-before-heading\n") + (princ " Insert selected text before a Markdown heading.\n\n") + (princ "Each update creates a numbered .bak.N backup first.\n"))) + +(provide '07-linuxplus-update-files) + +;;; 07-linuxplus-update-files.el ends here diff --git a/RECOVERY.md b/RECOVERY.md index 51fe304..18e6c7a 100644 --- a/RECOVERY.md +++ b/RECOVERY.md @@ -313,3 +313,35 @@ Lesson content is based on missed Linux+ firewall book questions: * Fail2Ban The lesson preserves the real-world note that DenyHosts is strongly associated with `/etc/hosts.deny`, while Fail2Ban commonly blocks through firewall actions. For the book question, memorize the book's expected wording. + +## RECOVERY maintenance update - 2026-07-09 + +Added project-native Emacs Lisp helpers for updating existing files. + +New file: + + * `07-linuxplus-update-files.el` + +Reason: + + * Hand-written patch hunks proved fragile. + * Some updates need to modify existing Markdown files safely. + * The helper file is numbered so `linuxplus-load-all` loads it with + the rest of the project. + * Each update creates a numbered `.bak.N` backup before writing. + +Important commands: + + M-x linuxplus-update-help + M-x linuxplus-apply-firewall-score-update + M-x linuxplus-update-replace-section-with-region + M-x linuxplus-update-insert-region-before-heading + +The firewall score updater modifies `STUDY.md` by: + + * replacing the `## Weak Area: Firewall Review` section + * setting its status to `Solid` + * inserting the 20/20 score note before `## Current Notes` + +Future assistants should prefer these project update helpers over +hand-written shell patch hunks when updating existing project files. diff --git a/STUDY.md b/STUDY.md index dd22776..195ff1c 100644 --- a/STUDY.md +++ b/STUDY.md @@ -1553,3 +1553,23 @@ Score: Missed: Next action: ``` +## Project maintenance note: file update helpers + +The project now includes an Emacs Lisp helper file for safe updates to +existing project files: + + 07-linuxplus-update-files.el + +Use this when updating existing Markdown files such as `STUDY.md` and +`RECOVERY.md`. + +Useful commands: + + M-x linuxplus-update-help + M-x linuxplus-apply-firewall-score-update + M-x linuxplus-update-replace-section-with-region + M-x linuxplus-update-insert-region-before-heading + +The update helpers create numbered `.bak.N` backup files before writing +changes. This is preferred over hand-written patch hunks for small +project documentation updates.