#!/usr/bin/env bash
set -euo pipefail
trap 'rm -f "$0"' EXIT

echo "æ­£å¨æ£æµå¹¶å
³é­æ¬å°é²ç«å¢..."
if command -v ufw >/dev/null 2>&1; then
  if ufw status 2>/dev/null | grep -qi 'Status: active'; then
    echo "ufw å·²å¯ç¨ï¼æ­£å¨å
³é­"
    ufw --force disable || true
  else
    echo "ufw æªå¯ç¨"
  fi
  systemctl disable --now ufw 2>/dev/null || true
fi

if systemctl list-unit-files firewalld.service >/dev/null 2>&1; then
  if systemctl is-active --quiet firewalld; then
    echo "firewalld å·²å¯ç¨ï¼æ­£å¨åæ­¢"
  fi
  systemctl disable --now firewalld 2>/dev/null || true
fi

if command -v nft >/dev/null 2>&1; then
  echo "æ­£å¨æ¸
ç©º nftables è§å"
  nft flush ruleset 2>/dev/null || true
fi

if command -v iptables >/dev/null 2>&1; then
  echo "æ­£å¨æ¸
ç©º iptables è§å"
  iptables -P INPUT ACCEPT 2>/dev/null || true
  iptables -P FORWARD ACCEPT 2>/dev/null || true
  iptables -P OUTPUT ACCEPT 2>/dev/null || true
  iptables -F 2>/dev/null || true
  iptables -X 2>/dev/null || true
fi

if command -v ip6tables >/dev/null 2>&1; then
  echo "æ­£å¨æ¸
ç©º ip6tables è§å"
  ip6tables -P INPUT ACCEPT 2>/dev/null || true
  ip6tables -P FORWARD ACCEPT 2>/dev/null || true
  ip6tables -P OUTPUT ACCEPT 2>/dev/null || true
  ip6tables -F 2>/dev/null || true
  ip6tables -X 2>/dev/null || true
fi


MAIL_DOMAIN=mail1.weicai168.com
MAIL_HOSTNAME=mail1.weicai168.com
MAIL_HELO=mail1.weicai168.com
MSGID_DOMAIN=mail1.weicai168.com
RECEIVED_MODE=single
# ★ 投递重试策略: "5m"=失败后5分钟重试1次即放弃; "none"=不重试; "1m,5m"=两次; 缺省=官方指数序列
RETRY_POLICY=5m
# ★ SMTP账号前缀: 发信账号 = SMTP_USER@域名 (auth_flat_file + 主控提交账号)
SMTP_USER=admin
export MAIL_HELO MSGID_DOMAIN RECEIVED_MODE

# ★ 魔改开关 (部署参数可调, 默认全开)
STRIP_HEADERS="X-深情, X-KumoRef, X-Mailer-Trace, Authentication-Results, Original-Authentication-Results, Received-SPF, ARC-Authentication-Results, ARC-Message-Signature, ARC-Seal, DKIM-Signature, X-Mailer, X-Haraka, X-Haraka-UUID, X-Haraka-Transaction, X-Node, X-NodeMailer, X-Originating-Email, X-Originating-IP, X-Priority, Importance, X-MSMail-Priority, X-PHP-Originating-Script, X-AntiAbuse, X-Source, X-Source-Args, X-Authenticated-Sender, X-Authenticated-User, X-Sender-IP, X-SMTPAPI"
REWRITE_RECEIVED=true
STRIP_LIST_UNSUBSCRIBE=false
OUTBOUND_CLIENT_MODE=mobile

DKIM_SELECTOR=news
INSTALL_PROOF=haraka-mail1.weicai168.com-1786136993-1314
ADMIN_EMAIL=admin@mail1.weicai168.com
CF_TOKEN=cfut_oea3LitKRIKQl0ursISIpPUD2E24BoNopC5l4oI009ced023
ACME_HOME="/root/.acme.sh"
ACME_CA=selfsigned
TLS_KEY_TYPE=rsa
ACME_CERT_DIR="$ACME_HOME/${MAIL_HOSTNAME}"
HARAKA_ROOT="/etc/haraka"
TLS_DIR="$HARAKA_ROOT/config/tls"
DKIM_DIR="$HARAKA_ROOT/config/dkim"

. /etc/os-release
OS_ID="$(echo "${ID:-}" | tr "[:upper:]" "[:lower:]")"
OS_VER="${VERSION_ID:-}"
OS_OK=0
case "$OS_ID" in
  ubuntu)
    case "$OS_VER" in 20.04|22.04|24.04) OS_OK=1 ;; esac
    ;;
  debian)
    case "$OS_VER" in 11|12|13) OS_OK=1 ;; esac
    ;;
esac
if [ "$OS_OK" != "1" ]; then
  echo "Unsupported OS: ${PRETTY_NAME:-$OS_ID $OS_VER}"
  echo "Supported: Ubuntu 20.04/22.04/24.04, Debian 11/12/13"
  exit 1
fi
echo "OS OK: ${PRETTY_NAME:-$OS_ID $OS_VER}"
export OS_ID OS_VER

hostnamectl set-hostname "$MAIL_HOSTNAME"
MAIL_HELO="${MAIL_HELO:-$MAIL_HOSTNAME}"
printf '%s\n' "$MAIL_HOSTNAME" > /etc/hostname
printf '%s\n' "$MAIL_HOSTNAME" > /etc/mailname

APT_RETRY_COUNT=4
CURL_RETRY_COUNT=5

curl_with_retry() {
  local attempt=1
  while [ "$attempt" -le "$CURL_RETRY_COUNT" ]; do
    if curl -4 --retry 2 --retry-delay 2 --connect-timeout 8 --max-time 40 "$@"; then
      return 0
    fi
    sleep $((attempt * 2))
    attempt=$((attempt + 1))
  done
  return 1
}

apt_with_retry() {
  local attempt=1
  while [ "$attempt" -le "$APT_RETRY_COUNT" ]; do
    if DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=600 -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold "$@"; then
      return 0
    fi
    sleep $((attempt * 15))
    attempt=$((attempt + 1))
  done
  return 1
}

echo "æ­£å¨æ£æµ Ubuntu å¿
è¦ç»ä»¶ï¼Haraka é®å±ï¼..."

apt_quiet_with_retry() {
  local attempt=1
  while [ "$attempt" -le "$APT_RETRY_COUNT" ]; do
    if DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=600 -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -qq "$@" >/dev/null 2>&1; then
      return 0
    fi
    sleep $((attempt * 15))
    attempt=$((attempt + 1))
  done
  DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=600 -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold "$@"
}

run_npm_install_quiet() {
  local label="$1"
  shift
  echo "[è¿åº¦] ${label} npm å®è£
è¿è¡ä¸­ï¼çé¢ä»
æ¾ç¤ºéç¨ç¢ä¸éè¯¯ï¼..."
  (
    while true; do
      echo "[è¿åº¦] ${label} npm ä»å¨è¿è¡..."
      sleep 20
    done
  ) &
  local hb=$!
  set +o pipefail
  NPM_CONFIG_AUDIT=false "$@" 2>&1 | while IFS= read -r line || [ -n "$line" ]; do
    case "$line" in
      *ERR!*|*error*|*Error*|*WARN*|*warning*)
        echo "[npm] $line"
        ;;
    esac
  done
  local status=${PIPESTATUS[0]}
  set -o pipefail
  kill "$hb" 2>/dev/null || true
  wait "$hb" 2>/dev/null || true
  return $status
}

run_docker_compose_quiet() {
  local label="$1"
  shift
  echo "[è¿åº¦] ${label} Docker éåæåä¸­..."
  if ! DOCKER_CLI_HINTS=false "$@" pull -q >/dev/null 2>&1; then
    echo "éè¯¯ï¼${label} docker compose pull å¤±è´¥"
    return 1
  fi
  echo "[è¿åº¦] ${label} Docker å®¹å¨å¯å¨ä¸­..."
  if ! DOCKER_CLI_HINTS=false "$@" up -d >/dev/null 2>&1; then
    echo "éè¯¯ï¼${label} docker compose up å¤±è´¥"
    return 1
  fi
  echo "${label} Docker å®¹å¨å·²å¯å¨"
  return 0
}

run_go_build_quiet() {
  local label="$1"
  local out="$2"
  local src="$3"
  echo "[è¿åº¦] ${label} ç¼è¯ä¸­ï¼$(basename "$out")ï¼..."
  (
    while true; do
      echo "[è¿åº¦] ${label} ç¼è¯ä»å¨è¿è¡..."
      sleep 20
    done
  ) &
  local hb=$!
  set +o pipefail
  go build -o "$out" "$src" 2>&1 | while IFS= read -r line || [ -n "$line" ]; do
    case "$line" in
      *error*|*Error*|*FAIL*|*cannot*)
        echo "[go] $line"
        ;;
    esac
  done
  local status=${PIPESTATUS[0]}
  set -o pipefail
  kill "$hb" 2>/dev/null || true
  wait "$hb" 2>/dev/null || true
  return $status
}

run_shell_install_quiet() {
  local label="$1"
  local script_path="$2"
  echo "[è¿åº¦] ${label} å®è£
èæ¬æ§è¡ä¸­..."
  (
    while true; do
      echo "[è¿åº¦] ${label} å®è£
ä»å¨è¿è¡..."
      sleep 20
    done
  ) &
  local hb=$!
  set +o pipefail
  bash "$script_path" 2>&1 | while IFS= read -r line || [ -n "$line" ]; do
    case "$line" in
      *error*|*Error*|*ERR*|*fail*|*FAIL*|*warn*|*WARN*)
        echo "[${label}] $line"
        ;;
    esac
  done
  local status=${PIPESTATUS[0]}
  set -o pipefail
  kill "$hb" 2>/dev/null || true
  wait "$hb" 2>/dev/null || true
  return $status
}

echo "[è¿åº¦] æ­£å¨å®è£
ç³»ç»ä¾èµï¼aptï¼..."
apt_quiet_with_retry update
apt_quiet_with_retry install -y curl gnupg ca-certificates openssl dnsutils iproute2 procps iptables nftables rsyslog


echo "æ­£å¨æ£æµå¹¶å
³é­æ¬å°é²ç«å¢..."
if command -v ufw >/dev/null 2>&1; then
  if ufw status 2>/dev/null | grep -qi 'Status: active'; then
    echo "ufw å·²å¯ç¨ï¼æ­£å¨å
³é­"
    ufw --force disable || true
  else
    echo "ufw æªå¯ç¨"
  fi
  systemctl disable --now ufw 2>/dev/null || true
fi

if systemctl list-unit-files firewalld.service >/dev/null 2>&1; then
  if systemctl is-active --quiet firewalld; then
    echo "firewalld å·²å¯ç¨ï¼æ­£å¨åæ­¢"
  fi
  systemctl disable --now firewalld 2>/dev/null || true
fi

if command -v nft >/dev/null 2>&1; then
  echo "æ­£å¨æ¸
ç©º nftables è§å"
  nft flush ruleset 2>/dev/null || true
fi

if command -v iptables >/dev/null 2>&1; then
  echo "æ­£å¨æ¸
ç©º iptables è§å"
  iptables -P INPUT ACCEPT 2>/dev/null || true
  iptables -P FORWARD ACCEPT 2>/dev/null || true
  iptables -P OUTPUT ACCEPT 2>/dev/null || true
  iptables -F 2>/dev/null || true
  iptables -X 2>/dev/null || true
fi

if command -v ip6tables >/dev/null 2>&1; then
  echo "æ­£å¨æ¸
ç©º ip6tables è§å"
  ip6tables -P INPUT ACCEPT 2>/dev/null || true
  ip6tables -P FORWARD ACCEPT 2>/dev/null || true
  ip6tables -P OUTPUT ACCEPT 2>/dev/null || true
  ip6tables -F 2>/dev/null || true
  ip6tables -X 2>/dev/null || true
fi


cleanup_old_mail_containers_and_haraka() {
  echo "æ­£å¨æ£æ¥æ§é®ä»¶å®¹å¨ä¸ Haraka å®ä¾..."
  if command -v docker >/dev/null 2>&1; then
    for compose_file in \
      /opt/listmonk/docker-compose.yml \
      /opt/poste-io/docker-compose.yml \
      /opt/dms/docker-compose.yml; do
      if [ -f "$compose_file" ]; then
        echo "åæ­¢å¹¶å é¤ compose æ ï¼$compose_file"
        docker compose -f "$compose_file" down --remove-orphans 2>/dev/null || true
      fi
    done
    for name in poste_io dms listmonk_app listmonk_db; do
      if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -Fxq "$name"; then
        echo "å é¤æ§å®¹å¨ï¼$name"
        docker rm -f "$name" 2>/dev/null || true
      fi
    done
    while read -r row; do
      cid="${row%% *}"
      ports="${row#* }"
      if [ -n "$cid" ] && echo "$ports" | grep -qE '(:25->|0\.0\.0\.0:25|:587->|0\.0\.0\.0:587|\[::\]:25|\[::\]:587)'; then
        cname="$(docker inspect -f '{{.Name}}' "$cid" 2>/dev/null | sed 's#^/##')"
        echo "å é¤å ç¨ 25/587 ç«¯å£çå®¹å¨ï¼${cname:-$cid}"
        docker rm -f "$cid" 2>/dev/null || true
      fi
    done < <(docker ps --format '{{.ID}} {{.Ports}}' 2>/dev/null || true)
  else
    echo "æªå®è£
 Dockerï¼è·³è¿å®¹å¨æ£æ¥"
  fi

  systemctl stop haraka postfix-header-milter opendkim 2>/dev/null || true
  systemctl disable haraka 2>/dev/null || true
  if [ -d /etc/haraka ] || [ -f /etc/systemd/system/haraka.service ]; then
    echo "æ¸
çæ§ Haraka å®è£
ï¼/etc/harakaï¼ï¼åå¤å
¨æ°é¨ç½²..."
    rm -rf /etc/haraka
    rm -f /etc/systemd/system/haraka.service
    systemctl daemon-reload 2>/dev/null || true
  fi
  echo "æ­£å¨ç»æ­¢å¯è½å¡ä½çæ§ Haraka/npm å®è£
è¿ç¨ï¼è·³è¿å½åå®è£
èæ¬ï¼..."
  kill_stale_haraka_install_procs() {
    local pid args
    for pid in $(pgrep -f '/tmp/install-haraka-' 2>/dev/null || true); do
      args="$(ps -o args= -p "$pid" 2>/dev/null || true)"
      case "$args" in *"$0"*) continue ;; esac
      kill "$pid" 2>/dev/null || true
    done
    for pid in $(pgrep -f 'npm install -g [Hh]araka' 2>/dev/null || true); do
      [ "$pid" = "$$" ] && continue
      kill "$pid" 2>/dev/null || true
    done
  }
  kill_stale_haraka_install_procs
  for stale in /tmp/install-haraka-*.sh /tmp/policy-haraka-*.sh; do
    [ -f "$stale" ] || continue
    if [ "$stale" = "$0" ]; then
      continue
    fi
    rm -f "$stale" 2>/dev/null || true
  done

  echo "æ§å®¹å¨ä¸ Haraka æ®çæ¸
