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.
This commit is contained in:
parent
d6f179ec57
commit
a3db357b6f
2 changed files with 51 additions and 26 deletions
11
README.md
11
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue