feat: replace profiles with kubernetes-style secrets

Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).

- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michal
2026-02-22 18:40:58 +00:00
parent ede9e10990
commit 6d9a9f572c
77 changed files with 1014 additions and 1931 deletions

View File

@@ -1,7 +1,8 @@
import type { McpInstance } from '@prisma/client';
import type { IMcpInstanceRepository, IMcpServerRepository } from '../repositories/interfaces.js';
import type { IMcpInstanceRepository, IMcpServerRepository, ISecretRepository } from '../repositories/interfaces.js';
import type { McpOrchestrator, ContainerSpec, ContainerInfo } from './orchestrator.js';
import { NotFoundError } from './mcp-server.service.js';
import { resolveServerEnv } from './env-resolver.js';
export class InvalidStateError extends Error {
readonly statusCode = 409;
@@ -16,6 +17,7 @@ export class InstanceService {
private instanceRepo: IMcpInstanceRepository,
private serverRepo: IMcpServerRepository,
private orchestrator: McpOrchestrator,
private secretRepo?: ISecretRepository,
) {}
async list(serverId?: string): Promise<McpInstance[]> {
@@ -162,6 +164,19 @@ export class InstanceService {
spec.command = command;
}
// Resolve env vars from inline values and secret refs
if (this.secretRepo) {
try {
const resolvedEnv = await resolveServerEnv(server, this.secretRepo);
if (Object.keys(resolvedEnv).length > 0) {
spec.env = resolvedEnv;
}
} catch (envErr) {
// Log but don't prevent startup — env resolution failures are non-fatal
// The container may still work if env vars are optional
}
}
const containerInfo = await this.orchestrator.createContainer(spec);
const updateFields: { containerId: string; port?: number } = {