Skip to content

Release 1.6.1 - #153

Merged
retransmit merged 15 commits into
mainfrom
development
Sep 11, 2026
Merged

retransmit merged 15 commits into
mainfrom
development

Conversation

@retransmit

Copy link
Copy Markdown
Owner

Release 1.6.1, build 22. Version codes 221 / 222 / 223.

Mostly about running Atrium behind a reverse proxy, with a few small additions.

What is in it

Release chores

Version bumped in all seven places that carry it. The runbook listed only four of them and pointed at a changelog file that has since moved, so it now lists all seven.

Not yet verified

The reverse-proxy fixes were checked end to end against Authelia behind nginx forward-auth, nginx basic auth on a sub-path, and a header-gated proxy in front of twenty services. The user who reported the original problem has not yet confirmed them on his own setup. Cloudflare Access was not tested directly, since it needs an account and tunnel; it uses its own header names, so it does not meet the collisions the others hit.

retransmit and others added 15 commits September 4, 2026 22:55
Adding a torrent left the save path blank, so the whole path had to be
typed every time even though the server already knows where it puts
things. It now arrives filled in with the server's default, and follows
the category picker the way qBittorrent's own web interface does.

getCategories was reading the endpoint for names alone and discarding
the save path each category carries, so that is kept now. A category is
free to define no path of its own, which comes back as an empty string
and means the global default applies: picking one of those has to fall
back rather than blank the field, which is most of the point.

Note the key is savePath on that endpoint and save_path in
app/preferences. Both spellings are accepted rather than trusting one,
since reading the wrong one fails silently as an empty path.

The names-only callers are unchanged: they now derive from the same
fetch instead of asking again.

A prefill that lands late must not overwrite what someone has already
typed, so a hand edit switches it off.

Reported in #150
Waking something sat behind the settings screen, which is a long way to
go for a button pressed when a server is asleep. The devices still live
on the profile and are still managed there; this is a shortcut to firing
at them, and the card opens that screen.

Wake-on-LAN is the first widget with no service behind it. Every other
one counts as configured when an instance of some service exists, and
that test would have called this unconfigured forever and hidden it, so
the check now asks the kind whether it is device backed and counts the
profile's devices instead. The edit sheet says what it wants rather than
naming a service it does not have.

The snackbar says the packet was sent, not that the machine woke.
Nothing acknowledges a magic packet, so the stronger claim would be one
the app cannot stand behind.

Two tests froze the full set of widget kinds, one as a count and one as
an order, so adding any widget broke them. Both derive from the enum
now.

Reported in #151
The card has room for three rows and sorted purely on progress, so a
queue of nearly-complete torrents took every slot and pushed the one
actually running off the bottom. Anyone with more than a few things
queued had an Active downloads card that never showed the active
download.

Rows now say whether data is moving, and that ranks ahead of progress.
Queued and stalled stay counted and still appear when there is room:
they are downloads someone is waiting on, and dropping them would
understate what is on the go. They just stop crowding out the live one.

Only qBittorrent reports a state fine-grained enough to tell the
difference. Every other client is already filtered to what is
downloading, so their rows default to moving.

Reported in #144
- wrap sidebar service list in RefreshIndicator to support pull-to-refresh
- re-probe health on open if older than 60s (stale-while-revalidate)
- poll health every 30s while drawer is open, pausing when backgrounded
- stop polling immediately when drawer closes
- sync last health refresh timestamp between dashboard and sidebar
- add widget tests covering pull-to-refresh, freshness on open, and open-only polling
The global headers were an optional argument to create() defaulting to none,
and five of its six callers never passed it. The health probe, the connection
tester and the Beszel, dashdot and Glances clients all dropped the profile's
headers while the screens sent them.

Anyone behind a forward-auth proxy who configured headers globally, the
obvious place when one proxy fronts everything, got a working app whose
"Test connection" answered "Reachable, but the check did not pass". The
factory holds them now, so a caller cannot forget.
Artwork is the one thing that does not go through Dio. Posters, banners and
backdrops are fetched by CachedNetworkImage on its own client, which sends
only what it is handed, so the user-configured headers never reached them.
Behind a forward-auth proxy the proxy answered every image request and every
poster came up blank. Silently, because errorWidget swallows the failure and
a refused poster looks exactly like a missing one.

AtriumNetworkImage and atriumImageProvider resolve the headers from the URL
and pass them on. The lookup is a static holder rather than a provider
because artwork is requested from PaletteGenerator calls, DecorationImage
and plain State classes, none of which have a ref to read; it has one writer,
next to the existing global header sync.

Artwork on a third party keeps sending nothing, so an add-search poster from
TMDB does not carry the user's proxy credentials off their network.
… mark

Three review fixes on the sidebar health refresh.

The poll now cancels its timer when the app leaves the foreground instead of
firing every thirty seconds to decide it has nothing to do, and starts it
again on resume. A drawer left open behind a lock screen no longer wakes for
the rest of the session.

The refresh timestamp is written when a round finishes rather than when it is
fired. Stamping it up front meant a round that failed immediately still
counted as fresh for the next sixty seconds; overlapping rounds are already
held off by the in-flight future.

The pull-to-refresh header no longer shows "last updated at". EasyRefresh
substitutes a raw DateTime.hour there, so it reads 0:05 where the device
clock says 12:05 AM, and the package offers no hook to format it against the
device's locale.

Adds a test for the backgrounded case, which nothing covered.
…quest

Auditing every service in a real profile turned up two ways a reverse-proxy
header silently does nothing.

Five kinds spend Authorization on their own credentials: NZBGet, Transmission
and rTorrent on Basic, Speedtest Tracker and Tracearr on Bearer. The
interceptor runs per request, after the profile's headers are installed as
client defaults, so a user's Authorization is overwritten and never leaves the
device. Configured for a proxy, it is thrown away before the proxy can see it.

qBittorrent goes the other way. It parses an Authorization header it did not
issue rather than falling back to its session cookie, and answers 401. The
same request answers 403 without the header, so a proxy credential sent this
way locks the user out of a service that is otherwise reachable.

Neither can be resolved by picking a winner, since both consumers want the one
header. Proxy-Authorization is what RFC 7235 reserves for an intermediary, and
it was the only name that survived all nineteen services tested: nothing
overwrites it and nothing rejects it. It is now the placeholder on a screen
whose own subtitle says it exists for reverse-proxy auth, in place of
X-Api-Key, which every *arr would have overwritten.

serviceAuthHeaderNames reports what the interceptor will set, with a test
holding it to what the interceptor actually sends for every kind and auth, so
the warning cannot quietly go stale.
Every health endpoint here answers with JSON, or XML-RPC for rTorrent. None
answer with a web page. A forward-auth proxy redirects an unauthenticated
probe to its login portal, the client follows the redirect as any client
would, and the portal returns a perfectly healthy 200 full of markup. Status
alone cannot tell that apart from a working server, so a service nobody could
actually reach was shown as Online.

Reproduced against Authelia behind nginx: an unauthenticated probe ends on the
portal with 200 and text/html after one redirect, and the sidebar called it
Online. It now reads Warning, while an authenticated probe to the same service
still reads Online.

Read from the content type rather than the body, because Authelia's portal
opens with a licence comment and looking for a leading <html would miss it.
Restricted to 2xx because several services answer with a page on an error and
are already interpreted correctly: Transmission's 409 conflict and rTorrent's
502 both arrive as markup and both still mean what they meant before.
The warning told everyone to use Proxy-Authorization for reverse-proxy auth.
That is right for Authelia, which accepts it on an authz endpoint carrying the
HeaderProxyAuthorization strategy, and wrong for nginx's auth_basic, which an
nginx Proxy Manager access list runs on and which only ever reads
Authorization. Verified both against real nginx: Authorization 200,
Proxy-Authorization 401.

Someone following the old wording on nginx Proxy Manager would have moved off
a header that worked onto one their proxy ignores, and lost access to
everything rather than just qBittorrent.
Version bumped in all seven places that carry it, with the in-app notes and the
store changelog for build 22.

The runbook listed four of those places and pointed at a changelog file that
has since moved, so it now lists all seven.
@retransmit
retransmit merged commit b1d6c49 into main Sep 11, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants