Skip to content

Releases: Dim145/opentracker

Release 0.37.2

Choose a tag to compare

@github-actions github-actions released this 13 Sep 01:52
1165d6e

A patch release with one change: the messaging stream now connects immediately
instead of a heartbeat later.

The live stream took up to a minute to open

If you run Trackarr behind a CDN or a buffering proxy, /messaging/events
took about a minute to connect on every page load. It always worked — chat and
notifications arrived once it was up — it was just late, every single time.

The relay sent its response headers at once and then no body byte at all
until its first keep-alive ping, thirty seconds later. A flush with an empty
body produces no chunk to forward, so anything in the path that waits for body
bytes before releasing a response waited. Reported from a Cloudflare-fronted
install whose network trace showed 16 bytes of content on that request:
exactly two pings.

The stream now writes a priming comment the moment the headers go out.
Measured on the first byte of body, before and after:

headers first body byte
before 104 ms 30 121 ms
after 116 ms 117 ms

30 121 ms is the heartbeat interval to the millisecond.

The stream also now states its own reconnect delay (retry: 2000) instead of
leaving the browser on a default that differs between them.

Nothing else changed — no behaviour, no data, no configuration, and the
heartbeat interval is untouched. If your reverse proxy already streamed this
endpoint without buffering, you would never have seen the delay and nothing
changes for you.

Upgrading

Swap the relay image. No migrations, no configuration changes. Reverse-proxy
settings need no adjustment: this was ours, not yours.

🤖 Generated with Claude Code

What's Changed

Full Changelog: 0.37.1...0.37.2

Release 0.37.1

Choose a tag to compare

@github-actions github-actions released this 13 Sep 00:14
c57e009

A patch release with one change: controls that had no visible edge now have one.

Control borders

Trackarr's palette has two neutral lines. --line-default is the decorative
rule that separates cards and table rows. --line-field is the one meant for
anything you click — the Tailwind config has said so all along: "the border
of a FIELD … which we want at 3:1"
.

Half the product's controls were drawn with the decorative one. Measured
against their own surface, those borders came out at 1.21:1 on dark and
1.26:1 on light
, where WCAG 2.1 SC 1.4.11 asks for 3:1 on anything that
identifies a user interface component. Buttons, inputs, selects, chips,
toggles, pagers, tabs and filter segments across 31 screens.

They now use the field line, which was itself raised: its light value only
reached 2.98:1 on the page background, and 2.89:1 on the lightest surface the
product actually uses. It now clears 3:1 on all four. The dark value is
unchanged.

Three related defects surfaced while measuring:

  • Hover made outlines fainter. The hover border sat at 2.64:1 — below the
    resting state — so pointing at a button weakened its edge instead of
    strengthening it. It now sits above.
  • Some state colours were frozen to the dark theme. A toggle's "on"
    state, a moderator badge, an admin badge and several active filters used
    hardcoded greens, ambers and cyans. In the light theme those fall to
    1.6–1.9:1 at any opacity, so "on" was effectively invisible. They now use
    the theme-aware tokens, which match the old dark values.
  • A few panels tinted their background with the very colour drawn on top of
    it
    , which cannot contrast by definition. Those got an inked variant.

One element is deliberately left as it was: the placeholder tile shown for a
work with no artwork. What identifies that link is its letter, its icon and
its accessible name, not its frame, and pushing the per-work hue to full
strength would make every artless tile shout in a grid people scan. The
reasoning is written into the stylesheet next to the rule.

Nothing else changed: no behaviour, no data, no configuration. Measured on 31
routes in both themes, 65 affected classes down to that single deliberate
exception.

Upgrading

Straight image swap. No migrations, no configuration changes.

🤖 Generated with Claude Code

What's Changed

Full Changelog: 0.37.0...0.37.1

Release 0.37.0

Choose a tag to compare

@github-actions github-actions released this 12 Sep 20:34
a1b3ef7

Moderation, brought level with itself.

Trackarr has four moderation queues and they were not the same age. The
anti-cheat flags had a taxonomy, a priority, a typed verdict and bulk actions.
Tickets had case management. Reports had resolved_by and nothing else.
Pending uploads had three columns. This release brings the other three up to
where the first one already was, then closes the gaps that were left.

The queue stops asking you to judge cold

Deciding on a pending upload cost three pages: you left for the public torrent
page, left again for the uploader's profile, and came back to a queue that had
lost its place. The decision now happens beside the list — above 1080px the
panel docks to the right with no scrim, below it becomes a sheet — and a
decision advances to the next item instead of returning you to the top.

Every rejection reason was free text, so the same refusal was worded
differently by every moderator and none of it could be counted. Ten reason
codes now sit beside the message, which stays mandatory: the code is what you
count, the sentence is what the member reads.

And the queue handed over a name and a category while the database already
computed — and threw away — the uploader's accept/reject history, whether the
same content already exists, open anti-cheat flags, and the automated checks
on NFO, description and naming. All four are now a signals band. They inform;
none of them decides.

Also: claim an item so two moderators stop instructing the same dossier,
snooze without closing, order by priority rather than by date alone, warnings
as the rung between nothing and a ban, and a "my decisions" page — moderators
fill the staff register and could not read it.

Seven things that needed an administrator

  • The room's two emergency levers. Slow mode and scope lived behind
    requireAdminSession, while /slow was offered to all staff — a moderator
    could read the command, type it, and collect a 403 at the one moment a chat
    needs a hand. Both are now a button in the room header, on closed steps
    rather than a free integer, writing the same settings an administrator reads.
  • Renaming and moving a forum topic, which existed for nobody: the title
    and section were written once, by the insert that created the topic. A badly
    filed thread could only be deleted and reposted, which destroys the replies.
  • A member's warning history, on their profile, loaded for staff on open —
    it is what you read before deciding, not what you fetch once you doubt.
  • Editing a torrent comment. There was only delete, so one bad line in a
    long useful comment meant losing the whole thing. And the delete route had
    shipped with no button anywhere, for moderators or for authors.
  • Reports on comments and forum posts arrived as an unresolved id. They
    now carry an excerpt, their author, their date and a link that lands on the
    content — and say so plainly when it has been deleted since.

When staff rewrite your words, it says so

A moderator could already rewrite a member's forum post, and nothing marked
it: updated_at moves when an author re-reads themselves too, so it never
told the two apart. Both torrent comments and forum posts now carry who edited
them, shown beside the timestamp and notified to the member. A self-correction
stays unmarked — that is nobody's business.

Upholding a report no longer has to destroy the release

Resolving a torrent report always rejected the torrent. Half the report
categories are fixable rather than fatal — wrong label, bad metadata,
duplicate, dead — so a perfectly correct "the title says 1080p, the media info
says 720p" left the choice between killing a good release and telling the
reporter they were mistaken. Accepting now asks what the release deserves. Keep
it, and the reporter is thanked exactly the same way, the decision goes to the
torrent's moderation thread, and the uploader is left alone.

Fixes along the way

Defects that were already there, surfaced by the work above: the "no comments"
message showing underneath a full comment list; a report's reason column
collapsing to one character per line whenever a panel opened beside it; a raw
translation path rendered on the ban panel; six translation keys that never
existed, shown to members as key paths inside toasts; icon-button outlines
measured at 1.21:1 against WCAG's 3:1; and migrations 0077 and 0078 shipping
without a drizzle snapshot, which would have handed the next person to run
generate a migration re-creating what already exists.

Upgrading

Migrations 0077 and 0078 apply at API boot, as usual. No configuration
changes, no manual steps.

🤖 Generated with Claude Code

What's Changed

Full Changelog: 0.36.0...0.37.0

Release 0.36.0

Choose a tag to compare

@github-actions github-actions released this 10 Sep 20:48
9519636

28 commits since 0.35.0 — the torrent detail page and the catalogue rebuilt
around the questions their readers arrive with, and the review passes that
followed. As with 0.35.0, the findings that mattered came from probing the
running stack, not from static analysis
: semgrep and gitleaks were clean or
already arbitrated, while calling the changed routes without a session, then
with another member's session, is what turned up the leaks below.

The torrent detail page

The page used to answer "what is this release?" some 700px below the fold,
repeat every audio and subtitle track twice, and offer no way to say "wrong
film". It now opens on a hero band that names the work, with two columns where
the decision — download, cost, seed obligation — is pinned, swarm health read
from the collector, and a report affordance on the metadata itself. The work's
own colour, extracted from its poster, tints the band; the remaining motion is
limited to what carries meaning (the click, the fold, the numbers).

  • Hit & Run thresholds had no interface. The tracker and the API both read
    them; the 24 h shown on every torrent page was a code default. They are now
    in the admin panel, bounded, and read from one place.
  • A timed-out metadata lookup was cached as "no such record" for an hour.
    One network hiccup cost thirty posters until the entry expired. Timeouts,
    5xx, 429 and malformed JSON now share a 120 s error cache; only a real 404
    is remembered as an absence.
  • The poster relay accepted any client. No Sec-Fetch headers, any port,
    any image type — an SVG served from our own origin would have executed
    there. Now: same-origin image requests only, three CDN hosts, raster types,
    a 400 KB cap enforced during the stream, no redirects, 60/min per address.
  • A held-down D key downloaded on repeat; the shortcut now ignores key
    repeat, composition, open modals and editable targets.

The catalogue

A bar that understands what you type — s02e04, @member, a TMDb or IMDb
link, a resolution, a year — turning each into a removable chip; a rail of
facets that carry their counts; works before releases; and an empty state that
proposes what to drop rather than apologising.

A work card opens on its own header, with a chevron: the scope badge used to be
the only way in. Inside, seasons and episodes fold down to the releases, each
carrying its age with the exact date on hover.

  • Relevance ranking. A text search could only be sorted by age. Work titles
    are now indexed and searched, so a file named Sousou.no.Frieren.S01E10
    answers to "frieren", and a near miss gets a did-you-mean.
  • Saved searches match what the page filters — season, episode, year,
    uploader and tag families included — and their links replay exactly what was
    stored.
  • A hover menu on Torrents: shortcuts (everything, today, freeleech,
    favourites), this browser's recent searches, and the category tree. Opens on
    hover with an intention, on the chevron by click or keyboard, never on hover
    alone for a touch screen.
  • Per-instance catalogue defaults (view, sort, page size, visible facets), a
    record of searches that found nothing, and category merge — all in the admin
    panel.

Security and correctness

  • The abuse counter is per address, and the web container has one for the
    whole site.
    Four API calls per page view meant 25 page views in 10 seconds
    blacklisted the instance for everyone, for five minutes doubling to twenty-four
    hours. Server-side rendering is now recognised by a discriminant an Internet
    client cannot forge (private socket peer and no forwarding header), and
    only the coarse counter is relaxed — never the agent filter, the path
    validation, the address bans, the per-route limits or authentication.
  • anonymous_uploads was honoured by the detail page and the feeds but by
    none of the new catalogue paths.
    The listing returned the name,
    ?uploader=<name> listed an anonymous member's whole history, and a saved
    search on that name notified every one of their uploads.
  • "frieren" found one release out of nine. A dotted release name was a
    single token for the text-search parser, and the fuzzy fallback hid the hole
    whenever the exact search returned zero.
  • backdrop-filter written before its -webkit twin was dropped by the
    minifier.
    Five glass surfaces across the site had no blur in Chrome — a
    correct source and a stripped stylesheet.
  • Unbounded work, now bounded: tagGroups fanned out to one query per group on
    a pool of ten connections, search_misses grew a row per distinct empty
    search, and neither the listing nor the facets carried a rate limit.
  • Visibility: the did-you-mean read work titles with no veil, and two per-hash
    routes answered for a torrent the member cannot see.
  • Category merge failed with a 500 on its nominal case (both categories
    carrying an upload pattern), and could make an adult category's torrents
    visible to members who opted out.
  • Determinism: every sort now ends on a unique key, so a "load more" cannot
    repeat or drop a row; the year facet counts every year in a name with the
    same pattern the filter uses; and the metadata warmer no longer marks a work
    done for a week because the upstream was briefly unavailable.

Verified

nuxt typecheck and the API tsc clean; 558 API and 948 web unit tests; schema
parity between schema.ts and the 77 migrations; the end-to-end suite (13
scenarios, 543 checks) against images built from this branch; and a browser
pass at 1280 and 390, dark and light, with no horizontal scroll, no truncated
text, no dangling aria-controls and no failed asset.

🤖 Generated with Claude Code

What's Changed

  • Rebuild the torrent detail page and the catalogue around the reader's questions by @Dim145 in #30
  • Release 0.36.0 by @Dim145 in #31

Full Changelog: 0.35.0...0.36.0

Release 0.35.0

Choose a tag to compare

@github-actions github-actions released this 03 Sep 16:20
baa4297

101 commits since 0.34.0 — 20 features, 68 fixes, the rest docs, tests, CI
and two performance changes. Most of it came out of an audit pass over the
four apps, where the findings that mattered came from probing the running
stack, not from static analysis
: semgrep, trivy, gitleaks, gosec, hadolint,
actionlint, zizmor, govulncheck and osv-scanner were clean or already
arbitrated, while calling 131 GET and 90 write routes without a session — and
then with another member's session — was what turned up the real leak.

Security

  • safeFetch handed the caller's credentials to whoever asked for a
    redirect.
    The SSRF guard re-validated every hop, so no private range could
    be reached, but the caller's init was spread into every fetch — headers
    included. A public target answering 302 Location: https://elsewhere
    received the sixteen member-chosen webhook headers, the body HMAC, the ntfy
    Authorization or the object store's SigV4 signature. Fixed with an
    allowlist rather than the standard denylist, because the callers here set
    their own credential headers. A 307/308 to another origin carrying a body
    is now refused outright rather than replayed.
  • The freeleech pool named its top members to anonymous callers, with
    internal ids. The anonymous path existed for one field; the contributor list
    had come along with it.
  • The Caddy credential scrub in the access log had never matched a field —
    uri instead of request>uri — so passkeys had been written in cleartext
    since it was added, without a warning.
  • A banned member kept the account routes under /api/auth; an IP ban locked
    staff out of the very route that lifts it; the API served a CSP written for
    the web application.
  • The Torznab feed published the announce passkey to read-key holders, and a
    block a member could lift by rotating their own key.
  • encrypt was an unbounded oracle against the panic password, and a
    twelve-character password was derived at Node's defaults.
  • One address seen on five days read as five different places in the login
    history.
  • Postgres trusted the network in the reference compose, and Caddy had no body
    limit.

Performance

  • Byte credits no longer ride the announce. A member seeding 70 torrents
    was updating their own row 70 times per interval, and the increments are
    commutative. Measured over five minutes at 1 964 credited announces/s, 50 000
    members, steady state: WAL 113–125 MB → 45.8 MB at the 60 s default and
    7.5 MB at 300 s; statements 1.00 per announce → 0.038. Table and index sizes
    unchanged. Flushed in SMALL chunks, because a single-block flush defeats HOT
    pruning and writes more WAL than the individual writes it replaces.
  • Migration 0071 was measured wrong; 0072 corrects it. Its benchmark ran
    100 000 updates inside one DO block — one transaction — so nothing could
    prune and fillfactor looked guilty. Remeasured with one transaction per
    announce, hnr_tracking reaches full HOT at 95 for +0.5% of table, where 70
    cost +35% for the same result.
  • The swarm-cache ceiling counted traffic rather than memory; a hundred
    concurrent peer ids could reach a hundred GiB/s of credit; an unauthenticated
    scrape could reach Postgres.

Features

Members — a copy of your own record and what it deliberately leaves out;
three keys instead of one doing three jobs; alerts on saved searches; "sign out
everywhere"; where an account has been signed in from; drafts kept until sent.

Tracker — BEP 52: the second infohash a hybrid torrent announces under; a
partial seed is not a seed; an operator can close UDP scrape (BEP 15 has no
field for authentication in a scrape request, so a private tracker closes the
door rather than guarding it).

Torznab / integrations — a Prowlarr definition generated per instance; the
seeding rules a client was never told, and rates that were a lie; one line in
an IRC channel and the parser for it in the same breath.

Staff — who vouched for them and who they let in; an audit register across
the whole console; the moderation queue says which of these are yours; a
freeleech you can put on a single torrent.

Site — a statistics page, the site looking at itself; installable as a PWA,
with an icon size nobody made up; three things a catalogue needs when it gets
old.

Interface and accessibility

An interface pass produced five hundred findings; four systemic faults were
behind most of them
and were fixed at the root. Since then: 49 form controls
had no accessible name, then 24 more that a first crude count had put at ~300 —
the difference was three classes of false positive, now documented in the test
that guards the number. A modal with no focus trap and a group row only a mouse
could open. A sanitiser that passed a phishing link and ate legitimate
attributes. Two locales hardcoded in a bilingual site, an error page in
English, numbers formatted in the wrong locale. Two pages that shipped the
server's clock and corrected it on hydration. document.write on the
recovery-codes screen. Three pages rendering bare native buttons.

Correctness

  • A failed seed-time write blocked its own retry for 900 seconds.
  • Closing the UDP socket stopped the read loop but not the goroutines writing
    to Postgres; shutdown now drains them.
  • A topic author could delete everyone else's replies.
  • A takedown left the catalogue, the page and the file untouched.
  • The IRC queue paced nothing and two thirds of announces never left.
  • stop() then start() left an orphan message stream delivering twice.
  • A relay race unsubscribed a channel for good, and the drain signalled nobody.
  • A post from twelve minutes ago read as two hours old — db.execute silently
    drops the timestamp parser that db.select honours.
  • 40 routes where a malformed query parameter answered 500 instead of a
    readable 400.
  • A case-different username was a second account, and the foreign keys had no
    index.
  • One proof of work could be spent twice.
  • The dashboard's tracker light was a constant, not a status.
  • Saved searches rebuilt their link with the API's parameter names, so the
    saved URL never reopened the search.
  • The integration suite never connected Redis, so eleven failures per CI
    job were invisible — and worse, the tests were not exercising what they
    appeared to: account erasure passed while its Redis half did nothing at all.

Operators

Two new settings, both on the tracker, both optional:

Variable Default What it does
TRACKER_STATS_FLUSH_INTERVAL 60s Byte-credit batching window. 0s restores the per-announce write. 300s divides WAL by 15 at the cost of five minutes of staleness on the counters a member sees.
TRACKER_STATS_FLUSH_CHUNK 10 Members written per transaction during a flush. Do not raise without measuring — past a few dozen, a flush writes more WAL than what it replaces.

TRACKER_UDP_SCRAPE_ENABLED shipped in 0.34.0's successor work but was never
in the env reference or the Helm chart; both are corrected here
(tracker.udp.scrapeEnabled).

15 migrations (0058–0072), applied at boot. Verified to apply to an empty
database: 204 tables, 1 672 columns, 578 indexes.

Shutdown grace period. The tracker now flushes byte credits (up to 10 s)
and drains in-flight UDP datagrams (up to 5 s) before the existing HnR drain
(8 s) — 33 s worst case. Compose gained stop_grace_period: 45s; the chart's
terminationGracePeriodSeconds moved 30 → 45. Docker's 10 s default was
already cutting the HnR drain short before this release.

A NetworkPolicy is now shipped with the chart.

After upgrading

Migration 0072 sets fillfactor on hnr_tracking but deliberately rewrites
nothing — a boot-time migration must not take an exclusive lock on a table in
the announce path. To realise it on existing pages:

pg_repack --no-order --table public.hnr_tracking -d trackarr

Online, no maintenance window. VACUUM FULL public.hnr_tracking is the
alternative and takes an ACCESS EXCLUSIVE lock.

Verification

Every CI gate, plus what CI cannot reach. The behavioural claims above were
checked against a running compiled stack rather than read off the source: the
Caddy scrub with a canary, the forum cascade, session revocation across two
devices, the credit-budget clamp (2 000 GiB claimed → 68 GiB credited), the UDP
scrape switch in both positions, the byte-credit batching against a real
tracker with real announces, and the redirect header-stripping against real
undici with two servers on two origins.

🤖 Generated with Claude Code

What's Changed

  • Audit pass over the four apps: BEP 52, GDPR, PWA — and what probing the running stack found by @Dim145 in #28
  • Release 0.35.0 by @Dim145 in #29

Full Changelog: 0.34.0...0.35.0

Release 0.34.0

Choose a tag to compare

@github-actions github-actions released this 30 Aug 22:58
cf6194e

Messaging: private messages, a public room, and a ticket desk — with the Go relay that fans out to all three.

What ships

Private messages. First contact lands in a request queue rather than the inbox, so a stranger cannot put anything in front of you without your say-so. Optional per-conversation encryption, sealed in the browser. Reactions, replies, edits, read receipts, archiving, muting and search across everything the server can actually read.

A public room. Partitioned by day, with slow mode, mutes, pins and staff broadcasts.

A ticket desk. A member writes to the staff as a body rather than to a person — which is the point: somebody who has blocked the moderator on shift can still reach the desk. Assignment happens by answering. Closed is closed in both directions, with a staff-only reopen as the escape hatch.

Live delivery. A Go relay, fanning out over SSE, with per-user channels and a token the API mints.

The rules it holds to

  • Nothing is on by default. Each surface has its own switch, and an instance running none of them looks like a build that never had them: 404, not 403.
  • Blocking closes every door but the one to the staff.
  • What the staff can read is logged. One route reads a reported private message, never a second one, never a ciphertext, and every read is on a page the whole team can see.
  • Retention is published and enforced. /privacy reads the live settings rather than repeating numbers, and each sweep now has tests — which is how the one that had never deleted a row was found.

Verified before the merge

e2e, clean database 537 across 13 scenarios
Unit 765 web · 429 API
Integration 463
Typecheck API and web templates, 0
Migrations upgrade rehearsal: a database built from main's 49 and brought up on 58 is byte-identical to a fresh one

Operators

Nine migrations (0049–0057). Everything starts switched off; the messaging settings live in the admin panel. The relay is optional — without it the surfaces work by reloading, which is how they were built.

🤖 Generated with Claude Code

What's Changed

Full Changelog: 0.33.0...0.34.0

Release 0.33.0

Choose a tag to compare

@github-actions github-actions released this 28 Aug 19:46
8a97f2e

Release 0.33.0. Version bumped in the six places the previous releases move; no other change.

What is in it

Configurable themes. 47 tokens over thirteen groups — surfaces, text, lines, accents, status, charts, elevation, shape, density, typography, motion, chrome — edited on their own admin page, with a live preview and contrast warnings. Colours are RGB triplets so Tailwind's <alpha-value> keeps working, and five of the tokens are multipliers folded into hand-tuned values through calc(), which makes 0 a feature: shadow-strength: 0 is a flat interface, motion-scale: 0 a still one.

An owner distinct from the other administrators. Federation governance, uploaded fonts and free-form CSS are owner-only and re-authenticate; ownership transfers under an advisory lock and can be relinquished.

Owner-authored CSS, parsed rather than filtered. css-tree builds an AST, the rules are structural, and the output is regenerated from the AST so a browser never sees the bytes that were typed.

Self-hosted fonts. fonts.googleapis.com and fonts.gstatic.com are gone from every Content-Security-Policy; no visitor's IP reaches Google. Owners can upload a woff2 face for a role.

A site default that applies to somebody. users.theme is nullable and NULL means "follow the site default" — so changing the default moves everyone who never chose, which it previously did for nobody.

Upgrading

doc/guide/upgrading.md has the section. The short version: nothing to run, and do not run two versions against the same database at once while the theme migration lands. A single instance that stops and starts is unaffected.

Verified

typecheck clean · 423 API unit · 519 web unit · 449 integration · 178 end-to-end, across both build shapes (front-ssr and the static front).

🤖 Generated with Claude Code

What's Changed

Full Changelog: 0.32.0...0.33.0

Release 0.32.0

Choose a tag to compare

@github-actions github-actions released this 27 Aug 14:35
fe8042d

Version bump only. Everything in this release landed in
#24.

Minor rather than patch: two migrations, and behaviour that changes on
upgrade.

What it fixes

A code review of the 0.31.0 tag found 28 defects. The three worst:

  • The public outbox published the member-identity channel, so a
    crawler could turn a pseudonymous upload history into a name and a home
    instance with one join.
  • Any partner accepting the accounts channel could credit any member,
    for any window, twice.
  • An erased account got a fresh signing key on the next mint sweep, and
    erasure had never reached the catalogue this instance serves.

Before upgrading

Two migrations, 0043 and 0044. Both additive, both applied at boot.

FEDERATION_REQUIRE_AUDIENCE now also governs relay
countersignatures.
Nothing to do — records carry both the bare and the
bound proof, so a mesh mid-upgrade works in both directions and the flag
stays off. Turning it on before every partner runs this build stops
accepting their relayed records silently: a refused record is sourced as
rejected and never asked for again, so the catalogue just stops growing.
doc/guide/upgrading.md has the detail.

🤖 Generated with Claude Code

What's Changed

  • Fix everything the 0.31.0 code review found by @Dim145 in #24
  • chore(release): 0.32.0 by @Dim145 in #25

