diff --git a/server/src/chat/transport/matrix/new_project.rs b/server/src/chat/transport/matrix/new_project.rs index 66f373c3..0ac1810c 100644 --- a/server/src/chat/transport/matrix/new_project.rs +++ b/server/src/chat/transport/matrix/new_project.rs @@ -441,24 +441,17 @@ async fn handle_adopt_project( let container_url = format!("http://127.0.0.1:{port}"); let container_name = format!("huskies-{name}"); - let mut docker_args: Vec = vec![ - "run".into(), - "-d".into(), - "--name".into(), - container_name.clone(), - "-p".into(), - format!("127.0.0.1:{port}:3001"), - "-p".into(), - format!("127.0.0.1:{ssh_port}:22"), - "-e".into(), - format!("HUSKIES_SSH_PUBKEY={pubkey}"), - "-e".into(), - format!("GIT_USER_NAME={git_user_name}"), - "-e".into(), - format!("GIT_USER_EMAIL={git_user_email}"), - "-v".into(), - format!("{}:/workspace", host_path.display()), - ]; + let mut docker_args = project_docker_run_args( + &container_name, + port, + ssh_port, + &pubkey, + &git_user_name, + &git_user_email, + ); + + docker_args.push("-v".into()); + docker_args.push(format!("{}:/workspace", host_path.display())); for mount in &ssh_key_mounts { docker_args.push("-v".into()); @@ -793,22 +786,14 @@ pub async fn handle_new_project( // Build the `docker run` argument list. The token must never appear in any // string that is returned to the caller or written to a log. - let mut docker_args: Vec = vec![ - "run".into(), - "-d".into(), - "--name".into(), - container_name.clone(), - "-p".into(), - format!("127.0.0.1:{port}:3001"), - "-p".into(), - format!("127.0.0.1:{ssh_port}:22"), - "-e".into(), - format!("HUSKIES_SSH_PUBKEY={pubkey}"), - "-e".into(), - format!("GIT_USER_NAME={git_user_name}"), - "-e".into(), - format!("GIT_USER_EMAIL={git_user_email}"), - ]; + let mut docker_args = project_docker_run_args( + &container_name, + port, + ssh_port, + &pubkey, + &git_user_name, + &git_user_email, + ); // HTTPS push token: passed as env vars consumed by the entrypoint credential helper. if let Some(token) = git_token { @@ -922,6 +907,42 @@ pub async fn handle_new_project( } } +/// Build the base `docker run` argument list for a project container. +/// +/// Includes `-e HUSKIES_HOST=0.0.0.0` so the server inside the container binds +/// to all interfaces, making Docker port forwarding reachable from the host. +/// Without this the server defaults to `127.0.0.1` inside the container — +/// reachable only from within the container itself, not via `docker -p`. +fn project_docker_run_args( + container_name: &str, + port: u16, + ssh_port: u16, + pubkey: &str, + git_user_name: &str, + git_user_email: &str, +) -> Vec { + vec![ + "run".into(), + "-d".into(), + "--name".into(), + container_name.to_string(), + "-p".into(), + format!("127.0.0.1:{port}:3001"), + "-p".into(), + format!("127.0.0.1:{ssh_port}:22"), + "-e".into(), + "HUSKIES_HOST=0.0.0.0".into(), + "-e".into(), + "HUSKIES_PORT=3001".into(), + "-e".into(), + format!("HUSKIES_SSH_PUBKEY={pubkey}"), + "-e".into(), + format!("GIT_USER_NAME={git_user_name}"), + "-e".into(), + format!("GIT_USER_EMAIL={git_user_email}"), + ] +} + /// Convert a failed `docker run` stderr into an actionable chat message. /// /// When Docker cannot find the image locally it prints `Unable to find image`. @@ -1301,6 +1322,32 @@ mod tests { // ── Missing-image error path ───────────────────────────────────────────── + #[test] + fn project_docker_args_include_huskies_host_env() { + let args = project_docker_run_args( + "huskies-myapp", + 3100, + 2200, + "ssh-ed25519 AAAA...", + "Test User", + "test@example.com", + ); + // Find "-e" followed by "HUSKIES_HOST=0.0.0.0" + let pairs: Vec<_> = args.windows(2).collect(); + assert!( + pairs + .iter() + .any(|w| w[0] == "-e" && w[1] == "HUSKIES_HOST=0.0.0.0"), + "expected -e HUSKIES_HOST=0.0.0.0 in docker args, got: {args:?}" + ); + assert!( + pairs + .iter() + .any(|w| w[0] == "-e" && w[1] == "HUSKIES_PORT=3001"), + "expected -e HUSKIES_PORT=3001 in docker args, got: {args:?}" + ); + } + #[test] fn interpret_docker_run_error_missing_image_points_at_script() { let stderr = "Unable to find image 'huskies-project-rust:latest' locally\n\