#!/usr/bin/env bash
# 根据本机 IP 自动：证书、Nginx、Tomcat server.xml、keytool、业务配置（application.properties 等）、重启
# 适用于笔记 a.txt 中的路径约定；可通过环境变量覆盖路径

set -euo pipefail

# ---------- 日志（控制台；可选写入文件：export DEPLOY_LOG=/var/log/deploy_https.log）----------
log() {
  local level="$1"
  shift
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] $*"
}

# 若设置 DEPLOY_LOG，则同时追加到文件（需对目录有写权限）
if [[ -n "${DEPLOY_LOG:-}" ]]; then
  _log_dir=$(dirname -- "${DEPLOY_LOG}")
  mkdir -p "${_log_dir}" 2>/dev/null || true
  touch "${DEPLOY_LOG}" 2>/dev/null || true
  exec > >(tee -a "${DEPLOY_LOG}") 2>&1
  log "INFO" "---------- 会话开始，日志文件: ${DEPLOY_LOG} ----------"
fi

# ---------- 可配置路径（与 a.txt 一致，可按环境 export 覆盖）----------
: "${OPENRESTY_PREFIX:=/usr/local/openresty}"
: "${NGINX_CONF_DIR:=${OPENRESTY_PREFIX}/nginx/conf}"
: "${NGINX_CONF:=${NGINX_CONF_DIR}/nginx.conf}"
: "${TOMCAT_HOME:=/usr/local/apache-tomcat-9.0.96}"
: "${TOMCAT_SERVER_XML:=${TOMCAT_HOME}/conf/server.xml}"
: "${TOMCAT_RESTART:=${TOMCAT_HOME}/restart.sh}"
: "${JDK_HOME:=/usr/local/jdk1.8.0_401}"
: "${JAVA_CACERTS:=${JDK_HOME}/jre/lib/security/cacerts}"
# OpenResty 主进程 PID 文件（未运行时 reload 会报 kill … No such process）
: "${OPENRESTY_PID_FILE:=${OPENRESTY_PREFIX}/nginx/logs/nginx.pid}"
# 业务配置（a.txt：证书导入后）
: "${APP_PROPERTIES:=/usr/local/server/config/application.properties}"
: "${IP_CONFIG_FILE:=/usr/local/ipConfig.txt}"
: "${SERVER_STARTUP_SH:=/usr/local/server/bin/startup.sh}"

# ---------- 获取本机用于对外的 IPv4（动态）----------
detect_ip() {
  local ip
  ip=$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for (i=1;i<=NF;i++) if ($i=="src") {print $(i+1); exit}}')
  if [[ -z "${ip}" ]]; then
    ip=$(hostname -I 2>/dev/null | awk '{print $1}')
  fi
  if [[ -z "${ip}" ]]; then
    log "FAIL" "检测本机 IPv4 失败，请手动设置: export SERVER_IP=你的IP"
    exit 1
  fi
  echo "$ip"
}

log "INFO" "正在检测本机 IP …"
if [[ -n "${SERVER_IP:-}" ]]; then
  log "INFO" "已使用环境变量 SERVER_IP=${SERVER_IP}（跳过自动检测）"
  log "OK" "本机 IP 已确定: ${SERVER_IP}"
else
  SERVER_IP="$(detect_ip)" || exit 1
  log "OK" "检测本机 IP 完成: ${SERVER_IP}"
fi

KEY_PEM="${NGINX_CONF_DIR}/key.pem"
CERT_PEM="${NGINX_CONF_DIR}/cert.pem"
CERT_CRT="${NGINX_CONF_DIR}/${SERVER_IP}.crt"

# ---------- 1. 生成自签名证书（CN 使用当前本机 IP）----------
log "INFO" "正在生成自签名证书（CN=${SERVER_IP}）…"
if ! openssl req -x509 -newkey rsa:2048 \
  -keyout "${KEY_PEM}" \
  -out "${CERT_PEM}" \
  -days 365 -nodes \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=Company/CN=${SERVER_IP}"; then
  log "FAIL" "生成证书失败（openssl req）"
  exit 1
fi
if ! openssl x509 -in "${CERT_PEM}" -out "${CERT_CRT}" -outform PEM; then
  log "FAIL" "导出 .crt 失败（openssl x509）"
  exit 1
