Repository navigation
Releases
Releases · Dim145/arr-metadata-server
0.15.1
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
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 answered503withRetry-After. Link previews, a network's page
and/readyasked 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/findpath unchecked; it is checked as a
film's is and written as one segment. - Logs. A sign-in's
code,stateandsession_state, TMDB's
session_id,guest_session_idandrequest_token, and anauth
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 checkwarns 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.shchecked 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
transferrefuses a database URL with a password among its arguments: such
URLs are read fromAMS_TRANSFER_FROMandAMS_TRANSFER_TO, which a
process list and a shell's history do not keep.- A provider's upstream that redirects
http://tohttps://is no longer
followed there: give it ashttps://. - 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-routeris 8.5.1, a week old: it mends the
document.startViewTransitiontype 8.5.0 declared, which had kept it at 8.4.
0.14.0
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 —Mediaby AniList's or MyAnimeList's id, a work or one
of its seasons or cours as the anime identifier list places it, aPageof
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.relaystays 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; withwikidata.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 withtvdb.relayMode = local-first,
TheTVDB'scharacters/{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 a502, and
so do Sonarr'stvdb:,imdb:andtmdb: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'ssearch/remoteidwithlocal-first, Sonarr's
and Radarr'simdb:andtmdb: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
404at 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.fallbackAfteris above0— as TheTVDB writes it: the kind of
record a series was merged into ismergeToTypethere, where its swagger
saysmergeToEntityType, and a series merged into a film is read as
deleted. Sonarr v5 is sentmetadataSourcetvdbmeanwhile; 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/changesand/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
updatesfeed for the works changed
here carry the fields TheTVDB's own records of an update carry, as its feed
writes them: nomergeToIdnormergeToEntityType, which its swagger
describes and its feed leaves out of an update. - A
/3/findthat TMDB answers with no work at all is answered with the work
held here under the id asked — or, withwikidata.enabled, under another id
of its Wikidata item — inrelaytoo, aslocal-firstanswers 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.renumberedis 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
transfercarry 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
404for — 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'...
0.13.0
Added
- The relays stand in for a service that fails. When TMDB, TheTVDB or
AniList cannot be reached or does not answer in time, answers429or a
500,502,503or504— 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 anAgeheader 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_totalcounts 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 = serveron
Opening & APIs, the media store on andAMS_PUBLIC_URLset,
/3/configurationgives{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 whenservercannot 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=apikeyreading
asallowlistthere since a picture's address carries no key; an address
it lets in is not held toAMS_RATE_LIMIT_PER_MINUTE, and a picture is not
counted as a call to TMDB's API.tmdbstays 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/changesand/3/tv/changesby TMDB id, in the window asked;
TheTVDB'supdatesby 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 ofQueryandPage), the keys of Skyhook's documents under
/v1/tvdb,/v2/tvdband/v2/tmdband the last change to Sonarr's
client of it, the version of TheTVDB's swagger with the last commit and the
open pull requests ofthetvdb/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.pyruns 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 olderreleases,
trailersandcastsincluded — 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.relaystays 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_totalcounts what both relays answered so. - A series'
tvdb_idin 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 atvdb_idTMDB 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 afternumbering.fallbackAftersilent 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 a502, which Sonarr asks
again on.scripts/e2e-skyhook-v2.shand 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 totmdb.crewLimit(30) — the crew beyond directing, writing and
production without portraits; TMDB's pictures with their language, size
and votes, up totmdb.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=trueadds 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.replayfills 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, ...
0.12.0
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
andams_process_threads, what the kernel keeps resident for it; and its
cgroup's account, of whichams_cgroup_memory_working_set_bytesis the
figuredocker statsshows, beside the ceiling, the charge by kind and how
many times the ceiling was reached.ams_cache_bytescounts 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 saysstatement-cache-capacitykeeps its own size. - The Rust crates are at their newest releases a week old or more, within
their ranges:redis1.8,uuid1.29 andutoipa-swagger-ui10.0.2
among them. - So are the interface's
@tanstack/react-query5.104.5,
typescript-eslint8.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-routerstays at 8.4: 8.5.0 declares adocument.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/nodefollows 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.yamlfronts 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.yamland docs/caching.md describe is 9 as well.compose.integration.yamlruns Seerr, which carries Jellyseerr on: the
fallenbagel/jellyseerrimage is deprecated. Seerr moves a Jellyseerr
configuration over on its first start, and the stack keeps the volume's
name for it; the port isSEERR_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-ui10.0.2 builds the Swagger UI withzip9, past
RUSTSEC-2026-0367 to -0369:cargo auditandcargo denyno longer set
them aside.
0.11.1
Changed
- Building the image keeps its compiled dependencies across a change of
version: the layer they are compiled in is made fromCargo.tomland
Cargo.lockwith 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.modeset tovisible), 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.clientPolicyset todeny) is now
listed none, TMDB's included.
0.11.0
Added
- The image builds with
--build-arg CARGO_PROFILE_RELEASE_LTO=thinon 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 (purginginGET /api/v1/media/status).
Changed
- The WebP copies are off until
media.webpis 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.variantsprepares the
placeholders, and the first copies only while the WebP copies are on;
GET /api/v1/auth/optionssays 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. Atvdb: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'sTmdbId, 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
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'slanguage, Sonarr's and Radarr's
answers, the.nfoexport 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.ttlContinuingand
refresh.ttlEndedsay 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.mdsays 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,
altdvdand
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
byscripts/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.mdsays 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
metricsscope readsGET /api/v1/admin/metricsand
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 inx-request-idand written beside every
log line the request produces, at everyAMS_LOGlevel (request_idon
the request span, which the JSON format files underspan). The
x-request-ida proxy inAMS_TRUSTED_PROXIESsends is kept, so its log
and the server's name a request alike, and itstraceparentnames 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 andlcov.infois 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),
inAMS_BACKUP_DIR—backupsbeside the SQLite database,/data/backups
in the image — or, among several instances whose media are in a bucket, in
that bucket underbackups/. WithAMS_BACKUP_PASSPHRASEset, 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 taskbackup.runis on the Jobs page, the
dashboard says when the newest archive is older than twice the schedule,
/api/v1/admin/healthcarrieslastBackupAt, a scrape
ams_backup_age_seconds, and the webhooks are toldbackup.completedand
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 —
/healthaside — 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.restoredandbackup.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.beforeUpgradeis turned off. An archive that cannot be made stops
the start;AMS_BACKUP_BEFORE_UPGRADE=falsestarts without it.
migrate rundoes 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|runandversion, beside
healthcheckandtransfer; with no command the binary serves, as
before. What changes accounts, keys or locks goes through the server's own
API in the process, asclient: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.mdgives thedocker execand
docker compose runforms 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.mdsays 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 underams-
and their name. Through the API:POST /items/{id}/orders,PUTand
DELETE /items/{id}/orders/{key}; the audit trail saysorder.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 withmeta=episodes— and the.nfoexport 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.nfocarries<displayorder>, and the
group's id for a TMDB group; a special an order places among the seasons
is written with Kodi'sdisplayseasonanddisplayepisode. - TMDB numbers a series TheTVDB has stopped answering for, after
numbering.fallbackAfterrefreshes in a row — three unless set, twenty
hours apart at the least,0never — each episode keeping the TheTVDB id
it had, until TheTVDB answers again. The audit trail says
item.numbering_fallbackanditem.numbering_restored, a notification
destination that wantsnumbering.fallbackis told, Sonarr v5 is sent
metadataSource: tmdb, ...
0.8.3
Added
- A documentation site, https://dim145.github.io/arr-metadata-server/, built
with Starlight fromdocs/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
byUPDATE_DOCS=1 cargo test settings_reference;cargo testfails 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'sRatingResource,
ImageResourceandAlternativeTitleResource, which carried Skyhook's
shape — so twenty-five fields were documented with another type's shape:
each type has its own name now (AccountRoleandPersonRole,
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,nullwhen empty,
are listed as required. Nothing the server sends has changed; a client
generated from the document gets the new names.cargo testfails 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.tsthe 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 underdocs/: 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 bycargo test,UPDATE_OPENAPI=1rewriting it, and turned
into types bynpm 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 withIdentity::require_adminand a new
Identity::require_writeinstead 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
Added
- Failed refreshes, grouped. A notification destination can be told of
failed refreshes together: they wait five minutes from the first, then go
as onerefresh.failedmessage — 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 forstandardand
json, read by programs that want an event per work. Switched on the
destination's form,groupedin 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 norefresh.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 outpast 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.