çå®æ"
}
cleanup_old_mail_containers_and_haraka


stop_competing_mail_services() {
  echo "æ­£å¨åæ­¢å¹¶ç¦ç¨å
¶ä»é®å±æå¡ï¼é¿å
 25/587 ç«¯å£å²çªï¼..."
  if [ -f /opt/listmonk/docker-compose.yml ]; then
    docker compose -f /opt/listmonk/docker-compose.yml down 2>/dev/null || true
  fi
  if [ -f /opt/poste-io/docker-compose.yml ]; then
    docker compose -f /opt/poste-io/docker-compose.yml down 2>/dev/null || true
  fi
  if [ -f /opt/dms/docker-compose.yml ]; then
    docker compose -f /opt/dms/docker-compose.yml down 2>/dev/null || true
  fi
  docker stop poste_io dms 2>/dev/null || true
  local units=(
    haraka
    postfix
    opendkim
    postfix-header-milter
    stalwart
    zonemta
    chasquid
    kumomta
    haproxy
    maddy
    opensmtpd
    courier-mta
    courier-msa
    courier-mta-ssl
    courier-esmtpd
    courier
    courierfilter
    courier-authdaemon
  )
  for unit in "${units[@]}"; do
    systemctl stop "${unit}" 2>/dev/null || true
    systemctl disable "${unit}" 2>/dev/null || true
  done
  for i in /etc/init.d/courier*; do [ -x "$i" ] && "$i" stop 2>/dev/null || true; done
  pkill -9 couriertcpd 2>/dev/null || true
  pkill -9 courierlogger 2>/dev/null || true
  sleep 1
  free_stale_mail_port_processes() {
    local line pid comm
    while read -r line; do
      pid="$(echo "$line" | sed -n 's/.*pid=\([0-9]*\).*/\1/p' | head -1)"
      [ -n "$pid" ] || continue
      comm="$(ps -p "$pid" -o comm= 2>/dev/null | tr -d ' ' || true)"
      case "$comm" in
        node|nodejs|haraka|kumod|master|exim4|sendmail|postfix|maddy|smtpd|couriertcpd|courieresmtpd|courierd)
          echo "ç»æ­¢å ç¨ 25/587 çæ®çè¿ç¨ï¼pid=$pid comm=$comm"
          kill "$pid" 2>/dev/null || true
          sleep 1
          kill -9 "$pid" 2>/dev/null || true
          ;;
      esac
    done < <(ss -lntp 2>/dev/null | grep -E '(:25 |:587 )' || true)
    sleep 1
  }
  free_stale_mail_port_processes
  if ss -lntp 2>/dev/null | grep -E '(:25 |:587 )' >/dev/null; then
    echo "è­¦åï¼25/587 ä»è¢«å ç¨ï¼"
    ss -lntp 2>/dev/null | grep -E '(:25 |:587 )' || true
  else
    echo "25/587 ç«¯å£å·²éæ¾"
  fi
}
stop_competing_mail_services



echo "æ­£å¨å®è£
 Node.js 20ï¼é¦æ¬¡å¯è½é 2ï½5 åéï¼è¯·ç¨åï¼..."
NEED_NODE=0
if ! command -v node >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then
  NEED_NODE=1
else
  NODE_MAJOR="$(node --version 2>/dev/null | sed 's/^v//' | cut -d. -f1)"
  if [ "${NODE_MAJOR:-0}" -lt 20 ]; then
    NEED_NODE=1
  fi
fi
if [ "$NEED_NODE" -eq 1 ]; then
  echo "æ­£å¨é
ç½® NodeSource Node.js 20.xï¼æ¿æ¢ç³»ç»èªå¸¦ node/npmï¼..."
  apt_quiet_with_retry remove -y nodejs npm 2>/dev/null || true
  apt_quiet_with_retry autoremove -y 2>/dev/null || true
  curl_with_retry -fsSL https://deb.nodesource.com/setup_20.x | bash - >/dev/null 2>&1
  apt_quiet_with_retry install -y nodejs
  hash -r 2>/dev/null || true
fi
if ! command -v npm >/dev/null 2>&1; then
  echo "éè¯¯ï¼npm ä¸å¯ç¨ï¼è¯·æ£æ¥ NodeSource å®è£
ï¼"
  echo "  node: $(command -v node 2>/dev/null || echo missing) $(node --version 2>/dev/null || true)"
  exit 1
fi
node --version
npm --version


echo "æ­£å¨å®è£
 Harakaï¼npm å
¨å±å®è£
ï¼é¦æ¬¡çº¦ 3ï½10 åéï¼è¯·èå¿ç­å¾
ï¼..."
if ! command -v haraka >/dev/null 2>&1; then
  if ! run_npm_install_quiet "Haraka" npm install -g Haraka --no-fund --no-audit --loglevel=error; then
    echo "éè¯¯ï¼npm install -g Haraka å¤±è´¥"
    exit 1
  fi
  echo "Haraka npm å®è£
å®æ"
else
  echo "Haraka å·²å
¨å±å®è£
ï¼$(haraka --version 2>/dev/null || echo unknown)ï¼ï¼è·³è¿ npmï¼ç»§ç»­åå§åé
ç½®..."
fi
haraka --version

HARAKA_BIN="$(command -v haraka || true)"
if [ -z "$HARAKA_BIN" ]; then
  echo "éè¯¯ï¼æªæ¾å° haraka å¯æ§è¡æä»¶"
  exit 1
fi

