Skip to content

Releases

0.15.1

Choose a tag to compare

@Dim145 Dim145 released this 03 Nov 23:25

Fixed

  • Reading Wikidata's links failed — "line 153945 of Wikidata's answer for
    TheTVDB's films is no row of the table" — once a site's query came to the
    sixty seconds Wikidata's query service gives one: TMDB's films and TheTVDB's
    did by November 2026, and the service cut them partway. Each site is asked in
    eleven parts now, its ids by their last digit and what ends in none, each a
    few seconds.

0.15.0

Choose a tag to compare

@Dim145 Dim145 released this 03 Nov 22:16

Security

A review of the whole server, area by area, and of what semgrep, CodeQL,
gitleaks, trivy and zizmor find in it. Nothing let a caller past a guard; what
follows let one cost the server too much, see what was not theirs, or keep
what they had lost.

  • Signing in. Failed sign-ins locked an account for everyone, so a guesser
    could shut the administrator out of their own server; they are counted per
    account and address now (an IPv6 /64 as one), each before its password is
    checked and with one atomic count on the cache server, and guessing from
    many addresses meets a bar five times higher that never applies to an
    address the account signed in from in the last thirty days. Concurrent
    guesses no longer slip past the count while it is read, and a full count
    forgets its oldest rather than stop counting. A run of failed sign-ins, or of
    returns from the identity provider, from one address is one audit row with
    its number; a return nobody started here is not written at all.
  • Accounts and keys. A member could get round an administrator's pause of
    their key by deleting it and making another; the pause stands on the member
    now, until an administrator enables one of their keys or lifts it
    (DELETE /api/v1/users/{id}/keys-pause). Tying an identity-provider login to
    an account asks for the account's password — or has the provider sign the
    person in again, for an account with none — where a session alone did; the
    owner can untie it from their account page, an administrator's password
    reset unties it, and the journal says who tied and who untied.
  • The identity provider's client refuses link-local and metadata addresses
    at connect time (loopback excepted), judges the provider's endpoints before
    it asks any of them, and sends no Referer.
  • A trusted proxy that forwarded a request without an X-Forwarded-For
    hop made every client look like the proxy, whose address the allowlist
    admits; the client is unknown now, refused by every allowlist, and the log
    says so.
  • Load. One address could hold every connection of a door — idle HTTP/2
    connections that answer pings, slow readers: no network holds more than 64
    now (a trusted proxy's and this machine's excepted), a connection with no
    request in flight for two minutes is closed, and a response its peer does
    not read is given up on after a minute. Searches, facets, lists and figures
    no cache held are read three at a time, a caller waiting more than three
    seconds answered 503 with Retry-After. Link previews, a network's page
    and /ready asked the database afresh for every request: previews count
    against the caller's allowance and are kept five seconds, the catalogue's
    mark and /ready's answer are asked once in five seconds, and a network's
    slug is matched in memory. /tmdb-images/ has a quota of its own, ten times
    the requests', and hands on 16 pictures at a time. Search terms are cut at
    200 characters at every door, as the catalogue's own search always did.
  • Pictures. One picture could take some 700 MB to decode, as only the
    decoder's own buffer was bounded: decoding is counted by the byte a pixel
    from the picture's header before anything is decoded, a picture that would
    hold more than 256 MB is served whole, and the pictures decoding together
    share those 256 MB. A copy made on demand waits for its turn before reading
    the original, at most 32 are made at once, and each counts against the
    caller's rate limit. An upload is cut off as it is read, at the weight its
    first bytes say it may have, and a writer who is not an administrator keeps
    1 GiB of uploads all told. A writer is told only that a picture could not be
    fetched, no longer how or after how many tries — which let a typed-in
    address map the private network.
  • Relays. TheTVDB's relay read every season, episode and character kept
    without a TheTVDB id on each request for a negative number: a number no id
    made here can be is answered at once, the others are found in maps read once
    while the catalogue stays as it was, and seasons and orders' episodes are
    indexed by TheTVDB's id (migration 0053). A reference list asked with a
    made-up parameter or header filed a new database row each time: lists are
    filed under their path and the language and region asked, 512 at most per
    relay. TheTVDB's and AniList's relays key their answers on the Accept,
    Accept-Language and Content-Type they ask with, as TMDB's does.
  • Outbound requests send no Referer, which handed the next host of a
    redirect the address it came from, key included; TMDB, Fanart.tv, TheTVDB
    and MyAnimeList follow a redirect only within the origin first asked. A
    series' IMDb id went into TMDB's /find path unchecked; it is checked as a
    film's is and written as one segment.
  • Logs. A sign-in's code, state and session_state, TMDB's
    session_id, guest_session_id and request_token, and an auth
    parameter are masked as the other keys are; an import rule that could not
    fetch its list wrote reqwest's error, address and key included, into its
    history and the log; a database address whose password holds an @, or
    that carries it as ?password=, was written in part.
  • Backups. Archives, and the copy of the database one is made from, were
    readable by every user of the machine: they are written for the server's
    user alone (0600, new directories 0700), config check warns when archives
    are not encrypted, and downloading one is written to the audit trail. A
    restore wrote the archive's column names into SQL as they stood, so a crafted
    archive could run statements of its own: names are quoted, and such an
    archive is refused.
  • Elsewhere. The trailer's frame is sandboxed. trust-ca.sh checked a
    pinned fingerprint against a decoding of the whole file while installing
    every certificate in it: a fetched or pinned file must be exactly one
    certificate. History lines gitleaks takes for secrets are named, each with
    why it is none, in .gitleaksignore.

Changed

  • transfer refuses a database URL with a password among its arguments: such
    URLs are read from AMS_TRANSFER_FROM and AMS_TRANSFER_TO, which a
    process list and a shell's history do not keep.
  • A provider's upstream that redirects http:// to https:// is no longer
    followed there: give it as https://.
  • Tying an account to the identity provider is a button on the account page
    that asks for the password (POST /api/v1/account/provider); untying is
    beside it (DELETE /api/v1/account/provider).
  • The interface's react-router is 8.5.1, a week old: it mends the
    document.startViewTransition type 8.5.0 declared, which had kept it at 8.4.

0.14.0

Choose a tag to compare

@Dim145 Dim145 released this 03 Nov 16:28

Added

  • The AniList relay can answer the anime this server holds itself
    (anilist.relayMode = local-first, on Opening & APIs): a query about
    entries held here — Media by AniList's or MyAnimeList's id, a work or one
    of its seasons or cours as the anime identifier list places it, a Page of
    such entries named by their ids, filtered and sorted, their schedules,
    AniList's genres — is read as GraphQL reads it, variables, fragments,
    aliases and directives included, and answered in AniList's shape from the
    catalogue, its corrections, its characters and what AniList and MyAnimeList
    last said of the entry, without AniList. Works and relations for adults
    follow each key's adult policy; a query past what a client sends — longer,
    deeper or wider — goes to AniList whole. Searches, pages of AniList's
    catalogue and anything only AniList can say are still AniList's; a query
    about entries held here that asks for what AniList's schema does not have is
    refused here, in AniList's own words. relay stays the default; see
    docs/relays.md.
  • A work entered by hand is known to AniList's clients by the number every
    other dialect hands it; with wikidata.enabled, a work the catalogue ties to
    no AniList id is known by the one Wikidata ties to it.
  • Characters are things of their own (migration 0049): TheTVDB's parts and
    AniList's characters — one character once, whoever lists it — each with its
    ids, its picture, what it is to the work, the person of the cast who plays
    it and, for an anime, who voices it in every dub AniList knows of. A refresh
    a source does not answer keeps what that source gave each character; one
    character kept as two rows is made one, and those no work lists any more are
    forgotten within the hour, with their pictures. Works held gain TheTVDB's
    from the answers kept (refresh.replay), and AniList's at their next
    refresh.
  • Collections are kept: their name and synopsis in every language TMDB has
    them in, their pictures, their films — asked again with one of their films,
    at a refresh or a sync, once a week at the most, and a day after TMDB said
    it has no such collection — and Radarr, the collection pages and the NFO
    export read the one kept. Companies' logos, collections' pictures,
    characters' pictures and their voices' portraits are kept by the media
    store, as a work's pictures and the cast's photographs are, and the works
    whose media failed include those whose characters' pictures or voices'
    portraits did. A company's logo or a collection's picture given up on is
    counted once, on the Media page, and is no failure of each work that shows
    it. A work's media list (GET /api/v1/items/{id}/media) names them all
    beside its own, as its totals count them, and a copy of one may be
    forgotten from the work.
  • With tmdb.relayMode = local-first, a collection the catalogue keeps one of
    the films of is answered here in TMDB's shape — in the language asked, with
    its pictures and its translations — and with tvdb.relayMode = local-first,
    TheTVDB's characters/{id} and the people of a held work's records are the
    catalogue's characters, a character asked alone written as TheTVDB writes
    one: the series or the film it is of as its name, picture and year, its
    translations, aliases and tags as lists.
  • TVmaze's index of every series it lists can be kept (tvmaze.index, under
    Settings › Further sources; migration 0050): read page by page the first
    time, some four hundred pages at TVmaze's pace, then weekly for the series
    TVmaze changed since, by the task TVmaze's index (import.tvmaze), which
    can be stopped and goes on from where it stopped. Of each series it keeps
    the name, kind, language, status, days, network or web channel, country,
    TheTVDB, IMDb and TVRage ids, and weight. A series search that finds nothing
    while a source that could have found it did not answer lists the series the
    index finds by that name, under their TheTVDB ids, where it was a 502, and
    so do Sonarr's tvdb:, imdb: and tmdb: lookups by the ids it files.
    Nothing it lists is kept as a work. A series TVmaze lists that cannot be read
    is passed over rather than forgotten, and a reading that ends far below the
    highest id kept forgets none. TVmaze is credited on every page while the
    index is on.
  • A work TMDB gives no popularity — one entered by hand, or every work of a
    catalogue TMDB does not serve — is given one worked out here, from TVmaze's
    weight of a series and how many votes IMDb's daily list sees it gather a
    day, placed on TMDB's scale among the works held: the catalogue's lists, the
    search and the similar works rank by it where TMDB's is missing, and it is
    never written in place of TMDB's, nor as AniList's. It is worked out again
    after each reading of IMDb's ratings or TVmaze's index, and daily; IMDb's
    votes of the works held are kept eight days for it.
  • Wikidata's links between the sites' ids can be kept (wikidata.enabled,
    under Settings › Further sources; migration 0051): read weekly by eight
    queries to Wikidata's query service (AMS_WIKIDATA_ENDPOINT), one after the
    other, by the task Wikidata's links between ids (import.wikidata) — a
    million links, some thirty-six megabytes in SQLite and a hundred in
    PostgreSQL — the links read before kept until a reading completes. An id a
    client asks by then leads to the work held here under another of its ids —
    TMDB's /3/find, TheTVDB's search/remoteid with local-first, Sonarr's
    and Radarr's imdb: and tmdb: terms, AniList's entries — and, where TMDB
    does not answer, has no key or knows no work by an IMDb id, to the ids TMDB
    and TheTVDB are asked by. The links only find ids, and never change the one
    a client holds a work by: a series TMDB alone names keeps its number and its
    numbering, whatever TheTVDB id the links know for it.
  • A series TheTVDB deletes, merges into another or no longer knows keeps the
    numbering Sonarr matched its files by, frozen as its aired numbering
    (tvdb-last, TheTVDB (as last given); migration 0052); the episodes that
    air afterwards are added after it — from TMDB's own numbering, else from
    TVmaze, or, for a series merged into another, from TheTVDB under the id it
    kept, which the work takes while answering to its own. TheTVDB answering
    404 at two refreshes in a row, twenty hours apart at the least, sets it
    off, and so does the daily task TheTVDB's deleted series (tvdb.deleted),
    which reads TheTVDB's feed of updates while TheTVDB is configured and
    numbering.fallbackAfter is above 0 — as TheTVDB writes it: the kind of
    record a series was merged into is mergeToType there, where its swagger
    says mergeToEntityType, and a series merged into a film is read as
    deleted. Sonarr v5 is sent metadataSource tvdb meanwhile; the audit
    trail and the notifications say it as they say TMDB's numbering in
    TheTVDB's place, and Back to TheTVDB gives the series back by hand.
  • A work's page lists its characters — each with what it is to the work, who
    plays it and who voices it in the dub chosen — and each has a page of its
    own, /characters/{id} (GET /api/v1/characters/{id}, open to visitors
    where the catalogue is), with every work of the catalogue it is in. The task
    page, the Sources page and the settings know TVmaze's index, Wikidata's
    links and TheTVDB's deleted series; the numbering banner says why a series
    TheTVDB no longer knows is served as it was, and what giving it back did;
    the AniList relay's mode is set beside TMDB's and TheTVDB's. A link to a
    company's, a keyword's, a character's or a kept collection's page carries a
    preview.

