#!/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
export MAIL_HELO MSGID_DOMAIN RECEIVED_MODE

DKIM_SELECTOR="default"
SMTP_AUTH_USER=admin@mail1.weicai168.com
SMTP_PASS=qwffHPPQ4966
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}"
POSTE_ROOT="/opt/poste-io"
POSTE_DATA="$POSTE_ROOT/data"

. /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 å¿è¦ç»ä»¶ï¼Poste.io é®å±ï¼..."

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 ca-certificates openssl dnsutils iproute2 python3 docker.io docker-compose-v2
systemctl enable --now docker


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



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


mkdir -p "$POSTE_DATA/ssl"
TLS_DIR="$POSTE_DATA/ssl"

echo "æ­£å¨éç½® Poste.io TLS è¯ä¹¦ï¼OpenSSL èªç­¾åï¼..."
if [ -f "${TLS_DIR}/server.crt" ] && [ -f "${TLS_DIR}/server.key" ] && \
    openssl x509 -in "${TLS_DIR}/server.crt" -noout -checkend 604800 >/dev/null 2>&1; then
  echo "Poste.io èªç­¾å TLS è¯ä¹¦ä»ææï¼è·³è¿çæ"
else
  echo "çæ $MAIL_HOSTNAME èªç­¾åè¯ä¹¦ï¼æææ 3650 å¤©ï¼..."

mkdir -p "$(dirname "${TLS_DIR}/server.crt")"
if [ "$TLS_KEY_TYPE" = "ec" ]; then
  openssl ecparam -genkey -name prime256v1 -out "${TLS_DIR}/server.key"
  openssl req -new -x509 -key "${TLS_DIR}/server.key" -out "${TLS_DIR}/server.crt" -days 3650 -nodes \
    -subj "/CN=$MAIL_HOSTNAME" \
    -addext "subjectAltName=DNS:$MAIL_HOSTNAME"
else
  openssl req -x509 -newkey rsa:2048 -nodes \
    -keyout "${TLS_DIR}/server.key" -out "${TLS_DIR}/server.crt" -days 3650 \
    -subj "/CN=$MAIL_HOSTNAME" \
    -addext "subjectAltName=DNS:$MAIL_HOSTNAME"
fi

cp -f "${TLS_DIR}/server.crt" "${TLS_DIR}/ca.crt"
chmod 644 "${TLS_DIR}/ca.crt"

chmod 644 "${TLS_DIR}/server.crt"
chmod 600 "${TLS_DIR}/server.key"

fi
if [ ! -f "${TLS_DIR}/server.crt" ] || [ ! -f "${TLS_DIR}/server.key" ]; then
  echo "éè¯¯ï¼Poste.io èªç­¾å TLS è¯ä¹¦çæå¤±è´¥"
  exit 1
fi
echo "Poste.io èªç­¾å TLS è¯ä¹¦å°±ç»ª"


mkdir -p "$POSTE_ROOT"
cat > "$POSTE_ROOT/docker-compose.yml" <<'POSTECOMPOSE'
services:
  poste_io:
    image: analogic/poste.io:2
    container_name: poste_io
    hostname: POSTE_HOSTNAME_PLACEHOLDER
    restart: unless-stopped
    network_mode: host
    environment:
      - TZ=Etc/UTC
      - DISABLE_CLAMAV=TRUE
    volumes:
      - ./data:/data