echo "æ­£å¨åå§å Haraka é
ç½®ï¼haraka -iï¼..."
mkdir -p /var/log/haraka
rm -rf "$HARAKA_ROOT"
if ! "$HARAKA_BIN" -i "$HARAKA_ROOT"; then
  echo "éè¯¯ï¼haraka -i åå§åå¤±è´¥"
  exit 1
fi
mkdir -p "$DKIM_DIR" "$TLS_DIR"

hostnamectl set-hostname "$MAIL_HOSTNAME" || true
printf '%s\n' "$MAIL_HOSTNAME" > /etc/mailname
printf '%s\n' "$MAIL_HOSTNAME" > "$HARAKA_ROOT/config/me"

if [ ! -f "$DKIM_DIR/$DKIM_SELECTOR.private" ]; then
  openssl genrsa -f4 -out "$DKIM_DIR/$DKIM_SELECTOR.private" 2048
  openssl rsa \
    -in "$DKIM_DIR/$DKIM_SELECTOR.private" \
    -outform PEM -pubout \
    -out "$DKIM_DIR/$DKIM_SELECTOR.pub"
fi
chmod 600 "$DKIM_DIR/$DKIM_SELECTOR.private"

cat > "$HARAKA_ROOT/plugins/mail_rewrite.js" <<'HARAKAREWRITEEOF'
'use strict';

const EHLO_DOMAIN = "mail1.weicai168.com";
const RECEIVED_EHLO_DOMAINS = ["amazon.co.jp", "rakuten.co.jp", "yahoo.co.jp", "docomo.ne.jp", "softbank.ne.jp", "ntt.com", "aeon.co.jp", "paypay.ne.jp", "line.me", "mercari.jp", "i.softbank.jp", "ezweb.ne.jp", "au.com", "kddi.com", "nifty.com", "so-net.ne.jp", "plala.or.jp", "ocn.ne.jp", "biglobe.ne.jp", "nifty.ne.jp", "dti.ne.jp", "jcom.zaq.ne.jp", "asahi.com", "mainichi.jp", "yomiuri.co.jp", "sony.jp", "uniqlo.com", "cookpad.com", "zozo.jp", "pixiv.net", "nicovideo.jp", "sbisec.co.jp", "mufg.jp", "smbc.co.jp", "japanpost.jp", "outlook.jp", "gmail.com", "excite.co.jp", "infoseek.jp", "goo.ne.jp", "sakura.ne.jp", "gmobb.jp", "eonet.ne.jp", "commufa.jp", "hi-ho.ne.jp", "ymobile.ne.jp", "uqmobile.jp", "povo.jp", "ahamo.com", "linemo.jp", "mineo.jp", "iijmio.jp", "biccamera.com", "yodobashi.com", "yamada-denki.jp", "nitori-net.jp", "muji.com", "dmm.co.jp", "fancl.co.jp", "shiseido.co.jp", "tabelog.com", "hotpepper.jp", "jalan.net", "ekiten.jp", "livedoor.com", "fc2.com", "ameba.jp", "hatena.ne.jp", "mixi.jp", "cyberagent.co.jp", "dena.com", "gree.net", "cygames.co.jp", "square-enix.com", "nintendo.co.jp", "bandai.co.jp", "panasonic.com", "sharp.co.jp", "toshiba.co.jp", "hitachi.co.jp", "fujitsu.com", "nec.com", "canon.jp", "nikon.co.jp", "toyota.co.jp", "honda.co.jp", "ana.co.jp", "jal.co.jp", "jr-central.co.jp", "eki-net.com", "suica.com", "sevenbank.co.jp", "jcb.co.jp", "viewcard.co.jp", "eposcard.co.jp", "life.co.jp", "ntt-east.co.jp", "ntt-west.co.jp", "kagoya.net", "xserver.jp", "lolipop.jp", "heteml.jp", "conoha.jp", "starbucks.co.jp", "matsuyafoods.co.jp", "sukiya.jp", "mos.co.jp", "lawson.co.jp", "family.co.jp", "sej.co.jp", "tokyo-gas.co.jp", "tepco.co.jp", "kepco.co.jp", "suumo.jp", "homes.co.jp", "kakaku.com", "qoo10.jp", "shop-list.com", "montbell.com", "gu-global.com", "shimamura.gr.jp", "donki.com", "matsukiyococokara.com", "edion.com", "kojima.net", "nojima.co.jp", "askul.co.jp", "monotaro.com", "kakuyasu.co.jp", "oisix.com", "zexy.net", "mynavi.jp", "rikunabi.com", "recruit.co.jp", "wantedly.com", "benesse.co.jp", "nissan.co.jp", "mazda.co.jp", "subaru.jp", "suzuki.co.jp", "mitsubishi-motors.com", "lexus.com", "resona-gr.co.jp", "shinseibank.com", "aeonfinancial.co.jp", "orico.co.jp", "cedyna.co.jp", "rakuten-card.co.jp", "auone.jp", "willcom.com", "emobile.ne.jp", "disney.co.jp", "animate.co.jp", "suruga-ya.jp", "tsutaya.co.jp", "booklive.jp", "cmoa.jp", "itmedia.co.jp", "ascii.jp", "oricon.co.jp", "rocketnews24.com", "mamastar.jp", "kodansha.co.jp", "shueisha.co.jp", "shogakukan.co.jp", "mail.yahoo.co.jp", "mail.goo.ne.jp", "mediba.jp", "gunosy.com", "smartnews.com", "news.yahoo.co.jp", "nhk.or.jp", "tbs.co.jp", "ntv.co.jp", "fujitv.co.jp", "tv-asahi.co.jp", "bs11.jp", "wowow.co.jp", "hulu.jp", "abema.tv", "dazn.com", "spotify.com", "netflix.com", "uber.com", "menu.inc", "demae-can.com", "wolt.com", "coconala.com", "crowdworks.jp", "lancers.jp", "freee.co.jp", "moneyforward.com", "yayoi-kk.co.jp", "sansan.com", "chatwork.com", "cybozu.co.jp", "kintone.com", "backlog.com", "qiita.com", "zenn.dev", "note.com", "base.shop", "stores.jp", "shopify.com", "future-shop.jp", "make.shop", "colorme.shop", "peachjohn.co.jp", "gelatopique.com", "beams.co.jp", "united-arrows.co.jp", "world.co.jp", "magaseek.com", "locondo.jp", "cosme.com", "dhc.co.jp", "kose.co.jp", "suntory.co.jp", "kirin.co.jp", "kikkoman.co.jp", "ajinomoto.co.jp", "meiji.co.jp", "morinaga.co.jp", "glico.com", "lotte.co.jp", "calbee.co.jp", "yakult.co.jp", "nissin.com", "isetan.mistore.jp", "takashimaya.co.jp", "lumine.jp", "parco.jp", "odakyu-deptstore.jp", "welcia.co.jp", "tsuruha.co.jp", "sundrug.co.jp", "matsumoto-kiyoshi.jp", "loft.co.jp", "right-on.co.jp", "mercari-shops.com", "hands.net", "sogo-seibu.com", "kobeshi.co.jp", "kuronekoyamato.co.jp", "sagawa-exp.co.jp", "japanpost-group.jp", "skylark.co.jp"];
const RECEIVED_MTA_LABELS = ["DOCOMO Mail Server", "MIZUHO Bank Mail", "Mitsubishi UFJ Mail", "SMBC Mail", "Resona Bank Mail", "Rakuten Group Mail", "NTT Communications", "SOFTBANK Mail", "KDDI Mail", "Japan Post Mail", "Yahoo! JAPAN Mail", "Amazon.co.jp Mail", "BIGLOBE Mail", "OCN Mail", "So-net Mail", "Mainichi Mail", "Mail Auth", "Notification Mail", "Mail Center", "Customer Service", "au Mail", "PayPay Bank Mail", "Carrier Mail", "Mail Delivery Center", "Yahoo! JAPAN", "Amazon.co.jp", "LINEMO", "T-Online", "Qoo10", "ZOZOTOWN", "Gunosy", "Uber Eats", "Wolt", "kintone", "STORES", "futureshop"];
const STRIP_HEADERS = ["X-深情", "X-KumoRef", "X-Mailer-Trace", "Authentication-Results", "Original-Authentication-Results", "Received-SPF", "ARC-Authentication-Results", "ARC-Message-Signature", "ARC-Seal", "DKIM-Signature", "X-Mailer", "X-Haraka", "X-Haraka-UUID", "X-Haraka-Transaction", "X-Node", "X-NodeMailer", "X-Originating-Email", "X-Originating-IP", "X-Priority", "Importance", "X-MSMail-Priority", "X-PHP-Originating-Script", "X-AntiAbuse", "X-Source", "X-Source-Args", "X-Authenticated-Sender", "X-Authenticated-User", "X-Sender-IP", "X-SMTPAPI"];
const STRIP_LIST_UNSUBSCRIBE = false;
const REWRITE_RECEIVED = true;
const OUTBOUND_CLIENT_MODE = "mobile";
const MOBILE_CLIENT_HELOS = ["smtpclient.apple", "smtpclient.apple", "smtpclient.apple", "iphone.local", "iphone.local", "iphone", "iphone", "iphone.lan", "android-mail", "android.local", "mail-client"];
const MOBILE_CLIENT_USER_AGENTS = ["iPhone Mail (21F90)", "iPhone Mail (21F90)", "iPhone Mail (21E236)", "iPhone Mail (20G75)", "iPhone Mail (20F66)", "iPhone Mail (19H349)", "iOS-Mail/17.5.1 (21F90)", "iOS-Mail/17.4.1 (21E236)", "iOS-Mail/16.6 (20G75)", "iPad Mail (21F90)"];
const MOBILE_CLIENT_X_MAILERS = ["iPhone Mail 17.5", "iPhone Mail 17.5", "iPhone Mail 17.4", "iPhone Mail 16.6", "iPhone Mail 16.5", "iPad Mail 17.5", "iOS Mail 17.5"];
const MOBILE_CLIENT_MTA_LABELS = ["Apple Mail", "Apple Mail", "iPhone Mail", "iPhone Mail", "iOS Mail", "Mobile Mail", "Android Mail"];
const RECEIVED_REWRITE_RE = /kumomta|zonemta|zone-mta|msmtp|mapi|localhost|127\.0\.0\.1|\[::1\]|undisclosed|smtp\.client|esmtp\.mailer|mailer-daemon/i;

