|
1 | | -#!/bin/bash |
2 | | - |
3 | | -# Скрипт для добавления доменов и IP в Postfix и Postgrey whitelist |
4 | | -# Использование: ./add_whitelists.sh whitelist.txt |
5 | | - |
6 | | -# Цвета |
7 | | -RED="\e[31m" |
8 | | -GREEN="\e[32m" |
9 | | -YELLOW="\e[33m" |
10 | | -CYAN="\e[36m" |
11 | | -RESET="\e[0m" |
12 | | - |
13 | | -# Проверка аргумента |
14 | | -if [ -z "$1" ]; then |
15 | | - echo -e "❌ ${RED}Укажите файл с доменами и IP. Пример: ./add_whitelists.sh whitelist.txt${RESET}" |
16 | | - exit 1 |
| 1 | +#!/usr/bin/env bash |
| 2 | +# add_whitelists.sh — add one or many domains/IPs to Postfix + Postgrey |
| 3 | +# Usage: |
| 4 | +# ./add_whitelists.sh example.com |
| 5 | +# ./add_whitelists.sh -f whitelists.txt |
| 6 | +# ./add_whitelists.sh -n -f whitelists.txt # dry-run |
| 7 | + |
| 8 | +set -Eeuo pipefail |
| 9 | + |
| 10 | +POSTFIX_FILE="/etc/postfix/client_whitelist" |
| 11 | +POSTGREY_FILE="/etc/postgrey/whitelist_clients.local" |
| 12 | +BACKUP_DATE="$(date +%F_%H%M%S)" |
| 13 | + |
| 14 | +usage() { |
| 15 | + cat <<'EOF' |
| 16 | +Usage: |
| 17 | + add_whitelists.sh [-n] <domain-or-ip> |
| 18 | + add_whitelists.sh [-n] -f <file_with_entries> |
| 19 | +
|
| 20 | +Options: |
| 21 | + -f FILE File with entries (one per line; empty lines and #comments ignored) |
| 22 | + -n Dry-run (no changes applied) |
| 23 | + -h Show this help |
| 24 | +EOF |
| 25 | + exit 1 |
| 26 | +} |
| 27 | + |
| 28 | +# --- light coloring (TTY only) --- |
| 29 | +if [ -t 1 ]; then |
| 30 | + C_GREEN=$(tput setaf 2 || true); C_CYAN=$(tput setaf 6 || true) |
| 31 | + C_YELL=$(tput setaf 3 || true); C_RED=$(tput setaf 1 || true) |
| 32 | + C_BOLD=$(tput bold || true); C_RESET=$(tput sgr0 || true) |
| 33 | +else |
| 34 | + C_GREEN=""; C_CYAN=""; C_YELL=""; C_RED=""; C_BOLD=""; C_RESET="" |
17 | 35 | fi |
18 | 36 |
|
19 | | -LIST_FILE="$1" |
20 | | -DRY_RUN=0 |
21 | | - |
22 | | -echo -e "🛠 ${CYAN}Dry-run:${RESET} $DRY_RUN" |
23 | | - |
24 | | -# Пути к файлам |
25 | | -PF_FILE="/etc/postfix/client_whitelist" |
26 | | -PG_FILE="/etc/postgrey/whitelist_clients.local" |
27 | | - |
28 | | -# Создаём файлы, если нет |
29 | | -echo -e "📄 ${YELLOW}Creating file:${RESET} $PF_FILE" |
30 | | -touch "$PF_FILE" |
31 | | - |
32 | | -echo -e "📄 ${YELLOW}Creating file:${RESET} $PG_FILE" |
33 | | -touch "$PG_FILE" |
34 | | - |
35 | | -# Резервные копии |
36 | | -PF_BACKUP="${PF_FILE}.bak_$(date +%F_%H%M%S)" |
37 | | -PG_BACKUP="${PG_FILE}.bak_$(date +%F_%H%M%S)" |
38 | | - |
39 | | -cp "$PF_FILE" "$PF_BACKUP" |
40 | | -echo -e "📦 ${CYAN}Backup:${RESET} $PF_BACKUP" |
41 | | - |
42 | | -cp "$PG_FILE" "$PG_BACKUP" |
43 | | -echo -e "📦 ${CYAN}Backup:${RESET} $PG_BACKUP" |
44 | | - |
45 | | -# Добавляем в Postfix |
46 | | -grep -vE '^\s*#|^\s*$' "$LIST_FILE" | while read -r entry; do |
47 | | - echo "$entry OK" >> "$PF_FILE" |
| 37 | +msg() { printf '%b\n' "$*"; } |
| 38 | +die() { printf '%sERROR:%s %s\n' "$C_RED" "$C_RESET" "$*" >&2; exit 1; } |
| 39 | + |
| 40 | +DRY=0 |
| 41 | +LIST_FILE="" |
| 42 | +while getopts ":f:nh" opt; do |
| 43 | + case "$opt" in |
| 44 | + f) LIST_FILE="$OPTARG" ;; |
| 45 | + n) DRY=1 ;; |
| 46 | + h) usage ;; |
| 47 | + *) usage ;; |
| 48 | + esac |
48 | 49 | done |
49 | | -echo -e "➕ ${GREEN}Adding to Postfix:${RESET} $LIST_FILE OK" |
50 | | - |
51 | | -# Добавляем в Postgrey (только домены, без IP) |
52 | | -grep -vE '^\s*#|^\s*$' "$LIST_FILE" | grep -vE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' >> "$PG_FILE" |
53 | | -echo -e "➕ ${GREEN}Adding to Postgrey:${RESET} $LIST_FILE" |
54 | | - |
55 | | -# Перегенерация и рестарт сервисов |
56 | | -postmap "$PF_FILE" |
57 | | -echo -e "🔄 ${YELLOW}Restarting Postfix${RESET}" |
58 | | -systemctl restart postfix |
59 | | - |
60 | | -echo -e "🔄 ${YELLOW}Restarting Postgrey${RESET}" |
61 | | -systemctl restart postgrey |
62 | | - |
63 | | -# Итог |
64 | | -POSTFIX_COUNT=$(grep -vE '^\s*#|^\s*$' "$LIST_FILE" | wc -l) |
65 | | -POSTGREY_COUNT=$(grep -vE '^\s*#|^\s*$' "$LIST_FILE" | grep -vE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | wc -l) |
66 | | - |
67 | | -echo -e "✅ ${GREEN}Done.${RESET} Changes: Postfix=${CYAN}$POSTFIX_COUNT${RESET}, Postgrey=${CYAN}$POSTGREY_COUNT${RESET}, Errors=${CYAN}0${RESET}" |
| 50 | +shift $((OPTIND - 1)) |
| 51 | +SINGLE_TARGET="${1:-}" |
| 52 | + |
| 53 | +[ -z "$SINGLE_TARGET" ] && [ -z "$LIST_FILE" ] && usage |
| 54 | + |
| 55 | +require_root() { |
| 56 | + if [ "${EUID:-$(id -u)}" -ne 0 ]; then |
| 57 | + die "Run as root or with sudo." |
| 58 | + fi |
| 59 | +} |
| 60 | + |
| 61 | +ensure_file() { |
| 62 | + # create parent dir and file if missing |
| 63 | + local path="$1" dir |
| 64 | + dir="$(dirname "$path")" |
| 65 | + if [ ! -d "$dir" ]; then |
| 66 | + msg "📁 ${C_YELL}Creating dir:${C_RESET} $dir" |
| 67 | + [ "$DRY" -eq 0 ] && mkdir -p "$dir" |
| 68 | + fi |
| 69 | + if [ ! -f "$path" ]; then |
| 70 | + msg "📝 ${C_YELL}Creating file:${C_RESET} $path" |
| 71 | + [ "$DRY" -eq 0 ] && touch "$path" && chmod 644 "$path" |
| 72 | + fi |
| 73 | +} |
| 74 | + |
| 75 | +backup_if_exists() { |
| 76 | + local path="$1" |
| 77 | + if [ -f "$path" ]; then |
| 78 | + msg "🗂 ${C_CYAN}Backup:${C_RESET} ${path}.bak_${BACKUP_DATE}" |
| 79 | + [ "$DRY" -eq 0 ] && cp -a "$path" "${path}.bak_${BACKUP_DATE}" |
| 80 | + fi |
| 81 | +} |
| 82 | + |
| 83 | +is_domain() { [[ "$1" =~ ^([A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,}$ ]]; } |
| 84 | +is_ipv4() { [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; } |
| 85 | +is_cidr() { [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$ ]]; } |
| 86 | + |
| 87 | +already_in_file() { |
| 88 | + # match full token at start or before whitespace |
| 89 | + local needle="$1" file="$2" |
| 90 | + grep -qE -- "^${needle//./\\.}([[:space:]]|$)" "$file" |
| 91 | +} |
| 92 | + |
| 93 | +add_postfix() { |
| 94 | + local v="$1" |
| 95 | + if already_in_file "$v" "$POSTFIX_FILE"; then |
| 96 | + return 1 |
| 97 | + fi |
| 98 | + [ "$DRY" -eq 0 ] && printf '%s OK\n' "$v" >> "$POSTFIX_FILE" |
| 99 | + return 0 |
| 100 | +} |
| 101 | + |
| 102 | +add_postgrey() { |
| 103 | + local v="$1" |
| 104 | + if already_in_file "$v" "$POSTGREY_FILE"; then |
| 105 | + return 1 |
| 106 | + fi |
| 107 | + [ "$DRY" -eq 0 ] && printf '%s\n' "$v" >> "$POSTGREY_FILE" |
| 108 | + return 0 |
| 109 | +} |
| 110 | + |
| 111 | +# collectors |
| 112 | +ADDED_ALL=() |
| 113 | +ADDED_PF=0 |
| 114 | +ADDED_PG=0 |
| 115 | +ERRORS=0 |
| 116 | + |
| 117 | +process_entry() { |
| 118 | + local raw="$1" entry |
| 119 | + # trim + lower |
| 120 | + entry="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]' | xargs)" |
| 121 | + [ -z "$entry" ] && return 0 |
| 122 | + [[ "$entry" =~ ^# ]] && return 0 |
| 123 | + |
| 124 | + if is_cidr "$entry"; then |
| 125 | + msg "⚠️ ${C_YELL}CIDR not supported in hash map:${C_RESET} $entry" |
| 126 | + return 0 |
| 127 | + elif is_ipv4 "$entry"; then |
| 128 | + if add_postfix "$entry"; then |
| 129 | + ADDED_PF=$((ADDED_PF+1)); ADDED_ALL+=( "$entry" ) |
| 130 | + fi |
| 131 | + elif is_domain "$entry"; then |
| 132 | + local touched=0 |
| 133 | + if add_postfix "$entry"; then ADDED_PF=$((ADDED_PF+1)); touched=1; fi |
| 134 | + if add_postgrey "$entry"; then ADDED_PG=$((ADDED_PG+1)); touched=1; fi |
| 135 | + [ "$touched" -eq 1 ] && ADDED_ALL+=( "$entry" ) |
| 136 | + else |
| 137 | + msg "❌ ${C_RED}Invalid entry:${C_RESET} $entry" |
| 138 | + ERRORS=$((ERRORS+1)) |
| 139 | + return 1 |
| 140 | + fi |
| 141 | +} |
| 142 | + |
| 143 | +# ------------ main ------------ |
| 144 | +require_root |
| 145 | +msg "🔧 Dry-run: $DRY" |
| 146 | + |
| 147 | +ensure_file "$POSTFIX_FILE" |
| 148 | +ensure_file "$POSTGREY_FILE" |
| 149 | +backup_if_exists "$POSTFIX_FILE" |
| 150 | +backup_if_exists "$POSTGREY_FILE" |
| 151 | + |
| 152 | +if [ -n "$LIST_FILE" ]; then |
| 153 | + [ -f "$LIST_FILE" ] || die "File not found: $LIST_FILE" |
| 154 | + while IFS= read -r line || [ -n "$line" ]; do |
| 155 | + process_entry "$line" || true |
| 156 | + done < "$LIST_FILE" |
| 157 | +else |
| 158 | + process_entry "$SINGLE_TARGET" || true |
| 159 | +fi |
68 | 160 |
|
69 | | -echo -e "\n📊 ${YELLOW}Всего добавлено в белый список ($POSTFIX_COUNT записей):${RESET}" |
| 161 | +if [ "$DRY" -eq 0 ]; then |
| 162 | + if [ "$ADDED_PF" -gt 0 ]; then |
| 163 | + msg "🧰 postmap $POSTFIX_FILE" |
| 164 | + postmap "$POSTFIX_FILE" |
| 165 | + msg "🔄 Restarting Postfix"; systemctl restart postfix |
| 166 | + fi |
| 167 | + if [ "$ADDED_PG" -gt 0 ]; then |
| 168 | + msg "🔄 Restarting Postgrey"; systemctl restart postgrey || true |
| 169 | + fi |
| 170 | + msg "✅ ${C_GREEN}Done.${C_RESET} Changes: Postfix=${C_CYAN}${ADDED_PF}${C_RESET}, Postgrey=${C_CYAN}${ADDED_PG}${C_RESET}, Errors=${C_CYAN}${ERRORS}${C_RESET}" |
| 171 | +else |
| 172 | + msg "🔎 Dry-run complete. Would change: Postfix=${ADDED_PF}, Postgrey=${ADDED_PG}, Errors=${ERRORS}" |
| 173 | +fi |
70 | 174 |
|
71 | | -# Красивый цветной вывод — IP одним цветом, домены другим |
72 | | -grep -vE '^\s*#|^\s*$' "$LIST_FILE" | while read -r entry; do |
73 | | - if [[ "$entry" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then |
74 | | - echo -e " 🌐 ${CYAN}$entry${RESET}" # IP — голубой |
| 175 | +# Summary list of actually added items (deduplicated by logic above) |
| 176 | +if [ "${#ADDED_ALL[@]}" -gt 0 ]; then |
| 177 | + msg "" |
| 178 | + msg "📊 ${C_BOLD}Added to whitelist (${#ADDED_ALL[@]} items):${C_RESET}" |
| 179 | + for item in "${ADDED_ALL[@]}"; do |
| 180 | + if is_ipv4 "$item"; then |
| 181 | + printf ' 🌐 %s%s%s\n' "$C_CYAN" "$item" "$C_RESET" |
75 | 182 | else |
76 | | - echo -e " 🏷 ${GREEN}$entry${RESET}" # Домен — зелёный |
| 183 | + printf ' 🏷 %s%s%s\n' "$C_GREEN" "$item" "$C_RESET" |
77 | 184 | fi |
78 | | -done |
| 185 | + done |
| 186 | +else |
| 187 | + msg "ℹ️ No new entries were added." |
| 188 | +fi |
0 commit comments