Skip to content

Instantly share code, notes, and snippets.

@zzf01
Created January 21, 2026 14:44
Show Gist options
  • Select an option

  • Save zzf01/23cbf3c186ba201860d8cbd8c0910a73 to your computer and use it in GitHub Desktop.

Select an option

Save zzf01/23cbf3c186ba201860d8cbd8c0910a73 to your computer and use it in GitHub Desktop.
ufw firewall um kommentare ergänzen
#!/usr/bin/env bash
set -euo pipefail
COMMENTS_FILE="${COMMENTS_FILE:-/etc/ufw/rule-comments.tsv}"
usage() {
echo "Usage:"
echo " $0 # explain (default)"
echo " $0 explain # show ufw rules with stored comments"
echo " $0 comment # ask for comments for each current ufw rule"
echo
echo "Env:"
echo " COMMENTS_FILE=/path/to/file.tsv"
}
require_root_if_needed() {
if [[ "${1:-}" == "comment" ]]; then
if [[ "$COMMENTS_FILE" == /etc/* ]] && [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
echo "ERROR: comment mode needs root to write to $COMMENTS_FILE (or set COMMENTS_FILE to a writable path)." >&2
exit 1
fi
fi
}
ensure_comments_file() {
local dir; dir="$(dirname "$COMMENTS_FILE")"
[[ -d "$dir" ]] || mkdir -p "$dir"
[[ -f "$COMMENTS_FILE" ]] || touch "$COMMENTS_FILE"
chmod 600 "$COMMENTS_FILE" 2>/dev/null || true
}
get_rules() {
ufw status numbered | sed -n 's/^\[[[:space:]]*[0-9]\+\][[:space:]]*//p' | sed '/^$/d'
}
tsv_get_comment() {
local key="$1"
awk -F'\t' -v k="$key" '$1==k { $1=""; sub(/^\t/,""); print; exit }' "$COMMENTS_FILE" 2>/dev/null || true
}
tsv_set_comment() {
local key="$1"; local val="$2"; local tmp; tmp="$(mktemp)"
awk -F'\t' -v k="$key" '$1!=k {print}' "$COMMENTS_FILE" > "$tmp" 2>/dev/null || true
printf "%s\t%s\n" "$key" "$val" >> "$tmp"
mv "$tmp" "$COMMENTS_FILE"
}
tsv_prune_empty() {
awk -F'\t' '!(NF>=2 && $2=="")' "$COMMENTS_FILE" > "${COMMENTS_FILE}.tmp" 2>/dev/null || true
mv "${COMMENTS_FILE}.tmp" "$COMMENTS_FILE"
}
extract_ips_from_rule() {
# prints one IP per line (v4 and v6)
local rule="$1"
# IPv4
grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' <<<"$rule" || true
# IPv6 (simple heuristic: any token containing ':' with hex chars)
tr ' ' '\n' <<<"$rule" | grep -E '^[0-9A-Fa-f:]+$' | grep -q ':' && tr ' ' '\n' <<<"$rule" | grep -E '^[0-9A-Fa-f:]+$' | grep ':' || true
}
reverse_dns_one() {
local ip="$1"
# getent uses system resolver (NSS). Output often: "IP FQDN [aliases...]"
local name; name="$(getent hosts "$ip" 2>/dev/null | awk '{print $2}' | head -n1 || true)"
[[ -n "$name" ]] && echo "$name" || true
}
suggest_comment_from_rule() {
local rule="$1"
local ip; local name; local out=""
while IFS= read -r ip; do
[[ -z "$ip" ]] && continue
name="$(reverse_dns_one "$ip")"
if [[ -n "$name" ]]; then
if [[ -n "$out" ]]; then out="${out}, ${name}"; else out="$name"; fi
fi
done < <(extract_ips_from_rule "$rule" | awk '!seen[$0]++')
[[ -n "$out" ]] && echo "$out" || true
}
mode_comment() {
ensure_comments_file
mapfile -t rules < <(get_rules)
if [[ "${#rules[@]}" -eq 0 ]]; then echo "No ufw rules found."; exit 0; fi
echo "Comment file: $COMMENTS_FILE"
echo "ENTER: keep existing comment (or accept default if none exists). Type '-' to clear."
echo
local rule existing suggested input
for rule in "${rules[@]}"; do
existing="$(tsv_get_comment "$rule")"
suggested=""
if [[ -z "$existing" ]]; then suggested="$(suggest_comment_from_rule "$rule")"; fi
echo "Rule: $rule"
[[ -n "$existing" ]] && echo "Existing comment: $existing"
[[ -z "$existing" && -n "$suggested" ]] && echo "Suggested (reverse DNS): $suggested"
if [[ -z "$existing" && -n "$suggested" ]]; then
read -r -p "Comment [default: $suggested]> " input || true
else
read -r -p "Comment> " input || true
fi
if [[ -z "$input" ]]; then
if [[ -n "$existing" ]]; then echo; continue; fi
if [[ -z "$existing" && -n "$suggested" ]]; then tsv_set_comment "$rule" "$suggested"; echo "(saved default)"; echo; continue; fi
echo; continue
fi
if [[ "$input" == "-" ]]; then
tsv_set_comment "$rule" ""
tsv_prune_empty
echo "(cleared)"; echo
continue
fi
tsv_set_comment "$rule" "$input"
echo "(saved)"; echo
done
echo "Done."
}
mode_explain() {
ensure_comments_file
local line rule comment
while IFS= read -r line; do
if [[ "$line" =~ ^\[[[:space:]]*[0-9]+\] ]]; then
rule="$(sed -E 's/^\[[[:space:]]*[0-9]+\][[:space:]]*//' <<<"$line")"
comment="$(tsv_get_comment "$rule")"
if [[ -n "$comment" ]]; then printf "%s # %s\n" "$line" "$comment"; else echo "$line"; fi
else
echo "$line"
fi
done < <(ufw status numbered)
}
main() {
# default = explain
local mode="${1:-explain}"
case "$mode" in
explain) mode_explain ;;
comment) require_root_if_needed comment; mode_comment ;;
-h|--help|help) usage ;;
*) echo "Unknown mode: $mode" >&2; usage; exit 1 ;;
esac
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment