Created
January 21, 2026 14:44
-
-
Save zzf01/23cbf3c186ba201860d8cbd8c0910a73 to your computer and use it in GitHub Desktop.
ufw firewall um kommentare ergänzen
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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