fi
chmod 600 "${KEY_PEM}" 2>/dev/null || true
chmod 644 "${CERT_PEM}" "${CERT_CRT}" 2>/dev/null || true
log "OK" "证书生成完成: ${CERT_PEM}, ${KEY_PEM}, ${CERT_CRT}"

# ---------- 2. 修改 nginx.conf：仅 listen 443 的 server 块内改 server_name；仅其下 location / 内改 proxy_pass ----------
if [[ ! -f "${NGINX_CONF}" ]]; then
  log "FAIL" "未找到 nginx 配置: ${NGINX_CONF}"
  exit 1
fi
log "INFO" "正在修改 nginx（443 的 server：server_name；其中 location / 或 location = /：proxy_pass → http://${SERVER_IP}:80）…"
cp -a "${NGINX_CONF}" "${NGINX_CONF}.bak.$(date +%Y%m%d%H%M%S)"
log "INFO" "已备份: ${NGINX_CONF}.bak.*"

if ! python3 - "${NGINX_CONF}" "${SERVER_IP}" <<'PY'
import re
import sys

path, sip = sys.argv[1], sys.argv[2]
with open(path, "r", encoding="utf-8", errors="replace") as f:
    content = f.read()


def find_closing_brace(s: str, open_idx: int) -> int:
    """open_idx 指向 '{'; 跳过行内 # 注释后的大括号配对"""
    depth = 0
    i = open_idx
    n = len(s)
    while i < n:
        if s[i] == "#":
            nl = s.find("\n", i)
            if nl == -1:
                break
            i = nl + 1
            continue
        if s[i] == "{":
            depth += 1
        elif s[i] == "}":
            depth -= 1
            if depth == 0:
                return i
        i += 1
    return -1


def line_has_listen_443(line: str) -> bool:
    line = line.split("#")[0].strip()
    if not re.search(r"listen\s+", line, re.I):
        return False
    # 端口 443（\b 避免误认 8443 中的 443）
    return bool(re.search(r"\b443\b", line))


def inner_has_listen_443(inner: str) -> bool:
    for line in inner.splitlines():
        if line_has_listen_443(line):
            return True
    return False


LOC_ROOT = re.compile(
    r"location\s*=\s*/\s*\{|location\s+/\s*\{",
    re.I,
)
# 整行替换到第一个 ; 为止，不解析后面是域名、路径或何种端口
SERVER_NAME_LINE = re.compile(r"^(\s*)server_name\b[^;]*;", re.MULTILINE | re.I)
PROXY_PASS_LINE = re.compile(r"^(\s*)proxy_pass\b[^;]*;", re.MULTILINE | re.I)


def patch_location_root_proxy(inner: str) -> str:
    for m in list(LOC_ROOT.finditer(inner))[::-1]:
        ob = m.end() - 1
        if ob < 0 or ob >= len(inner) or inner[ob] != "{":
            continue
        cb = find_closing_brace(inner, ob)
        if cb < 0:
            continue
        block = inner[ob : cb + 1]
        new_block = PROXY_PASS_LINE.sub(rf"\1proxy_pass http://{sip}:80;", block)
        inner = inner[:ob] + new_block + inner[cb + 1 :]
    return inner


def patch_server_inner_443(inner: str) -> str:
    inner = SERVER_NAME_LINE.sub(rf"\1server_name {sip};", inner)
    inner = patch_location_root_proxy(inner)
    return inner


def process(content: str) -> str:
    out = []
    pos = 0
    while pos < len(content):
        m = re.search(r"\bserver\s*\{", content[pos:], re.I)
        if not m:
            out.append(content[pos:])
            break
        abs_start = pos + m.start()
        open_brace = pos + m.end() - 1
        close_brace = find_closing_brace(content, open_brace)
        if close_brace < 0:
            out.append(content[pos:])
            break
        out.append(content[pos:abs_start])
        inner = content[open_brace + 1 : close_brace]
        if inner_has_listen_443(inner):
            inner2 = patch_server_inner_443(inner)
            out.append(content[abs_start : open_brace + 1] + inner2 + content[close_brace : close_brace + 1])
        else:
            out.append(content[abs_start : close_brace + 1])
        pos = close_brace + 1
    return "".join(out)


newc = process(content)
with open(path, "w", encoding="utf-8", newline="\n") as f:
    f.write(newc)
print("OK")
PY
then
  log "FAIL" "修改 ${NGINX_CONF} 失败（python3）"
  exit 1
fi
if grep -qE "server_name[[:space:]]+${SERVER_IP}" "${NGINX_CONF}"; then
  log "OK" "nginx 已写入 listen 443 的 server_name；校验：${NGINX_CONF} 内能检索到 server_name ${SERVER_IP}"
else
  log "WARN" "nginx 本步未改写 server_name（未配置 listen 443 的 server、或改写后仍无 ${SERVER_IP}），请检查 ${NGINX_CONF}"
fi
if grep -q "proxy_pass http://${SERVER_IP}:80" "${NGINX_CONF}"; then
  log "OK" "nginx 已写入 443 server·location / 的 proxy_pass；校验：文件中能检索到 http://${SERVER_IP}:80"
else
  log "WARN" "nginx 本步未得到预期 proxy_pass 行（缺少 listen 443、或缺少 location / / proxy_pass），请检查 ${NGINX_CONF}"
fi

# ---------- 3. 修改 Tomcat server.xml：删除全部 Connector（含已有 80），再插入 a.txt 固定片段 ----------
if [[ ! -f "${TOMCAT_SERVER_XML}" ]]; then
  log "FAIL" "未找到 Tomcat 配置: ${TOMCAT_SERVER_XML}"
  exit 1
fi
log "INFO" "正在修改 Tomcat server.xml（移除全部 Connector 后插入 a.txt 标准 80 端口段）…"
log "INFO" "若 server.xml 曾被旧版脚本写坏，请先用备份覆盖后再运行: ls ${TOMCAT_SERVER_XML}.bak.*"
cp -a "${TOMCAT_SERVER_XML}" "${TOMCAT_SERVER_XML}.bak.$(date +%Y%m%d%H%M%S)"
log "INFO" "已备份: ${TOMCAT_SERVER_XML}.bak.*"

if ! python3 - "$TOMCAT_SERVER_XML" <<'PY'
import re
import sys

path = sys.argv[1]
with open(path, "r", encoding="utf-8", errors="replace") as f:
    xml = f.read()

# XML 注释内不能出现「--」，不可用 <!-- --> 包 Connector。
# 做法：非注释区内删除「所有」Connector（含原任意 80 端口），再插入笔记 a.txt 24–27 行标准段。

NEW_CONN = """
   <Connector port="80" protocol="HTTP/1.1"
           connectionTimeout="20000"
           maxParameterCount="1000"
           />
"""


def strip_connectors_outside_comments(src: str) -> str:
    out = []
    i = 0
    n = len(src)
    while i < n:
        if src.startswith("<!--", i):
            j = src.find("-->", i + 4)
            if j == -1:
                out.append(src[i:])
                break
            out.append(src[i : j + 3])
            i = j + 3
            continue
        if src.startswith("<Connector", i):
            k = src.find("/>", i)
            if k != -1:
                # 删除该块（含原 80 等），稍后统一插入 a.txt 标准段
                i = k + 2
                continue
            lc = src.lower().find("</connector>", i)
            if lc != -1:
                i = lc + len("</Connector>")
                continue
        out.append(src[i])
        i += 1
    return "".join(out)


xml_new = strip_connectors_outside_comments(xml)

# 去掉说明性注释后，若已无未注释的 Connector，则在第一个 <Service> 后插入固定 80 段
body_wo_comments = re.sub(r"<!--[\s\S]*?-->", "", xml_new)
if not re.search(r"<Connector\b", body_wo_comments, re.I):
    xml_new = re.sub(
        r"(<Service\s[^>]*>)",
        r"\1" + NEW_CONN,
        xml_new,
        count=1,
        flags=re.I,
    )

with open(path, "w", encoding="utf-8", newline="\n") as f:
    f.write(xml_new)
print("OK")
PY
then
  log "FAIL" "修改 server.xml 失败（python3 执行出错）"
  exit 1
