350 lines
10 KiB
Bash
Executable File
350 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# OpsxShell launcher — scene profiles(对齐 opsxdb / opsxapi)
|
||
#
|
||
# ./run.sh # = web:仅 Gin 官网 + docs(:8789)
|
||
# ./run.sh web # 同上(浏览器不当操作客户端)
|
||
# ./run.sh gui # 桌面唯一操作入口:临时 .app(Dock 图标)+ 默认 Gin
|
||
# ./run.sh all # 同 gui(兼容旧命令)
|
||
# ./run.sh server|desktop
|
||
# ./run.sh clean-apps # 全量开发机卫生(~/devops/scripts/clean-opsx-apps.sh)
|
||
# ./run.sh stop|restart|status|help
|
||
#
|
||
# 产品形态:浏览器 = 官网/docs;操作 = 桌面 Tauri(src/ + src-tauri/)。
|
||
# Vite :5175 仅作桌面 WebView 源,不要用浏览器当工作台。
|
||
|
||
set -euo pipefail
|
||
|
||
PROJECT_NAME="OpsxShell"
|
||
SERVER_PORT="${OPSXSHELL_SERVER_PORT:-8789}"
|
||
WEB_PORT="${OPSXSHELL_WEB_PORT:-5175}"
|
||
SERVER_DIR="server"
|
||
SERVER_BIN="opsxshell-server"
|
||
SERVER_LABEL="com.opsx.shell.server"
|
||
WEB_LABEL="com.opsx.shell.web"
|
||
DESKTOP_LABEL="com.opsx.shell.desktop"
|
||
|
||
# gui 默认是否顺带起 Gin(可用 OPSXSHELL_GUI_WITH_SERVER=0 关掉)
|
||
GUI_WITH_SERVER="${OPSXSHELL_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=gui-dev-app.sh
|
||
source "$SCRIPT_DIR/scripts/gui-dev-app.sh"
|
||
|
||
OPSX_CLEAN_SCRIPT="${OPSX_CLEAN_SCRIPT:-$HOME/devops/scripts/clean-opsx-apps.sh}"
|
||
|
||
# gui 启动前轻量清:推残留 DMG + 注销非 /Applications、非 keep-path 的幽灵。
|
||
# 不删 target、不 ResetLaunchPad / kill Dock。逻辑在 clean-opsx-apps.sh --light。
|
||
opsx_light_clean_before_gui() {
|
||
local keep="$1"
|
||
if [ "$(uname -s)" != "Darwin" ]; then
|
||
return 0
|
||
fi
|
||
if [ ! -x "$OPSX_CLEAN_SCRIPT" ]; then
|
||
warning "找不到清理脚本: $OPSX_CLEAN_SCRIPT(跳过轻量清)"
|
||
return 0
|
||
fi
|
||
info "gui light-clean (keep-path=${keep})..."
|
||
"$OPSX_CLEAN_SCRIPT" --light --keep-path="$keep" --no-verify \
|
||
|| warning "轻量清未完全成功(继续启动)"
|
||
}
|
||
|
||
run_clean_apps() {
|
||
if [ ! -x "$OPSX_CLEAN_SCRIPT" ]; then
|
||
error "找不到: $OPSX_CLEAN_SCRIPT"
|
||
return 1
|
||
fi
|
||
# 转发剩余参数(如 --delete-target-apps);默认 full
|
||
"$OPSX_CLEAN_SCRIPT" "$@"
|
||
}
|
||
|
||
# Same path as src-tauri infrastructure/file_log.rs (dirs::data_local_dir).
|
||
if [ "$(uname -s)" = "Darwin" ]; then
|
||
APP_LOG="$HOME/Library/Application Support/opsxshell/logs/opsxshell.log"
|
||
else
|
||
APP_LOG="${XDG_DATA_HOME:-$HOME/.local/share}/opsxshell/logs/opsxshell.log"
|
||
fi
|
||
|
||
export PATH="/opt/homebrew/bin:/usr/local/bin:${HOME}/.cargo/bin:$PATH"
|
||
|
||
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 或 nvm。"
|
||
exit 1
|
||
fi
|
||
NODE_BIN_DIR="$best"
|
||
export PATH="$NODE_BIN_DIR:$PATH"
|
||
info "使用 Node.js: $NODE_BIN_DIR"
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
build_server() {
|
||
info "编译 Gin 官网/docs..."
|
||
(
|
||
cd "$SCRIPT_DIR/$SERVER_DIR"
|
||
export GOPROXY="${GOPROXY:-https://goproxy.io,direct}"
|
||
CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" \
|
||
-o "$SCRIPT_DIR/$SERVER_DIR/bin/$SERVER_BIN" ./cmd/opsxshell-site
|
||
)
|
||
}
|
||
|
||
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 "启动官网/docs :$SERVER_PORT ..."
|
||
: >"$SERVER_LOG"
|
||
kill_port "$SERVER_PORT"
|
||
build_server
|
||
local bind_env="OPSXSHELL_ADDR=127.0.0.1:$SERVER_PORT"
|
||
if [ -n "${OPSXSHELL_ADDR:-}" ]; then
|
||
bind_env="OPSXSHELL_ADDR=$OPSXSHELL_ADDR"
|
||
elif [ "${OPSXSHELL_BIND_PUBLIC:-0}" = "1" ] || [ "${OPSXSHELL_BIND_PUBLIC:-}" = "true" ]; then
|
||
bind_env="OPSXSHELL_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/ · docs → http://127.0.0.1:$SERVER_PORT/docs"
|
||
}
|
||
|
||
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"
|
||
start_detached "$WEB_LABEL" "$WEB_LOG" \
|
||
"export PATH=\"$NODE_BIN_DIR:/opt/homebrew/bin:/usr/local/bin:\$PATH\"; cd \"$SCRIPT_DIR\" && exec npm run dev:ui"
|
||
wait_http "http://127.0.0.1:$WEB_PORT/" "Vite" 80
|
||
success "桌面 UI(Vite) UP http://127.0.0.1:${WEB_PORT}(请用 ./run.sh gui)"
|
||
}
|
||
|
||
stop_web() {
|
||
stop_detached "$WEB_LABEL"
|
||
kill_port "$WEB_PORT"
|
||
success "Vite 已停止"
|
||
}
|
||
|
||
start_desktop() {
|
||
opsx_light_clean_before_gui "$GUI_APP"
|
||
info "启动桌面 OpsxShell(临时 .app + 当前图标)..."
|
||
: >"$DESKTOP_LOG"
|
||
ensure_npm
|
||
kill_opsxshell_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 "桌面 OpsxShell 已启动(临时 .app,Dock 用 icon.icns / 名称 OpsxShell)"
|
||
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_opsxshell_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/" >/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 OpsxShell UP"
|
||
else
|
||
warning "gui OpsxShell DOWN"
|
||
fi
|
||
}
|
||
|
||
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=OpsxShell / icon.icns;默认也起 Gin)
|
||
./run.sh all # 同 gui(兼容旧名)
|
||
|
||
./run.sh server # 只 Gin(官网/docs)
|
||
./run.sh desktop # 只 Desktop
|
||
./run.sh stop | restart | status | help
|
||
./run.sh clean-apps # 全量开发机卫生(默认只注销不删 target)
|
||
|
||
端口: Gin/官网 $SERVER_PORT · 桌面 Vite 源 ${WEB_PORT}(不对外当客户端)
|
||
日志: logs/{server,web,desktop}.log · 应用日志见 $APP_LOG
|
||
USAGE
|
||
}
|
||
|
||
start_web_scene() {
|
||
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(桌面)。:$WEB_PORT 不是产品入口。"
|
||
}
|
||
|
||
start_gui_scene() {
|
||
if [ "$GUI_WITH_SERVER" = "1" ]; then
|
||
start_server
|
||
else
|
||
info "跳过 Gin(OPSXSHELL_GUI_WITH_SERVER=0)"
|
||
fi
|
||
start_desktop
|
||
echo
|
||
success "GUI 场景就绪 → 桌面窗口 OpsxShell"
|
||
}
|
||
|
||
start_all_scene() {
|
||
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 ;;
|
||
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 ;;
|
||
clean-apps) run_clean_apps "${@:2}" ;;
|
||
help|-h|--help) usage ;;
|
||
*) error "未知命令: $cmd"; usage; exit 1 ;;
|
||
esac
|