#!/usr/bin/env bash # ============================================================================= # scense 应用一键部署脚本(元景项目) # 唯一部署单元:一个入口 app/scense.py + 一个端口 ${SERVER_PORT:-9380} # 门禁:部署完成后 30s 端口监听门禁——30s 内未监听自动重启一次兜底, # 再等 30s 仍失败才输出诊断并以非零退出(不静默通过)。 # ============================================================================= set -uo pipefail APP_NAME="scense" APP_PORT="${SERVER_PORT:-9380}" WORKDIR="$(cd "$(dirname "$0")" && pwd)" VENV_DIR="$WORKDIR/venv" PKGS_DIR="$WORKDIR/pkgs" LOGS_DIR="$WORKDIR/logs" ENV_FILE="$WORKDIR/.env" # 模块清单(基础引用 + 本应用业务模块,与 yuanjing_spec.json generated_modules 一致) FOUNDATION_MODULES=(apppublic sqlor ahserver bricks_for_python appbase rbac) BIZ_MODULES=(world scene entity script_engine world_snapshot world_sync) ALL_MODULES=("${FOUNDATION_MODULES[@]}" "${BIZ_MODULES[@]}") log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } die() { log "FATAL: $*"; exit 1; } # 0) 加载 .env(含 MYSQL_* / REDIS_* / SERVER_PORT / PASSWORD_KEY / SCENSE_ADMIN_*) if [ -f "$ENV_FILE" ]; then set -a; . "$ENV_FILE"; set +a fi MYSQL_ARGS=(--host="${MYSQL_HOST:-127.0.0.1}" --port="${MYSQL_PORT:-3306}" --user="${MYSQL_USER:-root}" --password="${MYSQL_PASSWORD:-}") DBNAME="${MYSQL_DATABASE:-scense}" # 1) 系统依赖 + venv command -v python3 >/dev/null 2>&1 || die "python3 not found" command -v mysql >/dev/null 2>&1 || die "mysql client not found" [ -d "$VENV_DIR" ] || python3 -m venv "$VENV_DIR" # shellcheck disable=SC1091 . "$VENV_DIR/bin/activate" pip install --quiet --upgrade pip pip install --quiet xls2ddl # 2) 克隆并安装模块(基础 + 业务) mkdir -p "$PKGS_DIR" for m in "${ALL_MODULES[@]}"; do if [ ! -d "$PKGS_DIR/$m" ]; then git clone "https://git.opencomputing.cn/yumoqing/$m.git" "$PKGS_DIR/$m" 2>/dev/null \ || git clone "git@git.opencomputing.cn:yumoqing/$m.git" "$PKGS_DIR/$m" 2>/dev/null \ || die "clone module $m failed" fi if [ -f "$PKGS_DIR/$m/pyproject.toml" ]; then (cd "$PKGS_DIR/$m" && pip install --quiet -e .) fi done pip install --quiet bricks_for_python # 3) 前端 wwwroot 软链(软链非 cp,保持同步) mkdir -p "$WORKDIR/wwwroot" for m in "${BIZ_MODULES[@]}"; do [ -d "$PKGS_DIR/$m/wwwroot" ] && ln -sfn "../pkgs/$m/wwwroot" "$WORKDIR/wwwroot/$m" done [ -d "$PKGS_DIR/bricks_for_python/dist" ] && ln -sfn "../pkgs/bricks_for_python/dist" "$WORKDIR/wwwroot/bricks" # 4) i18n 合并(merge_i18n.py 存在则执行,否则跳过不阻断) if [ -f "$PKGS_DIR/apppublic/tools/merge_i18n.py" ]; then python "$PKGS_DIR/apppublic/tools/merge_i18n.py" "$WORKDIR/wwwroot" "${BIZ_MODULES[@]}" \ || log "i18n merge skipped (non-fatal)" fi # 5) 数据库:建库 + 各模块 DDL + 应用级 DDL/seed mysql "${MYSQL_ARGS[@]}" -e "CREATE DATABASE IF NOT EXISTS \`${DBNAME}\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" \ || die "create database failed" for m in "${BIZ_MODULES[@]}"; do if [ -d "$PKGS_DIR/$m/models" ]; then (cd "$PKGS_DIR/$m/models" && json2ddl mysql . > "$WORKDIR/mysql.ddl.$m.sql" 2>/dev/null) \ || (cd "$PKGS_DIR/$m/models" && xls2ddl mysql . > "$WORKDIR/mysql.ddl.$m.sql" 2>/dev/null) || true if [ -f "$WORKDIR/mysql.ddl.$m.sql" ]; then mysql "${MYSQL_ARGS[@]}" "$DBNAME" < "$WORKDIR/mysql.ddl.$m.sql" || log "ddl $m warn" fi fi done [ -f "$WORKDIR/scripts/ddl.sql" ] && mysql "${MYSQL_ARGS[@]}" "$DBNAME" < "$WORKDIR/scripts/ddl.sql" [ -f "$WORKDIR/scripts/seed.sql" ] && mysql "${MYSQL_ARGS[@]}" "$DBNAME" < "$WORKDIR/scripts/seed.sql" # 6) CRUD 生成(xls2ui 从模块 json/ 生成 .dspy + .ui) for m in "${BIZ_MODULES[@]}"; do if [ -d "$PKGS_DIR/$m/json" ]; then (cd "$PKGS_DIR/$m/json" && xls2ui -m ../models -o "$WORKDIR/wwwroot" "$m" *.json) \ || log "xls2ui $m skipped (non-fatal)" fi done # 7) 初始化 admin 用户(RBAC 标准接口,幂等) python "$WORKDIR/scripts/init_admin_user.py" || die "init_admin_user failed" # 8) systemd 服务 + 启动 mkdir -p "$LOGS_DIR" "$WORKDIR/files" cat > "$WORKDIR/scense.service" </dev/null || true sudo systemctl daemon-reload 2>/dev/null || true sudo systemctl enable scense 2>/dev/null || true sudo systemctl restart scense 2>/dev/null \ || nohup "$VENV_DIR/bin/python" "$WORKDIR/app/scense.py" >> "$LOGS_DIR/scense.log" 2>&1 & # 9) 端口监听门禁(30s;未监听自动重启一次兜底再等 30s) wait_port() { local port="$1" tries="${2:-30}" for _ in $(seq 1 "$tries"); do if (exec 3<>"/dev/tcp/127.0.0.1/$port") 2>/dev/null; then exec 3>&- 3<&-; return 0 fi sleep 1 done return 1 } if ! wait_port "$APP_PORT" 30; then log "WARN: port $APP_PORT not listening in 30s, restart once (bootstrap)" sudo systemctl restart scense 2>/dev/null || true if ! wait_port "$APP_PORT" 30; then log "ERROR: port $APP_PORT still not listening after restart bootstrap" tail -n 50 "$LOGS_DIR/scense.log" 2>/dev/null || true exit 1 fi fi log "OK: scense is listening on port $APP_PORT"