Instantly share code, notes, and snippets.
Created
September 2, 2025 18:14
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save iykex/e002c7822f378f58fff00d3070068172 to your computer and use it in GitHub Desktop.
This script helps you manage Exim4 mail server configuration.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -euo pipefail | |
| # ── Colors ────────────────────────────────────────────── | |
| readonly RED="\e[31m" | |
| readonly GREEN="\e[32m" | |
| readonly YELLOW="\e[33m" | |
| readonly BLUE="\e[34m" | |
| readonly CYAN="\e[36m" | |
| readonly MAGENTA="\e[35m" | |
| readonly BOLD="\e[1m" | |
| readonly DIM="\e[2m" | |
| readonly RESET="\e[0m" | |
| # ── Config paths ──────────────────────────────────────── | |
| readonly CONF_FILE="/etc/exim4/update-exim4.conf.conf" | |
| readonly MACRO_FILE="/etc/exim4/exim4.conf.localmacros" | |
| readonly LOG_FILE="/var/log/exim4/mainlog" | |
| readonly SCRIPT_NAME="Exim4 Manager" | |
| readonly VERSION="2.1" | |
| # ── UI Helpers ───────────────────────────────────────── | |
| print_header() { | |
| clear | |
| echo -e "${BOLD}${BLUE}╔════════════════════════════════════════════════════════╗${RESET}" | |
| echo -e "${BOLD}${BLUE}║ ${SCRIPT_NAME} v${VERSION} ║${RESET}" | |
| echo -e "${BOLD}${BLUE}╚════════════════════════════════════════════════════════╝${RESET}" | |
| echo | |
| } | |
| print_separator() { | |
| echo -e "${DIM}${BLUE}────────────────────────────────────────────────────────${RESET}" | |
| } | |
| pause_for_input() { | |
| echo -e "\n${DIM}Press Enter to continue...${RESET}" | |
| read -r | |
| } | |
| log_action() { | |
| echo "[$(date '+%F %T')] $1" >> /tmp/exim4_manager.log 2>/dev/null || true | |
| } | |
| # ── Validation ───────────────────────────────────────── | |
| validate_root() { | |
| if [[ $EUID -ne 0 ]] && ! sudo -n true 2>/dev/null; then | |
| echo -e "${RED}❌ Needs root privileges${RESET}" | |
| exit 1 | |
| fi | |
| } | |
| validate_exim() { | |
| command -v exim4 >/dev/null 2>&1 || { | |
| echo -e "${RED}❌ Exim4 not installed${RESET}" | |
| echo -e "${YELLOW}💡 Install: sudo apt install exim4${RESET}" | |
| return 1 | |
| } | |
| } | |
| validate_port() { | |
| local p="$1" | |
| [[ "$p" =~ ^[0-9]+$ ]] || { echo -e "${RED}❌ Not numeric${RESET}"; return 1; } | |
| (( p >= 1 && p <= 65535 )) || { echo -e "${RED}❌ Out of range${RESET}"; return 1; } | |
| if ss -tulpn 2>/dev/null | grep -q ":$p " && ! ss -tulpn 2>/dev/null | grep -q "exim4.*:$p "; then | |
| echo -e "${RED}❌ Port $p in use by another service${RESET}"; return 1 | |
| fi | |
| } | |
| # ── Core Features ───────────────────────────────────── | |
| get_exim_ports() { | |
| ss -tulpn 2>/dev/null | awk '/exim4/ {print $5}' | awk -F: '{print $NF}' | sort -n | uniq | |
| } | |
| check_status() { | |
| print_header | |
| echo -e "${BOLD}${CYAN}📊 Exim4 Status${RESET}" | |
| print_separator | |
| if ! validate_exim; then pause_for_input; return; fi | |
| if systemctl is-active --quiet exim4; then | |
| echo -e "${GREEN}✅ Running${RESET}" | |
| else | |
| echo -e "${RED}❌ Not running${RESET}" | |
| fi | |
| local ports | |
| ports=$(get_exim_ports) | |
| if [[ -n "$ports" ]]; then | |
| echo -e "\n${BLUE}📡 Listening on:${RESET} ${YELLOW}$ports${RESET}" | |
| else | |
| echo -e "\n${RED}❌ No listening ports${RESET}" | |
| fi | |
| log_action "Checked status" | |
| pause_for_input | |
| } | |
| check_ports() { | |
| print_header | |
| echo -e "${BOLD}${CYAN}🔌 Listening Ports${RESET}" | |
| print_separator | |
| local ports | |
| ports=$(get_exim_ports) | |
| if [[ -z "$ports" ]]; then | |
| echo -e "${RED}❌ No ports${RESET}"; pause_for_input; return | |
| fi | |
| for p in $ports; do | |
| echo -e "${BLUE} 📡 Port ${BOLD}$p${RESET}" | |
| case "$p" in | |
| 25) echo -e " ${DIM}SMTP (25)${RESET}" ;; | |
| 587) echo -e " ${DIM}Submission (587)${RESET}" ;; | |
| 465) echo -e " ${DIM}SMTPS (465)${RESET}" ;; | |
| *) echo -e " ${DIM}Custom${RESET}" ;; | |
| esac | |
| done | |
| log_action "Checked ports" | |
| pause_for_input | |
| } | |
| change_port() { | |
| print_header | |
| echo -e "${BOLD}${CYAN}🔧 Change Listening Port${RESET}" | |
| print_separator | |
| local new_port | |
| while true; do | |
| read -rp "Enter new port (or q to quit): " new_port | |
| [[ "$new_port" =~ ^[Qq]$ ]] && return | |
| if validate_port "$new_port"; then break; fi | |
| done | |
| echo -e "${YELLOW}⚠️ Restart required, confirm change to ${BOLD}$new_port${RESET}" | |
| read -rp "Continue? (y/N): " confirm | |
| [[ "$confirm" =~ ^[Yy]$ ]] || return | |
| local backup="/tmp/exim4_backup_$(date +%s).conf" | |
| [[ -f "$CONF_FILE" ]] && sudo cp "$CONF_FILE" "$backup" && echo -e "${DIM}Backup: $backup${RESET}" | |
| if grep -q "^dc_local_interfaces" "$CONF_FILE" 2>/dev/null; then | |
| sudo sed -i "s|^dc_local_interfaces=.*|dc_local_interfaces='0.0.0.0:$new_port'|" "$CONF_FILE" | |
| else | |
| echo "MAIN_LOCAL_INTERFACES = 0.0.0.0:$new_port" | sudo tee "$MACRO_FILE" >/dev/null | |
| fi | |
| sudo update-exim4.conf | |
| sudo systemctl restart exim4 && echo -e "${GREEN}✅ Restarted on port $new_port${RESET}" \ | |
| || echo -e "${RED}❌ Restart failed${RESET}" | |
| log_action "Changed port to $new_port" | |
| pause_for_input | |
| } | |
| # ── Menu ────────────────────────────────────────────── | |
| main_menu() { | |
| while true; do | |
| print_header | |
| echo -e "1) 📊 Status" | |
| echo -e "2) 🔌 Ports" | |
| echo -e "3) 🔧 Change Port" | |
| echo -e "4) 🚪 Exit" | |
| echo | |
| read -rp "Choose [1-4]: " c | |
| case "$c" in | |
| 1) check_status ;; | |
| 2) check_ports ;; | |
| 3) change_port ;; | |
| 4) clear; echo -e "${GREEN}Bye 👋${RESET}"; exit ;; | |
| *) echo -e "${RED}❌ Invalid${RESET}"; sleep 1 ;; | |
| esac | |
| done | |
| } | |
| # ── Entry ───────────────────────────────────────────── | |
| trap 'echo -e "\n${YELLOW}⚠️ Interrupted${RESET}"; exit 130' INT | |
| validate_root | |
| main_menu |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment