merge: maestro/tsk_nqvOyIZkd1Dn [tsk_nqvOyIZkd1Dn]
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<!--
|
||||
maestrod launchd User Agent 模板
|
||||
============================================================
|
||||
此文件为模板,占位符由 install-daemon.sh 在安装时替换后写入:
|
||||
~/Library/LaunchAgents/com.maestro.maestrod.plist
|
||||
|
||||
占位符说明:
|
||||
__NODE__ - node 可执行文件的绝对路径(如 /opt/homebrew/bin/node)
|
||||
__PROJ_DIR__ - 项目根目录绝对路径
|
||||
__HOME__ - 用户家目录绝对路径
|
||||
__PATH__ - 环境变量 PATH(含 node 所在目录)
|
||||
|
||||
管理命令:
|
||||
安装/重装:bash scripts/install-daemon.sh
|
||||
卸载: bash scripts/uninstall-daemon.sh
|
||||
查看状态: launchctl list com.maestro.maestrod
|
||||
实时日志: tail -f ~/.maestro/maestrod.log
|
||||
-->
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
|
||||
<!-- 服务标识符,必须全局唯一 -->
|
||||
<key>Label</key>
|
||||
<string>com.maestro.maestrod</string>
|
||||
|
||||
<!-- 启动命令:node <项目>/dist/daemon/index.js -->
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>__NODE__</string>
|
||||
<string>__PROJ_DIR__/dist/daemon/index.js</string>
|
||||
</array>
|
||||
|
||||
<!-- 崩溃 / 退出后自动拉起 -->
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
|
||||
<!-- 用户登录后立即启动(开机常驻) -->
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
|
||||
<!-- 日志:stdout + stderr 合并写入同一文件 -->
|
||||
<key>StandardOutPath</key>
|
||||
<string>__HOME__/.maestro/maestrod.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>__HOME__/.maestro/maestrod.log</string>
|
||||
|
||||
<!-- 工作目录 -->
|
||||
<key>WorkingDirectory</key>
|
||||
<string>__PROJ_DIR__</string>
|
||||
|
||||
<!-- 最小运行环境(launchd 默认环境极简,HOME/PATH 必须显式注入) -->
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>HOME</key>
|
||||
<string>__HOME__</string>
|
||||
<key>PATH</key>
|
||||
<string>__PATH__</string>
|
||||
</dict>
|
||||
|
||||
<!-- 两次崩溃拉起之间的最短冷却时间(秒),防止崩溃风暴 -->
|
||||
<key>ThrottleInterval</key>
|
||||
<integer>10</integer>
|
||||
|
||||
<!-- 标记为后台进程 -->
|
||||
<key>ProcessType</key>
|
||||
<string>Background</string>
|
||||
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env bash
|
||||
# install-daemon.sh — 安装 maestrod 为 launchd 用户 Agent
|
||||
#
|
||||
# 功能:
|
||||
# 1. 检测 node 路径
|
||||
# 2. 构建项目(tsc → dist/)
|
||||
# 3. 生成 ~/Library/LaunchAgents/com.maestro.maestrod.plist(填充绝对路径)
|
||||
# 4. 通过 launchctl 加载服务(开机自启 + 崩溃自拉)
|
||||
#
|
||||
# 用法:
|
||||
# bash scripts/install-daemon.sh
|
||||
#
|
||||
# 重装(更新 plist / 重启服务):重新执行本脚本即可。
|
||||
set -euo pipefail
|
||||
|
||||
LABEL="com.maestro.maestrod"
|
||||
PLIST_NAME="${LABEL}.plist"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJ_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
PLIST_TEMPLATE="${SCRIPT_DIR}/${PLIST_NAME}"
|
||||
PLIST_DEST="${HOME}/Library/LaunchAgents/${PLIST_NAME}"
|
||||
DATA_DIR="${HOME}/.maestro"
|
||||
|
||||
# ── 1. 检测 Node.js ──────────────────────────────────────────
|
||||
echo "==> 检测 Node.js…"
|
||||
NODE_BIN="$(command -v node || true)"
|
||||
if [[ -z "${NODE_BIN}" ]]; then
|
||||
echo "错误:找不到 node,请先安装 Node.js >= 20(推荐 Homebrew:brew install node)" >&2
|
||||
exit 1
|
||||
fi
|
||||
NODE_VER="$(node --version 2>&1)"
|
||||
# 主版本号(去掉 "v" 和 ".x.y")
|
||||
NODE_MAJOR="$(node --version 2>&1 | sed 's/^v//' | cut -d. -f1)"
|
||||
if [[ "${NODE_MAJOR}" -lt 20 ]]; then
|
||||
echo "错误:需要 Node.js >= 20,当前 ${NODE_VER}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo " node: ${NODE_BIN} (${NODE_VER})"
|
||||
|
||||
# node 所在目录,确保 launchd 进程能找到 node 和相关工具
|
||||
NODE_DIR="$(dirname "${NODE_BIN}")"
|
||||
# 合并常用路径,去重
|
||||
DAEMON_PATH="${NODE_DIR}:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
||||
|
||||
# ── 2. 构建项目 ───────────────────────────────────────────────
|
||||
echo "==> 构建项目(tsc)…"
|
||||
cd "${PROJ_DIR}"
|
||||
npm run build
|
||||
DAEMON_ENTRY="${PROJ_DIR}/dist/daemon/index.js"
|
||||
if [[ ! -f "${DAEMON_ENTRY}" ]]; then
|
||||
echo "错误:构建产物不存在:${DAEMON_ENTRY}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo " 入口文件: ${DAEMON_ENTRY}"
|
||||
|
||||
# ── 3. 确保数据/日志目录存在 ─────────────────────────────────
|
||||
echo "==> 创建数据目录 ${DATA_DIR}…"
|
||||
mkdir -p "${DATA_DIR}"
|
||||
mkdir -p "${HOME}/Library/LaunchAgents"
|
||||
|
||||
# ── 4. 生成 plist(用 sed 填充占位符)────────────────────────
|
||||
echo "==> 生成 plist…"
|
||||
if [[ ! -f "${PLIST_TEMPLATE}" ]]; then
|
||||
echo "错误:找不到 plist 模板:${PLIST_TEMPLATE}" >&2
|
||||
exit 1
|
||||
fi
|
||||
# 使用中间临时文件,避免 sed in-place 跨平台差异
|
||||
TMP_PLIST="$(mktemp /tmp/${LABEL}.XXXXXX.plist)"
|
||||
trap 'rm -f "${TMP_PLIST}"' EXIT
|
||||
|
||||
sed \
|
||||
-e "s|__NODE__|${NODE_BIN}|g" \
|
||||
-e "s|__PROJ_DIR__|${PROJ_DIR}|g" \
|
||||
-e "s|__HOME__|${HOME}|g" \
|
||||
-e "s|__PATH__|${DAEMON_PATH}|g" \
|
||||
"${PLIST_TEMPLATE}" > "${TMP_PLIST}"
|
||||
|
||||
# 校验生成的 plist
|
||||
plutil -lint "${TMP_PLIST}" || { echo "错误:生成的 plist 格式不合法" >&2; exit 1; }
|
||||
cp "${TMP_PLIST}" "${PLIST_DEST}"
|
||||
echo " 已写入: ${PLIST_DEST}"
|
||||
|
||||
# ── 5. 加载(或重载)服务 ────────────────────────────────────
|
||||
echo "==> 加载 launchd 服务…"
|
||||
|
||||
# 若服务已运行,先卸载再重载
|
||||
if launchctl list 2>/dev/null | grep -qF "${LABEL}"; then
|
||||
echo " 检测到旧服务,先卸载…"
|
||||
launchctl bootout "gui/${UID}" "${PLIST_DEST}" 2>/dev/null \
|
||||
|| launchctl unload -w "${PLIST_DEST}" 2>/dev/null \
|
||||
|| true
|
||||
# 短暂等待彻底退出
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
# bootstrap 是 macOS 10.11+ 推荐 API;失败时回退到 load -w
|
||||
if ! launchctl bootstrap "gui/${UID}" "${PLIST_DEST}" 2>/dev/null; then
|
||||
launchctl load -w "${PLIST_DEST}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ maestrod 安装完成"
|
||||
echo ""
|
||||
echo " 服务标识: ${LABEL}"
|
||||
echo " 日志文件: ${DATA_DIR}/maestrod.log"
|
||||
echo " API 地址: http://127.0.0.1:4517"
|
||||
echo ""
|
||||
echo " 常用命令:"
|
||||
echo " 查看状态: launchctl list ${LABEL}"
|
||||
echo " 实时日志: tail -f ${DATA_DIR}/maestrod.log"
|
||||
echo " 停止服务: launchctl stop ${LABEL}"
|
||||
echo " 启动服务: launchctl start ${LABEL}"
|
||||
echo " 卸载: bash ${SCRIPT_DIR}/uninstall-daemon.sh"
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
# uninstall-daemon.sh — 卸载 maestrod launchd 用户 Agent
|
||||
#
|
||||
# 功能:
|
||||
# 1. 停止并卸载 launchd 服务
|
||||
# 2. 删除 ~/Library/LaunchAgents/com.maestro.maestrod.plist
|
||||
#
|
||||
# 注意:数据目录 ~/.maestro(SQLite 数据库、日志)不会被删除,需手动清理。
|
||||
#
|
||||
# 用法:
|
||||
# bash scripts/uninstall-daemon.sh
|
||||
set -euo pipefail
|
||||
|
||||
LABEL="com.maestro.maestrod"
|
||||
PLIST_DEST="${HOME}/Library/LaunchAgents/${LABEL}.plist"
|
||||
|
||||
echo "==> 卸载 maestrod (${LABEL})…"
|
||||
|
||||
# ── 1. 停止并卸载服务 ─────────────────────────────────────────
|
||||
if launchctl list 2>/dev/null | grep -qF "${LABEL}"; then
|
||||
echo " 服务运行中,正在停止…"
|
||||
# 优先使用 bootout(macOS 10.11+ 推荐),失败则回退 unload
|
||||
if launchctl bootout "gui/${UID}" "${PLIST_DEST}" 2>/dev/null; then
|
||||
echo " 已通过 bootout 卸载"
|
||||
elif launchctl unload -w "${PLIST_DEST}" 2>/dev/null; then
|
||||
echo " 已通过 unload 卸载"
|
||||
else
|
||||
echo "警告:无法正常卸载服务,将继续删除 plist 文件" >&2
|
||||
fi
|
||||
else
|
||||
echo " 服务未加载,跳过停止步骤"
|
||||
fi
|
||||
|
||||
# ── 2. 删除 plist ─────────────────────────────────────────────
|
||||
if [[ -f "${PLIST_DEST}" ]]; then
|
||||
rm -f "${PLIST_DEST}"
|
||||
echo " 已删除: ${PLIST_DEST}"
|
||||
else
|
||||
echo " plist 不存在,跳过删除"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ maestrod 已卸载"
|
||||
echo ""
|
||||
echo " 注意:数据目录 ${HOME}/.maestro 未被删除(保留数据库和日志)。"
|
||||
echo " 如需彻底清除:rm -rf ${HOME}/.maestro"
|
||||
Reference in New Issue
Block a user