Changed

  • What the TMDB relay writes itself — a work's documents with
    tmdb.relayMode = local-first, the catalogue's works in a search, the works
    changed here on /3/movie/changes and /3/tv/changes — lists a work for
    adults as the caller's key or address rule says (adult.clientPolicy), as
    the TheTVDB relay does, where it followed the server's setting alone. The
    AniList relay's own answers do the same.
  • The records TheTVDB's relay lays on its updates feed for the works changed
    here carry the fields TheTVDB's own records of an update carry, as its feed
    writes them: no mergeToId nor mergeToEntityType, which its swagger
    describes and its feed leaves out of an update.
  • A /3/find that TMDB answers with no work at all is answered with the work
    held here under the id asked — or, with wikidata.enabled, under another id
    of its Wikidata item — in relay too, as local-first answers it: TMDB does
    not know every id a work carries.
  • AniList is asked, with an entry, for its characters and their voices, every
    studio with its id and whether it is a main one, its tags' ids and its count
    of members, where it was asked for the main studios' names alone: what the
    relay's own answers need. A query AniList refuses for the characters is sent
    again without them, and so is every later one.
  • The catalogue's two indexes on TMDB's popularity, which no list read any
    more, are replaced by two on whichever popularity a work has, TMDB's or the
    one worked out here (migration 0050).
  • No item.renumbered is told while an order is frozen as a series' aired
    numbering — by a person, or as TheTVDB last gave a series it no longer
    knows: that order is what the clients are sent, and the series' own episodes
    moving moves nothing they are sent.
  • The archives and transfer carry TVmaze's index and Wikidata's links, as
    they carry IMDb's lists, so that a database restored or moved has them at
    once: from a PostgreSQL, a million rows more to copy, and some thirty-six
    megabytes of the archive's database.

Fixed

  • A series TheTVDB answers 404 for — a duplicate it deleted, most often —
    was renumbered by TMDB at its next refresh without a word: TMDB's episodes
    took the place of TheTVDB'...
Read more

0.13.0

Choose a tag to compare

@Dim145 Dim145 released this 02 Nov 20:37

Added

  • The relays stand in for a service that fails. When TMDB, TheTVDB or
    AniList cannot be reached or does not answer in time, answers 429 or a
    500, 502, 503 or 504 — or, for AniList, 403, its API switched
    off; for TheTVDB, will not sign this server in — the last good answer
    to the same request is served, with an Age header and the locks written
    in: up to a week old for TMDB and TheTVDB, a day for AniList. TMDB's and
    TheTVDB's reference lists — TMDB's configuration, genres and
    certifications, TheTVDB's languages, genres, types and statuses — are kept
    in the database too (migration 0047, relay_document), up to 180 days, and
    answer when the memory holds nothing, after a restart for instance. Nothing
    asked with a client's own key, nothing about somebody's account and no feed
    of changes ever stands in. relays.staleIfError, on by default and set on
    Opening & APIs, turns it off; ams_relay_stale_total counts what was
    served so.
  • Requests a relay is asked the same at once share one exchange with the
    service.
  • TMDB's pictures can come from this server: with tmdb.images = server on
    Opening & APIs, the media store on and AMS_PUBLIC_URL set,
    /3/configuration gives {AMS_PUBLIC_URL}/tmdb-images/ as the address of
    TMDB's pictures, and Jellyfin, Kodi, Kometa and Emby fetch them there; the
    page says which is missing when server cannot take effect. The gate,
    GET /tmdb-images/{size}/{file}, serves a picture the store keeps from the
    store, and hands any other on from image.tmdb.org without keeping it; it
    takes TMDB's sizes and its site's crops alone, and names of TMDB's pictures
    alone. It is guarded as the TMDB relay is, AMS_TMDB_AUTH=apikey reading
    as allowlist there since a picture's address carries no key; an address
    it lets in is not held to AMS_RATE_LIMIT_PER_MINUTE, and a picture is not
    counted as a call to TMDB's API. tmdb stays the default; see
    docs/relays.md.
  • The relays' feeds of changes name the works changed here — a lock put on
    or lifted, something added or taken out by hand, a refresh that changed a
    value — after the service's own, on their first page: TMDB's
    /3/movie/changes and /3/tv/changes by TMDB id, in the window asked;
    TheTVDB's updates by TheTVDB id, as TheTVDB writes a record of it, since
    the instant asked and fourteen days back at the most. A client that keeps in
    step by them — Yamtrack by TMDB's, Jellyfin's plugin by TheTVDB's — fetches
    a work corrected here at once rather than at its next full refresh. When
    the service fails, the feed fails with it, so that the client asks the same
    window again.
  • The NFO export writes an anime's AniList and MyAnimeList ids, as
    <uniqueid type="anilist"> and <uniqueid type="mal">, after the others
    and never as the default: Jellyfin's AniList plugin then reads that entry
    rather than searching by title. A work filed under several entries is
    given the oldest of each, the first season far more often than not, since
    a reader keeps one id of a type. Episodes carry neither.
  • .github/workflows/watch.yml: every Monday, and on demand, what this
    server imitates, relays or downloads is read against a snapshot kept in
    scripts/watch/snapshots/ — AniList's schema (every field, argument and
    value of the types this server's queries, its relay and six known clients
    read, and all of Query and Page), the keys of Skyhook's documents under
    /v1/tvdb, /v2/tvdb and /v2/tmdb and the last change to Sonarr's
    client of it, the version of TheTVDB's swagger with the last commit and the
    open pull requests of thetvdb/v4-api, the date and the OpenAPI definition
    of each page of TMDB's reference, and the keys of the anime list's entries.
    A run fails when one of them moved, or when the anime list or the database
    it is made from has not been updated for 21 days, and its summary says
    which, with the deprecated fields still read (Media.averageScore,
    today). It reads with no secret, GitHub's API with the run's own token, and
    writes nothing. python3 scripts/watch/watch.py runs it from a checkout;
    CONTRIBUTING.md, "Upstream watch", says how to take a snapshot again.
  • The TMDB relay can answer the works this server holds itself
    (tmdb.relayMode = local-first, on Opening & APIs): a film's or a
    series' document and what is appended to it — the older releases,
    trailers and casts included — each part asked on its own, a series'
    seasons and episodes in TMDB's own numbering, each episode with the
    guests, director and writers the catalogue keeps of it, find, and the
    catalogue's works first on a search's first page, in TMDB's shape, from
    the catalogue with its locks and translations, and from TMDB's own
    document of the work for what the catalogue does not keep. No TMDB key is
    needed for them.
    Where to watch, reviews and the like are still asked of TMDB where it
    knows the work. relay stays the default.
  • TheTVDB's relay can answer the works the catalogue holds itself
    (tvdb.relayMode = local-first): a series' record, its translations, its
    pictures, a page of its episodes in any order it holds, its seasons and
    episodes, a film's record — written in TheTVDB's shape from the catalogue
    and from TheTVDB's own record of the work, as last fetched, without asking
    TheTVDB or needing its key. A series TheTVDB does not know — one TMDB
    alone lists, a Fan-Kai, one entered by hand — is answered under the number
    Sonarr keeps it under, its seasons and episodes under negative ids. A
    search lists the works the catalogue finds before TheTVDB's results.
    ams_relay_local_total counts what both relays answered so.
  • A series' tvdb_id in TMDB's ids is the number Sonarr keeps it under,
    made up where TheTVDB has none: Seerr hands a series entered by hand, or
    one TMDB alone lists, to Sonarr under a number Sonarr's metadata answers.
    The relay fills a tvdb_id TMDB leaves null for a series held here.
  • TMDB's picture gate serves a picture of another source, or one uploaded,
    kept in the media store, under its key: what a document written in TMDB's
    shape names it by when TMDB's clients are sent to this server for
    pictures.
  • TMDB's own numbering of a series is kept beside TheTVDB's, as an order,
    tmdb-aired ("TMDB (aired)"), written at every refresh that reads TMDB's
    seasons, and read from TMDB the first time a client asks for a series in
    it that no refresh has kept it for. Each of its places names the episode
    the series holds for it — the one of the same TMDB id when the two aired
    the same day, else the one episode held that aired that day — or carries
    TMDB's record of it. It is offered on the site and to Plex, as Plex's
    TMDB agent offers "TheMovieDB (Aired)", not among the season types of
    TheTVDB's documents, and can be frozen as the aired numbering as any
    order can. Who numbers a series does not change: TMDB does in TheTVDB's
    place only after numbering.fallbackAfter silent refreshes.
  • Skyhook's TMDB edition, /v2/tmdb/shows/{language}/{id}/{seasonType} and
    /v2/tmdb/search, which the real service serves and Sonarr v5 does not
    call yet: a series by TMDB's id — a Fan-Kai or a series entered by hand by
    the id Sonarr knows it by — numbered by TMDB (default), each episode by
    its TMDB id, TMDB's episode groups as season types under their bare ids,
    in the real service's shape. A type the series has not is answered in
    default, where the real service sends no episode; a series TheTVDB
    numbers whose TMDB numbering cannot be read is a 502, which Sonarr asks
    again on. scripts/e2e-skyhook-v2.sh and the weekly parity run compare
    it with the real service.
  • The model keeps what TMDB's and TheTVDB's answers carried all along
    (migration 0048): networks and companies, and TMDB's keywords, as things
    of their own with their ids, logos and countries; every video, the chosen
    trailer unchanged; the whole crew, each credit with TMDB's department and
    job, up to tmdb.crewLimit (30) — the crew beyond directing, writing and
    production without portraits; TMDB's pictures with their language, size
    and votes, up to tmdb.imageLimit (5) of each kind, the best of each
    useful language first; a season's title and synopsis, and a person's
    biography, in every language TMDB has them in. Works held gain them at
    their next refresh. Radarr is given each crew member's department and job.
  • The season chart shows the AniList and MyAnimeList entry that premieres in
    the quarter beside the season it belongs to — the second half of a split
    season among the series carrying on — placed on the season's episodes by
    the anime identifier list and described from them: its part, episodes,
    days, status and next episode. describe=true adds MyAnimeList's
    description of each, which the site asks for only when the reader opens
    it.
  • A work's page lists its whole crew a department at a time, every video its
    sources list — the trailer chosen first, a YouTube one played in place only
    once asked for — and its networks and companies with their logos and their
    countries. Each company and each of TMDB's keywords has a page of its own,
    /company/{id} and /keyword/{id} (GET /api/v1/companies/{id},
    /keywords/{id}, open to visitors where the catalogue is), and the
    catalogue is filtered by them (company=, keywordId=). A person known
    only for their crew work has a tab of it.
  • Opening & APIs chooses what the TMDB and TheTVDB relays answer themselves
    (tmdb.relayMode, tvdb.relayMode), and the settings take how much of
    TMDB's crew and pictures a work keeps (tmdb.crewLimit, tmdb.imageLimit).
  • The task refresh.replay fills into every work, from the answers kept with
    it and without asking anybody, what this version keeps that earlier ones
    let go: networks and companies, TMDB's keywords and videos, the pictures'
    languages and sizes, ...
Read more

0.12.0

Choose a tag to compare

@Dim145 Dim145 released this 31 Oct 23:06

Added

  • What the process holds in memory, in the metrics: ams_memory_heap_bytes,
    the bytes the program holds from its allocator, counted as they come and
    go; ams_memory_resident_bytes{kind}, ams_memory_resident_peak_bytes
    and ams_process_threads, what the kernel keeps resident for it; and its
    cgroup's account, of which ams_cgroup_memory_working_set_bytes is the
    figure docker stats shows, beside the ceiling, the charge by kind and how
    many times the ceiling was reached. ams_cache_bytes counts the pictures'
    copies made in the last minute too (copies). The kernel's figures are
    read on Linux alone; see docs/monitoring.

Changed

  • The image has its allocator give back what it frees at once, and in pages
    of the system's own size (MIMALLOC_PURGE_DELAY=0, MIMALLOC_ALLOW_THP=0):
    under a library's import the server held half of what it did, for some
    15 % more of its CPU. Either is undone on the container; see
    docs/perf/memory.md.
  • A JPEG's placeholder is drawn from the picture decoded small — an eighth
    of its sides or a quarter, by the decoder's inverse transform — where it
    was decoded whole, then held again as RGBA: one of TMDB's posters of
    2000×3000 takes 0.7 MB to prepare where it took 42, a progressive backdrop
    half of what it took, for the same sketch. A picture a WebP copy is made
    of is decoded whole, as before.
  • On PostgreSQL, each connection keeps the two dozen statements it runs
    most, prepared once, where every query was parsed and described anew: a
    missing number is bound as the double PostgreSQL holds it as, the fault
    of sqlx's the cache had been switched off for. Under a library's import
    PostgreSQL spent 30 % less CPU and held half the memory; sqlx's own
    hundred spared it no more, for 20 MB of the server's memory against 8.
    An address that says statement-cache-capacity keeps its own size.
  • The Rust crates are at their newest releases a week old or more, within
    their ranges: redis 1.8, uuid 1.29 and utoipa-swagger-ui 10.0.2
    among them.
  • So are the interface's @tanstack/react-query 5.104.5,
    typescript-eslint 8.72 and Vite 8.3.5, the documentation site's Astro
    7.4, which wants Node 22.22 at least, and the workflows' actions.
    react-router stays at 8.4: 8.5.0 declares a document.startViewTransition
    the DOM's own type cannot take, which 8.5.1 mends.
  • The image's interface is built on Node 26, the LTS line since 28 October
    — Node 24 is in maintenance — and its server with Rust 1.99; CI runs
    Node 26 too, and @types/node follows it.
  • Building from source takes Rust 1.95, where it took 1.94: parking_lot
    0.13 asks for it, whose locks wake the thread waiting on them once they
    are let go of, not before, and change hands sooner when several threads
    want them. The crates that bring its 0.12 along — sqlx, moka,
    object_store and governor among them — keep it until they move on.
  • compose.multi.yaml fronts the clients' door with HAProxy 3.4, the LTS
    line maintained until 2031, keeps its cache in Valkey 9 and its media in
    Garage v2.5.0. A Garage node that has run 2.5 cannot go back to an older
    release, and a bucket's lifecycle is now read and changed with a key that
    owns it alone — the server never asks for it. The Valkey that
    compose.yaml and docs/caching.md describe is 9 as well.
  • compose.integration.yaml runs Seerr, which carries Jellyseerr on: the
    fallenbagel/jellyseerr image is deprecated. Seerr moves a Jellyseerr
    configuration over on its first start, and the stack keeps the volume's
    name for it; the port is SEERR_PORT.

Fixed

  • On PostgreSQL, every query left its prepared statement in the session for
    as long as the connection lived: with the statement cache off, as the
    server had it, sqlx names each statement it prepares and never closes it.
    A library's import grew each of PostgreSQL's connections to some 800 MB,
    and the simplest statements waited minutes to be parsed. The cache is on
    again (see Changed); an address that switches it off
    (statement-cache-capacity=0) has its statements let go of as each
    connection returns to the pool.
  • Storing a work held each provider's answer twice while it was merged and
    written: parsed, and a parsed copy for the snapshots. The snapshots are
    kept as the text they are written as, and the answers let go of: a series
    of ten thousand episodes peaks at 123 MB where it took 210.
  • The anime identifier list is read an element at a time: read whole as
    documents first, its seven and a half megabytes took the server's memory
    to 150 MB at every start it was due, where it now reaches 105.

