#!/bin/bash
# ============================================================
#  梦泽官网系统 · 自托管一键安装
#
#  用法（root 执行）:
#    curl -fsSL https://saas1.ymz2026.top/install.sh | bash -s -- \
#        --domain  www.yourcompany.com \
#        --code    MZ-XXXX-XXXX-XXXX \
#        [--admin  admin] [--pwd  yourpassword] [--email you@example.com]
#
#  做了什么:
#    1. 探测系统（Ubuntu / Debian / CentOS / Rocky / Alma / 宝塔面板）
#    2. 装 Python、Nginx、certbot、sqlite
#    3. 向平台核对授权码，取回公司名
#    4. 下载程序包、初始化该站点自己的数据库（含清除演示数据里的我方痕迹）
#    5. 配 systemd 服务 + Nginx 虚拟主机 + Let's Encrypt 证书
#
#  要求: root 权限；80 / 443 端口未被占用；域名已解析到本机。
# ============================================================

set -euo pipefail

API="https://saas1.ymz2026.top"
PKG_URL="$API/pkg/mzsite.tar.gz"
INSTALL_DIR=/opt/mzsite
PORT=8848

DOMAIN=""; CODE=""; ADMIN_USER=""; ADMIN_PWD=""; EMAIL=""

# ---------- 参数 ----------
while [ $# -gt 0 ]; do
  case "$1" in
    --domain) DOMAIN="${2:-}"; shift 2;;
    --code)   CODE="${2:-}";   shift 2;;
    --admin)  ADMIN_USER="${2:-}"; shift 2;;
    --pwd)    ADMIN_PWD="${2:-}";  shift 2;;
    --email)  EMAIL="${2:-}";  shift 2;;
    --api)    API="${2:-}";    shift 2;;
    -h|--help)
      sed -n '2,22p' "$0" | sed 's/^#\{1,2\} \{0,1\}//'; exit 0;;
    *) echo "不认识的参数: $1（用 --help 看用法）" >&2; exit 1;;
  esac
done

say()  { printf '\n\033[1;36m==>\033[0m %s\n' "$*"; }
ok()   { printf '    \033[0;32m✓\033[0m %s\n' "$*"; }
warn() { printf '    \033[0;33m!\033[0m %s\n' "$*"; }
die()  { printf '\n\033[0;31m✗ %s\033[0m\n' "$*" >&2; exit 1; }

# ---------- 前置检查 ----------
[ "$(id -u)" = "0" ] || die "请用 root 执行：sudo -i 后再跑这条命令"
[ -n "$DOMAIN" ] || die "缺少 --domain"
[ -n "$CODE" ]   || die "缺少 --code（授权码）"

DOMAIN="$(echo "$DOMAIN" | tr 'A-Z' 'a-z' | sed -E 's#^https?://##; s#/.*$##; s#^www\.##; s#\.$##')"
echo "$DOMAIN" | grep -Eq '^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$' \
  || die "域名格式不对：$DOMAIN"

[ -z "$ADMIN_USER" ] && ADMIN_USER="admin"
[ -z "$ADMIN_PWD" ] && ADMIN_PWD="$(head -c 9 /dev/urandom | base64 | tr -d '/+=' | head -c 10)"
[ -z "$EMAIL" ] && EMAIL="admin@$DOMAIN"

say "目标域名   : $DOMAIN"
say "授权码     : $CODE"

# ---------- 1. 探测系统 ----------
say "探测系统环境"
OS=""; PKG=""; BT=0
if [ -f /etc/os-release ]; then
  . /etc/os-release
  OS="$ID"
fi
if command -v apt-get >/dev/null 2>&1; then PKG=apt; fi
if command -v dnf >/dev/null 2>&1; then PKG=dnf; fi
if command -v yum >/dev/null 2>&1 && [ -z "$PKG" ]; then PKG=yum; fi
[ -z "$PKG" ] && die "没找到 apt / yum / dnf，这个系统装不了（只支持 Ubuntu/Debian/CentOS 系）"
if [ -d /www/server/panel ]; then BT=1; fi

