#!/usr/bin/env python3 """jfsetup.py — S5 prep: Jellyfin's first-run wizard and two libraries, by its own API, INSIDE the guest. The admin password is generated on DooPlex (kept ONLY in ~/.felhom-tester1/secrets.json, 0600) and substituted into this file's copy on the box for the run; the copy is deleted afterwards. Nothing secret is printed.""" import json, subprocess, time, urllib.parse, urllib.request PW = "__JF_PW__" ip = subprocess.run("docker inspect jellyfin -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'", shell=True, capture_output=True, text=True).stdout.strip() B = f"http://{ip}:8096" AUTH = 'MediaBrowser Client="felhom-test", Device="wire", DeviceId="felhom-wire-1", Version="1"' def call(m, path, body=None, tok=None): h = {"Content-Type": "application/json", "Authorization": AUTH + (f', Token="{tok}"' if tok else "")} r = urllib.request.Request(B + path, data=json.dumps(body).encode() if body is not None else None, headers=h, method=m) try: with urllib.request.urlopen(r, timeout=60) as x: t = x.read().decode(); print(f" {m} {path.split('?')[0]} -> {x.status}"); return json.loads(t) if t else None except urllib.error.HTTPError as e: print(f" {m} {path.split('?')[0]} -> {e.code} {e.read()[:150]}"); return None for _ in range(30): try: urllib.request.urlopen(B + "/health", timeout=5); break except Exception: time.sleep(3) call("POST", "/Startup/Configuration", {"UICulture": "en-US", "MetadataCountryCode": "HU", "PreferredMetadataLanguage": "hu"}) call("GET", "/Startup/User") call("POST", "/Startup/User", {"Name": "admin", "Password": PW}) call("POST", "/Startup/RemoteAccess", {"EnableRemoteAccess": True, "EnableAutomaticPortMapping": False}) call("POST", "/Startup/Complete") a = call("POST", "/Users/AuthenticateByName", {"Username": "admin", "Pw": PW}) tok = a["AccessToken"] for name, ct, sub in (("TV Shows", "tvshows", "tv"), ("Movies", "movies", "movies")): q = urllib.parse.urlencode({"name": name, "collectionType": ct, "refreshLibrary": "true"}) call("POST", f"/Library/VirtualFolders?{q}", {"LibraryOptions": {"PathInfos": [ {"Path": f"/mnt/felhom-drives/{d}/userdata/media/{sub}"} for d in ("media_a", "media_b")]}}, tok) for _ in range(40): time.sleep(6) s = call("GET", "/Items?Recursive=true&IncludeItemTypes=Series,Movie,Episode&Fields=Path", tok) or {} items = s.get("Items", []) eps = [i for i in items if i["Type"] == "Episode"] if len(eps) >= 16: break for t in ("Series", "Movie"): print(t, sorted(i["Name"] for i in items if i["Type"] == t)) print("Episodes:", len(eps)) for v in call("GET", "/Library/VirtualFolders", tok) or []: print("library", v["Name"], v.get("Locations"))