Architecture
Static description of the AX-NEWS technical stack — no live metrics.
Request Flow
Browser / Client
→ Cloudflare (CDN + DDoS)
→ HAProxy (TLS termination, ax-lab05:443)
→ Nginx (vhost, static assets, rate limiting)
→ FastAPI / uvicorn (port 8000, 2 workers)
→ PostgreSQL 16 (local socket)
Static assets (CSS, JS, images) are served directly by Nginx from /opt/ax-news/frontend/ without hitting FastAPI. HTML pages go through FastAPI for per-request CSP nonce injection via Jinja2 templates.
Stack
| Layer | Technology | Role |
|---|---|---|
| Edge / CDN | Cloudflare | DDoS protection, global CDN, Proxy DNS |
| Load balancer | HAProxy | TLS termination, HTTP→HTTPS redirect |
| Web server | Nginx | Static files, reverse proxy, rate limiting (login: 5/min, API: 60/min, audio: 10/min) |
| Application | FastAPI + uvicorn | REST API, Jinja2 HTML rendering, background tasks |
| Database | PostgreSQL 16 | Articles, users, sessions, NLP metadata |
| RSS ingestion | FreshRSS (PHP 8.3) | 200+ RSS feeds aggregated at /news |
| Translation | Argos Translate | CJK/Korean titles translated to English — runs as a standalone systemd service to avoid OOM in uvicorn |
| Audio summaries | Ollama (qwen2.5:3b) + Piper TTS | French oral summaries generated on-demand — Ollama runs on bare-metal host (.37), Piper synthesises WAV via subprocess (not inline in uvicorn) |
| Enrichment | Keyword analysis (Python) | Entity extraction, sentiment classification, country tagging — runs at sync time |
| Hosting | ax-lab05 VM (~3.8 GB RAM) | Single Linux VM, all services via systemd |
Audio Summary Pipeline
User clicks "Listen"
→ GET /api/articles/{id}/audio
→ FastAPI checks audio_status in DB
If 'none'/'failed': locks row → launches asyncio background task
Task: Ollama (.37) → French summary (~120 words) → Piper subprocess → WAV file
Frontend polls every 2s until status = 'done' → Audio plays in browser
If 'none'/'failed': locks row → launches asyncio background task
Task: Ollama (.37) → French summary (~120 words) → Piper subprocess → WAV file
Frontend polls every 2s until status = 'done' → Audio plays in browser
- Piper TTS voice: fr_FR-siwis-medium (MIT license, CPU-only, ~61 MB model)
- WAV files stored in
/opt/ax-news/audio/, served statically by Nginx - Audio files purged automatically with their article after 30 days
- Rate limit on audio generation endpoint: 10 req/min per IP
Data Flow — Article Lifecycle
- Ingestion: FreshRSS syncs feeds every 15 min → FastAPI upserts into
articlestable - Enrichment: Keyword analysis runs at sync time — extracts entities, sentiment, country code
- Translation: CJK/Korean titles translated by Argos standalone worker (every 10 min)
- Deduplication: Dedup worker runs every 6 hours to remove near-identical articles
- Image prefetch: OG image fetched nightly for articles missing thumbnails
- Purge: Articles older than 30 days deleted every Monday 3 AM, including audio files
Security Notes
This page is intentionally static and descriptive. It does not expose live system metrics (CPU, RAM, disk, hostname). A previous
/api/metrics endpoint was removed because it disclosed server internals without authentication.
- CSP with per-request nonce injected by FastAPI — no
unsafe-evalin practice - No cookies except session (httpOnly, SameSite=Lax)
- No localStorage / sessionStorage (privacy policy — zero client-side persistence)
- Public feed is unauthenticated read-only — write operations and admin actions require session
- Rate limiting at Nginx level for login, API, and audio endpoints
Source Code
The full source is available on GitHub: github.com/plkarin/ax-news (MIT license).