5ef38dc46f
- deps guards before -h|--help in docker-health/ps, network-scan, usb-server, media-mp3/mp4 (--dry-run pre-scan kept); system-firewall gains usage()/--help; autostart/usb-automount get flags.sh + template - install.sh: normalize N-M range syntax in --steps - bin/pos: INTERACTIVE_CMDS += docker-compose docker-vbox network-hotspot - common.sh: canonical XDG-aware CONFIG_DIR + DIM color var; notify.sh stderr fallback; ent_plugin_* registry renames (runtime plugin API kept) - docker-compose SCALE_DIR/CONFIG_ENV env seams; ffmpeg in PACKAGES; scrcpy.sh exec bit - docs: health is console-only (--send/--markdown removed), POS.md file refs for config/tree/entertainment, DEV.md no-guard exception, docmap/ filetable regenerated (make gen), hand-maintained line rows bumped - add scripts/lint-conventions.sh gate + Makefile lint target; record all VERIFIED outcomes in MAINTENANCE.md; AGENT_TODO Done entry (2026-08-14) - gates: make gen/check/lint all green (0 FAIL, 0 WARN); bash -n sweep clean; restricted-PATH dep tests + step-matrix dry-runs verified
309 lines
9.4 KiB
Bash
Executable File
309 lines
9.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
# POS: system firewall — Interactive UFW management
|
|
IFS=$'\n\t'
|
|
|
|
if [[ $EUID -ne 0 ]]; then
|
|
echo "ERROR: Please run as root (sudo)."
|
|
echo "Usage: sudo pos system firewall"
|
|
exit 1
|
|
fi
|
|
|
|
source "$(dirname "$0")/../lib/notify.sh" 2>/dev/null || source "$(dirname "$0")/notify.sh"
|
|
|
|
HISTORY=()
|
|
DRY_RUN=0
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: sudo pos system firewall [--dry-run]
|
|
|
|
Interactive UFW management menu.
|
|
|
|
Options:
|
|
--dry-run Preview commands without executing them
|
|
-h, --help Show this help
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
case "${1:-}" in
|
|
-h|--help) usage ;;
|
|
esac
|
|
|
|
if [[ "${1:-}" == "--dry-run" ]]; then
|
|
DRY_RUN=1
|
|
fi
|
|
|
|
log() { echo "[+] $*"; }
|
|
warn() { echo "[!] $*"; }
|
|
err() { echo "ERROR: $*" >&2; exit 1; }
|
|
|
|
run_cmd() {
|
|
local -a cmd=("$@")
|
|
printf "\n>>> %s\n" "${cmd[*]}"
|
|
read -rp "Execute this command? [y/N]: " confirm
|
|
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
|
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
echo "(dry-run) skipping execution"
|
|
else
|
|
"${cmd[@]}"
|
|
# Alert on mutating commands only (status queries are read-only)
|
|
if [[ "${cmd[1]:-}" != "status" ]]; then
|
|
notify_send "Firewall: ${cmd[*]}"
|
|
fi
|
|
fi
|
|
HISTORY+=("${cmd[*]}")
|
|
else
|
|
echo "Cancelled."
|
|
fi
|
|
}
|
|
|
|
build_ufw_cmd() {
|
|
local action="$1"
|
|
local direction="$2"
|
|
local proto="$3"
|
|
local from="$4"
|
|
local to="$5"
|
|
local port="$6"
|
|
local onif="$7"
|
|
local logmode="$8"
|
|
local comment="$9"
|
|
local insert_pos="${10:-}"
|
|
local suffix="${11:-}"
|
|
|
|
local -a cmd=(ufw)
|
|
|
|
if [[ -n "$insert_pos" ]]; then
|
|
if [[ "$insert_pos" == "prepend" ]]; then
|
|
cmd+=(prepend)
|
|
else
|
|
cmd+=(insert "$insert_pos")
|
|
fi
|
|
fi
|
|
|
|
cmd+=("$action")
|
|
|
|
[[ -n "$direction" ]] && cmd+=("$direction")
|
|
[[ -n "$onif" ]] && cmd+=(on "$onif")
|
|
[[ -n "$proto" ]] && cmd+=(proto "$proto")
|
|
[[ -n "$from" ]] && cmd+=(from "$from")
|
|
cmd+=(to "$to")
|
|
[[ -n "$port" ]] && cmd+=(port "$port")
|
|
[[ -n "$logmode" ]] && cmd+=("$logmode")
|
|
if [[ -n "$comment" ]]; then
|
|
local safe="${comment// /_}"
|
|
cmd+=(comment "$safe")
|
|
fi
|
|
[[ "$suffix" == "v6" ]] && cmd+=(v6)
|
|
|
|
run_cmd "${cmd[@]}"
|
|
}
|
|
|
|
prompt_ipver() {
|
|
local ver
|
|
read -rp "IP version (4 / 6 / both): " ver
|
|
echo "$ver"
|
|
}
|
|
|
|
apply_for_versions() {
|
|
local action="$1" direction="$2" proto="$3" from="$4" to="$5"
|
|
local port="$6" onif="$7" logmode="$8" comment="$9"
|
|
local insert_pos="${10:-}"
|
|
local ipver
|
|
ipver=$(prompt_ipver)
|
|
|
|
case "$ipver" in
|
|
4) build_ufw_cmd "$action" "$direction" "$proto" "$from" "$to" "$port" "$onif" "$logmode" "$comment" "$insert_pos" "" ;;
|
|
6) build_ufw_cmd "$action" "$direction" "$proto" "$from" "$to" "$port" "$onif" "$logmode" "$comment" "$insert_pos" "v6" ;;
|
|
both)
|
|
build_ufw_cmd "$action" "$direction" "$proto" "$from" "$to" "$port" "$onif" "$logmode" "$comment" "$insert_pos" ""
|
|
build_ufw_cmd "$action" "$direction" "$proto" "$from" "$to" "$port" "$onif" "$logmode" "$comment" "$insert_pos" "v6"
|
|
;;
|
|
*) echo "Invalid choice. Choose 4, 6 or both." ;;
|
|
esac
|
|
}
|
|
|
|
add_rule() {
|
|
echo
|
|
echo "Choose rule type:"
|
|
echo "1) Port/service (eg: port 8080 or 'ssh')"
|
|
echo "2) IP-based (from X to Y)"
|
|
echo "3) Directional port rule (in/out to any port ...)"
|
|
read -rp "Choice: " rtype
|
|
|
|
case "$rtype" in
|
|
1)
|
|
read -rp "Action (allow/deny/reject/limit) [allow]: " action
|
|
action=${action:-allow}
|
|
read -rp "Enter port number or service name (eg 'ssh' or '8080'): " port_or_svc
|
|
|
|
if [[ "$port_or_svc" =~ ^[0-9]+$ ]]; then
|
|
read -rp "Protocol (tcp/udp/any) [tcp]: " proto
|
|
proto=${proto:-tcp}
|
|
[[ "$proto" == "any" ]] && proto=""
|
|
read -rp "Interface (leave empty for any): " onif
|
|
read -rp "Log? (none/log/log-all) [none]: " logmode
|
|
[[ "$logmode" == "none" ]] && logmode=""
|
|
read -rp "Comment (optional): " comment
|
|
|
|
apply_for_versions "$action" "" "$proto" "" "any" "$port_or_svc" "$onif" "$logmode" "$comment"
|
|
else
|
|
read -rp "IP version (4 / 6 / both) [4]: " ipver
|
|
ipver=${ipver:-4}
|
|
case "$ipver" in
|
|
4) run_cmd ufw "$action" "$port_or_svc" ;;
|
|
6) run_cmd ufw "$action" "$port_or_svc" v6 ;;
|
|
both) run_cmd ufw "$action" "$port_or_svc"
|
|
run_cmd ufw "$action" "$port_or_svc" v6 ;;
|
|
*) echo "invalid ipver" ;;
|
|
esac
|
|
fi
|
|
;;
|
|
|
|
2)
|
|
read -rp "Action (allow/deny/reject) [deny]: " action
|
|
action=${action:-deny}
|
|
read -rp "From address/CIDR (eg 192.168.1.5 or 10.0.0.0/24): " from
|
|
read -rp "To address (leave empty for 'any') [any]: " to
|
|
to=${to:-any}
|
|
read -rp "Direction (in/out) [in]: " direction
|
|
direction=${direction:-in}
|
|
read -rp "Port (leave empty if not applicable): " port
|
|
read -rp "Protocol (tcp/udp/any) [any]: " proto
|
|
[[ "$proto" == "any" ]] && proto=""
|
|
read -rp "Interface (leave empty for any): " onif
|
|
read -rp "Log? (none/log/log-all) [none]: " logmode
|
|
[[ "$logmode" == "none" ]] && logmode=""
|
|
read -rp "Comment (optional): " comment
|
|
|
|
apply_for_versions "$action" "$direction" "$proto" "$from" "$to" "$port" "$onif" "$logmode" "$comment"
|
|
;;
|
|
|
|
3)
|
|
read -rp "Action (allow/deny/reject/limit) [allow]: " action
|
|
action=${action:-allow}
|
|
read -rp "Direction (in/out) [in]: " direction
|
|
direction=${direction:-in}
|
|
read -rp "Port number: " port
|
|
read -rp "Protocol (tcp/udp/any) [tcp]: " proto
|
|
[[ "$proto" == "any" ]] && proto=""
|
|
read -rp "On interface (leave empty for any): " onif
|
|
read -rp "From address (optional): " from
|
|
from=${from:-}
|
|
read -rp "To address [any]: " to
|
|
to=${to:-any}
|
|
read -rp "Log? (none/log/log-all) [none]: " logmode
|
|
[[ "$logmode" == "none" ]] && logmode=""
|
|
read -rp "Comment (optional): " comment
|
|
read -rp "Insert position (number/prepend/empty): " insert_pos
|
|
|
|
apply_for_versions "$action" "$direction" "$proto" "$from" "$to" "$port" "$onif" "$logmode" "$comment" "$insert_pos"
|
|
;;
|
|
|
|
*) echo "Unknown choice." ;;
|
|
esac
|
|
}
|
|
|
|
delete_rule() {
|
|
echo
|
|
echo "Delete rule by:"
|
|
echo "1) rule number (use 'ufw status numbered' to see numbers)"
|
|
echo "2) rule text (eg: 'allow 22/tcp')"
|
|
read -rp "Choice: " dch
|
|
|
|
case "$dch" in
|
|
1)
|
|
ufw status numbered
|
|
read -rp "Number to delete: " num
|
|
run_cmd ufw delete "$num"
|
|
;;
|
|
2)
|
|
read -rp "Exact rule text to delete (eg: deny 80/tcp): " ruletext
|
|
run_cmd ufw delete $ruletext
|
|
;;
|
|
*) echo "Unknown choice." ;;
|
|
esac
|
|
}
|
|
|
|
show_status() {
|
|
echo
|
|
echo "1) Simple status"
|
|
echo "2) Verbose status"
|
|
echo "3) Numbered status (useful for delete)"
|
|
read -rp "Choice: " sc
|
|
case "$sc" in
|
|
1) run_cmd ufw status ;;
|
|
2) run_cmd ufw status verbose ;;
|
|
3) run_cmd ufw status numbered ;;
|
|
*) echo "Unknown choice." ;;
|
|
esac
|
|
}
|
|
|
|
while true; do
|
|
cat <<'MENU'
|
|
|
|
==============================
|
|
UFW POWER — human friendly
|
|
==============================
|
|
1) Add rule (port/service/ip/directional)
|
|
2) Delete rule (by number or text)
|
|
3) Show status (simple / verbose / numbered)
|
|
4) Enable UFW
|
|
5) Disable UFW
|
|
6) Reset UFW (delete all rules)
|
|
7) Set default policy (incoming/outgoing)
|
|
8) Show executed commands history (so far)
|
|
0) Exit
|
|
------------------------------
|
|
MENU
|
|
read -rp "Choose: " opt
|
|
|
|
case "$opt" in
|
|
1) add_rule ;;
|
|
2) delete_rule ;;
|
|
3) show_status ;;
|
|
4) run_cmd ufw enable ;;
|
|
5) run_cmd ufw disable ;;
|
|
6)
|
|
echo "WARNING: ufw reset will disable and remove all rules."
|
|
read -rp "Type 'RESET' to confirm: " c
|
|
[[ "$c" == "RESET" ]] && run_cmd ufw reset || echo "Reset aborted."
|
|
;;
|
|
7)
|
|
read -rp "Default incoming policy (allow/deny/reject) [deny]: " defin
|
|
defin=${defin:-deny}
|
|
read -rp "Default outgoing policy (allow/deny/reject) [allow]: " defout
|
|
defout=${defout:-allow}
|
|
run_cmd ufw default "$defin" incoming
|
|
run_cmd ufw default "$defout" outgoing
|
|
;;
|
|
8)
|
|
echo
|
|
echo "Executed commands so far:"
|
|
echo
|
|
if [[ "${#HISTORY[@]}" -eq 0 ]]; then
|
|
echo "(none yet)"
|
|
else
|
|
for c in "${HISTORY[@]}"; do echo " - $c"; done
|
|
fi
|
|
;;
|
|
0)
|
|
echo
|
|
echo "Final executed commands summary:"
|
|
if [[ "${#HISTORY[@]}" -eq 0 ]]; then
|
|
echo "(no commands executed)"
|
|
else
|
|
for c in "${HISTORY[@]}"; do echo " - $c"; done
|
|
fi
|
|
echo "Goodbye — firewall remains watchful."
|
|
exit 0
|
|
;;
|
|
*) echo "Unknown option." ;;
|
|
esac
|
|
|
|
echo
|
|
read -rp "Press Enter to continue..."
|
|
clear
|
|
done
|