Spaces:
Sleeping
Sleeping
Claude Code
Claude Code: Fix worker env injection - add ANTHROPIC_API_KEY and CLAUDE_API_KEY to env config
740ba92 | #!/usr/bin/env python3 | |
| """ | |
| Environment initialization utility for HuggingClaw Cain. | |
| Ensures .env file exists with fallback to .env.example or hardcoded skeleton. | |
| This prevents FileNotFoundError when Python modules load before entrypoint.sh runs. | |
| Provides robust dotenv loading with safe defaults for critical variables. | |
| """ | |
| import os | |
| import logging | |
| from pathlib import Path | |
| logger = logging.getLogger(__name__) | |
| # Paths | |
| _APP_DIR = Path("/app") | |
| _ENV_FILE = _APP_DIR / ".env" | |
| _ENV_EXAMPLE = _APP_DIR / ".env.example" | |
| # Safe defaults for critical variables - these allow Cain to start with partial config | |
| _CRITICAL_DEFAULTS = { | |
| # Data persistence defaults | |
| "OPENCLAW_DATA_DIR": "/data", | |
| "OPENCLAW_HOME": "/data/.openclaw", | |
| "OPENCLAW_STATE_DIR": "/data/.openclaw", | |
| # Server defaults | |
| "PORT": "7860", | |
| "WORKER_MODE": "auto", | |
| "OPENCLAW_PASSWORD": "huggingclaw", | |
| # Agent defaults | |
| "AGENT_MODE": "active", | |
| "WORKER_START_TIMEOUT": "30", | |
| "SLEEP_INTERVAL": "5", | |
| # Sync defaults | |
| "SYNC_INTERVAL": "60", | |
| "AUTO_CREATE_DATASET": "false", | |
| # LLM defaults (empty - must be set for AI to work) | |
| "OPENAI_API_KEY": "", | |
| "OPENROUTER_API_KEY": "", | |
| "ANTHROPIC_API_KEY": "", | |
| "CLAUDE_API_KEY": "", | |
| "OPENCLAW_DEFAULT_MODEL": "", | |
| # HuggingFace defaults (empty - must be set for data sync to work) | |
| "HF_TOKEN": "", | |
| "OPENCLAW_DATASET_REPO": "", | |
| "SPACE_ID": "tao-shen/HuggingClaw-Cain", | |
| } | |
| def _get_env_skeleton() -> str: | |
| """ | |
| Return a hardcoded .env skeleton with safe defaults. | |
| This is the ultimate fallback if .env.example is missing. | |
| """ | |
| lines = [ | |
| "# HuggingClaw Cain - Auto-generated .env skeleton", | |
| "# Generated because .env.example was not found", | |
| "", | |
| ] | |
| for key, value in _CRITICAL_DEFAULTS.items(): | |
| if value: # Only include non-empty defaults | |
| lines.append(f"{key}={value}") | |
| else: | |
| lines.append(f"{key}=") # Include empty key as placeholder | |
| return "\n".join(lines) + "\n" | |
| def _ensure_env_file() -> bool: | |
| """ | |
| Ensure .env file exists, using .env.example as fallback, | |
| then hardcoded skeleton as ultimate fallback. | |
| Returns: | |
| True if .env exists or was created, False otherwise. | |
| """ | |
| # If .env already exists, we're done | |
| if _ENV_FILE.exists(): | |
| logger.debug("[ENV_INIT] .env already exists") | |
| return True | |
| logger.warning("[ENV_INIT] .env missing - creating fallback .env file") | |
| # Try to copy from .env.example | |
| if _ENV_EXAMPLE.exists(): | |
| try: | |
| # Copy non-empty lines from .env.example to .env | |
| # This matches entrypoint.sh behavior (grep -E '^[A-Z_]+=.+[^[:space:]]') | |
| lines = [] | |
| with open(_ENV_EXAMPLE, "r") as f: | |
| for line in f: | |
| line = line.strip() | |
| # Only include lines with VAR=value (non-empty values) | |
| if line and "=" in line and not line.startswith("#"): | |
| var_name = line.split("=", 1)[0].strip() | |
| if var_name.isupper() and var_name.isidentifier(): | |
| # Only include if value is non-empty | |
| value = line.split("=", 1)[1].strip() | |
| if value and value not in ('""', "''"): | |
| lines.append(f"{var_name}={value}\n") | |
| if lines: | |
| with open(_ENV_FILE, "w") as f: | |
| f.writelines(lines) | |
| logger.info(f"[ENV_INIT] Created .env from .env.example ({len(lines)} variables)") | |
| return True | |
| else: | |
| # No valid lines found, use skeleton | |
| logger.warning("[ENV_INIT] No valid vars in .env.example - using skeleton fallback") | |
| except Exception as e: | |
| logger.warning(f"[ENV_INIT] Failed to create .env from .env.example: {e}") | |
| # .env.example missing or failed - use hardcoded skeleton | |
| try: | |
| skeleton = _get_env_skeleton() | |
| with open(_ENV_FILE, "w") as f: | |
| f.write(skeleton) | |
| logger.warning("[ENV_INIT] Created .env from hardcoded skeleton (safe defaults applied)") | |
| return True | |
| except Exception as e: | |
| logger.error(f"[ENV_INIT] Failed to create .env from skeleton: {e}") | |
| return False | |
| def _load_env_file() -> bool: | |
| """ | |
| Load environment variables from .env file using pure Python. | |
| Falls back to safe defaults if loading fails. | |
| Returns: | |
| True if loading succeeded or defaults were applied, False on critical failure. | |
| """ | |
| if not _ENV_FILE.exists(): | |
| logger.error("[ENV_INIT] Cannot load .env - file does not exist") | |
| return False | |
| try: | |
| # Parse .env file manually (no python-dotenv dependency) | |
| with open(_ENV_FILE, "r") as f: | |
| for line in f: | |
| line = line.strip() | |
| # Skip empty lines and comments | |
| if not line or line.startswith("#"): | |
| continue | |
| # Parse VAR=value lines | |
| if "=" in line: | |
| key, value = line.split("=", 1) | |
| key = key.strip() | |
| value = value.strip() | |
| # Only set if not already in environment (system vars take precedence) | |
| if key not in os.environ: | |
| os.environ[key] = value | |
| logger.info("[ENV_INIT] Loaded environment variables from .env") | |
| return True | |
| except Exception as e: | |
| logger.error(f"[ENV_INIT] Failed to load .env file: {e}") | |
| logger.warning("[ENV_INIT] Applying safe defaults for critical variables") | |
| # Apply safe defaults as fallback | |
| for key, value in _CRITICAL_DEFAULTS.items(): | |
| if key not in os.environ: | |
| os.environ[key] = value | |
| return True # Still return True since we applied defaults | |
| def init_env() -> bool: | |
| """ | |
| Initialize environment - call this at application startup. | |
| This ensures .env exists and is loaded with safe defaults. | |
| Safe to call multiple times (idempotent). | |
| Returns: | |
| True if initialization succeeded, False otherwise. | |
| """ | |
| try: | |
| # Step 1: Ensure .env file exists | |
| if not _ENV_FILE.exists(): | |
| logger.info("[ENV_INIT] .env missing, initializing...") | |
| if not _ensure_env_file(): | |
| logger.error("[ENV_INIT] Failed to create .env file") | |
| # Step 2: Load environment variables | |
| if _ENV_FILE.exists(): | |
| if not _load_env_file(): | |
| logger.warning("[ENV_INIT] .env load failed, but safe defaults applied") | |
| # Step 3: Verify critical variables have values (log warnings if missing) | |
| _missing_critical = [] | |
| _optional_critical = ["HF_TOKEN", "OPENCLAW_DATASET_REPO", "OPENAI_API_KEY", "OPENROUTER_API_KEY", "ANTHROPIC_API_KEY", "CLAUDE_API_KEY"] | |
| for key in _optional_critical: | |
| if not os.environ.get(key): | |
| _missing_critical.append(key) | |
| if _missing_critical: | |
| logger.warning(f"[ENV_INIT] Optional variables not set (features may be limited): {', '.join(_missing_critical)}") | |
| # Log successful initialization | |
| logger.info("[ENV_INIT] Environment initialization complete") | |
| return True | |
| except Exception as e: | |
| logger.error(f"[ENV_INIT] Critical error during initialization: {e}") | |
| logger.error("[ENV_INIT] Applying emergency safe defaults") | |
| # Emergency fallback - set critical defaults directly | |
| for key, value in _CRITICAL_DEFAULTS.items(): | |
| if key not in os.environ: | |
| os.environ[key] = value | |
| return True # Still return True to allow app to start | |
| # Auto-run on import (safe, idempotent) | |
| _init_done = False | |
| if not _init_done: | |
| init_env() | |
| _init_done = True | |