From a3db357b6f0ba9a30745e8003fbbc2cb9324550b Mon Sep 17 00:00:00 2001 From: Jeena Date: Tue, 31 Mar 2026 04:26:20 +0000 Subject: [PATCH] container: Forward env vars by prefix and source shell config from container-home Replace hardcoded env var lists with prefix-based forwarding (OPENCODE_*, ANTHROPIC_*, CLAUDE_*) so new env vars are picked up automatically. Run tools through bash -l inside the container so that .bashrc, .bash_profile, and .profile from container-home are sourced. Seed container-home with default shell config from /etc/skel on first run if the files don't already exist. --- README.md | 11 ++++++-- agent-container.py | 66 +++++++++++++++++++++++++++++----------------- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index d2ffffc..613f913 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,16 @@ The container home directory at `$XDG_DATA_HOME/agent-container/container-home/` ## Environment Variables +Host environment variables matching the following prefixes are automatically forwarded into the container: + +- `OPENCODE_*` -- passed through to OpenCode +- `ANTHROPIC_*`, `CLAUDE_*` -- passed through to Claude Code + +This means inline overrides work as expected: `OPENCODE_CONFIG=foo opencode` + +For persistent environment variables, add them to `container-home/.bashrc` (or `.bash_profile` / `.profile`). Tools run through `bash -l` inside the container, so standard shell config files are sourced automatically. Default shell config files from `/etc/skel` are seeded into the container home on first run. + - `XDG_DATA_HOME`: Override default data directory (default: `~/.local/share`) -- `ANTHROPIC_API_KEY`: Your Anthropic API key (passed through to Claude Code) -- `ANTHROPIC_BASE_URL`: Override the API base URL (optional, passed through to Claude Code) ## Usage diff --git a/agent-container.py b/agent-container.py index 7b2aad6..bd93a57 100755 --- a/agent-container.py +++ b/agent-container.py @@ -3,6 +3,7 @@ import hashlib import logging import os +import shlex import signal import subprocess import sys @@ -69,23 +70,13 @@ class AgentContainer: self._run_tool( "opencode", args, - env_vars=[ - "OPENCODE_SERVER_PASSWORD", - "OPENCODE_SERVER_USERNAME", - "OPENCODE_CONFIG", - "OPENCODE_CONFIG_DIR", - "OPENCODE_CONFIG_CONTENT", - "OPENCODE_TUI_CONFIG", - ], + env_prefixes=["OPENCODE_"], ) elif command == "claude": self._run_tool( "claude", args, - env_vars=[ - "ANTHROPIC_API_KEY", - "ANTHROPIC_BASE_URL", - ], + env_prefixes=["ANTHROPIC_", "CLAUDE_"], extra_env={"DISABLE_AUTOUPDATER": "1"}, ) elif command == "update": @@ -105,7 +96,7 @@ class AgentContainer: self, tool: str, args: list[str], - env_vars: list[str], + env_prefixes: list[str], extra_env: dict[str, str] | None = None, ) -> None: self.container_home_path.mkdir(parents=True, exist_ok=True) @@ -143,27 +134,53 @@ class AgentContainer: if not self.start_container(): raise RuntimeError("Container failed to start after recreation") + # Seed container home with default shell config from /etc/skel + # (only copies files that don't already exist) + self._seed_home() + try: signal.signal(signal.SIGTSTP, signal.SIG_IGN) - self._exec_tool(tool, args, env_vars, extra_env or {}) + self._exec_tool(tool, args, env_prefixes, extra_env or {}) finally: signal.signal(signal.SIGTSTP, signal.SIG_DFL) self.stop_container() + def _seed_home(self) -> None: + """Copy default shell config from /etc/skel into the container + home directory, skipping files that already exist.""" + subprocess.run( + [ + "docker", + "exec", + self.container_name, + "bash", + "-c", + 'for f in /etc/skel/.*; do ' + '[ -f "$f" ] && [ ! -e "$HOME/$(basename "$f")" ] && ' + 'cp "$f" "$HOME/"; done', + ], + ) + def _exec_tool( - self, tool: str, args: list[str], env_vars: list[str], extra_env: dict[str, str] + self, + tool: str, + args: list[str], + env_prefixes: list[str], + extra_env: dict[str, str], ) -> None: env_args: list[str] = [] - # Pass through host environment variables if set - for var in env_vars: - val = os.environ.get(var) - if val: - env_args += ["-e", f"{var}={val}"] + # Pass through host environment variables matching any prefix + for key, val in os.environ.items(): + if any(key.startswith(prefix) for prefix in env_prefixes): + env_args += ["-e", f"{key}={val}"] # Set additional environment variables - for var, val in extra_env.items(): - env_args += ["-e", f"{var}={val}"] + for key, val in extra_env.items(): + env_args += ["-e", f"{key}={val}"] + + # Build the shell command with proper quoting + cmd_str = " ".join(shlex.quote(a) for a in [tool, *args]) result = subprocess.run( [ @@ -174,8 +191,9 @@ class AgentContainer: "-w", str(self.project_path), self.container_name, - tool, - *args, + "bash", + "-lc", + f"exec {cmd_str}", ], ) if result.returncode != 0: