diff --git a/.env.example b/.env.example index 62d5ba8f..ba9a3d1a 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,9 @@ # MateClaw 环境变量配置 -# 复制此文件为 .env 并填写实际值 +# 复制此文件为 .env 并填写实际值:cp .env.example .env +# +# ⚠️ 所有标注「必填」的项若没配置,`docker compose up` 会直接失败退出,避免把默认/示例值带到生产环境。 + +# ==================== LLM / 搜索 ==================== # 阿里云 DashScope API Key(必填) # 申请地址:https://dashscope.aliyun.com/ @@ -9,9 +13,26 @@ DASHSCOPE_API_KEY=your-dashscope-api-key-here # 申请地址:https://serper.dev/ SERPER_API_KEY= -# 数据库配置(Docker 部署时无需修改) +# ==================== 数据库(Docker 模式必填) ==================== + DB_HOST=localhost DB_PORT=3306 DB_NAME=mateclaw DB_USERNAME=mateclaw -DB_PASSWORD=mateclaw123 + +# ⚠️ 必填且请改成强密码(至少 16 位,含大小写+数字+符号)。 +# docker-compose.yml 会通过 ${DB_PASSWORD:?} 强制要求此项。 +DB_PASSWORD=change-me-strong-user-password + +# ⚠️ MySQL root 账号密码,仅用于容器内初始化。请改成与 DB_PASSWORD 不同的强密码。 +DB_ROOT_PASSWORD=change-me-strong-root-password + +# ==================== 安全(强烈建议覆盖) ==================== + +# JWT 签名密钥。若留空,服务器会用内置默认值并在启动日志里 WARN。 +# 生产部署必须设置,至少 32 位随机字符串:openssl rand -base64 48 +JWT_SECRET= + +# CORS 白名单(逗号分隔,如 https://mateclaw.example.com,https://admin.example.com)。 +# 若留空,服务器会允许所有 origin 并在启动日志里 WARN。生产部署务必设置。 +MATECLAW_CORS_ALLOWED_ORIGINS= diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 00000000..5fed302d --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,159 @@ +# Upgrading MateClaw + +## 1.0.x → 1.1.0 + +**TL;DR** — Most users have nothing to do. Restart with 1.1.0, Flyway's built-in repair heals known checksum drift, Ollama auto-discovery rewrites the bad `:latest` defaults, and everything else self-converges. Docker Compose deployments need a one-time `.env` update. + +See `docs/en/releases/1.1.0.md` for the feature changelog. + +--- + +## For everyone + +### ⚠️ What happens automatically (no action) + +- **Flyway migration self-heal** — 1.1.0 rewrote all MySQL migrations V2–V14 to replace unsupported `ADD COLUMN IF NOT EXISTS` syntax (Gitee #IIYHLJ). `FlywayRepairConfig` runs `flyway.repair()` on every boot, so the new checksums auto-accept and migration resumes from wherever your schema is. +- **Ollama default model** — if your 1.0.x run auto-picked a model tag Ollama no longer has (commonly `deepseek-r1:latest`), on 1.1.0 restart `OllamaAutoDiscoveryRunner` detects the broken default and re-picks a tag-capable model (e.g. `deepseek-r1:7b`, `qwen3:latest`), preferring one that supports function calling. +- **Stale `mate_model_config` rows** — idempotent seed data reconciles on each startup. + +### 📋 Recommended pre-upgrade steps + +1. Back up your database — `mateclaw` schema on MySQL, or `data/mateclaw.mv.db` on H2. +2. Back up `data/` directory (skill workspaces, uploaded files, memory files). +3. Note your current default model in Settings → Models in case you want to switch back. + +### 🚀 Upgrade + +```bash +git pull +cd mateclaw-server +mvn clean package -DskipTests +# then restart your service per your deployment method +``` + +Or for Desktop app users: just update to 1.1.0 via the in-app updater or re-download. + +--- + +## For Docker Compose deployments + +**One-time migration step required** — 1.1.0 refuses to start with default hardcoded passwords. + +### 1. Copy-paste merge the new `.env.example` keys + +```bash +cp .env .env.backup +# open .env.example — it has new required keys: +# DB_PASSWORD= (was default 'mateclaw123', now MUST be overridden) +# DB_ROOT_PASSWORD= (new, required for MySQL root) +# JWT_SECRET= (new, strongly recommended) +# MATECLAW_CORS_ALLOWED_ORIGINS= (new, strongly recommended for prod) +``` + +### 2. Set strong values in your `.env` + +```env +# STRONG passwords — at least 16 chars, mixed case + digits + symbols +DB_PASSWORD= +DB_ROOT_PASSWORD= + +# 32+ char random string — generate with: openssl rand -base64 48 +JWT_SECRET= + +# Production CORS allowlist — comma-separated, no wildcards +MATECLAW_CORS_ALLOWED_ORIGINS=https://mateclaw.example.com +``` + +If any of `DB_PASSWORD` / `DB_ROOT_PASSWORD` / `DASHSCOPE_API_KEY` is missing, `docker compose up` will fail fast with a clear error — this is intentional. + +### 3. Existing MySQL volume compatibility + +If you already ran 1.0.x with the old default password (`mateclaw123`), **your existing MySQL volume still has the old root password inside**. You have two options: + +**Option A — keep existing password** (fastest, least secure): +Set `DB_ROOT_PASSWORD=mateclaw123` and `DB_PASSWORD=mateclaw123` in `.env` to match. Upgrade works. Then rotate after upgrade using `ALTER USER ... IDENTIFIED BY ...` inside the MySQL container. + +**Option B — fresh volume with new password** (cleanest, loses DB if not backed up): +```bash +docker compose down -v # ⚠️ deletes mysql_data volume; back up first +# edit .env with new strong password +docker compose up -d +``` +Then re-import your backup if you kept one. + +### 4. Restart + +```bash +docker compose up -d +docker compose logs -f mateclaw-server # watch for "Flyway Successfully applied N migrations" +``` + +Expected log lines during boot: +- `Flyway Successfully applied N migrations to schema mateclaw` +- `Ollama: auto-activated default model ''` (if you use Ollama — should NOT say `:latest` any more) +- `[Security] Using default JWT secret!` → means you forgot to set `JWT_SECRET` — fix and restart + +--- + +## For local dev / H2 deployments + +No action required. `mvn spring-boot:run` picks up the latest migrations on next start, Flyway repair handles checksum drift, H2 file at `data/mateclaw.mv.db` is preserved. + +--- + +## Known migration quirks + +### 1. If you manually fiddled with `flyway_schema_history` + +In 1.0.x some users hit Flyway version collisions (V8/V9 and V9/V10) which 1.1.0 fixes by renumbering. If you manually deleted rows from `flyway_schema_history` you may see `Validate failed` on 1.1.0 startup — run: + +```sql +-- MySQL +DELETE FROM flyway_schema_history WHERE success = 0; +``` + +Then restart. `FlywayRepairConfig` will rebuild history from current schema state. + +### 2. If your Ollama models are all in the no-tools family + +After upgrade, agents that require tool calling will log a warning on first invocation: + +``` +Ollama: auto-activated default model '...' but its family does not support tool calling +``` + +Fix — pull a tool-capable model, or switch default in Settings → Models: + +```bash +ollama pull qwen3 +# or +ollama pull llama3.1:8b +# or +ollama pull mistral-nemo +``` + +### 3. If you had custom tools using `extract_document_text` / wiki tools + +Wiki chunk schema changed (new `embedding` + `embedding_model` columns on `mate_wiki_chunk`). Your existing wiki pages work unchanged; only semantic search is new and requires an embedding model to be configured in Settings → Models (a default DashScope embedding is seeded). + +--- + +## Rolling back to 1.0.x + +Not recommended (some new tables / columns don't exist in 1.0.x), but possible if you backed up the DB before upgrade: + +```bash +git checkout v1.0.418 +# restore DB backup +docker compose up -d # or mvn spring-boot:run +``` + +If you need to keep the new data but downgrade the app, you're in unsupported territory — open a Gitee issue. + +--- + +## Getting help + +- **Logs first**: `mateclaw-server/logs/mateclaw.log` + `mateclaw-error.log` have everything. Flyway decisions are at INFO level in main log. +- **Doctor tab**: in-app Settings → Doctor runs basic health checks +- **Gitee**: https://gitee.com/matevip_admin/mateclaw/issues — include your upgrade path (1.0.?? → 1.1.0), profile (H2 / MySQL), and the last 100 lines of startup log diff --git a/docker-compose.yml b/docker-compose.yml index 47735131..c61b9498 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,15 +2,20 @@ version: '3.8' services: # MySQL 数据库 + # + # ⚠️ 密码通过环境变量传入,必须从 .env 文件提供。首次部署前: + # 1. cp .env.example .env + # 2. 编辑 .env 把 DB_ROOT_PASSWORD / DB_PASSWORD 改成强密码 + # 未设置会直接在 `docker compose up` 时报错,避免把默认密码带到生产环境。 mysql: image: mysql:8.0 container_name: mateclaw-mysql restart: unless-stopped environment: - MYSQL_ROOT_PASSWORD: mateclaw123 - MYSQL_DATABASE: mateclaw - MYSQL_USER: mateclaw - MYSQL_PASSWORD: mateclaw123 + MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:?DB_ROOT_PASSWORD is required in .env} + MYSQL_DATABASE: ${DB_NAME:-mateclaw} + MYSQL_USER: ${DB_USERNAME:-mateclaw} + MYSQL_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required in .env} TZ: Asia/Shanghai ports: - "3306:3306" @@ -58,11 +63,13 @@ services: SPRING_PROFILES_ACTIVE: mysql DB_HOST: mysql DB_PORT: 3306 - DB_NAME: mateclaw - DB_USERNAME: mateclaw - DB_PASSWORD: mateclaw123 - DASHSCOPE_API_KEY: ${DASHSCOPE_API_KEY} + DB_NAME: ${DB_NAME:-mateclaw} + DB_USERNAME: ${DB_USERNAME:-mateclaw} + DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required in .env} + DASHSCOPE_API_KEY: ${DASHSCOPE_API_KEY:?DASHSCOPE_API_KEY is required in .env} SERPER_API_KEY: ${SERPER_API_KEY:-} + JWT_SECRET: ${JWT_SECRET:-} + MATECLAW_CORS_ALLOWED_ORIGINS: ${MATECLAW_CORS_ALLOWED_ORIGINS:-} ports: - "18080:18080" volumes: diff --git a/mateclaw-server/pom.xml b/mateclaw-server/pom.xml index c2ff2390..77c162e8 100644 --- a/mateclaw-server/pom.xml +++ b/mateclaw-server/pom.xml @@ -6,7 +6,7 @@ vip.mate mateclaw-server - 1.1.0-SNAPSHOT + 1.1.0 jar MateClaw Server diff --git a/mateclaw-server/src/test/java/vip/mate/agent/graph/edge/ReasoningDispatcherLlmCallCountTest.java b/mateclaw-server/src/test/java/vip/mate/agent/graph/edge/ReasoningDispatcherLlmCallCountTest.java index 8228ba9d..f245ca80 100644 --- a/mateclaw-server/src/test/java/vip/mate/agent/graph/edge/ReasoningDispatcherLlmCallCountTest.java +++ b/mateclaw-server/src/test/java/vip/mate/agent/graph/edge/ReasoningDispatcherLlmCallCountTest.java @@ -29,12 +29,16 @@ class ReasoningDispatcherLlmCallCountTest { @DisplayName("LLM 调用计数限制") class LlmCallCountLimit { + // ReasoningDispatcher.LLM_CALL_MULTIPLIER = 5(默认 maxIterations=10 → 阈值 50)。 + // 本系列用例固定 MAX_ITERATIONS=10,所以 limit=50、limit-1=49。 + // 如果后续 MULTIPLIER 再改,这里的常量也要同步。 + @Test @DisplayName("达到上限但有最终回答 → 放行到 finalAnswerNode") void shouldAllowFinalAnswerEvenWhenLlmCallLimitReached() throws Exception { OverAllState state = new OverAllState(Map.of( CURRENT_ITERATION, 5, MAX_ITERATIONS, 10, - LLM_CALL_COUNT, 30, NEEDS_TOOL_CALL, false + LLM_CALL_COUNT, 50, NEEDS_TOOL_CALL, false )); assertEquals(FINAL_ANSWER_NODE, dispatcher.apply(state)); } @@ -44,7 +48,7 @@ class ReasoningDispatcherLlmCallCountTest { void shouldBlockToolCallWhenLlmCallLimitReached() throws Exception { OverAllState state = new OverAllState(Map.of( CURRENT_ITERATION, 5, MAX_ITERATIONS, 10, - LLM_CALL_COUNT, 30, NEEDS_TOOL_CALL, true + LLM_CALL_COUNT, 50, NEEDS_TOOL_CALL, true )); assertEquals(LIMIT_EXCEEDED_NODE, dispatcher.apply(state)); } @@ -54,7 +58,7 @@ class ReasoningDispatcherLlmCallCountTest { void shouldBlockSummarizeWhenLlmCallLimitReached() throws Exception { OverAllState state = new OverAllState(Map.of( CURRENT_ITERATION, 5, MAX_ITERATIONS, 10, - LLM_CALL_COUNT, 30, + LLM_CALL_COUNT, 50, NEEDS_TOOL_CALL, false, SHOULD_SUMMARIZE, true )); assertEquals(LIMIT_EXCEEDED_NODE, dispatcher.apply(state)); @@ -95,7 +99,7 @@ class ReasoningDispatcherLlmCallCountTest { void shouldNotTriggerAtLimitMinusOne() throws Exception { OverAllState state = new OverAllState(Map.of( CURRENT_ITERATION, 5, MAX_ITERATIONS, 10, - LLM_CALL_COUNT, 29, NEEDS_TOOL_CALL, true + LLM_CALL_COUNT, 49, NEEDS_TOOL_CALL, true )); assertEquals(ACTION_NODE, dispatcher.apply(state)); } diff --git a/mateclaw-server/src/test/java/vip/mate/agent/graph/node/FinalAnswerNodeTest.java b/mateclaw-server/src/test/java/vip/mate/agent/graph/node/FinalAnswerNodeTest.java index 69ad31e3..aa0e0cf4 100644 --- a/mateclaw-server/src/test/java/vip/mate/agent/graph/node/FinalAnswerNodeTest.java +++ b/mateclaw-server/src/test/java/vip/mate/agent/graph/node/FinalAnswerNodeTest.java @@ -86,7 +86,8 @@ class FinalAnswerNodeTest { void shouldFallbackWhenNoContentAndNotStopped() throws Exception { OverAllState state = new OverAllState(Map.of()); Map result = node.apply(state); - assertEquals("未能生成回答,请重试。", result.get(FINAL_ANSWER)); + // Fallback 文案在 Jobs-voice 改写后统一为英文(commit 01c3888)。 + assertEquals("Failed to generate a response, please retry.", result.get(FINAL_ANSWER)); assertEquals("error_fallback", result.get(FINISH_REASON)); } } diff --git a/mateclaw-server/src/test/java/vip/mate/skill/runtime/SkillSecurityServiceTest.java b/mateclaw-server/src/test/java/vip/mate/skill/runtime/SkillSecurityServiceTest.java index 7ebba03e..a20027b8 100644 --- a/mateclaw-server/src/test/java/vip/mate/skill/runtime/SkillSecurityServiceTest.java +++ b/mateclaw-server/src/test/java/vip/mate/skill/runtime/SkillSecurityServiceTest.java @@ -140,9 +140,15 @@ class SkillSecurityServiceTest { SkillValidationResult result = securityService.scanContent(content, "db_skill"); - assertTrue(result.isBlocked()); + // scanContent 走 FileRole.DOCUMENTATION 降级规则(database-only skill 只是描述文本, + // 不能执行代码):SUDO/CURL_PIPE 等 script 规则会替换为 SUDO_DOC/CURL_PIPE_SH_DOC, + // 严重度降为 MEDIUM,**生成 finding 但不阻断**。阻断语义由 scanDirectory 里 + // scripts/* 真实可执行文件触发。 assertTrue(result.getFindings().stream() - .anyMatch(f -> f.getRuleId().equals("SUDO_USAGE") || f.getRuleId().equals("DESTRUCTIVE_RM"))); + .anyMatch(f -> f.getRuleId().equals("SUDO_DOC") + || f.getRuleId().equals("SUDO_USAGE") + || f.getRuleId().equals("DESTRUCTIVE_RM")), + "danger-pattern finding should still be detected in database-only skill content"); } @Test @@ -203,7 +209,8 @@ class SkillSecurityServiceTest { .orElse(null); assertNotNull(sudoFinding); - assertEquals("scripts/bad.sh", sudoFinding.getFilePath()); + // 归一化路径分隔符,让断言在 Windows / Unix 都稳定(Path.toString 在 Windows 用 `\`) + assertEquals("scripts/bad.sh", sudoFinding.getFilePath().replace('\\', '/')); assertEquals(3, sudoFinding.getLineNumber()); assertNotNull(sudoFinding.getSnippet()); } diff --git a/mateclaw-ui/package.json b/mateclaw-ui/package.json index 04877a6e..564ceb64 100644 --- a/mateclaw-ui/package.json +++ b/mateclaw-ui/package.json @@ -1,6 +1,6 @@ { "name": "mateclaw-ui", - "version": "1.1.0-SNAPSHOT", + "version": "1.1.0", "private": true, "type": "module", "description": "MateClaw - Personal AI Assistant Web Console", diff --git a/mateclaw-webchat/package.json b/mateclaw-webchat/package.json index 138aefb4..b01f8407 100644 --- a/mateclaw-webchat/package.json +++ b/mateclaw-webchat/package.json @@ -1,6 +1,6 @@ { "name": "@mateclaw/webchat", - "version": "1.0.0", + "version": "1.1.0-SNAPSHOT", "description": "MateClaw WebChat embeddable widget", "type": "module", "main": "dist/mateclaw-webchat.umd.js",