POSTECOMPOSE
sed -i "s|POSTE_HOSTNAME_PLACEHOLDER|$MAIL_HOSTNAME|g" "$POSTE_ROOT/docker-compose.yml"
echo "æ­£å¨å®è£ Poste.io Haraka é­æ¹å¤´æä»¶ï¼privacy å¢å¼ºï¼dkim_sign ä¹åæ§è¡ï¼..."
cat > "$POSTE_ROOT/privacy.js" <<'POSTEPRIVJS'
'use strict';
const punycode = require('punycode');
let cfg;
const SQ_STRIP = ['x-\u6df1\u60c5','x-kumoref','x-mailer-trace','x-mailer','x-php-originating-script','message-id','list-unsubscribe', 'list-unsubscribe-post'];
const SQ_EHLO = ['orico.co.jp','rakuten.co.jp','sony.co.jp','nifty.com','biglobe.ne.jp','so-net.ne.jp'];
const SQ_LABELS = ['Customer Service','Mailer'];
const SQ_RX = /kumomta|zonemta|maddy|opensmtpd|openbsd|exim|postfix|haraka|localhost|127\.0\.0\.1|\[::1\]|esmtpsa/i;
const SQ_REWRITE_RECEIVED = true;
exports.register = function () { this.loadConfig(); };
exports.loadConfig = function () {
    const plugin = this;
    cfg = plugin.config.get('privacy.ini','ini',function(){plugin.loadConfig();},
        {booleans:['-remove_user_agent','+remove_x_headers','+set_received_from_to_localhost']});
};
exports.hook_queue_outbound = async function (next, connection) {
    const plugin = this;
    const txn = connection.transaction;
    if (connection.relaying !== true) { return next(); }
    try {
        const domain = await plugin.get_domain(txn.rcpt_to[0].host.toLowerCase());
        if (domain) { return next(); }
    } catch (e) { plugin.logdebug(txn, e); }
    if (cfg.main.remove_user_agent && txn.header.get('User-Agent')) { txn.remove_header('User-Agent'); }
    if (cfg.main.remove_x_headers) {
        for (let key in txn.header.headers_decoded) {
            if (key.startsWith('x-haraka')||key.startsWith('x-rspamd')||key.startsWith('x-p0f')||key.startsWith('x-sieve')||key.startsWith('x-test')) { txn.remove_header(key); }
        }
    }
    for (const h of SQ_STRIP) { txn.remove_header(h); }
    if (SQ_REWRITE_RECEIVED) {
    let receiveds = txn.header.get_all('Received');
    txn.remove_header('Received');
    let hn = String(plugin.config.get('me') || 'localhost').trim() || 'localhost';
    for (let line of receiveds) {
        let v = String(line);
        if (SQ_RX.test(v.replace(/\s+/g,' '))) {
            const dom = SQ_EHLO[Math.floor(Math.random()*SQ_EHLO.length)];
            const lab = SQ_LABELS[Math.floor(Math.random()*SQ_LABELS.length)];
            v = 'from ' + dom + ' (127.0.0.1) by ' + hn + ' (' + lab + ') with ESMTP; ' + new Date().toUTCString();
        }
        txn.add_header('Received', v);
    }
    }
    return next();
};
exports.get_domain = function (domain) {
    return new Promise((resolve) => {
        server.notes.poste.get_domain(domain, function (err, row) {
            if (!row && punycode.toASCII(domain) !== domain) {
                server.notes.poste.get_domain(punycode.toASCII(domain), function (err, row) { return resolve(row); });
            } else { return resolve(row); }
        });
    });
};
POSTEPRIVJS
if ! grep -q "privacy.js:/opt/haraka-submission" "$POSTE_ROOT/docker-compose.yml"; then
  sed -i "s#\(- \./data:/data\)#\1\n      - ./privacy.js:/opt/haraka-submission/plugins/privacy.js:ro\n      - ./privacy.js:/opt/haraka-smtp/plugins/privacy.js:ro#" "$POSTE_ROOT/docker-compose.yml"
fi

cd "$POSTE_ROOT"
run_docker_compose_quiet "Poste.io" docker compose

echo "ç­å¾ Poste.io å®¹å¨å¯å¨..."
for attempt in $(seq 1 60); do
  if docker compose ps --status running 2>/dev/null | grep -q poste_io; then
    break
  fi
  sleep 3
done


poste_wait_ready() {
  local attempt=1
  while [ "$attempt" -le 200 ]; do
    if [ "$(docker inspect -f '{{.State.Health.Status}}' poste_io 2>/dev/null || echo none)" = "healthy" ]; then
      return 0
    fi
    if curl -4 -ksS --max-time 6 "https://127.0.0.1/admin/login" -o /dev/null 2>/dev/null; then
      return 0
    fi
    if curl -4 -ksS --max-time 6 "https://$MAIL_HOSTNAME/admin/login" -o /dev/null 2>/dev/null; then
      return 0
    fi
    if [ $((attempt % 15)) -eq 0 ]; then echo "  waiting Poste.io admin API ($attempt/200)..."; fi
    sleep 3
    attempt=$((attempt + 1))
  done
  echo "éè¯¯ï¼Poste.io ç®¡ççé¢æªå°±ç»ª"
  return 1
}

poste_api_base() {
  if curl -4 -ksS --max-time 5 "https://127.0.0.1/admin/login" -o /dev/null 2>/dev/null; then
    echo "https://127.0.0.1/admin"
    return 0
  fi
  echo "https://$MAIL_HOSTNAME/admin"
}

