#!/bin/bash # No set -e — individual commands handle their own errors gracefully # ────────────────────────────────────────────────────────── # Volume ownership fix (runs as root) # ────────────────────────────────────────────────────────── # Docker creates volume contents as root. The agent process runs as UID 1000 # and needs to write to /configs (CLAUDE.md, skills, plugins) and /workspace # (cloned repos, scratch files). Fix ownership once at startup so every # future file operation works without per-file chown hacks. if [ "$(id -u)" = "0" ]; then # Fix /configs recursively (plugins, CLAUDE.md, skills — small directory) chown -R agent:agent /configs 2>/dev/null # /workspace handling: # - Always fix the top-level dir so agent can create files in it. # - If the contents are root-owned (common on Docker Desktop / Windows # bind mounts where host uid maps to 0 inside the container), do a # full recursive chown — otherwise git clone, pip install, and file # writes under /workspace fail with EACCES (issue #13). On normal # Linux Docker with matching uids this branch is skipped, so we keep # the fast startup for the common case. chown agent:agent /workspace 2>/dev/null if [ -d /workspace ]; then # Sample the first entry inside /workspace; if it's root-owned assume # the whole tree is a root-owned bind mount and recursively chown. first_entry=$(find /workspace -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null) if [ -n "$first_entry" ] && [ "$(stat -c '%u' "$first_entry" 2>/dev/null)" = "0" ]; then echo "[entrypoint] /workspace contents are root-owned — chowning recursively to agent (uid 1000)" chown -R agent:agent /workspace 2>/dev/null fi fi # Re-exec this script as the agent user via gosu (clean PID 1 handoff) exec gosu agent "$0" "$@" fi # ────────────────────────────────────────────────────────── # Everything below runs as the agent user (UID 1000) # ────────────────────────────────────────────────────────── # Ensure user-installed packages are in PATH export PATH="$HOME/.local/bin:$PATH" # Determine runtime from config.yaml RUNTIME=$(python3 -c " import yaml from pathlib import Path cfg_path = Path('/configs/config.yaml') if cfg_path.exists(): cfg = yaml.safe_load(cfg_path.read_text()) or {} print(cfg.get('runtime', 'langgraph')) else: print('langgraph') " 2>/dev/null || echo "langgraph") echo "=== Molecule AI Workspace ===" echo "Runtime: $RUNTIME" # NOTE: Adapter-specific deps are now pre-installed in each adapter's Docker image # (standalone template repos). Each image installs molecule-ai-workspace-runtime # from PyPI plus the adapter-specific requirements. No per-runtime pip install needed here. exec python3 main.py