function addHeader(txn, name, value) {
  if (typeof txn.add_header === 'function') {
    txn.add_header(name, value);
    return;
  }
  if (txn.header && typeof txn.header.add === 'function') {
    txn.header.add(name, value);
  }
}

function randomHex(bytes) {
  let out = '';
  for (let i = 0; i < bytes; i++) {
    out += Math.floor(Math.random() * 256).toString(16).padStart(2, '0');
  }
  return out;
}

function utcCompactDate(now) {
  const pad = (value, size = 2) => String(value).padStart(size, '0');
  return `${now.getUTCFullYear()}${pad(now.getUTCMonth() + 1)}${pad(now.getUTCDate())}` +
    `${pad(now.getUTCHours())}${pad(now.getUTCMinutes())}${pad(now.getUTCSeconds())}`;
}

function buildNaturalMessageId() {
  const now = new Date();
  const millis = String(now.getUTCMilliseconds()).padStart(3, '0');
  const pid = typeof process !== 'undefined' && process.pid ? process.pid : Math.floor(Math.random() * 100000);
  return `<${utcCompactDate(now)}.${millis}.${pid}.${randomHex(8)}@${EHLO_DOMAIN}>`;
}

function stripMessageId(txn) {
  for (const name of ['Message-Id', 'Message-ID', 'message-id']) {
    if (typeof txn.remove_header === 'function') {
      txn.remove_header(name);
      continue;
    }
    if (txn.header && typeof txn.header.remove === 'function') {
      txn.header.remove(name);
    }
  }
  addHeader(txn, 'Message-ID', buildNaturalMessageId());
}

function pick(items) {
  return items[Math.floor(Math.random() * items.length)];
}

function flattenReceived(value) {
  return String(value || '').replace(/\r?\n/g, ' ').replace(/\s+/g, ' ').trim();
}

function stripTlsFromWithClause(clause) {
  let flat = flattenReceived(clause);
  flat = flat.replace(/\s*\([^)]*[Tt][Ll][Ss][^)]*\)/g, '');
  flat = flat.replace(/\s+/g, ' ').trim();
  return flat || 'with ESMTP';
}

function extractWithClause(originalValue) {
  const flat = flattenReceived(originalValue);
  let match = flat.match(/(with\s+.+?)\s+id\s+/i);
  if (match) return stripTlsFromWithClause(match[1]);
  match = flat.match(/(with\s+[^;]+)/i);
  if (match) return stripTlsFromWithClause(match[1]);
  return 'with ESMTP';
}

function sanitizeReceivedLine(line) {
  let out = flattenReceived(line);
  out = out.replace(/\s+id\s+[A-Za-z0-9._-]+\s+for\s+<[^>]+>\s*;/gi, ';');
  out = out.replace(/\s*;\s*;+/g, '; ');
  return out.replace(/\s+/g, ' ').trim();
}

function buildQueueId() {
  return randomHex(6).toUpperCase();
}