Security

  • utoipa-swagger-ui 10.0.2 builds the Swagger UI with zip 9, past
    RUSTSEC-2026-0367 to -0369: cargo audit and cargo deny no longer set
    them aside.

0.11.1

Choose a tag to compare

@Dim145 Dim145 released this 26 Oct 21:14

Changed

  • Building the image keeps its compiled dependencies across a change of
    version: the layer they are compiled in is made from Cargo.toml and
    Cargo.lock with this project's own version set aside, so a release, or a
    local build after one, no longer compiles six hundred crates again.

Fixed

  • Sonarr's and Radarr's searches list the works for adults the catalogue holds
    wherever the server shows adult titles (adult.mode set to visible), as
    they always listed those TMDB knows: a series or a film for adults entered
    by hand, which TMDB cannot bring back, was never listed. A client whose own
    policy refuses adult titles (adult.clientPolicy set to deny) is now
    listed none, TMDB's included.

0.11.0

Choose a tag to compare

@Dim145 Dim145 released this 26 Oct 17:41

Added

  • The image builds with --build-arg CARGO_PROFILE_RELEASE_LTO=thin on a
    machine short of memory: the whole-program optimisation the published image
    is made with wants some 4 GB at its last step, more than a Docker VM shared
    with other containers may have. A build from source takes the same
    variable; CI and the published image are unchanged.
  • A Reading panel on the profile page: whether the posters of works for
    adults are shown as they are or veiled, kept with the account
    (reader.adultPosters) so the choice follows it from one browser to the
    next. The switch in the bar's Language and reading menu is the same
    choice for whoever is signed in, and stays this browser's for a visitor.
  • Turning the WebP or the AVIF copies off, while some are kept, asks whether
    to delete them too. DELETE /api/v1/media/variants/{format} deletes every
    copy of a format once it is off, in the background, which the Media page
    follows (purging in GET /api/v1/media/status).

Changed

  • The WebP copies are off until media.webp is set: the pages are given the
    pictures themselves — with an AVIF <source> over them where the night's
    copies reach as wide — and an address of a WebP copy answers the copy kept
    or sends to the original, making none. A server that wants the lighter
    pages turns them on, on the Media page. media.variants prepares the
    placeholders, and the first copies only while the WebP copies are on;
    GET /api/v1/auth/options says whether they are (webp).
  • The front page no longer offers an administrator to write a programmer's
    note there: the administration's page writes it, as do the pages of a
    selection, a genre, a network, a year and a decade.
  • Radarr's search leaves out the films it could not add, those without a
    TMDB id that were not entered by hand. A tvdb: term from Sonarr resolves
    every id this server gives, those made of a TMDB id or a Fankai one too.
  • An administrator's own page among the accounts no longer offers to reset
    their password, which the server refuses there, and points to their account
    page instead. Closing every session of their own there asks first, since it
    ends this one too.

Fixed

  • A work entered by hand reaches Sonarr and Radarr. It is given a number of
    its own, from 1,000,000,000 up, worked out from its catalogue id so that it
    never changes: Sonarr's searches list it under that number and its lookups
    answer it, Radarr takes it as the film's TmdbId, and the change
    notifications and the seeding find it by it. Fankai's numbers end at
    999,999,999. Plex was given such a work whole already: an agent that also
    lists Plex's own provider lets Plex guess it by its title, which
    docs/plex.md explains.
  • Signing out, or changing one's password, shows a visitor at once: the bar
    kept the account's Admin until the page was reloaded. So does closing
    one's own sessions, or untying one's own account from the identity
    provider, from the administration. A session that ends somewhere else — it
    runs out, or is closed from another browser or by another administrator —
    is noticed at the next request it is refused, or when who is signed in is
    next asked: the interface then forgets the account and shows a visitor.

0.10.0

Choose a tag to compare

@Dim145 Dim145 released this 25 Oct 23:43