poste_install_if_needed() {
  local api
  api="$(poste_api_base)"
  local install_code
  install_code="$(curl -4 -ksS --max-time 10 -o /dev/null -w '%{http_code}' "$api/install/server" || true)"
  if [ "$install_code" = "200" ]; then
    echo "æ­£å¨åå§å Poste.io ç®¡çå..."
    curl -4 -ksS --max-time 60 "$api/install/server" \
      -H 'Content-Type: application/x-www-form-urlencoded' \
      --data-raw "install%5Bhostname%5D=${MAIL_HOSTNAME}&install%5BsuperAdmin%5D=${SMTP_AUTH_USER}&install%5BsuperAdminPassword%5D=${SMTP_PASS}" \
      >/dev/null || true
    sleep 2
  fi
}

poste_ensure_domain_and_box() {
  local api auth
  api="$(poste_api_base)"
  auth="${SMTP_AUTH_USER}:${SMTP_PASS}"
  echo "æ­£å¨éç½® Poste.io ååä¸é®ç®±..."
  curl -4 -ksS --max-time 30 --user "$auth" -X POST "$api/api/v1/domains" \
    --data "name=${MAIL_DOMAIN}" >/dev/null 2>&1 || true
  docker exec poste_io poste domain:create "$MAIL_DOMAIN" >/dev/null 2>&1 || true
  curl -4 -ksS --max-time 30 --user "$auth" -X POST "$api/api/v1/boxes" \
    --data "email=${SMTP_AUTH_USER}&passwordPlaintext=${SMTP_PASS}&disabled=false&superAdmin=1" >/dev/null 2>&1 || true
  docker exec poste_io poste email:create "$SMTP_AUTH_USER" "$SMTP_PASS" >/dev/null 2>&1 || true
  docker exec poste_io poste email:admin "$SMTP_AUTH_USER" >/dev/null 2>&1 || true
}

poste_ensure_dkim() {
  local api auth dkim_json
  api="$(poste_api_base)"
  auth="${SMTP_AUTH_USER}:${SMTP_PASS}"
  echo "æ­£å¨çæ/è¯»å Poste.io DKIM..."
  docker exec poste_io poste domain:dkim --create "$MAIL_DOMAIN" >/dev/null 2>&1 || true
  dkim_json="$(docker exec poste_io poste domain:dkim --json "$MAIL_DOMAIN" 2>/dev/null || true)"
  if [ -z "$dkim_json" ]; then
    dkim_json="$(curl -4 -ksS --max-time 30 --user "$auth" "$api/api/v1/domains/${MAIL_DOMAIN}/dkim" || true)"
  fi
  if [ -z "$dkim_json" ]; then
    echo "è­¦åï¼æ æ³è¯»å Poste.io DKIM éç½®ï¼è·³è¿ DKIM åå¸"
    return 0
  fi
  export DKIM_JSON="$dkim_json"
  DKIM_SELECTOR="$(DKIM_JSON="$dkim_json" python3 - <<'PY'
import json, os, sys
raw = os.environ.get("DKIM_JSON", "")
try:
    data = json.loads(raw)
except json.JSONDecodeError:
    sys.exit(1)
if isinstance(data, dict) and isinstance(data.get("dkim"), dict):
    data = data["dkim"]
selector = str(data.get("selector") or "").strip()
if not selector:
    sys.exit(2)
print(selector)
PY
)"
  DKIM_PUBLIC="$(DKIM_JSON="$dkim_json" python3 - <<'PY'
import json, os, re, sys
raw = os.environ.get("DKIM_JSON", "")
try:
    data = json.loads(raw)
except json.JSONDecodeError:
    sys.exit(1)
if isinstance(data, dict) and isinstance(data.get("dkim"), dict):
    data = data["dkim"]
public = data.get("public")
if isinstance(public, str):
    text = public.strip()
elif isinstance(public, list):
    text = "".join(str(item) for item in public if item)
else:
    text = ""
text = re.sub(r"-----BEGIN[^-]+-----", "", text)
text = re.sub(r"-----END[^-]+-----", "", text)
text = re.sub(r"[^A-Za-z0-9+/=]", "", text)
if not text:
    sys.exit(2)
print(text)
PY
)"
  if [ -z "$DKIM_SELECTOR" ] || [ -z "$DKIM_PUBLIC" ]; then
    echo "è­¦åï¼è§£æ Poste.io DKIM å¬é¥å¤±è´¥ï¼è·³è¿ DKIM åå¸"
    return 0
  fi
  export DKIM_SELECTOR DKIM_PUBLIC
}

poste_locate_dkim_key() {
  local base="${POSTE_DATA:-/opt/poste-io/data}/dkim"
  [ -d "$base" ] || base="/opt/poste-io/data/dkim"
  local f
  POSTE_DKIM_KEYFILE=""
  while IFS= read -r f; do
    if openssl rsa -in "$f" -noout >/dev/null 2>&1; then
      POSTE_DKIM_KEYFILE="$f"
      return 0
    fi
  done < <(find "$base" -path "*$MAIL_DOMAIN*" -type f 2>/dev/null)
  return 1
}

poste_force_our_dkim() {
  if ! poste_locate_dkim_key; then
    echo "[DKIM] æªå®ä½å° Poste.io ç§é¥æä»¶ï¼ä¿çå¶èªå¸¦ DKIMï¼ä»æçå® selector åå¸ DNSï¼"
    return 0
  fi
  local kf="$POSTE_DKIM_KEYFILE" tmp pub
  tmp="$(mktemp)"
  if ! openssl genrsa -f4 -out "$tmp" 2048 >/dev/null 2>&1; then
    echo "[DKIM] çæé¢æ¿ç§é¥å¤±è´¥ï¼ä¿ç Poste.io èªå¸¦å¯é¥"
    rm -f "$tmp"
    return 0
  fi
  cp -p "$kf" "$kf.poste-orig" 2>/dev/null || true
  if ! cat "$tmp" > "$kf" 2>/dev/null; then
    echo "[DKIM] åå¥é¢æ¿ç§é¥å¤±è´¥ï¼ä¿ç Poste.io èªå¸¦å¯é¥"
    rm -f "$tmp"
    return 0
  fi
  pub="$(dirname "$kf")/public"
  if [ -f "$pub" ]; then
    openssl rsa -in "$tmp" -pubout > "$pub" 2>/dev/null || true
  fi
  rm -f "$tmp"
  echo "[DKIM] å·²ç¨é¢æ¿ç§é¥è¦ç Poste.io DKIM ç§é¥ï¼$kf"
}

poste_refresh_dkim_public_from_disk() {
  if [ -n "${POSTE_DKIM_KEYFILE:-}" ] && [ -f "${POSTE_DKIM_KEYFILE:-}" ]; then
    local p
    p="$(openssl rsa -in "$POSTE_DKIM_KEYFILE" -pubout 2>/dev/null | grep -v '^-----' | tr -d '\n' || true)"
    if [ -n "$p" ]; then
      DKIM_PUBLIC="$p"
      export DKIM_PUBLIC
      echo "[DKIM] DNS å¬é¥å·²å¯¹é½ç£çå®éç§é¥ï¼selector=$DKIM_SELECTORï¼"
    fi
  fi
}

poste_wait_ready
poste_install_if_needed
poste_ensure_domain_and_box
poste_ensure_dkim
poste_force_our_dkim

docker compose restart poste_io || true
echo "Waiting for Poste.io to become ready after restart (first boot is slow)..."
_poste_up=0
for attempt in $(seq 1 200); do
  if [ "$(docker inspect -f '{{.State.Health.Status}}' poste_io 2>/dev/null || echo none)" = "healthy" ]; then _poste_up=1; break; fi
  if ss -lntp 2>/dev/null | grep -qE ':587'; then _poste_up=1; break; fi
  if [ $((attempt % 15)) -eq 0 ]; then echo "  waiting Poste.io ready ($attempt/200)..."; fi
  sleep 3
done
poste_refresh_dkim_public_from_disk
ss -lntp | grep -E ':25|:587|:443' || true
if [ "$_poste_up" != "1" ]; then
  echo "ERROR: Poste.io did not become ready in time"
  docker logs poste_io 2>&1 | tail -30 || true
  exit 1
fi

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
echo "Poste.io ç®¡ççé¢ï¼https://$MAIL_HOSTNAME/admin ï¼$SMTP_AUTH_USERï¼"

PUBLIC_IP="$(dig +short myip.opendns.com @resolver1.opendns.com || true)"

echo "__KUMOMTA_RESULT__"
printf '{"public_ip":"%s","dkim_public":"%s","selector":"%s"}\n' "$PUBLIC_IP" "$DKIM_PUBLIC" "$DKIM_SELECTOR"
