diff --git a/.gitignore b/.gitignore index ced432d..1c17f63 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,23 @@ ai-service/.env *.exe~ +# 本地意见稿 / 不入库 +database_fastapi修改意见.md + +# 一键部署脚本(本机/运维侧保留,不推远程) +start.sh +stop.sh +restart.sh +pull-and-restart.sh +reload-config.sh +deploy-menu.sh +start.bat +start.ps1 +stop.bat +stop.ps1 +restart.bat +restart.ps1 +start-host.ps1 +scripts/lib-aijz-deploy.sh +scripts/linux/ + diff --git a/database_fastapi修改意见.md b/database_fastapi修改意见.md deleted file mode 100644 index 3bc3e27..0000000 --- a/database_fastapi修改意见.md +++ /dev/null @@ -1,107 +0,0 @@ -# database_fastapi 修改意见 - -> 针对文件:`E:\project\yh_one\new_project\yuhengyihao_client\yxd\app_fastapi\database_fastapi.py` -> 背景:需要与线上主库强一致;断网可记日志,恢复后合并回放至线上;主写在线上。 -> 对照能力:宇信达智建平台 `dbsync` 出站队列 / 冲突 / 对账(见 `ai建站/docs/数据同步-中间件.md`)。 - ---- - -## 落地状态(已实现) - -| 项 | 状态 | 位置 | -|----|------|------| -| 统一写网关 `apply_write` | ✅ | `db_write_gateway.py` | -| 写接口收口 | ✅ | insert / update / update/id / delete / sql(DML) | -| 库绑定防串库 | ✅ | `sync_binding.py` → `cache/db_bindings/bindings.json` | -| 配置 | ✅ | `sync_config.py`(环境变量) | -| 断网 pending | ✅ | 复用 `QueueDatabaseManager`,库名默认 `sync_pending` | -| 手动回放 | ✅ | `POST /database/sync/replay` | -| 绑定/状态 API | ✅ | `/database/sync/*` | -| 后台自动回放 | ✅ 可选 | `sync_replay_worker.py`,需 `YXD_SYNC_REPLAY_WORKER=1` | -| DDL / 批量 import 收口 | ⏳ 未做 | 仍可直写本地;后续按需收口 | -| 冲突队列 API | ⏳ 未做 | 回放失败即停保序,暂无独立 conflicts 列表 | - -**默认安全**:`YXD_SYNC_MODE` 未配且无 `YXD_ONLINE_API_BASE` 时为 **`local_only`**(只写本地,仍会 ensure 绑定信息)。 -配了 `YXD_ONLINE_API_BASE` 且未显式写 `YXD_SYNC_MODE` 时自动升为 **`online_primary`**。 - -### 环境变量 - -| 变量 | 说明 | 默认 | -|------|------|------| -| `YXD_SYNC_MODE` | `local_only` \| `online_primary` | `local_only`(有 ONLINE_BASE 且未设 mode 则升 primary) | -| `YXD_ONLINE_API_BASE` | 线上 API 根,如 `https://api.example.com` | 空 | -| `YXD_ONLINE_TIMEOUT_SEC` | 线上写超时 | `8` | -| `YXD_OFFLINE_POLICY` | `pending_log` \| `reject` | `pending_log` | -| `YXD_SYNC_ENFORCE_BINDING` | 是否强制已绑定 | `true` | -| `YXD_SYNC_AUTO_ENSURE_BINDING` | 写时自动 ensure | `true` | -| `YXD_SYNC_PENDING_QUEUE` | pending 队列库名 | `sync_pending` | -| `YXD_ONLINE_TOKEN_HEADER` | 透传 token 头 | `Authorization` | -| `YXD_SYNC_REPLAY_WORKER` | 启后台回放 | 关 | -| `YXD_SYNC_REPLAY_INTERVAL_SEC` | 回放间隔秒 | `30` | - -### 新增/改动文件 - -```text -yxd/app_fastapi/ - db_write_gateway.py # apply_write / replay_pending - sync_config.py - sync_binding.py - sync_context.py # Request → WriteContext - sync_replay_worker.py - database_fastapi.py # 写路由改走网关 + /sync/* API -``` - -### 同步 API - -- `GET /database/sync/status` -- `POST /database/sync/binding/ensure` -- `GET /database/sync/binding` -- `GET /database/sync/pending` -- `POST /database/sync/replay` body: `{ "limit": 50 }` - -请求头需带:`Database-ID`、`User-ID`(或应用已登录)、可选 `Tenant-ID` / `Authorization`。 - -### 写路径行为(`online_primary`) - -```text -联网:先写线上(Database-ID=online_db_id)→ 成功再写本地镜像 → synced=true -离线 pending_log:不写正式本地业务结果 → 入 sync_pending 队列 → pending=true -离线 reject:直接失败 -回放:按 id 升序提交线上 → 成功后写本地并删队列记录;失败即停保序 -``` - -绑定键:`(tenant_id, user_id, local_database_id) → online_db_id`(全局唯一)。 - ---- - -## 一、原状结论(改造前) - -| 项 | 原状 | -|----|------| -| 写入口 | `/database/permanent/table/data/insert\|update\|update/id\|delete`、`/permanent/sql` 等 | -| 落库方式 | 直接 `PermanentDatabaseManager`,**只写本地 SQLite** | -| 队列能力 | `queue_database_fastapi.py` 与 permanent **未打通** | -| 线上主写 / 断网日志 | **未实现** | - ---- - -## 二、目标模型 - -```text -联网:API 写 → 先写线上主库 → 再写本地镜像 -断网:API 写 → pending 日志(queue_database)→ 不 formal commit -恢复:有序回放到线上主 → 成功再对齐本地 -``` - -硬约束:主写在线上;断网日志只能回放到线上主;断网窗口非强一致。 - ---- - -## 三~十、设计说明(保留) - -原 P0–P4、响应约定、勿做事项、分阶段 M1–M4 已按上文落地;M5(SQL/导入全收口 + 冲突监控)待续。 - -与智建平台 dbsync:客户端强一致仍以「API 同步写线上」为准,不要只靠触发器异步 outbox 充当主路径。 - -本意见文档:`E:\project\ai建站\database_fastapi修改意见.md`。 -实现目录:`yuhengyihao_client\yxd\app_fastapi\`。 diff --git a/deploy-menu.sh b/deploy-menu.sh deleted file mode 100644 index a4f4900..0000000 --- a/deploy-menu.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -# 跳转到同级 ops 多项目菜单 -set -euo pipefail -ROOT="$(cd "$(dirname "$0")" && pwd)" -MENU="${OPS_MENU:-$ROOT/../ops/deploy.sh}" -if [ ! -f "$MENU" ]; then - echo "找不到菜单: $MENU" >&2 - echo "请把 ops/ 放在与本项目同级,或设置 OPS_MENU=/path/to/ops/deploy.sh" >&2 - exit 1 -fi -exec bash "$MENU" "$@" diff --git a/pull-and-restart.sh b/pull-and-restart.sh deleted file mode 100644 index 963ef75..0000000 --- a/pull-and-restart.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash -# AI 建站 — 拉取代码并重启 -# 用法:./pull-and-restart.sh -# 默认仓库:https://gitea.yuxindazhineng.com/whm/ai_site.git -# 环境变量: -# AIJZ_GIT_URL 覆盖仓库地址 -# GIT_BRANCH 分支(默认 main) -# FORCE_GIT_RESET 默认 1,与远程硬对齐 -set -euo pipefail -ROOT="$(cd "$(dirname "$0")" && pwd)" -cd "$ROOT" -# shellcheck disable=SC1091 -. "$ROOT/scripts/lib-aijz-deploy.sh" - -echo "========================================" -echo " AI 建站 拉取并重启" -echo " 路径: $ROOT" -echo " 仓库: ${AIJZ_GIT_URL:-https://gitea.yuxindazhineng.com/whm/ai_site.git}" -echo " 分支: ${GIT_BRANCH:-main}" -echo "========================================" - -ensure_docker -git_pull_hard -stack_down -sleep 1 -stack_up 1 -healthcheck -print_endpoints diff --git a/reload-config.sh b/reload-config.sh deleted file mode 100644 index 6b15069..0000000 --- a/reload-config.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash -# 轻量重载配置(不 rebuild 镜像、不重建 web/dist、不 down 整栈) -# 用法: -# ./reload-config.sh # 全部 -# ./reload-config.sh web # 容器 nginx(web/nginx.conf) -# ./reload-config.sh platform # restart platform(platform/etc/platform.docker.yaml) -# ./reload-config.sh gateway # restart gateway -# ./reload-config.sh ai # recreate ai(.env) -# ./reload-config.sh host-nginx # 宿主机 Nginx + 证书 + verify-root -# 行尾:LF -set -euo pipefail -ROOT="$(cd "$(dirname "$0")" && pwd)" -cd "$ROOT" -# shellcheck disable=SC1091 -. "$ROOT/scripts/lib-aijz-deploy.sh" - -usage() { - cat <&2 - usage >&2 - exit 1 - ;; -esac diff --git a/restart.bat b/restart.bat deleted file mode 100644 index 7bbea4c..0000000 --- a/restart.bat +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -chcp 65001 >nul -cd /d "%~dp0" -powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0restart.ps1" %* \ No newline at end of file diff --git a/restart.ps1 b/restart.ps1 deleted file mode 100644 index 6bc37cf..0000000 --- a/restart.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -# restart: stop then start -param( - [switch]$Rebuild, - [switch]$UseHost, - [switch]$NoBrowser, - [switch]$ShowConsole -) - -$ErrorActionPreference = "Continue" -$Root = $PSScriptRoot -Set-Location -LiteralPath $Root - -Write-Host "==> Restart: stopping..." -ForegroundColor Yellow -& (Join-Path $Root "scripts\Stop-Stack.ps1") -if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne $null) { - Write-Host " (stop reported leftovers; continuing restart anyway)" -ForegroundColor DarkGray -} - -Start-Sleep -Seconds 2 - -Write-Host "==> Restart: starting..." -ForegroundColor Cyan -& (Join-Path $Root "start.ps1") -Rebuild:$Rebuild -UseHost:$UseHost -NoBrowser:$NoBrowser -ShowConsole:$ShowConsole -exit $LASTEXITCODE diff --git a/restart.sh b/restart.sh deleted file mode 100644 index b89de9e..0000000 --- a/restart.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash -# AI 建站 — 重启(不拉代码:停 → 构建 → 起) -# 用法:./restart.sh [--rebuild] -set -euo pipefail -ROOT="$(cd "$(dirname "$0")" && pwd)" -cd "$ROOT" -# shellcheck disable=SC1091 -. "$ROOT/scripts/lib-aijz-deploy.sh" - -REBUILD=1 -for a in "$@"; do - case "$a" in - --no-rebuild) REBUILD=0 ;; - -h|--help) - echo "用法: $0 [--no-rebuild]" - exit 0 - ;; - esac -done - -echo "========================================" -echo " AI 建站 重启" -echo " 路径: $ROOT" -echo "========================================" - -ensure_docker -stack_down -sleep 1 -stack_up "$REBUILD" -healthcheck -print_endpoints diff --git a/scripts/lib-aijz-deploy.sh b/scripts/lib-aijz-deploy.sh deleted file mode 100644 index 68bef19..0000000 --- a/scripts/lib-aijz-deploy.sh +++ /dev/null @@ -1,441 +0,0 @@ -# shellcheck shell=bash -# AI 建站 Linux 部署公共函数。由 start/restart/stop/pull-and-restart 在 ROOT 下 source。 - -run_sudo() { - if [ "$(id -u)" -eq 0 ]; then - "$@" - else - sudo "$@" - fi -} - -ensure_runtime_dirs() { - mkdir -p "$ROOT/.runtime/pgdata" "$ROOT/.runtime/uploads" "$ROOT/.runtime/logs" \ - "$ROOT/.runtime/logs/generate" "$ROOT/web/dist" -} - -ensure_env_file() { - if [ ! -f "$ROOT/.env" ]; then - if [ -f "$ROOT/.env.example" ]; then - cp "$ROOT/.env.example" "$ROOT/.env" - echo "已从 .env.example 创建 .env,请按需填写 API Key。" - else - cat >"$ROOT/.env" <<'EOF' -LLM_PROVIDER=deepseek -DEEPSEEK_API_KEY= -MINIMAX_API_KEY= -EOF - echo "已创建默认 .env" - fi - fi - sed -i 's/\r$//' "$ROOT/.env" 2>/dev/null || true - # 供 compose 变量替换与本脚本健康检查使用 - set -a - # shellcheck disable=SC1091 - [ -f "$ROOT/.env" ] && . "$ROOT/.env" - set +a - export AIJZ_WEB_PUBLISH="${AIJZ_WEB_PUBLISH:-127.0.0.1:5173}" - export AIJZ_GATEWAY_PUBLISH="${AIJZ_GATEWAY_PUBLISH:-127.0.0.1:8180}" - export AIJZ_PLATFORM_PUBLISH="${AIJZ_PLATFORM_PUBLISH:-127.0.0.1:8888}" - export AIJZ_AI_PUBLISH="${AIJZ_AI_PUBLISH:-127.0.0.1:8001}" - export AIJZ_PG_PUBLISH="${AIJZ_PG_PUBLISH:-127.0.0.1:5432}" - export AIJZ_ENABLE_HOST_NGINX="${AIJZ_ENABLE_HOST_NGINX:-0}" - export AIJZ_DOMAIN="${AIJZ_DOMAIN:-}" - export AIJZ_PUBLIC_BASE_URL="${AIJZ_PUBLIC_BASE_URL:-}" -} - -# 若 .env 设了 AIJZ_PUBLIC_BASE_URL,同步到 platform.docker.yaml(挂载文件,restart platform 即生效) -apply_public_base_url() { - local base="${AIJZ_PUBLIC_BASE_URL:-}" - local pf="$ROOT/platform/etc/platform.docker.yaml" - [ -n "$base" ] || return 0 - [ -f "$pf" ] || return 0 - base="${base%/}" - local storage_base="${base}/api/v1/storage" - if grep -q '^PublicBaseURL:' "$pf"; then - sed -i "s|^PublicBaseURL:.*|PublicBaseURL: \"${base}\"|" "$pf" - fi - if grep -q '^ PublicBase:' "$pf"; then - sed -i "s|^ PublicBase:.*| PublicBase: \"${storage_base}\"|" "$pf" - fi - echo "已同步 PublicBaseURL -> ${base}(platform/etc/platform.docker.yaml)" -} - -sync_ssl_certs() { - local domain="${AIJZ_DOMAIN:-}" - [ -n "$domain" ] || return 0 - local ssl_dir="/etc/ssl/aijianzhan/$domain" - run_sudo mkdir -p "$ssl_dir" - if [ -f "$ROOT/nginx/$domain.pem" ] && [ -f "$ROOT/nginx/$domain.key" ]; then - run_sudo cp -f "$ROOT/nginx/$domain.pem" "$ssl_dir/fullchain.pem" - run_sudo cp -f "$ROOT/nginx/$domain.key" "$ssl_dir/privkey.pem" - elif [ -f "$ROOT/nginx/fullchain.pem" ] && [ -f "$ROOT/nginx/privkey.pem" ]; then - run_sudo cp -f "$ROOT/nginx/fullchain.pem" "$ROOT/nginx/privkey.pem" "$ssl_dir/" - elif [ -f "$ROOT/nginx/$domain/fullchain.pem" ] && [ -f "$ROOT/nginx/$domain/privkey.pem" ]; then - run_sudo cp -f "$ROOT/nginx/$domain/fullchain.pem" "$ROOT/nginx/$domain/privkey.pem" "$ssl_dir/" - else - echo "提示: 未找到 nginx/ 下证书,宿主机 HTTPS 需手动放入 $ssl_dir" >&2 - return 0 - fi - run_sudo chmod 644 "$ssl_dir/fullchain.pem" 2>/dev/null || true - run_sudo chmod 600 "$ssl_dir/privkey.pem" 2>/dev/null || true - echo "证书已同步 -> $ssl_dir" -} - -host_nginx_online() { - command -v systemctl >/dev/null 2>&1 || return 1 - systemctl is-active nginx >/dev/null 2>&1 && return 0 - systemctl is-active nginx.service >/dev/null 2>&1 && return 0 - return 1 -} - -ensure_host_nginx_started() { - command -v nginx >/dev/null 2>&1 || { - echo "错误: 未安装 nginx,无法启用 AIJZ_ENABLE_HOST_NGINX" >&2 - exit 1 - } - if host_nginx_online; then - return 0 - fi - echo "宿主机 Nginx 未在线,尝试启动..." - run_sudo systemctl start nginx 2>/dev/null || run_sudo systemctl start nginx.service - run_sudo systemctl enable nginx 2>/dev/null || true - host_nginx_online || { - echo "错误: 无法启动宿主机 nginx" >&2 - exit 1 - } -} - -install_host_nginx_site_conf() { - local domain="${AIJZ_DOMAIN:-}" - [ -n "$domain" ] || return 0 - [ "${AIJZ_ENABLE_HOST_NGINX:-0}" = "1" ] || return 0 - local tpl="$ROOT/nginx/aijz.host.conf" - local out="/etc/nginx/conf.d/aijz_${domain}.conf" - local web_port - web_port="$(publish_host_port "${AIJZ_WEB_PUBLISH:-127.0.0.1:5173}")" - [ -f "$tpl" ] || { - echo "未找到 $tpl,跳过宿主机 Nginx 配置" >&2 - return 0 - } - mkdir -p "$ROOT/verify-root" - sync_ssl_certs - sed -e "s|__DOMAIN__|${domain}|g" \ - -e "s|__WEB_PORT__|${web_port}|g" \ - -e "s|__VERIFY_ROOT__|${ROOT}/verify-root|g" \ - "$tpl" | run_sudo tee "$out" >/dev/null - if ! run_sudo nginx -t 2>/dev/null; then - echo "错误: nginx -t 失败,请检查 $out" >&2 - exit 1 - fi - if host_nginx_online; then - run_sudo systemctl reload nginx 2>/dev/null && echo "宿主机 Nginx 已重载($out)" - else - ensure_host_nginx_started - fi -} - -reload_web_nginx() { - local cid - cid="$(compose_cmd ps -q web 2>/dev/null | head -1)" - if [ -z "$cid" ]; then - echo "web 容器未运行,跳过 reload" >&2 - return 1 - fi - run_sudo docker exec "$cid" nginx -t >/dev/null 2>&1 || { - echo "错误: 容器内 nginx -t 失败" >&2 - return 1 - } - run_sudo docker exec "$cid" nginx -s reload - echo "web 容器 nginx 已 reload(web/nginx.conf)" -} - -restart_service() { - local svc="$1" - compose_cmd restart "$svc" - echo "已 restart $svc" -} - -reload_host_nginx() { - [ "${AIJZ_ENABLE_HOST_NGINX:-0}" = "1" ] || { - echo "AIJZ_ENABLE_HOST_NGINX 未启用,跳过宿主机 Nginx" >&2 - return 0 - } - install_host_nginx_site_conf -} - -reload_all_config() { - ensure_env_file - apply_public_base_url - reload_host_nginx || true - reload_web_nginx || true - restart_service platform - restart_service gateway - compose_cmd up -d --force-recreate ai >/dev/null 2>&1 || compose_cmd restart ai - echo "配置重载完成(未 rebuild 镜像、未重建 web/dist)" -} - -ensure_docker() { - if command -v docker >/dev/null 2>&1 && run_sudo docker info >/dev/null 2>&1; then - echo "Docker 已就绪." - return 0 - fi - if command -v docker >/dev/null 2>&1; then - echo "Docker 守护进程未连接,尝试启动..." - run_sudo systemctl start docker 2>/dev/null || run_sudo systemctl start podman 2>/dev/null || true - if run_sudo docker info >/dev/null 2>&1; then - echo "Docker 已就绪." - return 0 - fi - fi - echo "错误: Docker 不可用。请先安装并启动 docker 后再执行。" >&2 - exit 1 -} - -resolve_compose_cmd() { - if run_sudo docker compose version >/dev/null 2>&1; then - echo "docker compose" - return - fi - if command -v docker-compose >/dev/null 2>&1; then - echo "docker-compose" - return - fi - if [ -x /usr/local/bin/docker-compose ]; then - echo "/usr/local/bin/docker-compose" - return - fi - echo "" -} - -COMPOSE_CMD="" -compose_cmd() { - if [ -z "$COMPOSE_CMD" ]; then - COMPOSE_CMD="$(resolve_compose_cmd)" - fi - if [ -z "$COMPOSE_CMD" ]; then - echo "错误: 找不到 docker compose / docker-compose" >&2 - exit 1 - fi - # shellcheck disable=SC2086 - run_sudo env \ - DOCKER_BASE_REGISTRY="${DOCKER_BASE_REGISTRY}" \ - GOPROXY="${GOPROXY}" \ - AIJZ_WEB_PUBLISH="${AIJZ_WEB_PUBLISH}" \ - AIJZ_GATEWAY_PUBLISH="${AIJZ_GATEWAY_PUBLISH}" \ - AIJZ_PLATFORM_PUBLISH="${AIJZ_PLATFORM_PUBLISH}" \ - AIJZ_AI_PUBLISH="${AIJZ_AI_PUBLISH}" \ - AIJZ_PG_PUBLISH="${AIJZ_PG_PUBLISH}" \ - $COMPOSE_CMD "$@" -} - -export_defaults() { - export DOCKER_BASE_REGISTRY="${DOCKER_BASE_REGISTRY:-docker.m.daocloud.io/library}" - export GOPROXY="${GOPROXY:-https://goproxy.cn,direct}" - export REGISTRY_MIRROR="${REGISTRY_MIRROR:-docker.m.daocloud.io/library/}" -} - -# 从 "127.0.0.1:5173" 或 "5173" 取出宿主机端口号 -publish_host_port() { - local pub="$1" - echo "$pub" | awk -F: '{print $NF}' -} - -port_in_use() { - local port="$1" - if command -v ss >/dev/null 2>&1; then - run_sudo ss -tlnH "sport = :$port" 2>/dev/null | grep -q . && return 0 - ss -tlnH "sport = :$port" 2>/dev/null | grep -q . && return 0 - return 1 - fi - if command -v lsof >/dev/null 2>&1; then - lsof -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1 && return 0 - return 1 - fi - return 1 -} - -# 检查 AI 建站将占用的宿主机端口;若已被本项目容器占用则放行 -check_aijz_ports() { - local pairs=( - "web:$AIJZ_WEB_PUBLISH" - "gateway:$AIJZ_GATEWAY_PUBLISH" - "platform:$AIJZ_PLATFORM_PUBLISH" - "ai:$AIJZ_AI_PUBLISH" - "postgres:$AIJZ_PG_PUBLISH" - ) - echo "端口规划核对(相对 yh_web):AI建站 5173/8180/8888/8001/5432;宇恒 8088/9080/9081 + 宿主机 80/443。" - local name pub port conflict=0 - local self_running=0 - if compose_cmd ps -q 2>/dev/null | grep -q .; then - self_running=1 - fi - for item in "${pairs[@]}"; do - name="${item%%:*}" - pub="${item#*:}" - port="$(publish_host_port "$pub")" - [ -z "$port" ] && continue - if port_in_use "$port"; then - if [ "$self_running" -eq 1 ]; then - echo " 提示: 端口 $port($name)已占用,疑为本栈,继续。" - else - echo " 错误: 宿主机端口 $port 已被占用(服务 $name ← $pub)。" >&2 - echo " 请改 .env 中对应 AIJZ_*_PUBLISH,或停掉占用进程。" >&2 - conflict=1 - fi - else - echo " OK $name → $pub" - fi - done - [ "$conflict" -eq 0 ] || exit 1 -} - -# 构建 web/dist:优先本机 npm;否则用 Docker node 镜像 -build_web_dist() { - echo "构建 web dist ..." - if command -v npm >/dev/null 2>&1; then - ( - cd "$ROOT/web" - if [ -f package-lock.json ]; then - npm ci --legacy-peer-deps 2>/dev/null || npm install --legacy-peer-deps - else - npm install --legacy-peer-deps - fi - npm run build - ) - else - echo "本机无 npm,使用 Docker node 构建..." - run_sudo docker run --rm \ - -v "$ROOT/web:/app" \ - -v "$ROOT/web/dist:/app/dist" \ - -w /app \ - "${REGISTRY_MIRROR}node:20-alpine" \ - sh -c "(npm ci --legacy-peer-deps 2>/dev/null || npm install --legacy-peer-deps) && npm run build" - fi - if [ ! -f "$ROOT/web/dist/index.html" ]; then - echo "错误: web/dist/index.html 不存在,构建失败。" >&2 - exit 1 - fi - echo "web/dist 就绪." -} - -stack_up() { - local rebuild="${1:-0}" - export_defaults - ensure_runtime_dirs - ensure_env_file - apply_public_base_url - check_aijz_ports - build_web_dist - if [ "$rebuild" = "1" ]; then - compose_cmd up -d --build --force-recreate - else - if ! compose_cmd up -d; then - echo "compose up 失败,尝试 --build ..." - compose_cmd up -d --build - fi - fi - install_host_nginx_site_conf || true -} - -stack_down() { - compose_cmd down --remove-orphans 2>/dev/null || true -} - -healthcheck() { - local ok=0 i - local gw_port web_port - gw_port="$(publish_host_port "${AIJZ_GATEWAY_PUBLISH:-127.0.0.1:8180}")" - web_port="$(publish_host_port "${AIJZ_WEB_PUBLISH:-127.0.0.1:5173}")" - echo "等待 gateway(:$gw_port) / web(:$web_port) ..." - for i in $(seq 1 60); do - if curl -fsS --max-time 2 "http://127.0.0.1:${gw_port}/gateway/health" >/dev/null 2>&1 \ - && curl -fsS --max-time 2 "http://127.0.0.1:${web_port}/" >/dev/null 2>&1; then - ok=1 - break - fi - sleep 2 - done - if [ "$ok" -eq 1 ]; then - echo "健康检查通过." - else - echo "警告: 健康检查超时,请查看: docker compose -f $ROOT/docker-compose.yml logs" >&2 - fi - compose_cmd exec -T postgres psql -U platform -d platform -c 'ALTER USER platform CREATEDB;' >/dev/null 2>&1 || true -} - -print_endpoints() { - local pub_line="" - if [ -n "${AIJZ_DOMAIN:-}" ] && [ "${AIJZ_ENABLE_HOST_NGINX:-0}" = "1" ]; then - pub_line=" Public https://${AIJZ_DOMAIN}" - fi - cat </dev/null 2>&1; then - echo "错误: 未安装 git,无法拉取代码。请先安装 git。" >&2 - return 1 - fi - if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then - echo "当前目录不是 git 仓库,初始化并关联 origin -> $url" - git init - git remote add origin "$url" - return 0 - fi - if git remote get-url origin >/dev/null 2>&1; then - local cur - cur="$(git remote get-url origin)" - if [ "$cur" != "$url" ]; then - echo "更新 origin: $cur -> $url" - git remote set-url origin "$url" - fi - else - echo "添加 origin -> $url" - git remote add origin "$url" - fi -} - -# 从 Gitea ai_site 拉取并与远程对齐(默认硬重置,保留未跟踪文件如 .env) -# 环境变量: -# AIJZ_GIT_URL 仓库地址(默认 https://gitea.yuxindazhineng.com/whm/ai_site.git) -# GIT_BRANCH 分支(默认 main) -# FORCE_GIT_RESET 1=reset --hard 对齐远程(默认 1);0=仅快进合并 -git_pull_hard() { - local url="${AIJZ_GIT_URL:-$AIJZ_GIT_URL_DEFAULT}" - local branch="${GIT_BRANCH:-main}" - echo "拉取代码($url branch=$branch)..." - ensure_git_origin "$url" || return 1 - if ! git fetch origin --progress; then - echo "错误: git fetch 失败。私有仓库请配置凭据,或设置 AIJZ_GIT_URL 为带 token 的地址。" >&2 - return 1 - fi - if ! git rev-parse --verify "origin/$branch" >/dev/null 2>&1; then - echo "错误: 远程不存在分支 origin/$branch" >&2 - return 1 - fi - if [ "${FORCE_GIT_RESET:-1}" = "1" ]; then - git checkout -B "$branch" "origin/$branch" - git reset --hard "origin/$branch" - else - git checkout "$branch" 2>/dev/null || git checkout -b "$branch" "origin/$branch" - git merge --ff-only "origin/$branch" - fi - echo "当前: $(git rev-parse --short HEAD) $(git log -1 --pretty=%s)" -} diff --git a/scripts/linux/common.sh b/scripts/linux/common.sh deleted file mode 100644 index a1bda03..0000000 --- a/scripts/linux/common.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/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 diff --git a/scripts/linux/setup-host.sh b/scripts/linux/setup-host.sh deleted file mode 100644 index fa254b7..0000000 --- a/scripts/linux/setup-host.sh +++ /dev/null @@ -1,224 +0,0 @@ -#!/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 </dev/null 2>&1; then - # 补充密码认证 - cat > "$PGDATA/pg_hba.conf" </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" </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 </dev/null 2>&1 || true -fi diff --git a/scripts/linux/stop-host.sh b/scripts/linux/stop-host.sh deleted file mode 100644 index 6f7db98..0000000 --- a/scripts/linux/stop-host.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/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" diff --git a/start-host.ps1 b/start-host.ps1 deleted file mode 100644 index 940c163..0000000 --- a/start-host.ps1 +++ /dev/null @@ -1,199 +0,0 @@ -# native process mode (called by start.ps1 when Docker is unavailable) -param( - [switch]$Rebuild, - [switch]$NoBrowser, - [switch]$ShowConsole # show service windows (debug); default: hidden -) - -$ErrorActionPreference = "Stop" -$Root = $PSScriptRoot -Set-Location -LiteralPath $Root -$LogDir = Join-Path $Root ".runtime\logs" -New-Item -ItemType Directory -Force -Path $LogDir | Out-Null - -# Always wipe previous native/docker leftovers before binding ports -Write-Host "==> Pre-clean before native start" -ForegroundColor DarkGray -& (Join-Path $Root "scripts\Stop-Stack.ps1") -Quiet -Start-Sleep -Seconds 1 - -Write-Host "==> Native mode: need Go / Python / Node / PostgreSQL" -ForegroundColor Yellow -if (-not $ShowConsole) { - Write-Host " services run hidden; logs -> .runtime\logs\" -ForegroundColor DarkGray -} - -function Clear-Port([int]$port) { - $conns = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue - foreach ($c in $conns) { - $procId = $c.OwningProcess - if ($procId -gt 0) { - Write-Host " free port $port (pid=$procId)" - & taskkill.exe /F /T /PID $procId 2>$null | Out-Null - Stop-Process -Id $procId -Force -ErrorAction SilentlyContinue - } - } -} - -Write-Host "==> Clearing ports 8888/8001/8180/5173" -foreach ($p in 8888, 8001, 8180, 5173) { Clear-Port $p } -Start-Sleep -Seconds 1 - -function Reset-LogFile([string]$path) { - try { - if (Test-Path -LiteralPath $path) { - # 句柄未释放时 Set-Content 会失败:先挪走旧文件 - $prev = "$path.prev" - Remove-Item -LiteralPath $prev -Force -ErrorAction SilentlyContinue - Move-Item -LiteralPath $path -Destination $prev -Force -ErrorAction Stop - } - Set-Content -LiteralPath $path -Value "" -Encoding UTF8 - return $path - } catch { - $alt = ($path -replace '\.log$', ".$(Get-Date -Format 'HHmmss').log") - Set-Content -LiteralPath $alt -Value "" -Encoding UTF8 - return $alt - } -} - -function Start-Svc($name, $workDir, $filePath, $argumentList) { - $outLog = Reset-LogFile (Join-Path $LogDir "$name.out.log") - $errLog = Reset-LogFile (Join-Path $LogDir "$name.err.log") - $pidFile = Join-Path $LogDir "$name.pid" - - if ($ShowConsole) { - $argStr = "" - if ($argumentList) { - $parts = @() - foreach ($a in $argumentList) { - if ($a -match '\s') { $parts += ('"' + $a + '"') } else { $parts += $a } - } - $argStr = $parts -join ' ' - } - $cmd = "Set-Location -LiteralPath '$workDir'; Write-Host '[$name]' -ForegroundColor Cyan; & '$filePath' $argStr" - $p = Start-Process -FilePath "powershell.exe" -ArgumentList @( - "-NoExit", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $cmd - ) -WorkingDirectory $workDir -PassThru - } else { - $p = Start-Process -FilePath $filePath ` - -ArgumentList $argumentList ` - -WorkingDirectory $workDir ` - -WindowStyle Hidden ` - -RedirectStandardOutput $outLog ` - -RedirectStandardError $errLog ` - -PassThru - } - if ($p) { Set-Content -LiteralPath $pidFile -Value $p.Id -Encoding ASCII } - Write-Host " started $name (pid=$($p.Id))" -ForegroundColor DarkGray -} - -$plat = Join-Path $Root "platform\platform.exe" -$gw = Join-Path $Root "gateway\gateway.exe" -# 默认重编译,避免改 Go 源码后 restart 仍跑旧 exe -$needPlat = $Rebuild -or -not (Test-Path $plat) -$needGw = $Rebuild -or -not (Test-Path $gw) -if (-not $needPlat -and (Test-Path $plat)) { - $exeTime = (Get-Item $plat).LastWriteTime - $newer = Get-ChildItem -Path (Join-Path $Root "platform") -Recurse -Include *.go -ErrorAction SilentlyContinue | - Where-Object { $_.LastWriteTime -gt $exeTime } | - Select-Object -First 1 - if ($newer) { $needPlat = $true } -} -if (-not $needGw -and (Test-Path $gw)) { - $exeTime = (Get-Item $gw).LastWriteTime - $newer = Get-ChildItem -Path (Join-Path $Root "gateway") -Recurse -Include *.go -ErrorAction SilentlyContinue | - Where-Object { $_.LastWriteTime -gt $exeTime } | - Select-Object -First 1 - if ($newer) { $needGw = $true } -} -if ($needPlat) { - Write-Host " building platform.exe ..." -ForegroundColor DarkGray - Push-Location (Join-Path $Root "platform") - go build -o platform.exe . - Pop-Location -} -if ($needGw) { - Write-Host " building gateway.exe ..." -ForegroundColor DarkGray - Push-Location (Join-Path $Root "gateway") - go build -o gateway.exe . - Pop-Location -} - -Write-Host "==> Ensure web frontend (vite /#/preview)" -ForegroundColor DarkGray -& (Join-Path $Root "scripts\Ensure-Web.ps1") -# $? 判断 PS 脚本成败;勿单靠 $LASTEXITCODE(会残留 docker/compose 等 native 失败码) -if (-not $?) { throw "Ensure-Web failed" } - -$pyCmd = Get-Command python -ErrorAction SilentlyContinue -if (-not $pyCmd) { $pyCmd = Get-Command py -ErrorAction SilentlyContinue } -if (-not $pyCmd) { throw "python not found" } -$py = $pyCmd.Source - -$npmCmd = Get-Command npm.cmd -ErrorAction SilentlyContinue -if (-not $npmCmd) { $npmCmd = Get-Command npm -ErrorAction SilentlyContinue } -if (-not $npmCmd) { throw "npm not found" } - -# AI 截图直达本机 Vite -$env:WEB_BASE = "http://127.0.0.1:5173" -$env:PLATFORM_BASE = "http://127.0.0.1:8888" -$env:GATEWAY_BASE = "http://127.0.0.1:8180" -$env:GENERATE_LOG_DIR = Join-Path $Root ".runtime\logs\generate" -$env:FIDELITY_SHOT_DIR = Join-Path $Root ".runtime\fidelity_shots" - -Start-Svc "platform" (Join-Path $Root "platform") $plat @("-f", "etc/platform.yaml") -Start-Sleep -Seconds 2 -Start-Svc "ai-service" (Join-Path $Root "ai-service") $py @("-m", "uvicorn", "app:app", "--host", "127.0.0.1", "--port", "8001", "--reload") -Start-Sleep -Seconds 1 -Start-Svc "gateway" (Join-Path $Root "gateway") $gw @("-f", "etc/gateway.yaml") -Start-Sleep -Seconds 1 -Start-Svc "web" (Join-Path $Root "web") $npmCmd.Source @("run", "dev", "--", "--host", "127.0.0.1", "--port", "5173", "--strictPort") - -Write-Host "==> Waiting for services..." -$deadline = (Get-Date).AddSeconds(45) -do { - $webOk = $false - $gwOk = $false - try { - $r = Invoke-WebRequest -Uri "http://127.0.0.1:5173/" -UseBasicParsing -TimeoutSec 2 - if ($r.StatusCode -lt 500) { $webOk = $true } - } catch {} - try { - $r = Invoke-WebRequest -Uri "http://127.0.0.1:8180/gateway/health" -UseBasicParsing -TimeoutSec 2 - if ($r.StatusCode -lt 500) { $gwOk = $true } - } catch {} - if ($webOk -and $gwOk) { break } - Start-Sleep -Seconds 1 -} while ((Get-Date) -lt $deadline) - -if ($webOk -and $gwOk) { - Write-Host "OK Web http://127.0.0.1:5173 Gateway http://127.0.0.1:8180 account demo/demo123" -ForegroundColor Green - Write-Host " logs $LogDir" -ForegroundColor DarkGray -} else { - Write-Host "WARN some services not ready yet. Check logs:" -ForegroundColor Yellow - Write-Host " $LogDir" - Write-Host " web=$webOk gateway=$gwOk" - Write-Host " (debug with visible windows: .\start-host.ps1 -ShowConsole)" -} - -# 后台看门狗:ai-service / gateway 挂掉自动拉起 -$watchPidFile = Join-Path $LogDir "watchdog.pid" -if (Test-Path $watchPidFile) { - $oldWatch = Get-Content $watchPidFile -ErrorAction SilentlyContinue - if ($oldWatch) { Stop-Process -Id ([int]$oldWatch) -Force -ErrorAction SilentlyContinue } -} -$watchScript = Join-Path $Root "watch-services.ps1" -$watchOut = Join-Path $LogDir "watchdog.out.log" -$watchErr = Join-Path $LogDir "watchdog.err.log" -Set-Content -LiteralPath $watchOut -Value "" -Encoding UTF8 -Set-Content -LiteralPath $watchErr -Value "" -Encoding UTF8 -$wp = Start-Process -FilePath "powershell.exe" ` - -ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $watchScript) ` - -WorkingDirectory $Root ` - -WindowStyle Hidden ` - -RedirectStandardOutput $watchOut ` - -RedirectStandardError $watchErr ` - -PassThru -if ($wp) { - Set-Content -LiteralPath $watchPidFile -Value $wp.Id -Encoding ASCII - Write-Host " started watchdog (pid=$($wp.Id)) - auto-restarts ai-service if down" -ForegroundColor DarkGray -} - -if (-not $NoBrowser) { Start-Process "http://127.0.0.1:5173" } \ No newline at end of file diff --git a/start.bat b/start.bat deleted file mode 100644 index 5d08e07..0000000 --- a/start.bat +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -chcp 65001 >nul -cd /d "%~dp0" -powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0start.ps1" %* \ No newline at end of file diff --git a/start.ps1 b/start.ps1 deleted file mode 100644 index 922d84f..0000000 --- a/start.ps1 +++ /dev/null @@ -1,175 +0,0 @@ -# aijianzhan one-click start (Docker Compose by default; auto-starts Docker Desktop) -param( - [switch]$Rebuild, - [switch]$UseHost, - [switch]$NoBrowser, - [switch]$ShowConsole, # native mode only: show service console windows - [int]$DockerWaitSec = 120 -) - -$ErrorActionPreference = "Stop" -$Root = $PSScriptRoot -Set-Location -LiteralPath $Root - -function Write-Step([string]$msg) { Write-Host ""; Write-Host "==> $msg" -ForegroundColor Cyan } -function Write-Ok([string]$msg) { Write-Host " OK $msg" -ForegroundColor Green } -function Write-Warn([string]$msg) { Write-Host " !! $msg" -ForegroundColor Yellow } - -function Test-DockerCli { - try { - $null = Get-Command docker -ErrorAction Stop - return $true - } catch { - return $false - } -} - -function Test-DockerReady { - if (-not (Test-DockerCli)) { return $false } - try { - $old = $ErrorActionPreference - $ErrorActionPreference = "Continue" - $null = & docker info 2>&1 - $ok = ($LASTEXITCODE -eq 0) - $ErrorActionPreference = $old - return $ok - } catch { - return $false - } -} - -function Get-DockerDesktopPath { - $pf86 = [Environment]::GetEnvironmentVariable("ProgramFiles(x86)") - $candidates = @( - (Join-Path $env:ProgramFiles "Docker\Docker\Docker Desktop.exe"), - $(if ($pf86) { Join-Path $pf86 "Docker\Docker\Docker Desktop.exe" } else { $null }), - (Join-Path $env:LOCALAPPDATA "Docker\Docker Desktop.exe") - ) - foreach ($p in $candidates) { - if ($p -and (Test-Path -LiteralPath $p)) { return $p } - } - return $null -} - -function Start-DockerDesktopIfNeeded { - if (Test-DockerReady) { return $true } - - if (-not (Test-DockerCli)) { - Write-Warn "docker CLI not found; install Docker Desktop first" - return $false - } - - $exe = Get-DockerDesktopPath - $proc = Get-Process -Name "Docker Desktop","com.docker.backend","com.docker.service" -ErrorAction SilentlyContinue - if (-not $proc) { - if (-not $exe) { - Write-Warn "Docker Desktop.exe not found" - return $false - } - Write-Step "Starting Docker Desktop" - Start-Process -FilePath $exe | Out-Null - Write-Ok "Docker Desktop launched; waiting for engine..." - } else { - Write-Step "Docker Desktop process found; waiting for engine..." - } - - $started = Get-Date - $deadline = $started.AddSeconds([Math]::Max(30, $DockerWaitSec)) - $n = 0 - while ((Get-Date) -lt $deadline) { - if (Test-DockerReady) { - Write-Ok "Docker engine ready" - return $true - } - $n++ - if ($n % 5 -eq 0) { - $elapsed = [int]((Get-Date) - $started).TotalSeconds - Write-Host (" ...waiting Docker ({0}s / {1}s)" -f $elapsed, $DockerWaitSec) -ForegroundColor DarkGray - } - Start-Sleep -Seconds 2 - } - Write-Warn ("Docker wait timeout ({0}s)" -f $DockerWaitSec) - return $false -} - -function Start-NativeFallback([string]$reason) { - Write-Warn $reason - Write-Step "Fallback to native mode (start-host.ps1)" - & (Join-Path $Root "start-host.ps1") -Rebuild:$Rebuild -NoBrowser:$NoBrowser -ShowConsole:$ShowConsole - exit $LASTEXITCODE -} - -if ($UseHost) { - Start-NativeFallback "UseHost specified" -} - -$dockerReady = Start-DockerDesktopIfNeeded -if (-not $dockerReady) { - Start-NativeFallback "Docker not ready; fallback native. Fix Docker then rerun .\start.ps1" -} - -Write-Step "Apply China Docker registry mirrors" -& (Join-Path $Root "scripts\Apply-DockerMirrors.ps1") -NoRestartHint - -New-Item -ItemType Directory -Force -Path (Join-Path $Root ".runtime\pgdata") | Out-Null -New-Item -ItemType Directory -Force -Path (Join-Path $Root ".runtime\uploads") | Out-Null -New-Item -ItemType Directory -Force -Path (Join-Path $Root ".runtime\logs") | Out-Null - -# Wipe native leftovers so Docker and host never fight over the same ports -Write-Step "Pre-clean ports / native leftovers" -& (Join-Path $Root "scripts\Stop-Stack.ps1") -Quiet - -Write-Step "Build web dist (auto, includes /#/preview)" -& (Join-Path $Root "scripts\Ensure-Web.ps1") -BuildDist -if (-not $?) { - Start-NativeFallback "web dist build failed; fallback native (vite)." -} - -# Prefer DaoCloud-prefixed library images (see docker-compose.yml / Dockerfiles) -if (-not $env:DOCKER_BASE_REGISTRY) { - $env:DOCKER_BASE_REGISTRY = "docker.m.daocloud.io/library" -} - -Write-Step "Docker Compose up (data -> .runtime)" -# Prefer reuse of local images; --build only when -Rebuild (Hub often fails in CN) -$composeArgs = @("compose", "up", "-d") -if ($Rebuild) { $composeArgs += "--build" } -& docker @composeArgs -if ($LASTEXITCODE -ne 0) { - Write-Warn "compose up failed; retry once with --build" - & docker compose up -d --build -} -if ($LASTEXITCODE -ne 0) { - # Hub / network failures are common in CN; keep the stack usable via native mode - Start-NativeFallback "docker compose failed (Docker Hub/network). Auto fallback to native mode." -} - -Write-Step "Waiting for gateway / web" -$ok = $false -for ($i = 0; $i -lt 60; $i++) { - try { - $h = Invoke-WebRequest -Uri "http://127.0.0.1:8180/gateway/health" -UseBasicParsing -TimeoutSec 2 - $w = Invoke-WebRequest -Uri "http://127.0.0.1:5173/" -UseBasicParsing -TimeoutSec 2 - if ($h.StatusCode -lt 500 -and $w.StatusCode -lt 500) { $ok = $true; break } - } catch { - # retry - } - Start-Sleep -Seconds 2 -} -if ($ok) { Write-Ok "services ready" } else { Write-Warn "health check timeout, see: docker compose logs" } - -$sql = 'ALTER USER platform CREATEDB;' -& docker compose exec -T postgres psql -U platform -d platform -c $sql 2>&1 | Out-Null - -Write-Host "" -Write-Host "========================================" -ForegroundColor Green -Write-Host " Web http://127.0.0.1:5173" -Write-Host " Gateway http://127.0.0.1:8180" -Write-Host " Account demo / demo123" -Write-Host " Data $Root\.runtime" -Write-Host " Stop .\stop.ps1" -Write-Host "========================================" -ForegroundColor Green - -if (-not $NoBrowser) { - Start-Process "http://127.0.0.1:5173" -} diff --git a/start.sh b/start.sh deleted file mode 100644 index f29bce4..0000000 --- a/start.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -# AI 建站 — Linux 一键启动(Docker Compose) -# 用法:cd 项目根 && ./start.sh [--rebuild] [--no-pull] -# 默认从 Gitea 拉取最新代码再启动(可用 --no-pull / AIJZ_SKIP_GIT_PULL=1 跳过) -# 行尾:LF -set -euo pipefail -ROOT="$(cd "$(dirname "$0")" && pwd)" -cd "$ROOT" -# shellcheck disable=SC1091 -. "$ROOT/scripts/lib-aijz-deploy.sh" - -REBUILD=0 -DO_PULL=1 -for a in "$@"; do - case "$a" in - --rebuild|-r) REBUILD=1 ;; - --no-pull) DO_PULL=0 ;; - --pull) DO_PULL=1 ;; - -h|--help) - echo "用法: $0 [--rebuild] [--no-pull|--pull]" - echo " 默认拉取: ${AIJZ_GIT_URL:-https://gitea.yuxindazhineng.com/whm/ai_site.git} 分支 ${GIT_BRANCH:-main}" - echo " 跳过拉取: --no-pull 或 AIJZ_SKIP_GIT_PULL=1" - exit 0 - ;; - esac -done -if [ "${AIJZ_SKIP_GIT_PULL:-0}" = "1" ]; then - DO_PULL=0 -fi - -echo "========================================" -echo " AI 建站 启动" -echo " 路径: $ROOT" -echo "========================================" - -if [ "$DO_PULL" = "1" ]; then - git_pull_hard -else - echo "已跳过代码拉取(--no-pull / AIJZ_SKIP_GIT_PULL)" -fi - -ensure_docker -stack_up "$REBUILD" -healthcheck -print_endpoints diff --git a/stop.bat b/stop.bat deleted file mode 100644 index 23a6aff..0000000 --- a/stop.bat +++ /dev/null @@ -1,5 +0,0 @@ -@echo off -chcp 65001 >nul -cd /d "%~dp0" -powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0stop.ps1" -pause \ No newline at end of file diff --git a/stop.ps1 b/stop.ps1 deleted file mode 100644 index 292515f..0000000 --- a/stop.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -# Stop all stack processes (Docker + native). Thin wrapper around scripts\Stop-Stack.ps1. -$ErrorActionPreference = "Continue" -$Root = $PSScriptRoot -Set-Location -LiteralPath $Root -& (Join-Path $Root "scripts\Stop-Stack.ps1") -exit $LASTEXITCODE diff --git a/stop.sh b/stop.sh deleted file mode 100644 index 66f3fbd..0000000 --- a/stop.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash -# AI 建站 — 停止 Docker 栈 -# 用法:./stop.sh -set -euo pipefail -ROOT="$(cd "$(dirname "$0")" && pwd)" -cd "$ROOT" -# shellcheck disable=SC1091 -. "$ROOT/scripts/lib-aijz-deploy.sh" - -echo "停止 AI 建站 ($ROOT) ..." -ensure_docker -stack_down -echo "已停止."