Added

  • The tagline in every language TMDB describes a work in. media_translation
    carries it (migration 0035), and it is laid over the work as the title and
    the overview are — by the native API's language, Sonarr's and Radarr's
    answers, the .nfo export and the Kometa file — a tagline locked for one
    language standing over TMDB's in it. Plex is given the tagline in the
    language it asks for when the catalogue holds one in it, where it was given
    none outside the stored language.
  • A work's copy of TMDB's answer is six months old at the most, as TMDB's
    terms of use ask. A work refreshed longer ago than 180 days — an ended
    series kept for a year, or ten — is given a light refresh by the sweep,
    after the works that are due and within the same batch: TMDB alone, as a
    sync asks it, the other sources keeping what they last gave; the run's
    entries say which works were refreshed so. refresh.ttlContinuing and
    refresh.ttlEnded say it in their description, and the Settings page says
    it when either is set past six months.
  • Each source's terms of use, in a line with a link, on Administration ›
    Sources; docs/sources.md says what the server does with each source's
    data, and what it never does.
  • The editor's Seasons tab lists every picture attached to a season, the
    hidden ones struck through with a way to show them again, and hides a
    source's picture as the Artwork tab hides the work's own: a season's
    hidden picture could be shown again by the API alone.
  • Sonarr v5 is offered TheTVDB's two further alternate orders, altdvd and
    alttwo ("Alternate DVD Order", "Alternate Order 2"), which TheTVDB's own
    episodes endpoint answers empty for (thetvdb/v4-api#340): when it does,
    the order is read season by season from each season's own record, one
    call a season, as the real Skyhook serves it.
  • .github/workflows/parity.yml: every Monday, and on demand, the real
    Skyhook's second edition is set beside this server's for four real series
    by scripts/e2e-skyhook-v2.sh, and any difference that is not an addition
    fails the run. The Office (US) joins the script's default series for its
    two alternate orders: a season type the real Skyhook offers and this
    server does not is a failure, The Office's "Superfan Order" included. It
    needs the same repository secrets as the end-to-end job, and stands aside,
    saying so, without them.
  • docs/sonarr-radarr.md says what happens when Sonarr is upgraded to v5 —
    every series refreshed once, the requests coalesced, the limit per address
    and how to raise it for the day — and tells "not found" from "not
    answered".
  • A key with the new metrics scope reads GET /api/v1/admin/metrics and
    nothing else — not the catalogue, not the settings, not the other keys, on
    any surface — so what sits in Prometheus's configuration file can read the
    figures and no more. Made under Keys & network by ticking Scrape; a
    person's own key cannot carry the scope. An administrator's key reads them
    as before.
  • Every request is given an id — sixteen random bytes in hexadecimal, a
    trace id's shape — answered in x-request-id and written beside every
    log line the request produces, at every AMS_LOG level (request_id on
    the request span, which the JSON format files under span). The
    x-request-id a proxy in AMS_TRUSTED_PROXIES sends is kept, so its log
    and the server's name a request alike, and its traceparent names the
    request when it sent no id; anybody else's is replaced.
  • CI measures how much of the crate the tests reach (cargo llvm-cov),
    informatively: the summary is in the job's log and lcov.info is kept a
    week as an artifact. It never fails the run.
  • Backups. The server makes archives of itself — the whole database as
    one SQLite file, which restores on SQLite and on PostgreSQL alike, and,
    when asked, the media kept on disk — named for when and why they were
    made, ams-20261019-020000-scheduled.tar.zst, with a manifest that says
    the version and the last migration that made them, the engine read and
    each file's SHA-256. Weekly by default, the schedule's four newest kept
    (backup.schedule, backup.keep, backup.includeMedia; migration 0036),
    in AMS_BACKUP_DIR — backups beside the SQLite database, /data/backups
    in the image — or, among several instances whose media are in a bucket, in
    that bucket under backups/. With AMS_BACKUP_PASSPHRASE set, each
    archive is encrypted as age writes it (….tar.zst.age): an archive holds
    every account's password hash, every key's and the webhooks' secrets.
    Administration › Backups lists them, makes one now, downloads, deletes
    and restores them; the task backup.run is on the Jobs page, the
    dashboard says when the newest archive is older than twice the schedule,
    /api/v1/admin/health carries lastBackupAt, a scrape
    ams_backup_age_seconds, and the webhooks are told backup.completed and
    backup.failed.
  • Restoring an archive from the Backups page makes an archive of the
    database first, then answers every request, on every door, with 503 —
    /health aside — until the rows are copied, in one transaction: a restore
    that fails leaves the database as it was. An archive of a later version or
    migration is refused before anything is touched; an older one is brought
    up to this version's migrations first. The audit trail records
    backup.restored and backup.deleted.
  • Before a start applies a new version's migrations, the database is
    archived as the version before left it (ams-…-upgrade-m0037.tar.zst,
    without the media), the two newest kept apart from the schedule's
    (backup.keepUpgrades): always on SQLite, on PostgreSQL unless
    backup.beforeUpgrade is turned off. An archive that cannot be made stops
    the start; AMS_BACKUP_BEFORE_UPGRADE=false starts without it.
    migrate run does the same.
  • A command line beside the server: backup, restore — refused while a
    server holds the database, which it does from its start, by a lock beside
    an SQLite file or an advisory lock on PostgreSQL —, user list|create|reset-password|set-admin, keys create, locks export|import, config check, migrate status|run and version, beside
    healthcheck and transfer; with no command the binary serves, as
    before. What changes accounts, keys or locks goes through the server's own
    API in the process, as client:command line, with the interface's checks
    and its audit trail; a password is read from standard input or asked for,
    never taken as an argument. docs/cli.md gives the docker exec and
    docker compose run forms the distroless image needs.
  • A series' orders are orders of their own (migration 0037): each has
    the key a client asks for it by, its kind, where it was read and the name
    it goes by, and each of its places names an episode the work holds or
    carries the record its source gave of one it does not. An order TheTVDB
    files apart from the aired episodes is served whole — The Office's
    "Superfan Order", 194 extended cuts that are episodes of their own on
    TheTVDB, or a DVD's hour-long cut of two aired parts — where its places
    named aired episodes or nothing. TheTVDB's orders go by the series' own
    name for them ("Superfan Order", "Netflix Order"). The orders 0013 kept
    are carried over, and each series that had one is refreshed at the next
    sweep, which reads its orders as they are now. Two orders of one name are
    told apart in Sonarr's and Plex's lists by the source of the one not
    numbering the series — Superfan Order (TMDB).
  • TMDB's episode groups are orders beside TheTVDB's: listed with the series,
    read by the refresh that lists them, four at a time — the editor and a
    season's page ask TMDB nothing — offered to Sonarr v5 and Plex under
    tmdb- and the group's id. A group that is the same order as one
    of the source numbering the series, and holds no more episodes, is not
    offered: that one stands for it. docs/sources.md says which.
  • Orders made here, in the editor's Seasons tab: made empty or from
    another — the aired one, TheTVDB's, a TMDB group — named, their places
    moved up and down and renumbered, and given episodes of their own the work
    does not hold, served under TheTVDB's id when they carry one and a stable
    negative one otherwise. Sonarr v5 and Plex are offered them under ams-
    and their name. Through the API: POST /items/{id}/orders, PUT and
    DELETE /items/{id}/orders/{key}; the audit trail says order.created,
    order.updated, order.deleted.
  • An order can be frozen as the aired numbering, and thawed
    (POST/DELETE /items/{id}/orders/{key}/freeze; order.frozen,
    order.thawed): Sonarr v4 and v5 asking for the aired order, Plex with no
    order chosen, Jellyfin through the TheTVDB relay — its pages of episodes
    and a series' record with meta=episodes — and the .nfo export are
    served it, an episode that airs later keeping its aired numbers where the
    order leaves them free, and an episode of the order's own given one id by
    Sonarr v4 and v5 alike. It is how a series that has files in Sonarr v5 is
    given another order, which Sonarr will not change on it (Sonarr#9055);
    the dialog says what it does to XEM's mappings and to the files Sonarr
    matched. A frozen series' tvshow.nfo carries <displayorder>, and the
    group's id for a TMDB group; a special an order places among the seasons
    is written with Kodi's displayseason and displayepisode.
  • TMDB numbers a series TheTVDB has stopped answering for, after
    numbering.fallbackAfter refreshes in a row — three unless set, twenty
    hours apart at the least, 0 never — each episode keeping the TheTVDB id
    it had, until TheTVDB answers again. The audit trail says
    item.numbering_fallback and item.numbering_restored, a notification
    destination that wants numbering.fallback is told, Sonarr v5 is sent
    metadataSource: tmdb, ...
Read more

0.8.3

Choose a tag to compare

@Dim145 Dim145 released this 18 Oct 17:28

Added

  • A documentation site, https://dim145.github.io/arr-metadata-server/, built
    with Starlight from docs/ and the README and published on GitHub Pages by
    .github/workflows/docs.yml. Each page is written once and stays readable
    on GitHub: the build rewrites the links between pages and stops on one that
    leads nowhere.
  • docs/settings.md: every setting — what it holds, its default, where it
    may be set, the variable that seeds it — written from the settings registry
    by UPDATE_DOCS=1 cargo test settings_reference; cargo test fails when
    the page and the code disagree. docs/reverse-proxy.md: nginx, Traefik and
    Caddy in front of the server — AMS_PUBLIC_URL, AMS_TRUSTED_PROXIES, the
    headers read, the paths to keep off the internet.

Changed

  • The OpenAPI document (/api/openapi.json) describes what the server sends.
    Ten schema names stood for two types or more — Role, Status, Counts,
    Mode, Entry, Kind, Source, and Radarr's RatingResource,
    ImageResource and AlternativeTitleResource, which carried Skyhook's
    shape — so twenty-five fields were documented with another type's shape:
    each type has its own name now (AccountRole and PersonRole,
    AccountStatus, SkyhookRatingResource…). Every operation has an id of its
    own, <module>_<handler> (users_list), where fifty-three shared sixteen;
    and the sixty-seven fields the native API always sends, null when empty,
    are listed as required. Nothing the server sends has changed; a client
    generated from the document gets the new names. cargo test fails when two
    types share a name, when an operation lacks its id or shares one, or when a
    field always sent is documented as optional; the interface's generated
    types are a .ts the compiler checks.
  • The README is down to what the server is, what it serves, a quick start
    and the first steps for Sonarr, Radarr and Plex. The rest moved, unchanged
    in substance, to new pages under docs/: installing, configuration (every
    environment variable), sources, languages, media, caching, several
    instances, accounts and access, security, the API.
  • The interface loads only the language it is read in: English and French
    are separate files, the reader's fetched before the first paint and the
    other only when chosen. The first download is a third lighter: 489 kB
    instead of 746 (148 kB compressed instead of 235).
  • An API's access policy reads the same, in the same colour, on the
    dashboard, the configuration page and the access page; the public bar's
    language switch takes the administration's segmented look.
  • Internal, with nothing to see from outside: the interface's types are
    generated from the server's own OpenAPI document (frontend/openapi.json,
    kept current by cargo test, UPDATE_OPENAPI=1 rewriting it, and turned
    into types by npm run types), and CI lints the interface (ESLint with
    typescript-eslint, react-hooks and jsx-a11y), runs its unit tests (Vitest)
    and fails when the generated types fall behind. The works' repository, one
    file of some five thousand lines, is six, each with its tests; the native
    API's handlers guard with Identity::require_admin and a new
    Identity::require_write instead of ten private copies of either.

Fixed

  • A film's releases in a country listed those with no date first, and an
    undated rating could stand for the country instead of the dated cinema
    release's.
  • A field locked to an object shows its JSON in the editor, not
    [object Object].

0.8.2

Choose a tag to compare

@Dim145 Dim145 released this 18 Oct 02:03

Added

  • Failed refreshes, grouped. A notification destination can be told of
    failed refreshes together: they wait five minutes from the first, then go
    as one refresh.failed message — how many, counted by cause (the source
    and the kind of failure: TheTVDB — timed out: 31), the first works
    named — rather than one message per work; an outage of a source is one
    message. On by default for Discord, Slack, ntfy, Gotify and Apprise, and
    for the destinations of those kinds already made; off for standard and
    json, read by programs that want an event per work. Switched on the
    destination's form, grouped in the API; the message's JSON carries a
    count.

Changed

  • A supplement that does not answer — TVmaze, Fanart.tv, AniList,
    MyAnimeList, the Fankai wiki — no longer makes a work's refresh a failed
    one. What it gave before is kept, as it was, and the log says it did not
    answer; but the work is refreshed: no refresh error, not counted among the
    works whose refresh failed, and no refresh.failed. A hiccup of TVmaze
    flagged and told of every series refreshed meanwhile.

Fixed

  • TVmaze is given a minute in a background task — a refresh, a seed — and
    asked once more, after a pause, for what it never answered. Its servers
    take fifteen to twenty seconds, now and then, over what their cache does
    not hold, whatever the pace of the calls, and a refresh logged
    TVmaze lookup failed … timed out past the twenty it was given. A client
    waiting on this server still gives it twenty seconds, and no second try.
    TVmaze's API keys are not asked for: they open its user API, a premium
    member's own data, and change nothing of the public one's limit, which is
    per address.