Harden posture/borg audit scripts (robustness + coverage)

Working-tree improvements to the audit scripts (authored locally, not by me;
reviewed for correctness + bash -n clean before commit):

- compose-runtime-drift: prefer `docker compose config` for the expected image
  with a raw-parse fallback; raw parser now resolves YAML anchors (*alias) so
  anchor-based composes (e.g. dawarich) no longer mis-report drift.
- komodo-stack-hygiene: treat an unreachable Komodo API as critical and exit 3
  so the Healthchecks EXIT trap sends /fail (the monitor itself is down, not
  "all green"); git fetch before hash-drift compare; clearer "cannot compare"
  message; pin in-container km host to localhost:9120.
- cert-token-check: expand monitored cert domains to the full set incl.
  hc.kaleschke.info.
- gitea-bundle-mirror: skip empty repos without refs instead of failing.
- unraid-user-scripts.md: document SEND_NTFY/NTFY_TOPIC for the daily report.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 11:35:55 +02:00
parent 036eba99a8
commit ad9bb40b95
5 changed files with 145 additions and 14 deletions
@@ -26,7 +26,73 @@ add_result() {
printf '%s\t%s\t%s\n' "$1" "$2" "$3" >> "$RESULTS_FILE"
}
parse_compose() {
parse_normalized_compose() {
awk '
function clean(value) {
gsub(/\r/, "", value)
gsub(/["'\''"]/, "", value)
return value
}
function emit() {
if (in_services && service && image) {
print clean(container) "\t" clean(image)
}
}
/^services:/ {
emit()
in_services=1
service=""
image=""
container=""
next
}
/^[A-Za-z0-9_.-]+:/ && $0 !~ /^services:/ {
if (in_services) {
emit()
in_services=0
service=""
image=""
container=""
}
}
in_services && /^ [A-Za-z0-9_.-]+:/ {
emit()
service=$1
sub(/:$/, "", service)
image=""
container=service
next
}
in_services && service && /^ image:/ {
image=$0
sub(/^[[:space:]]*image:[[:space:]]*/, "", image)
next
}
in_services && service && /^ container_name:/ {
container=$0
sub(/^[[:space:]]*container_name:[[:space:]]*/, "", container)
next
}
END { emit() }
'
}
parse_compose_with_docker() {
local compose="$1"
local dir
local file
command -v docker >/dev/null 2>&1 || return 1
dir="$(dirname "$compose")"
file="$(basename "$compose")"
(
cd "$dir"
docker compose -f "$file" config 2>/dev/null
) | parse_normalized_compose
}
parse_compose_raw() {
local compose="$1"
awk '
function clean(value) {
@@ -36,9 +102,25 @@ parse_compose() {
}
function emit() {
if (service && image && !has_profile) {
if (image ~ /^\*/) {
alias=image
sub(/^\*/, "", alias)
if (alias in anchors) {
image=anchors[alias]
}
}
print clean(container) "\t" clean(image)
}
}
/^x-[A-Za-z0-9_.-]+:[[:space:]]*&[A-Za-z0-9_.-]+[[:space:]]+/ {
alias=$0
sub(/^.*&/, "", alias)
sub(/[[:space:]].*$/, "", alias)
value=$0
sub(/^.*&[A-Za-z0-9_.-]+[[:space:]]+/, "", value)
anchors[alias]=value
next
}
/^ [A-Za-z0-9_.-]+:/ {
emit()
service=$1
@@ -66,6 +148,16 @@ parse_compose() {
' "$compose"
}
parse_compose() {
local compose="$1"
if parse_compose_with_docker "$compose"; then
return 0
fi
parse_compose_raw "$compose"
}
while IFS= read -r -d '' compose; do
while IFS="$(printf '\t')" read -r container expected_image; do
[ -n "$container" ] || continue