From 84010e33acb8a7b5b5983861d016066dd88236e7 Mon Sep 17 00:00:00 2001 From: "mula.liu" Date: Tue, 10 Mar 2026 12:40:01 +0800 Subject: [PATCH] v0.1.4 --- .env.prod.example | 15 +++--- README.md | 3 +- backend/.env.example | 4 -- backend/core/settings.py | 4 -- backend/main.py | 111 +++------------------------------------ docker-compose.prod.yml | 2 - 6 files changed, 16 insertions(+), 123 deletions(-) diff --git a/.env.prod.example b/.env.prod.example index 164e3db..4730d47 100644 --- a/.env.prod.example +++ b/.env.prod.example @@ -23,13 +23,16 @@ PIP_TRUSTED_HOST=pypi.tuna.tsinghua.edu.cn # Frontend package registry mirror (used by yarn, recommended in CN) NPM_REGISTRY=https://registry.npmmirror.com -# Optional DB override. -# Keep empty to use SQLite at: -# sqlite:///{HOST_DATA_ROOT}/nanobot_dashboard.db +# Database (choose one: SQLite / PostgreSQL / MySQL) +# SQLite example: +# DATABASE_URL=sqlite:///${HOST_DATA_ROOT}/nanobot_dashboard.db +# PostgreSQL example: # DATABASE_URL=postgresql+psycopg://user:password@127.0.0.1:5432/nanobot_dashboard +# MySQL example: +# DATABASE_URL=mysql+pymysql://user:password@127.0.0.1:3306/nanobot_dashboard DATABASE_URL=postgresql+psycopg://postgres:change_me@127.0.0.1:5432/nanobot_dashboard -# Redis cache +# Redis cache (optional) REDIS_ENABLED=true REDIS_URL=redis://127.0.0.1:6379/8 REDIS_PREFIX=dashboard_nanobot @@ -40,7 +43,3 @@ PANEL_ACCESS_PASSWORD=change_me_panel_password # Max upload size for backend validation (MB) UPLOAD_MAX_MB=100 - -# 升级迁移 -AUTO_MIGRATE_SQLITE_TO_PRIMARY=true -SQLITE_MIGRATION_SOURCE=../data/nanobot_dashboard.db diff --git a/README.md b/README.md index 75b7d5d..76305c2 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,9 @@ graph TD - 示例文件:`backend/.env.example` - 本地配置:`backend/.env` - 关键项: - - `DATABASE_URL`:数据库连接串(支持 SQLite / PostgreSQL / MySQL) + - `DATABASE_URL`:数据库连接串(三选一:SQLite / PostgreSQL / MySQL) - `DATABASE_ECHO`:SQL 日志输出开关 + - 不提供自动数据迁移(如需升级迁移请离线完成后再切换连接串) - `DATA_ROOT`、`BOTS_WORKSPACE_ROOT`:运行数据与 Bot 工作目录 - `DEFAULT_*_MD`:创建向导默认模板来源(其中默认输出规范已并入 `DEFAULT_AGENTS_MD`) - 前端: diff --git a/backend/.env.example b/backend/.env.example index 0bf35b0..2c691f8 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -12,10 +12,6 @@ BOTS_WORKSPACE_ROOT=../workspace/bots # DATABASE_URL=mysql+pymysql://user:password@127.0.0.1:3306/nanobot_dashboard # Show SQL statements in backend logs (debug only). DATABASE_ECHO=true -# Auto-migrate legacy SQLite data into primary database on first startup. -AUTO_MIGRATE_SQLITE_TO_PRIMARY=true -# Legacy SQLite source path used by the auto-migrator. -SQLITE_MIGRATION_SOURCE=../data/nanobot_dashboard.db # Redis cache REDIS_ENABLED=false diff --git a/backend/core/settings.py b/backend/core/settings.py index 4a9ee6a..58bd1cc 100644 --- a/backend/core/settings.py +++ b/backend/core/settings.py @@ -122,10 +122,6 @@ REDIS_ENABLED: Final[bool] = _env_bool("REDIS_ENABLED", False) REDIS_URL: Final[str] = str(os.getenv("REDIS_URL") or "").strip() REDIS_PREFIX: Final[str] = str(os.getenv("REDIS_PREFIX") or "dashboard_nanobot").strip() or "dashboard_nanobot" REDIS_DEFAULT_TTL: Final[int] = _env_int("REDIS_DEFAULT_TTL", 60, 1, 86400) -AUTO_MIGRATE_SQLITE_TO_PRIMARY: Final[bool] = _env_bool("AUTO_MIGRATE_SQLITE_TO_PRIMARY", True) -SQLITE_MIGRATION_SOURCE: Final[str] = _normalize_dir_path( - os.getenv("SQLITE_MIGRATION_SOURCE", str(Path(DATA_ROOT) / "nanobot_dashboard.db")) -) PANEL_ACCESS_PASSWORD: Final[str] = str(os.getenv("PANEL_ACCESS_PASSWORD") or "").strip() DEFAULT_AGENTS_MD: Final[str] = _env_text( diff --git a/backend/main.py b/backend/main.py index f84fb1a..1552b9c 100644 --- a/backend/main.py +++ b/backend/main.py @@ -4,7 +4,6 @@ import mimetypes import os import re import shutil -import sqlite3 import tempfile import zipfile from datetime import datetime @@ -20,10 +19,9 @@ from sqlmodel import Session, select from core.config_manager import BotConfigManager from core.cache import cache -from core.database import align_postgres_sequences, engine, get_session, init_database +from core.database import engine, get_session, init_database from core.docker_manager import BotDockerManager from core.settings import ( - AUTO_MIGRATE_SQLITE_TO_PRIMARY, BOTS_WORKSPACE_ROOT, DATA_ROOT, DATABASE_ECHO, @@ -39,7 +37,6 @@ from core.settings import ( REDIS_ENABLED, REDIS_PREFIX, REDIS_URL, - SQLITE_MIGRATION_SOURCE, UPLOAD_MAX_MB, ) from models.bot import BotInstance, BotMessage, NanobotImage @@ -349,7 +346,12 @@ async def bot_access_password_guard(request: Request, call_next): return await call_next(request) if request.url.path.startswith("/api/"): - if request.url.path in {"/api/panel/auth/status", "/api/panel/auth/login"}: + if request.url.path in { + "/api/panel/auth/status", + "/api/panel/auth/login", + "/api/health", + "/api/health/cache", + }: return await call_next(request) panel_error = _validate_panel_access_password(_get_supplied_panel_password_http(request)) if panel_error: @@ -427,104 +429,6 @@ def _invalidate_images_cache() -> None: cache.delete(_cache_key_images()) -def _parse_dt(raw: Any) -> datetime: - if isinstance(raw, datetime): - return raw - text = str(raw or "").strip() - if not text: - return datetime.utcnow() - for candidate in (text, text.replace("Z", "+00:00")): - try: - return datetime.fromisoformat(candidate) - except Exception: - continue - return datetime.utcnow() - - -def _target_has_data() -> bool: - with Session(engine) as session: - if session.exec(select(BotInstance).limit(1)).first() is not None: - return True - if session.exec(select(BotMessage).limit(1)).first() is not None: - return True - if session.exec(select(NanobotImage).limit(1)).first() is not None: - return True - return False - - -def _migrate_sqlite_if_needed() -> None: - if DATABASE_ENGINE == "sqlite": - return - if not AUTO_MIGRATE_SQLITE_TO_PRIMARY: - return - source_path = str(SQLITE_MIGRATION_SOURCE or "").strip() - if not source_path or not os.path.isfile(source_path): - return - if _target_has_data(): - return - - conn = sqlite3.connect(source_path) - conn.row_factory = sqlite3.Row - try: - with Session(engine) as session: - for row in conn.execute("SELECT * FROM botinstance"): - bot_id = str(row["id"]) - if session.get(BotInstance, bot_id): - continue - session.add( - BotInstance( - id=bot_id, - name=str(row["name"] or ""), - access_password=str(row["access_password"] or ""), - workspace_dir=str(row["workspace_dir"] or ""), - docker_status=str(row["docker_status"] or "STOPPED"), - current_state=str(row["current_state"] or "IDLE"), - last_action=str(row["last_action"] or "") or None, - image_tag=str(row["image_tag"] or "nanobot-base:v0.1.4"), - created_at=_parse_dt(row["created_at"]), - updated_at=_parse_dt(row["updated_at"]), - ) - ) - - for row in conn.execute("SELECT * FROM nanobotimage"): - tag = str(row["tag"]) - if session.get(NanobotImage, tag): - continue - session.add( - NanobotImage( - tag=tag, - image_id=str(row["image_id"] or "") or None, - version=str(row["version"] or ""), - status=str(row["status"] or "READY"), - source_dir=str(row["source_dir"] or "") or None, - created_at=_parse_dt(row["created_at"]), - ) - ) - - session.commit() - - for row in conn.execute("SELECT * FROM botmessage ORDER BY id ASC"): - message_id = int(row["id"]) - if session.get(BotMessage, message_id): - continue - session.add( - BotMessage( - id=message_id, - bot_id=str(row["bot_id"] or ""), - role=str(row["role"] or "assistant"), - text=str(row["text"] or ""), - media_json=str(row["media_json"] or "") or None, - feedback=str(row["feedback"] or "") or None, - feedback_at=_parse_dt(row["feedback_at"]) if row["feedback_at"] else None, - created_at=_parse_dt(row["created_at"]), - ) - ) - session.commit() - align_postgres_sequences() - finally: - conn.close() - - @app.on_event("startup") async def on_startup(): app.state.main_loop = asyncio.get_running_loop() @@ -534,7 +438,6 @@ async def on_startup(): print(f"🧠 Redis 缓存: {'enabled' if cache.ping() else 'disabled'} ({REDIS_URL if REDIS_ENABLED else 'not configured'})") print(f"🔐 面板访问密码: {'enabled' if str(PANEL_ACCESS_PASSWORD or '').strip() else 'disabled'}") init_database() - _migrate_sqlite_if_needed() cache.delete_prefix("") with Session(engine) as session: for bot in session.exec(select(BotInstance)).all(): diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 5d7bf05..1d03238 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -24,8 +24,6 @@ services: REDIS_PREFIX: ${REDIS_PREFIX:-dashboard_nanobot} REDIS_DEFAULT_TTL: ${REDIS_DEFAULT_TTL:-60} PANEL_ACCESS_PASSWORD: ${PANEL_ACCESS_PASSWORD:-} - AUTO_MIGRATE_SQLITE_TO_PRIMARY: ${AUTO_MIGRATE_SQLITE_TO_PRIMARY:-true} - SQLITE_MIGRATION_SOURCE: ${SQLITE_MIGRATION_SOURCE:-} volumes: - /var/run/docker.sock:/var/run/docker.sock - ${HOST_DATA_ROOT}:${HOST_DATA_ROOT}