22#
33# Set up UFW to only allow HTTP and HTTPS traffic from Cloudflare's IP ranges.
44#
5- # Version: v1.0.1
5+ # Version: v1.0.3
66# License: MIT License
77# Copyright (c) 2024-2026 Hunter T. (StrangeRanger)
88#
99# ###########################################################################################
10+ set -euo pipefail
1011# ###[ Global Variables ]####################################################################
1112
1213
@@ -27,29 +28,29 @@ readonly C_RED=$'\033[1;31m'
2728readonly C_NC=$' \033 [0m'
2829
2930readonly C_ERROR=" ${C_RED} ERROR:${C_NC} "
30- readonly C_SUCC=" ${C_GREEN} ==>${C_NC} "
3131readonly C_WARN=" ${C_YELLOW} ==>${C_NC} "
32+ readonly C_SUCC=" ${C_GREEN} ==>${C_NC} "
3233readonly C_INFO=" ${C_BLUE} ==>${C_NC} "
3334readonly C_NOTE=" ${C_CYAN} ==>${C_NC} "
3435
3536current_cloudflare_rule_numbers=()
3637current_cloudflare_ip_ranges=()
3738new_cloudflare_ip_ranges=()
38- stage=0
39+ modifications_in_progress=false
3940
4041
4142# ###[ Function ]############################################################################
4243
4344
4445# ###
45- # Cleanly exit the script by removing temporary files, restoring backups if needed, and
46- # displaying a message based on the exit code.
47- #
48- # PARAMETERS:
49- # - $1: exit_code (Required)
46+ # Remove temporary files, restore backups if needed, and display a message based on the exit
47+ # code.
5048clean_exit () {
5149 local exit_code=" $1 "
5250
51+ trap - ERR
52+ set +e
53+
5354 case " $exit_code " in
5455 0|1) echo " " ;;
5556 129) echo -e " \n\n${C_WARN} Hangup signal detected (SIGHUP)" ;;
@@ -58,21 +59,22 @@ clean_exit() {
5859 * ) echo -e " \n\n${C_WARN} Exiting with code: $exit_code " ;;
5960 esac
6061
61- case $stage in
62- 2|3|4)
63- echo " ${C_WARN} Interrupt occurred during stage '$stage '; incomplete changes"
64- echo " ${C_INFO} Temporarily disabling UFW..."
65- ufw disable
66- echo " ${C_INFO} Restoring previous UFW rules..."
67- sudo tar -C /etc -xf " $C_UFW_BACKUP_ARCHIVE "
68- echo " ${C_INFO} Re-enabling UFW..."
69- ufw enable
70- echo " ${C_INFO} Displaying current UFW status..."
71- echo " ---"
72- ufw status verbose
73- echo " ---"
74- ;;
75- esac
62+ # Check if we need to restore the original configurations.
63+ if [[ $modifications_in_progress == true ]]; then
64+ echo " ${C_INFO} Temporarily disabling UFW..."
65+ ufw disable
66+
67+ echo " ${C_INFO} Restoring previous UFW rules..."
68+ tar -C /etc -xzf " $C_UFW_BACKUP_ARCHIVE "
69+
70+ echo " ${C_INFO} Re-enabling UFW..."
71+ ufw enable
72+
73+ echo " ${C_INFO} Displaying current UFW status..."
74+ echo " ---"
75+ ufw status verbose
76+ echo " ---"
77+ fi
7678
7779 if [[ -d " $C_TMP_DIR " ]]; then
7880 echo " ${C_INFO} Cleaning up..."
@@ -83,13 +85,24 @@ clean_exit() {
8385 exit " $exit_code "
8486}
8587
88+ # shellcheck disable=SC2329,SC2317
89+ # These appear to be false positives. The function is intended to be used in the 'ERR'
90+ # trap handler, and the exit code is passed implicitly via the special variable '$?'.
91+ on_err () {
92+ local exit_code=$?
93+
94+ echo " ${C_ERROR} Command failed at line ${BASH_LINENO[0]} : ${BASH_COMMAND} " >&2
95+ clean_exit " $exit_code "
96+ }
97+
8698
8799# ###[ Trapping Logic ]######################################################################
88100
89101
90102trap ' clean_exit 129' SIGHUP
91103trap ' clean_exit 130' SIGINT
92104trap ' clean_exit 143' SIGTERM
105+ trap ' on_err' ERR
93106
94107
95108# ###[ Prepping ]############################################################################
106119
107120read -rp " ${C_NOTE} We will now configure Cloudflare UFW rules. Press [Enter] to continue."
108121
122+ echo " ${C_INFO} Checking UFW status..."
123+ if ! ufw status | grep -q ' ^Status: active$' ; then
124+ echo " ${C_ERROR} UFW is not active"
125+ clean_exit 1
126+ fi
127+
109128# ##
110129# ## [ Initial Setup ]
111130# ##
112131
113- stage=1
114-
115132echo " ${C_INFO} Retrieving current Cloudflare IP rules from UFW..."
116133while IFS= read -r line; do
117134 read -ra fields <<< " $line"
118135 current_cloudflare_ip_ranges+=(" ${fields[2]} " )
119- done < <( sudo ufw status | grep " Cloudflare IP " )
136+ done < <( ufw status | grep " $C_CLOUDFLARE_UFW_COMMENT " )
120137unset fields
121138
122139echo " ${C_INFO} Retrieving new Cloudflare IP ranges..."
@@ -127,28 +144,27 @@ mapfile -t new_cloudflare_ip_ranges < <(
127144)
128145
129146echo " ${C_INFO} Creating UFW backup archive at: $C_UFW_BACKUP_ARCHIVE "
130- tar -C /etc -cf " $C_UFW_BACKUP_ARCHIVE " ufw
147+ tar -C /etc -czf " $C_UFW_BACKUP_ARCHIVE " ufw
131148
132149# ##
133150# ## Add temporary rule to prevent traffic disruption.
134151# ##
135152
136- stage=2
153+ modifications_in_progress=true
137154
138155echo " ${C_INFO} Temporarily opening ports 80 and 443 from any IP address..."
139156if ! ufw allow from any to any port 80,443 proto tcp comment " Temporary rule" ; then
140157 echo " ${C_ERROR} Failed to add temporary rule" >&2
141158 clean_exit 1
142159fi
143160
161+ echo " ${C_NOTE} Waiting '$C_SLEEP_TIME ' second for changes to take effect..."
162+ sleep " $C_SLEEP_TIME "
163+
144164# ##
145165# ## Remove the existing Cloudflare IP ranges to allow new ones.
146166# ##
147167
148- stage=3
149- echo " ${C_NOTE} Waiting '$C_SLEEP_TIME ' second for changes to take effect..."
150- sleep " $C_SLEEP_TIME "
151-
152168if (( ${# current_cloudflare_ip_ranges[@]} != 0 )) ; then
153169 echo " ${C_INFO} Removing the existing Cloudflare IP ranges..."
154170
@@ -168,8 +184,11 @@ if (( ${#current_cloudflare_ip_ranges[@]} != 0 )); then
168184 )
169185
170186 for rule_num in " ${current_cloudflare_rule_numbers[@]} " ; do
171- yes | ufw delete " $rule_num "
187+ echo " y " | ufw delete " $rule_num "
172188 done
189+
190+ echo " ${C_NOTE} Waiting '$C_SLEEP_TIME ' second for changes to take effect..."
191+ sleep " $C_SLEEP_TIME "
173192fi
174193
175194unset current_cloudflare_rule_numbers
@@ -178,10 +197,6 @@ unset current_cloudflare_rule_numbers
178197# ## Add the new Cloudflare IP ranges.
179198# ##
180199
181- stage=4
182- echo " ${C_NOTE} Waiting '$C_SLEEP_TIME ' second for changes to take effect..."
183- sleep " $C_SLEEP_TIME "
184-
185200echo " ${C_INFO} Adding the new Cloudflare IPv4 and IPv6 ranges..."
186201for ip in " ${new_cloudflare_ip_ranges[@]} " ; do
187202 echo " ${C_INFO} Adding rule for '$ip '..."
@@ -192,21 +207,22 @@ for ip in "${new_cloudflare_ip_ranges[@]}"; do
192207 fi
193208done
194209
210+ echo " ${C_NOTE} Waiting '$C_SLEEP_TIME ' second for changes to take effect..."
211+ sleep " $C_SLEEP_TIME "
212+
195213# ##
196214# ## Perform the last modifications to UFW.
197215# ##
198216
199- stage=5
200- echo " ${C_NOTE} Waiting '$C_SLEEP_TIME ' second for changes to take effect..."
201- sleep " $C_SLEEP_TIME "
202-
203217echo " ${C_INFO} Removing temporary rules..."
204218if ! ufw delete allow from any to any port 80,443 proto tcp comment " Temporary rule" ; then
205219 echo " ${C_ERROR} Failed to remove temporary rule" >&2
206220 echo " ${C_NOTE} Please check your UFW configuration and remove it manually"
207221fi
208222
209- sleep " $C_SLEEP_TIME "
223+ modifications_in_progress=false
224+
210225echo " ${C_NOTE} Waiting '$C_SLEEP_TIME ' second for changes to take effect..."
226+ sleep " $C_SLEEP_TIME "
211227echo " ${C_SUCC} Finished setting up UFW with Cloudflare IP ranges"
212228clean_exit 0
0 commit comments