diff --git a/src/openenv_cli/commands/push.py b/src/openenv_cli/commands/push.py index 31fc495b..2ebb7aa0 100644 --- a/src/openenv_cli/commands/push.py +++ b/src/openenv_cli/commands/push.py @@ -138,38 +138,64 @@ def _prepare_staging_directory( else: shutil.copy2(item, dest) + # Ensure Dockerfile is at repository root (required by Hugging Face) + dockerfile_server_path = staging_dir / "server" / "Dockerfile" + dockerfile_root_path = staging_dir / "Dockerfile" + dockerfile_path: Path | None = None + + if dockerfile_server_path.exists(): + if dockerfile_root_path.exists(): + dockerfile_root_path.unlink() + dockerfile_server_path.rename(dockerfile_root_path) + console.print( + "[bold cyan]Moved Dockerfile to repository root for deployment[/bold cyan]" + ) + dockerfile_path = dockerfile_root_path + elif dockerfile_root_path.exists(): + dockerfile_path = dockerfile_root_path + # Modify Dockerfile to optionally enable web interface and update base image - dockerfile_path = staging_dir / "server" / "Dockerfile" - if dockerfile_path.exists(): + if dockerfile_path and dockerfile_path.exists(): dockerfile_content = dockerfile_path.read_text() lines = dockerfile_content.split("\n") new_lines = [] cmd_found = False base_image_updated = False web_interface_env_exists = "ENABLE_WEB_INTERFACE" in dockerfile_content + last_instruction = None for line in lines: + stripped = line.strip() + token = stripped.split(maxsplit=1)[0] if stripped else "" + current_instruction = token.upper() + + is_healthcheck_continuation = last_instruction == "HEALTHCHECK" + # Update base image if specified - if base_image and line.strip().startswith("FROM") and not base_image_updated: - # Replace the FROM line with new base image + if base_image and stripped.startswith("FROM") and not base_image_updated: new_lines.append(f"FROM {base_image}") base_image_updated = True + last_instruction = "FROM" continue - # Add ENABLE_WEB_INTERFACE before CMD if requested - if line.strip().startswith("CMD") and not cmd_found and not web_interface_env_exists: - # Add ENV line before CMD - if enable_interface: - new_lines.append("ENV ENABLE_WEB_INTERFACE=true") + if ( + stripped.startswith("CMD") + and not cmd_found + and not web_interface_env_exists + and enable_interface + and not is_healthcheck_continuation + ): + new_lines.append("ENV ENABLE_WEB_INTERFACE=true") cmd_found = True new_lines.append(line) - # Add ENABLE_WEB_INTERFACE if CMD not found and not already present + if current_instruction: + last_instruction = current_instruction + if not cmd_found and not web_interface_env_exists and enable_interface: new_lines.append("ENV ENABLE_WEB_INTERFACE=true") - # If base image was specified but FROM line wasn't found, add it at the beginning if base_image and not base_image_updated: new_lines.insert(0, f"FROM {base_image}") diff --git a/src/openenv_cli/templates/openenv_env/server/Dockerfile b/src/openenv_cli/templates/openenv_env/server/Dockerfile index ff1dae2f..70eda75f 100644 --- a/src/openenv_cli/templates/openenv_env/server/Dockerfile +++ b/src/openenv_cli/templates/openenv_env/server/Dockerfile @@ -10,7 +10,7 @@ # - Standalone environments (with openenv-core from pip) # The build script (openenv build) handles context detection and sets appropriate build args. -ARG BASE_IMAGE=openenv-base:latest +ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest FROM ${BASE_IMAGE} AS builder WORKDIR /app @@ -26,6 +26,13 @@ COPY . /app/env # For standalone builds, openenv-core will be installed from pip via pyproject.toml WORKDIR /app/env +# Ensure uv is available (for local builds where base image lacks it) +RUN if ! command -v uv >/dev/null 2>&1; then \ + curl -LsSf https://astral.sh/uv/install.sh | sh && \ + mv /root/.local/bin/uv /usr/local/bin/uv && \ + mv /root/.local/bin/uvx /usr/local/bin/uvx; \ + fi + # Install dependencies using uv sync # If uv.lock exists, use it; otherwise resolve on the fly RUN --mount=type=cache,target=/root/.cache/uv \ diff --git a/src/openenv_cli/templates/openenv_env/server/__ENV_NAME___environment.py b/src/openenv_cli/templates/openenv_env/server/__ENV_NAME___environment.py index f2f39c9c..63df6c01 100644 --- a/src/openenv_cli/templates/openenv_env/server/__ENV_NAME___environment.py +++ b/src/openenv_cli/templates/openenv_env/server/__ENV_NAME___environment.py @@ -16,7 +16,7 @@ from openenv_core.env_server.interfaces import Environment from openenv_core.env_server.types import State -from __ENV_NAME__.models import __ENV_CLASS_NAME__Action, __ENV_CLASS_NAME__Observation +from models import __ENV_CLASS_NAME__Action, __ENV_CLASS_NAME__Observation class __ENV_CLASS_NAME__Environment(Environment): @@ -93,4 +93,3 @@ def state(self) -> State: Current State with episode_id and step_count """ return self._state - diff --git a/src/openenv_cli/templates/openenv_env/server/app.py b/src/openenv_cli/templates/openenv_env/server/app.py index c4d96380..1db6ce55 100644 --- a/src/openenv_cli/templates/openenv_env/server/app.py +++ b/src/openenv_cli/templates/openenv_env/server/app.py @@ -30,7 +30,7 @@ ) from e from .__ENV_NAME___environment import __ENV_CLASS_NAME__Environment -from __ENV_NAME__.models import __ENV_CLASS_NAME__Action, __ENV_CLASS_NAME__Observation +from models import __ENV_CLASS_NAME__Action, __ENV_CLASS_NAME__Observation # Create the environment instance env = __ENV_CLASS_NAME__Environment()