280 lines
9.0 KiB
EmacsLisp
280 lines
9.0 KiB
EmacsLisp
;;; 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
|