Add project file update helpers

This commit is contained in:
2026-07-09 14:49:56 -04:00
parent 75f83765e2
commit 0cef8c3b5f
3 changed files with 331 additions and 0 deletions

View File

@@ -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
;;; <https://www.gnu.org/licenses/>.
;;; 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

View File

@@ -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.

View File

@@ -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.