241 lines
5.1 KiB
Bash
Executable File
241 lines
5.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
|
||
# Общие функции для скриптов проекта WireGuard.
|
||
|
||
if [[ -t 1 ]]; then
|
||
C_RESET='\033[0m'
|
||
C_RED='\033[0;31m'
|
||
C_GREEN='\033[0;32m'
|
||
C_YELLOW='\033[1;33m'
|
||
C_BLUE='\033[0;34m'
|
||
else
|
||
C_RESET=''
|
||
C_RED=''
|
||
C_GREEN=''
|
||
C_YELLOW=''
|
||
C_BLUE=''
|
||
fi
|
||
|
||
LOG_FILE="${LOG_FILE:-}"
|
||
|
||
timestamp() {
|
||
date '+%Y-%m-%d %H:%M:%S'
|
||
}
|
||
|
||
_write_log() {
|
||
local level="$1"
|
||
local msg="$2"
|
||
local line="[$(timestamp)] [$level] $msg"
|
||
if [[ -n "${LOG_FILE}" ]]; then
|
||
mkdir -p "$(dirname "$LOG_FILE")"
|
||
printf '%s\n' "$line" >> "$LOG_FILE"
|
||
fi
|
||
}
|
||
|
||
log_info() {
|
||
local msg="$*"
|
||
printf '%b[INFO]%b %s\n' "$C_BLUE" "$C_RESET" "$msg"
|
||
_write_log "INFO" "$msg"
|
||
}
|
||
|
||
log_warn() {
|
||
local msg="$*"
|
||
printf '%b[WARN]%b %s\n' "$C_YELLOW" "$C_RESET" "$msg"
|
||
_write_log "WARN" "$msg"
|
||
}
|
||
|
||
log_error() {
|
||
local msg="$*"
|
||
printf '%b[ERR ]%b %s\n' "$C_RED" "$C_RESET" "$msg" >&2
|
||
_write_log "ERROR" "$msg"
|
||
}
|
||
|
||
log_success() {
|
||
local msg="$*"
|
||
printf '%b[ OK ]%b %s\n' "$C_GREEN" "$C_RESET" "$msg"
|
||
_write_log "SUCCESS" "$msg"
|
||
}
|
||
|
||
die() {
|
||
log_error "$*"
|
||
exit 1
|
||
}
|
||
|
||
require_root() {
|
||
if [[ "${EUID}" -ne 0 ]]; then
|
||
die "Скрипт должен выполняться от root."
|
||
fi
|
||
}
|
||
|
||
require_cmd() {
|
||
local cmd="$1"
|
||
command -v "$cmd" >/dev/null 2>&1 || die "Не найдена команда: $cmd"
|
||
}
|
||
|
||
check_os_supported() {
|
||
if [[ ! -r /etc/os-release ]]; then
|
||
die "Не найден файл /etc/os-release, определить ОС не удалось."
|
||
fi
|
||
# shellcheck disable=SC1091
|
||
source /etc/os-release
|
||
local id="${ID:-}"
|
||
local like="${ID_LIKE:-}"
|
||
if [[ "$id" != "debian" && "$id" != "ubuntu" && "$like" != *"debian"* ]]; then
|
||
die "Поддерживаются только Debian/Ubuntu. Обнаружено: ${PRETTY_NAME:-unknown}"
|
||
fi
|
||
}
|
||
|
||
apt_install_if_missing() {
|
||
local pkgs=()
|
||
local pkg
|
||
for pkg in "$@"; do
|
||
if ! dpkg -s "$pkg" >/dev/null 2>&1; then
|
||
pkgs+=("$pkg")
|
||
fi
|
||
done
|
||
|
||
if ((${#pkgs[@]} == 0)); then
|
||
log_info "Все необходимые пакеты уже установлены."
|
||
return
|
||
fi
|
||
|
||
log_info "Устанавливаю пакеты: ${pkgs[*]}"
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
apt-get update -y
|
||
apt-get install -y "${pkgs[@]}"
|
||
}
|
||
|
||
backup_file() {
|
||
local file="$1"
|
||
if [[ -f "$file" ]]; then
|
||
local backup="${file}.bak.$(date +%Y%m%d_%H%M%S)"
|
||
cp -a "$file" "$backup"
|
||
log_info "Создана резервная копия: $backup"
|
||
fi
|
||
}
|
||
|
||
ensure_line_in_file() {
|
||
local line="$1"
|
||
local file="$2"
|
||
touch "$file"
|
||
if ! grep -Fxq "$line" "$file"; then
|
||
printf '%s\n' "$line" >> "$file"
|
||
fi
|
||
}
|
||
|
||
set_kv_in_file() {
|
||
local key="$1"
|
||
local value="$2"
|
||
local file="$3"
|
||
touch "$file"
|
||
if grep -Eq "^${key}=" "$file"; then
|
||
sed -i "s|^${key}=.*|${key}=${value}|" "$file"
|
||
else
|
||
printf '%s=%s\n' "$key" "$value" >> "$file"
|
||
fi
|
||
}
|
||
|
||
is_valid_port() {
|
||
local p="$1"
|
||
[[ "$p" =~ ^[0-9]+$ ]] || return 1
|
||
((p >= 1 && p <= 65535))
|
||
}
|
||
|
||
is_valid_cidr_list() {
|
||
local input="$1"
|
||
local cidr
|
||
IFS=',' read -r -a _cidrs <<< "$input"
|
||
for cidr in "${_cidrs[@]}"; do
|
||
cidr="$(echo "$cidr" | xargs)"
|
||
[[ -n "$cidr" ]] || return 1
|
||
if ! [[ "$cidr" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[12][0-9]|3[0-2])$|^::/0$ ]]; then
|
||
return 1
|
||
fi
|
||
done
|
||
}
|
||
|
||
ask_with_default() {
|
||
local prompt="$1"
|
||
local default="$2"
|
||
local var_name="$3"
|
||
local answer
|
||
|
||
read -r -p "$prompt [$default]: " answer
|
||
answer="${answer:-$default}"
|
||
printf -v "$var_name" '%s' "$answer"
|
||
}
|
||
|
||
ask_secret() {
|
||
local prompt="$1"
|
||
local var_name="$2"
|
||
local answer
|
||
read -r -s -p "$prompt: " answer
|
||
echo
|
||
printf -v "$var_name" '%s' "$answer"
|
||
}
|
||
|
||
confirm() {
|
||
local prompt="$1"
|
||
local ans
|
||
read -r -p "$prompt [y/N]: " ans
|
||
[[ "$ans" =~ ^([yY][eE][sS]|[yY])$ ]]
|
||
}
|
||
|
||
detect_public_ip() {
|
||
local ip
|
||
for url in "https://api.ipify.org" "https://ifconfig.me/ip" "https://icanhazip.com"; do
|
||
if ip="$(curl -4fsS --max-time 5 "$url" 2>/dev/null | tr -d '[:space:]')"; then
|
||
if [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
||
echo "$ip"
|
||
return 0
|
||
fi
|
||
fi
|
||
done
|
||
return 1
|
||
}
|
||
|
||
detect_default_iface() {
|
||
ip route show default 2>/dev/null | awk '{print $5; exit}'
|
||
}
|
||
|
||
detect_default_gateway() {
|
||
ip route show default 2>/dev/null | awk '{print $3; exit}'
|
||
}
|
||
|
||
safe_chmod_600() {
|
||
local file="$1"
|
||
[[ -f "$file" ]] || return 0
|
||
chmod 600 "$file"
|
||
}
|
||
|
||
safe_chmod_700() {
|
||
local dir="$1"
|
||
[[ -d "$dir" ]] || return 0
|
||
chmod 700 "$dir"
|
||
}
|
||
|
||
systemd_enable_now() {
|
||
local unit="$1"
|
||
systemctl daemon-reload
|
||
systemctl enable --now "$unit"
|
||
}
|
||
|
||
sanitize_name() {
|
||
local input="$1"
|
||
echo "$input" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9_.-' | sed 's/^[-.]*//;s/[-.]*$//'
|
||
}
|
||
|
||
random_alnum() {
|
||
local len="${1:-20}"
|
||
local out=""
|
||
while ((${#out} < len)); do
|
||
# openssl выдает конечный блок данных, что безопасно для pipefail
|
||
# и не приводит к SIGPIPE как в схеме с head/tr от /dev/urandom.
|
||
out+="$(
|
||
openssl rand -base64 48 2>/dev/null \
|
||
| tr -dc 'A-Za-z0-9'
|
||
)"
|
||
done
|
||
printf '%s' "${out:0:len}"
|
||
}
|