Compare commits
1 Commits
build-6084
...
build-df3d
| Author | SHA1 | Date | |
|---|---|---|---|
| df3d434877 |
@@ -102,20 +102,16 @@ pub async fn start_project_container(
|
|||||||
|
|
||||||
// Check for existing container
|
// Check for existing container
|
||||||
let container_id = if let Some(existing_id) = docker::find_existing_container(&project).await? {
|
let container_id = if let Some(existing_id) = docker::find_existing_container(&project).await? {
|
||||||
// Check if docker socket mount matches the current project setting.
|
// Compare the running container's configuration (mounts, env vars)
|
||||||
// If the user toggled "Allow container spawning" after the container was
|
// against the current project settings. If anything changed (SSH key
|
||||||
// created, we need to recreate the container for the mount change to take
|
// path, git config, docker socket, etc.) we recreate the container.
|
||||||
// effect.
|
// Safe to recreate: the claude config named volume is keyed by
|
||||||
let has_socket = docker::container_has_docker_socket(&existing_id).await.unwrap_or(false);
|
// project ID (not container ID) so it persists across recreation.
|
||||||
if has_socket != project.allow_docker_access {
|
let needs_recreation = docker::container_needs_recreation(&existing_id, &project)
|
||||||
log::info!(
|
.await
|
||||||
"Docker socket mismatch (container has_socket={}, project wants={}), recreating container",
|
.unwrap_or(false);
|
||||||
has_socket, project.allow_docker_access
|
if needs_recreation {
|
||||||
);
|
log::info!("Container config changed, recreating container for project {}", project.id);
|
||||||
// Safe to remove and recreate: the claude config named volume is
|
|
||||||
// keyed by project ID (not container ID) so it persists across
|
|
||||||
// container recreation. Bind mounts (workspace, SSH, AWS) are
|
|
||||||
// host paths and are unaffected.
|
|
||||||
let _ = docker::stop_container(&existing_id).await;
|
let _ = docker::stop_container(&existing_id).await;
|
||||||
docker::remove_container(&existing_id).await?;
|
docker::remove_container(&existing_id).await?;
|
||||||
let new_id = docker::create_container(
|
let new_id = docker::create_container(
|
||||||
|
|||||||
@@ -288,25 +288,82 @@ pub async fn remove_container(container_id: &str) -> Result<(), String> {
|
|||||||
.map_err(|e| format!("Failed to remove container: {}", e))
|
.map_err(|e| format!("Failed to remove container: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether an existing container has docker socket mounted.
|
/// Check whether the existing container's configuration still matches the
|
||||||
pub async fn container_has_docker_socket(container_id: &str) -> Result<bool, String> {
|
/// current project settings. Returns `true` when the container must be
|
||||||
|
/// recreated (mounts or env vars differ).
|
||||||
|
pub async fn container_needs_recreation(container_id: &str, project: &Project) -> Result<bool, String> {
|
||||||
let docker = get_docker()?;
|
let docker = get_docker()?;
|
||||||
let info = docker
|
let info = docker
|
||||||
.inspect_container(container_id, None)
|
.inspect_container(container_id, None)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to inspect container: {}", e))?;
|
.map_err(|e| format!("Failed to inspect container: {}", e))?;
|
||||||
|
|
||||||
let has_socket = info
|
let mounts = info
|
||||||
.host_config
|
.host_config
|
||||||
.and_then(|hc| hc.mounts)
|
.as_ref()
|
||||||
.map(|mounts| {
|
.and_then(|hc| hc.mounts.as_ref());
|
||||||
mounts.iter().any(|m| {
|
|
||||||
m.target.as_deref() == Some("/var/run/docker.sock")
|
// ── Docker socket mount ──────────────────────────────────────────────
|
||||||
})
|
let has_socket = mounts
|
||||||
|
.map(|m| {
|
||||||
|
m.iter()
|
||||||
|
.any(|mount| mount.target.as_deref() == Some("/var/run/docker.sock"))
|
||||||
})
|
})
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
if has_socket != project.allow_docker_access {
|
||||||
|
log::info!("Docker socket mismatch (container={}, project={})", has_socket, project.allow_docker_access);
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(has_socket)
|
// ── SSH key path mount ───────────────────────────────────────────────
|
||||||
|
let ssh_mount_source = mounts
|
||||||
|
.and_then(|m| {
|
||||||
|
m.iter()
|
||||||
|
.find(|mount| mount.target.as_deref() == Some("/tmp/.host-ssh"))
|
||||||
|
})
|
||||||
|
.and_then(|mount| mount.source.as_deref());
|
||||||
|
let project_ssh = project.ssh_key_path.as_deref();
|
||||||
|
if ssh_mount_source != project_ssh {
|
||||||
|
log::info!(
|
||||||
|
"SSH key path mismatch (container={:?}, project={:?})",
|
||||||
|
ssh_mount_source,
|
||||||
|
project_ssh
|
||||||
|
);
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Git environment variables ────────────────────────────────────────
|
||||||
|
let env_vars = info
|
||||||
|
.config
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|c| c.env.as_ref());
|
||||||
|
|
||||||
|
let get_env = |name: &str| -> Option<String> {
|
||||||
|
env_vars.and_then(|vars| {
|
||||||
|
vars.iter()
|
||||||
|
.find(|v| v.starts_with(&format!("{}=", name)))
|
||||||
|
.map(|v| v[name.len() + 1..].to_string())
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let container_git_name = get_env("GIT_USER_NAME");
|
||||||
|
let container_git_email = get_env("GIT_USER_EMAIL");
|
||||||
|
let container_git_token = get_env("GIT_TOKEN");
|
||||||
|
|
||||||
|
if container_git_name.as_deref() != project.git_user_name.as_deref() {
|
||||||
|
log::info!("GIT_USER_NAME mismatch (container={:?}, project={:?})", container_git_name, project.git_user_name);
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
if container_git_email.as_deref() != project.git_user_email.as_deref() {
|
||||||
|
log::info!("GIT_USER_EMAIL mismatch (container={:?}, project={:?})", container_git_email, project.git_user_email);
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
if container_git_token.as_deref() != project.git_token.as_deref() {
|
||||||
|
log::info!("GIT_TOKEN mismatch");
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_container_info(project: &Project) -> Result<Option<ContainerInfo>, String> {
|
pub async fn get_container_info(project: &Project) -> Result<Option<ContainerInfo>, String> {
|
||||||
|
|||||||
Reference in New Issue
Block a user