fi
log "OK" "修改 Tomcat server.xml 完成"

# ---------- 4. 将证书导入 JDK cacerts（默认口令 changeit；非交互）----------
KEYTOOL_ALIAS="srv-$(echo "${SERVER_IP}" | tr '.' '_')"
if [[ -f "${JAVA_CACERTS}" ]]; then
  log "INFO" "正在将证书导入 JDK cacerts（alias=${KEYTOOL_ALIAS}）…"
  if keytool -importcert -noprompt \
    -alias "${KEYTOOL_ALIAS}" \
    -keystore "${JAVA_CACERTS}" \
    -storepass changeit \
    -file "${CERT_CRT}"; then
    log "OK" "证书导入 cacerts 完成"
  else
    log "WARN" "keytool 导入未成功（若提示证书已存在，可先删除别名: keytool -delete -alias ${KEYTOOL_ALIAS} -keystore ${JAVA_CACERTS} -storepass changeit）"
  fi
else
  log "WARN" "未找到 ${JAVA_CACERTS}，跳过 keytool 导入"
fi

# ---------- 5. 业务文件：base.url、ipConfig.txt、startup.sh 中的 JAVA_HOME（a.txt）----------
if [[ -f "${APP_PROPERTIES}" ]]; then
  log "INFO" "正在修改 ${APP_PROPERTIES}（base.url → http://${SERVER_IP}）…"
  cp -a "${APP_PROPERTIES}" "${APP_PROPERTIES}.bak.$(date +%Y%m%d%H%M%S)"
  if grep -qE '^[[:space:]]*base\.url[[:space:]]*=' "${APP_PROPERTIES}"; then
    sed -i -E "s|^[[:space:]]*base\.url[[:space:]]*=.*|base.url = http://${SERVER_IP}|" "${APP_PROPERTIES}"
  else
    printf '\nbase.url = http://%s\n' "${SERVER_IP}" >> "${APP_PROPERTIES}"
  fi
  log "OK" "已写入 base.url = http://${SERVER_IP}"
else
  log "WARN" "未找到 ${APP_PROPERTIES}，跳过 base.url"
fi

if [[ -f "${IP_CONFIG_FILE}" ]]; then
  log "INFO" "正在将 ${IP_CONFIG_FILE} 第一行改为本机 IP …"
  cp -a "${IP_CONFIG_FILE}" "${IP_CONFIG_FILE}.bak.$(date +%Y%m%d%H%M%S)"
  sed -i "1s|.*|${SERVER_IP}|" "${IP_CONFIG_FILE}"
  log "OK" "已更新 ${IP_CONFIG_FILE} 第一行为 ${SERVER_IP}"
else
  log "WARN" "未找到 ${IP_CONFIG_FILE}，跳过"
fi

if [[ -f "${SERVER_STARTUP_SH}" ]]; then
  log "INFO" "正在设置 ${SERVER_STARTUP_SH} 中 JAVA_HOME=${JDK_HOME} …"
  cp -a "${SERVER_STARTUP_SH}" "${SERVER_STARTUP_SH}.bak.$(date +%Y%m%d%H%M%S)"
  if grep -qE '^[[:space:]]*export[[:space:]]+JAVA_HOME=' "${SERVER_STARTUP_SH}"; then
    sed -i -E "s|^[[:space:]]*export[[:space:]]+JAVA_HOME=.*|export JAVA_HOME=\"${JDK_HOME}\"|" "${SERVER_STARTUP_SH}"
  else
    if head -1 "${SERVER_STARTUP_SH}" | grep -q '^#!'; then
      sed -i "1a export JAVA_HOME=\"${JDK_HOME}\"" "${SERVER_STARTUP_SH}"
    else
      sed -i "1i export JAVA_HOME=\"${JDK_HOME}\"" "${SERVER_STARTUP_SH}"
    fi
  fi
  log "OK" "已设置 export JAVA_HOME=\"${JDK_HOME}\""
else
  log "WARN" "未找到 ${SERVER_STARTUP_SH}，跳过 JAVA_HOME"
fi