ok "发行版 $OS  包管理器 $PKG"
[ "$BT" = "1" ] && ok "检测到宝塔面板，按宝塔的路径来" || ok "标准 Nginx 环境"

# ---------- 2. 装依赖 ----------
say "安装依赖（Python / Nginx / certbot / sqlite），可能要一两分钟"
if [ "$PKG" = "apt" ]; then
  export DEBIAN_FRONTEND=noninteractive
  apt-get update -qq
  apt-get install -y -qq python3 python3-pip python3-venv nginx curl sqlite3 \
    certbot python3-certbot-nginx >/dev/null 2>&1 || warn "部分包安装失败，继续尝试"
else
  $PKG install -y epel-release >/dev/null 2>&1 || true
  $PKG install -y python3 python3-pip nginx curl sqlite certbot >/dev/null 2>&1 \
    || warn "部分包安装失败，继续尝试"
fi
command -v python3 >/dev/null || die "python3 装不上，请手动安装后重试"
ok "python3 $(python3 -V 2>&1 | awk '{print $2}')"

say "安装 Python 依赖（FastAPI + uvicorn）"
PIP_FLAGS=""
python3 -c "import fastapi" 2>/dev/null || PIP_FLAGS="--break-system-packages"
python3 -m pip install -q $PIP_FLAGS fastapi "uvicorn[standard]" 2>&1 | tail -2 || true
python3 -c "import fastapi, uvicorn" 2>/dev/null || die "FastAPI/uvicorn 安装失败"
ok "FastAPI + uvicorn 就绪"

# ---------- 3. 端口 ----------
while ss -lnt 2>/dev/null | grep -q ":$PORT "; do
  warn "端口 $PORT 已被占用，换下一个"
  PORT=$((PORT+1))
done
ok "后端监听 127.0.0.1:$PORT"

# ---------- 4. 向平台核对授权码 ----------
say "向平台核对授权码"
MYIP="$(curl -s --max-time 8 http://api.ipify.org || echo '')"
ACT="$(curl -s --max-time 20 -X POST "$API/api/saas/activate" \
        -H "Content-Type: application/json" \
        -d "{\"domain\":\"$DOMAIN\",\"code\":\"$CODE\",\"ip\":\"$MYIP\"}")" \
  || die "连不上平台，检查网络后重试"
echo "$ACT" | grep -q '"ok"[[:space:]]*:[[:space:]]*true' \
  || die "授权码核对失败：$(echo "$ACT" | python3 -c 'import sys,json;print(json.load(sys.stdin).get("detail","未知错误"))' 2>/dev/null || echo "$ACT")"

COMPANY="$(echo "$ACT" | python3 -c 'import sys,json;print(json.load(sys.stdin).get("company",""))' 2>/dev/null || echo '')"
[ -z "$COMPANY" ] && COMPANY="$DOMAIN"
ok "授权有效 · 公司：$COMPANY"

# ---------- 5. 下载程序包 ----------
say "下载程序包"
mkdir -p "$INSTALL_DIR"
TMP="$(mktemp -d)"
curl -fsSL "$PKG_URL" -o "$TMP/mzsite.tar.gz" || die "程序包下载失败：$PKG_URL"
tar -xzf "$TMP/mzsite.tar.gz" -C "$INSTALL_DIR" --strip-components=1
rm -rf "$TMP"
[ -f "$INSTALL_DIR/backend/app.py" ] || die "程序包内容不对，缺 backend/app.py"
ok "已展开到 $INSTALL_DIR"

# ---------- 6. 建站点目录 ----------
if [ "$BT" = "1" ]; then
  WEBROOT="/www/wwwroot/$DOMAIN"
  NGINX_BIN=/www/server/nginx/sbin/nginx
  VHOST_DIR=/www/server/panel/vhost/nginx
else
  WEBROOT="/var/www/$DOMAIN"
  NGINX_BIN="$(command -v nginx)"
  VHOST_DIR=/etc/nginx/conf.d
fi
mkdir -p "$WEBROOT"
if [ -d "$INSTALL_DIR/site" ]; then
  cp -a "$INSTALL_DIR/site/." "$WEBROOT/"
else
  die "程序包里没有 site/ 目录"
