feat(FN-3111): add mesh configuration API routes and generator service

The merge lands the mesh configuration system (FN-3111) with type definitions, a `MeshConfigGenerator` service using a new `DockerClientService.recreateContainer()` method, and backend API routes for managing Docker-based mesh nodes. It also adds a "New Chat" button to the ChatView header on desktop

Fusion-Task-Id: FN-3111
This commit is contained in:
Fusion
2026-05-03 15:13:08 -07:00
committed by gsxdsm
parent 0347bd4afe
commit d812427be7
10 changed files with 1420 additions and 2 deletions

View File

@@ -7,6 +7,7 @@ import type {
DockerContainerInspectResult,
DockerContextInfo,
DockerHostConfig,
DockerVolumeMount,
} from "./types.js";
const EXEC_OPTIONS = {
@@ -198,6 +199,77 @@ export class DockerClientService {
return this.dockerInstance;
}
/**
* Recreate a container with updated environment variables.
*
* Docker environment variables are baked in at container creation time and
* cannot be changed without recreating the container. This method:
* 1. Inspects the old container to capture its configuration
* 2. Stops and removes the old container
* 3. Creates a new container with the same image and volumes but updated env vars
* 4. Starts the new container
* 5. Returns the new container ID
*
* Volume mounts are preserved across recreation. If persistentStorage is false,
* volumes are not included in the new container.
*/
async recreateContainer(
containerId: string,
options: {
envVars: Record<string, string>;
imageName: string;
volumeMounts: DockerVolumeMount[];
hostConfig?: DockerHostConfig;
},
): Promise<string> {
const docker = await this.getDockerInstance(options.hostConfig);
const container = docker.getContainer(containerId);
// Inspect the old container to capture its config
const inspect = await container.inspect();
const oldName = (inspect.Name ?? "").replace(/^\//, "");
// Build environment variable array from the provided map
const envArray = Object.entries(options.envVars).map(
([key, value]) => `${key}=${value}`,
);
// Build binds for volume mounts
const binds = options.volumeMounts.map(
(mount) => `${mount.hostPath}:${mount.containerPath}:${mount.mode}`,
);
// Stop and remove the old container
try {
await container.stop({ t: 5 });
} catch (error) {
// Container may already be stopped
const message = toErrorMessage(error);
if (!message.includes("is not running") && !message.includes("already stopped")) {
throw error;
}
}
await container.remove({ force: true });
// Create the new container with updated env vars
const newContainer = await docker.createContainer({
name: oldName || undefined,
Image: options.imageName,
Env: envArray,
HostConfig: {
Binds: binds.length > 0 ? binds : undefined,
RestartPolicy: {
Name: "unless-stopped",
},
},
});
// Start the new container
await newContainer.start();
return newContainer.id;
}
dispose(): void {
this.dockerInstance = null;
}