# ---------- 6. 重启 Tomcat、重载 OpenResty ----------
# 注意：许多自定义 restart.sh 里写的是 ./stop.sh、./start.sh，必须在脚本所在目录执行，
# 否则当前目录若为 ~ 会报「没有那个文件或目录」。
TOMCAT_RESTART_DIR=$(dirname -- "${TOMCAT_RESTART}")
TOMCAT_RESTART_BASE=$(basename -- "${TOMCAT_RESTART}")

restart_tomcat_with_cwd() {
  if [[ ! -f "${TOMCAT_RESTART}" ]]; then
    return 1
  fi
  log "INFO" "正在重启 Tomcat: 进入 ${TOMCAT_RESTART_DIR} 后执行 ./${TOMCAT_RESTART_BASE} …"
  ( cd "${TOMCAT_RESTART_DIR}" && bash "./${TOMCAT_RESTART_BASE}" )
}

restart_tomcat_official() {
  local shut start
  shut="${TOMCAT_HOME}/bin/shutdown.sh"
  start="${TOMCAT_HOME}/bin/startup.sh"
  if [[ ! -x "${shut}" ]] || [[ ! -x "${start}" ]]; then
    return 1
  fi
  log "INFO" "正在执行 ${shut}（忽略未运行时的错误）…"
  "${shut}" 2>/dev/null || true
  sleep 3
  log "INFO" "正在执行 ${start} …"
  "${start}"
}

if [[ -f "${TOMCAT_RESTART}" ]]; then
  if restart_tomcat_with_cwd; then
    log "OK" "Tomcat 重启完成（${TOMCAT_RESTART_BASE}）"
  else
    log "WARN" "自定义 restart 脚本未成功，尝试 Tomcat 自带 shutdown.sh / startup.sh …"
    if restart_tomcat_official; then
      log "OK" "Tomcat 已通过 bin/shutdown.sh + bin/startup.sh 重启完成"
    else
      log "FAIL" "Tomcat 重启失败（请确认 ${TOMCAT_RESTART_DIR} 下存在 start.sh、stop.sh，且与 restart.sh 同级）"
      exit 1
    fi
  fi
else
  log "WARN" "未找到 ${TOMCAT_RESTART}，尝试官方脚本 …"
  if restart_tomcat_official; then
    log "OK" "Tomcat 已通过官方脚本重启完成"
  else
    log "WARN" "未找到可用的 Tomcat 重启方式，请手动重启"
  fi
fi

reload_or_start_openresty() {
  local or_bin="${OPENRESTY_PREFIX}/bin/openresty"
  local pid=""
  log "INFO" "检测 OpenResty 配置（${or_bin} -t）…"
  if ! "${or_bin}" -t; then
    log "FAIL" "OpenResty 配置检测失败，请修正 nginx 配置后再执行"
    return 1
  fi
  log "OK" "OpenResty 配置检测通过"

  if [[ -f "${OPENRESTY_PID_FILE}" ]]; then
    pid=$(tr -d ' \t\r\n' < "${OPENRESTY_PID_FILE}" 2>/dev/null || true)
  fi
  if [[ -n "${pid}" ]] && kill -0 "${pid}" 2>/dev/null; then
    log "INFO" "OpenResty 已在运行（PID ${pid}，${OPENRESTY_PID_FILE}），正在重载 …"
    if "${or_bin}" -s reload; then
      log "OK" "OpenResty 重载完成"
      return 0
    fi
    log "WARN" "重载失败，将尝试启动（若端口被占用请先人工处理）…"
  else
    log "INFO" "OpenResty 未运行或 PID 无效（${OPENRESTY_PID_FILE}），将启动 …"
  fi
  if "${or_bin}"; then
    log "OK" "OpenResty 已启动"
    return 0
  fi
  log "FAIL" "OpenResty 启动失败（若提示 Address already in use，说明已有 nginx 占用端口，可检查 pid 路径是否需设 OPENRESTY_PID_FILE）"
  return 1
}

if [[ -x "${OPENRESTY_PREFIX}/bin/openresty" ]]; then
  if ! reload_or_start_openresty; then
    exit 1
  fi
else
  log "WARN" "未找到 ${OPENRESTY_PREFIX}/bin/openresty，请手动启动或重载"
fi

log "OK" "全部步骤结束（本机 IP=${SERVER_IP}）"