fi
mkdir -p "$WEBROOT/assets/uploads"
ok "站点文件 → $WEBROOT"

# ---------- 7. 品牌中性化 ----------
say "把模板里的示例品牌换成你的"
python3 - "$WEBROOT" "$COMPANY" "$DOMAIN" <<'PY'
import os, re, sys
site, company, domain = sys.argv[1], sys.argv[2], sys.argv[3]
short = re.sub(r'(有限责?任?公司|股份有限公司|集团|科技|软件开发|信息技术|网络技术|（[^）]*）|\([^)]*\))', '', company).strip() or company
if len(short) > 6: short = short[:6]
PAIRS = [
    ("广州梦泽软件开发有限公司", company),
    ("Guangzhou Mengze Software Development Co., Ltd.", ""),
    ("MengZe Software", company), ("MENGZE", company), ("MengZe", company),
    ("mengze", company), ("梦泽", short),
    ("杨金澎", ""), ("乞丐", ""),
    ("15687636682", ""), ("3979991341@qq.com", "contact@%s" % domain),
    ("contact@mengze.cn", "contact@%s" % domain), ("Y19143286891", ""),
    ("mengze-soft", ""), ("mengze.cn", domain), ("ymz2026.cn", domain),
]
n = 0
for name in ("index.html", "services.html", "cases.html", "about.html", "contact.html"):
    p = os.path.join(site, name)
    if not os.path.isfile(p): continue
    s = open(p, encoding="utf-8").read(); o = s
    for a, b in PAIRS: s = s.replace(a, b)
    if s != o: open(p, "w", encoding="utf-8").write(s); n += 1
print("    ✓ 改写 %d 个页面（公司简称「%s」）" % (n, short))
PY

# ---------- 8. 初始化数据库 ----------
say "初始化站点数据库"
DATA_DIR="$INSTALL_DIR/data"
mkdir -p "$DATA_DIR"
DB="$DATA_DIR/mzsite.db"
(
  cd "$INSTALL_DIR/backend"
  MENGZE_DB="$DB" MENGZE_ADMIN="$ADMIN_USER" MENGZE_PASS="$ADMIN_PWD" \
  MENGZE_STATIC="$WEBROOT" MENGZE_SAAS_HOME="$INSTALL_DIR/saas" \
  python3 - "$DOMAIN" "$COMPANY" "$ADMIN_USER" "$ADMIN_PWD" <<'PY'
import sys, os
sys.path.insert(0, os.getcwd())
import app, saas_core
app.init_db()
saas_core._setup_tenant_db(app.DB_PATH, sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])
print("    ✓ 库已建好，管理员换成你自己的账号")
PY
) || die "数据库初始化失败"
ok "数据库 $DB"

# ---------- 9. systemd ----------
say "配置 systemd 服务"
cat > /etc/systemd/system/mzsite.service <<EOF
[Unit]
Description=Mengze Site ($DOMAIN)
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=$INSTALL_DIR/backend
Environment="MENGZE_DB=$DB"
Environment="MENGZE_ADMIN=$ADMIN_USER"
Environment="MENGZE_PASS=$ADMIN_PWD"
Environment="MENGZE_STATIC=$WEBROOT"
Environment="MENGZE_SAAS_HOME=$INSTALL_DIR/saas"
Environment="MENGZE_PUBLIC_IP=$MYIP"
ExecStart=/usr/bin/python3 -m uvicorn app:app --host 127.0.0.1 --port $PORT --workers 1 --no-server-header
Restart=always
RestartSec=3
StandardOutput=append:/var/log/mzsite.log
StandardError=append:/var/log/mzsite.log

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable mzsite >/dev/null 2>&1 || true
systemctl restart mzsite
sleep 4
systemctl is-active mzsite >/dev/null || { journalctl -u mzsite -n 20 --no-pager || true; die "服务没起来，看上面日志"; }
ok "服务运行中"

# ---------- 10. Nginx ----------
say "配置 Nginx"
if [ "$BT" = "1" ]; then
  CONF="$VHOST_DIR/$DOMAIN.conf"
else
  CONF="$VHOST_DIR/$DOMAIN.conf"
fi

