diff --git a/.gitea/workflows/build-image.yml b/.gitea/workflows/build-image.yml index a83c911..f7dd192 100644 --- a/.gitea/workflows/build-image.yml +++ b/.gitea/workflows/build-image.yml @@ -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 diff --git a/Auth/JwtBearerSetup.cs b/Auth/JwtBearerSetup.cs index 50738e6..c732bba 100644 --- a/Auth/JwtBearerSetup.cs +++ b/Auth/JwtBearerSetup.cs @@ -55,14 +55,14 @@ public static class JwtBearerSetup /// private static IEnumerable 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)); } } diff --git a/Config/JwtOptions.cs b/Config/JwtOptions.cs index b7c4b8a..77ef81f 100644 --- a/Config/JwtOptions.cs +++ b/Config/JwtOptions.cs @@ -3,6 +3,7 @@ namespace GiteaMcp.Config; /// /// JWT 验签配置,与 nas-auth / obsidian-mcp 共用同款 HS256 对称密钥。 /// ValidIssuer = auth.zhengchentao.win,ValidAudience = gitea。 +/// 环境变量:Jwt__Issuer, Jwt__Audience, Jwt__SigningKey__Current, Jwt__SigningKey__Previous /// 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"; - /// 当前签名密钥(HS256 对称密钥,base64 或原文均可,长度 >= 32 字节) - public string SigningKeyCurrent { get; set; } = string.Empty; + public SigningKeyPair SigningKey { get; set; } = new(); - /// - /// 上一轮密钥(轮换窗口内保留,允许旧 Token 继续使用)。 - /// 留空表示不存在旧密钥。 - /// - public string SigningKeyPrevious { get; set; } = string.Empty; + public class SigningKeyPair + { + /// 当前签名密钥(HS256 对称密钥),env: Jwt__SigningKey__Current + public string Current { get; set; } = string.Empty; + + /// 上一轮密钥,密钥轮换过渡期用,env: Jwt__SigningKey__Previous(可为空) + public string? Previous { get; set; } + } } diff --git a/README.md b/README.md index 877cfc0..146754f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/appsettings.json b/appsettings.json index 8b7710d..608fa9a 100644 --- a/appsettings.json +++ b/appsettings.json @@ -17,8 +17,10 @@ "Jwt": { "Issuer": "https://auth.zhengchentao.win", "Audience": "gitea", - "SigningKeyCurrent": "", - "SigningKeyPrevious": "" + "SigningKey": { + "Current": "", + "Previous": "" + } }, "Mcp": { "OAuthDiscovery": {