Commit Graph
4 Commits
Author SHA1 Message Date
bvandeusenandClaude Opus 5.5 29042e4eb2 feat: a new Minstrel restores a backup, from the web app or the command line, then rescans (#6775)
release / govulncheck (push) Successful in 29s
release / web (push) Failing after 1m21s
release / go (push) Successful in 1m39s
release / integration (push) Successful in 5m26s
release / android (push) Successful in 5m27s
release / Build signed APK (push) Successful in 5m44s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
A new server (no accounts, no library) offers "Restore a backup" beside
"Create account". The file goes up as the request body to
POST /api/auth/restore with the setup token from the log in X-Setup-Token,
since the backup's admins become this server's. It loads in one transaction,
then a library scan starts and finds each file at its path. The same load runs
offline as `minstrel restore FILE`, which migrates the schema first.

That path alone gets a 1 GiB, 15-minute body allowance instead of 4 MiB and
30s, and skips the JSON-only check for cookie writes: a stale cookie from the
old server would otherwise refuse the upload, and the custom header already
rules out a cross-site form.

The library sync answers 410 cursor_ahead to a cursor past the last one the
server has handed out, read from the change sequence, so an app synced with
the old server after the backup was made starts over instead of skipping the
ids the restored server reuses. Restore now leaves each sequence as having
issued the restored maximum, so that reading holds; a compacted log still
counts its handed-out cursors.

backup.Read keeps the underlying error in its chain, so an oversized upload
answers 413 rather than "not a backup". The hosting docs say how to put the
music back first and what is entered again afterwards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 16:10:06 -05:00
bvandeusenandClaude Opus 5.5 d191a28217 docs: server comments say what the code does, not how it came to be (#6277)
Comment blocks in cmd/minstrel and 30 smaller internal packages, rewritten
to rule 32's standard with each citation read in context: task, milestone
and rule ids go, and so do the stories they were attached to ("used to",
"Before #N", measurements from one install, quotes, pointers to Fabled
Librarian's records and to docs/superpowers files that are not in the
repo). Where a story wrapped a real constraint, the constraint stays as a
present-tense fact.

Comments that had gone stale are corrected:
- taste.Config: the tuned values are overlaid by internal/recsettings;
  it is no longer "a later follow-up".
- StorageConfig: the data dir holds art, caches and the stream secret,
  not only playlist covers.
- the Subsonic album list and getUser comments describe what they do
  today rather than a milestone that would change it.

Checked: the non-comment token stream of every changed file matches
HEAD, and gofmt -l is empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 12:15:05 -04:00
bvandeusenandClaude Opus 5.5 d411693bb2 feat(server): security headers and a hash-based CSP for the web app (M462 #4980)
test-web / test (push) Successful in 55s
test-go / test (push) Successful in 1m14s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / Build signed APK (releases and dev) (push) Canceled after 2m50s
test-go / integration (push) Canceled after 2m50s
There were no security headers at all. Now:
- every response: nosniff, Referrer-Policy strict-origin-when-cross-origin,
  Permissions-Policy (no camera/mic/geolocation), X-Frame-Options DENY;
  each set only if the handler hasn't.
- HSTS only when the trusted proxy reports HTTPS (rule 94); never a redirect.
- index.html carries a Content-Security-Policy whose script-src is 'self'
  plus the sha256 of each inline script in the page as served, computed
  after the branding template runs. No 'unsafe-inline' or 'unsafe-eval'
  for scripts. img-src admits remote https/http because Lidarr suggestion
  art is a remote poster URL.

Hashing in Go rather than via SvelteKit's kit.csp covers the inline scripts
SvelteKit doesn't know about (app.html's theme bootstrap and the branding
global injected at build) and stays correct whatever the app name is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:29:26 -04:00
bvandeusenandClaude Opus 5.5 24b767c23b feat(server): cap request bodies, bound body reads, private cache headers, JSON-only cookie writes (M462 #4979)
test-go / test (push) Successful in 1m48s
test-web / test (push) Successful in 2m0s
android / Build + lint + test (push) Successful in 6m27s
release / Build signed APK (releases and dev) (push) Successful in 6m42s
test-go / integration (push) Successful in 4m55s
release / Build + push container image (push) Successful in 1m20s
release / Verify release artifacts (tag releases only) (push) Skipped
- Every request body is capped at 4 MiB and must arrive within 30s. The
  deadline is set per request and cleared at end of body rather than via
  http.Server.ReadTimeout, which would cancel audio streams and the SSE
  stream once the background read hit it.
- IdleTimeout 120s closes idle keep-alive connections. Still no global
  WriteTimeout, for the same streaming reason.
- Streams, album covers and playlist covers are Cache-Control: private, so
  a shared cache never keeps an authenticated response for others.
- A cookie-authenticated write to /api must be application/json (415
  otherwise). SameSite=Strict can't see a sibling app on the same
  registrable domain; forms and no-preflight fetches can't send JSON.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 08:34:26 -04:00