Initial commit: Docker Sandbox Runner complete setup
This commit is contained in:
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,128 @@
|
||||
# Docker Sandbox Runner for ComfyUI
|
||||
|
||||
This **Custom Node** provides a secure, isolated execution environment for Python scripts within ComfyUI, utilizing persistent Docker containers. Code execution is ultra-fast (milliseconds) and completely isolated from the host operating system.
|
||||
|
||||
-----
|
||||
|
||||
## Key Features
|
||||
|
||||
* **Maximum Security (Docker Isolation):** Code runs in an ephemeral Docker container, blocked from accessing the Host's filesystem, network, or sensitive resources.
|
||||
* **Performance (Sidecar Pattern):** The container is started only once (the *Sidecar* pattern) and reused via `docker exec`, eliminating Docker boot time latency.
|
||||
* **Dynamic Inputs:** Allows adding execution inputs (`*`, `STRING`, `INT`, etc.) dynamically via the context menu.
|
||||
* **Multiple Outputs:** Provides 6 result outputs (`r1` to `r6`) and 1 log output (`log`).
|
||||
* **Non-Root Execution:** Code runs as the **`nobody`** user inside the container, significantly minimizing internal security risk.
|
||||
|
||||
-----
|
||||
|
||||
## Installation
|
||||
|
||||
### 1\. Mandatory Prerequisites
|
||||
|
||||
You must have two pieces of software installed:
|
||||
|
||||
* **Docker Desktop/Engine:** The containerization engine itself.
|
||||
* **`docker-py`:** The Python library to control Docker.
|
||||
|
||||
Execute the following command in your ComfyUI Python environment:
|
||||
|
||||
```bash
|
||||
# Installs the Python library required to communicate with the Docker daemon
|
||||
pip install docker
|
||||
```
|
||||
|
||||
### 2\. Node Installation
|
||||
|
||||
1. Clone this repository into your ComfyUI's `custom_nodes` folder:
|
||||
```bash
|
||||
cd path/to/ComfyUI/custom_nodes/
|
||||
git clone [Your Repository URL]
|
||||
```
|
||||
2. Restart ComfyUI.
|
||||
|
||||
-----
|
||||
|
||||
## Docker Engine Installation (Detailed CLI Instructions)
|
||||
|
||||
You must install and start the Docker service (Daemon) before using the node. **Note:** After installation, ensure your user is added to the `docker` group to run commands without `sudo` (e.g., `sudo usermod -aG docker $USER`).
|
||||
|
||||
| OS / Distribution | Installation Command Set (Root/Sudo Required) | Verification & Service |
|
||||
| :--- | :--- | :--- |
|
||||
| **Ubuntu/Debian** | **1. Install Prerequisites:**<br>`sudo apt update && sudo apt install ca-certificates curl gnupg`<br>**2. Add GPG Key:**<br>`sudo install -m 0755 -d /etc/apt/keyrings`<br>`curl -fsSL https://download.docker.com/linux/ubuntu/gpg \| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg`<br>**3. Add Repository:**<br>`echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null`<br>**4. Install Docker:**<br>`sudo apt update && sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin` | `sudo systemctl enable docker --now` |
|
||||
| **Arch/Manjaro** | **1. Install Package:**<br>`sudo pacman -Syu docker`<br>**2. Start Service:**<br>`sudo systemctl start docker.service` | `sudo systemctl status docker.service` |
|
||||
| **Fedora/RHEL** | **1. Install Package:**<br>`sudo dnf -y install docker-ce docker-ce-cli containerd.io --repo docker-ce --repo docker-ce-stable`<br>**2. Start Service:**<br>`sudo systemctl start docker` | `sudo systemctl enable docker --now` |
|
||||
| **macOS** | **Install Docker Desktop (Recommended):** Download the official `.dmg` installer from the Docker website. | Start the application. CLI tools (`docker`) are installed automatically. |
|
||||
| **Windows** | **Install Docker Desktop (Recommended):** Download the official installer from the Docker website.<br>**Prerequisite:** Ensure **WSL 2** is installed and enabled for optimal performance. | Start the application. Verify in PowerShell: `docker info` |
|
||||
|
||||
-----
|
||||
|
||||
### Clarification on Windows Installation
|
||||
|
||||
**WSL 2 and Windows:** Docker Desktop for Windows uses a lightweight Linux virtual machine (VM) powered by **WSL 2** by default. This VM hosts the Docker Engine. Therefore, while you install the *Desktop app*, you must have **WSL 2 enabled** for the container engine to function correctly and offer the best performance.
|
||||
|
||||
-----
|
||||
|
||||
## Usage Guide
|
||||
|
||||
### 1\. Add the Node
|
||||
|
||||
Find **"Docker Sandbox Runner"** in the **Advanced/Scripting** menu.
|
||||
|
||||
### 2\. Define Inputs
|
||||
|
||||
1. **Code (`code`):** Insert your Python script. The desired outputs must be assigned to variables `r1` through `r6`.
|
||||
2. **Dynamic Inputs:** Right-click the node and use **"+ Add Input (Dynamic)"** to inject variables (e.g., `my_int (INT)`).
|
||||
|
||||
### 3\. Example Code
|
||||
|
||||
```python
|
||||
# Access the injected variable "my_value" from the input slot
|
||||
# Libraries like math, random, json, re, time are pre-loaded.
|
||||
|
||||
import math
|
||||
|
||||
if my_value > 50:
|
||||
r1 = "Processed successfully."
|
||||
r2 = my_value * math.pi
|
||||
else:
|
||||
r1 = "Value too low."
|
||||
r2 = None
|
||||
```
|
||||
|
||||
### 4\. Resource Management
|
||||
|
||||
The persistent container `comfyui-sandbox-worker` will run until explicitly stopped.
|
||||
|
||||
* **To Stop Consumption:** Stop the container manually via the Docker Desktop GUI or by CLI:
|
||||
```bash
|
||||
docker stop comfyui-sandbox-worker
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the **MIT License**.
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 [Your Name or Project Name]
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
# __init__.py
|
||||
from .sandbox_node import DockerSandboxRunner
|
||||
|
||||
# Mapeia a string usada no JSON do workflow para a classe Python
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"DockerSandboxRunner": DockerSandboxRunner
|
||||
}
|
||||
|
||||
# Mapeia o nome da classe para o nome de exibição no menu do ComfyUI
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"DockerSandboxRunner": "Docker Sandbox Runner"
|
||||
}
|
||||
|
||||
# ESTE CAMPO É CRÍTICO para o carregamento do JavaScript!
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
|
||||
|
||||
# No final do arquivo __init__.py
|
||||
print("✅ CUSTOM NODE DOCKER SANDBOX REGISTRADO COM SUCESSO.")
|
||||
Binary file not shown.
Binary file not shown.
Executable
+348
@@ -0,0 +1,348 @@
|
||||
{
|
||||
"id": "f32fcb48-b15c-444d-93c9-1e4ecf3d9806",
|
||||
"revision": 0,
|
||||
"last_node_id": 9,
|
||||
"last_link_id": 7,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 6,
|
||||
"type": "PreviewAny",
|
||||
"pos": [
|
||||
985,
|
||||
215
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
76
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "source",
|
||||
"type": "*",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.76",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"input_ue_unconnectable": {}
|
||||
},
|
||||
"Node name for S&R": "PreviewAny"
|
||||
},
|
||||
"widgets_values": [],
|
||||
"color": "#4a7b7b",
|
||||
"bgcolor": "#335555",
|
||||
"shape": 1
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "PreviewAny",
|
||||
"pos": [
|
||||
985,
|
||||
499
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
165
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "source",
|
||||
"type": "*",
|
||||
"link": 6
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.76",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"input_ue_unconnectable": {}
|
||||
},
|
||||
"Node name for S&R": "PreviewAny"
|
||||
},
|
||||
"widgets_values": [],
|
||||
"color": "#4a7b7b",
|
||||
"bgcolor": "#335555",
|
||||
"shape": 1
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "DockerSandboxRunner",
|
||||
"pos": [
|
||||
555,
|
||||
215
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
256
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "code",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "code"
|
||||
},
|
||||
"link": 2
|
||||
},
|
||||
{
|
||||
"name": "my_value (INT)",
|
||||
"type": "INT",
|
||||
"link": 3
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "r1",
|
||||
"type": "*",
|
||||
"links": [
|
||||
4
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "r2",
|
||||
"type": "*",
|
||||
"links": [
|
||||
7
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "r3",
|
||||
"type": "*",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "r4",
|
||||
"type": "*",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "r5",
|
||||
"type": "*",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "r6",
|
||||
"type": "*",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "log",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
6
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DockerSandboxRunner",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"input_ue_unconnectable": {},
|
||||
"version": "7.5.2"
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
10,
|
||||
"512m"
|
||||
],
|
||||
"color": "#4a7b7b",
|
||||
"bgcolor": "#335555",
|
||||
"shape": 1
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "PreviewAny",
|
||||
"pos": [
|
||||
985,
|
||||
351
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
88
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "source",
|
||||
"type": "*",
|
||||
"link": 7
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.76",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"input_ue_unconnectable": {}
|
||||
},
|
||||
"Node name for S&R": "PreviewAny"
|
||||
},
|
||||
"widgets_values": [],
|
||||
"color": "#4a7b7b",
|
||||
"bgcolor": "#335555",
|
||||
"shape": 1
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "PrimitiveInt",
|
||||
"pos": [
|
||||
105,
|
||||
215
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
3
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.76",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"input_ue_unconnectable": {}
|
||||
},
|
||||
"Node name for S&R": "PrimitiveInt"
|
||||
},
|
||||
"widgets_values": [
|
||||
60,
|
||||
"fixed"
|
||||
],
|
||||
"color": "#4a7b7b",
|
||||
"bgcolor": "#335555",
|
||||
"shape": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "PrimitiveStringMultiline",
|
||||
"pos": [
|
||||
105,
|
||||
357
|
||||
],
|
||||
"size": [
|
||||
420,
|
||||
255
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.76",
|
||||
"Node name for S&R": "PrimitiveStringMultiline",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"input_ue_unconnectable": {},
|
||||
"version": "7.5.2"
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
"# Access the injected variable \"my_value\" from the input slot\n# Libraries like math, random, json, re, time are pre-loaded.\n\nimport math \n\nif my_value > 50:\n r1 = \"Processed successfully.\"\n r2 = my_value * math.pi\nelse:\n r1 = \"Value too low.\"\n r2 = None\n\n\nprint (\"✅✅✅ Finished ✅✅✅\")"
|
||||
],
|
||||
"color": "#4a7b7b",
|
||||
"bgcolor": "#335555",
|
||||
"shape": 1
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
2,
|
||||
2,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
3,
|
||||
5,
|
||||
0,
|
||||
4,
|
||||
1,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
4,
|
||||
4,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
6,
|
||||
4,
|
||||
6,
|
||||
8,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
7,
|
||||
4,
|
||||
1,
|
||||
9,
|
||||
0,
|
||||
"*"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ue_links": [],
|
||||
"ds": {
|
||||
"scale": 1.021848739495798,
|
||||
"offset": [
|
||||
103.33333333333337,
|
||||
-34.98958333333326
|
||||
]
|
||||
},
|
||||
"links_added_by_ue": [],
|
||||
"frontendVersion": "1.33.10",
|
||||
"VHS_latentpreview": false,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
docker
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
import docker
|
||||
import json
|
||||
import re
|
||||
import uuid
|
||||
import base64
|
||||
import time
|
||||
|
||||
class AnyType(str):
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
def __eq__(self, __value: object) -> bool:
|
||||
return True
|
||||
|
||||
# CLASSE RENOMEADA
|
||||
class DockerSandboxRunner:
|
||||
CONTAINER_NAME = "comfyui-sandbox-worker"
|
||||
DOCKER_IMAGE = "python:3.9-slim"
|
||||
SANDBOX_USER = "nobody"
|
||||
|
||||
def __init__(self):
|
||||
self.client = None
|
||||
try:
|
||||
self.client = docker.from_env()
|
||||
except Exception:
|
||||
print("⚠️ AVISO: Não foi possível conectar ao Docker. Verifique se o Docker Desktop está rodando.")
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"code": ("STRING", {"multiline": True, "placeholder": "# Código limpo, focado apenas na lógica de execução."}),
|
||||
# CAMPOS OCULTOS PARA CONFIGURAÇÃO TÉCNICA
|
||||
"timeout": ("INT", {"default": 10, "min": 1, "max": 60, "step": 1, "hidden": True}),
|
||||
"memory_limit": ("STRING", {"default": "512m", "placeholder": "Ex: 512m, 1g, 2gb", "hidden": True}),
|
||||
},
|
||||
"optional": {
|
||||
# Campo de ação removido (como solicitado anteriormente).
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (
|
||||
AnyType("*"), AnyType("*"), AnyType("*"), AnyType("*"), AnyType("*"), AnyType("*"),
|
||||
"STRING"
|
||||
)
|
||||
RETURN_NAMES = ("r1", "r2", "r3", "r4", "r5", "r6", "log")
|
||||
FUNCTION = "execute_sandbox"
|
||||
CATEGORY = "Advanced/Scripting"
|
||||
|
||||
def _ensure_container_running(self, mem_limit):
|
||||
"""Garante que o container worker está vivo e esperando comandos"""
|
||||
if not self.client:
|
||||
raise RuntimeError("Docker Client não inicializado. O Docker Desktop está rodando?")
|
||||
|
||||
try:
|
||||
container = self.client.containers.get(self.CONTAINER_NAME)
|
||||
if container.status != 'running':
|
||||
print(f"🛠️ Docker Sandbox: Iniciando container: {self.CONTAINER_NAME}")
|
||||
container.start()
|
||||
return container
|
||||
except docker.errors.NotFound:
|
||||
print(f"🛠️ Criando container sandbox: {self.CONTAINER_NAME} com {mem_limit} de RAM...")
|
||||
return self.client.containers.run(
|
||||
self.DOCKER_IMAGE,
|
||||
command="tail -f /dev/null",
|
||||
name=self.CONTAINER_NAME,
|
||||
detach=True,
|
||||
mem_limit=mem_limit,
|
||||
network_mode="none",
|
||||
user=self.SANDBOX_USER
|
||||
)
|
||||
|
||||
def execute_sandbox(self, code, timeout, memory_limit="512m", **kwargs):
|
||||
log_messages = []
|
||||
|
||||
final_results = [None] * 6
|
||||
object_references = {}
|
||||
variable_injection_commands = []
|
||||
|
||||
try:
|
||||
container = self._ensure_container_running(memory_limit)
|
||||
except Exception as e:
|
||||
log_messages.append(f"Docker Error (Startup/Connect): {str(e)}")
|
||||
return (*final_results, "\n".join(log_messages))
|
||||
|
||||
# 1. Prepara Variáveis (usando Literal Python)
|
||||
for key, value in kwargs.items():
|
||||
clean_key = key.split(" (")[0].strip().replace(" ", "_")
|
||||
|
||||
if value is None or isinstance(value, bool):
|
||||
variable_injection_commands.append(f"{clean_key} = {str(value)}")
|
||||
elif isinstance(value, (int, float, str)):
|
||||
variable_injection_commands.append(f"{clean_key} = {repr(value)}")
|
||||
elif isinstance(value, (list, dict)):
|
||||
try:
|
||||
variable_injection_commands.append(f"{clean_key} = {repr(value)}")
|
||||
except:
|
||||
ref_id = f"__REF__{uuid.uuid4().hex}"
|
||||
object_references[ref_id] = value
|
||||
variable_injection_commands.append(f"{clean_key} = '{ref_id}'")
|
||||
else:
|
||||
ref_id = f"__REF__{uuid.uuid4().hex}"
|
||||
object_references[ref_id] = value
|
||||
variable_injection_commands.append(f"{clean_key} = '{ref_id}'")
|
||||
|
||||
# 2. Constrói o Payload Final (Base64)
|
||||
injection_script = "\n".join(variable_injection_commands)
|
||||
|
||||
payload_script = f"""
|
||||
import json
|
||||
import sys
|
||||
import math, random, time, re
|
||||
import signal
|
||||
|
||||
TIMEOUT_SECONDS = {timeout}
|
||||
|
||||
def signal_handler(signum, frame):
|
||||
print(f"__ERROR_START__TimeoutError: Script execution exceeded {{TIMEOUT_SECONDS}} seconds.__ERROR_END__")
|
||||
sys.exit(1)
|
||||
|
||||
signal.signal(signal.SIGALRM, signal_handler)
|
||||
signal.alarm(TIMEOUT_SECONDS)
|
||||
|
||||
{injection_script}
|
||||
|
||||
_user_error = None
|
||||
try:
|
||||
exec({repr(code)})
|
||||
finally:
|
||||
signal.alarm(0)
|
||||
|
||||
outputs = {{}}
|
||||
if _user_error:
|
||||
outputs['error'] = _user_error
|
||||
else:
|
||||
for i in range(1, 7):
|
||||
vname = f"r{{i}}"
|
||||
if vname in locals():
|
||||
val = locals()[vname]
|
||||
try:
|
||||
json.dumps(val)
|
||||
outputs[vname] = val
|
||||
except:
|
||||
outputs[vname] = str(val)
|
||||
else:
|
||||
outputs[vname] = None
|
||||
|
||||
sys.stdout.write("__COMFY_RESULT_START__" + json.dumps(outputs) + "__COMFY_RESULT_END__")
|
||||
"""
|
||||
|
||||
b64_payload = base64.b64encode(payload_script.encode('utf-8')).decode('utf-8')
|
||||
cmd = f"python3 -c \"import base64; exec(base64.b64decode('{b64_payload}'))\""
|
||||
|
||||
try:
|
||||
exec_result = container.exec_run(
|
||||
cmd=cmd,
|
||||
workdir="/tmp"
|
||||
)
|
||||
output_text = exec_result.output.decode('utf-8', errors='ignore')
|
||||
|
||||
err_match = re.search(r"__ERROR_START__(.*?)__ERROR_END__", output_text, re.DOTALL)
|
||||
if err_match:
|
||||
log_messages.append(f"Script Error (in Docker):\n{err_match.group(1)}")
|
||||
return (*final_results, "\n".join(log_messages) + "\n" + output_text)
|
||||
|
||||
match = re.search(r"__COMFY_RESULT_START__(.*?)__COMFY_RESULT_END__", output_text, re.DOTALL)
|
||||
if match:
|
||||
json_str = match.group(1)
|
||||
extracted_dict = json.loads(json_str)
|
||||
|
||||
if 'error' in extracted_dict:
|
||||
log_messages.append(f"Runtime Error:\n{extracted_dict['error']}")
|
||||
return (*final_results, "\n".join(log_messages) + "\n" + output_text)
|
||||
|
||||
for i in range(6):
|
||||
key = f"r{i+1}"
|
||||
val = extracted_dict.get(key)
|
||||
|
||||
if isinstance(val, str) and val in object_references:
|
||||
final_results[i] = object_references[val]
|
||||
else:
|
||||
final_results[i] = val
|
||||
|
||||
log_messages.append("SUCCESS: Script executado com sucesso.")
|
||||
return (*final_results, "\n".join(log_messages) + "\n" + output_text)
|
||||
|
||||
except Exception as e:
|
||||
log_messages.append(f"Execution Infrastructure Error (Docker exec failed): {str(e)}")
|
||||
return (*final_results, "\n".join(log_messages))
|
||||
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,79 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "DockerSandbox.Extensions",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
// CORRIGIDO: Checa pelo nome da classe Python (sem espaços)
|
||||
if (nodeData.name === "DockerSandboxRunner") {
|
||||
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
||||
this.setSize([350, 150]);
|
||||
return r;
|
||||
};
|
||||
|
||||
// --- FUNÇÃO PARA INJETAR OPÇÕES DE MENU ---
|
||||
const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
||||
nodeType.prototype.getExtraMenuOptions = function (_, options) {
|
||||
const me = this;
|
||||
|
||||
// 1. ADD INPUT
|
||||
options.push({
|
||||
content: "+ Add Input (Dynamic)",
|
||||
callback: () => {
|
||||
const name = prompt("Enter variable name (e.g., width, my_text):");
|
||||
if (!name) return;
|
||||
|
||||
const validTypes = ["STRING", "INT", "FLOAT", "BOOLEAN", "*"];
|
||||
let type = prompt(
|
||||
`Enter type exactly as shown:\n\n${validTypes.join("\n")}\n\n(* = Any/Wildcard)`,
|
||||
"*"
|
||||
);
|
||||
|
||||
if (!type) return;
|
||||
type = type.toUpperCase();
|
||||
|
||||
if (!validTypes.includes(type)) {
|
||||
alert(`Invalid type! Please use one of: ${validTypes.join(", ")}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const finalName = `${name} (${type})`;
|
||||
const exists = me.findInputSlot(finalName);
|
||||
|
||||
if (exists !== -1) {
|
||||
alert("An input with this name already exists.");
|
||||
return;
|
||||
}
|
||||
|
||||
me.addInput(finalName, type);
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
});
|
||||
|
||||
// 2. REMOVE UNUSED INPUTS
|
||||
options.push({
|
||||
content: "- Remove Unused Inputs",
|
||||
callback: () => {
|
||||
for (let i = me.inputs.length - 1; i >= 0; i--) {
|
||||
const input = me.inputs[i];
|
||||
// Ignora os inputs fixos (code, timeout, memory_limit)
|
||||
if (["code", "timeout", "memory_limit"].includes(input.name)) continue;
|
||||
|
||||
// Remove se não estiver conectado
|
||||
if (!input.link) {
|
||||
me.removeInput(i);
|
||||
}
|
||||
}
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
});
|
||||
|
||||
if (getExtraMenuOptions) {
|
||||
return getExtraMenuOptions.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user