Files
mesh-catalog/modules/jackett/tools/index.ts
T
jschoubben 75eee9d4a0 jackett: its config dir is placed, and its tools find their own key
The manifest named /services/jackett/config and /var/lib/mesh/jackett/config.json — host
paths ADR 0112 takes out of definitions. The config dir is now a pathless directory
(${dir:config}) and the runtime's config and route binding live in a placed state dir, as
searxng does.

The image is pinned to v0.24.2627-ls34, the digest ace runs today; the old pin
(v0.24.2517-ls16) was older than the running version.

The runtime reached jackett at a fixed 127.0.0.1:9117; it now uses ${port:9117}, the
machine port the mesh actually assigned.

The tools never loaded: the client needed an API key nobody set. Like sonarr/radarr read
config.xml, it now reads APIKey from Jackett's own ServerConfig.json (the config dir is
already mounted read-only), so no secret goes into an assignment. jackett_indexers called
/api/v2.0/indexers, which is the web UI's endpoint and answers an API key with a redirect;
it now reads the Torznab t=indexers feed, and treats Torznab's 200-with-<error> as a
failure.

Verified: catalogue key tests (MESH_CATALOGUE set, not skipped); a throwaway container of
the pinned image on a fresh 0700 1000:1000 config dir serves its UI; the client discovers
the key from the generated ServerConfig.json, lists 617 indexers through the Torznab feed,
searches via /results, and a wrong key is refused; client.ts typechecks under --strict.
2026-09-29 23:41:21 +02:00

49 lines
1.9 KiB
TypeScript

// jackett's tools — its own code (novox/hq ADR 0039), importing jackett's client. Jackett has
// nothing worth watching (an indexer proxy answers queries; it has no timeline of its own), so it
// is a tools-only module: no events entrypoint, no broker. What is useful is asking it things.
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
import { JackettClient } from "../client.js";
export function getJackettTools(jackett: JackettClient): ToolDefinition[] {
return [
{
name: "jackett_indexers",
description: "List the indexers Jackett proxies, with their type and site.",
input: { all: { type: "boolean", description: "include indexers not yet configured (default false)" } },
run: async (args) => {
const indexers = await jackett.getIndexers(!args.all);
return { count: indexers.length, indexers };
},
},
{
name: "jackett_search",
description: "Torznab search across Jackett's indexers, returning normalised torrent results.",
input: {
query: { type: "string", description: "the search query" },
indexer: { type: "string", description: 'an indexer id, or "all" to aggregate (default "all")' },
limit: { type: "number", description: "max results (default 25)" },
},
run: async (args) => {
const query = String(args.query);
const results = await jackett.search(
query,
args.indexer ? String(args.indexer) : "all",
args.limit ? Number(args.limit) : 25,
);
return { query, count: results.length, results };
},
},
];
}
// Only exposed when Jackett is configured; otherwise jackett contributes no tools rather than
// failing the whole runtime.
registerModuleTools("jackett", (env) => {
try {
return getJackettTools(JackettClient.fromEnv(env));
} catch {
return [];
}
});