-
Notifications
You must be signed in to change notification settings - Fork 1.6k
feat: detect stale local server and notify developer to restart #1517
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| # Server Refactor v1 - Upgrade Guide | ||
|
|
||
| > Applies to: v0.0.89+ | ||
| > PR: #1509 | ||
|
|
||
| ## What Changed | ||
|
|
||
| The server codebase has been restructured from a flat layout to a **domain-driven architecture**. No API endpoints or database schemas were changed — this is a code organization refactor only. | ||
|
|
||
| ### Directory Mapping | ||
|
|
||
| | Before | After | Description | | ||
| |---|---|---| | ||
| | `app/component/` | `app/core/` | Infrastructure utilities (database, encryption, celery, etc.) | | ||
| | `app/controller/` | `app/domains/*/api/` | API controllers, grouped by domain | | ||
| | `app/service/` | `app/domains/*/service/` | Business logic, grouped by domain | | ||
| | `app/exception/` | `app/shared/exception/` | Exception handling | | ||
| | `app/type/` | `app/shared/types/` | Shared type definitions | | ||
| | _(new)_ | `app/shared/auth/` | Authentication & authorization | | ||
| | _(new)_ | `app/shared/middleware/` | CORS, rate limiting, trace ID | | ||
| | _(new)_ | `app/shared/http/` | HTTP client utilities | | ||
| | _(new)_ | `app/shared/logging/` | Logging & sensitive data filtering | | ||
|
|
||
| ### Domain Structure | ||
|
|
||
| Each domain (`chat`, `config`, `mcp`, `model_provider`, `oauth`, `trigger`, `user`) follows the same layout: | ||
|
|
||
| ``` | ||
| app/domains/<domain>/ | ||
| api/ # Controllers (route handlers) | ||
| service/ # Business logic | ||
| schema/ # Request/response schemas | ||
| ``` | ||
|
|
||
| ## Upgrade Action Required | ||
|
|
||
| **This is a breaking change for local deployments.** The old server code will fail to start due to changed import paths. | ||
|
|
||
| ### Docker Users | ||
|
|
||
| ```bash | ||
| cd server | ||
| docker-compose up --build -d | ||
| ``` | ||
|
|
||
| You **must** include `--build` to rebuild the image. Running `docker-compose up -d` without `--build` will use the stale old image and fail. | ||
|
|
||
| ### Non-Docker Users (Local Development) | ||
|
|
||
| If you are running the server directly (via `start_server.sh` or `uv run uvicorn`): | ||
|
|
||
| 1. Stop the running server process | ||
| 2. Pull the latest code | ||
| 3. Restart the server | ||
|
|
||
| ```bash | ||
| # If using start_server.sh | ||
| cd server | ||
| ./start_server.sh | ||
|
|
||
| # If running uvicorn directly | ||
| cd server | ||
| uv run uvicorn main:api --reload --port 3001 --host 0.0.0.0 | ||
| ``` | ||
|
|
||
| ### Electron App Users | ||
|
|
||
| If you are running Eigent as a desktop app, simply restart the application. The server will be restarted automatically. | ||
|
|
||
| ## FAQ | ||
|
|
||
| **Q: Will I lose my data?** | ||
| A: No. Database volumes and schemas are not affected. Only the Python source code layout changed. | ||
|
|
||
| **Q: Do I need to re-run database migrations?** | ||
| A: No. There are no new migrations in this change. | ||
|
|
||
| **Q: I see import errors like `ModuleNotFoundError: No module named 'app.component'`** | ||
| A: This means you are running an old server binary/image. Follow the upgrade steps above. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| # Server 重构 v1 - 升级指南 | ||
|
|
||
| > 适用版本: v0.0.89+ | ||
| > PR: #1509 | ||
|
|
||
| ## 改动概述 | ||
|
|
||
| Server 代码从扁平结构重构为**领域驱动架构 (Domain-Driven)**。API 接口和数据库结构均未变更,这是一次纯代码组织层面的重构。 | ||
|
|
||
| ### 目录变更对照 | ||
|
|
||
| | 重构前 | 重构后 | 说明 | | ||
| |---|---|---| | ||
| | `app/component/` | `app/core/` | 基础设施(数据库、加密、celery 等) | | ||
| | `app/controller/` | `app/domains/*/api/` | 按领域分组的 API 控制器 | | ||
| | `app/service/` | `app/domains/*/service/` | 按领域分组的业务逻辑 | | ||
| | `app/exception/` | `app/shared/exception/` | 异常处理 | | ||
| | `app/type/` | `app/shared/types/` | 共享类型定义 | | ||
| | _(新增)_ | `app/shared/auth/` | 认证与授权 | | ||
| | _(新增)_ | `app/shared/middleware/` | CORS、限流、Trace ID | | ||
| | _(新增)_ | `app/shared/http/` | HTTP 客户端工具 | | ||
| | _(新增)_ | `app/shared/logging/` | 日志与敏感信息过滤 | | ||
|
|
||
| ### 领域结构 | ||
|
|
||
| 每个领域(`chat`、`config`、`mcp`、`model_provider`、`oauth`、`trigger`、`user`)遵循统一结构: | ||
|
|
||
| ``` | ||
| app/domains/<领域>/ | ||
| api/ # 控制器(路由处理) | ||
| service/ # 业务逻辑 | ||
| schema/ # 请求/响应模型 | ||
| ``` | ||
|
|
||
| ## 升级操作(必须) | ||
|
|
||
| **此改动对本地部署是 breaking change。** 旧版 server 代码因 import 路径变更将无法启动。 | ||
|
|
||
| ### Docker 用户 | ||
|
|
||
| ```bash | ||
| cd server | ||
| docker-compose up --build -d | ||
| ``` | ||
|
|
||
| **必须**加 `--build` 参数重新构建镜像。直接 `docker-compose up -d` 会使用旧镜像导致启动失败。 | ||
|
|
||
| ### 非 Docker 用户(本地开发) | ||
|
|
||
| 如果你通过 `start_server.sh` 或 `uv run uvicorn` 直接运行 server: | ||
|
|
||
| 1. 停止正在运行的 server 进程 | ||
| 2. 拉取最新代码 | ||
| 3. 重新启动 server | ||
|
|
||
| ```bash | ||
| # 使用 start_server.sh | ||
| cd server | ||
| ./start_server.sh | ||
|
|
||
| # 直接运行 uvicorn | ||
| cd server | ||
| uv run uvicorn main:api --reload --port 3001 --host 0.0.0.0 | ||
| ``` | ||
|
|
||
| ### Electron 桌面应用用户 | ||
|
|
||
| 重启应用即可,server 会自动重启。 | ||
|
|
||
| ## 常见问题 | ||
|
|
||
| **Q: 数据会丢失吗?** | ||
| A: 不会。数据库卷和表结构未受影响,仅 Python 源码目录结构发生了变化。 | ||
|
|
||
| **Q: 需要重新执行数据库迁移吗?** | ||
| A: 不需要。此次改动没有新增数据库迁移。 | ||
|
|
||
| **Q: 出现 `ModuleNotFoundError: No module named 'app.component'`** | ||
| A: 说明正在运行旧版 server。请按上述升级步骤操作。 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,7 +22,8 @@ | |
| sys.path.insert(0, str(_project_root)) | ||
|
|
||
| import logging | ||
| import sys | ||
| import subprocess | ||
| from importlib.metadata import version as pkg_version | ||
|
|
||
| from fastapi.staticfiles import StaticFiles | ||
| from fastapi_pagination import add_pagination | ||
|
|
@@ -50,10 +51,51 @@ | |
| auto_include_routers(router, "", "app/api") | ||
| api.include_router(router, prefix=f"{prefix}/v1") | ||
|
|
||
| # Server version — read once at import time so it reflects the running code | ||
| try: | ||
| SERVER_VERSION = pkg_version("Eigent") | ||
| except Exception: | ||
| SERVER_VERSION = "unknown" | ||
|
|
||
|
Comment on lines
+54
to
+59
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically from now on version is linked to docker image, right?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The version field was only informational — the actual stale detection uses server_hash (git commit hash), not the package version. |
||
| # Git hash of the last commit that touched server/ — used for stale-server detection. | ||
| # Captured once at startup; stays constant while the process lives. | ||
| # 1) Try git directly (works in local dev) | ||
| # 2) Fall back to .image_env baked by Dockerfile (works in Docker) | ||
| def _read_server_code_hash() -> str: | ||
| # Try git first (local dev) | ||
| try: | ||
| h = subprocess.check_output( | ||
| ["git", "log", "-1", "--format=%H", "--", "server/"], | ||
| cwd=str(_project_root), text=True, stderr=subprocess.DEVNULL, | ||
| ).strip() | ||
| if h: | ||
| return h | ||
| except Exception: | ||
| pass | ||
| # Fallback: read from Docker-baked .image_env | ||
| try: | ||
| env_file = pathlib.Path(__file__).parent / ".image_env" | ||
| for line in env_file.read_text().splitlines(): | ||
| if line.startswith("EIGENT_SERVER_GIT_COMMIT="): | ||
| v = line.split("=", 1)[1].strip() | ||
| if v: | ||
| return v | ||
| except Exception: | ||
| pass | ||
| return "unknown" | ||
|
|
||
| SERVER_CODE_HASH = _read_server_code_hash() | ||
|
|
||
|
|
||
| # Health check at root level for Docker healthcheck (GET /health) | ||
| @api.get("/health", tags=["Health"]) | ||
| async def health_check(): | ||
| return {"status": "ok", "service": "eigent-server"} | ||
| return { | ||
| "status": "ok", | ||
| "service": "eigent-server", | ||
| "version": SERVER_VERSION, | ||
| "server_hash": SERVER_CODE_HASH, | ||
| } | ||
|
Comment on lines
91
to
+98
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually it turns out if building an image /health endpoint is |
||
|
|
||
| # Backward-compatible webhook route (/api/webhook/...) | ||
| from app.domains.trigger.api.webhook_controller import router as webhook_router | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took off a direction to see if adding to docker compose would be easier; but turns out nope for dynamic values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right — docker-compose.yml can only pass static values or .env vars as build args