refactor: unify JwtOptions schema with obsidian-mcp + simplify deploy
- Config/JwtOptions: flatten SigningKeyCurrent/Previous into nested
SigningKey { Current, Previous } class to match obsidian-mcp shape.
Both services now bind the same env var pattern (Jwt__SigningKey__Current),
removing the schema fork that caused gitea-mcp to start with empty keys
when compose used the obsidian-mcp convention.
- Auth/JwtBearerSetup, appsettings.json, README: follow rename.
- .gitea/workflows/build-image.yml: deploy job no longer clones nas-infra
to a temp dir (which lacks the gitignored .env.shared). Now cd directly
into /volume1/docker/compose/gitea-mcp, exposed by gitea-runner mount.
This commit is contained in:
@@ -91,7 +91,10 @@ jobs:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# deploy job 是独立 runner,凭据不跨 job 继承,必须再 login 一次
|
||||
# 不再 clone nas-infra:deploy 直接操作 NAS 上 /volume1/docker/compose/gitea-mcp/。
|
||||
# 该目录由 gitea-runner 挂载暴露给 runner(host 模式 + bind mount)。
|
||||
# .env.shared 也在那一层(../.env.shared),不需要再注入凭据。
|
||||
# nas-infra 的 compose 改动靠 NAS 上手动 `git pull` 同步,不进 CI 链路。
|
||||
- name: Login to Gitea Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -100,26 +103,11 @@ jobs:
|
||||
password: ${{ secrets.PACKAGES_TOKEN }}
|
||||
|
||||
- name: Pull and restart gitea-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/gitea-mcp"
|
||||
|
||||
cd /volume1/docker/compose/gitea-mcp
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
|
||||
sleep 3
|
||||
docker compose ps
|
||||
docker compose logs --tail=30 gitea-mcp
|
||||
|
||||
@@ -55,14 +55,14 @@ public static class JwtBearerSetup
|
||||
/// </summary>
|
||||
private static IEnumerable<SecurityKey> BuildSigningKeys(JwtOptions opts)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(opts.SigningKeyCurrent))
|
||||
if (string.IsNullOrWhiteSpace(opts.SigningKey.Current))
|
||||
throw new InvalidOperationException(
|
||||
"Jwt:SigningKeyCurrent 未配置,gitea-mcp 无法启动。" +
|
||||
"Jwt:SigningKey:Current 未配置,gitea-mcp 无法启动。" +
|
||||
"请在 .env.shared 设置 JWT_SIGNING_KEY_CURRENT 与 nas-auth 共用。");
|
||||
|
||||
yield return new SymmetricSecurityKey(Encoding.UTF8.GetBytes(opts.SigningKeyCurrent));
|
||||
yield return new SymmetricSecurityKey(Encoding.UTF8.GetBytes(opts.SigningKey.Current));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(opts.SigningKeyPrevious))
|
||||
yield return new SymmetricSecurityKey(Encoding.UTF8.GetBytes(opts.SigningKeyPrevious));
|
||||
if (!string.IsNullOrWhiteSpace(opts.SigningKey.Previous))
|
||||
yield return new SymmetricSecurityKey(Encoding.UTF8.GetBytes(opts.SigningKey.Previous));
|
||||
}
|
||||
}
|
||||
|
||||
+10
-7
@@ -3,6 +3,7 @@ namespace GiteaMcp.Config;
|
||||
/// <summary>
|
||||
/// JWT 验签配置,与 nas-auth / obsidian-mcp 共用同款 HS256 对称密钥。
|
||||
/// ValidIssuer = auth.zhengchentao.win,ValidAudience = gitea。
|
||||
/// 环境变量:Jwt__Issuer, Jwt__Audience, Jwt__SigningKey__Current, Jwt__SigningKey__Previous
|
||||
/// </summary>
|
||||
public class JwtOptions
|
||||
{
|
||||
@@ -11,12 +12,14 @@ public class JwtOptions
|
||||
public string Issuer { get; set; } = "https://auth.zhengchentao.win";
|
||||
public string Audience { get; set; } = "gitea";
|
||||
|
||||
/// <summary>当前签名密钥(HS256 对称密钥,base64 或原文均可,长度 >= 32 字节)</summary>
|
||||
public string SigningKeyCurrent { get; set; } = string.Empty;
|
||||
public SigningKeyPair SigningKey { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 上一轮密钥(轮换窗口内保留,允许旧 Token 继续使用)。
|
||||
/// 留空表示不存在旧密钥。
|
||||
/// </summary>
|
||||
public string SigningKeyPrevious { get; set; } = string.Empty;
|
||||
public class SigningKeyPair
|
||||
{
|
||||
/// <summary>当前签名密钥(HS256 对称密钥),env: Jwt__SigningKey__Current</summary>
|
||||
public string Current { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>上一轮密钥,密钥轮换过渡期用,env: Jwt__SigningKey__Previous(可为空)</summary>
|
||||
public string? Previous { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,8 +66,8 @@ All tools require a valid JWT with `scope=read:gitea` issued by nas-auth.
|
||||
| `Gitea__MaxFileBytes` | `1048576` | Max file read size in bytes (1MB) |
|
||||
| `Jwt__Issuer` | `https://auth.zhengchentao.win` | Expected JWT issuer |
|
||||
| `Jwt__Audience` | `gitea` | Expected JWT audience |
|
||||
| `Jwt__SigningKeyCurrent` | *(required)* | HS256 signing key (shared with nas-auth) |
|
||||
| `Jwt__SigningKeyPrevious` | *(empty)* | Previous key for rotation window |
|
||||
| `Jwt__SigningKey__Current` | *(required)* | HS256 signing key (shared with nas-auth) |
|
||||
| `Jwt__SigningKey__Previous` | *(empty)* | Previous key for rotation window |
|
||||
| `ASPNETCORE_ENVIRONMENT` | `Production` | Use `Development` locally |
|
||||
|
||||
All secrets come from `/volume1/docker/compose/.env.shared` on NAS — never hardcode them.
|
||||
@@ -95,7 +95,7 @@ dotnet user-jwts create \
|
||||
--claim scope=read:gitea
|
||||
```
|
||||
|
||||
Or use [jwt.io](https://jwt.io) with alg=HS256 and the key from `Jwt:SigningKeyCurrent`.
|
||||
Or use [jwt.io](https://jwt.io) with alg=HS256 and the key from `Jwt:SigningKey:Current`.
|
||||
|
||||
### 3. Test with MCP Inspector
|
||||
|
||||
@@ -147,7 +147,7 @@ services:
|
||||
- Gitea__RepoBlacklist=
|
||||
- Jwt__Issuer=https://auth.zhengchentao.win
|
||||
- Jwt__Audience=gitea
|
||||
- Jwt__SigningKeyCurrent=${JWT_SIGNING_KEY_CURRENT}
|
||||
- Jwt__SigningKey__Current=${JWT_SIGNING_KEY_CURRENT}
|
||||
- TZ=Asia/Shanghai
|
||||
env_file:
|
||||
- ../.env.shared
|
||||
|
||||
+4
-2
@@ -17,8 +17,10 @@
|
||||
"Jwt": {
|
||||
"Issuer": "https://auth.zhengchentao.win",
|
||||
"Audience": "gitea",
|
||||
"SigningKeyCurrent": "",
|
||||
"SigningKeyPrevious": ""
|
||||
"SigningKey": {
|
||||
"Current": "",
|
||||
"Previous": ""
|
||||
}
|
||||
},
|
||||
"Mcp": {
|
||||
"OAuthDiscovery": {
|
||||
|
||||
Reference in New Issue
Block a user