fix: move media server to 8093 — YOURLS/Apache owns 8092 on xNAS
Rebuild & Deploy / rebuild (push) Successful in 1m0s
Rebuild & Deploy / rebuild (push) Successful in 1m0s
The Unraid deployment's /media/api requests were answered by an Apache/YOURLS stack squatting on 8092, surfacing as UI 503s; the CI healthcheck (curl without -f) counted that 503 as healthy, so run #15 deployed green against a broken media server. - media server port 8092 -> 8093 everywhere (server, MCP, agent, nginx, compose, CI, .env.example, docs, tests). - healthchecks use curl -f in compose and rebuild.yml so HTTP error statuses fail deployment. - tests default to the Unraid deployment (VOICE_WEB_URL= https://192.168.86.2:9178); token goes through nginx (loopback :8091 is unreachable off-host); bind-mount-coupled tests skip on remote targets. AGENTS.md documents the convention and both failure modes.
This commit is contained in:
1 parent
d7c385301e
commit
ce8fd1e046
11 files changed
+72
-28
No files matched your search
+1
-1
@@ -34,7 +34,7 @@ VOICE_DEFAULT_USER=shane
|
|||||||
# media/files/ transcoded mp3 tracks
|
# media/files/ transcoded mp3 tracks
|
||||||
# media/thumbs/ cover art
|
# media/thumbs/ cover art
|
||||||
# media/jobs/ download job status
|
# media/jobs/ download job status
|
||||||
# MEDIA_SERVER_PORT=8092
|
# MEDIA_SERVER_PORT=8093 (8092 is taken by YOURLS/Apache on xNAS)
|
||||||
#
|
#
|
||||||
# Optional: export cookies.txt from a browser logged into YouTube and drop it
|
# Optional: export cookies.txt from a browser logged into YouTube and drop it
|
||||||
# in ./media/cookies.txt to avoid "confirm you're not a bot" download blocks.
|
# in ./media/cookies.txt to avoid "confirm you're not a bot" download blocks.
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
--name voice \
|
--name voice \
|
||||||
--network host \
|
--network host \
|
||||||
--restart unless-stopped \
|
--restart unless-stopped \
|
||||||
--health-cmd='curl -sk https://localhost:${WEB_PORT:-9178}/ -o /dev/null && curl -s http://localhost:7880/ -o /dev/null && curl -s http://localhost:${MEDIA_SERVER_PORT:-8092}/media/api/healthz -o /dev/null' \
|
--health-cmd='curl -sfk https://localhost:${WEB_PORT:-9178}/ -o /dev/null && curl -sf http://localhost:7880/ -o /dev/null && curl -sf http://localhost:${MEDIA_SERVER_PORT:-8093}/media/api/healthz -o /dev/null' \
|
||||||
--health-interval=15s \
|
--health-interval=15s \
|
||||||
--health-timeout=5s \
|
--health-timeout=5s \
|
||||||
--health-retries=3 \
|
--health-retries=3 \
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
-e VOICE_USERS_FILE=/etc/hope/users.json \
|
-e VOICE_USERS_FILE=/etc/hope/users.json \
|
||||||
-e VOICE_DEFAULT_USER=shane \
|
-e VOICE_DEFAULT_USER=shane \
|
||||||
-e MEDIA_DIR=/data/media \
|
-e MEDIA_DIR=/data/media \
|
||||||
-e MEDIA_SERVER_PORT=8092 \
|
-e MEDIA_SERVER_PORT=8093 \
|
||||||
-e YTDLP_COOKIES=/data/media/cookies.txt \
|
-e YTDLP_COOKIES=/data/media/cookies.txt \
|
||||||
hope-voice-api-voice:latest
|
hope-voice-api-voice:latest
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ One Docker container runs four processes via supervisord:
|
|||||||
1. **LiveKit server** — open-source WebRTC media transport (port 7880 TCP, 7882 UDP muxed media)
|
1. **LiveKit server** — open-source WebRTC media transport (port 7880 TCP, 7882 UDP muxed media)
|
||||||
2. **Voice agent** — Python LiveKit Agents pipeline: Azure STT → LLM (OpenWebUI) → Azure TTS
|
2. **Voice agent** — Python LiveKit Agents pipeline: Azure STT → LLM (OpenWebUI) → Azure TTS
|
||||||
3. **Web frontend** — static HTML served by nginx (port 9178 by default)
|
3. **Web frontend** — static HTML served by nginx (port 9178 by default)
|
||||||
4. **Media server** — FastAPI music library/player coordinator (port 8092 internal, proxied at /media/api/)
|
4. **Media server** — FastAPI music library/player coordinator (port 8093 internal, proxied at /media/api/)
|
||||||
|
|
||||||
```
|
```
|
||||||
Browser ──WebRTC──► LiveKit Server ──audio──► Agent
|
Browser ──WebRTC──► LiveKit Server ──audio──► Agent
|
||||||
@@ -44,7 +44,7 @@ LAN access requires firewall rules: `9178/tcp` (UI + signaling), `7882/udp`
|
|||||||
| 7880 | TCP | LiveKit HTTP/WS | internal only (proxied via nginx at /livekit/) |
|
| 7880 | TCP | LiveKit HTTP/WS | internal only (proxied via nginx at /livekit/) |
|
||||||
| 7881 | TCP | LiveKit RTC media (TCP fallback) | LAN |
|
| 7881 | TCP | LiveKit RTC media (TCP fallback) | LAN |
|
||||||
| 7882 | UDP | LiveKit RTC media (muxed) | LAN |
|
| 7882 | UDP | LiveKit RTC media (muxed) | LAN |
|
||||||
| 8092 | TCP | Media server (library, player state, stream proxy) | internal only (proxied via nginx at /media/api/) |
|
| 8093 | TCP | Media server (library, player state, stream proxy) | internal only (proxied via nginx at /media/api/) |
|
||||||
| 9178 | TCP | Web frontend (HTTPS) | LAN |
|
| 9178 | TCP | Web frontend (HTTPS) | LAN |
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
@@ -107,10 +107,23 @@ The LLM is instructed to:
|
|||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
|
**Tests run against the Unraid deployment by default** (`https://192.168.86.2:9178`,
|
||||||
|
via `VOICE_WEB_URL` in tests/conftest.py) — that is the instance users hit
|
||||||
|
and the one CI (rebuild.yml) deploys on every push to master. The local dev
|
||||||
|
compose instance is for development only; set `VOICE_WEB_URL=https://localhost:9178`
|
||||||
|
to target it instead.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Verify the container is running
|
# Verify the container is running
|
||||||
docker compose ps
|
docker compose ps
|
||||||
|
|
||||||
|
# Run the suite against the Unraid deployment (default)
|
||||||
|
python -m pytest tests/ -v -m "not slow"
|
||||||
|
|
||||||
|
# Real end-to-end YouTube Music download (network-heavy, gated)
|
||||||
|
HOPE_RUN_YTDLP_DOWNLOAD=1 python -m pytest tests/test_media.py -m slow
|
||||||
|
```
|
||||||
|
|
||||||
# Check all logs (single service: "voice")
|
# Check all logs (single service: "voice")
|
||||||
docker compose logs -f voice
|
docker compose logs -f voice
|
||||||
|
|
||||||
@@ -179,6 +192,9 @@ curl -s "https://eastus.tts.speech.microsoft.com/cognitiveservices/v1" \
|
|||||||
- **External stream URLs are proxied, never played directly.** An `http://` radio URL would be blocked as mixed content on the HTTPS UI, so `GET /media/api/stream/{id}` re-serves the upstream over HTTPS with Range passthrough. Downloaded tracks on that endpoint just redirect to `/media/files/`.
|
- **External stream URLs are proxied, never played directly.** An `http://` radio URL would be blocked as mixed content on the HTTPS UI, so `GET /media/api/stream/{id}` re-serves the upstream over HTTPS with Range passthrough. Downloaded tracks on that endpoint just redirect to `/media/files/`.
|
||||||
- **Player state is per-room desired state, reconciled by the browser.** The media server never streams to anything; it records what SHOULD play per LiveKit room and the browser polls and reports back. Track ids for YouTube tracks are the video ids (dedupe by video_id); stream tracks get `s<uuid>`.
|
- **Player state is per-room desired state, reconciled by the browser.** The media server never streams to anything; it records what SHOULD play per LiveKit room and the browser polls and reports back. Track ids for YouTube tracks are the video ids (dedupe by video_id); stream tracks get `s<uuid>`.
|
||||||
- **Anonymous yt-dlp first, cookies when flaky.** Search (`ytsearch`) and downloads work anonymously from a residential IP; if YouTube starts bot-blocking, drop a cookies.txt into `./media/cookies.txt` and set `YTDLP_COOKIES=/data/media/cookies.txt`.
|
- **Anonymous yt-dlp first, cookies when flaky.** Search (`ytsearch`) and downloads work anonymously from a residential IP; if YouTube starts bot-blocking, drop a cookies.txt into `./media/cookies.txt` and set `YTDLP_COOKIES=/data/media/cookies.txt`.
|
||||||
|
- **Never hard-require container env in supervisord.** `%(ENV_X)s` expansion aborts the whole supervisord config when the var is missing, taking every program down (this silently killed the run-151 deploy). Programs with safe in-code defaults (media server) must inherit the container env instead of listing it.
|
||||||
|
- **Healthchecks must use `curl -f`.** Without it, any HTTP response — including a 503 from a squatter — counts as healthy. Run #15 deployed "healthy" against a broken media port this way.
|
||||||
|
- **Port 8092 is taken on xNAS.** An Apache/YOURLS stack occupies 8092 on the Unraid host; the media server uses 8093. Before picking a new internal port, probe the Unraid deployment host, not just the dev box.
|
||||||
|
|
||||||
## Git
|
## Git
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -688,7 +688,7 @@ def build_mcp_toolsets(user_profile: dict, task_dir: str) -> list[mcp.MCPToolset
|
|||||||
|
|
||||||
# Media tools talk to the local media server; the room name scopes player
|
# Media tools talk to the local media server; the room name scopes player
|
||||||
# state so concurrent sessions don't fight over one virtual player.
|
# state so concurrent sessions don't fight over one virtual player.
|
||||||
media_server_url = os.environ.get("MEDIA_SERVER_URL", "http://127.0.0.1:8092")
|
media_server_url = os.environ.get("MEDIA_SERVER_URL", "http://127.0.0.1:8093")
|
||||||
media_mcp_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "media_mcp.py")
|
media_mcp_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "media_mcp.py")
|
||||||
toolsets.append(
|
toolsets.append(
|
||||||
mcp.MCPToolset(
|
mcp.MCPToolset(
|
||||||
|
|||||||
+2
-2
@@ -7,7 +7,7 @@ server and reports playback back, so voice controls and the web panel stay
|
|||||||
in sync.
|
in sync.
|
||||||
|
|
||||||
Per-session environment (set by the agent when spawning this MCP):
|
Per-session environment (set by the agent when spawning this MCP):
|
||||||
- MEDIA_SERVER_URL e.g. http://127.0.0.1:8092
|
- MEDIA_SERVER_URL e.g. http://127.0.0.1:8093
|
||||||
- MEDIA_ROOM LiveKit room name this session controls
|
- MEDIA_ROOM LiveKit room name this session controls
|
||||||
|
|
||||||
The "stop listening while music plays" rule lives in the agent itself: the
|
The "stop listening while music plays" rule lives in the agent itself: the
|
||||||
@@ -22,7 +22,7 @@ import os
|
|||||||
import httpx
|
import httpx
|
||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
|
||||||
MEDIA_SERVER_URL = os.environ.get("MEDIA_SERVER_URL", "http://127.0.0.1:8092").rstrip("/")
|
MEDIA_SERVER_URL = os.environ.get("MEDIA_SERVER_URL", "http://127.0.0.1:8093").rstrip("/")
|
||||||
MEDIA_ROOM = os.environ.get("MEDIA_ROOM", "default")
|
MEDIA_ROOM = os.environ.get("MEDIA_ROOM", "default")
|
||||||
|
|
||||||
mcp = FastMCP("media")
|
mcp = FastMCP("media")
|
||||||
|
|||||||
+2
-2
@@ -19,7 +19,7 @@ services:
|
|||||||
VOICE_USERS_FILE: "/etc/hope/users.json"
|
VOICE_USERS_FILE: "/etc/hope/users.json"
|
||||||
VOICE_DEFAULT_USER: "${VOICE_DEFAULT_USER:-shane}"
|
VOICE_DEFAULT_USER: "${VOICE_DEFAULT_USER:-shane}"
|
||||||
MEDIA_DIR: "/data/media"
|
MEDIA_DIR: "/data/media"
|
||||||
MEDIA_SERVER_PORT: "${MEDIA_SERVER_PORT:-8092}"
|
MEDIA_SERVER_PORT: "${MEDIA_SERVER_PORT:-8093}"
|
||||||
YTDLP_COOKIES: "${YTDLP_COOKIES:-/data/media/cookies.txt}"
|
YTDLP_COOKIES: "${YTDLP_COOKIES:-/data/media/cookies.txt}"
|
||||||
volumes:
|
volumes:
|
||||||
- ./livekit.yaml:/etc/livekit.yaml:ro
|
- ./livekit.yaml:/etc/livekit.yaml:ro
|
||||||
@@ -27,7 +27,7 @@ services:
|
|||||||
- ./skills:/skills
|
- ./skills:/skills
|
||||||
- ./media:/data/media
|
- ./media:/data/media
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "curl -sk https://localhost:$${WEB_PORT:-9178}/ -o /dev/null && curl -s http://localhost:7880/ -o /dev/null && curl -s http://localhost:$${MEDIA_SERVER_PORT:-8092}/media/api/healthz -o /dev/null"]
|
test: ["CMD-SHELL", "curl -sfk https://localhost:$${WEB_PORT:-9178}/ -o /dev/null && curl -sf http://localhost:7880/ -o /dev/null && curl -sf http://localhost:$${MEDIA_SERVER_PORT:-8093}/media/api/healthz -o /dev/null"]
|
||||||
interval: 15s
|
interval: 15s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ server {
|
|||||||
# external live-stream URLs (http:// sources would be blocked as mixed
|
# external live-stream URLs (http:// sources would be blocked as mixed
|
||||||
# content in the browser, so they are re-served over HTTPS from here).
|
# content in the browser, so they are re-served over HTTPS from here).
|
||||||
location /media/api/ {
|
location /media/api/ {
|
||||||
proxy_pass http://127.0.0.1:8092/media/api/;
|
proxy_pass http://127.0.0.1:8093/media/api/;
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_read_timeout 3600s;
|
proxy_read_timeout 3600s;
|
||||||
proxy_send_timeout 3600s;
|
proxy_send_timeout 3600s;
|
||||||
|
|||||||
+21
-8
@@ -1,23 +1,35 @@
|
|||||||
"""Shared fixtures for the Hope voice-assistant test suite.
|
"""Shared fixtures for the Hope voice-assistant test suite.
|
||||||
|
|
||||||
Tests run on the HOST machine against the live "voice" container:
|
Tests run against the **Unraid deployment** by default — that is the
|
||||||
- HTTPS web UI -> https://localhost:9178 (self-signed cert, verify=False)
|
instance users hit and the one CI (rebuild.yml) keeps up to date:
|
||||||
- Token server -> http://127.0.0.1:8091 (host network mode)
|
- HTTPS web UI -> https://192.168.86.2:9178 (self-signed cert, verify=False)
|
||||||
|
- Token server -> via the same nginx (https://.../token; the loopback
|
||||||
|
:8091 socket is only reachable on the deployment host itself)
|
||||||
- LLM API -> LLM_BASE_URL (default http://192.168.86.2:3005/api)
|
- LLM API -> LLM_BASE_URL (default http://192.168.86.2:3005/api)
|
||||||
- In-container -> docker exec voice ...
|
- In-container -> docker exec voice ... (targets the LOCAL dev container
|
||||||
|
unless a docker context/DOCKER_HOST for the Unraid host is configured)
|
||||||
|
|
||||||
|
Override VOICE_WEB_URL to test a different instance (e.g. the local dev
|
||||||
|
compose deployment at https://localhost:9178).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
CONTAINER = "voice"
|
CONTAINER = "voice"
|
||||||
WEB_BASE_URL = os.environ.get("VOICE_WEB_URL", "https://localhost:9178")
|
WEB_BASE_URL = os.environ.get("VOICE_WEB_URL", "https://192.168.86.2:9178").rstrip("/")
|
||||||
TOKEN_URL = os.environ.get("VOICE_TOKEN_URL", "http://127.0.0.1:8091/token")
|
TOKEN_URL = os.environ.get(
|
||||||
|
"VOICE_TOKEN_URL", f"{WEB_BASE_URL}/token"
|
||||||
|
)
|
||||||
|
# Filesystem-coupled tests (repo ./media bind mount) only make sense when
|
||||||
|
# the test host IS the deployment host.
|
||||||
|
IS_LOCAL_DEPLOYMENT = urlparse(WEB_BASE_URL).hostname in ("localhost", "127.0.0.1")
|
||||||
LLM_BASE_URL = (
|
LLM_BASE_URL = (
|
||||||
os.environ.get("LLM_BASE_URL")
|
os.environ.get("LLM_BASE_URL")
|
||||||
or os.environ.get("GEMMA_BASE_URL")
|
or os.environ.get("GEMMA_BASE_URL")
|
||||||
@@ -91,8 +103,9 @@ def client(base_url):
|
|||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def token() -> dict:
|
def token() -> dict:
|
||||||
"""Fetch a fresh LiveKit token from the token endpoint (direct, no proxy)."""
|
"""Fetch a fresh LiveKit token (via nginx by default; the loopback
|
||||||
resp = httpx.post(TOKEN_URL, json={}, timeout=10)
|
:8091 socket is only reachable on the deployment host itself)."""
|
||||||
|
resp = httpx.post(TOKEN_URL, json={}, timeout=10, verify=False)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
assert "token" in data, f"token endpoint did not return a token: {data.keys()}"
|
assert "token" in data, f"token endpoint did not return a token: {data.keys()}"
|
||||||
|
|||||||
+15
-6
@@ -17,7 +17,7 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
|
|||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from conftest import TOKEN_URL, docker_exec
|
from conftest import TOKEN_URL, docker_exec, IS_LOCAL_DEPLOYMENT
|
||||||
|
|
||||||
MEDIA_API = "/media/api"
|
MEDIA_API = "/media/api"
|
||||||
TEST_ROOM = f"media-test-{uuid.uuid4().hex[:8]}"
|
TEST_ROOM = f"media-test-{uuid.uuid4().hex[:8]}"
|
||||||
@@ -200,7 +200,12 @@ def test_media_stream_proxy_and_range(client):
|
|||||||
|
|
||||||
|
|
||||||
def test_media_files_static_range_support(client):
|
def test_media_files_static_range_support(client):
|
||||||
"""Downloaded tracks are nginx static files with native Range support."""
|
"""Downloaded tracks are nginx static files with native Range support.
|
||||||
|
|
||||||
|
Writes a probe file into the repo's ./media bind mount, so it only runs
|
||||||
|
when the test host IS the deployment host (local dev deployment)."""
|
||||||
|
if not IS_LOCAL_DEPLOYMENT:
|
||||||
|
pytest.skip("writes to the repo ./media bind mount — local deployments only")
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
marker = uuid.uuid4().hex[:8]
|
marker = uuid.uuid4().hex[:8]
|
||||||
@@ -222,7 +227,11 @@ def test_media_files_static_range_support(client):
|
|||||||
|
|
||||||
|
|
||||||
def test_media_library_persists_in_volume(client):
|
def test_media_library_persists_in_volume(client):
|
||||||
"""The library JSON is written to the ./media bind mount (permanent)."""
|
"""The library JSON is written to the ./media bind mount (permanent).
|
||||||
|
|
||||||
|
Reads the repo's ./media directory — local deployments only."""
|
||||||
|
if not IS_LOCAL_DEPLOYMENT:
|
||||||
|
pytest.skip("reads the repo ./media bind mount — local deployments only")
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
upstream, url = _serve_fake_audio(b"\x00" * 16)
|
upstream, url = _serve_fake_audio(b"\x00" * 16)
|
||||||
@@ -271,7 +280,7 @@ def test_ui_media_styles_serve(client):
|
|||||||
|
|
||||||
|
|
||||||
def test_token_response_includes_room():
|
def test_token_response_includes_room():
|
||||||
resp = httpx.post(TOKEN_URL, json={}, timeout=10)
|
resp = httpx.post(TOKEN_URL, json={}, timeout=10, verify=False)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert resp.json()["room"].startswith("voice-")
|
assert resp.json()["room"].startswith("voice-")
|
||||||
|
|
||||||
@@ -279,7 +288,7 @@ def test_token_response_includes_room():
|
|||||||
# ── Hope's media MCP toolset ────────────────────────────────────────────────
|
# ── Hope's media MCP toolset ────────────────────────────────────────────────
|
||||||
|
|
||||||
MEDIA_MCP_ENV = {
|
MEDIA_MCP_ENV = {
|
||||||
"MEDIA_SERVER_URL": "http://127.0.0.1:8092",
|
"MEDIA_SERVER_URL": "http://127.0.0.1:8093",
|
||||||
"MEDIA_ROOM": "mcp-test-room",
|
"MEDIA_ROOM": "mcp-test-room",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +343,7 @@ def test_mcp_media_resume_without_load(mcp_client_factory, client):
|
|||||||
)
|
)
|
||||||
text, is_error = mcp_client_factory(
|
text, is_error = mcp_client_factory(
|
||||||
"media_mcp.py", "resume_music", {}, env={
|
"media_mcp.py", "resume_music", {}, env={
|
||||||
"MEDIA_SERVER_URL": "http://127.0.0.1:8092",
|
"MEDIA_SERVER_URL": "http://127.0.0.1:8093",
|
||||||
"MEDIA_ROOM": "mcp-empty-room",
|
"MEDIA_ROOM": "mcp-empty-room",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -54,8 +54,11 @@ def test_livekit_client_bundle_serves(client):
|
|||||||
|
|
||||||
|
|
||||||
def test_token_endpoint_direct():
|
def test_token_endpoint_direct():
|
||||||
"""Token server on 127.0.0.1:8091 (host network mode)."""
|
"""Token endpoint via TOKEN_URL (nginx path on remote deployments,
|
||||||
resp = httpx.post("http://127.0.0.1:8091/token", json={}, timeout=10)
|
loopback :8091 when VOICE_TOKEN_URL points at the deployment host)."""
|
||||||
|
import conftest
|
||||||
|
|
||||||
|
resp = httpx.post(conftest.TOKEN_URL, json={}, timeout=10, verify=False)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
assert "token" in data and data["token"].count(".") == 2 # JWT shape
|
assert "token" in data and data["token"].count(".") == 2 # JWT shape
|
||||||
|
|||||||
+4
-1
@@ -49,7 +49,10 @@ JOBS_DIR = MEDIA_DIR / "jobs"
|
|||||||
LIBRARY_FILE = MEDIA_DIR / "library.json"
|
LIBRARY_FILE = MEDIA_DIR / "library.json"
|
||||||
COOKIES_FILE = os.environ.get("YTDLP_COOKIES", "")
|
COOKIES_FILE = os.environ.get("YTDLP_COOKIES", "")
|
||||||
MEDIA_HOST = os.environ.get("MEDIA_SERVER_HOST", "127.0.0.1")
|
MEDIA_HOST = os.environ.get("MEDIA_SERVER_HOST", "127.0.0.1")
|
||||||
MEDIA_PORT = int(os.environ.get("MEDIA_SERVER_PORT", "8092"))
|
# 8093, NOT 8092: 8092 is occupied by an Apache/YOURLS stack on the Unraid
|
||||||
|
# deployment host (xNAS) — a bind conflict there looks like a healthy
|
||||||
|
# deployment because the healthcheck curl (without -f) accepts any response.
|
||||||
|
MEDIA_PORT = int(os.environ.get("MEDIA_SERVER_PORT", "8093"))
|
||||||
|
|
||||||
DOWNLOAD_TIMEOUT = int(os.environ.get("MEDIA_DOWNLOAD_TIMEOUT", "600"))
|
DOWNLOAD_TIMEOUT = int(os.environ.get("MEDIA_DOWNLOAD_TIMEOUT", "600"))
|
||||||
SEARCH_TIMEOUT = int(os.environ.get("MEDIA_SEARCH_TIMEOUT", "45"))
|
SEARCH_TIMEOUT = int(os.environ.get("MEDIA_SEARCH_TIMEOUT", "45"))
|
||||||
|
|||||||
Reference in new issue
Block a user