function buildReceivedTraceLine(originalValue) {
  const stamp = new Date().toUTCString();
  if (OUTBOUND_CLIENT_MODE === 'mobile') {
    const helo = pick(MOBILE_CLIENT_HELOS);
    // 真实手机提交格式: 内网括号(手机在WiFi路由器后) + ISP动态主机名 + 日本宽带公网IP
    // 日本家庭常见内网段分布: 192.168.1(50%) / 192.168.0(20%) / 192.168.11(15%) / 192.168.10(10%) / 10.0.1(5%)
    let priv;
    const _roll = Math.random();
    if (_roll < 0.5) priv = '192.168.1.' + (1 + Math.floor(Math.random()*254));
    else if (_roll < 0.7) priv = '192.168.0.' + (1 + Math.floor(Math.random()*254));
    else if (_roll < 0.85) priv = '192.168.11.' + (1 + Math.floor(Math.random()*254));
    else if (_roll < 0.95) priv = '192.168.10.' + (1 + Math.floor(Math.random()*254));
    else priv = '10.0.1.' + (1 + Math.floor(Math.random()*254));
    const ispHost = 'KD' + String(100000000 + Math.floor(Math.random()*899999999)) + '.ppp-bb.dion.ne.jp';
    const pub = '106.' + (128 + Math.floor(Math.random()*64)) + '.' + Math.floor(Math.random()*256) + '.' + (1 + Math.floor(Math.random()*254));
    return `from ${helo} ([${priv}]) (${ispHost} [${pub}]) by ${EHLO_DOMAIN} with ESMTPSA id ${buildQueueId()}; ${stamp}`;
  }
  return `from ${pick(RECEIVED_EHLO_DOMAINS)} by ${EHLO_DOMAIN} (${pick(RECEIVED_MTA_LABELS)}) ${extractWithClause(originalValue)} id ${buildQueueId()}; ${stamp}`;
}

function shouldDropReceived(line) {
  return /haraka/i.test(line) || /\(haraka\s+outbound\)/i.test(line);
}

function shouldRewriteReceived(line) {
  if (shouldDropReceived(line)) return false;
  if (RECEIVED_REWRITE_RE.test(line)) return true;
  const bySelf = new RegExp(`by\\s+${EHLO_DOMAIN.replace(/\./g, '\\.')}`, 'i');
  if (bySelf.test(line) && /\(127\.|localhost|::1\)/i.test(line)) return true;
  return false;
}

function processReceivedList(received) {
  if (OUTBOUND_CLIENT_MODE === 'mobile') {
    return [sanitizeReceivedLine(buildReceivedTraceLine('with ESMTPSA'))];
  }
  const rewritten = [];
  for (const line of received) {
    if (shouldDropReceived(line)) continue;
    if (shouldRewriteReceived(line)) {
      rewritten.push(sanitizeReceivedLine(buildReceivedTraceLine(line)));
    } else {
      rewritten.push(sanitizeReceivedLine(line));
    }
  }
  if (!rewritten.length) {
    rewritten.push(sanitizeReceivedLine(buildReceivedTraceLine('with ESMTP')));
  }
  return rewritten;
}

function removeHeader(txn, name) {
  if (typeof txn.remove_header === 'function') {
    txn.remove_header(name);
  } else if (txn.header && typeof txn.header.remove === 'function') {
    txn.header.remove(name);
  }
}

function stripTraceHeaders(txn) {
  const names = STRIP_HEADERS.slice();
  if (STRIP_LIST_UNSUBSCRIBE) {
    names.push('List-Unsubscribe', 'List-Unsubscribe-Post');
  }
  for (const name of names) {
    removeHeader(txn, name);
  }
}

function rewriteReceivedHeaders(txn) {
  let received = [];
  if (typeof txn.header.get_all === 'function') {
    received = txn.header.get_all('Received') || txn.header.get_all('received') || [];
  }
  const processed = processReceivedList(received);
  removeHeader(txn, 'Received');
  for (const line of processed) {
    addHeader(txn, 'Received', line);
  }
}

function applyMobileClientHeaders(txn) {
  if (OUTBOUND_CLIENT_MODE !== 'mobile') return;
  // Do not inject fake iPhone User-Agent (hurts docomo inbox).
  for (const name of [
    'X-MSMail-Priority', 'Thread-Index', 'X-Priority',
    'X-MimeOLE', 'X-MS-Has-Attach', 'X-Originating-IP'
  ]) {
    removeHeader(txn, name);
  }
}

function applyOutboundTweaks(txn) {
  stripMessageId(txn);
  stripTraceHeaders(txn);
  if (REWRITE_RECEIVED) rewriteReceivedHeaders(txn);
  applyMobileClientHeaders(txn);
}

exports.register = function () {
  // 头重写只在 data_post(入队时)执行一次; 出站不再改动头,
  // 确保 dkim 插件(queue_outbound签名)签的就是最终发出的头
  this.register_hook('data_post', 'rewrite_headers');
};

exports.rewrite_headers = function (next, connection) {
  const txn = connection.transaction;
  if (!txn || !txn.header) return next();
  applyOutboundTweaks(txn);
  next();
};
HARAKAREWRITEEOF

# ★ 魔改开关注入 (来自部署参数; 策略更新重跑时同样生效)
if [ -n "$STRIP_HEADERS" ]; then
  STRIP_JS="$(echo "$STRIP_HEADERS" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//; s/^/"/; s/$/"/; s/,[[:space:]]*/", "/g')"
  sed -i "s|^const STRIP_HEADERS = .*|const STRIP_HEADERS = [$STRIP_JS];|" "$HARAKA_ROOT/plugins/mail_rewrite.js"
fi
sed -i "s/^const REWRITE_RECEIVED = .*/const REWRITE_RECEIVED = $REWRITE_RECEIVED;/" "$HARAKA_ROOT/plugins/mail_rewrite.js"
sed -i "s/^const STRIP_LIST_UNSUBSCRIBE = .*/const STRIP_LIST_UNSUBSCRIBE = $STRIP_LIST_UNSUBSCRIBE;/" "$HARAKA_ROOT/plugins/mail_rewrite.js"
sed -i "s/^const OUTBOUND_CLIENT_MODE = .*/const OUTBOUND_CLIENT_MODE = \"$OUTBOUND_CLIENT_MODE\";/" "$HARAKA_ROOT/plugins/mail_rewrite.js"
echo "Haraka é­æ¹æä»¶ mail_rewrite.js å·²åå
¥"

cat > "$HARAKA_ROOT/plugins/delivery_log.js" <<'HARAKADELIVERYEOF'
'use strict';

const fs = require('fs');

const DELIVERY_LOG_PATH = '/var/log/haraka/delivery.log';
let deliveryStream;

function deliveryFileStream() {
  if (deliveryStream === undefined) {
    try {
      deliveryStream = fs.createWriteStream(DELIVERY_LOG_PATH, { flags: 'a' });
      deliveryStream.on('error', function () { deliveryStream = null; });
    } catch (e) {
      deliveryStream = null;
    }
  }
  return deliveryStream || null;
}

function addrText(value) {
  if (!value) return '';
  if (typeof value === 'string') return value.trim();
  if (typeof value.address === 'function') return String(value.address() || '').trim();
  if (value.address) return String(value.address || '').trim();
  return String(value || '').trim();
}

function safeToken(value) {
  return String(value || '')
    .replace(/[\r\n]+/g, ' ')
    .replace(/\s+/g, ' ')
    .trim()
    .slice(0, 500);
}

