Files
opsxshell/scripts/run.sh
T
Logan.Li 5eb11826db
CI / Lint & TypeCheck (push) Canceled after 0s
CI / Test (push) Canceled after 0s
CI / Tauri Foundation Acceptance (push) Canceled after 0s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Build (ubuntu-latest) (push) Canceled after 0s
CI / Build (windows-latest) (push) Canceled after 0s
feat(site): 并入 Gin 官网与文档服务,对齐桌面产品形态
- apps/server:Gin embed 官网 + /docs + /health + /download(无操作 API)
- run.sh 重写 web/gui 场景;.gitignore 排除 server 二进制与挂载的安装包
- README/AGENTS/CLAUDE 同步「浏览器只看官网、操作走桌面」的产品形态说明
- 终端视口背景变量与全宽终端边缘 hairline 修正(serial/terminal/i18n 微调)
2026-10-10 13:35:01 +08:00

327 lines
9.9 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
# OpsXShell launcher — scene profiles(对齐 opsxdb / opsxapi)
#
# ./run.sh # = web:仅 Gin 官网 + docs(:8789)
# ./run.sh web # 同上(浏览器不当操作客户端)
# ./run.sh gui # 桌面唯一操作入口:Tauri;默认也起 Gin
# ./run.sh all # 同 gui(兼容旧命令)
# ./run.sh server|desktop
# ./run.sh stop|restart|status|help
#
# 产品形态:浏览器 = 官网/docs;操作 = 桌面 Tauri(src/ + src-tauri/)。
# Vite :1420 仅作桌面 WebView 源,不要用浏览器当工作台。
set -euo pipefail
PROJECT_NAME="OpsXShell"
SERVER_PORT="${OPSXSHELL_SERVER_PORT:-8789}"
WEB_PORT="${OPSXSHELL_WEB_PORT:-1420}"
SERVER_DIR="apps/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"
# 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() {
info "启动桌面 OpsXShell ..."
: >"$DESKTOP_LOG"
ensure_npm
# 清理旧进程
pkill -f 'target/debug/opsxshell' 2>/dev/null || true
pkill -f 'target/debug/OpsXShell' 2>/dev/null || true
stop_detached "$DESKTOP_LABEL"
# tauri dev 会自己起 Vite;detached 跑 npm run dev
start_detached "$DESKTOP_LABEL" "$DESKTOP_LOG" \
"export PATH=\"$NODE_BIN_DIR:/opt/homebrew/bin:/usr/local/bin:\$HOME/.cargo/bin:\$PATH\"; cd \"$SCRIPT_DIR\" && exec npm run dev"
local i=0
while true; do
if pgrep -f 'target/debug/opsxshell' >/dev/null 2>&1 \
|| pgrep -f 'target/debug/OpsXShell' >/dev/null 2>&1; then
success "桌面 OpsXShell 已启动"
info "应用日志: tail -f \"$APP_LOG\""
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 180 ]; 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/opsxshell' 2>/dev/null || true
pkill -f 'target/debug/OpsXShell' 2>/dev/null || true
pkill -f '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 "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 pgrep -f 'target/debug/opsxshell' >/dev/null 2>&1 \
|| pgrep -f 'target/debug/OpsXShell' >/dev/null 2>&1; 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 # 桌面唯一操作入口(Tauri;默认也起 Gin)
./run.sh all # 同 gui(兼容旧名)
./run.sh server # 只 Gin(官网/docs)
./run.sh desktop # 只 Desktop
./run.sh stop | restart | status | help
端口: 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 ;;
help|-h|--help) usage ;;
*) error "未知命令: $cmd"; usage; exit 1 ;;
esac