Fix docker volume mounts for host directory access

- Replace named volume (agent_data) with bind mounts for /opt/letsbe/{env,stacks,nginx}
- Update ALLOWED_FILE_ROOT default from /opt/agent_data to /opt/letsbe
- Add startup validation that warns (but doesn't block) if host dirs missing

This fixes ENV_UPDATE writes going to container filesystem instead of host,
and DOCKER_RELOAD failing with "File does not exist" errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Matt 2025-12-03 15:20:07 +01:00
parent b351217509
commit 9385ab09e4
4 changed files with 71 additions and 6 deletions

View File

@ -0,0 +1,35 @@
{
"permissions": {
"allow": [
"Bash(python -m venv:*)",
"Bash(.venv/Scripts/python.exe -m pip:*)",
"Bash(.venv/Scripts/python.exe:*)",
"Bash(timeout 15 .venv/Scripts/python.exe:*)",
"Bash(ORCHESTRATOR_URL=http://localhost:8000 AGENT_TOKEN=test-token timeout 20 .venv/Scripts/python.exe:*)",
"Bash(ORCHESTRATOR_URL=http://nonexistent:9999 AGENT_TOKEN=test-token timeout 15 .venv/Scripts/python.exe:*)",
"Bash(docker compose:*)",
"Bash(curl:*)",
"Bash(docker logs:*)",
"Bash(powershell -Command:*)",
"Bash(jq:*)",
"Bash(pytest:*)",
"Bash(python -m pytest:*)",
"Bash(.venvScriptspython.exe -m pip install -r requirements.txt -q)",
"Bash(.venvScriptspython.exe -m pytest tests/executors/test_env_update_executor.py -v)",
"Bash(..venvScriptspython.exe -m pytest tests/executors/test_env_update_executor.py -v)",
"mcp__serena__get_symbols_overview",
"mcp__serena__find_symbol",
"Bash(..venvScriptspython.exe -m pytest tests/executors/test_composite_executor.py -v)",
"Bash(git init:*)",
"Bash(git remote add:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git push:*)",
"Bash(git remote:*)",
"Bash(git config:*)",
"Bash(git fetch:*)"
],
"deny": [],
"ask": []
}
}

View File

@ -54,7 +54,7 @@ class Settings(BaseSettings):
circuit_breaker_cooldown: int = Field(default=300, ge=30, le=900, description="Cooldown period in seconds") circuit_breaker_cooldown: int = Field(default=300, ge=30, le=900, description="Cooldown period in seconds")
# Security - File operations # Security - File operations
allowed_file_root: str = Field(default="/opt/agent_data", description="Root directory for file operations") allowed_file_root: str = Field(default="/opt/letsbe", description="Root directory for file operations")
allowed_env_root: str = Field(default="/opt/letsbe/env", description="Root directory for ENV file operations") allowed_env_root: str = Field(default="/opt/letsbe/env", description="Root directory for ENV file operations")
max_file_size: int = Field(default=10 * 1024 * 1024, description="Max file size in bytes (default 10MB)") max_file_size: int = Field(default=10 * 1024 * 1024, description="Max file size in bytes (default 10MB)")

View File

@ -3,6 +3,7 @@
import asyncio import asyncio
import signal import signal
import sys import sys
from pathlib import Path
from typing import Optional from typing import Optional
from app import __version__ from app import __version__
@ -28,6 +29,34 @@ def print_banner() -> None:
print(banner) print(banner)
def validate_mounted_directories() -> None:
"""Check that required host directories are mounted.
Logs warnings if directories are missing but does not prevent startup.
"""
logger = get_logger("main")
required_dirs = [
"/opt/letsbe/env",
"/opt/letsbe/stacks",
"/opt/letsbe/nginx",
]
missing = []
for dir_path in required_dirs:
if not Path(dir_path).is_dir():
missing.append(dir_path)
if missing:
logger.warning(
"mounted_directories_missing",
missing=missing,
message="Some host directories are not mounted. Tasks requiring these paths will fail.",
)
else:
logger.info("mounted_directories_ok", directories=required_dirs)
async def main() -> int: async def main() -> int:
"""Main async entry point. """Main async entry point.
@ -41,6 +70,7 @@ async def main() -> int:
logger = get_logger("main") logger = get_logger("main")
print_banner() print_banner()
validate_mounted_directories()
logger.info( logger.info(
"agent_starting", "agent_starting",

View File

@ -28,7 +28,7 @@ services:
- CIRCUIT_BREAKER_COOLDOWN=${CIRCUIT_BREAKER_COOLDOWN:-300} - CIRCUIT_BREAKER_COOLDOWN=${CIRCUIT_BREAKER_COOLDOWN:-300}
# Security # Security
- ALLOWED_FILE_ROOT=${ALLOWED_FILE_ROOT:-/opt/agent_data} - ALLOWED_FILE_ROOT=${ALLOWED_FILE_ROOT:-/opt/letsbe}
- MAX_FILE_SIZE=${MAX_FILE_SIZE:-10485760} - MAX_FILE_SIZE=${MAX_FILE_SIZE:-10485760}
- SHELL_TIMEOUT=${SHELL_TIMEOUT:-60} - SHELL_TIMEOUT=${SHELL_TIMEOUT:-60}
@ -39,8 +39,10 @@ services:
# Hot reload in development # Hot reload in development
- ./app:/app/app:ro - ./app:/app/app:ro
# Agent data directory # Host directory mounts for real infrastructure access
- agent_data:/opt/agent_data - /opt/letsbe/env:/opt/letsbe/env
- /opt/letsbe/stacks:/opt/letsbe/stacks
- /opt/letsbe/nginx:/opt/letsbe/nginx
# Pending results persistence # Pending results persistence
- agent_home:/home/agent/.letsbe-agent - agent_home:/home/agent/.letsbe-agent
@ -62,7 +64,5 @@ services:
memory: 64M memory: 64M
volumes: volumes:
agent_data:
name: letsbe-agent-data
agent_home: agent_home:
name: letsbe-agent-home name: letsbe-agent-home