Skip to content

Instantly share code, notes, and snippets.

@iykex
Created September 2, 2025 18:14
Show Gist options
  • Select an option

  • Save iykex/e002c7822f378f58fff00d3070068172 to your computer and use it in GitHub Desktop.

Select an option

Save iykex/e002c7822f378f58fff00d3070068172 to your computer and use it in GitHub Desktop.
This script helps you manage Exim4 mail server configuration.
#!/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