Full Changelog: 0.31.0...0.32.0

Release 0.31.0

Choose a tag to compare

@github-actions github-actions released this 27 Aug 08:17
ae1b2f6

Minor, not patch. PR #22 landed the signed-records federation branch — 197 files, +93 590 lines, 10 migrations.

Federation

Three feeds became one stream of signed records, ingested on their proof rather than on the channel they arrived through, and reconciled as a set instead of followed by a cursor. On that spine:

  • BitTorrent v2 content addressing — the data-sharing spine
  • a request → fill bridge, turning a mirrored release into a request
  • cross-seed assist: surface the same content on partners
  • a mesh availability signal (rather than peer injection)
  • an inter-instance credit model — the settlement layer
  • local moderation of federated content, and member reports on mirrored releases
  • partner category slugs bridged to local categories
  • opt-in federated releases in the Torznab and RSS feeds

Identity became portable

A member can hold their own key, prove a past account instead of asking an admin for it, publish the link so a catalogue survives its author, withdraw a leaked key, and rotate without orphaning earlier work. GDPR erasure anonymises locally and retracts the federated identity.

No operator action

Both destructive statements in the new migrations are idempotent and need nothing from you: federation_catalog_removals is dropped because the mechanism it served was retired, and an unused foreign key on torrents goes with the erasure work.

The chart is untouched by this release, so its own version stays 0.2.1 and only appVersion follows the application.

Verified on the merged tree

install (frozen lockfile) OK
typecheck clean
unit tests 28 api + 13 web files, 595 tests (up from 471)
schema parity gate (real 18.6) schema.ts and the migrations agree · 43 journal entries, no orphan · chain applies to an empty database: 62 tables, 561 columns, 190 indexes
Helm chart lints, renders in default / production / S3
docs build

Also checked the trap a long-lived branch sets: merged repeatedly from main over weeks, its migrations could have carried when timestamps older than entries already applied, and drizzle would have skipped them in silence. The journal is monotone — it did not bite.

🤖 Generated with Claude Code

What's Changed

  • feat(federation): signed records, data sharing, and the credit model by @Dim145 in #22
  • chore(release): 0.31.0 by @Dim145 in #23

Full Changelog: 0.30.1...0.31.0

Release 0.30.1

Choose a tag to compare

@github-actions github-actions released this 24 Aug 23:31
0f348dd

Fixes on top of 0.30.0. Nothing needs operator action.

Federation was showing when there was none

Three places offered federation on an instance that is not federated:

Where Now
Federated link in the main nav hidden
Federated identity in the user menu hidden
Per-torrent swarm-federation switch on a torrent page hidden

And the federation pages themselves (/federated, /federated-identity, remote torrent detail) render an explicit "Federation is off" instead of an empty UI — pointing admins at the switch rather than leaving them to find it.

The flag travels through /api/branding, which the layout already fetches on every page, so gating costs no extra round trip. Cached for a minute so that endpoint keeps costing no query and keeps answering through a brief database hiccup — which every other field there already did. Deliberately not cached for getFederationConfig's other callers: inbound.ts gates authorisation on it, and a stale authorisation check is not the same kind of stale as a nav item.

Page backdrops were clipped to the content column

Eleven pages anchored their atmospheric backdrop to their centred max-width column instead of the viewport, so gradients and grain stopped short of the edges with a visible boundary. Reported on /invites and /reports.

Measured on /reports at 1440×900, flipping the property in the page:

absolute   x=176 y=81   1080x951     ← 352px of horizontal clipping
fixed      x=0   y=0    1432x900     ← the viewport, less the scrollbar

shop.vue already used position: fixed — its comment even says "fixed position so it doesn't scroll". The other eleven now match it.

Helm chart

RustFS credentials are generated rather than typed in, the way Valkey's password already was. --set storage.driver=s3 --set rustfs.enabled=true now renders with nothing else supplied. Chart version 0.2.1.

Verified

On a running stack, federation off then on without a restart: the nav entries, the menu entry and the torrent section appear and disappear together, and the disabled pages render the explanation. Typecheck clean, 471 tests passing, chart lints and renders in default, production and S3 modes, docs build.

🤖 Generated with Claude Code

What's Changed

Full Changelog: 0.30.0...0.30.1