diff --git a/romm-automation-import-worker.py b/romm-automation-import-worker.py index dfa55a6..cbd9a04 100644 --- a/romm-automation-import-worker.py +++ b/romm-automation-import-worker.py @@ -40,6 +40,7 @@ QUIET_IDLE = os.environ.get("IMPORT_QUIET_IDLE", "true").lower() in {"1", "true" ROMM_SERVER_URL = os.environ.get("ROMM_SERVER_URL", "").rstrip("/") ROMM_USERNAME = os.environ.get("ROMM_USERNAME", "") ROMM_PASSWORD = os.environ.get("ROMM_PASSWORD", "") +ROMM_API_TOKEN = os.environ.get("ROMM_API_TOKEN", "") IGNORE_SUFFIXES = {".part", ".partial", ".tmp", ".crdownload"} @@ -190,8 +191,19 @@ def quarantine(path: Path, reason: str) -> None: def romm_token() -> str: + """Return a RomM bearer token for task calls. + + Prefer ROMM_API_TOKEN because RomM task execution requires the tasks.run + scope. Username/password logins can authenticate successfully while still + lacking that scope, which causes HTTP 403 on /api/tasks/run/scan_library. + """ + if ROMM_API_TOKEN: + return ROMM_API_TOKEN + if not (ROMM_SERVER_URL and ROMM_USERNAME and ROMM_PASSWORD): - raise RuntimeError("ROMM_SERVER_URL, ROMM_USERNAME, and ROMM_PASSWORD are required for scan trigger") + raise RuntimeError( + "ROMM_SERVER_URL plus either ROMM_API_TOKEN or ROMM_USERNAME/ROMM_PASSWORD are required for scan trigger" + ) data = parse.urlencode({"username": ROMM_USERNAME, "password": ROMM_PASSWORD}).encode() req = request.Request( f"{ROMM_SERVER_URL}/api/token", @@ -223,6 +235,12 @@ def trigger_romm_scan() -> None: log(f"triggered RomM scan_library: HTTP {resp.status} {body[:300]}") except error.HTTPError as exc: body = exc.read().decode("utf-8", errors="replace") + if exc.code == 403: + raise RuntimeError( + "RomM scan trigger failed: HTTP 403 Forbidden. The RomM credential/token can log in, " + "but it does not have the tasks.run scope required to run scan_library. " + "Create a RomM API token with Run tasks/tasks.run permission and set ROMM_API_TOKEN." + ) from exc raise RuntimeError(f"RomM scan trigger failed: HTTP {exc.code} {body}") from exc diff --git a/romm-automation-truenas-compose.yml b/romm-automation-truenas-compose.yml index b67bf1d..29e16fd 100644 --- a/romm-automation-truenas-compose.yml +++ b/romm-automation-truenas-compose.yml @@ -177,8 +177,9 @@ services: environment: TZ: ${TZ:-America/Chicago} ROMM_SERVER_URL: ${ROMM_SERVER_URL:?set ROMM_SERVER_URL in .env} - ROMM_USERNAME: ${ROMM_USERNAME:?set ROMM_USERNAME in .env} - ROMM_PASSWORD: ${ROMM_PASSWORD:?set ROMM_PASSWORD in .env} + ROMM_USERNAME: ${ROMM_USERNAME:-} + ROMM_PASSWORD: ${ROMM_PASSWORD:-} + ROMM_API_TOKEN: ${ROMM_API_TOKEN:-} ROMM_SCAN_AFTER_IMPORT: ${ROMM_SCAN_AFTER_IMPORT:-true} IMPORT_POLL_SECONDS: ${IMPORT_POLL_SECONDS:-60} IMPORT_QUIET_IDLE: ${IMPORT_QUIET_IDLE:-true} diff --git a/romm-automation-truenas.env.example b/romm-automation-truenas.env.example index b8517ea..3338bba 100644 --- a/romm-automation-truenas.env.example +++ b/romm-automation-truenas.env.example @@ -60,6 +60,11 @@ IGDB_CLIENT_SECRET=fill_me ROMM_SERVER_URL=http://your-romm-host:port ROMM_USERNAME=fill_me ROMM_PASSWORD=fill_me +# Optional but recommended for the import worker scan trigger. Create a RomM API +# token with the Run tasks / tasks.run permission and paste it in the real env. +# Username/password may pass basic library checks but still receive HTTP 403 when +# trying to run /api/tasks/run/scan_library without this scope. +ROMM_API_TOKEN= # Romarr image. Upstream docs list romarr/romarr:latest, but the public image was not pullable # when this scaffold was created. Override this if we build/publish a local image.