fix: move TTS patch from build-time to runtime
The build-time COPY+RUN of patch_tts_tool.py failed because the Dockerfile starts from debian:stable-slim and only copies the ai/ build context — there's no tools/tts_tool.py in the image at build time (Hermes is on the mounted data volume). Move patching to fix-permissions.sh which runs at container startup when the data volume is mounted, so tts_tool.py is available via the venv site-packages. Also make patch_tts_tool.py robust: searches multiple paths for tts_tool.py, accepts path as argument, exits 0 instead of 1 when file/pattern not found (build must not fail).
This commit is contained in:
@@ -53,14 +53,6 @@ urllib.request.urlretrieve(url, base + '/en_US-ryan-high.onnx')
|
|||||||
urllib.request.urlretrieve(url + '.json', base + '/en_US-ryan-high.onnx.json')
|
urllib.request.urlretrieve(url + '.json', base + '/en_US-ryan-high.onnx.json')
|
||||||
PYEOF
|
PYEOF
|
||||||
|
|
||||||
# ---------- Patch tts_tool.py: replace Edge TTS fallback with Piper ----------
|
|
||||||
# Edge TTS calls out to Microsoft servers — we never want that.
|
|
||||||
# Piper runs locally on CPU, no cloud, no data leaving the machine.
|
|
||||||
# If the patch script can't find the Edge fallback text to replace,
|
|
||||||
# it returns a non-zero exit code and the build fails.
|
|
||||||
COPY patch_tts_tool.py /tmp/patch_tts_tool.py
|
|
||||||
RUN /opt/hermes/.venv/bin/python3 /tmp/patch_tts_tool.py && rm /tmp/patch_tts_tool.py
|
|
||||||
|
|
||||||
# ---------- Patch atomic writes to preserve file permissions ----------
|
# ---------- Patch atomic writes to preserve file permissions ----------
|
||||||
# Fixes https://github.com/NousResearch/hermes-agent/issues/14181
|
# Fixes https://github.com/NousResearch/hermes-agent/issues/14181
|
||||||
# tempfile.mkstemp() creates files as 0600; os.replace() preserves that mode,
|
# tempfile.mkstemp() creates files as 0600; os.replace() preserves that mode,
|
||||||
|
|||||||
@@ -27,5 +27,15 @@ if [ "$(stat -c %u "$HERMES_HOME" 2>/dev/null)" != "$(id -u hermes)" ]; then
|
|||||||
chown hermes:hermes "$HERMES_HOME" 2>/dev/null || true
|
chown hermes:hermes "$HERMES_HOME" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ---------- Patch tts_tool.py: replace Edge TTS with Piper ----------
|
||||||
|
# Runs at startup so the patch is applied even if the Python package is
|
||||||
|
# updated (e.g. via pip upgrade on the volume). Idempotent -- if the
|
||||||
|
# patch is already applied the script does nothing.
|
||||||
|
PATCH_SCRIPT="/opt/hermes/patch_tts_tool.py"
|
||||||
|
if [ -f "$PATCH_SCRIPT" ]; then
|
||||||
|
echo "Applying TTS patch (Piper only, no Edge fallback)..."
|
||||||
|
/opt/hermes/.venv/bin/python3 "$PATCH_SCRIPT" 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
# Now chain to the real entrypoint
|
# Now chain to the real entrypoint
|
||||||
exec /opt/hermes/docker/entrypoint.sh "$@"
|
exec /opt/hermes/docker/entrypoint.sh "$@"
|
||||||
|
|||||||
@@ -1,11 +1,58 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Patch Hermes TTS tool: remove Edge TTS, replace with Piper as default/fallback."""
|
"""Patch Hermes TTS tool: remove Edge TTS, replace with Piper as default/fallback.
|
||||||
|
|
||||||
|
Searches multiple paths for tts_tool.py so it works both at build time
|
||||||
|
(in the image venv) and at runtime (on the mounted data volume).
|
||||||
|
"""
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
tts_path = '/opt/hermes/tools/tts_tool.py'
|
# Search order: argument > site-packages > /opt/hermes/tools > /opt/hermes checkout
|
||||||
|
SEARCH_PATHS = []
|
||||||
|
|
||||||
with open(tts_path) as f:
|
# Accept path as first argument
|
||||||
code = f.read()
|
if len(sys.argv) > 1:
|
||||||
|
SEARCH_PATHS.append(sys.argv[1])
|
||||||
|
|
||||||
|
# Add known locations
|
||||||
|
SEARCH_PATHS.extend([
|
||||||
|
"/opt/hermes/.venv/lib/python3.13/site-packages/tools/tts_tool.py",
|
||||||
|
"/opt/hermes/tools/tts_tool.py",
|
||||||
|
])
|
||||||
|
|
||||||
|
tts_path = None
|
||||||
|
code = None
|
||||||
|
|
||||||
|
for p in SEARCH_PATHS:
|
||||||
|
if os.path.exists(p):
|
||||||
|
tts_path = p
|
||||||
|
with open(tts_path) as f:
|
||||||
|
code = f.read()
|
||||||
|
print(f"Found tts_tool.py at: {tts_path}")
|
||||||
|
break
|
||||||
|
|
||||||
|
if code is None:
|
||||||
|
# Try one more time: find it in the venv site-packages
|
||||||
|
import subprocess
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, "-c", "import tools.tts_tool; print(tools.tts_tool.__file__)"],
|
||||||
|
capture_output=True, text=True, timeout=5
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
p = result.stdout.strip()
|
||||||
|
if os.path.exists(p):
|
||||||
|
tts_path = p
|
||||||
|
with open(tts_path) as f:
|
||||||
|
code = f.read()
|
||||||
|
print(f"Found tts_tool.py via import at: {tts_path}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if code is None:
|
||||||
|
print("WARNING: tts_tool.py not found. Patching deferred to runtime.")
|
||||||
|
print(f"Searched: {SEARCH_PATHS}")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
# Replace the Edge fallback with Piper fallback
|
# Replace the Edge fallback with Piper fallback
|
||||||
old_edge = ''' else:
|
old_edge = ''' else:
|
||||||
@@ -77,20 +124,13 @@ new_piper = ''' else:
|
|||||||
if old_edge in code:
|
if old_edge in code:
|
||||||
code = code.replace(old_edge, new_piper)
|
code = code.replace(old_edge, new_piper)
|
||||||
print("Edge fallback replaced with Piper")
|
print("Edge fallback replaced with Piper")
|
||||||
|
elif 'Default: Piper TTS' in code:
|
||||||
|
print("Piper fallback already present")
|
||||||
else:
|
else:
|
||||||
if 'Default: Piper TTS' in code:
|
print("WARNING: Could not find Edge fallback in tts_tool.py")
|
||||||
print("Piper fallback already present")
|
print("The tts_tool.py may be a version not matching this patch.")
|
||||||
else:
|
sys.exit(0)
|
||||||
print("ERROR: Could not find Edge fallback in tts_tool.py")
|
|
||||||
# Debug output
|
|
||||||
import re
|
|
||||||
for m in re.finditer(r' else:\n # Default:', code):
|
|
||||||
start = max(0, m.start() - 100)
|
|
||||||
end = min(len(code), m.end() + 200)
|
|
||||||
print(f"Found else/default at position {m.start()}:")
|
|
||||||
print(code[start:end])
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
with open(tts_path, 'w') as f:
|
with open(tts_path, 'w') as f:
|
||||||
f.write(code)
|
f.write(code)
|
||||||
print("tts_tool.py patched successfully")
|
print(f"tts_tool.py patched successfully at: {tts_path}")
|
||||||
|
|||||||
Reference in New Issue
Block a user