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
|
## 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`)
|
- `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
|
## Usage
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import shlex
|
||||||
import signal
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -69,23 +70,13 @@ class AgentContainer:
|
||||||
self._run_tool(
|
self._run_tool(
|
||||||
"opencode",
|
"opencode",
|
||||||
args,
|
args,
|
||||||
env_vars=[
|
env_prefixes=["OPENCODE_"],
|
||||||
"OPENCODE_SERVER_PASSWORD",
|
|
||||||
"OPENCODE_SERVER_USERNAME",
|
|
||||||
"OPENCODE_CONFIG",
|
|
||||||
"OPENCODE_CONFIG_DIR",
|
|
||||||
"OPENCODE_CONFIG_CONTENT",
|
|
||||||
"OPENCODE_TUI_CONFIG",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
elif command == "claude":
|
elif command == "claude":
|
||||||
self._run_tool(
|
self._run_tool(
|
||||||
"claude",
|
"claude",
|
||||||
args,
|
args,
|
||||||
env_vars=[
|
env_prefixes=["ANTHROPIC_", "CLAUDE_"],
|
||||||
"ANTHROPIC_API_KEY",
|
|
||||||
"ANTHROPIC_BASE_URL",
|
|
||||||
],
|
|
||||||
extra_env={"DISABLE_AUTOUPDATER": "1"},
|
extra_env={"DISABLE_AUTOUPDATER": "1"},
|
||||||
)
|
)
|
||||||
elif command == "update":
|
elif command == "update":
|
||||||
|
|
@ -105,7 +96,7 @@ class AgentContainer:
|
||||||
self,
|
self,
|
||||||
tool: str,
|
tool: str,
|
||||||
args: list[str],
|
args: list[str],
|
||||||
env_vars: list[str],
|
env_prefixes: list[str],
|
||||||
extra_env: dict[str, str] | None = None,
|
extra_env: dict[str, str] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.container_home_path.mkdir(parents=True, exist_ok=True)
|
self.container_home_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
@ -143,27 +134,53 @@ class AgentContainer:
|
||||||
if not self.start_container():
|
if not self.start_container():
|
||||||
raise RuntimeError("Container failed to start after recreation")
|
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:
|
try:
|
||||||
signal.signal(signal.SIGTSTP, signal.SIG_IGN)
|
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:
|
finally:
|
||||||
signal.signal(signal.SIGTSTP, signal.SIG_DFL)
|
signal.signal(signal.SIGTSTP, signal.SIG_DFL)
|
||||||
self.stop_container()
|
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(
|
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:
|
) -> None:
|
||||||
env_args: list[str] = []
|
env_args: list[str] = []
|
||||||
|
|
||||||
# Pass through host environment variables if set
|
# Pass through host environment variables matching any prefix
|
||||||
for var in env_vars:
|
for key, val in os.environ.items():
|
||||||
val = os.environ.get(var)
|
if any(key.startswith(prefix) for prefix in env_prefixes):
|
||||||
if val:
|
env_args += ["-e", f"{key}={val}"]
|
||||||
env_args += ["-e", f"{var}={val}"]
|
|
||||||
|
|
||||||
# Set additional environment variables
|
# Set additional environment variables
|
||||||
for var, val in extra_env.items():
|
for key, val in extra_env.items():
|
||||||
env_args += ["-e", f"{var}={val}"]
|
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(
|
result = subprocess.run(
|
||||||
[
|
[
|
||||||
|
|
@ -174,8 +191,9 @@ class AgentContainer:
|
||||||
"-w",
|
"-w",
|
||||||
str(self.project_path),
|
str(self.project_path),
|
||||||
self.container_name,
|
self.container_name,
|
||||||
tool,
|
"bash",
|
||||||
*args,
|
"-lc",
|
||||||
|
f"exec {cmd_str}",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue