chore: initial commit of ai site platform
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
96
scripts/linux/common.sh
Normal file
96
scripts/linux/common.sh
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
# 宇信达智建 — Linux 公共函数(宿主机物理目录模式,不依赖 Docker)
|
||||
# shellcheck disable=SC2034
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
RUNTIME="$ROOT/.runtime"
|
||||
LOG_DIR="$RUNTIME/logs"
|
||||
PID_DIR="$RUNTIME/pids"
|
||||
VENV_DIR="$RUNTIME/venv"
|
||||
PGDATA="$RUNTIME/postgres"
|
||||
UPLOAD_DIR="$ROOT/platform/data/uploads"
|
||||
PG_PORT="${AJZ_PG_PORT:-5432}"
|
||||
PG_USER="${AJZ_PG_USER:-platform}"
|
||||
PG_PASS="${AJZ_PG_PASS:-platform}"
|
||||
PG_DB="${AJZ_PG_DB:-platform}"
|
||||
|
||||
mkdir -p "$LOG_DIR" "$PID_DIR" "$UPLOAD_DIR"
|
||||
|
||||
c_info() { printf '\033[36m==> %s\033[0m\n' "$*"; }
|
||||
c_ok() { printf '\033[32m OK %s\033[0m\n' "$*"; }
|
||||
c_warn() { printf '\033[33m !! %s\033[0m\n' "$*"; }
|
||||
c_err() { printf '\033[31m ERR %s\033[0m\n' "$*" >&2; }
|
||||
|
||||
have_cmd() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
docker_ready() {
|
||||
have_cmd docker || return 1
|
||||
docker info >/dev/null 2>&1
|
||||
}
|
||||
|
||||
port_listen() {
|
||||
local port="$1"
|
||||
if have_cmd ss; then
|
||||
ss -ltn "( sport = :$port )" 2>/dev/null | grep -q ":$port"
|
||||
elif have_cmd netstat; then
|
||||
netstat -ltn 2>/dev/null | grep -q "[.:]$port "
|
||||
else
|
||||
(echo >/dev/tcp/127.0.0.1/"$port") >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
wait_port() {
|
||||
local port="$1" name="$2" sec="${3:-40}"
|
||||
local i=0
|
||||
while (( i < sec * 2 )); do
|
||||
if port_listen "$port"; then
|
||||
c_ok "$name 端口 $port 已监听"
|
||||
return 0
|
||||
fi
|
||||
sleep 0.5
|
||||
((i++)) || true
|
||||
done
|
||||
c_warn "$name 端口 $port 未在 ${sec}s 内就绪"
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_http() {
|
||||
local url="$1" name="$2" sec="${3:-40}"
|
||||
local i=0
|
||||
while (( i < sec * 2 )); do
|
||||
if curl -fsS --max-time 2 "$url" >/dev/null 2>&1; then
|
||||
c_ok "$name 就绪 $url"
|
||||
return 0
|
||||
fi
|
||||
sleep 0.5
|
||||
((i++)) || true
|
||||
done
|
||||
c_warn "$name 未在 ${sec}s 内就绪: $url"
|
||||
return 1
|
||||
}
|
||||
|
||||
detect_os() {
|
||||
if [[ -f /etc/os-release ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
. /etc/os-release
|
||||
echo "${ID:-unknown}"
|
||||
else
|
||||
echo "unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
sudo_run() {
|
||||
if [[ "$(id -u)" -eq 0 ]]; then
|
||||
"$@"
|
||||
elif have_cmd sudo; then
|
||||
sudo "$@"
|
||||
else
|
||||
c_err "需要 root 或 sudo 才能安装系统包: $*"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
export ROOT RUNTIME LOG_DIR PID_DIR VENV_DIR PGDATA UPLOAD_DIR
|
||||
export PG_PORT PG_USER PG_PASS PG_DB
|
||||
224
scripts/linux/setup-host.sh
Normal file
224
scripts/linux/setup-host.sh
Normal file
@@ -0,0 +1,224 @@
|
||||
#!/usr/bin/env bash
|
||||
# 在宿主机物理目录自动配置环境(无 Docker)
|
||||
# 数据落盘:$ROOT/.runtime/{postgres,venv,logs,pids} 与 platform/data/uploads
|
||||
# shellcheck disable=SC1091
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
c_info "根目录(物理): $ROOT"
|
||||
c_info "运行时目录: $RUNTIME"
|
||||
c_info "上传目录: $UPLOAD_DIR"
|
||||
|
||||
need_pkgs=()
|
||||
have_cmd curl || need_pkgs+=(curl)
|
||||
have_cmd git || need_pkgs+=(git)
|
||||
have_cmd go || need_pkgs+=(golang-go)
|
||||
have_cmd python3 || need_pkgs+=(python3)
|
||||
have_cmd pip3 || need_pkgs+=(python3-pip)
|
||||
python3 -c "import venv" 2>/dev/null || need_pkgs+=(python3-venv)
|
||||
have_cmd node || need_pkgs+=(nodejs)
|
||||
have_cmd npm || need_pkgs+=(npm)
|
||||
have_cmd psql || need_pkgs+=(postgresql-client)
|
||||
have_cmd initdb || have_cmd pg_ctl || need_pkgs+=(postgresql)
|
||||
|
||||
install_pkgs() {
|
||||
local os
|
||||
os="$(detect_os)"
|
||||
c_info "安装系统包 (OS=$os): ${need_pkgs[*]}"
|
||||
case "$os" in
|
||||
ubuntu|debian|linuxmint|pop)
|
||||
sudo_run apt-get update -y
|
||||
local pkgs=()
|
||||
for p in "${need_pkgs[@]}"; do
|
||||
case "$p" in
|
||||
golang-go) pkgs+=(golang-go) ;;
|
||||
python3) pkgs+=(python3 python3-venv python3-dev) ;;
|
||||
python3-pip) pkgs+=(python3-pip) ;;
|
||||
python3-venv) pkgs+=(python3-venv) ;;
|
||||
nodejs) pkgs+=(nodejs) ;;
|
||||
npm) pkgs+=(npm) ;;
|
||||
postgresql) pkgs+=(postgresql postgresql-contrib) ;;
|
||||
postgresql-client) pkgs+=(postgresql-client) ;;
|
||||
*) pkgs+=("$p") ;;
|
||||
esac
|
||||
done
|
||||
sudo_run apt-get install -y "${pkgs[@]}"
|
||||
;;
|
||||
fedora|rhel|centos|rocky|almalinux)
|
||||
local pkgs=()
|
||||
for p in "${need_pkgs[@]}"; do
|
||||
case "$p" in
|
||||
golang-go) pkgs+=(golang) ;;
|
||||
python3-pip) pkgs+=(python3-pip) ;;
|
||||
python3-venv) pkgs+=(python3) ;;
|
||||
postgresql) pkgs+=(postgresql-server postgresql) ;;
|
||||
postgresql-client) pkgs+=(postgresql) ;;
|
||||
*) pkgs+=("$p") ;;
|
||||
esac
|
||||
done
|
||||
if have_cmd dnf; then sudo_run dnf install -y "${pkgs[@]}"
|
||||
else sudo_run yum install -y "${pkgs[@]}"; fi
|
||||
;;
|
||||
arch|manjaro)
|
||||
local pkgs=()
|
||||
for p in "${need_pkgs[@]}"; do
|
||||
case "$p" in
|
||||
golang-go) pkgs+=(go) ;;
|
||||
python3) pkgs+=(python) ;;
|
||||
python3-pip) pkgs+=(python-pip) ;;
|
||||
python3-venv) ;;
|
||||
postgresql-client) pkgs+=(postgresql) ;;
|
||||
*) pkgs+=("$p") ;;
|
||||
esac
|
||||
done
|
||||
sudo_run pacman -Sy --noconfirm "${pkgs[@]}"
|
||||
;;
|
||||
*)
|
||||
c_err "未识别发行版 $os,请手动安装: go python3 node npm postgresql curl"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
if ((${#need_pkgs[@]} > 0)); then
|
||||
install_pkgs
|
||||
else
|
||||
c_ok "系统依赖已满足"
|
||||
fi
|
||||
|
||||
missing=()
|
||||
for c in go python3 curl; do have_cmd "$c" || missing+=("$c"); done
|
||||
have_cmd node || have_cmd nodejs || missing+=("node")
|
||||
have_cmd npm || missing+=("npm")
|
||||
if ((${#missing[@]} > 0)); then
|
||||
c_err "仍缺少命令: ${missing[*]}"
|
||||
exit 1
|
||||
fi
|
||||
c_ok "$(go version)"
|
||||
|
||||
c_info "配置 Python venv → $VENV_DIR"
|
||||
if [[ ! -d "$VENV_DIR" ]]; then
|
||||
python3 -m venv "$VENV_DIR"
|
||||
fi
|
||||
# shellcheck source=/dev/null
|
||||
source "$VENV_DIR/bin/activate"
|
||||
pip install -U pip wheel >/dev/null
|
||||
pip install -r "$ROOT/ai-service/requirements.txt"
|
||||
pip install -r "$ROOT/agent_sdk/requirements.txt"
|
||||
c_ok "venv 依赖已安装"
|
||||
|
||||
c_info "安装前端依赖 → $ROOT/web/node_modules"
|
||||
if [[ ! -d "$ROOT/web/node_modules" ]]; then
|
||||
(cd "$ROOT/web" && npm install)
|
||||
else
|
||||
c_ok "web/node_modules 已存在"
|
||||
fi
|
||||
|
||||
find_pg_bin() {
|
||||
local name="$1"
|
||||
if have_cmd "$name"; then command -v "$name"; return; fi
|
||||
local d
|
||||
for d in /usr/lib/postgresql/*/bin /usr/pgsql-*/bin; do
|
||||
if [[ -x "$d/$name" ]]; then echo "$d/$name"; return; fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
INITDB="$(find_pg_bin initdb || true)"
|
||||
PG_CTL="$(find_pg_bin pg_ctl || true)"
|
||||
PSQL="$(find_pg_bin psql || true)"
|
||||
|
||||
ensure_local_postgres() {
|
||||
if [[ -z "$INITDB" || -z "$PG_CTL" || -z "$PSQL" ]]; then
|
||||
c_warn "未找到 initdb/pg_ctl/psql,跳过本地库初始化(请自备 Postgres)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 已有可连库则复用
|
||||
if PGPASSWORD="$PG_PASS" "$PSQL" -h 127.0.0.1 -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -c "SELECT 1" >/dev/null 2>&1; then
|
||||
c_ok "复用已有 Postgres $PG_USER@$PG_DB:$PG_PORT"
|
||||
PGPASSWORD="$PG_PASS" "$PSQL" -h 127.0.0.1 -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" \
|
||||
-c "ALTER USER $PG_USER CREATEDB;" >/dev/null 2>&1 || true
|
||||
return 0
|
||||
fi
|
||||
|
||||
mkdir -p "$PGDATA" "$RUNTIME/pg_sockets"
|
||||
if [[ ! -f "$PGDATA/PG_VERSION" ]]; then
|
||||
c_info "initdb → $PGDATA (物理目录)"
|
||||
"$INITDB" -D "$PGDATA" --auth=trust -U postgres
|
||||
{
|
||||
echo ""
|
||||
echo "listen_addresses = '127.0.0.1'"
|
||||
echo "port = $PG_PORT"
|
||||
echo "unix_socket_directories = '$RUNTIME/pg_sockets'"
|
||||
} >> "$PGDATA/postgresql.conf"
|
||||
fi
|
||||
|
||||
if ! "$PG_CTL" -D "$PGDATA" status >/dev/null 2>&1; then
|
||||
c_info "启动本地 Postgres"
|
||||
"$PG_CTL" -D "$PGDATA" -l "$LOG_DIR/postgres.log" -o "-k $RUNTIME/pg_sockets" start
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
local sock="$RUNTIME/pg_sockets"
|
||||
"$PSQL" -h "$sock" -U postgres -d postgres -v ON_ERROR_STOP=1 <<SQL
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '$PG_USER') THEN
|
||||
CREATE ROLE $PG_USER LOGIN PASSWORD '$PG_PASS' CREATEDB;
|
||||
ELSE
|
||||
ALTER ROLE $PG_USER WITH LOGIN PASSWORD '$PG_PASS' CREATEDB;
|
||||
END IF;
|
||||
END
|
||||
\$\$;
|
||||
SELECT 'ok' FROM pg_database WHERE datname = '$PG_DB';
|
||||
SQL
|
||||
if ! "$PSQL" -h "$sock" -U postgres -d postgres -tc "SELECT 1 FROM pg_database WHERE datname='$PG_DB'" | grep -q 1; then
|
||||
"$PSQL" -h "$sock" -U postgres -d postgres -c "CREATE DATABASE $PG_DB OWNER $PG_USER;"
|
||||
fi
|
||||
|
||||
# 校验 TCP
|
||||
if ! PGPASSWORD="$PG_PASS" "$PSQL" -h 127.0.0.1 -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -c "SELECT 1" >/dev/null 2>&1; then
|
||||
# 补充密码认证
|
||||
cat > "$PGDATA/pg_hba.conf" <<EOF
|
||||
local all all trust
|
||||
host all all 127.0.0.1/32 trust
|
||||
host all all ::1/128 trust
|
||||
EOF
|
||||
"$PG_CTL" -D "$PGDATA" reload || "$PG_CTL" -D "$PGDATA" restart -m fast
|
||||
sleep 1
|
||||
fi
|
||||
PGPASSWORD="$PG_PASS" "$PSQL" -h 127.0.0.1 -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -c "SELECT 1" >/dev/null
|
||||
c_ok "Postgres 就绪 (物理 data=$PGDATA)"
|
||||
}
|
||||
|
||||
ensure_local_postgres
|
||||
|
||||
c_info "编译 platform / gateway"
|
||||
(cd "$ROOT/platform" && go build -o platform .)
|
||||
(cd "$ROOT/gateway" && go build -o gateway .)
|
||||
c_ok "二进制已生成"
|
||||
|
||||
if [[ ! -f "$ROOT/ai-service/sample_inventory.xlsx" ]]; then
|
||||
(cd "$ROOT/ai-service" && "$VENV_DIR/bin/python" make_sample_excel.py) || true
|
||||
fi
|
||||
|
||||
date -Iseconds > "$RUNTIME/setup.ok"
|
||||
cat > "$RUNTIME/env.sh" <<EOF
|
||||
# 由 setup-host.sh 生成 — 物理目录运行时
|
||||
export AJZ_ROOT="$ROOT"
|
||||
export AJZ_RUNTIME="$RUNTIME"
|
||||
export PATH="$VENV_DIR/bin:\$PATH"
|
||||
export PGHOST=127.0.0.1
|
||||
export PGPORT=$PG_PORT
|
||||
export PGUSER=$PG_USER
|
||||
export PGPASSWORD=$PG_PASS
|
||||
export PGDATABASE=$PG_DB
|
||||
EOF
|
||||
|
||||
c_info "环境配置完成(无 Docker,全部落在物理目录)"
|
||||
c_ok "下一步: ./start.sh"
|
||||
100
scripts/linux/start-host.sh
Normal file
100
scripts/linux/start-host.sh
Normal file
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env bash
|
||||
# 宿主机物理目录模式(Docker 不可用或 ./start.sh --host)
|
||||
# shellcheck disable=SC1091
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
REBUILD=0
|
||||
NO_BROWSER=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--rebuild|-Rebuild) REBUILD=1 ;;
|
||||
--no-browser) NO_BROWSER=1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
start_bg() {
|
||||
local name="$1" logfile="$2"
|
||||
shift 2
|
||||
local pidfile="$PID_DIR/$name.pid"
|
||||
if [[ -f "$pidfile" ]] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
|
||||
c_warn "$name 已在运行 pid=$(cat "$pidfile")"
|
||||
return 0
|
||||
fi
|
||||
c_info "启动 $name"
|
||||
nohup "$@" >>"$logfile" 2>&1 &
|
||||
echo $! >"$pidfile"
|
||||
c_ok "$name pid=$(cat "$pidfile") 日志 $logfile"
|
||||
}
|
||||
|
||||
ensure_postgres_running() {
|
||||
local pg_ctl=""
|
||||
pg_ctl="$(command -v pg_ctl 2>/dev/null || true)"
|
||||
if [[ -z "$pg_ctl" ]]; then
|
||||
for d in /usr/lib/postgresql/*/bin /usr/pgsql-*/bin; do
|
||||
[[ -x "$d/pg_ctl" ]] && pg_ctl="$d/pg_ctl" && break
|
||||
done
|
||||
fi
|
||||
if [[ -n "$pg_ctl" && -f "$PGDATA/PG_VERSION" ]]; then
|
||||
if ! "$pg_ctl" -D "$PGDATA" status >/dev/null 2>&1; then
|
||||
c_info "启动物理目录 Postgres → $PGDATA"
|
||||
mkdir -p "$RUNTIME/pg_sockets"
|
||||
"$pg_ctl" -D "$PGDATA" -l "$LOG_DIR/postgres.log" -o "-k $RUNTIME/pg_sockets" start
|
||||
sleep 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
c_warn "宿主机模式:数据落在 $RUNTIME"
|
||||
if [[ ! -f "$RUNTIME/setup.ok" || "$REBUILD" -eq 1 ]]; then
|
||||
bash "$SCRIPT_DIR/setup-host.sh"
|
||||
fi
|
||||
# shellcheck source=/dev/null
|
||||
[[ -f "$RUNTIME/env.sh" ]] && source "$RUNTIME/env.sh"
|
||||
|
||||
ensure_postgres_running
|
||||
|
||||
if [[ "$REBUILD" -eq 1 || ! -x "$ROOT/platform/platform" ]]; then
|
||||
(cd "$ROOT/platform" && go build -o platform .)
|
||||
fi
|
||||
if [[ "$REBUILD" -eq 1 || ! -x "$ROOT/gateway/gateway" ]]; then
|
||||
(cd "$ROOT/gateway" && go build -o gateway .)
|
||||
fi
|
||||
|
||||
PY="$VENV_DIR/bin/python"
|
||||
[[ -x "$PY" ]] || PY="$(command -v python3)"
|
||||
|
||||
start_bg platform "$LOG_DIR/platform.log" \
|
||||
bash -c "cd '$ROOT/platform' && exec ./platform -f etc/platform.yaml"
|
||||
start_bg ai "$LOG_DIR/ai.log" \
|
||||
bash -c "cd '$ROOT/ai-service' && exec '$PY' -m uvicorn app:app --host 127.0.0.1 --port 8001"
|
||||
start_bg gateway "$LOG_DIR/gateway.log" \
|
||||
bash -c "cd '$ROOT/gateway' && exec ./gateway -f etc/gateway.yaml"
|
||||
start_bg web "$LOG_DIR/web.log" \
|
||||
bash -c "cd '$ROOT/web' && exec npm run dev -- --host 127.0.0.1 --port 5173"
|
||||
|
||||
wait_port 8888 platform 45 || true
|
||||
wait_http "http://127.0.0.1:8001/health" ai-service 45 || true
|
||||
wait_http "http://127.0.0.1:8180/gateway/health" gateway 45 || true
|
||||
wait_port 5173 web 60 || true
|
||||
|
||||
cat <<EOF
|
||||
|
||||
========================================
|
||||
前端 http://127.0.0.1:5173
|
||||
网关 http://127.0.0.1:8180
|
||||
账号 demo / demo123
|
||||
模式 宿主机物理目录
|
||||
停止 ./stop.sh
|
||||
========================================
|
||||
|
||||
EOF
|
||||
|
||||
if [[ "$NO_BROWSER" -eq 0 ]] && have_cmd xdg-open; then
|
||||
xdg-open "http://127.0.0.1:5173" >/dev/null 2>&1 || true
|
||||
fi
|
||||
52
scripts/linux/stop-host.sh
Normal file
52
scripts/linux/stop-host.sh
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# 停止宿主机模式进程
|
||||
# shellcheck disable=SC1091
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
# shellcheck source=common.sh
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh"
|
||||
|
||||
c_info "停止宿主机模式服务..."
|
||||
|
||||
stop_pidfile() {
|
||||
local name="$1"
|
||||
local f="$PID_DIR/$name.pid"
|
||||
if [[ -f "$f" ]]; then
|
||||
local pid
|
||||
pid="$(cat "$f")"
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
kill "$pid" 2>/dev/null || true
|
||||
sleep 0.3
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
rm -f "$f"
|
||||
fi
|
||||
}
|
||||
|
||||
for n in web gateway ai platform; do
|
||||
stop_pidfile "$n"
|
||||
done
|
||||
|
||||
for port in 5173 8180 8001 8888; do
|
||||
if have_cmd fuser; then
|
||||
fuser -k "${port}/tcp" >/dev/null 2>&1 || true
|
||||
elif have_cmd lsof; then
|
||||
pids="$(lsof -t -iTCP:"$port" -sTCP:LISTEN 2>/dev/null || true)"
|
||||
[[ -n "$pids" ]] && kill $pids 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${1:-}" == "--with-db" ]]; then
|
||||
pg_ctl_bin="$(command -v pg_ctl 2>/dev/null || true)"
|
||||
if [[ -z "$pg_ctl_bin" ]]; then
|
||||
for d in /usr/lib/postgresql/*/bin /usr/pgsql-*/bin; do
|
||||
[[ -x "$d/pg_ctl" ]] && pg_ctl_bin="$d/pg_ctl" && break
|
||||
done
|
||||
fi
|
||||
if [[ -n "$pg_ctl_bin" && -f "$PGDATA/PG_VERSION" ]]; then
|
||||
"$pg_ctl_bin" -D "$PGDATA" stop -m fast || true
|
||||
fi
|
||||
fi
|
||||
|
||||
c_ok "已停止。数据仍在: $RUNTIME"
|
||||
Reference in New Issue
Block a user