mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(wiki): surface scan errors and fix Chinese path handling in Docker (#260)
Surface directory-scan failures to the user via toast and render ScanResult.errors[]; expose MATE_WIKI_ALLOWED_SOURCE_ROOTS as a Docker env entry with blank-entry filtering in the path validator; set C.UTF-8 locale in the runtime image so non-ASCII file names decode correctly during scans. Fixes #259
This commit is contained in:
parent
808047d723
commit
29fcfb5572
13
.env.example
13
.env.example
@ -74,6 +74,19 @@ MATECLAW_BROWSER_CHANNEL=
|
||||
MATECLAW_OAUTH_OPENAI_DEPLOYMENT_MODE=
|
||||
MATECLAW_OAUTH_OPENAI_CALLBACK_BIND_HOST=
|
||||
|
||||
# ==================== Wiki 知识库目录白名单(Docker 模式,可选)====================
|
||||
#
|
||||
# Docker 生产部署开启了路径安全校验(fail-closed)。
|
||||
# 知识库使用「目录扫描」功能时,扫描路径必须在此白名单内,否则返回 400 错误。
|
||||
# 多个路径用英文逗号分隔;留空则禁止所有目录扫描。
|
||||
#
|
||||
# 示例:MATE_WIKI_ALLOWED_SOURCE_ROOTS=/data/wiki,/opt/docs
|
||||
#
|
||||
# 同时在 docker-compose.yml 的 volumes 里把宿主机目录挂进容器,例如:
|
||||
# volumes:
|
||||
# - /your/host/path:/data/wiki
|
||||
MATE_WIKI_ALLOWED_SOURCE_ROOTS=
|
||||
|
||||
# ── Maven 镜像(国内加速)─────────────────────────────────────────
|
||||
# 在中国大陆构建时取消注释,将 Aliyun 仓库优先级提前,大幅提速 mvn 拉包。
|
||||
# 空值(默认)使用 US Maven Central → Google CDN → Aliyun 的顺序。
|
||||
|
||||
@ -96,6 +96,10 @@ services:
|
||||
# 本机 Docker 若要强制使用 localhost:1455 回调,可在 .env 显式设为 local。
|
||||
MATECLAW_OAUTH_OPENAI_DEPLOYMENT_MODE: ${MATECLAW_OAUTH_OPENAI_DEPLOYMENT_MODE:-}
|
||||
MATECLAW_OAUTH_OPENAI_CALLBACK_BIND_HOST: ${MATECLAW_OAUTH_OPENAI_CALLBACK_BIND_HOST:-0.0.0.0}
|
||||
# Wiki 知识库目录扫描白名单(逗号分隔,留空则禁止所有目录扫描)。
|
||||
# 示例:MATE_WIKI_ALLOWED_SOURCE_ROOTS=/data/wiki,/opt/docs
|
||||
# 记得同步在 volumes 里把宿主机路径挂进容器。
|
||||
MATE_WIKI_ALLOWED_SOURCE_ROOTS: ${MATE_WIKI_ALLOWED_SOURCE_ROOTS:-}
|
||||
# Chromium needs a real /dev/shm. Docker defaults to 64MB which causes
|
||||
# SIGBUS / "Target page closed" errors under load. 2GB is the usual
|
||||
# recommendation for Playwright / headless chrome.
|
||||
|
||||
@ -105,7 +105,9 @@ RUN apt-get update \
|
||||
# BrowserLauncher's BUNDLED strategy will then succeed without extra config.
|
||||
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright \
|
||||
TZ=Asia/Shanghai \
|
||||
JAVA_TOOL_OPTIONS="-Duser.timezone=Asia/Shanghai"
|
||||
LANG=C.UTF-8 \
|
||||
LC_ALL=C.UTF-8 \
|
||||
JAVA_TOOL_OPTIONS="-Duser.timezone=Asia/Shanghai -Dsun.jnu.encoding=UTF-8"
|
||||
|
||||
COPY --from=builder /build/mateclaw-server/target/*.jar app.jar
|
||||
EXPOSE 18088
|
||||
|
||||
@ -46,18 +46,20 @@ public class WikiSourcePathValidator {
|
||||
}
|
||||
Path resolved = canonicalize(Paths.get(rawPath));
|
||||
List<String> roots = properties.getAllowedSourceRoots();
|
||||
if (roots == null || roots.isEmpty()) {
|
||||
// Filter blank entries so MATE_WIKI_ALLOWED_SOURCE_ROOTS="" (unset env var)
|
||||
// behaves identically to an empty list rather than a list with one blank entry.
|
||||
List<String> nonBlankRoots = (roots == null) ? List.of()
|
||||
: roots.stream().filter(r -> r != null && !r.isBlank()).toList();
|
||||
if (nonBlankRoots.isEmpty()) {
|
||||
if (properties.isRequireAllowedRoots()) {
|
||||
throw new IllegalArgumentException(
|
||||
"No allowed source roots are configured; refusing the path (fail-closed). "
|
||||
+ "Set mate.wiki.allowed-source-roots to permit directories.");
|
||||
+ "Set MATE_WIKI_ALLOWED_SOURCE_ROOTS (env var) or "
|
||||
+ "mate.wiki.allowed-source-roots to permit directories.");
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
for (String root : roots) {
|
||||
if (root == null || root.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
for (String root : nonBlankRoots) {
|
||||
Path rootPath = canonicalize(Paths.get(root));
|
||||
if (resolved.startsWith(rootPath)) {
|
||||
return resolved;
|
||||
|
||||
@ -30,8 +30,10 @@ spring:
|
||||
# validation. With no allowed-source-roots configured, every KB source
|
||||
# directory is rejected rather than allowing full-filesystem reads — so a
|
||||
# missing allow-list cannot silently re-open arbitrary directory scanning.
|
||||
# Operators set mate.wiki.allowed-source-roots to permit specific roots.
|
||||
# Set MATE_WIKI_ALLOWED_SOURCE_ROOTS in .env (comma-separated paths):
|
||||
# MATE_WIKI_ALLOWED_SOURCE_ROOTS=/data/wiki,/opt/docs
|
||||
# The default profile (H2 / desktop / single-tenant) leaves this off.
|
||||
mate:
|
||||
wiki:
|
||||
require-allowed-roots: true
|
||||
allowed-source-roots: ${MATE_WIKI_ALLOWED_SOURCE_ROOTS:}
|
||||
|
||||
@ -2264,6 +2264,7 @@ export default {
|
||||
scan: 'Scan',
|
||||
scanning: 'Scanning...',
|
||||
scanResult: 'Scanned {scanned} files, added {added}, skipped {skipped}',
|
||||
scanFailed: 'Scan failed',
|
||||
saving: 'Saving...',
|
||||
status: {
|
||||
active: 'ACTIVE',
|
||||
|
||||
@ -2276,6 +2276,7 @@ export default {
|
||||
scan: '扫描',
|
||||
scanning: '扫描中...',
|
||||
scanResult: '已扫描 {scanned} 个文件,新增 {added} 个,跳过 {skipped} 个',
|
||||
scanFailed: '扫描失败',
|
||||
saving: '保存中...',
|
||||
status: {
|
||||
active: '启用',
|
||||
|
||||
@ -67,6 +67,9 @@
|
||||
</div>
|
||||
<div v-if="scanResult" class="scan-result">
|
||||
{{ t('wiki.scanResult', { scanned: scanResult.scanned, added: scanResult.added, skipped: scanResult.skipped }) }}
|
||||
<div v-if="scanResult.errors?.length" class="scan-errors">
|
||||
<span v-for="err in scanResult.errors" :key="err" class="scan-error-item">{{ err }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Raw materials list -->
|
||||
@ -680,7 +683,8 @@ async function handleScanDir() {
|
||||
const result = await store.scanDirectory(store.currentKB.id)
|
||||
scanResult.value = result
|
||||
} catch (e: any) {
|
||||
console.error('Scan failed', e)
|
||||
const msg = e?.response?.data?.message || e?.message || t('wiki.scanFailed')
|
||||
mcToast.error(msg)
|
||||
} finally {
|
||||
scanning.value = false
|
||||
}
|
||||
@ -708,6 +712,8 @@ async function handleScanDir() {
|
||||
.dir-input { flex: 1; border: none; background: transparent; font-size: 13px; color: var(--mc-text-primary); outline: none; }
|
||||
.dir-input::placeholder { color: var(--mc-text-tertiary); }
|
||||
.scan-result { font-size: 12px; color: var(--mc-text-secondary); padding: 8px 10px; background: rgba(90,138,90,0.1); border-radius: 10px; }
|
||||
.scan-errors { margin-top: 6px; display: flex; flex-direction: column; gap: 2px; }
|
||||
.scan-error-item { color: var(--mc-danger); font-size: 11px; }
|
||||
|
||||
/* Upload row: zone + add text side by side */
|
||||
.upload-row { display: flex; gap: 12px; align-items: stretch; }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user