With capacity=2 in the runner, two builds can run in parallel and share
the same buildkit cache mount (sharing=shared is the default). dotnet
restore writes NuGet temp files (*.ar5 etc.) that the OTHER build can
race-remove mid-extraction, yielding 'Could not find file' errors like:
error : Could not find file '/root/.nuget/packages/microsoft.identitymodel.abstractions/8.0.1/3wu4hkqc.ar5'
sharing=locked serializes cache access (one build at a time can touch
the mount). Still benefits from across-build caching, just no concurrency
on the mount itself.
Same fix as obsidian-mcp: JwtBearer 10.0.x's transitive JsonWebTokens 9.x
calls Decode(ReadOnlySpan, Span) which is only in Tokens 9.x. Explicit
pin to 8.* forces downgrade → MissingMethodException → 401 on every
JWT validation.
Mirror the cache infra added to nas-auth (commit a1ecba3) and
obsidian-mcp. Same pattern: --mount=type=cache for restore+publish
in Dockerfile, registry cache mode=max in workflow.
Same fix as obsidian-mcp: Claude.ai needs PRM to know the resource
identifier and send RFC 8707 `resource` in /authorize requests.
Adds /.well-known/oauth-protected-resource. ResourceUrl is configurable
via Mcp__OAuthDiscovery__ResourceUrl, falling back to request authority
when unset.
- 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.
Polly StandardResilienceHandler validates SamplingDuration >= 2 * AttemptTimeout
at startup. Default SamplingDuration is 30s and our AttemptTimeout is 30s, so
the container failed to boot with OptionsValidationException.
Set SamplingDuration explicitly to 60s while keeping AttemptTimeout at 30s.