This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

archiver-bot: use cid-map for IPFS links, fix Jellyfin lookup, bind-mount cid-map into yellow

+28 -40
+2 -1
2configs/services/archiver-bot.nix
··· 31 31 ]; 32 32 wants = [ "network-online.target" ]; 33 33 34 - path = [ pkgs.kubo ]; 35 34 environment = { 36 35 MATRIX_HOMESERVER = "https://matrix.lassul.us"; 37 36 MATRIX_USER_ID = "@archiver:lassul.us"; ··· 43 42 JELLYFIN_URL = "http://localhost:8096"; 44 43 45 44 MEDIA_PATH_PREFIX = "/var/download/"; 45 + IPFS_CID_MAP = "/var/lib/ipfs/cid-map.txt"; 46 + IPFS_PATH_PREFIX = "/var/lib/ipfs/download/"; 46 47 WEBHOOK_PORT = "8099"; 47 48 STORE_PATH = "/var/lib/archiver-bot"; 48 49 };
+4
2configs/services/flix/container-host.nix
··· 16 16 hostPath = "/var/download"; 17 17 isReadOnly = false; 18 18 }; 19 + containers.yellow.bindMounts."/var/lib/ipfs/cid-map.txt" = { 20 + hostPath = "/var/lib/ipfs/cid-map.txt"; 21 + isReadOnly = true; 22 + }; 19 23 networking.firewall.allowedTCPPorts = [ 20 24 8096 21 25 8920
+22 -39
5pkgs/archiver-bot/archiver_bot/__init__.py
··· 38 38 JELLYFIN_URL = os.environ.get("JELLYFIN_URL", "http://localhost:8096") 39 39 JELLYFIN_API_KEY = os.environ.get("JELLYFIN_API_KEY", "") 40 40 IPFS_GATEWAY_URL = os.environ.get("IPFS_GATEWAY_URL", "https://ipfs.lassul.us") 41 - IPFS_BIN = os.environ.get("IPFS_BIN", "ipfs") 42 41 MEDIA_PATH_PREFIX = os.environ.get("MEDIA_PATH_PREFIX", "/var/download/") 42 + IPFS_CID_MAP = os.environ.get("IPFS_CID_MAP", "/var/lib/ipfs/cid-map.txt") 43 + IPFS_PATH_PREFIX = os.environ.get("IPFS_PATH_PREFIX", "/var/lib/ipfs/download/") 43 44 44 45 # Persistent tracking of requested media -> rooms 45 46 TRACKING_FILE = Path(STORE_PATH) / "tracked_requests.json" ··· 87 88 return f"{FLAX_URL}/{quote(relative)}" 88 89 89 90 90 - async def compute_cid(path: str) -> str | None: 91 - """Compute the IPFS CID for a file using ipfs add --only-hash.""" 91 + def lookup_cid(path: str) -> str | None: 92 + """Look up the IPFS CID for a file from the cid-map.""" 93 + ipfs_path = path 94 + if ipfs_path.startswith(MEDIA_PATH_PREFIX): 95 + ipfs_path = IPFS_PATH_PREFIX + ipfs_path[len(MEDIA_PATH_PREFIX):] 92 96 try: 93 - ipfs_path = "/tmp/ipfs-hash-repo" 94 - if not os.path.exists(os.path.join(ipfs_path, "config")): 95 - init_proc = await asyncio.create_subprocess_exec( 96 - IPFS_BIN, "init", 97 - stdout=asyncio.subprocess.PIPE, 98 - stderr=asyncio.subprocess.PIPE, 99 - env={**os.environ, "IPFS_PATH": ipfs_path}, 100 - ) 101 - await init_proc.communicate() 102 - env = {**os.environ, "IPFS_PATH": ipfs_path} 103 - proc = await asyncio.create_subprocess_exec( 104 - IPFS_BIN, "add", "--only-hash", "--quieter", path, 105 - stdout=asyncio.subprocess.PIPE, 106 - stderr=asyncio.subprocess.PIPE, 107 - env=env, 108 - ) 109 - try: 110 - stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=30) 111 - except asyncio.TimeoutError: 112 - proc.kill() 113 - await proc.communicate() 114 - log.warning(f"ipfs add --only-hash timed out for {path}") 115 - return None 116 - if proc.returncode == 0: 117 - cid = stdout.decode().strip() 118 - if cid: 119 - return cid 120 - else: 121 - log.warning(f"ipfs add --only-hash failed for {path}: {stderr.decode().strip()}") 97 + with open(IPFS_CID_MAP) as f: 98 + for line in f: 99 + line = line.strip() 100 + if not line: 101 + continue 102 + cid, _, file_path = line.partition(" ") 103 + if file_path == ipfs_path: 104 + return cid 122 105 except Exception as e: 123 - log.warning(f"Failed to compute CID for {path}: {e}") 106 + log.warning(f"Failed to look up CID for {path}: {e}") 124 107 return None 125 108 126 109 ··· 182 165 return None 183 166 184 167 185 - async def make_media_links(path: str, is_dir: bool = False, jellyfin_id: str | None = None) -> tuple[str, str]: 168 + def make_media_links(path: str, is_dir: bool = False, jellyfin_id: str | None = None) -> tuple[str, str]: 186 169 """Generate plain and HTML text with all available links for a media path.""" 187 170 from urllib.parse import quote 188 171 ··· 204 187 205 188 # IPFS link (compute CID for the file) 206 189 if not is_dir: 207 - cid = await compute_cid(path) 190 + cid = lookup_cid(path) 208 191 if cid: 209 192 ipfs_link = f"{IPFS_GATEWAY_URL}/ipfs/{cid}" 210 193 lines_plain.append(f"๐ŸŒ IPFS: {ipfs_link}") ··· 324 307 if path: 325 308 is_dir = media_type == "tv" 326 309 jf_id = await lookup_jellyfin_id(session, media_type, media_id) 327 - links_plain, links_html = await make_media_links(path, is_dir=is_dir, jellyfin_id=jf_id) 310 + links_plain, links_html = make_media_links(path, is_dir=is_dir, jellyfin_id=jf_id) 328 311 return (f"โœ… {display} is already available!\n{links_plain}", f"โœ… <b>{display}</b> is already available!<br>{links_html}") 329 312 return (f"โœ… {display} is already available!", f"โœ… <b>{display}</b> is already available!") 330 313 elif status in (2, 3, 4): ··· 340 323 if existing_path: 341 324 is_dir = media_type == "tv" 342 325 jf_id = await lookup_jellyfin_id(session, media_type, media_id) 343 - links_plain, links_html = await make_media_links(existing_path, is_dir=is_dir, jellyfin_id=jf_id) 326 + links_plain, links_html = make_media_links(existing_path, is_dir=is_dir, jellyfin_id=jf_id) 344 327 # Still send the request to Jellyseerr so it's tracked there too 345 328 await jellyseerr_request(session, "/request", method="POST", json=payload) 346 329 return (f"โœ… {display} is already available!\n{links_plain}", f"โœ… <b>{display}</b> is already available!<br>{links_html}") ··· 471 454 await trigger_jellyfin_scan(http) 472 455 await asyncio.sleep(10) 473 456 jf_id = await lookup_jellyfin_id(http, "movie", tmdb_id) 474 - links_plain, links_html = await make_media_links(file_path, jellyfin_id=jf_id) 457 + links_plain, links_html = make_media_links(file_path, jellyfin_id=jf_id) 475 458 plain = f"โœ… {display} has been downloaded and is now available!\n{links_plain}" 476 459 html = f"โœ… <b>{display}</b> has been downloaded and is now available!<br>{links_html}" 477 460 for room_id in rooms: ··· 515 498 await trigger_jellyfin_scan(http) 516 499 await asyncio.sleep(10) 517 500 jf_id = await lookup_jellyfin_id(http, "tv", tmdb_id) 518 - links_plain, links_html = await make_media_links(folder_path, is_dir=True, jellyfin_id=jf_id) 501 + links_plain, links_html = make_media_links(folder_path, is_dir=True, jellyfin_id=jf_id) 519 502 plain = f"โœ… {display}{ep_info} has been downloaded and is now available!\n{links_plain}" 520 503 html = f"โœ… <b>{display}</b>{ep_info} has been downloaded and is now available!<br>{links_html}" 521 504 for room_id in rooms: