Files
opsxapi/run.sh
T
logan 39f4d5addc 桌面应用图标替换为 B-brackets 设计(深藏青 + 白/蓝花括号)
- icon.svg 取自 opsx-icon-options/api/B-brackets.svg,全套光栅重生成:
  icon.icns(iconutil)、icon.ico(PNG-in-ICO 6 尺寸)、16-1024 PNG、Windows Square*/StoreLogo
- 替换 Tauri 脚手架默认五角星图标;GUI 重启实测 Dock 图标已生效
- 服务全量重启:server 8787(官网/文档 200)+ web 5173 + 桌面 GUI
2026-09-20 00:14:32 +08:00

285 lines
8.3 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
# OpsxAPI launcher — scene profiles, not random process soup
#
# ./run.sh # = web
# ./run.sh web # Gin 代理 + Vite(浏览器)
# ./run.sh gui # Vite + Tauri 桌面(Gin 可选;默认也拉起方便对照)
# ./run.sh all # Gin + Vite + Desktop
# ./run.sh server|web-only|desktop # 单组件
# ./run.sh stop|restart|status|help
#
# 设计:一份 Vite UI,Web/GUI 共用;Gin 对 Web 刚需,对 GUI 可选。
set -euo pipefail
PROJECT_NAME="OpsxAPI"
SERVER_PORT="${OPSXAPI_SERVER_PORT:-8787}"
WEB_PORT="${OPSXAPI_WEB_PORT:-5173}"
SERVER_DIR="apps/server"
SERVER_BIN="opsxapi-server"
SERVER_LABEL="com.opsx.api.server"
WEB_LABEL="com.opsx.api.web"
DESKTOP_LABEL="com.opsx.api.desktop"
# gui 默认是否顺带起 Gin(可用 OPSXAPI_GUI_WITH_SERVER=0 关掉)
GUI_WITH_SERVER="${OPSXAPI_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"
export PATH="/Users/logan/.nvm/versions/node/v24.19.0/bin:/opt/homebrew/bin:/usr/local/bin:${HOME}/.cargo/bin:$PATH"
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() {
if [ ! -d "$SCRIPT_DIR/node_modules" ]; then
warning "缺少 node_modules,正在 npm install..."
npm install
fi
if [ ! -d "$SCRIPT_DIR/apps/desktop/node_modules" ]; then
warning "desktop 缺少依赖,正在安装..."
npm --prefix "$SCRIPT_DIR/apps/desktop" install
fi
}
build_server() {
info "编译 Gin 服务..."
(cd "$SCRIPT_DIR/$SERVER_DIR" && go build -o "$SCRIPT_DIR/$SERVER_DIR/bin/$SERVER_BIN" ./cmd/opsxapi)
}
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
# 默认仅本机;对外需 OPSXAPI_BIND_PUBLIC=1(或显式 OPSXAPI_ADDR)
local bind_env=""
if [ -n "${OPSXAPI_ADDR:-}" ]; then
bind_env="OPSXAPI_ADDR=$OPSXAPI_ADDR"
elif [ "${OPSXAPI_BIND_PUBLIC:-0}" = "1" ] || [ "${OPSXAPI_BIND_PUBLIC:-}" = "true" ]; then
bind_env="OPSXAPI_BIND_PUBLIC=1"
else
bind_env="OPSXAPI_ADDR=127.0.0.1:$SERVER_PORT"
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 "后端已停止"
}
start_web() {
if curl -fsS "http://127.0.0.1:$WEB_PORT/" >/dev/null 2>&1; then
success "Web 已在运行 :$WEB_PORT"
return 0
fi
info "启动 Web UI :$WEB_PORT ..."
: >"$WEB_LOG"
ensure_npm
kill_port "$WEB_PORT"
start_detached "$WEB_LABEL" "$WEB_LOG" \
"export PATH=\"/Users/logan/.nvm/versions/node/v24.19.0/bin:/opt/homebrew/bin:/usr/local/bin:\$PATH\"; cd \"$SCRIPT_DIR\" && exec npm run dev -w @opsxapi/web"
wait_http "http://127.0.0.1:$WEB_PORT/" "Web UI" 80
success "Web UP http://127.0.0.1:$WEB_PORT"
}
stop_web() {
stop_detached "$WEB_LABEL"
kill_port "$WEB_PORT"
success "Web 已停止"
}
start_desktop() {
# GUI 依赖同一份 Vite;先保证 5173
start_web
info "启动桌面 OpsxAPI ..."
: >"$DESKTOP_LOG"
ensure_npm
# 清掉旧的 debug 进程(二进制名 OpsxAPI)
pkill -f 'target/debug/OpsxAPI' 2>/dev/null || true
pkill -f 'target/debug/opsxapi-desktop' 2>/dev/null || true
stop_detached "$DESKTOP_LABEL"
start_detached "$DESKTOP_LABEL" "$DESKTOP_LOG" \
"export PATH=\"/Users/logan/.nvm/versions/node/v24.19.0/bin:/opt/homebrew/bin:/usr/local/bin:\$HOME/.cargo/bin:\$PATH\"; cd \"$SCRIPT_DIR/apps/desktop\" && exec npm run tauri -- dev"
local i=0
while true; do
if pgrep -f 'target/debug/OpsxAPI' >/dev/null 2>&1 \
|| pgrep -f 'target/debug/opsxapi-desktop' >/dev/null 2>&1; then
success "桌面 OpsxAPI 已启动(Dock 名应为 OpsxAPI)"
return 0
fi
if grep -Eiq 'error:|panicked|could not compile' "$DESKTOP_LOG" 2>/dev/null; then
error "桌面启动失败,见 $DESKTOP_LOG"
tail -n 40 "$DESKTOP_LOG" || true
return 1
fi
i=$((i + 1))
if [ "$i" -ge 120 ]; then
error "桌面启动超时,见 $DESKTOP_LOG"
tail -n 40 "$DESKTOP_LOG" || true
return 1
fi
sleep 1
done
}
stop_desktop() {
stop_detached "$DESKTOP_LABEL"
pkill -f 'target/debug/OpsxAPI' 2>/dev/null || true
pkill -f 'target/debug/opsxapi-desktop' 2>/dev/null || true
pkill -f 'apps/desktop/node_modules/.bin/tauri' 2>/dev/null || true
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 "web :$WEB_PORT UP http://127.0.0.1:$WEB_PORT"
else
warning "web :$WEB_PORT DOWN"
fi
if pgrep -f 'target/debug/OpsxAPI' >/dev/null 2>&1 \
|| pgrep -f 'target/debug/opsxapi-desktop' >/dev/null 2>&1; then
success "gui OpsxAPI UP"
else
warning "gui OpsxAPI DOWN"
fi
}
usage() {
cat <<USAGE
$PROJECT_NAME 启动脚本(场景 profile)
./run.sh # 同 web
./run.sh web # 浏览器:Gin + Vite
./run.sh gui # 桌面:Vite + Tauri(默认也起 Gin,OPSXAPI_GUI_WITH_SERVER=0 可关)
./run.sh all # Gin + Vite + Desktop
./run.sh server # 只 Gin
./run.sh web-only # 只 Vite
./run.sh desktop # 只 Desktop(会确保 Vite)
./run.sh stop | restart | status | help
端口: server $SERVER_PORT / web $WEB_PORT
日志: logs/{server,web,desktop}.log
USAGE
}
start_web_scene() {
start_server
start_web
echo
success "Web 场景就绪 → http://127.0.0.1:$WEB_PORT"
}
start_gui_scene() {
if [ "$GUI_WITH_SERVER" = "1" ]; then
start_server
else
info "跳过 Gin(OPSXAPI_GUI_WITH_SERVER=0);桌面 HTTP 走 tauri-native"
fi
start_desktop
echo
success "GUI 场景就绪 → 桌面窗口 OpsxAPI"
}
start_all_scene() {
start_server
start_web
start_desktop
echo
success "All 就绪:Web + Desktop + Gin"
}
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) start_web ;;
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