diff --git a/.dockerignore b/.dockerignore index ba20b00b..43023c78 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,5 +4,5 @@ .git/ .venv/ .github/ -ui/node_modules/ +ui/ .dockerignore diff --git a/.github/workflows/comfyui-base.yaml b/.github/workflows/comfyui-base.yaml new file mode 100644 index 00000000..ffac6b4e --- /dev/null +++ b/.github/workflows/comfyui-base.yaml @@ -0,0 +1,79 @@ +name: Build and push comfyui-base docker image + +on: + pull_request: + paths: + - docker/Dockerfile.base + - src/comfystream/scripts/ + - configs/ + - .github/workflows/comfyui-base.yaml + branches: + - main + push: + paths: + - docker/Dockerfile.base + - src/comfystream/scripts/ + - configs/ + - .github/workflows/comfyui-base.yaml + branches: + - main + tags: + - "v*" + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + docker: + name: docker builds + permissions: + packages: write + contents: read + runs-on: [self-hosted, linux, gpu] + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.CI_DOCKERHUB_USERNAME }} + password: ${{ secrets.CI_DOCKERHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: | + livepeer/comfyui-base + tags: | + type=sha + type=ref,event=pr + type=ref,event=tag + type=sha,format=long + type=ref,event=branch + type=semver,pattern={{version}},prefix=v + type=semver,pattern={{major}}.{{minor}},prefix=v + type=raw,value=latest,enable={{is_default_branch}} + type=raw,value=${{ github.event.pull_request.head.ref }} + type=raw,value=stable,enable=${{ startsWith(github.event.ref, 'refs/tags/v') }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push livepeer docker image + timeout-minutes: 200 + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + file: docker/Dockerfile.base + labels: ${{ steps.meta.outputs.labels }} + annotations: ${{ steps.meta.outputs.annotations }} + cache-from: type=registry,ref=livepeer/comfyui-base:build-cache + cache-to: type=registry,mode=max,ref=livepeer/comfyui-base:build-cache diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 0e2a52b1..74537e41 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -66,6 +66,3 @@ jobs: annotations: ${{ steps.meta.outputs.annotations }} cache-from: type=registry,ref=${{ github.repository }}:build-cache cache-to: type=registry,mode=max,ref=${{ github.repository }}:build-cache - - - name: Notify new build upload - run: curl -X POST https://holy-bread-207a.livepeer.workers.dev diff --git a/docker/Dockerfile.base b/docker/Dockerfile.base index d453fe6b..b8ddc1fb 100644 --- a/docker/Dockerfile.base +++ b/docker/Dockerfile.base @@ -1,58 +1,66 @@ -ARG BASE_IMAGE=nvidia/cuda:12.2.2-cudnn8-devel-ubuntu22.04 -FROM ${BASE_IMAGE} +ARG BASE_IMAGE=nvidia/cuda:12.2.2-cudnn8-devel-ubuntu22.04 \ + CONDA_VERSION=latest \ + PYTHON_VERSION=3.11 + +FROM "${BASE_IMAGE}" + +ARG CONDA_VERSION \ + PYTHON_VERSION + + +ENV DEBIAN_FRONTEND=noninteractive \ + CONDA_VERSION="${CONDA_VERSION}" \ + PATH="/workspace/miniconda3/bin:${PATH}" \ + PYTHON_VERSION="${PYTHON_VERSION}" # System dependencies -RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && apt-get install -y \ - git \ - wget \ - nano \ - socat \ - libsndfile1 \ - build-essential llvm tk-dev \ - && rm -rf /var/lib/apt/lists/* +RUN apt update && apt install -yqq \ + git \ + wget \ + nano \ + socat \ + libsndfile1 \ + build-essential llvm tk-dev && \ + rm -rf /var/lib/apt/lists/* # Conda setup -RUN mkdir -p /workspace/comfystream -RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh \ - && bash /tmp/miniconda.sh -b -p /workspace/miniconda3 \ - && rm /tmp/miniconda.sh - -ENV PATH="/workspace/miniconda3/bin:${PATH}" -RUN eval "$(/workspace/miniconda3/bin/conda shell.bash hook)" -RUN conda create -n comfystream python=3.11 -y +RUN mkdir -p /workspace/comfystream && \ + wget "https://repo.anaconda.com/miniconda/Miniconda3-${CONDA_VERSION}-Linux-x86_64.sh" -O /tmp/miniconda.sh && \ + bash /tmp/miniconda.sh -b -p /workspace/miniconda3 && \ + eval "$(/workspace/miniconda3/bin/conda shell.bash hook)" && \ + conda create -n comfystream python="${PYTHON_VERSION}" -y && \ + rm /tmp/miniconda.sh && \ + conda run -n comfystream --no-capture-output pip install aiortc aiohttp requests tqdm pyyaml --root-user-action=ignore # Clone ComfyUI -RUN git clone https://github.com/comfyanonymous/ComfyUI.git /workspace/ComfyUI +ADD --link https://github.com/comfyanonymous/ComfyUI.git /workspace/ComfyUI # Copy only files needed for setup -COPY ./src/comfystream/scripts /workspace/comfystream/src/comfystream/scripts -COPY ./configs /workspace/comfystream/configs - -# Install base dependencies -RUN conda run -n comfystream --no-capture-output pip install aiortc aiohttp requests tqdm pyyaml --root-user-action=ignore +COPY --link ./src/comfystream/scripts /workspace/comfystream/src/comfystream/scripts +COPY --link ./configs /workspace/comfystream/configs # Run setup_nodes (cached unless setup_nodes.py or nodes/ changes) -RUN conda run -n comfystream --no-capture-output --cwd /workspace/comfystream python src/comfystream/scripts/setup_nodes.py --workspace /workspace/ComfyUI +RUN conda run -n comfystream --no-capture-output --cwd /workspace/comfystream python src/comfystream/scripts/setup_nodes.py --workspace /workspace/ComfyUI # Copy ComfyStream files into ComfyUI -COPY . /workspace/comfystream +COPY . /workspace/comfystream # Copy comfystream and example workflows to ComfyUI -COPY ./workflows/comfyui/* /workspace/ComfyUI/user/default/workflows +COPY ./workflows/comfyui/* /workspace/ComfyUI/user/default/workflows # Install ComfyUI requirements -RUN conda run -n comfystream --no-capture-output --cwd /workspace/ComfyUI pip install -r requirements.txt --root-user-action=ignore +RUN conda run -n comfystream --no-capture-output --cwd /workspace/ComfyUI pip install -r requirements.txt --root-user-action=ignore # Install ComfyStream requirements -RUN conda run -n comfystream --no-capture-output --cwd /workspace/comfystream pip install -r requirements.txt --root-user-action=ignore -RUN conda run -n comfystream --no-capture-output --cwd /workspace/comfystream pip install . --root-user-action=ignore -RUN ln -s /workspace/comfystream /workspace/ComfyUI/custom_nodes/comfystream -RUN conda run -n comfystream --no-capture-output --cwd /workspace/comfystream python install.py --workspace /workspace/ComfyUI -RUN conda run -n comfystream --no-capture-output pip install --upgrade tensorrt-cu12-bindings tensorrt-cu12-libs --root-user-action=ignore -RUN conda run -n comfystream --no-capture-output pip install mediapipe==0.10.8 --root-user-action=ignore +RUN conda run -n comfystream --no-capture-output --cwd /workspace/comfystream pip install -r requirements.txt --root-user-action=ignore && \ + conda run -n comfystream --no-capture-output --cwd /workspace/comfystream pip install . --root-user-action=ignore && \ + ln -s /workspace/comfystream /workspace/ComfyUI/custom_nodes/comfystream && \ + conda run -n comfystream --no-capture-output --cwd /workspace/comfystream python install.py --workspace /workspace/ComfyUI && \ + conda run -n comfystream --no-capture-output pip install --upgrade tensorrt-cu12-bindings tensorrt-cu12-libs --root-user-action=ignore && \ + conda run -n comfystream --no-capture-output pip install mediapipe==0.10.8 --root-user-action=ignore # Configure no environment activation by default -RUN conda config --set auto_activate_base false -RUN conda init bash +RUN conda config --set auto_activate_base false && \ + conda init bash -WORKDIR /workspace/comfystream \ No newline at end of file +WORKDIR /workspace/comfystream diff --git a/src/comfystream/scripts/setup_nodes.py b/src/comfystream/scripts/setup_nodes.py index 9818b9a3..6805083a 100755 --- a/src/comfystream/scripts/setup_nodes.py +++ b/src/comfystream/scripts/setup_nodes.py @@ -5,18 +5,24 @@ import yaml import argparse from utils import get_config_path, load_model_config + + def parse_args(): - parser = argparse.ArgumentParser(description='Setup ComfyUI nodes and models') - parser.add_argument('--workspace', - default=os.environ.get('COMFY_UI_WORKSPACE', os.path.expanduser('~/comfyui')), - help='ComfyUI workspace directory (default: ~/comfyui or $COMFY_UI_WORKSPACE)') + parser = argparse.ArgumentParser(description="Setup ComfyUI nodes and models") + parser.add_argument( + "--workspace", + default=os.environ.get("COMFY_UI_WORKSPACE", Path("~/comfyui").expanduser()), + help="ComfyUI workspace directory (default: ~/comfyui or $COMFY_UI_WORKSPACE)", + ) return parser.parse_args() + def setup_environment(workspace_dir): os.environ["COMFY_UI_WORKSPACE"] = str(workspace_dir) os.environ["PYTHONPATH"] = str(workspace_dir) os.environ["CUSTOM_NODES_PATH"] = str(workspace_dir / "custom_nodes") + def setup_directories(workspace_dir): """Create required directories in the workspace""" # Create base directories @@ -24,10 +30,11 @@ def setup_directories(workspace_dir): custom_nodes_dir = workspace_dir / "custom_nodes" custom_nodes_dir.mkdir(parents=True, exist_ok=True) + def install_custom_nodes(workspace_dir, config_path=None): """Install custom nodes based on configuration""" if config_path is None: - config_path = get_config_path('nodes.yaml') + config_path = get_config_path("nodes.yaml") try: config = load_model_config(config_path) except FileNotFoundError: @@ -42,40 +49,56 @@ def install_custom_nodes(workspace_dir, config_path=None): os.chdir(custom_nodes_path) try: - for _, node_info in config['nodes'].items(): - dir_name = node_info['url'].split("/")[-1].replace(".git", "") + for _, node_info in config["nodes"].items(): + dir_name = node_info["url"].split("/")[-1].replace(".git", "") node_path = custom_nodes_path / dir_name print(f"Installing {node_info['name']}...") # Clone the repository if it doesn't already exist if not node_path.exists(): - cmd = ["git", "clone", node_info['url']] - if 'branch' in node_info: - cmd.extend(["-b", node_info['branch']]) + cmd = ["git", "clone", node_info["url"]] + if "branch" in node_info: + cmd.extend(["-b", node_info["branch"]]) subprocess.run(cmd, check=True) else: print(f"{node_info['name']} already exists, skipping clone.") # Checkout specific commit if branch is a commit hash - if 'branch' in node_info and len(node_info['branch']) == 40: # SHA-1 hash length - subprocess.run(["git", "-C", dir_name, "checkout", node_info['branch']], check=True) + if ( + "branch" in node_info and len(node_info["branch"]) == 40 + ): # SHA-1 hash length + subprocess.run( + ["git", "-C", dir_name, "checkout", node_info["branch"]], check=True + ) # Install requirements if present requirements_file = node_path / "requirements.txt" if requirements_file.exists(): - subprocess.run([sys.executable, "-m", "pip", "install", "-r", str(requirements_file)], check=True) + subprocess.run( + [ + sys.executable, + "-m", + "pip", + "install", + "-r", + str(requirements_file), + ], + check=True, + ) # Install additional dependencies if specified - if 'dependencies' in node_info: - for dep in node_info['dependencies']: - subprocess.run([sys.executable, "-m", "pip", "install", dep], check=True) + if "dependencies" in node_info: + for dep in node_info["dependencies"]: + subprocess.run( + [sys.executable, "-m", "pip", "install", dep], check=True + ) print(f"Installed {node_info['name']}") except Exception as e: print(f"Error installing {node_info['name']} {e}") raise e - return + def setup_nodes(): args = parse_args() @@ -86,4 +109,5 @@ def setup_nodes(): install_custom_nodes(workspace_dir) -setup_nodes() +if __name__ == "__main__": + setup_nodes()