Files
IPTables-Management/lib/common.sh

303 lines
5.9 KiB
Bash

#!/usr/bin/env bash
if [[ -n ${IPF_COMMON_SH_LOADED:-} ]]; then
return 0
fi
IPF_COMMON_SH_LOADED=1
: "${IPF_COLOR_ENABLED:=1}"
: "${IPF_FORCE_PLAIN_UI:=0}"
: "${IPF_APP_NAME:=IPTables 端口转发管理工具}"
if [[ ! -t 1 ]]; then
IPF_COLOR_ENABLED=0
fi
if [[ ${IPF_COLOR_ENABLED} == 1 ]]; then
CLR_RED=$'\033[31m'
CLR_GREEN=$'\033[32m'
CLR_YELLOW=$'\033[33m'
CLR_BLUE=$'\033[34m'
CLR_CYAN=$'\033[36m'
CLR_BOLD=$'\033[1m'
CLR_DIM=$'\033[2m'
CLR_RESET=$'\033[0m'
else
CLR_RED=''
CLR_GREEN=''
CLR_YELLOW=''
CLR_BLUE=''
CLR_CYAN=''
CLR_BOLD=''
CLR_DIM=''
CLR_RESET=''
fi
command_is_available() {
local cmd=${1:-}
if [[ -z ${cmd} ]]; then
return 1
fi
if [[ ${cmd} == */* ]]; then
[[ -x ${cmd} ]]
return
fi
command -v "${cmd}" >/dev/null 2>&1
}
ts_now() {
date '+%H:%M:%S'
}
_log_with_color() {
local color=$1
local level=$2
shift 2
printf '%s[%s] [%s]%s %s\n' "${color}" "$(ts_now)" "${level}" "${CLR_RESET}" "$*" >&2
}
log_info() { _log_with_color "${CLR_BLUE}" INFO "$@"; }
log_warn() { _log_with_color "${CLR_YELLOW}" WARN "$@"; }
log_err() { _log_with_color "${CLR_RED}" ERROR "$@"; }
log_ok() { _log_with_color "${CLR_GREEN}" OK "$@"; }
display_width() {
local input=${1-}
awk -v str="${input}" 'BEGIN {
n = split(str, chars, "")
width = 0
for (i = 1; i <= n; i++) {
if (chars[i] ~ /[ -~]/) {
width += 1
} else {
width += 2
}
}
print width
}'
}
repeat_char() {
local char=$1
local count=$2
local out=''
while (( count > 0 )); do
out+="${char}"
((count--))
done
printf '%s' "${out}"
}
term_width() {
if [[ -n ${COLUMNS:-} ]]; then
printf '%s\n' "${COLUMNS}"
return
fi
if command_is_available tput; then
tput cols 2>/dev/null && return
fi
printf '80\n'
}
use_box_ui() {
[[ ${IPF_FORCE_PLAIN_UI} != 1 ]] || return 1
[[ $(term_width) -ge 60 ]]
}
pad_right() {
local text=${1-}
local width=$2
local actual padding
actual=$(display_width "${text}")
if (( actual >= width )); then
printf '%s' "${text}"
return
fi
padding=$((width - actual))
printf '%s%*s' "${text}" "${padding}" ''
}
box_top() {
local width=${1:-60}
printf '╔%s╗\n' "$(repeat_char '═' "$((width - 2))")"
}
box_bottom() {
local width=${1:-60}
printf '╚%s╝\n' "$(repeat_char '═' "$((width - 2))")"
}
box_separator() {
local width=${1:-60}
printf '╠%s╣\n' "$(repeat_char '═' "$((width - 2))")"
}
box_line() {
local width=${1:-60}
local text=${2-}
local inner=$((width - 4))
printf '║ %s ║\n' "$(pad_right "${text}" "${inner}")"
}
print_section() {
local title=${1-}
if use_box_ui; then
local width
width=$(term_width)
if (( width > 78 )); then
width=78
fi
box_top "${width}"
box_line "${width}" "${title}"
box_bottom "${width}"
else
printf '== %s ==\n' "${title}"
fi
}
read_test_input() {
local reply=''
if [[ -n ${IPF_TEST_INPUTS-} ]]; then
if [[ ${IPF_TEST_INPUTS} == *$'\n'* ]]; then
reply=${IPF_TEST_INPUTS%%$'\n'*}
IPF_TEST_INPUTS=${IPF_TEST_INPUTS#*$'\n'}
else
reply=${IPF_TEST_INPUTS}
IPF_TEST_INPUTS=''
fi
fi
printf '%s' "${reply}"
}
prompt_input() {
local prompt=$1
local default_value=${2-}
local reply=''
if [[ -n ${IPF_TEST_INPUTS-} ]]; then
reply=$(read_test_input)
if [[ -z ${reply} && -n ${default_value} ]]; then
reply=${default_value}
fi
printf '%s\n' "${reply}" >&2
printf '%s\n' "${reply}"
return 0
fi
if [[ -n ${default_value} ]]; then
read -r -p "${prompt} [${default_value}]: " reply
reply=${reply:-${default_value}}
else
read -r -p "${prompt}: " reply
fi
printf '%s\n' "${reply}"
}
prompt_confirm() {
local prompt=$1
local default_choice=${2:-n}
local answer normalized suffix
if [[ ${default_choice} == y ]]; then
suffix='[Y/n]'
else
suffix='[y/N]'
fi
answer=$(prompt_input "${prompt} ${suffix}" "")
if [[ -z ${answer} ]]; then
answer=${default_choice}
fi
normalized=${answer,,}
[[ ${normalized} == y || ${normalized} == yes ]]
}
prompt_select() {
local prompt=$1
shift
local options=("$@")
local idx answer count
count=${#options[@]}
for ((idx = 0; idx < count; idx++)); do
printf ' [%d] %s\n' "$((idx + 1))" "${options[idx]}" >&2
done
while true; do
answer=$(prompt_input "${prompt}" "")
if [[ ${answer} =~ ^[0-9]+$ ]] && (( answer >= 1 && answer <= count )); then
printf '%s\n' "$((answer - 1))"
return 0
fi
log_warn "请输入 1-${count} 之间的编号。"
done
}
pause_return() {
if [[ -n ${IPF_TEST_INPUTS-} ]]; then
return 0
fi
read -r -p '按回车继续...' _
}
validate_ipv4() {
local ip=${1-}
local IFS='.'
local -a parts=()
local part
[[ ${ip} =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1
read -r -a parts <<<"${ip}"
for part in "${parts[@]}"; do
[[ ${part} =~ ^[0-9]+$ ]] || return 1
(( part >= 0 && part <= 255 )) || return 1
done
}
validate_ipv6() {
local ip=${1-}
local base scope
[[ -n ${ip} ]] || return 1
[[ ${ip} != *.* ]] || return 1
[[ ${ip} == *:* ]] || return 1
[[ ${ip} != *:::* ]] || return 1
scope=''
base=${ip}
if [[ ${ip} == *%* ]]; then
scope=${ip#*%}
base=${ip%%\%*}
[[ -n ${scope} ]] || return 1
[[ ${scope} =~ ^[A-Za-z0-9_.:-]+$ ]] || return 1
fi
[[ ${base} =~ ^[0-9A-Fa-f:]+$ ]] || return 1
[[ ${base} =~ :: ]] || [[ ${base} =~ ^([0-9A-Fa-f]{1,4}:){1,7}[0-9A-Fa-f]{1,4}$ ]] || return 1
}
validate_port() {
local port=${1-}
[[ ${port} =~ ^[0-9]+$ ]] || return 1
(( port >= 1 && port <= 65535 ))
}
validate_proto() {
case ${1-} in
tcp|udp|both) return 0 ;;
*) return 1 ;;
esac
}
validate_ipver() {
case ${1-} in
4|6|both) return 0 ;;
*) return 1 ;;
esac
}
require_root() {
if (( EUID != 0 )); then
log_err '请使用 root 或 sudo 运行此脚本。'
return 2
fi
}