#!/usr/bin/env bash : <<'IRANUX_METADATA' { "standard": { "name": "iranux-script-metadata", "schema_version": "1.2" }, "script": { "id": "masterdnsvpn-server-linux-installer", "name": "MasterDnsVPN Server Linux Installer", "version": "1.0.0", "description": "Installs, updates or uninstalls the MasterDnsVPN DNS tunnel server on Linux. To free port 53 it stops and disables other DNS services, changes or stops systemd-resolved and kills any process still using port 53; it also removes port 53 redirect rules, opens port 53 in the firewall, changes kernel and file limits, generates an encryption key and creates a systemd service.", "estimated_minutes": 5, "i18n": { "fa": { "name": "نصب سرور MasterDnsVPN روی لینوکس", "description": "سرور تونل DNS به نام MasterDnsVPN را روی لینوکس نصب، به‌روزرسانی یا حذف می‌کند. برای آزاد کردن پورت 53، سرویس‌های DNS دیگر را متوقف و غیرفعال می‌کند، systemd-resolved را تغییر می‌دهد یا متوقف می‌کند و هر برنامه‌ای را که از پورت 53 استفاده می‌کند می‌بندد. قانون‌های انتقال پورت 53 را حذف و این پورت را در فایروال باز می‌کند، تنظیمات هسته و محدودیت فایل‌ها را تغییر می‌دهد، کلید رمزنگاری می‌سازد و سرویس systemd می‌سازد." } } }, "risk": { "level": "dangerous" }, "requirements": { "requires_root": true, "requires_internet": true, "supported_os": [ "ubuntu", "debian", "almalinux", "rocky", "rhel", "centos", "fedora", "amzn" ], "required_commands": [ "systemctl" ] }, "ui": { "category": { "id": "network-services", "name": "Network Services" }, "action": { "id": "dns-tunnel-servers", "name": "DNS Tunnel Servers" }, "icon": { "library": "mdi", "name": "dns" } } } IRANUX_METADATA : <<'IRANUX_PARAM' { "name": "install_action", "label": "Action", "description": "Choose whether to install or uninstall MasterDnsVPN. Install also updates an existing installation to the selected version and keeps your current settings if the new version can still use them.", "type": "enum", "required": true, "default": "install", "options": [ { "label": "Install", "value": "install" }, { "label": "Uninstall", "value": "uninstall" } ], "group": "Installation", "i18n": { "fa": { "label": "عملیات", "description": "انتخاب کنید MasterDnsVPN نصب شود یا حذف شود. اگر MasterDnsVPN از قبل نصب باشد، نصب آن را به نسخه‌ی انتخاب‌شده به‌روزرسانی می‌کند و اگر نسخه‌ی جدید بتواند از تنظیمات فعلی شما استفاده کند، این تنظیمات حفظ می‌شود." } } } IRANUX_PARAM : <<'IRANUX_PARAM' { "name": "target_version", "label": "Version", "description": "Enter the MasterDnsVPN release tag to install, for example v2026.04.12.234117-978faee. Leave empty to install the latest release. Leave it empty when you uninstall.", "type": "string", "required": false, "example": "v2026.04.12.234117-978faee", "placeholder": "v2026.04.12.234117-978faee", "validation": { "pattern": "^[A-Za-z0-9._+-]+$" }, "group": "Installation", "level": "advanced", "i18n": { "fa": { "label": "نسخه", "description": "برچسب نسخه‌ای از MasterDnsVPN را که می‌خواهید نصب شود وارد کنید، مثلاً v2026.04.12.234117-978faee. برای نصب آخرین نسخه خالی بگذارید. هنگام حذف هم این را خالی بگذارید." } } } IRANUX_PARAM : <<'IRANUX_PARAM' { "name": "user_domain", "label": "Tunnel domain", "description": "Enter the domain (or subdomain) that points to this server with an NS record, for example vpn.example.com. It is needed for a new installation. It is written to server_config.toml only when no domain has been set there yet, so leave it empty if you already set one.", "type": "domain", "required": false, "example": "vpn.example.com", "placeholder": "vpn.example.com", "group": "Tunnel", "i18n": { "fa": { "label": "دامنه‌ی تونل", "description": "دامنه (یا زیردامنه‌ای) را وارد کنید که با رکورد NS به این سرور اشاره می‌کند، مثلاً vpn.example.com. برای نصب جدید لازم است. این دامنه فقط وقتی در فایل server_config.toml نوشته می‌شود که هنوز دامنه‌ای در آن تنظیم نشده باشد؛ اگر قبلاً دامنه را تنظیم کرده‌اید، خالی بگذارید." } } } IRANUX_PARAM set -euo pipefail IFS=$'\n\t' RED='\033[1;31m' GREEN='\033[1;32m' YELLOW='\033[1;33m' BLUE='\033[1;34m' MAGENTA='\033[1;35m' CYAN='\033[1;36m' BOLD='\033[1m' NC='\033[0m' log_header() { echo -e "\n${CYAN}${BOLD}>>> $1${NC}"; } log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_success() { echo -e "${GREEN}[DONE]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; } require_cmd() { command -v "$1" >/dev/null 2>&1 || log_error "Missing command: $1"; } iranux_json_string() { local s="$1" s="${s//\\/\\\\}" s="${s//\"/\\\"}" s="${s//$'\t'/\\t}" s="${s//$'\r'/\\r}" s="${s//$'\n'/\\n}" printf '"%s"' "$s" } backup_file_once() { local f="$1" [[ -f "$f" && ! -f "${f}.bak" ]] && cp -a "$f" "${f}.bak" } extract_config_version() { local f="$1" [[ -f "$f" ]] || return 0 grep '^CONFIG_VERSION' "$f" | awk -F'=' '{print $2}' | tr -d ' "' | head -n1 } version_lt() { [[ "$1" == "$2" ]] && return 1 [[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" == "$1" ]] } detect_legacy_linux() { local id="${ID:-}" local version_major="${VERSION_ID%%.*}" case "$id" in ubuntu) [[ "${version_major:-0}" -le 20 ]] ;; debian) [[ "${version_major:-0}" -le 11 ]] ;; almalinux|rocky|rhel|centos) [[ "${version_major:-0}" -le 8 ]] ;; *) return 1 ;; esac } print_usage() { cat <<'USAGE' MasterDnsVPN Server Linux Installer Usage: bash <(curl -Ls https://raw.githubusercontent.com/masterking32/MasterDnsVPN/main/server_linux_install.sh) [OPTIONS] Options: -v, --version Install a specific MasterDnsVPN release (tag), e.g. v1.2.3. If omitted, the latest release is installed. -u, --uninstall Uninstall MasterDnsVPN: stop and remove the systemd service, drop kernel/limit tunings, and clean up binaries and config files in the install directory. -h, --help Show this help message and exit. Examples: # Install the latest release (default behavior): bash <(curl -Ls https://raw.githubusercontent.com/masterking32/MasterDnsVPN/main/server_linux_install.sh) # Install a specific release version: bash <(curl -Ls https://raw.githubusercontent.com/masterking32/MasterDnsVPN/main/server_linux_install.sh) --version v2026.04.12.234117-978faee # Uninstall MasterDnsVPN: bash <(curl -Ls https://raw.githubusercontent.com/masterking32/MasterDnsVPN/main/server_linux_install.sh) --uninstall USAGE } select_release_artifact() { local arch="$1" local version="${2:-}" local legacy=0 if detect_legacy_linux; then legacy=1 log_info "Legacy system detected (broader Linux compatibility mode)." fi local base_url if [[ -n "$version" ]]; then base_url="https://github.com/masterking32/MasterDnsVPN/releases/download/${version}" log_info "Targeting MasterDnsVPN release: ${version}" else base_url="https://github.com/masterking32/MasterDnsVPN/releases/latest/download" fi case "$arch" in aarch64|arm64) if [[ $legacy -eq 1 ]]; then PREFIX="MasterDnsVPN_Server_Linux-Legacy_ARM64" else PREFIX="MasterDnsVPN_Server_Linux_ARM64" fi ;; armv7l|armv7|armhf) PREFIX="MasterDnsVPN_Server_Linux_ARMV7" ;; x86_64|amd64) if [[ $legacy -eq 1 ]]; then PREFIX="MasterDnsVPN_Server_Linux-Legacy_AMD64" else PREFIX="MasterDnsVPN_Server_Linux_AMD64" fi ;; i386|i486|i586|i686|x86) PREFIX="MasterDnsVPN_Server_Linux_X86" ;; *) log_error "Unsupported architecture: $arch" ;; esac URL="${base_url}/${PREFIX}.zip" } ACTION="${INSTALL_ACTION:-install}" TARGET_VERSION="${TARGET_VERSION:-}" while [[ $# -gt 0 ]]; do case "$1" in -v|--version) [[ $# -ge 2 ]] || { echo "Error: $1 requires a value" >&2; print_usage; exit 2; } TARGET_VERSION="$2" shift 2 ;; --version=*) TARGET_VERSION="${1#*=}" shift ;; -u|--uninstall) ACTION="uninstall" shift ;; -h|--help) print_usage echo "IRANUX_RESULT {\"outputs\":[]}" echo "__IRANUX_REACHED_END_V1__" exit 0 ;; --) shift break ;; *) echo "Error: unknown option: $1" >&2 print_usage exit 2 ;; esac done if [[ "$ACTION" == "uninstall" && -n "$TARGET_VERSION" ]]; then echo "Error: --version cannot be combined with --uninstall" >&2 exit 2 fi if [[ -n "$TARGET_VERSION" && ! "$TARGET_VERSION" =~ ^[A-Za-z0-9._+-]+$ ]]; then echo "Error: invalid version tag: $TARGET_VERSION" >&2 exit 2 fi if [[ -n "$TARGET_VERSION" ]]; then log_info "Requested release tag: $TARGET_VERSION" fi if [[ "${EUID}" -ne 0 ]]; then log_error "Run this script as root (sudo)." fi INSTALL_DIR="$(pwd -P)" [[ -n "${PWD:-}" ]] && INSTALL_DIR="$PWD" if [[ "$INSTALL_DIR" == /dev/fd* || "$INSTALL_DIR" == /proc/*/fd* ]]; then INSTALL_DIR="$(pwd -P)" fi log_info "Installation directory: $INSTALL_DIR" cd "$INSTALL_DIR" || log_error "Cannot access install directory: $INSTALL_DIR" if [[ -f "server_config.toml" && -f "server_config.toml.backup" ]]; then log_error "Both server_config.toml and server_config.toml.backup exist. Remove one and retry." fi if [[ -f /etc/os-release ]]; then # shellcheck disable=SC1091 . /etc/os-release else log_error "OS detection failed (/etc/os-release missing)." fi echo -e "${MAGENTA}${BOLD}" echo " __ __ _ _____ _ _ _____ " echo " | \/ | | | | __ \| \ | |/ ____|" echo " | \ / | __ _ ___| |_ ___ _ __ | | | | \| | (___ " echo " | |\/| |/ _\` / __| __/ _ \ '__|| | | | . \ |\___ \ " echo " | | | | (_| \__ \ || __/ | | |__| | |\ |____) |" echo " |_| |_|\__,_|___/\__\___|_| |_____/|_| \_|_____/ " if [[ "$ACTION" == "uninstall" ]]; then echo -e " MasterDnsVPN Server Auto-Uninstaller${NC}" else echo -e " MasterDnsVPN Server Auto-Installer${NC}" fi echo -e "${CYAN}------------------------------------------------------${NC}" TMP_LOG="init_logs.tmp" DOWNLOAD_DIR="" cleanup() { rm -f "$TMP_LOG" 2>/dev/null || true if [[ -n "${DOWNLOAD_DIR:-}" && -d "${DOWNLOAD_DIR:-}" ]]; then rm -rf "$DOWNLOAD_DIR" 2>/dev/null || true fi } trap cleanup EXIT PM="" if command -v apt-get >/dev/null 2>&1; then PM="apt"; elif command -v dnf >/dev/null 2>&1; then PM="dnf"; elif command -v yum >/dev/null 2>&1; then PM="yum"; else log_error "No supported package manager found (apt/dnf/yum)."; fi log_header "Preparing Environment" log_info "Installing dependencies..." if [[ "$PM" == "apt" ]]; then apt-get update -y >/dev/null 2>&1 apt-get install -y lsof net-tools wget unzip curl ca-certificates iproute2 procps irqbalance >/dev/null 2>&1 elif [[ "$PM" == "dnf" ]]; then dnf -y install lsof net-tools wget unzip curl ca-certificates iproute procps-ng irqbalance >/dev/null 2>&1 else yum -y install lsof net-tools wget unzip curl ca-certificates iproute procps-ng irqbalance >/dev/null 2>&1 fi require_cmd ss require_cmd unzip require_cmd systemctl require_cmd sysctl log_success "System tools are ready." if systemctl list-unit-files --type=service --all 2>/dev/null | awk '{print $1}' | grep -qx 'irqbalance.service'; then log_info "Enabling irqbalance for better multi-core packet distribution..." systemctl enable --now irqbalance >/dev/null 2>&1 || log_warn "Could not enable/start irqbalance." fi check_port53() { ss -H -lun "sport = :53" 2>/dev/null | grep -q ':53' && return 0 ss -H -ltn "sport = :53" 2>/dev/null | grep -q ':53' && return 0 return 1 } show_port53_usage() { log_warn "Current listeners on port 53:" ss -lupn "sport = :53" 2>/dev/null || true ss -ltpn "sport = :53" 2>/dev/null || true lsof -nP -iUDP:53 -iTCP:53 2>/dev/null || true } get_port53_pids() { local pids_udp pids_tcp pids pids_udp="$(ss -H -lupn "sport = :53" 2>/dev/null | sed -n 's/.*pid=\([0-9]\+\).*/\1/p' | sort -u)" pids_tcp="$(ss -H -ltpn "sport = :53" 2>/dev/null | sed -n 's/.*pid=\([0-9]\+\).*/\1/p' | sort -u)" pids="$(printf '%s\n%s\n' "$pids_udp" "$pids_tcp" | sed '/^$/d' | sort -u)" if [[ -n "$pids" ]]; then echo "$pids" return 0 fi lsof -ti :53 2>/dev/null || true } stop_service_if_present() { local unit="$1" if systemctl list-unit-files --type=service --all 2>/dev/null | awk '{print $1}' | grep -qx "$unit"; then if systemctl is-active --quiet "$unit"; then log_info "Stopping conflicting service: $unit" systemctl stop "$unit" || true fi systemctl disable "$unit" >/dev/null 2>&1 || true fi } stop_socket_if_present() { local unit="$1" if systemctl list-unit-files --type=socket --all 2>/dev/null | awk '{print $1}' | grep -qx "$unit"; then if systemctl is-active --quiet "$unit"; then log_info "Stopping conflicting socket: $unit" systemctl stop "$unit" || true fi systemctl disable "$unit" >/dev/null 2>&1 || true fi } terminate_port53_pid() { local pid="$1" [[ -n "$pid" ]] || return 0 if ! kill -0 "$pid" 2>/dev/null; then return 0 fi local cmdline cmdline="$(ps -p "$pid" -o cmd= 2>/dev/null || true)" log_warn "Trying to terminate PID on port 53: $pid (${cmdline:-unknown})" kill "$pid" 2>/dev/null || true for _ in 1 2 3; do sleep 1 if ! kill -0 "$pid" 2>/dev/null; then return 0 fi done kill -9 "$pid" 2>/dev/null || true sleep 1 if kill -0 "$pid" 2>/dev/null; then log_warn "PID $pid is still alive after SIGKILL." return 1 fi return 0 } force_release_port53() { local stubborn=0 local pid while IFS= read -r pid; do [[ -z "$pid" ]] && continue terminate_port53_pid "$pid" || stubborn=1 done <<< "$(get_port53_pids)" if command -v fuser >/dev/null 2>&1 && check_port53; then log_warn "Trying fuser fallback for port 53..." fuser -k 53/udp 2>/dev/null || true fuser -k 53/tcp 2>/dev/null || true sleep 1 fi return "$stubborn" } remove_iptables_port53_redirects() { local tool="$1" command -v "$tool" >/dev/null 2>&1 || return 0 local rule delete_rule while IFS= read -r rule; do [[ -z "$rule" ]] && continue delete_rule="${rule/-A /-D }" log_warn "Removing ${tool} NAT redirect rule for port 53: $rule" # shellcheck disable=SC2086 $tool -t nat $delete_rule >/dev/null 2>&1 || true done < <("$tool" -t nat -S 2>/dev/null | grep -E '(^-A )' | grep -E -- '(-p (tcp|udp)|-p (udp|tcp)).*--dport 53([^0-9]|$)' | grep -E 'REDIRECT|DNAT' || true) } remove_nft_port53_redirects() { command -v nft >/dev/null 2>&1 || return 0 local rule while IFS= read -r rule; do [[ -z "$rule" ]] && continue log_warn "Removing nftables redirect rule for port 53: $rule" nft delete rule $rule >/dev/null 2>&1 || true done < <(nft -a list ruleset 2>/dev/null | awk ' / dport 53 / && ($0 ~ /redirect/ || $0 ~ /dnat/) { for (i = 1; i <= NF; i++) { if ($i == "table") table = $(i+1) if ($i == "chain") chain = $(i+1) if ($i == "handle") handle = $(i+1) } if (table != "" && chain != "" && handle != "") { print "ip " table " " chain " handle " handle } table = ""; chain = ""; handle = "" } ' || true) } remove_port53_forward_rules() { log_info "Checking for port 53 redirect/forward rules..." remove_iptables_port53_redirects iptables remove_iptables_port53_redirects ip6tables remove_nft_port53_redirects } do_uninstall() { log_header "Uninstalling MasterDnsVPN" if systemctl list-unit-files --all 2>/dev/null | grep -q '^masterdnsvpn\.service'; then log_info "Stopping and disabling masterdnsvpn service..." systemctl stop masterdnsvpn 2>/dev/null || true systemctl disable masterdnsvpn >/dev/null 2>&1 || true systemctl reset-failed masterdnsvpn 2>/dev/null || true else log_info "No masterdnsvpn systemd unit found." fi if [[ -f /etc/systemd/system/masterdnsvpn.service ]]; then rm -f /etc/systemd/system/masterdnsvpn.service log_success "Removed /etc/systemd/system/masterdnsvpn.service" fi systemctl daemon-reload 2>/dev/null || true local pid cmdline while IFS= read -r pid; do [[ -z "$pid" ]] && continue cmdline="$(ps -p "$pid" -o cmd= 2>/dev/null || true)" if echo "$cmdline" | grep -qiE 'masterdnsvpn'; then log_warn "Terminating stray MasterDnsVPN process (PID: $pid)..." kill "$pid" 2>/dev/null || true sleep 1 if kill -0 "$pid" 2>/dev/null; then kill -9 "$pid" 2>/dev/null || true fi fi done < <(pgrep -fi 'masterdnsvpn' 2>/dev/null || true) if [[ -f /etc/sysctl.d/99-masterdnsvpn.conf ]]; then rm -f /etc/sysctl.d/99-masterdnsvpn.conf sysctl --system >/dev/null 2>&1 || true log_success "Removed kernel tuning (/etc/sysctl.d/99-masterdnsvpn.conf)." fi if [[ -f /etc/security/limits.d/99-masterdnsvpn.conf ]]; then rm -f /etc/security/limits.d/99-masterdnsvpn.conf log_success "Removed file descriptor limits (/etc/security/limits.d/99-masterdnsvpn.conf)." fi if [[ -f /etc/systemd/resolved.conf.bak && -f /etc/systemd/resolved.conf ]]; then log_info "Restoring original /etc/systemd/resolved.conf from backup..." mv -f /etc/systemd/resolved.conf.bak /etc/systemd/resolved.conf systemctl restart systemd-resolved 2>/dev/null || true fi log_header "Cleaning Install Directory" log_info "Install directory: $INSTALL_DIR" shopt -s nullglob local removed=0 for f in \ "$INSTALL_DIR"/MasterDnsVPN_Server_Linux*_v* \ "$INSTALL_DIR"/server_config.toml \ "$INSTALL_DIR"/server_config.toml.backup \ "$INSTALL_DIR"/server_config.toml.bak \ "$INSTALL_DIR"/server_config_*.toml \ "$INSTALL_DIR"/encrypt_key.txt \ "$INSTALL_DIR"/init_logs.tmp \ "$INSTALL_DIR"/*.spec; do if [[ -e "$f" ]]; then rm -f -- "$f" log_info "Removed: $f" removed=1 fi done shopt -u nullglob if [[ $removed -eq 0 ]]; then log_warn "No MasterDnsVPN files found in $INSTALL_DIR. If you installed elsewhere, run the uninstaller from that directory." fi echo -e "\n${CYAN}======================================================${NC}" echo -e " ${GREEN}${BOLD} MASTERDNSVPN UNINSTALL COMPLETED${NC}" echo -e "${CYAN}======================================================${NC}" echo -e "${YELLOW}Note:${NC} Firewall rules for port 53 (UDP/TCP) were left in place." echo -e " Remove them manually if no longer needed." } stop_existing_masterdnsvpn_service() { local unit_present=0 if systemctl list-unit-files --all 2>/dev/null | grep -q '^masterdnsvpn\.service'; then unit_present=1 log_info "Stopping existing MasterDnsVPN service..." systemctl stop masterdnsvpn 2>/dev/null || true for _ in 1 2 3 4 5; do if ! systemctl is-active --quiet masterdnsvpn; then break fi sleep 1 done local main_pid main_pid="$(systemctl show masterdnsvpn --property MainPID --value 2>/dev/null || true)" if [[ -n "${main_pid:-}" && "$main_pid" != "0" ]] && kill -0 "$main_pid" 2>/dev/null; then log_warn "masterdnsvpn service is still active. Trying to terminate MainPID: $main_pid" terminate_port53_pid "$main_pid" || true fi systemctl stop masterdnsvpn 2>/dev/null || true systemctl reset-failed masterdnsvpn 2>/dev/null || true fi local pid cmdline killed=0 while IFS= read -r pid; do [[ -z "$pid" ]] && continue cmdline="$(ps -p "$pid" -o cmd= 2>/dev/null || true)" if echo "$cmdline" | grep -qiE 'masterdnsvpn|masterdnsvpn_server'; then if [[ $killed -eq 0 && $unit_present -eq 0 ]]; then log_info "Stopping existing MasterDnsVPN process that was started outside systemd..." fi terminate_port53_pid "$pid" || true killed=1 fi done <<< "$(get_port53_pids)" } if [[ "$ACTION" == "uninstall" ]]; then do_uninstall echo "IRANUX_RESULT {\"outputs\":[{\"key\":\"install_dir\",\"label\":\"Cleaned installation folder\",\"value\":$(iranux_json_string "$INSTALL_DIR"),\"type\":\"text\",\"i18n\":{\"fa\":{\"label\":\"پوشه‌ی نصب پاک‌شده\"}}}]}" echo "__IRANUX_REACHED_END_V1__" exit 0 fi log_header "Stopping Existing MasterDnsVPN" stop_existing_masterdnsvpn_service log_header "Managing Network Ports (Port 53)" remove_port53_forward_rules if check_port53; then log_warn "Port 53 is occupied. Trying auto-cleanup..." show_port53_usage if systemctl is-active --quiet systemd-resolved; then log_info "Configuring systemd-resolved DNSStubListener=no ..." if [[ -f /etc/systemd/resolved.conf && ! -f /etc/systemd/resolved.conf.bak ]]; then cp -a /etc/systemd/resolved.conf /etc/systemd/resolved.conf.bak fi if grep -q '^#\?DNSStubListener=' /etc/systemd/resolved.conf; then sed -i 's/^#\?DNSStubListener=.*/DNSStubListener=no/' /etc/systemd/resolved.conf || true else echo 'DNSStubListener=no' >> /etc/systemd/resolved.conf fi if ! grep -q '^DNS=' /etc/systemd/resolved.conf; then echo 'DNS=8.8.8.8' >> /etc/systemd/resolved.conf fi systemctl restart systemd-resolved || true fi stop_socket_if_present systemd-resolved.socket stop_socket_if_present dnsmasq.socket for srv in \ bind9 bind9.service named named.service named-pkcs11 named-pkcs11.service \ dnsmasq dnsmasq.service unbound unbound.service pdns pdns.service \ knot-resolver kresd kresd@1.service dnscrypt-proxy dnscrypt-proxy.service \ smartdns smartdns.service coredns coredns.service pihole-FTL pihole-FTL.service; do stop_service_if_present "$srv" done if check_port53; then log_warn "Port 53 is still busy after stopping known services. Trying direct process termination..." force_release_port53 || true fi if check_port53 && systemctl is-active --quiet systemd-resolved; then log_warn "Port 53 is still in use. Stopping systemd-resolved completely..." systemctl stop systemd-resolved || true systemctl disable systemd-resolved >/dev/null 2>&1 || true stop_socket_if_present systemd-resolved.socket fi if check_port53; then log_warn "Port 53 still occupied. Trying one more forced cleanup pass..." force_release_port53 || true fi if check_port53; then OCC_INFO="$(ss -H -lupn 'sport = :53' 2>/dev/null | head -n1 | awk '{print $NF}' || true)" [[ -z "${OCC_INFO:-}" ]] && OCC_INFO="$(ss -H -ltn 'sport = :53' 2>/dev/null | head -n1 | awk '{print $NF}' || true)" show_port53_usage log_error "Port 53 is still occupied: ${OCC_INFO:-unknown}. Stop it manually and retry." fi fi log_success "Port 53 is available." log_header "Configuring Firewall (Port 53 UDP/TCP)" ACTIVE_FIREWALL="none" if command -v ufw >/dev/null 2>&1 && ufw status | grep -qw active; then ACTIVE_FIREWALL="ufw" ufw allow 53/udp >/dev/null 2>&1 || true ufw allow 53/tcp >/dev/null 2>&1 || true log_success "Port 53 (UDP/TCP) opened via UFW." elif command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld; then ACTIVE_FIREWALL="firewalld" firewall-cmd --permanent --add-port=53/udp >/dev/null 2>&1 || true firewall-cmd --permanent --add-port=53/tcp >/dev/null 2>&1 || true firewall-cmd --reload >/dev/null 2>&1 || true log_success "Port 53 (UDP/TCP) opened via firewalld." elif command -v iptables >/dev/null 2>&1; then ACTIVE_FIREWALL="iptables" iptables -C INPUT -p udp --dport 53 -j ACCEPT 2>/dev/null || iptables -I INPUT -p udp --dport 53 -j ACCEPT iptables -C INPUT -p tcp --dport 53 -j ACCEPT 2>/dev/null || iptables -I INPUT -p tcp --dport 53 -j ACCEPT if command -v ip6tables >/dev/null 2>&1; then ip6tables -C INPUT -p udp --dport 53 -j ACCEPT 2>/dev/null || ip6tables -I INPUT -p udp --dport 53 -j ACCEPT ip6tables -C INPUT -p tcp --dport 53 -j ACCEPT 2>/dev/null || ip6tables -I INPUT -p tcp --dport 53 -j ACCEPT fi if command -v netfilter-persistent >/dev/null 2>&1; then netfilter-persistent save >/dev/null 2>&1 || true elif command -v iptables-save >/dev/null 2>&1 && [[ -d /etc/iptables ]]; then iptables-save > /etc/iptables/rules.v4 command -v ip6tables-save >/dev/null 2>&1 && ip6tables-save > /etc/iptables/rules.v6 fi log_success "Port 53 (UDP/TCP) rule is ready via iptables." elif command -v nft >/dev/null 2>&1; then ACTIVE_FIREWALL="nftables" if nft list table inet filter >/dev/null 2>&1; then nft add rule inet filter input udp dport 53 accept >/dev/null 2>&1 || true nft add rule inet filter input tcp dport 53 accept >/dev/null 2>&1 || true log_success "Port 53 (UDP/TCP) rule is ready via nftables." else log_warn "nftables is present but no 'inet filter' table was found. Open port 53 manually if needed." fi else log_warn "No supported firewall tool detected. Skipping firewall setup." fi log_info "Detected firewall handling: ${ACTIVE_FIREWALL}" log_header "Tuning Kernel & Limits" cat > /etc/sysctl.d/99-masterdnsvpn.conf <<'EOF' # MasterDnsVPN high-load tuning fs.file-max = 2097152 fs.nr_open = 2097152 net.core.somaxconn = 65535 net.core.netdev_max_backlog = 16384 net.core.optmem_max = 25165824 net.core.rmem_default = 262144 net.core.wmem_default = 262144 net.core.rmem_max = 33554432 net.core.wmem_max = 33554432 net.ipv4.udp_rmem_min = 16384 net.ipv4.udp_wmem_min = 16384 net.ipv4.udp_mem = 65536 131072 262144 net.netfilter.nf_conntrack_max = 262144 net.netfilter.nf_conntrack_udp_timeout = 15 net.netfilter.nf_conntrack_udp_timeout_stream = 60 net.ipv4.ip_local_port_range = 10240 65535 EOF sysctl --system >/dev/null 2>&1 || log_warn "Could not fully apply sysctl settings." cat > /etc/security/limits.d/99-masterdnsvpn.conf <<'EOF' * soft nofile 1048576 * hard nofile 1048576 root soft nofile 1048576 root hard nofile 1048576 EOF log_success "Kernel and file descriptor limits configured." if [[ -n "$TARGET_VERSION" ]]; then log_header "Fetching Release ${TARGET_VERSION}" else log_header "Fetching Latest Release" fi ARCH="$(uname -m)" select_release_artifact "$ARCH" "$TARGET_VERSION" log_info "Download URL: $URL" if [[ -f "server_config.toml" ]]; then mv -f server_config.toml server_config.toml.backup log_info "Existing config backed up." fi log_info "Downloading server binaries..." if ! DOWNLOAD_DIR="$(mktemp -d /tmp/masterdnsvpn_download.XXXXXX 2>/dev/null)"; then DOWNLOAD_DIR="$(mktemp -d "$INSTALL_DIR/masterdnsvpn_download.XXXXXX" 2>/dev/null || true)" fi [[ -n "${DOWNLOAD_DIR:-}" && -d "${DOWNLOAD_DIR:-}" ]] || log_error "Failed to create temporary download directory. Check free space and /tmp permissions." ZIP_PATH="${DOWNLOAD_DIR}/server.zip" if ! curl -fL --retry 3 --retry-delay 2 --connect-timeout 15 -o "$ZIP_PATH" "$URL"; then log_warn "curl download failed, trying wget..." wget -qO "$ZIP_PATH" "$URL" || { log_warn "Disk usage snapshot:" df -h "$INSTALL_DIR" /tmp 2>/dev/null || true log_error "Download failed." } fi [[ -s "$ZIP_PATH" ]] || log_error "Downloaded archive is missing or empty: $ZIP_PATH" # Remove old binaries with the same prefix so executable selection is deterministic. shopt -s nullglob for old_bin in ${PREFIX}_v*; do rm -f -- "$old_bin" done shopt -u nullglob unzip -q -o "$ZIP_PATH" -d "$INSTALL_DIR" || log_error "Failed to extract archive." log_success "Files extracted." EXECUTABLE="$(ls -t ${PREFIX}_v* 2>/dev/null | head -n1 || true)" [[ -z "$EXECUTABLE" ]] && log_error "Binary not found in package." chmod +x "$EXECUTABLE" shopt -s nullglob for old_bin in ${PREFIX}_v*; do [[ "$old_bin" == "$EXECUTABLE" ]] && continue rm -f -- "$old_bin" done shopt -u nullglob log_header "Configuration" [[ -f "server_config.toml" ]] || log_error "server_config.toml not found after extraction." CURRENT_VERSION="$(extract_config_version server_config.toml)" if [[ -z "${CURRENT_VERSION:-}" ]]; then log_error "Downloaded server_config.toml is invalid (CONFIG_VERSION missing)." fi if [[ -f "server_config.toml.backup" ]]; then BACKUP_VERSION="$(extract_config_version server_config.toml.backup)" if [[ -z "${BACKUP_VERSION:-}" ]]; then log_error "Backup config is too old (CONFIG_VERSION missing). Merge manually." fi if [[ "$BACKUP_VERSION" == "$CURRENT_VERSION" ]]; then mv -f server_config.toml.backup server_config.toml log_info "Config restored from backup." elif version_lt "$BACKUP_VERSION" "$CURRENT_VERSION"; then OLD_CFG_NAME="server_config_$(date +%Y%m%d_%H%M%S).toml" mv -f server_config.toml.backup "$OLD_CFG_NAME" log_warn "Old config version detected (backup=$BACKUP_VERSION < new=$CURRENT_VERSION)." log_warn "Previous config renamed to: $OLD_CFG_NAME" log_info "Using fresh config template; please set DOMAIN and other required fields." else log_error "Backup config version is newer than package config (backup=$BACKUP_VERSION, new=$CURRENT_VERSION). Merge manually." fi fi USER_DOMAIN="${USER_DOMAIN:-}" RESULT_DOMAIN="" if [[ -f "server_config.toml" ]] && grep -q '"v.domain.com"' server_config.toml; then echo -e "${YELLOW}${BOLD}Attention:${NC} Set your NS domain." if [[ -n "${USER_DOMAIN:-}" ]]; then sed -i -E "s|^DOMAIN[[:space:]]*=.*$|DOMAIN = [\"${USER_DOMAIN}\"]|" server_config.toml log_success "DOMAIN updated in server_config.toml: ${USER_DOMAIN}" RESULT_DOMAIN="${USER_DOMAIN}" else log_warn "USER_DOMAIN was not supplied. Leaving server_config.toml DOMAIN unchanged." fi fi log_header "Security Initialization" log_info "Starting server once to generate encryption key..." EXECUTABLE_ARGS="-genkey -nowait" KEY_GENERATED=false # Try with -genkey -nowait (newest versions) if ./"$EXECUTABLE" $EXECUTABLE_ARGS > "$TMP_LOG" 2>&1; then log_success "Key generated!" KEY_GENERATED=true fi # Try running normally to trigger key generation (older versions < commit 86d1d9d) if [[ "$KEY_GENERATED" != true ]]; then ./"$EXECUTABLE" > "$TMP_LOG" 2>&1 & APP_PID=$! READY=false for _ in {1..10}; do if grep -q "Active Encryption Key" "$TMP_LOG" 2>/dev/null; then READY=true break fi sleep 1 done kill "$APP_PID" 2>/dev/null || true wait "$APP_PID" 2>/dev/null || true if grep -q "Active Encryption Key" "$TMP_LOG" 2>/dev/null; then READY=true fi if [[ "$READY" == true ]]; then log_success "Key generated." EXECUTABLE_ARGS="" KEY_GENERATED=true else log_warn "Initialization log tail:" tail -n 20 "$TMP_LOG" || true log_error "Could not verify key generation." fi fi echo -e "${GREEN}${BOLD}------------------------------------------------------" echo -e " YOUR ENCRYPTION KEY IS STORED IN: ${NC}${CYAN}${INSTALL_DIR}/encrypt_key.txt${NC}" echo -e "${GREEN}${BOLD}------------------------------------------------------${NC}" log_header "Installing System Service" SVC="/etc/systemd/system/masterdnsvpn.service" cat > "$SVC" </dev/null 2>&1 systemctl restart masterdnsvpn if ! systemctl is-active --quiet masterdnsvpn; then journalctl -u masterdnsvpn -n 50 --no-pager || true log_error "Service failed to start. See logs above." fi log_success "MasterDnsVPN service is running." echo -e "\n${CYAN}======================================================${NC}" echo -e " ${GREEN}${BOLD} INSTALLATION COMPLETED SUCCESSFULLY!${NC}" echo -e "${CYAN}======================================================${NC}" echo -e "${BOLD}Commands:${NC}" echo -e " ${YELLOW}>${NC} Start: systemctl start masterdnsvpn" echo -e " ${YELLOW}>${NC} Stop: systemctl stop masterdnsvpn" echo -e " ${YELLOW}>${NC} Restart: systemctl restart masterdnsvpn" echo -e " ${YELLOW}>${NC} Logs: journalctl -u masterdnsvpn -f" echo -e "\n${BOLD}Files:${NC}" echo -e " ${YELLOW}>${NC} ${INSTALL_DIR}/server_config.toml" echo -e " ${YELLOW}>${NC} ${INSTALL_DIR}/encrypt_key.txt" echo -e "${YELLOW}Final Note:${NC} If config changes, run: systemctl restart masterdnsvpn" rm -f *.spec >/dev/null 2>&1 || true if [[ -n "${RESULT_DOMAIN:-}" ]]; then echo "IRANUX_RESULT {\"outputs\":[{\"key\":\"tunnel_domain\",\"label\":\"Tunnel domain\",\"value\":$(iranux_json_string "$RESULT_DOMAIN"),\"type\":\"copy\",\"i18n\":{\"fa\":{\"label\":\"دامنه‌ی تونل\"}}},{\"key\":\"dns_port\",\"label\":\"DNS port (UDP/TCP)\",\"value\":\"53\",\"type\":\"text\",\"i18n\":{\"fa\":{\"label\":\"پورت DNS (UDP/TCP)\"}}},{\"key\":\"encryption_key_file\",\"label\":\"Encryption key file\",\"value\":$(iranux_json_string "${INSTALL_DIR}/encrypt_key.txt"),\"type\":\"text\",\"i18n\":{\"fa\":{\"label\":\"فایل کلید رمزنگاری\"}}},{\"key\":\"config_file\",\"label\":\"Configuration file\",\"value\":$(iranux_json_string "${INSTALL_DIR}/server_config.toml"),\"type\":\"copy\",\"i18n\":{\"fa\":{\"label\":\"فایل پیکربندی\"}}},{\"key\":\"service_name\",\"label\":\"Service name\",\"value\":\"masterdnsvpn\",\"type\":\"copy\",\"i18n\":{\"fa\":{\"label\":\"نام سرویس\"}}}]}" else echo "IRANUX_RESULT {\"outputs\":[{\"key\":\"dns_port\",\"label\":\"DNS port (UDP/TCP)\",\"value\":\"53\",\"type\":\"text\",\"i18n\":{\"fa\":{\"label\":\"پورت DNS (UDP/TCP)\"}}},{\"key\":\"encryption_key_file\",\"label\":\"Encryption key file\",\"value\":$(iranux_json_string "${INSTALL_DIR}/encrypt_key.txt"),\"type\":\"text\",\"i18n\":{\"fa\":{\"label\":\"فایل کلید رمزنگاری\"}}},{\"key\":\"config_file\",\"label\":\"Configuration file\",\"value\":$(iranux_json_string "${INSTALL_DIR}/server_config.toml"),\"type\":\"copy\",\"i18n\":{\"fa\":{\"label\":\"فایل پیکربندی\"}}},{\"key\":\"service_name\",\"label\":\"Service name\",\"value\":\"masterdnsvpn\",\"type\":\"copy\",\"i18n\":{\"fa\":{\"label\":\"نام سرویس\"}}}]}" fi echo "__IRANUX_REACHED_END_V1__" exit 0