Files
zhengchen.tao 28f9a54ba9
Build Docker Image / build (push) Failing after 9m21s
Build Docker Image / deploy (push) Has been skipped
obsidian-mcp: 初次落地 Obsidian Vault MCP Server (.NET 10, read+write)
把 Obsidian vault 通过 MCP 暴露给 Claude.ai,OAuth 走 nas-auth。
设计文档见 vault Coding/obsidian-mcp/obsidian-mcp 设计.md。
代码层落地参考 vault Coding/obsidian-mcp/MCP 实现指南.md。

V1+V2 同时实现(用户要求跳过分阶段直接全部):

读 Tools(需 scope=read:obsidian):
- list_vault_tree(一次性 vault 地图,限制深度)
- list_files / read_file(含 offset/limit 大文件分页)
- search(子串匹配 + glob 过滤,最多 50 hits)
- get_metadata(size / modified_at / has_frontmatter)

写 Tools(需 scope=write:obsidian):
- write_file / append_file
- 多重门禁:scope 校验 + 路径黑名单 + 写入白名单 + 永禁文件
  - 永禁写:任意目录的 AGENTS.md / PROFILE.md / README.md / CLAUDE.md / 01-Secret/**
  - 白名单:02-ShengquGames/logs/ + Coding/ + NAS/NAS 待办清单.md
- 写入审计日志按天 rotate(JSON line)

安全:
- VaultPathResolver chroot:path traversal + symlink 双拒绝
- JwtBearer (HS256, Current+Previous fallback, MapInboundClaims=false)
- aud=obsidian, iss=https://auth.zhengchentao.win
- 黑名单:01-Secret / .obsidian / .trash / .git

技术栈:
- .NET 10 + ModelContextProtocol SDK 1.0
- Streamable HTTP transport (POST /mcp)
- JwtBearer 10.0 + IdentityModel.Tokens 8.x

部署:
- Dockerfile multi-stage,runtime 装 ripgrep(V3 备用),non-root user
- .gitea/workflows/build-image.yml:build + deploy 双 job,buildkit v0.13.2
- 容器内 :8080,宿主端口 9090
- 子域名 obs.zhengchentao.win
- vault 挂载 /volume1/docker/webdav/data/Zhengchen:/vault:rw(V2 写入需要 rw)

测试:35/35 单测过(VaultPathResolver path traversal/blacklist/symlink + VaultWriteGuard whitelist/forbidden)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 01:32:11 +08:00

132 lines
4.5 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Build Docker Image
on:
# 自动触发:push 到 main 分支(纯文档改动跳过)
# ⚠️ quirkgit commit --allow-empty 不触发 paths-ignore 过滤的 workflow
# 要强制重新触发必须改一个非 ignore 路径的真实文件(改 build-image.yml 自己最稳)
push:
branches: [main]
paths-ignore:
- '**.md'
- 'LICENSE'
- '.gitignore'
- '.dockerignore'
# 手动触发:应急通道(重新打包 / 指定自定义 tag)
workflow_dispatch:
inputs:
branch:
description: '要打包的分支(仅手动触发生效)'
required: true
default: 'main'
tag:
description: '镜像 tag(留空则用 commit short hash'
required: false
default: ''
# 同分支连续 push 只跑最新的 run,旧 in-progress run 被取消(build + deploy 一起停)
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout target branch
uses: actions/checkout@v4
with:
ref: ${{ inputs.branch || github.ref_name }}
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
# 钉 v0.13.2runc 1.1.x)避免 DSM 4.4.x 内核不支持 runc 1.2+ 的
# openat2/fsmount syscall 导致 build 失败
driver-opts: |
image=moby/buildkit:v0.13.2
- name: Login to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: git.zhengchentao.win
username: ${{ gitea.actor }}
password: ${{ secrets.PACKAGES_TOKEN }}
- name: Determine image tag and revision
id: meta
run: |
if [ -n "${{ inputs.tag }}" ]; then
IMAGE_TAG="${{ inputs.tag }}"
else
IMAGE_TAG="$(git rev-parse --short HEAD)"
fi
echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
echo "full_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
echo "==> Image tag: $IMAGE_TAG"
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
labels: |
org.opencontainers.image.source=https://git.zhengchentao.win/zhengchen.tao/obsidian-mcp
org.opencontainers.image.revision=${{ steps.meta.outputs.full_sha }}
tags: |
git.zhengchentao.win/zhengchen.tao/obsidian-mcp:${{ steps.meta.outputs.image_tag }}
git.zhengchentao.win/zhengchen.tao/obsidian-mcp:latest
- name: Build summary
if: always()
run: |
{
echo "## Build Summary"
echo ""
echo "| 项 | 值 |"
echo "|---|---|"
echo "| 触发方式 | \`${{ github.event_name }}\` |"
echo "| 源分支 | \`${{ inputs.branch || github.ref_name }}\` |"
echo "| 源 commit (full) | \`${{ steps.meta.outputs.full_sha }}\` |"
echo "| 源 commit (short) | \`${{ steps.meta.outputs.image_tag }}\` |"
echo "| 镜像 | \`git.zhengchentao.win/zhengchen.tao/obsidian-mcp:${{ steps.meta.outputs.image_tag }}\` + \`:latest\` |"
} >> "$GITHUB_STEP_SUMMARY"
deploy:
# needs: build 串起来 —— build 失败则 deploy 自动 skip,无需 if 条件
needs: build
runs-on: ubuntu-latest
steps:
# deploy job 跑在独立 runner 容器上,凭据不从 build job 继承,必须再登一次
- name: Login to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: git.zhengchentao.win
username: ${{ gitea.actor }}
password: ${{ secrets.PACKAGES_TOKEN }}
- name: Pull and restart obsidian-mcp
env:
NAS_INFRA_TOKEN: ${{ secrets.NAS_INFRA_TOKEN }}
run: |
set -e
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT
if [ -n "$NAS_INFRA_TOKEN" ]; then
CLONE_URL="https://x-access-token:${NAS_INFRA_TOKEN}@git.zhengchentao.win/dev/nas-infra.git"
else
CLONE_URL="https://git.zhengchentao.win/dev/nas-infra.git"
fi
git clone --depth 1 "$CLONE_URL" "$TMPDIR/nas-infra"
cd "$TMPDIR/nas-infra/obsidian-mcp"
docker compose pull
docker compose up -d
sleep 3
docker compose ps
docker compose logs --tail=30 obsidian-mcp