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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user