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
+10 -10
View File
@@ -6,7 +6,6 @@ import type { GitMount, GitMountMapping } from "../api/config_profiles";
interface GitMountEditorProps {
mounts: GitMount[];
onChange: (mounts: GitMount[]) => void;
defaultSshKeyId?: string;
}
function normalizeMount(mount: GitMount): GitMount {
@@ -34,7 +33,10 @@ function normalizeMounts(mounts: GitMount[]): GitMount[] {
return mounts.map(normalizeMount);
}
export const GitMountEditor = ({ mounts, onChange, defaultSshKeyId }: GitMountEditorProps) => {
export const GitMountEditor = ({
mounts,
onChange,
}: GitMountEditorProps) => {
const [normalizedMounts, setNormalizedMounts] = useState<GitMount[]>(() =>
normalizeMounts(mounts),
);
@@ -93,7 +95,6 @@ export const GitMountEditor = ({ mounts, onChange, defaultSshKeyId }: GitMountEd
mount={mount}
onSave={(updated) => handleUpdate(index, updated)}
onCancel={() => setEditingIndex(null)}
defaultSshKeyId={defaultSshKeyId}
/>
) : (
<div>
@@ -185,7 +186,6 @@ export const GitMountEditor = ({ mounts, onChange, defaultSshKeyId }: GitMountEd
}}
onSave={handleAdd}
onCancel={() => setIsAdding(false)}
defaultSshKeyId={defaultSshKeyId}
/>
</div>
) : (
@@ -206,7 +206,6 @@ interface GitMountFormProps {
mount: GitMount;
onSave: (mount: GitMount) => void;
onCancel: () => void;
defaultSshKeyId?: string;
}
type ValidationState =
@@ -216,7 +215,11 @@ type ValidationState =
| { status: "suggestion"; suggestedUrl: string; message: string }
| { status: "invalid"; message: string };
const GitMountForm = ({ mount, onSave, onCancel, defaultSshKeyId }: GitMountFormProps) => {
const GitMountForm = ({
mount,
onSave,
onCancel,
}: GitMountFormProps) => {
const [remoteUrl, setRemoteUrl] = useState(mount.remote_url);
const [branch, setBranch] = useState(mount.branch || "");
const [mappings, setMappings] = useState<GitMountMapping[]>(
@@ -245,10 +248,7 @@ const GitMountForm = ({ mount, onSave, onCancel, defaultSshKeyId }: GitMountForm
return next;
});
try {
const result = await validateGitUrl(
remoteUrl.trim(),
defaultSshKeyId,
);
const result = await validateGitUrl(remoteUrl.trim());
if (result.valid && result.branches) {
setValidation({
status: "valid",