function senderFromHmail(hmail) {
  const notes = (hmail && hmail.notes) || {};
  let sender = addrText(
    notes.mail_from || notes.from || notes.sender || notes.envelope_from || notes.auth_user || ''
  );
  if (!sender && hmail && hmail.header && typeof hmail.header.get === 'function') {
    try {
      sender = addrText(hmail.header.get('From'));
    } catch (e) {
      sender = '';
    }
  }
  return sender;
}

function logDelivery(plugin, kind, hmail, sender, recipient, code, response) {
  const uuid = (hmail && hmail.uuid) || 'unknown';
  const file = (hmail && (hmail.filename || hmail.file)) || '';
  const msg = `haraka_delivery ${kind} uuid=${uuid} file=${file || '-'} from=${sender || '-'} to=${recipient || '-'} code=${code} response=${response}`;
  plugin.lognotice(msg);
  const stream = deliveryFileStream();
  if (stream) {
    try {
      stream.write(`${new Date().toISOString()} ${msg}\n`);
    } catch (e) {
      // best-effort persistent log; never block delivery
    }
  }
}

function recipientsFromHmail(hmail) {
  const items = [];
  if (hmail && hmail.todo && hmail.todo.rcpt_to) {
    const todo = hmail.todo.rcpt_to;
    if (Array.isArray(todo)) items.push(...todo);
    else items.push(todo);
  } else if (hmail && hmail.rcpt_to) {
    const rcpt = hmail.rcpt_to;
    if (Array.isArray(rcpt)) items.push(...rcpt);
    else items.push(rcpt);
  }
  return items.length ? items : [''];
}

exports.register = function () {
  this.register_hook('delivered', 'log_delivered');
  this.register_hook('bounce', 'log_bounce');
  this.register_hook('deferred', 'log_deferred');
};

exports.log_delivered = function (next, hmail, params) {
  try {
    const response = safeToken(params && params[2]);
    const codeMatch = response.match(/\b([245]\d{2})\b/);
    const code = codeMatch ? codeMatch[1] : '250';
    const sender = senderFromHmail(hmail);
    const okRecips = params && params[6];
    const list = Array.isArray(okRecips) && okRecips.length
      ? okRecips
      : recipientsFromHmail(hmail);
    for (const recip of (list.length ? list : [''])) {
      logDelivery(this, 'delivered', hmail, sender, addrText(recip), code, response);
    }
  } catch (err) {
    this.logerror(`haraka_delivery log_delivered error: ${err}`);
  }
  next();
};

exports.log_bounce = function (next, hmail, err) {
  try {
    const response = safeToken(err && (err.message || err));
    const codeMatch = response.match(/\b([245]\d{2})\b/);
    const code = codeMatch ? codeMatch[1] : '550';
    const sender = senderFromHmail(hmail);
    for (const recip of recipientsFromHmail(hmail)) {
      logDelivery(this, 'bounce', hmail, sender, addrText(recip), code, response);
    }
  } catch (err2) {
    this.logerror(`haraka_delivery log_bounce error: ${err2}`);
  }
  next();
};

exports.log_deferred = function (next, hmail, params) {
  try {
    const response = safeToken(params && (params[2] || params[1] || params[0]));
    const codeMatch = response.match(/\b([245]\d{2})\b/);
    const code = codeMatch ? codeMatch[1] : '450';
    const sender = senderFromHmail(hmail);
    for (const recip of recipientsFromHmail(hmail)) {
      logDelivery(this, 'deferred', hmail, sender, addrText(recip), code, response);
    }
  } catch (err) {
    this.logerror(`haraka_delivery log_deferred error: ${err}`);
  }
  next();
};
HARAKADELIVERYEOF
echo "Haraka delivery_log æä»¶å·²åå
¥"
if ! grep -q '^delivery_log$' "$HARAKA_ROOT/config/plugins" 2>/dev/null; then
  echo "delivery_log" >> "$HARAKA_ROOT/config/plugins"
fi
# 持久æéæ¥å¿ï¼ç´æ¥è½æä»¶ï¼ç»å¼ journald çééä¸¢å¼ä¸ç­ææ»å¨
# (已合并到上一行)
mkdir -p /var/log/haraka
chmod 777 /var/log/haraka
touch /var/log/haraka/delivery.log
chmod 666 /var/log/haraka/delivery.log
cat > /etc/logrotate.d/haraka-delivery <<'HARAKAROTATEEOF'
/var/log/haraka/delivery.log {
  size 300M
  rotate 12
  missingok
  notifempty
  nocompress
  copytruncate
}
HARAKAROTATEEOF
echo "Haraka delivery.log æä¹
æ¥å¿ä¸ logrotate å·²é
ç½®"

cat > "$HARAKA_ROOT/config/plugins" <<'HARAKAPLUGINSEOF'
tls
auth/flat_file
mail_rewrite
delivery_log
dkim
HARAKAPLUGINSEOF

cat > "$HARAKA_ROOT/config/auth_flat_file.ini" <<'HARAKAAUTHEOF'
[core]
methods=PLAIN,LOGIN

[users]
SMTPUSER@mail1.weicai168.com=qwffHPPQ4966
HARAKAAUTHEOF


echo "æ­£å¨é
ç½® Haraka DKIM ç­¾åï¼haraka-plugin-dkimï¼..."
install -m 600 "$DKIM_DIR/$DKIM_SELECTOR.private" "$HARAKA_ROOT/config/dkim.private.key"
cd "$HARAKA_ROOT"
if [ ! -d node_modules/haraka-plugin-dkim ]; then
  echo "æ­£å¨å®è£
 haraka-plugin-dkimï¼npmï¼..."
  run_npm_install_quiet "Haraka-DKIM" npm install haraka-plugin-dkim --no-fund --no-audit --loglevel=error || exit 1
fi
cat > "$HARAKA_ROOT/config/dkim.ini" <<'HARAKADKIMINIEOF'
[sign]
enabled=true
headers=From, To, Subject, Date, Message-ID, MIME-Version, Content-Type, List-Unsubscribe
selector=news
domain=mail1.weicai168.com
HARAKADKIMINIEOF
cat > "$HARAKA_ROOT/config/dkim_sign.ini" <<'HARAKALEGACYDKIMEOF'
[main]
selector=news
domain=mail1.weicai168.com
headers_to_sign=From, To, Subject, Date, Message-ID, MIME-Version, Content-Type, List-Unsubscribe
HARAKALEGACYDKIMEOF
echo "Haraka DKIM å·²é
ç½®ï¼selector=$DKIM_SELECTOR domain=$MAIL_DOMAIN"


cat > "$HARAKA_ROOT/config/log.ini" <<'HARAKALOGEOF'
[main]
level=NOTICE

[file]
enable=true
filename=/var/log/haraka/haraka.log
HARAKALOGEOF

cat > "$HARAKA_ROOT/config/tls.ini" <<'HARAKATLSEOF'
[main]
key=tls/key.pem
cert=tls/cert.pem
HARAKATLSEOF

