#!/usr/bin/env bash set -euo pipefail DOMAIN="${DOMAIN:-${1:-}}" UPSTREAM="${UPSTREAM:-http://127.0.0.1:3000}" EMAIL="${EMAIL:-}" ENGINE="${ENGINE:-auto}" FORCE_REPLACE_WEBSERVER="${FORCE_REPLACE_WEBSERVER:-false}" WEBROOT_ROOT="${WEBROOT_ROOT:-/var/www/aiapi-market-acme}" NGINX_CONF_DIR="${NGINX_CONF_DIR:-}" CADDYFILE="${CADDYFILE:-/etc/caddy/Caddyfile}" log() { printf '\033[1;34m[aiapi-https]\033[0m %s\n' "$*" } warn() { printf '\033[1;33m[aiapi-https]\033[0m %s\n' "$*" } fail() { printf '\033[1;31m[aiapi-https]\033[0m %s\n' "$*" >&2 exit 1 } need_cmd() { command -v "$1" >/dev/null 2>&1 } as_root() { if [ "$(id -u)" -eq 0 ]; then "$@" elif need_cmd sudo; then sudo "$@" else fail "请使用 root 或安装 sudo 后再运行。" fi } require_domain() { if [ -z "${DOMAIN}" ]; then fail "请指定域名,例如:DOMAIN=api.aiapi.market bash install-aiapi-market-https.sh" fi if ! printf '%s' "${DOMAIN}" | grep -Eq '^[A-Za-z0-9.-]+$'; then fail "域名格式不合法:${DOMAIN}" fi } detect_os() { if [ ! -f /etc/os-release ]; then fail "暂只支持常见 Linux 发行版。" fi # shellcheck disable=SC1091 . /etc/os-release OS_ID="${ID:-}" OS_LIKE="${ID_LIKE:-}" } install_packages() { if need_cmd apt-get; then as_root apt-get update as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y "$@" elif need_cmd dnf; then as_root dnf install -y "$@" elif need_cmd yum; then as_root yum install -y "$@" else fail "未找到 apt-get/dnf/yum,无法自动安装依赖。" fi } install_certbot() { if need_cmd certbot; then return fi log "安装 Certbot。" install_packages certbot ca-certificates curl } install_caddy() { if need_cmd caddy; then return fi log "安装 Caddy。" if need_cmd apt-get; then install_packages debian-keyring debian-archive-keyring apt-transport-https curl ca-certificates curl -fsSL https://dl.cloudsmith.io/public/caddy/stable/gpg.key \ | as_root tee /usr/share/keyrings/caddy-stable-archive-keyring.gpg >/dev/null curl -fsSL https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt \ | as_root tee /etc/apt/sources.list.d/caddy-stable.list >/dev/null as_root apt-get update as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y caddy else install_packages caddy fi } open_firewall_ports() { if need_cmd ufw && as_root ufw status 2>/dev/null | grep -qi active; then as_root ufw allow 80/tcp || true as_root ufw allow 443/tcp || true fi if need_cmd firewall-cmd && as_root firewall-cmd --state >/dev/null 2>&1; then as_root firewall-cmd --permanent --add-service=http || true as_root firewall-cmd --permanent --add-service=https || true as_root firewall-cmd --reload || true fi } port_owner() { local port="$1" ss -ltnp 2>/dev/null | awk -v port=":${port}" '$4 ~ port"$" {print}' | head -1 } is_nginx_active() { pgrep -x nginx >/dev/null 2>&1 || systemctl is-active nginx >/dev/null 2>&1 } is_caddy_active() { pgrep -x caddy >/dev/null 2>&1 || systemctl is-active caddy >/dev/null 2>&1 } choose_engine() { if [ "${ENGINE}" != "auto" ]; then printf '%s' "${ENGINE}" return fi if is_nginx_active; then printf 'nginx' return fi printf 'caddy' } backup_file() { local file="$1" if [ -f "${file}" ]; then as_root cp "${file}" "${file}.bak.$(date +%Y%m%d%H%M%S)" fi } nginx_conf_dir() { if [ -n "${NGINX_CONF_DIR}" ]; then printf '%s' "${NGINX_CONF_DIR}" elif [ -d /www/server/panel/vhost/nginx ]; then printf '%s' /www/server/panel/vhost/nginx elif [ -d /etc/nginx/conf.d ]; then printf '%s' /etc/nginx/conf.d else fail "未找到 Nginx 站点配置目录,请设置 NGINX_CONF_DIR。" fi } reload_nginx() { as_root nginx -t if need_cmd systemctl && systemctl list-unit-files nginx.service >/dev/null 2>&1; then as_root systemctl reload nginx || as_root service nginx reload else as_root nginx -s reload fi } write_nginx_http_config() { local conf="$1" local webroot="$2" as_root mkdir -p "${webroot}/.well-known/acme-challenge" as_root tee "${conf}" >/dev/null </dev/null </dev/null <<'EOF' #!/usr/bin/env sh nginx -t && (systemctl reload nginx 2>/dev/null || service nginx reload 2>/dev/null || nginx -s reload) EOF as_root chmod +x "${hook}" } install_caddy_https() { local port80 port443 marker_start marker_end tmp_file port80="$(port_owner 80 || true)" port443="$(port_owner 443 || true)" if { [ -n "${port80}" ] || [ -n "${port443}" ]; } && ! is_caddy_active; then if [ "${FORCE_REPLACE_WEBSERVER}" != "true" ]; then fail "80/443 已被占用,无法直接启用 Caddy。请改用 ENGINE=nginx,或确认可替换现有 Web 服务后设置 FORCE_REPLACE_WEBSERVER=true。" fi warn "即将停止现有 Web 服务以释放 80/443。" as_root systemctl stop nginx 2>/dev/null || true as_root systemctl disable nginx 2>/dev/null || true as_root systemctl stop apache2 2>/dev/null || true as_root systemctl disable apache2 2>/dev/null || true fi install_caddy as_root mkdir -p "$(dirname "${CADDYFILE}")" if [ ! -f "${CADDYFILE}" ]; then as_root touch "${CADDYFILE}" fi backup_file "${CADDYFILE}" marker_start="# BEGIN AIAPI_MARKET_${DOMAIN}" marker_end="# END AIAPI_MARKET_${DOMAIN}" tmp_file="$(mktemp)" awk -v start="${marker_start}" -v end="${marker_end}" ' $0 == start { skip = 1; next } $0 == end { skip = 0; next } !skip { print } ' "${CADDYFILE}" >"${tmp_file}" cat >>"${tmp_file}" </tmp/aiapi-market-https-check.json if grep -q '"success"[[:space:]]*:[[:space:]]*true' /tmp/aiapi-market-https-check.json; then log "HTTPS 验证成功。" else warn "HTTPS 已可访问,但 /api/status 响应未包含 success=true。" head -c 300 /tmp/aiapi-market-https-check.json || true printf '\n' fi } print_summary() { cat <