feat: instance-level SSH key selection for container mounting

- Revert mistaken ssh_key_id from ConfigProfile (model, API, resolver, frontend)
- Add ssh_key_ids JSON column to tool_instances via migration
- Update create_instance to accept and store ssh_key_ids
- Update start_instance to mount selected SSH keys to {home_dir}/.ssh
- Update list_instances to return ssh_key_ids
- Frontend CreateSessionForm: multi-select SSH key checkboxes
- Frontend instance-list: SSH key selector for start/restart actions
- Maintain separate SSH key dirs per key to avoid conflicts

Quality gates: pytest (231 passed, 6 pre-existing), tsc --noEmit clean
This commit is contained in:
Alex Blank
2026-05-29 13:30:53 +02:00
parent cbd3436ff7
commit e9364fa70f
14 changed files with 2049 additions and 1524 deletions
-4
View File
@@ -12,7 +12,6 @@ export interface ConfigProfile {
mounts: ConfigProfileMount[];
git_mounts: GitMount[];
files: Record<string, string>;
ssh_key_id: string | null;
is_default: boolean;
includes: ConfigProfileInclude[];
created_at: string;
@@ -53,7 +52,6 @@ export interface ResolvedProfile {
mounts: ResolvedMount[];
git_mounts: GitMount[];
files: Record<string, string>;
ssh_key_id: string | null;
overrides: {
env_vars: Record<string, string>;
runtime_hints: Record<string, string>;
@@ -80,7 +78,6 @@ export interface CreateConfigProfileRequest {
mounts?: ConfigProfileMount[];
git_mounts?: GitMount[];
files?: Record<string, string>;
ssh_key_id?: string;
is_default?: boolean;
}
@@ -94,7 +91,6 @@ export interface UpdateConfigProfileRequest {
mounts?: ConfigProfileMount[];
git_mounts?: GitMount[];
files?: Record<string, string>;
ssh_key_id?: string;
is_default?: boolean;
}
+10 -5
View File
@@ -12,6 +12,7 @@ export interface ToolInstance {
url: string | null;
port: number | null;
selected_config_profile_id: string | null;
ssh_key_ids: string[];
created_at: string;
}
@@ -52,7 +53,8 @@ export async function createInstance(
cloneMode?: string,
branch?: string,
newBranch?: string,
configProfileId?: string
configProfileId?: string,
sshKeyIds?: string[]
): Promise<ToolInstance> {
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances`,
@@ -63,6 +65,7 @@ export async function createInstance(
branch: branch || undefined,
new_branch: newBranch || undefined,
config_profile_id: configProfileId,
ssh_key_ids: sshKeyIds || [],
}
);
return response.data;
@@ -73,12 +76,13 @@ export async function startInstance(
repoId: string,
instanceId: string,
configProfileId?: string,
sshKeyIds?: string[],
retries = 2
): Promise<{ status: string; url?: string }> {
try {
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/start`,
{ config_profile_id: configProfileId }
{ config_profile_id: configProfileId, ssh_key_ids: sshKeyIds || [] }
);
return response.data;
} catch (error) {
@@ -86,7 +90,7 @@ export async function startInstance(
const axiosError = error as AxiosError;
if (retries > 0 && !axiosError.response) {
await new Promise((r) => setTimeout(r, 1500));
return startInstance(projectId, repoId, instanceId, configProfileId, retries - 1);
return startInstance(projectId, repoId, instanceId, configProfileId, sshKeyIds, retries - 1);
}
throw error;
}
@@ -108,12 +112,13 @@ export async function restartInstance(
repoId: string,
instanceId: string,
configProfileId?: string,
sshKeyIds?: string[],
retries = 2
): Promise<{ status: string; url?: string }> {
try {
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/restart`,
{ config_profile_id: configProfileId }
{ config_profile_id: configProfileId, ssh_key_ids: sshKeyIds || [] }
);
return response.data;
} catch (error) {
@@ -121,7 +126,7 @@ export async function restartInstance(
const axiosError = error as AxiosError;
if (retries > 0 && !axiosError.response) {
await new Promise((r) => setTimeout(r, 1500));
return restartInstance(projectId, repoId, instanceId, configProfileId, retries - 1);
return restartInstance(projectId, repoId, instanceId, configProfileId, sshKeyIds, retries - 1);
}
throw error;
}