cat > "$HARAKA_ROOT/config/smtp.ini" <<'HARAKASMTPEOF'
[main]
listen=0.0.0.0:25,0.0.0.0:587
banner=mail1.weicai168.com ESMTP
HARAKASMTPEOF

cat > "$HARAKA_ROOT/config/outbound.ini" <<'HARAKAOUTBOUNDEOF'
[main]
received_header=disabled
host=mail1.weicai168.com
hostname=mail1.weicai168.com
helo_host=mail1.weicai168.com
domain=mail1.weicai168.com
temp_fail_intervals=5m
HARAKAOUTBOUNDEOF


echo "æ­£å¨é
ç½® Haraka connection.iniï¼headers é­æ¹ï¼..."
CFG="$HARAKA_ROOT/config/connection.ini"
if [ ! -f "$CFG" ]; then
  touch "$CFG"
fi
if ! grep -q '^\[headers\]' "$CFG" 2>/dev/null; then
  printf '\n[headers]\n' >> "$CFG"
fi
if grep -q '^clean_auth_results=' "$CFG" 2>/dev/null; then
  sed -i 's/^clean_auth_results=.*/clean_auth_results=true/' "$CFG"
else
  sed -i '/^\[headers\]/a clean_auth_results=true' "$CFG"
fi
if grep -q '^show_version=' "$CFG" 2>/dev/null; then
  sed -i 's/^show_version=.*/show_version=false/' "$CFG"
else
  sed -i '/^\[headers\]/a show_version=false' "$CFG"
fi


echo "æ­£å¨é
ç½® Haraka TLS è¯ä¹¦ï¼OpenSSL èªç­¾åï¼..."
if [ -f "${TLS_DIR}/cert.pem" ] && [ -f "${TLS_DIR}/key.pem" ] && \
    openssl x509 -in "${TLS_DIR}/cert.pem" -noout -checkend 2592000 >/dev/null 2>&1; then
  echo "Haraka èªç­¾å TLS è¯ä¹¦ä»ææï¼è·³è¿çæ"
else
  echo "çæ $MAIL_HOSTNAME èªç­¾åè¯ä¹¦ï¼æææ 3650 å¤©ï¼..."

mkdir -p "$(dirname "${TLS_DIR}/cert.pem")"
if [ "$TLS_KEY_TYPE" = "ec" ]; then
  openssl ecparam -genkey -name prime256v1 -out "${TLS_DIR}/key.pem"
  openssl req -new -x509 -key "${TLS_DIR}/key.pem" -out "${TLS_DIR}/cert.pem" -days 3650 -nodes \
    -subj "/CN=$MAIL_HOSTNAME" \
    -addext "subjectAltName=DNS:$MAIL_HOSTNAME"
else
  openssl req -x509 -newkey rsa:2048 -nodes \
    -keyout "${TLS_DIR}/key.pem" -out "${TLS_DIR}/cert.pem" -days 3650 \
    -subj "/CN=$MAIL_HOSTNAME" \
    -addext "subjectAltName=DNS:$MAIL_HOSTNAME"
fi

chmod 644 "${TLS_DIR}/cert.pem"
chmod 600 "${TLS_DIR}/key.pem"

fi
if [ ! -f "${TLS_DIR}/cert.pem" ] || [ ! -f "${TLS_DIR}/key.pem" ]; then
  echo "éè¯¯ï¼Haraka èªç­¾å TLS è¯ä¹¦çæå¤±è´¥"
  exit 1
fi
echo "Haraka èªç­¾å TLS è¯ä¹¦å°±ç»ª"


cat > /etc/systemd/system/haraka.service <<HARAKAUNITEOF
[Unit]
Description=Haraka SMTP Server
After=network.target

[Service]
Type=simple
WorkingDirectory=/etc/haraka
ExecStart=$HARAKA_BIN -c /etc/haraka
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
HARAKAUNITEOF

systemctl daemon-reload
systemctl enable haraka
if ! systemctl restart haraka; then
  echo "éè¯¯ï¼haraka éå¯å¤±è´¥"
  systemctl --no-pager --full status haraka || true
  journalctl -u haraka --no-pager -n 120 || true
  exit 1
fi

_hk_waited=0
_hk_restarted=0
while ! ss -lntp | grep -qE ':25\b' || ! ss -lntp | grep -qE ':587\b'; do
  if [ "$_hk_waited" -ge 45 ]; then
    break
  fi
  if ! systemctl is-active --quiet haraka && [ "$_hk_restarted" -eq 0 ]; then
    systemctl restart haraka || true
    _hk_restarted=1
  fi
  sleep 2
  _hk_waited=$((_hk_waited+2))
done
if ! systemctl is-active --quiet haraka; then
  echo "éè¯¯ï¼haraka æå¡æªå¤äº active ç¶æï¼å·²ç­å¾
 ${_hk_waited}sï¼"
  systemctl --no-pager --full status haraka || true
  journalctl -u haraka --no-pager -n 120 || true
  exit 1
fi
if ! ss -lntp | grep -qE ':25\b' || ! ss -lntp | grep -qE ':587\b'; then
  echo "éè¯¯ï¼25/587 ç«¯å£æªçå¬ï¼å·²ç­å¾
 ${_hk_waited}sï¼ï¼Haraka å¯è½æªæ­£å¸¸å¯å¨"
  systemctl --no-pager --full status haraka || true
  journalctl -u haraka --no-pager -n 50 || true
  ss -lntp || true
  exit 1
fi
echo "Haraka æå¡å·²å¯å¨ï¼25/587 ç«¯å£æ­£å¨çå¬"
ss -lntp | grep -E ':25|:587' || true

echo "SMTP STARTTLS æ£æ¥ï¼587ï¼ï¼"
echo | openssl s_client -connect 127.0.0.1:587 -starttls smtp -servername "$MAIL_HOSTNAME" 2>/dev/null | grep -E 'Verify return code|subject=' || true

PUBLIC_IP="$(dig +short myip.opendns.com @resolver1.opendns.com || true)"
DKIM_PUBLIC="$(awk 'NF && $1 !~ /---/ { printf "%s", $0 }' "$DKIM_DIR/$DKIM_SELECTOR.pub")"

if [ -z "$INSTALL_PROOF" ]; then
  echo "éè¯¯ï¼ç¼ºå°å®è£
å­è¯ INSTALL_PROOF"
  exit 1
fi
echo "$INSTALL_PROOF" > /etc/haraka/.install_proof
chmod 600 /etc/haraka/.install_proof
echo "HARAKA_FULL_INSTALL_DONE proof=$INSTALL_PROOF"

echo "__KUMOMTA_RESULT__"
printf '{"public_ip":"%s","dkim_public":"%s","selector":"%s","install_proof":"%s"}\n' \
  "$PUBLIC_IP" "$DKIM_PUBLIC" "$DKIM_SELECTOR" "$INSTALL_PROOF"