write_http() {
cat > "$CONF" <<EOF
server {
    listen 80;
    server_name $DOMAIN www.$DOMAIN;
    index index.html;
    root $WEBROOT;

    location ^~ /.well-known/acme-challenge/ {
        root $WEBROOT;
        default_type "text/plain";
        try_files \$uri =404;
    }

    location ^~ /api/ {
        proxy_pass http://127.0.0.1:$PORT;
        proxy_http_version 1.1;
        proxy_set_header Host \$host;
        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;
        proxy_read_timeout 60s;
        client_max_body_size 8m;
    }

    location ^~ /admin {
        proxy_pass http://127.0.0.1:$PORT;
        proxy_http_version 1.1;
        proxy_set_header Host \$host;
        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;
        proxy_set_header Connection "";
        proxy_read_timeout 60s;
        client_max_body_size 8m;
    }

    location / {
        try_files \$uri \$uri/ =404;
    }
}
EOF
}

write_https() {
cat > "$CONF" <<EOF
server {
    listen 80;
    server_name $DOMAIN www.$DOMAIN;
    location ^~ /.well-known/acme-challenge/ {
        root $WEBROOT;
        default_type "text/plain";
        try_files \$uri =404;
    }
    location / { return 301 https://\$host\$request_uri; }
}

server {
    listen 443 ssl;
    http2 on;
    server_name $DOMAIN www.$DOMAIN;
    index index.html;
    root $WEBROOT;

    ssl_certificate     /etc/letsencrypt/live/$DOMAIN/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    add_header Strict-Transport-Security "max-age=31536000" always;

    location ^~ /.well-known/acme-challenge/ {
        root $WEBROOT;
        default_type "text/plain";
        try_files \$uri =404;
    }

    location ^~ /api/ {
        proxy_pass http://127.0.0.1:$PORT;
        proxy_http_version 1.1;
        proxy_set_header Host \$host;
        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;
        proxy_read_timeout 60s;
        client_max_body_size 8m;
    }

    location ^~ /admin {
        proxy_pass http://127.0.0.1:$PORT;
        proxy_http_version 1.1;
        proxy_set_header Host \$host;
        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;
        proxy_set_header Connection "";
        proxy_read_timeout 60s;
        client_max_body_size 8m;
    }

    location / {
        try_files \$uri \$uri/ =404;
    }
}
EOF
}

write_http
"$NGINX_BIN" -t && { systemctl reload nginx 2>/dev/null || "$NGINX_BIN" -s reload; }
ok "HTTP 配置已生效"

# ---------- 11. SSL ----------
DNIP="$(python3 -c "import socket,sys;print(socket.gethostbyname(sys.argv[1]))" "$DOMAIN" 2>/dev/null || echo '')"
if [ "$DNIP" = "$MYIP" ] && [ -n "$MYIP" ]; then
  say "签发 SSL 证书（Let's Encrypt）"
  if certbot certonly --webroot -w "$WEBROOT" -d "$DOMAIN" \
       --agree-tos -m "$EMAIL" --no-eff-email --non-interactive 2>&1 | tail -3; then
    write_https
    "$NGINX_BIN" -t && { systemctl reload nginx 2>/dev/null || "$NGINX_BIN" -s reload; }
    ok "HTTPS 已启用"
  else
    warn "证书没签下来，站点暂时走 HTTP。等解析稳定后重跑：certbot certonly --webroot -w $WEBROOT -d $DOMAIN"
  fi
else
  warn "域名解析到 [$DNIP]，本机公网 IP 是 [$MYIP]，跳过证书签发"
  warn "解析改好后再跑一次这条命令即可（已装的部分会自动跳过）"
fi

# ---------- 完成 ----------
say "装好了"
echo
echo "  站点地址   https://$DOMAIN"
echo "  后台入口   https://$DOMAIN/admin"
echo "  登录账号   $ADMIN_USER"
echo "  登录密码   $ADMIN_PWD"
echo
echo "  程序装在   $INSTALL_DIR"
echo "  数据库     $DB（单文件，拷走即备份）"
echo "  日志       journalctl -u mzsite -f"
echo
echo "  密码只显示这一次，现在就记下来。"
echo
