#!/usr/bin/env bash # 安装 m.tkmind.cn + plaza.tkmind.cn 生产 LaunchAgent(登录自启 + 崩溃自动重启) # # 架构: # cloudflared → g2 :8090 (Caddy) → Portal :8081 # cloudflared → plaza :3001 (Next.js) → Portal :8081 # # 用法:pnpm setup:prod-services set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" LOG_DIR="${HOME}/Library/Logs" PORTAL_PLIST="${HOME}/Library/LaunchAgents/cn.tkmind.memind-portal.plist" PLAZA_PLIST="${HOME}/Library/LaunchAgents/cn.tkmind.plaza.plist" G2_LB_PLIST="${HOME}/Library/LaunchAgents/cn.tkmind.g2-lb.plist" CF_PLIST="${HOME}/Library/LaunchAgents/com.cloudflare.cloudflared.plist" PORTAL_RUN="${ROOT}/scripts/run-memind-portal-prod.sh" PLAZA_RUN="${ROOT}/scripts/run-plaza-prod.sh" CADDYFILE="${ROOT}/scripts/g2-lb.Caddyfile" UID_NUM="$(id -u)" GUI="gui/${UID_NUM}" PATH_ENV="/opt/homebrew/bin:/opt/homebrew/opt/node@22/bin:/usr/local/bin:/usr/bin:/bin" chmod +x "${PORTAL_RUN}" "${PLAZA_RUN}" "${ROOT}/scripts/install-g2-lb.sh" mkdir -p "${LOG_DIR}" cat >"${PORTAL_PLIST}" < Label cn.tkmind.memind-portal ProgramArguments ${PORTAL_RUN} WorkingDirectory ${ROOT} EnvironmentVariables PATH ${PATH_ENV} RunAtLoad KeepAlive ThrottleInterval 10 StandardOutPath ${LOG_DIR}/memind-portal.log StandardErrorPath ${LOG_DIR}/memind-portal.log EOF cat >"${PLAZA_PLIST}" < Label cn.tkmind.plaza ProgramArguments ${PLAZA_RUN} WorkingDirectory ${ROOT} EnvironmentVariables PATH ${PATH_ENV} RunAtLoad KeepAlive ThrottleInterval 15 StandardOutPath ${LOG_DIR}/plaza-prod.log StandardErrorPath ${LOG_DIR}/plaza-prod.log EOF if [[ ! -f "${G2_LB_PLIST}" ]]; then cat >"${G2_LB_PLIST}" < Label cn.tkmind.g2-lb ProgramArguments /opt/homebrew/bin/caddy run --config ${CADDYFILE} RunAtLoad KeepAlive ThrottleInterval 10 StandardOutPath ${LOG_DIR}/g2-lb.log StandardErrorPath ${LOG_DIR}/g2-lb.log EOF fi install_agent() { local label="$1" local plist="$2" if launchctl print "${GUI}/${label}" >/dev/null 2>&1; then launchctl bootout "${GUI}/${label}" 2>/dev/null || true sleep 1 fi launchctl bootstrap "${GUI}" "${plist}" launchctl enable "${GUI}/${label}" launchctl kickstart -k "${GUI}/${label}" } echo "==> 释放端口 8081 / 3001(避免与手动进程冲突)…" for port in 8081 3001; do lsof -ti "TCP:${port}" -sTCP:LISTEN 2>/dev/null | xargs kill -9 2>/dev/null || true done sleep 1 echo "==> 安装 LaunchAgent…" install_agent "cn.tkmind.memind-portal" "${PORTAL_PLIST}" sleep 2 install_agent "cn.tkmind.plaza" "${PLAZA_PLIST}" install_agent "cn.tkmind.g2-lb" "${G2_LB_PLIST}" if [[ -f "${CF_PLIST}" ]]; then install_agent "com.cloudflare.cloudflared" "${CF_PLIST}" else echo "⚠ 未找到 ${CF_PLIST},请确认 cloudflared LaunchAgent 已配置" >&2 fi echo "==> 等待服务就绪…" for _ in $(seq 1 60); do portal_ok=0 plaza_ok=0 g2_ok=0 curl -sf "http://127.0.0.1:8081/api/status" >/dev/null 2>&1 && portal_ok=1 curl -sf "http://127.0.0.1:3001/plaza" >/dev/null 2>&1 && plaza_ok=1 curl -sf "http://127.0.0.1:8090/api/status" >/dev/null 2>&1 && g2_ok=1 if [[ "${portal_ok}" -eq 1 && "${plaza_ok}" -eq 1 && "${g2_ok}" -eq 1 ]]; then break fi sleep 2 done echo "" echo "=== 生产服务状态 ===" launchctl list | grep -E "cn.tkmind\.(memind-portal|plaza|g2-lb)|com.cloudflare.cloudflared" || true curl -s -o /dev/null -w "Portal :8081 → %{http_code}\n" "http://127.0.0.1:8081/api/status" || true curl -s -o /dev/null -w "g2 LB :8090 → %{http_code}\n" "http://127.0.0.1:8090/api/status" || true curl -s -o /dev/null -w "Plaza :3001 → %{http_code}\n" "http://127.0.0.1:3001/plaza" || true curl -s -o /dev/null -w "外网 m → %{http_code}\n" "https://m.tkmind.cn/api/status" || true curl -s -o /dev/null -w "外网 plaza → %{http_code}\n" "https://plaza.tkmind.cn/plaza" || true echo "" echo "✅ 生产 LaunchAgent 已安装(登录自启 + KeepAlive)" echo " Portal cn.tkmind.memind-portal → ~/Library/Logs/memind-portal.log" echo " Plaza cn.tkmind.plaza → ~/Library/Logs/plaza-prod.log" echo " g2 LB cn.tkmind.g2-lb → ~/Library/Logs/g2-lb.log" echo " 隧道 com.cloudflare.cloudflared" echo "" echo "管理命令:" echo " launchctl kickstart -k ${GUI}/cn.tkmind.memind-portal" echo " launchctl kickstart -k ${GUI}/cn.tkmind.plaza" echo " tail -f ~/Library/Logs/memind-portal.log ~/Library/Logs/plaza-prod.log"