Files
opsxdb/run.sh
T

352 lines
11 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# OpsxDB launcher — scene profiles, not random process soup
#
# ./run.sh # = web:仅 Gin 官网 + docs(:8788)
# ./run.sh web # 同上(浏览器不当客户端)
# ./run.sh gui # 桌面唯一操作入口:临时 .app(Dock 名 OpsxDB)+ 默认 Gin
# ./run.sh all # 同 gui(兼容旧命令;不再起「浏览器工作台产品」)
# ./run.sh server|desktop # 单组件
# ./run.sh package # Tauri 安装包 + sidecar
# ./run.sh stop|restart|status|help
#
# 产品形态:浏览器 = 官网/docs;操作 = 桌面 Tauri。
# src 的 Vite 仅作桌面打包/devUrl 源,不要用浏览器当工作台。
set -euo pipefail
PROJECT_NAME="OpsxDB"
SERVER_PORT="${OPSXDB_SERVER_PORT:-8788}"
WEB_PORT="${OPSXDB_WEB_PORT:-5174}"
SERVER_DIR="server"
SERVER_BIN="opsxdb-server"
SERVER_LABEL="com.opsx.db.server"
WEB_LABEL="com.opsx.db.web"
DESKTOP_LABEL="com.opsx.db.desktop"
# gui 默认是否顺带起 Gin(可用 OPSXDB_GUI_WITH_SERVER=0 关掉)
GUI_WITH_SERVER="${OPSXDB_GUI_WITH_SERVER:-1}"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC} $*"; }
warning() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERR]${NC} $*"; }
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
LOG_DIR="$SCRIPT_DIR/logs"
mkdir -p "$LOG_DIR" "$SCRIPT_DIR/$SERVER_DIR/bin"
SERVER_LOG="$LOG_DIR/server.log"
WEB_LOG="$LOG_DIR/web.log"
DESKTOP_LOG="$LOG_DIR/desktop.log"
# shellcheck source=scripts/gui-dev-app.sh
source "$SCRIPT_DIR/scripts/gui-dev-app.sh"
# node/npm 不在此强制注入,由 detect_node 按需探测(start_web/start_desktop 前调用)。
export PATH="/opt/homebrew/bin:/usr/local/bin:${HOME}/.cargo/bin:$PATH"
# detect_node 探测 node/npm:PATH 已有直接用;否则依次探测
# $NVM_DIR/versions/node、~/.nvm/versions/node(取最高版本)、homebrew、/usr/local。
# 找不到给出清晰报错并退出。结果写入 NODE_BIN_DIR 供 launchctl/nohup 子 shell 使用。
NODE_BIN_DIR=""
detect_node() {
if command -v node >/dev/null 2>&1 && command -v npm >/dev/null 2>&1; then
NODE_BIN_DIR="$(cd "$(dirname "$(command -v node)")" && pwd)"
return 0
fi
local roots=() d v best=""
[ -n "${NVM_DIR:-}" ] && roots+=("$NVM_DIR/versions/node")
roots+=("${HOME}/.nvm/versions/node")
for d in "${roots[@]}"; do
[ -d "$d" ] || continue
v="$(ls -1 "$d" 2>/dev/null | sort -V | tail -n 1)"
if [ -n "$v" ] && [ -x "$d/$v/bin/node" ]; then
best="$d/$v/bin"
break
fi
done
if [ -z "$best" ]; then
for d in /opt/homebrew/bin /usr/local/bin; do
if [ -x "$d/node" ]; then
best="$d"
break
fi
done
fi
if [ -z "$best" ]; then
error "未找到 node/npm。请先安装 Node.js(https://nodejs.org)或 nvm,或设置 NVM_DIR 后重试。"
exit 1
fi
NODE_BIN_DIR="$best"
export PATH="$NODE_BIN_DIR:$PATH"
info "使用 Node.js: $NODE_BIN_DIR"
}
check_port() { lsof -ti:"$1" 2>/dev/null | head -1 || true; }
kill_port() {
local pids
pids="$(lsof -ti:"$1" 2>/dev/null || true)"
if [ -n "$pids" ]; then
info "释放端口 $1"
# shellcheck disable=SC2086
kill -9 $pids 2>/dev/null || true
sleep 0.3
fi
}
start_detached() {
local label="$1" log_file="$2" command="$3"
if [ "$(uname -s)" = "Darwin" ] && command -v launchctl >/dev/null 2>&1; then
launchctl remove "$label" 2>/dev/null || true
launchctl submit -l "$label" -o "$log_file" -e "$log_file" -- /bin/bash -lc "$command"
else
nohup /bin/bash -lc "$command" >>"$log_file" 2>&1 </dev/null &
fi
}
stop_detached() {
local label="$1"
if [ "$(uname -s)" = "Darwin" ] && command -v launchctl >/dev/null 2>&1; then
launchctl remove "$label" 2>/dev/null || true
fi
}
ensure_npm() {
detect_node
if [ ! -d "$SCRIPT_DIR/node_modules" ]; then
warning "缺少 node_modules,正在 npm install..."
npm install
fi
if [ ! -d "$SCRIPT_DIR/node_modules" ]; then
warning "desktop 缺少依赖,正在安装..."
npm install
fi
}
build_server() {
info "编译 Gin 服务..."
# -trimpath 去本地路径;-ldflags "-s -w" 去符号表/调试信息;CGO_ENABLED=0 静态化减体积
(cd "$SCRIPT_DIR/$SERVER_DIR" && CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o "$SCRIPT_DIR/$SERVER_DIR/bin/$SERVER_BIN" ./cmd/opsxdb)
}
wait_http() {
local url="$1" name="$2" retries="${3:-60}"
local i=0
while ! curl -fsS "$url" >/dev/null 2>&1; do
i=$((i + 1))
if [ "$i" -ge "$retries" ]; then
error "$name 启动超时:$url"
return 1
fi
sleep 0.5
done
}
start_server() {
if curl -fsS "http://127.0.0.1:$SERVER_PORT/health" >/dev/null 2>&1; then
success "后端已在运行 :$SERVER_PORT"
return 0
fi
info "启动后端 :$SERVER_PORT ..."
: >"$SERVER_LOG"
kill_port "$SERVER_PORT"
build_server
# 默认仅本机;对外需 OPSXDB_BIND_PUBLIC=1
local bind_env="OPSXDB_ADDR=127.0.0.1:$SERVER_PORT"
if [ -n "${OPSXDB_ADDR:-}" ]; then
bind_env="OPSXDB_ADDR=$OPSXDB_ADDR"
elif [ "${OPSXDB_BIND_PUBLIC:-0}" = "1" ] || [ "${OPSXDB_BIND_PUBLIC:-}" = "true" ]; then
bind_env="OPSXDB_BIND_PUBLIC=1"
fi
start_detached "$SERVER_LABEL" "$SERVER_LOG" \
"export PATH=\"/opt/homebrew/bin:/usr/local/bin:\$PATH\"; cd \"$SCRIPT_DIR/$SERVER_DIR\" && exec env $bind_env GIN_MODE=debug \"./bin/$SERVER_BIN\""
wait_http "http://127.0.0.1:$SERVER_PORT/health" "后端"
success "后端 UP http://127.0.0.1:$SERVER_PORT/health"
}
stop_server() {
stop_detached "$SERVER_LABEL"
kill_port "$SERVER_PORT"
success "后端已停止"
}
# 仅供桌面 Tauri devUrl / 打包源;不是浏览器产品入口
start_vite_for_desktop() {
if curl -fsS "http://127.0.0.1:$WEB_PORT/" >/dev/null 2>&1; then
success "桌面 UI(Vite) 已在运行 :$WEB_PORT"
return 0
fi
info "启动桌面 UI 源 :${WEB_PORT}(仅本机给 Tauri,勿当浏览器客户端)..."
: >"$WEB_LOG"
ensure_npm
kill_port "$WEB_PORT"
# Vite 用 nohup:launchctl submit 在本机对 npm -w 经常交不出进程(web.log 空、40s 超时)
nohup /bin/bash -lc "export PATH=\"$NODE_BIN_DIR:/opt/homebrew/bin:/usr/local/bin:\$PATH\"; cd \"$SCRIPT_DIR\" && exec npm run" >>"$WEB_LOG" 2>&1 </dev/null &
wait_http "http://127.0.0.1:$WEB_PORT/" "Web UI" 80
success "桌面 UI(Vite) UP http://127.0.0.1:${WEB_PORT}(请用 ./run.sh gui,勿当工作台)"
}
# 兼容旧名
start_web() { start_vite_for_desktop; }
stop_web() {
stop_detached "$WEB_LABEL"
kill_port "$WEB_PORT"
success "Web 已停止"
}
start_desktop() {
info "启动桌面 OpsxDB(临时 .app + 当前图标)..."
: >"$DESKTOP_LOG"
ensure_npm
kill_opsxdb_gui
ensure_gui_dev_app || return 1
if [ "${GUI_DEV_NEEDS_VITE:-0}" = "1" ]; then
start_vite_for_desktop
fi
open "$GUI_APP" 2>>"$DESKTOP_LOG" || { error "open 失败,见 $DESKTOP_LOG"; return 1; }
local i=0
while true; do
if gui_is_running; then
success "桌面 OpsxDB 已启动(临时 .app,Dock 用 icon.icns / 名称 OpsxDB)"
info "路径: $GUI_APP"
return 0
fi
i=$((i + 1))
if [ "$i" -ge 30 ]; then
error "桌面启动超时,见 $DESKTOP_LOG"
tail -n 40 "$DESKTOP_LOG" || true
return 1
fi
sleep 1
done
}
stop_desktop() {
kill_opsxdb_gui
success "桌面已停止"
}
status() {
echo "$PROJECT_NAME 状态"
if curl -fsS "http://127.0.0.1:$SERVER_PORT/health" >/dev/null 2>&1; then
success "server :$SERVER_PORT UP $(curl -fsS "http://127.0.0.1:$SERVER_PORT/health")"
else
warning "server :$SERVER_PORT DOWN"
fi
if curl -fsS "http://127.0.0.1:$WEB_PORT/" >/dev/null 2>&1; then
success "vite :${WEB_PORT} UP(桌面源,非浏览器产品)"
else
warning "vite :$WEB_PORT DOWN"
fi
if curl -fsS "http://127.0.0.1:$SERVER_PORT/home" >/dev/null 2>&1 \
|| curl -fsS "http://127.0.0.1:$SERVER_PORT/docs" >/dev/null 2>&1; then
success "site :$SERVER_PORT 官网/docs 可达"
fi
if gui_is_running; then
success "gui OpsxDB UP"
else
warning "gui OpsxDB DOWN"
fi
}
package_desktop() {
info "构建桌面可安装包(含本机 sidecar)..."
ensure_npm
build_server
local triple
triple="$(rustc -vV 2>/dev/null | awk '/host:/ {print $2}')"
if [ -z "$triple" ]; then
error "无法检测 rustc host triple"
return 1
fi
local bin_dir="$SCRIPT_DIR/src-tauri/binaries"
mkdir -p "$bin_dir"
cp "$SCRIPT_DIR/$SERVER_DIR/bin/$SERVER_BIN" "$bin_dir/opsxdb-server-$triple"
chmod +x "$bin_dir/opsxdb-server-$triple"
success "sidecar → binaries/opsxdb-server-$triple"
(
cd "$SCRIPT_DIR/apps/desktop"
export VITE_OPSXDB_API="http://127.0.0.1:$SERVER_PORT"
npm run tauri -- build
)
success "桌面包构建完成(见 apps/desktop/src-tauri/target/release/bundle)"
}
usage() {
cat <<USAGE
$PROJECT_NAME 启动脚本(产品形态:浏览器=官网/docs · 操作=桌面)
./run.sh # 同 web
./run.sh web # 仅 Gin:官网 + docs → http://127.0.0.1:$SERVER_PORT/
./run.sh gui # 桌面:临时 .app(Dock=OpsxDB / icon.icns;默认也起 Gin)
./run.sh all # 同 gui(兼容旧名)
./run.sh server # 只 Gin(官网/docs + API)
./run.sh desktop # 只 Desktop(会起本机 Vite 源)
./run.sh package # 打桌面安装包
./run.sh stop | restart | status | help
端口: Gin/官网 $SERVER_PORT · 桌面 Vite 源 ${WEB_PORT}(不对外当客户端)
日志: logs/{server,web,desktop}.log
USAGE
}
start_web_scene() {
# 产品:浏览器只开官网/docs,不起 Vite 工作台
start_server
stop_web || true
echo
success "官网/文档就绪 → http://127.0.0.1:$SERVER_PORT/ · docs → http://127.0.0.1:$SERVER_PORT/docs"
info "操作请用 ./run.sh gui(桌面)。:5174 不是产品入口。"
}
start_gui_scene() {
if [ "$GUI_WITH_SERVER" = "1" ]; then
start_server
else
info "跳过 Gin(OPSXDB_GUI_WITH_SERVER=0);桌面 HTTP 走 tauri-native"
fi
start_desktop
echo
success "GUI 场景就绪 → 桌面窗口 OpsxDB"
}
start_all_scene() {
# 与 gui 同:桌面操作 + Gin;不起「浏览器工作台产品」语义
start_gui_scene
}
stop_all() {
stop_desktop || true
stop_web || true
stop_server || true
}
cmd="${1:-web}"
case "$cmd" in
web|start) start_web_scene ;;
gui|desktop-scene) start_gui_scene ;;
all) start_all_scene ;;
server) start_server ;;
package|dist) package_desktop ;;
web-only|vite)
warning "已弃用浏览器工作台。Vite 仅桌面源;请用: ./run.sh gui"
start_vite_for_desktop
;;
desktop|gui-only) start_desktop ;;
stop) stop_all ;;
restart)
stop_all || true
start_web_scene
;;
status) status ;;
help|-h|--help) usage ;;
*) error "未知命令: $cmd"; usage; exit 1 ;;
esac