diff --git a/claude_bottle/backend/docker/backend.py b/claude_bottle/backend/docker/backend.py index 1cb50d9..6421da4 100644 --- a/claude_bottle/backend/docker/backend.py +++ b/claude_bottle/backend/docker/backend.py @@ -269,6 +269,20 @@ class DockerBottleBackend(BottleBackend): f"got {type(plan).__name__}" ) container = target + + prompt_path = self.provision_prompt(plan, container) + agent = plan.spec.manifest.agents[plan.spec.agent_name] + if agent.skills: + skills_mod.skills_copy_into(container, list(agent.skills)) + self.provision_ssh(plan, container) + self.provision_git(plan, container) + return prompt_path + + def provision_prompt(self, plan: DockerBottlePlan, container: str) -> str | None: + """Copy the prompt file into the container, fix ownership/mode. + Returns the in-container path if the agent has a non-empty + prompt (drives --append-system-prompt-file), else None. The + file is copied either way so the path always exists.""" container_home = os.environ.get("CLAUDE_BOTTLE_CONTAINER_HOME", "/home/node") in_container_prompt_path = f"{container_home}/.claude-bottle-prompt.txt" @@ -291,32 +305,39 @@ class DockerBottleBackend(BottleBackend): ) agent = plan.spec.manifest.agents[plan.spec.agent_name] - if agent.skills: - skills_mod.skills_copy_into(container, list(agent.skills)) - - bottle = plan.spec.manifest.bottle_for(plan.spec.agent_name) - if bottle.ssh: - proxy_host_port = pipelock.pipelock_proxy_host_port(plan.slug) - ssh_mod.ssh_setup(container, plan.stage_dir, proxy_host_port, bottle.ssh) - - if plan.spec.copy_cwd and Path(plan.spec.user_cwd, ".git").is_dir(): - info(f"copying {plan.spec.user_cwd}/.git -> {container}:/home/node/workspace/.git") - subprocess.run( - ["docker", "cp", f"{plan.spec.user_cwd}/.git", f"{container}:/home/node/workspace/.git"], - stdout=subprocess.DEVNULL, - check=True, - ) - subprocess.run( - [ - "docker", "exec", "-u", "0", container, - "chown", "-R", "node:node", "/home/node/workspace/.git", - ], - stdout=subprocess.DEVNULL, - check=True, - ) - return in_container_prompt_path if agent.prompt else None + def provision_ssh(self, plan: DockerBottlePlan, container: str) -> None: + """If the bottle has SSH entries, set up the in-container + ssh-agent and config so node can authenticate without ever + seeing the key bytes. No-op when the bottle has no SSH.""" + bottle = plan.spec.manifest.bottle_for(plan.spec.agent_name) + if not bottle.ssh: + return + proxy_host_port = pipelock.pipelock_proxy_host_port(plan.slug) + ssh_mod.ssh_setup(container, plan.stage_dir, proxy_host_port, bottle.ssh) + + def provision_git(self, plan: DockerBottlePlan, container: str) -> None: + """If --cwd was set and the host cwd has a .git directory, copy + it into /home/node/workspace/.git and fix ownership. No-op + otherwise.""" + if not (plan.spec.copy_cwd and Path(plan.spec.user_cwd, ".git").is_dir()): + return + info(f"copying {plan.spec.user_cwd}/.git -> {container}:/home/node/workspace/.git") + subprocess.run( + ["docker", "cp", f"{plan.spec.user_cwd}/.git", f"{container}:/home/node/workspace/.git"], + stdout=subprocess.DEVNULL, + check=True, + ) + subprocess.run( + [ + "docker", "exec", "-u", "0", container, + "chown", "-R", "node:node", "/home/node/workspace/.git", + ], + stdout=subprocess.DEVNULL, + check=True, + ) + # --- Cleanup --- def prepare_cleanup(self) -> DockerBottleCleanupPlan: