fixes for the service layer. Updated readme.
This commit is contained in:
@@ -215,18 +215,24 @@ docker tag guardian-newscast truenas.local:30095/guardian-newscast:latest
|
||||
docker push truenas.local:30095/guardian-newscast:latest
|
||||
```
|
||||
|
||||
### Deploying on TrueNAS
|
||||
#### Cross-platform builds
|
||||
|
||||
For Intel (x86_64):
|
||||
|
||||
```bash
|
||||
docker pull truenas.local:30095/guardian-newscast:latest
|
||||
docker run -d \
|
||||
--name guardian-newscast \
|
||||
--network host \
|
||||
-v /path/to/data:/app/data \
|
||||
-e OLLAMA_URL=http://127.0.0.1:11434/api/generate \
|
||||
-e PORT=30095 \
|
||||
--restart unless-stopped \
|
||||
truenas.local:30095/guardian-newscast:latest
|
||||
docker buildx build --platform linux/amd64 -t guardian-newscast:amd64 .
|
||||
```
|
||||
|
||||
For ARM (aarch64 / Apple Silicon):
|
||||
|
||||
```bash
|
||||
docker buildx build --platform linux/arm64 -t guardian-newscast:arm64 .
|
||||
```
|
||||
|
||||
To build and push both variants as a single multi-arch manifest:
|
||||
|
||||
```bash
|
||||
docker buildx build --platform linux/amd64,linux/arm64 -t truenas.local:30095/guardian-newscast:latest --push .
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
@@ -242,14 +248,10 @@ docker compose --profile full up -d
|
||||
### GPU Acceleration
|
||||
|
||||
The default Dockerfile uses `python:3.11-slim` (Debian), which has glibc — fully compatible with NVIDIA CUDA.
|
||||
|
||||
To run with GPU acceleration on TrueNAS:
|
||||
1. Install the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html).
|
||||
2. Build with CUDA-enabled torch:
|
||||
1. Build with CUDA-enabled torch:
|
||||
```bash
|
||||
docker build --target gpu -t guardian-newscast:gpu .
|
||||
```
|
||||
(Add a `FROM python:3.11-slim AS gpu` stage with `pip install torch --index-url https://download.pytorch.org/whl/cu121`)
|
||||
3. Run with `--gpus all`.
|
||||
4. Minimum **4 GB VRAM** recommended for Ollama + Kokoro in the same container.
|
||||
|
||||
4. Minimum **4 GB VRAM** recommended for Ollama + Kokoro in the same container.
|
||||
+13
@@ -37,8 +37,21 @@ from server.app import run_server as start_server
|
||||
logger.add("pipeline.log", rotation="5 MB", retention="7 days")
|
||||
|
||||
|
||||
def clear_json_data() -> None:
|
||||
"""Remove stale JSON files at the start of each pipeline cycle."""
|
||||
if not os.path.isdir(DATA_DIR):
|
||||
return
|
||||
for fname in ("output.json", "scripts.json"):
|
||||
fpath = os.path.join(DATA_DIR, fname)
|
||||
if os.path.exists(fpath):
|
||||
os.remove(fpath)
|
||||
logger.info(f"Cleared {fpath}")
|
||||
logger.info("Cleared JSON data at cycle start")
|
||||
|
||||
|
||||
async def run_pipeline() -> bool:
|
||||
"""Run all three pipeline stages sequentially. Returns True on success."""
|
||||
clear_json_data()
|
||||
logger.info("=" * 60)
|
||||
logger.info("Stage 1: Scraping Guardian articles...")
|
||||
try:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""HTTP server that serves the Guardian Daily Newscast MP3 index page and audio files."""
|
||||
|
||||
import glob
|
||||
import os
|
||||
import json
|
||||
import threading
|
||||
@@ -65,6 +66,41 @@ class NewscastHandler(SimpleHTTPRequestHandler):
|
||||
"""Suppress per-request logs to avoid noisy output."""
|
||||
pass
|
||||
|
||||
def do_POST(self):
|
||||
if self.path == "/clear":
|
||||
self.clear_audio()
|
||||
else:
|
||||
self.send_error(405, "Method Not Allowed")
|
||||
|
||||
def clear_audio(self):
|
||||
"""Remove all MP3 files from the audio directory."""
|
||||
if ".." in self.path:
|
||||
self.send_error(403, "Forbidden")
|
||||
return
|
||||
audio_dir = os.path.join(self.data_dir, "audio")
|
||||
if not os.path.isdir(audio_dir):
|
||||
self._json_response({"status": "ok", "cleared": 0})
|
||||
return
|
||||
mp3_paths = glob.glob(os.path.join(audio_dir, "*.mp3"))
|
||||
count = 0
|
||||
for path in mp3_paths:
|
||||
try:
|
||||
os.remove(path)
|
||||
count += 1
|
||||
except OSError:
|
||||
logger.exception(f"Failed to remove {path}")
|
||||
logger.info(f"Cleared {count} audio files.")
|
||||
self._json_response({"status": "ok", "cleared": count})
|
||||
|
||||
def _json_response(self, data):
|
||||
"""Send a JSON response."""
|
||||
body = json.dumps(data).encode("utf-8")
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
|
||||
def _generate_index(mp3_files, scripts_map):
|
||||
"""Generate the HTML index page for the MP3 newscast."""
|
||||
@@ -107,9 +143,12 @@ def _generate_index(mp3_files, scripts_map):
|
||||
.empty {{ text-align: center; color: #8b949e; padding: 3rem 0; }}
|
||||
.refresh-btn {{ background: #21262d; color: #c9d1d9; border: 1px solid #30363d; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; font-size: 0.85rem; margin-bottom: 1.5rem; }}
|
||||
.refresh-btn:hover {{ background: #30363d; }}
|
||||
.clear-btn {{ background: #490202; color: #f85149; border: 1px solid #f85149; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; font-size: 0.85rem; margin-bottom: 1.5rem; }}
|
||||
.clear-btn:hover {{ background: #da3633; color: #fff; }}
|
||||
</style>
|
||||
<script>
|
||||
function refresh() {{ location.reload(); }}
|
||||
function clearAudio() {{ fetch('/clear', {{ method: 'POST' }}).then(() => location.reload()); }}
|
||||
setInterval(refresh, 300000);
|
||||
</script>
|
||||
</head>
|
||||
@@ -118,6 +157,7 @@ def _generate_index(mp3_files, scripts_map):
|
||||
<h1>Guardian Daily Newscast</h1>
|
||||
<p class="subtitle">Daily US news — spoken by AI</p>
|
||||
<button class="refresh-btn" onclick="refresh()">Refresh</button>
|
||||
<button class="clear-btn" onclick="clearAudio()">Clear Audio</button>
|
||||
{audio_entries}
|
||||
</div>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user