diff --git a/pmoves/chit/secrets_manifest.yaml b/pmoves/chit/secrets_manifest.yaml index 21ea9b80fd..ef0b614bbf 100644 --- a/pmoves/chit/secrets_manifest.yaml +++ b/pmoves/chit/secrets_manifest.yaml @@ -982,6 +982,26 @@ entries: - file: env.tier-agent key: TAILSCALE_AUTHKEY required: false +- id: tailscale_api_key + source: + type: cgp + label: TAILSCALE_API_KEY + targets: + - file: .env.generated + key: TAILSCALE_API_KEY + - file: env.tier-agent + key: TAILSCALE_API_KEY + required: false +- id: tailscale_tailnet + source: + type: cgp + label: TAILSCALE_TAILNET + targets: + - file: .env.generated + key: TAILSCALE_TAILNET + - file: env.tier-agent + key: TAILSCALE_TAILNET + required: false - id: telegram_bot_token source: type: cgp diff --git a/pmoves/chit/secrets_manifest_v2.yaml b/pmoves/chit/secrets_manifest_v2.yaml index 97f4e1f191..65feb1f8e8 100644 --- a/pmoves/chit/secrets_manifest_v2.yaml +++ b/pmoves/chit/secrets_manifest_v2.yaml @@ -1225,6 +1225,31 @@ entries: - docker_secret: pmoves_tailscale_authkey required: false tier: agent +- id: tailscale_api_key + source: + type: cgp + label: TAILSCALE_APIKEY + aliases: + - TAILSCALE_API_KEY + targets: + - file: .env.generated + key: TAILSCALE_API_KEY + - file: env.tier-agent + key: TAILSCALE_API_KEY + - github_secret: TAILSCALE_APIKEY + required: false + tier: agent +- id: tailscale_tailnet + source: + type: cgp + label: TAILSCALE_TAILNET + targets: + - file: .env.generated + key: TAILSCALE_TAILNET + - file: env.tier-agent + key: TAILSCALE_TAILNET + required: false + tier: agent - id: telegram_bot_token source: type: cgp diff --git a/pmoves/config/mcp_inventory.json b/pmoves/config/mcp_inventory.json index 68ee67444c..4305db0500 100644 --- a/pmoves/config/mcp_inventory.json +++ b/pmoves/config/mcp_inventory.json @@ -1,11 +1,11 @@ { "version": 1, - "note": "Canonical inventory of PMOVES MCP servers. Used by pmoves/tools/mcp_config_generator.py to produce client-native config snippets for Claude, Kimi, KiloCode, Hermes, and Crush.", + "note": "Canonical inventory of PMOVES MCP servers. Used by pmoves/tools/mcp_config_generator.py to produce client-native config snippets for Claude, Kimi, KiloCode, Hermes, and Crush. Server entries support an optional 'disabled: true' field to mark servers that should be emitted as disabled in generated configs.", "defaults": { "cipher_local_url": "http://localhost:8105/api/mcp/sse", "cipher_fleet_url": "http://${TS_Z890}:8105/api/mcp/sse", - "agent_zero_local_url": "http://localhost:8080/mcp", - "agent_zero_fleet_url": "http://${TS_Z890}:8080/mcp", + "agent_zero_local_url": "http://localhost:8081/mcp/t-${AGENT_ZERO_MCP_TOKEN}/sse", + "agent_zero_fleet_url": "http://${TS_Z890}:8081/mcp/t-${AGENT_ZERO_MCP_TOKEN}/sse", "archon_local_url": "http://localhost:8051", "archon_fleet_url": "http://${TS_Z890}:8051", "docker_gateway_port": 8090 @@ -38,17 +38,18 @@ }, { "key": "agent-zero", - "description": "Agent Zero orchestrator", - "transport": "http", - "endpoint": "fleet", + "description": "Agent Zero orchestrator (SSE with MCP token auth)", + "transport": "sse", + "endpoint": "local", "endpoint_prefix": "agent_zero" }, { "key": "archon", - "description": "Archon knowledge / task orchestrator", + "description": "Archon knowledge / task orchestrator (REST-only MCP, no standard transport — disabled by default)", "transport": "http", - "endpoint": "fleet", - "endpoint_prefix": "archon" + "endpoint": "local", + "endpoint_prefix": "archon", + "disabled": true }, { "key": "pmoves-nats-fleet", @@ -122,6 +123,21 @@ "TAILSCALE_API_KEY": "${TAILSCALE_API_KEY}", "TAILSCALE_TAILNET": "${TAILSCALE_TAILNET}" } + }, + { + "key": "hostinger", + "description": "Hostinger VPS API (requires pmz-hostinger container)", + "transport": "stdio", + "command": "docker", + "args": [ + "exec", + "-i", + "pmz-hostinger", + "hostinger-api-mcp" + ], + "env": { + "HOSTINGER_API_TOKEN": "${HOSTINGER_API_TOKEN}" + } } ] }, diff --git a/pmoves/docker-compose.agents.yml b/pmoves/docker-compose.agents.yml index e7a2523a94..1b64b46cee 100644 --- a/pmoves/docker-compose.agents.yml +++ b/pmoves/docker-compose.agents.yml @@ -95,6 +95,9 @@ services: - A0_SET_browser_model_name=tensorzero::function_name::${AGENT_ZERO_TZ_FUNCTION:-agent_zero} - A0_SET_browser_model_api_base=http://tensorzero-gateway:3000/openai/v1 - A0_SET_a2a_server_enabled=true + # Expose Agent Zero's MCP SSE server so external clients (Crush, Archon, + # other nodes) can connect via standard MCP transport at /t-{token}/sse. + - A0_SET_mcp_server_enabled=true # A0_SET_mcp_server_token: soft default (CANONICAL_NAMES.md §5). The previous # ${MCP_SERVER_TOKEN:?...} hard-require made *every* compose invocation on the # shared file fail interpolation on nodes without the var — blocking bring-up diff --git a/pmoves/docker-compose.amd-voice.yml b/pmoves/docker-compose.amd-voice.yml index de073d446b..1ac091ab07 100644 --- a/pmoves/docker-compose.amd-voice.yml +++ b/pmoves/docker-compose.amd-voice.yml @@ -22,6 +22,10 @@ # RDNA2 (gfx1030/1031, RX6000): set HSA_OVERRIDE_GFX_VERSION=10.3.0 services: ultimate-tts-studio: + build: + context: ./docker/ultimate-tts-studio + dockerfile: Dockerfile.rocm + image: ${ULTIMATE_TTS_ROCM_IMAGE:-ghcr.io/powerfulmoves/pmoves-ultimate-tts-studio:rocm-latest} environment: - HSA_OVERRIDE_GFX_VERSION=${HSA_OVERRIDE_GFX_VERSION:-12.0.1} - PYTORCH_HIP_ALLOC_CONF=garbage_collection_threshold:0.6,max_split_size_mb:512 @@ -34,7 +38,7 @@ services: deploy: resources: reservations: - devices: [] + devices: !reset [] limits: memory: 16G cpus: '8.0' diff --git a/pmoves/docker-compose.yml b/pmoves/docker-compose.yml index dc9cf29a7d..84fed39d06 100644 --- a/pmoves/docker-compose.yml +++ b/pmoves/docker-compose.yml @@ -2875,6 +2875,9 @@ services: - A0_SET_browser_model_name=tensorzero::function_name::${AGENT_ZERO_TZ_FUNCTION:-agent_zero} - A0_SET_browser_model_api_base=http://tensorzero-gateway:3000/openai/v1 - A0_SET_a2a_server_enabled=true + # Expose Agent Zero's MCP SSE server so external clients (Crush, Archon, + # other nodes) can connect via standard MCP transport at /t-{token}/sse. + - A0_SET_mcp_server_enabled=true # A0_SET_mcp_server_token: soft default (CANONICAL_NAMES.md §5). The previous # ${MCP_SERVER_TOKEN:?...} hard-require made *every* compose invocation on the # shared file fail interpolation on nodes without the var — blocking bring-up diff --git a/pmoves/docker/ultimate-tts-studio/Dockerfile b/pmoves/docker/ultimate-tts-studio/Dockerfile index 3dc3a8c4e9..02328e8a95 100644 --- a/pmoves/docker/ultimate-tts-studio/Dockerfile +++ b/pmoves/docker/ultimate-tts-studio/Dockerfile @@ -51,7 +51,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # Step 1: Conda packages first (pynini + portaudio + scipy/numpy from conda-forge) # CRITICAL: Install scipy/numpy from conda for proper ABI compatibility # scipy 1.11.x works with numpy 1.26.x; scipy 1.14+ requires numpy 2.x -RUN conda install -c conda-forge pynini==2.1.6 portaudio scipy=1.11.4 numpy=1.26.4 -y && \ +RUN conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main 2>/dev/null; \ + conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r 2>/dev/null; \ + conda install --override-channels -c conda-forge pynini==2.1.6 portaudio scipy=1.11.4 numpy=1.26.4 -y && \ conda clean -afy # Step 2: Clone from PMOVES fork (same requirements.txt as SUP3RMASS1VE) @@ -88,6 +90,12 @@ RUN echo "onnx==1.16.0" > /tmp/constraints.txt && \ RUN pip install -c /tmp/constraints.txt -r requirements.txt || \ pip install --use-deprecated=legacy-resolver -c /tmp/constraints.txt -r requirements.txt +# Step 5c.1: Install supplementary PMOVES requirements (einops, omegaconf, etc.) +# These are TTS engine dependencies not included in the upstream repo's requirements.txt +COPY requirements-ultimate-tts.txt /tmp/requirements-ultimate-tts.txt +RUN pip install -c /tmp/constraints.txt -r /tmp/requirements-ultimate-tts.txt || \ + echo "WARN: Some supplementary deps failed (non-fatal — engines will lazy-load)" + # Step 5d: Skipped deepspeed compilation (removing it later to avoid runtime crashes) @@ -166,6 +174,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libsox3 \ sox \ curl \ + ca-certificates \ # CUDA runtime libraries for ONNX GPU provider cuda-nvrtc-12-4 \ && rm -rf /var/lib/apt/lists/* \ diff --git a/pmoves/docker/ultimate-tts-studio/Dockerfile.rocm b/pmoves/docker/ultimate-tts-studio/Dockerfile.rocm new file mode 100644 index 0000000000..95984359de --- /dev/null +++ b/pmoves/docker/ultimate-tts-studio/Dockerfile.rocm @@ -0,0 +1,180 @@ +# PMOVES.AI Ultimate-TTS-Studio — ROCm (AMD GPU) variant +# Multi-engine TTS for AMD ROCm nodes (RDNA3/RDNA4: gfx1100, gfx1201) +# +# Uses the same pytorch/pytorch conda base as the CUDA Dockerfile to get +# Python 3.11 + conda + pynini, then replaces CUDA PyTorch with ROCm PyTorch +# via pip. This avoids conda solver conflicts with pynini on Ubuntu. +# +# Build: +# docker build -f docker/ultimate-tts-studio/Dockerfile.rocm \ +# -t ghcr.io/powerfulmoves/pmoves-ultimate-tts-studio:rocm-latest \ +# --build-arg HSA_OVERRIDE_GFX_VERSION=12.0.1 \ +# ./docker/ultimate-tts-studio/ +# +# Usage: +# make -C pmoves up-voice-amd + +# ── Stage 1: Builder ────────────────────────────────────────────────────────── +# Same base as CUDA Dockerfile — provides conda + Python 3.11 + pynini +FROM pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime@sha256:c8268a92a69bd500f8be0e665b2630ee006dadaf7bfbc24249141b15ff622755 AS builder + +ENV DEBIAN_FRONTEND=noninteractive \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PIP_NO_CACHE_DIR=1 + +WORKDIR /build + +# System deps for audio + build utilities +RUN apt-get update && apt-get install -y --no-install-recommends \ + ffmpeg \ + espeak-ng \ + libespeak-ng1 \ + libespeak-ng-dev \ + libsndfile1 \ + libportaudio2 \ + portaudio19-dev \ + libaio-dev \ + libsox-dev \ + sox \ + git \ + curl \ + build-essential \ + cmake \ + protobuf-compiler \ + libprotobuf-dev \ + && rm -rf /var/lib/apt/lists/* + +# Conda packages (pynini + scipy/numpy ABI compat — same as CUDA Dockerfile) +RUN conda install -c conda-forge pynini==2.1.6 portaudio scipy=1.11.4 numpy=1.26.4 -y && \ + conda clean -afy + +# Replace CUDA PyTorch with ROCm PyTorch +# The base image ships torch+cu124; we uninstall and reinstall the ROCm wheel +RUN pip uninstall -y torch torchaudio torchvision 2>/dev/null || true && \ + pip install --pre torch torchaudio torchvision \ + --index-url https://download.pytorch.org/whl/nightly/rocm6.3 + +# Clone from PMOVES fork +RUN git clone --depth 1 https://github.com/POWERFULMOVES/PMOVES-Ultimate-TTS-Studio.git app + +WORKDIR /build/app + +# Install gradio and devicetorch first +RUN pip install --upgrade pip && \ + pip install gradio devicetorch + +# Pin huggingface-hub <1.0 before requirements +RUN pip install "huggingface-hub>=0.25.0,<1.0" + +# Pre-install packages that fail to build from source +RUN pip install \ + onnx==1.16.0 \ + s3tokenizer>=0.1.6 + +# Constraint file (same as CUDA Dockerfile) +RUN echo "onnx==1.16.0" > /tmp/constraints.txt && \ + echo "scipy==1.11.4" >> /tmp/constraints.txt && \ + echo "numpy==1.26.4" >> /tmp/constraints.txt && \ + echo "pydantic<2.12" >> /tmp/constraints.txt + +# Install upstream requirements.txt +RUN pip install -c /tmp/constraints.txt -r requirements.txt || \ + pip install --use-deprecated=legacy-resolver -c /tmp/constraints.txt -r requirements.txt + +# Install supplementary PMOVES requirements (einops, omegaconf, etc.) +COPY requirements-ultimate-tts.txt /tmp/requirements-ultimate-tts.txt +RUN pip install -c /tmp/constraints.txt -r /tmp/requirements-ultimate-tts.txt || \ + echo "WARN: Some supplementary deps failed (non-fatal)" + +# WeTextProcessing without deps +RUN pip install WeTextProcessing --no-deps || true + +# Fix phonemizer-fork +RUN pip uninstall -y phonemizer 2>/dev/null || true && \ + pip install phonemizer-fork + +# onnxruntime (CPU on ROCm — GPU provider needs ROCm-specific build) +RUN pip install -c /tmp/constraints.txt --upgrade --force-reinstall --no-deps --no-cache-dir onnxruntime==1.22.0 || \ + pip install -c /tmp/constraints.txt onnxruntime==1.22.0 + +# voxcpm and openai-whisper without deps +RUN pip install -c /tmp/constraints.txt voxcpm openai-whisper --no-deps || true + +# NOTE: Triton is CUDA-only — skipped on ROCm. PyTorch uses native attention. + +# MCP for Gradio MCP server support +RUN pip install -c /tmp/constraints.txt mcp + +# Download spacy model +RUN python -m spacy download en_core_web_sm || true + +# Final numpy/scipy fix +RUN pip uninstall -y numpy deepspeed 2>/dev/null || true && \ + rm -rf /opt/conda/lib/python3.11/site-packages/numpy* && \ + pip install --force-reinstall "numpy==1.26.4" "scipy==1.11.4" && \ + pip install --upgrade numba llvmlite && \ + rm -rf /opt/conda/lib/python3.11/site-packages/numpy/_core + +# Pre-download model checkpoint +RUN huggingface-cli download cocktailpeanut/oa --local-dir ./checkpoints/openaudio-s1-mini --quiet || \ + echo "Model download will happen at runtime" + +# ── Stage 2: Runtime ────────────────────────────────────────────────────────── +# Use Ubuntu 22.04 (no CUDA runtime needed — ROCm PyTorch ships its own libs) +FROM ubuntu:22.04 AS runtime + +ENV DEBIAN_FRONTEND=noninteractive \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + GRADIO_SERVER_NAME=0.0.0.0 \ + GRADIO_SERVER_PORT=7861 \ + GRADIO_MCP_SERVER=false \ + HF_HOME=/data/hf \ + HUGGINGFACE_HUB_CACHE=/data/hf \ + XDG_CACHE_HOME=/data/cache \ + HSA_OVERRIDE_GFX_VERSION=12.0.1 \ + PYTORCH_HIP_ALLOC_CONF=garbage_collection_threshold:0.6,max_split_size_mb:512 \ + LD_LIBRARY_PATH="/opt/conda/lib:/opt/rocm/lib:${LD_LIBRARY_PATH}" \ + PATH="/opt/conda/bin:${PATH}" \ + DISABLE_NVIDIA=true + +# Runtime system deps +RUN apt-get update && apt-get install -y --no-install-recommends \ + ffmpeg \ + espeak-ng \ + libespeak-ng1 \ + libsndfile1 \ + libportaudio2 \ + libaio1 \ + libsox3 \ + sox \ + curl \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean + +# Create non-root user +RUN groupadd -g 65532 pmoves && \ + useradd -u 65532 -g pmoves -d /app -s /bin/bash pmoves + +# Copy Python environment from builder +COPY --from=builder /opt/conda /opt/conda + +# Copy application code +COPY --from=builder /build/app /app + +RUN mkdir -p /app/outputs /app/models /data/hf /data/cache && \ + chown -R pmoves:pmoves /app /data + +WORKDIR /app + +USER pmoves + +EXPOSE 7861 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=180s --retries=3 \ + CMD curl -f http://localhost:7861/gradio_api/info || exit 1 + +CMD ["python", "launch.py"] diff --git a/pmoves/scripts/crush-env.sh b/pmoves/scripts/crush-env.sh index 371332ca77..cc17fb36fc 100644 --- a/pmoves/scripts/crush-env.sh +++ b/pmoves/scripts/crush-env.sh @@ -63,7 +63,8 @@ for var in \ HF_TOKEN \ OLLAMA_BASE_URL \ TAILSCALE_API_KEY \ - TAILSCALE_TAILNET; do + TAILSCALE_TAILNET \ + AGENT_ZERO_MCP_TOKEN; do if [ -n "${ENV_MAP[$var]:-}" ] && [ -z "${!var:-}" ]; then export "$var"="${ENV_MAP[$var]}" fi diff --git a/pmoves/tools/crush_configurator.py b/pmoves/tools/crush_configurator.py index 3b5db0624c..1e4fd9eeb1 100644 --- a/pmoves/tools/crush_configurator.py +++ b/pmoves/tools/crush_configurator.py @@ -307,10 +307,11 @@ def missing_envs(self, env_cache: Dict[Path, Dict[str, str]]) -> List[str]: MCPSpec( key="agent-zero", config={ - "type": "http", - "url": "http://${TS_Z890}:8080/mcp", + "type": "sse", + "url": "http://localhost:8081/mcp/t-${AGENT_ZERO_MCP_TOKEN}/sse", "timeout": 30, }, + required_env="AGENT_ZERO_MCP_TOKEN", ), MCPSpec( key="pmoves-nats-fleet", @@ -437,6 +438,15 @@ def missing_envs(self, env_cache: Dict[Path, Dict[str, str]]) -> List[str]: required_commands=["docker"], required_env="HOSTINGER_API_TOKEN", ), + MCPSpec( + key="archon", + config={ + "type": "http", + "url": "http://localhost:8051", + "timeout": 30, + "disabled": True, + }, + ), ] @@ -639,7 +649,13 @@ def build_config() -> Tuple[Dict[str, object], Dict[str, ProviderSpec]]: config["url"] = "http://localhost:8105/mcp/sse" elif spec.key == "agent-zero": config["url"] = "http://localhost:8093/mcp" - disabled = False + # Normalize agent-zero to SSE with token auth on every node (Spark uses :8093, + # others use :8081). Only applies if the URL hasn't already been set to SSE. + if spec.key == "agent-zero" and "/t-" not in str(config.get("url", "")): + port = "8093" if node == "spark" else "8081" + config["url"] = f"http://localhost:{port}/mcp/t-${{AGENT_ZERO_MCP_TOKEN}}/sse" + config["type"] = "sse" + disabled = config.pop("disabled", False) if spec.required_commands and not all(shutil.which(cmd) for cmd in spec.required_commands): disabled = True if spec.missing_envs(env_cache): diff --git a/pmoves/tools/mcp_config_generator.py b/pmoves/tools/mcp_config_generator.py index 445a6ef6cc..0d80fc818e 100644 --- a/pmoves/tools/mcp_config_generator.py +++ b/pmoves/tools/mcp_config_generator.py @@ -53,6 +53,7 @@ class ServerSpec: clients: Optional[List[str]] = None endpoint: Optional[str] = None endpoint_prefix: Optional[str] = None + disabled: bool = False def supports_client(self, client: str) -> bool: if self.clients is None: @@ -157,6 +158,7 @@ def _collect_servers(inventory: Dict[str, Any], client: str, endpoint: str) -> L clients=server.get("clients"), endpoint=server.get("endpoint"), endpoint_prefix=server.get("endpoint_prefix"), + disabled=server.get("disabled", False), ) if not spec.supports_client(client): continue @@ -213,6 +215,8 @@ def render_claude_kimi(specs: List[ServerSpec], context: Dict[str, str], **kw: A entry["timeout"] = spec.timeout else: continue + if spec.disabled: + entry["disabled"] = True servers[spec.key] = entry return {"mcpServers": servers} @@ -236,6 +240,8 @@ def render_kilocode(specs: List[ServerSpec], context: Dict[str, str], **kw: Any) entry["environment"] = _render_env(spec.env, context, **kw) else: continue + if spec.disabled: + entry["disabled"] = True mcp[spec.key] = entry permissions[f"{spec.key}_*"] = "allow" return {"mcp": mcp, "permission": permissions} @@ -245,7 +251,7 @@ def render_hermes(specs: List[ServerSpec], context: Dict[str, str], **kw: Any) - """Render Hermes Agent config.yaml mcp_servers block.""" servers: Dict[str, Any] = {} for spec in specs: - entry: Dict[str, Any] = {"enabled": True} + entry: Dict[str, Any] = {"enabled": not spec.disabled} if spec.transport in ("sse", "http"): entry["type"] = spec.transport entry["url"] = _expand(spec.url or "", context, **kw) @@ -284,10 +290,14 @@ def render_crush(specs: List[ServerSpec], context: Dict[str, str], **kw: Any) -> entry["type"] = "stdio" entry["command"] = command entry["args"] = _render_args(args, context, **kw) + if spec.env: + entry["env"] = _render_env(spec.env, context, **kw) else: continue if spec.timeout: entry["timeout"] = spec.timeout + if spec.disabled: + entry["disabled"] = True mcp[spec.key] = entry return {"mcp": mcp}