diff --git a/docs-developers/contributing/documentation.md b/docs-developers/contributing/documentation.md index bdc8377a..01c55f6b 100644 --- a/docs-developers/contributing/documentation.md +++ b/docs-developers/contributing/documentation.md @@ -96,7 +96,7 @@ mappings: | Downloads / PWA, the offline companion (`offline-meta.ts`), podcast episode downloads (`src/downloads/episode-ready.ts`, `src/playback/episode-fetch-rules.ts`, `autoDownloadEpisodes`) | [frontend/offline.md](../frontend/offline.md) (+ [frontend/podcasts.md](../frontend/podcasts.md#downloading-episodes) for episodes) + `/users/listening/offline-downloads` (+ `/users/listening/podcasts` and `/users/listening/settings` for episodes) + the `web-player/downloads.png` screenshot | | The online flag and the offline states (`src/lib/device-online.ts`, `offlineState`, `useUnreadQuery` and `useUnreadBooks` in `src/lib/offline-state.ts`, `OfflineState`, the `onlineManager` wiring and the `mutations` default in `provider.tsx`), the kept `/server` answer (`src/api/server-snapshots.ts`), the library the Library is on (`resolveLibrary`, `resolveCarLibrary`, `useSelectedLibrary`, `LibraryPicker`) | [frontend/state-and-data.md](../frontend/state-and-data.md#the-online-flag-srclibdevice-onlinets) ([Offline states](../frontend/state-and-data.md#offline-states-srcliboffline-statets), [the kept `/server` answer](../frontend/state-and-data.md#the-kept-server-answer-srcapiserver-snapshotsts), [Library selection](../frontend/state-and-data.md#library-selection-srcstoreslibrary-selectionts)) + [frontend/library-and-browse.md](../frontend/library-and-browse.md) + [frontend/offline.md](../frontend/offline.md#listening-sync-the-outbox) (which path listening takes, the companion's seeding, keep-ahead's held reads) + [frontend/native-integrations.md](../frontend/native-integrations.md) (the car's `library` tab) + [frontend/reader.md](../frontend/reader.md#the-map) and [frontend/player-ui.md](../frontend/player-ui.md) (the Text tab's `offline`) + [frontend/journal.md](../frontend/journal.md) (`unreadSources`) + [frontend/you-and-settings.md](../frontend/you-and-settings.md) (`useYearStory`'s `offline`) + `/users/listening/offline-downloads` (With no connection), `/users/listening/browsing`, `/users/listening/in-the-car`, `/users/listening/companion`, `/users/troubleshooting` | | The web start: async routes, `deferred`/`afterPaint`, the root's services (`root-services*.tsx`), the startup cache (`src/api/startup-cache.ts`), the startup queries (`startup-queries.ts`), the start scripts' compile hint and parts (`scripts/web-start-chunks.cjs`), first-screen covers, the web's language chunks (`catalogs.web.ts`), `sw.js`'s build precache | [frontend/web-build.md](../frontend/web-build.md#the-web-start-a-split-build) + [offline.md](../frontend/offline.md#publicswjs) + [state-and-data.md](../frontend/state-and-data.md#the-startup-cache-web) + [i18n.md](../frontend/i18n.md) + `/users/listening/browsing` (the home screen's note), `/users/listening/offline-downloads` (the PWA), `/users/listening/settings` (Language) | -| First-run setup: the `/setup` page (`handlers_setup.go`, `assets/setup.*`), the console's Set up your server flow and Overview card (`features/setup/`, `finish-setup-card.tsx`), `catalog/setup.go`, `library/preview.go`, `library/mounts.go`, `handlers_admin_setup.go` | `/users/getting-started/first-run` (a section and shot per step) + the quickstarts' setup step (`/users/getting-started/quickstart-docker`, `install-unraid`, `install-binary`) + `/users/admin/console-tour` (the card) + [web-ui.md](../server/web-ui.md#the-first-run-setup-flow) (the flow and the `/setup` page) + the [reference](../server/api/reference.md#admin-first-run-setup) (and `POST /setup`) + [api/index.md](../server/api/index.md#error-envelope-and-status-conventions) (the codes) + [data-model.md](../server/data-model.md#server-state) (`setup_state`, `0053`) + [configuration.md](../server/configuration.md) (`--setup`) + [backups-and-notifications.md](../server/backups-and-notifications.md#audit-log) (`settings.setup`) + the `server/setup-wizard*.png` and `setup/*.png` screenshots (`capture-setup.mjs`: `SHOTS_ONLY=setup/,server/setup-wizard screenshots/run.sh`) | +| First-run setup: the `/setup` page (`handlers_setup.go`, `assets/setup.*`), the console's Set up your server flow and Overview card (`features/setup/`, `finish-setup-card.tsx`), `catalog/setup.go`, `library/preview.go`, `library/mounts.go`, `handlers_admin_setup.go`; the first-run admin after its banner (`pkg/launcher/firstrun.go`: `reset-admin` and the sign-in hint, `auth/firstrun.go`, the console's login hint) | `/users/getting-started/first-run` (a section and shot per step, and If you lose the password) + `/users/troubleshooting` (I lost the first-run admin password) + the quickstarts' setup step (`/users/getting-started/quickstart-docker`, `install-unraid`, `install-synology`, `install-binary`) + `/users/admin/console-tour` (the card) + [web-ui.md](../server/web-ui.md#the-first-run-setup-flow) (the flow and the `/setup` page) + the [reference](../server/api/reference.md#admin-first-run-setup) (and `POST /setup`) + [api/index.md](../server/api/index.md#error-envelope-and-status-conventions) (the codes) + [data-model.md](../server/data-model.md#server-state) (`setup_state`, `0053`) + [configuration.md](../server/configuration.md) (`--setup`, the commands) + [backups-and-notifications.md](../server/backups-and-notifications.md#audit-log) (`settings.setup`) + the `server/setup-wizard*.png` and `setup/*.png` screenshots (`capture-setup.mjs`: `SHOTS_ONLY=setup/,server/setup-wizard screenshots/run.sh`) | | The ebook reader: the server's `ebook` field, `handlers_ebook.go`, `library/ebook.go` (`BookEbook`, `OpenEbook`), `catalog/reading_progress.go` (`ResolvePlace`), `web.readerCSP`; the player's `reader/` (the reader document, `scripts/build-reader.mjs`), `src/reader/` (the web and app hosts, the app's EPUB cache and chunked transfer), Read on the book page, the ebook in downloads (`manifest.ebookUri`) | [frontend/reader.md](../frontend/reader.md) + [frontend/offline.md](../frontend/offline.md) + the [reference](../server/api/reference.md#ebooks-and-the-reading-place) + [server/web-ui.md](../server/web-ui.md#the-reader-document-readercsp) + [scanner.md](../server/scanner.md#a-books-ebook) + the [cross-repo contract](../architecture/cross-repo-contract.md#18-the-ebook-reader) + `/users/listening/reading-the-ebook` (+ `/users/listening/keyboard-shortcuts` for its keys, `/users/listening/offline-downloads` for the ebook in downloads) + the `web-player/reader*.png` and `book-read.png` screenshots | | Scanner, detection, metadata | [server/scanner.md](../server/scanner.md) + `/users/getting-started/organizing-your-library` (+ [configuration.md](../server/configuration.md#library-scans-scan) and `/users/admin/libraries#rescanning` for `scan.concurrency`) | | Community chapters (`internal/chapteralign`, `internal/chaptercheck`, `catalog/communitychapters.go`, the Chapters card's panel) | [server/community-chapters.md](../server/community-chapters.md) + the Community chapters section of `/users/admin/books` (and `/users/admin/health` for the Health rows) | @@ -114,7 +114,7 @@ mappings: | Upcoming releases (Track U): the server's `internal/upcoming` (with `ics.go`, the calendar), `catalog/upcoming.go`, `meta/upcoming.go`, `handlers_upcoming.go`, `handlers_upcoming_calendar.go`, `auth/feed.go` (the `feed` token kind, `0060`), the `upcoming.*` config, the `upcoming`, `upcoming_calendar` and `upcoming_covers` capabilities, `release_date` on the `/meta` rails, `book_released`; the console's dated series gaps (`series-model.ts` `releaseState`, `series-card.tsx`) and Settings > Metadata's upcoming switch | the [reference](../server/api/reference.md#upcoming-releases) (and `release_date` on [`/meta`](../server/api/reference.md#get-apiv1librariesidmeta), `metadata.upcoming` on [`/admin/settings`](../server/api/reference.md#get-apiv1adminsettings)) + [api/index.md](../server/api/index.md#capability-flags---gate-your-features) (the flags, `upcoming_off`, `metadata_off`, `kid_account`, `demo_account`, `not_found`, `cover_unavailable`, the feed token and the `?token=` scope) + [auth-and-security.md](../server/auth-and-security.md#calendar-feed-tokens) (feed tokens; also `invariants.md`'s confined token-in-URL and `media.md`) + [configuration.md](../server/configuration.md#upcoming-releases-upcoming) + [data-model.md](../server/data-model.md#upcoming-releases) (`0059`, `0060` and the `tokens` kinds, the `upcoming` server state) + [backups-and-notifications.md](../server/backups-and-notifications.md#events) + the [cross-repo contract](../architecture/cross-repo-contract.md#8-capability-flags-get-apiv1server) (after the workspace `CROSS-REPO.md` §8 and §17) + `/users/admin/books#series`, `/users/admin/server#community-metadata`, `/users/admin/notifications` + the `admin/series.png` and `admin/settings-metadata.png` screenshots. The player's side (Track U2: `src/components/upcoming/`, `useUpcomingAll`, `src/lib/upcoming.ts`, `release-date.ts`, `formatReleaseDate`, the series page's dated ghosts, the You hub's Upcoming section; Track U3: `UpcomingCover`, `UpcomingCalendarCard`, `src/lib/upcoming-calendar.ts`, the calendar hooks and the device copy of the link): [state-and-data.md](../frontend/state-and-data.md#upcoming-releases) (and [The upcoming calendar](../frontend/state-and-data.md#the-upcoming-calendar)) + [testing.md](../frontend/testing.md) + the route map in [routes.md](../frontend/routes.md) + [you-and-settings.md](../frontend/you-and-settings.md) + `/users/listening/upcoming`, `/users/listening/browsing` (Home, series pages), `/users/listening/you` + `/users/admin/server#community-metadata` (the calendar's public-address note) + the `web-player/home-upcoming.png`, `upcoming.png`, `phone-upcoming.png` and `upcoming-calendar.png` screenshots (the capture's upcoming pass in `capture-web.mjs`) | | Listening sync (Sync v2, Track W1): the server's `catalog/listening_sync.go`, `handlers_listening_sync.go`, the `listening_sync` capability, `listening_events` (`0061`) and the progress columns, `device_name` / `other_place` on progress, the `put` event of a `PUT`, sessions and history from spans (`recordSpan`); the player's `src/playback/listening-outbox.ts`, `listening-sync.ts`, the spans in `store.ts`, `ack_version` in `progress-sync.ts`, `offerOf` and the offer toast, `listening-offer.tsx`, `listening-sync-card.tsx`, the unsynced warnings on sign-out, removing a server and removing a person | the [reference](../server/api/reference.md#post-apiv1melisteningsync) (and the progress routes' `device_name` / `other_place`) + [api/index.md](../server/api/index.md#capability-flags---gate-your-features) (the flag, `too_many_events`) + [data-model.md](../server/data-model.md#durable-per-user-state-path-keyed-no-fk-to-books) (`listening_events`, the progress columns, `0061`, [sessions](../server/data-model.md#listening-sessions-how-they-are-derived)) + [auth-and-security.md](../server/auth-and-security.md#the-listeners-own-state-me) + the [cross-repo contract](../architecture/cross-repo-contract.md#19-listening-sync-sync-v2) (after the workspace `CROSS-REPO.md` §20 and §25) + [frontend/offline.md](../frontend/offline.md#listening-sync-the-outbox) + [playback.md](../frontend/playback.md#listening-spans-sync-v2) (the spans, the offer) + [state-and-data.md](../frontend/state-and-data.md#progress-sync-srcplaybackprogress-syncts) + [you-and-settings.md](../frontend/you-and-settings.md#account-srccomponentsaccount) + [book-page.md](../frontend/book-page.md#the-primary-action-and-the-action-row) + `/users/listening/playback#listening-on-more-than-one-device`, `/users/listening/account#listening-sync`, `/users/listening/book-page`, `/users/listening/browsing` (Home's line), `/users/listening/offline-downloads`, `/users/listening/settings` and `/users/listening/profiles` (the remove warnings), `/users/admin/activity` + the `web-player/account.png`, `account-listening-sync.png` and `book-place-offer.png` screenshots | | Generated characters, recaps and descriptions: `.audiosilo/` (the server's `library/attachments.go`, `metadata/attachments.go`, `catalog/attachments.go`, `meta/generated.go`; the Bridge's `internal/generate`, `internal/pipeline/describe_stage.go`), the console's Description part (`describe-section.tsx`) or the player's generate action, mark and About (`src/components/bridge/`, `GeneratedMark`, `aboutContent`) | [bridge/attachments.md](../bridge/attachments.md) + [bridge/generating.md](../bridge/generating.md) + [server/scanner.md](../server/scanner.md#the-bridges-attachments) + the `/meta` [generated content](../server/api/reference.md#generated-content) + [frontend/book-page.md](../frontend/book-page.md#the-primary-action-and-the-action-row) (and its About) + `/users/admin/generating-characters-and-recaps`, `/users/listening/recaps-and-characters`, `/users/listening/book-page` (About) + the `admin/book-description.png` and `web-player/book-about-generated.png` screenshots | -| Community matching: `internal/matchrun` (`Classify`, `ReviewFloor`, `tidyTitle`, `workers`, `maxRequestsPerSecond`, `maxFailStreak`), `internal/meta/match.go`, `internal/meta/pacer.go`, a run's waits in `internal/meta/client.go`, the tag hints (`metadata.setHints`, `0067`), the recorded match (`catalog/bookmatches.go`, `book_matches`, `0069`, `0070`, `catalog.matchedExpr`, the book aside's Community card and Unlink, the match dialog's `match`), `health/bulk-match.tsx`; `works/match`'s params and `expand` in audiosilo-meta | the [reference](../server/api/reference.md#bulk-community-matching) (outcomes, scopes, [what a run asks of the service](../server/api/reference.md#what-a-run-asks-of-the-community-service)) and its [match endpoint](../server/api/reference.md#get-apiv1adminlibrariesidbookmatch) + [configuration.md](../server/configuration.md#community-metadata-metadata) (the waits) + [data-model.md](../server/data-model.md#tag-hints) (and `book_matches` under [durable per-library config](../server/data-model.md#durable-per-library-config-path-keyed-no-fk-to-books)) + [scanner.md](../server/scanner.md) + [web-ui.md](../server/web-ui.md) + [meta/api.md](../meta/api.md#apiv1worksmatch) (and its [rate limit](../meta/api.md#rate-limit-and-caching), which names `workers` and `maxRequestsPerSecond`) + the [cross-repo contract](../architecture/cross-repo-contract.md#13-community-metadata-a-three-repo-seam) (after the workspace `CROSS-REPO.md` §17) + `/users/admin/health#matching-every-book-at-once`, `/users/admin/books#matching-with-community-metadata` (and `#books-matched-to-a-store-listing`, `#unlinking-a-community-match`), `/users/admin/activity` (the coverage wording), `/users/admin/server#community-metadata`, `/users/faq` (what matching sends) + the site's `src/pages/metadata.astro` (privacy copy, kept in step with the console's `health.bulkMatch.privacy`) + the `admin/health-bulk-match.png` and `admin/book-community-match.png` screenshots | +| Community matching: `internal/matchrun` (`Classify`, `ReviewFloor`, `tidyTitle`, `workers`, `maxRequestsPerSecond`, `maxFailStreak`), `internal/meta/match.go`, `internal/meta/pacer.go`, a run's waits in `internal/meta/client.go`, the tag hints (`metadata.setHints`, `0067`), the recorded match (`catalog/bookmatches.go`, `book_matches`, `0069`, `0070`, `catalog.matchedExpr`, the book aside's Community card and Unlink, the match dialog's `match`), `health/bulk-match.tsx`, the Add it link (`lib/meta-site.ts`, `add-to-community.tsx`) and the not-matched notice (`unmatched-prompt.tsx`); `works/match`'s params and `expand` in audiosilo-meta | the [reference](../server/api/reference.md#bulk-community-matching) (outcomes, scopes, [what a run asks of the service](../server/api/reference.md#what-a-run-asks-of-the-community-service)) and its [match endpoint](../server/api/reference.md#get-apiv1adminlibrariesidbookmatch) + [configuration.md](../server/configuration.md#community-metadata-metadata) (the waits) + [data-model.md](../server/data-model.md#tag-hints) (and `book_matches` under [durable per-library config](../server/data-model.md#durable-per-library-config-path-keyed-no-fk-to-books)) + [scanner.md](../server/scanner.md) + [web-ui.md](../server/web-ui.md) + [meta/api.md](../meta/api.md#apiv1worksmatch) (and its [rate limit](../meta/api.md#rate-limit-and-caching), which names `workers` and `maxRequestsPerSecond`) + the [cross-repo contract](../architecture/cross-repo-contract.md#13-community-metadata-a-three-repo-seam) (after the workspace `CROSS-REPO.md` §17) + `/users/admin/health#matching-every-book-at-once`, `/users/admin/books#matching-with-community-metadata` (and `#books-matched-to-a-store-listing`, `#unlinking-a-community-match`), `/users/admin/activity` (the coverage wording), `/users/admin/server#community-metadata`, `/users/faq` (what matching sends), `/users/community/meta-site#adding-a-missing-book` (Add it) + the site's `src/pages/metadata.astro` (privacy copy, kept in step with the console's `health.bulkMatch.privacy`) + the `admin/health-bulk-match.png` and `admin/book-community-match.png` screenshots | | The unknown-ASIN report (LibraForge C3): the server's `internal/coverage`, `catalog/coverage.go` (`UnknownASINCandidates`), `meta/report.go`, `unknown_asin_reports` (`0068`), the `unknown_asin_report` server state, `metadata.report_unknown` and `metadata.report_status`, Settings > Metadata's *Help complete the community database* switch; metaserve's intake queue | [configuration.md](../server/configuration.md#the-unknown-asin-report-metadatareport_unknown) + the [reference](../server/api/reference.md#get-apiv1adminsettings) (`metadata.report_unknown`, `metadata.report_status`) + [data-model.md](../server/data-model.md#unknown-asin-reports) + the [cross-repo contract](../architecture/cross-repo-contract.md#13-community-metadata-a-three-repo-seam) + [meta/api.md](../meta/api.md#unknown-asin-intake-queue-optional) and [meta/sync-bot.md](../meta/sync-bot.md#the-reported-asin-lane) + `/users/admin/server#helping-complete-the-community-database`, `/users/admin/books`, `/users/admin/health` (tag hints), `/users/faq` (what is sent) + the `admin/settings-metadata.png` screenshot | | Metadata mirror mode (`metadata.mode`, `internal/metamirror`, `internal/meta/mirror.go`, the Source card, the mirror panel on Health > System) | [server/configuration.md](../server/configuration.md#mirror-mode-metadatamode-mirror) + the mirror routes in [api/reference.md](../server/api/reference.md#the-local-copys-status-metadatamirror) + `/users/admin/server` (Keeping a local copy), `/users/admin/health` (the local copy), `/users/faq` (privacy) | | `pkg/query`, `pkg/query/querytest`, `pkg/release` in audiosilo-meta, or the artifact schema | [meta/overview.md](../meta/overview.md#public-go-packages) + [meta/data-model.md](../meta/data-model.md#the-compiled-artifact-and-schema-versioning) + the [cross-repo contract](../architecture/cross-repo-contract.md#13-community-metadata-a-three-repo-seam) (mirror-mode servers read the artifact directly) | diff --git a/docs-developers/server/api/index.md b/docs-developers/server/api/index.md index 0b0fd03b..a8c7400d 100644 --- a/docs-developers/server/api/index.md +++ b/docs-developers/server/api/index.md @@ -313,7 +313,9 @@ Failures a person can fix also carry a machine-readable **`code`** next to | `code` | Status | When | |---|---|---| -| `username_taken` | `409` | creating an account with a username that exists (`POST /admin/users`) | +| `username_taken` | `409` | creating an account with a username that exists (`POST /admin/users`), or renaming one to it (`PATCH /admin/users/{id}`) | +| `username_required` | `400` | an empty username on `POST /admin/users` or `PATCH /admin/users/{id}` (from 2.7.0) | +| `current_password_incorrect` | `401` | `POST /auth/password` with a wrong `current_password` (from 2.7.0). A client tells this `401` apart from a session that ended, and keeps the session | | `name_taken` | `409` | a library or share name already in use (create, rename) | | `library_grant` | `409` | `POST`/`DELETE /admin/shares/{id}/paths` on a whole-library grant: its rules change only by granting or revoking the library | | `config_library` | `409` | a library the server's config declares: renaming it or moving its folder (`PATCH /admin/libraries/{id}`, from 2.4.0), or deleting it (`DELETE /admin/libraries/{id}`, from 2.4.1) | @@ -508,7 +510,7 @@ Status mapping is consistent across handlers: | Status | Meaning | |---|---| | `400` | malformed body / unknown JSON field, missing or invalid parameter (`path is required`, `invalid cursor`, non-integer `{id}`), path escaping the library root, domain validation (`mode must be "book" or "collection"`, admin needs a password, password too short) | -| `401` | missing/invalid/expired token, bad credentials, invalid auth code, wrong `current_password` | +| `401` | missing/invalid/expired token, bad credentials, invalid auth code, wrong `current_password` (`current_password_incorrect`) | | `403` | authenticated but not allowed: no share grants the library or path, `admin only`, demo accounts on the self-service routes (password/recovery/API keys), an API key on a credential-minting route (create key/recovery/pair/password/cast token) or on a household member route, a kid household member on the routes a kid can't use, a member managing members, adding a member while it is switched off, bad setup token | | `404` | library/user/share/invite not found, another user's (or an unknown) bookmark or note on an edit, `no book at that path`, a path with a hidden (dot) segment on any content route (it reads as missing, after the scope check: `library.ContentPath`), feature not configured (demo mode off, well-known files unset) | | `409` | conflicts: `name already taken` (library/share), last-enabled-admin guard, the household member cap, a session or pairing code for a member the admin disabled, the admin's household refusals, signing out the device making the request, starting someone's progress on a book they can't see, setup already completed | diff --git a/docs-developers/server/api/reference.md b/docs-developers/server/api/reference.md index b36ff8b2..461f0019 100644 --- a/docs-developers/server/api/reference.md +++ b/docs-developers/server/api/reference.md @@ -631,10 +631,14 @@ A shipped app takes its existing 401 path: signed out, pair again. The response carries no count. A client that wants to say what happened reads [`GET /me/devices`](#get-apiv1medevices) again. +An admin's change also ends the first-run sign-in hint and turns +[`GET /admin/setup`](#get-apiv1adminsetup)'s `printed_password` false for the +first-run admin. + | Status | Meaning | |---|---| | `400` | password missing or too short | -| `401` | `current_password` incorrect | +| `401` | `current_password` incorrect: `code: "current_password_incorrect"` (2.7.0), so a client can tell it from a session that ended and keep the session. An older server sends no code | | `403` | demo account (`demo_account`); a kid's account (`kid_account`, also when the owner's switch to kid commits between the wrapper and the write) | | `429` | account-mutation limit tripped | @@ -5495,7 +5499,8 @@ Create an account. Response `201`: the created account as the admin's user object above (re-read after the insert, so its stored defaults, `can_add_members` among them, are there). `409` `username already taken` (`code: "username_taken"`) on a duplicate username; `400` -with a specific message on a validation failure: `admin_needs_password`, +with a specific message on a validation failure: `username_required` (an empty +`username`; 2.7.0 adds the code to the old message), `admin_needs_password`, `password_too_short` (see [error codes](index.md#error-envelope-and-status-conventions)). ### `GET /api/v1/admin/users/{id}` @@ -5559,6 +5564,7 @@ Edit an account in place. Any subset of: | Body field | Type | Notes | |---|---|---| +| `username` | string | 2.7.0: renames the account, by the rule a new account's name follows: not `""` (`400 username_required`), not another account's (`409 username_taken`). The account keeps its sessions, since tokens name the user by id. The console's first-run Your account step uses it | | `role` | string | `"admin"` \| `"user"` | | `password` | string | `""` clears the password (non-admins only); a non-empty one is refused when the request leaves the account a kid (beside Make independent, or an assign as a grown-up, it is fine). Replacing or clearing a password the account had ends its sessions (below) | | `disabled` | bool | reversible lockout; disabling revokes nothing but blocks all token use | @@ -5595,15 +5601,18 @@ always is, or it is `400 admin_needs_password`). - **A password reset ends the person's sessions.** Replacing or clearing a password the account had ends their sessions exactly as a [self-service change](#post-apiv1authpassword) does, in the same transaction. Only - the admin's own session survives, when the account is the admin's. + the admin's own session survives, when the account is the admin's. A non-empty + password an admin sets on their **own** account counts as their own choice + (`auth.NoteOwnPassword`), as [`POST /auth/password`](#post-apiv1authpassword) + does. One another admin set for them does not. Response `200`: the updated user object (with the fields above). | Status | Meaning | |---|---| -| `400` | admin must keep a password (`admin_needs_password`) / password too short (`password_too_short`); `managed_by` that isn't a positive id or `null`; `code: "member_fields_required"` (`managed_by: ` without both `member_kind` and `member_access`, or either of those without it); a `kind` or `access` that isn't one of the two values; `code: "kid_no_password"` (a non-empty `password` when the request leaves the account a kid: a kid with no household change in it, or an assign as a kid) | +| `400` | `username_required` (an empty `username`); admin must keep a password (`admin_needs_password`) / password too short (`password_too_short`); `managed_by` that isn't a positive id or `null`; `code: "member_fields_required"` (`managed_by: ` without both `member_kind` and `member_access`, or either of those without it); a `kind` or `access` that isn't one of the two values; `code: "kid_no_password"` (a non-empty `password` when the request leaves the account a kid: a kid with no household change in it, or an assign as a kid) | | `404` | user not found (the account, or the owner named by `managed_by`) | -| `409` | would demote/disable the last enabled admin (`last_admin`); `code: "member_not_admin"` (making a member an admin, or assigning an admin as a member); `code: "owner_is_member"` (the owner is a member); `code: "owner_is_demo"` (the owner is a demo account); `code: "member_is_demo"` (the account being assigned is a demo account); `code: "self_managed"` (`managed_by` is the account itself); `code: "has_members"` (the account has members of its own) | +| `409` | `username_taken` (a rename to another account's name); would demote/disable the last enabled admin (`last_admin`); `code: "member_not_admin"` (making a member an admin, or assigning an admin as a member); `code: "owner_is_member"` (the owner is a member); `code: "owner_is_demo"` (the owner is a demo account); `code: "member_is_demo"` (the account being assigned is a demo account); `code: "self_managed"` (`managed_by` is the account itself); `code: "has_members"` (the account has members of its own) | What `user.update` records: [Audit log](../backups-and-notifications.md#audit-log). @@ -6369,7 +6378,7 @@ what the first does). | `min_duration` · `max_duration` | number (seconds) | - | inclusive bounds; `0` means no bound | | `added_after` | date | - | inclusive lower bound on `added_at`: `YYYY-MM-DD` (used as is) or an RFC 3339 time (any offset; converted to UTC before comparing) | | `added_before` | date | - | exclusive upper bound, same formats | -| `issue` | string | - | one [Health issue](#get-apiv1adminissues) kind's books: `scan_error`, `suspect`, `split_discs` (one row per split book: its first disc), `no_cover`, `unmatched`, `series_spellings`, `no_chapters`, `detailed_chapters` or `transcode` (not `duplicate`, which comes as groups from [`/admin/issues/duplicates`](#get-apiv1adminissuesduplicates)). Books an admin ignored for that kind are left out. Any other value is `400 unknown issue` | +| `issue` | string | - | one [Health issue](#get-apiv1adminissues) kind's books: `scan_error`, `suspect`, `split_discs` (one row per split book: its first disc), `no_cover`, `unmatched`, `unknown_asin`, `series_spellings`, `no_chapters`, `detailed_chapters` or `transcode` (not `duplicate`, which comes as groups from [`/admin/issues/duplicates`](#get-apiv1adminissuesduplicates)). Books an admin ignored for that kind are left out. Any other value is `400 unknown issue` | | `issue_ignored` | `true`\|`false` | - | with `issue`, list **only** the books an admin ignored for it; without `issue` it is `400 issue_ignored needs an issue` | | `sort` | string | `title` | `title` \| `author` \| `surname` \| `series` \| `narrator` \| `published` \| `added` \| `duration` \| `size` | | `order` | string | `asc` | `asc` \| `desc` | @@ -8010,7 +8019,7 @@ a scan last finished. - `categories` - one entry per kind, always in this order: `scan_error`, `suspect`, `split_discs`, `duplicate`, `no_cover`, `unmatched`, - `series_spellings`, `no_chapters`, `detailed_chapters`, `transcode`. Some of + `unknown_asin`, `series_spellings`, `no_chapters`, `detailed_chapters`, `transcode`. Some of them need a word: - `series_spellings` is a book whose main series another book of its library spells another way: the same series in a different case, accent, punctuation @@ -8029,6 +8038,13 @@ a scan last finished. as `duplicate`. - `unmatched` is **left out while community metadata is off**, since there is nothing to match against. + - `unknown_asin` is a book whose own ASIN the community database doesn't know, + as the [unknown-ASIN report](../configuration.md#the-unknown-asin-report-metadatareport_unknown) + last found it, with no ISBN and no community match. It still counts as + matched, so it is never `unmatched` too. It is **left out while the + report doesn't run** (`metadata.report_unknown` off, community metadata + off, or a demo server), since only the report's lookups find such a book. + A client that doesn't know the kind can skip it. What puts a book in each kind is in [Issues](../scanner.md#issues). - `count` - books that need attention, not counting the ones an admin ignored @@ -9136,6 +9152,7 @@ and `AUDIOSILO_*` variable each one is, and whether it needs a restart (see "upcoming": true, "report_unknown": false, "available": true, + "region_chosen": true, "upcoming_status": { "last_run_at": "2026-10-30T04:12:00.000Z", "last_success_at": "2026-10-30T04:12:00.000Z", @@ -9189,6 +9206,7 @@ effect. Lists are never `null`. | `players.web_dir` | read-only: changed only in `config.yaml` or `AUDIOSILO_WEB_DIR` | | `players.web_player` | read-only: where `/web` is served from - `"embedded"` (baked into the build), `"dir"` (from `web_dir`) or `""` (not mounted) | | `metadata.region` | the preferred Audible marketplace (`""` = none; see [Configuration](../configuration.md#community-metadata-metadata)) | +| `metadata.region_chosen` | read-only (2.7.0): `false` only while no marketplace was ever chosen: none in `config.yaml` or the environment, and no admin ever saved `metadata.region` here (the `server_state` key `metadata_region_chosen`, written by any save carrying the field, `""` included). A failed read counts as chosen. While it is `false`, the console's setup flow preselects the marketplace nearest the admin's browser (its languages, then its time zone) and Settings offers it. An older server sends no field, and the console offers nothing | | `metadata.mode` | `"remote"` (ask `base_url` for each book) or `"mirror"` (keep a local copy and answer from it; see [Mirror mode](../configuration.md#mirror-mode-metadatamode-mirror)). A restart setting: a saved change is listed in `restart_pending`, and `GET /admin/system`'s `metadata.mode` keeps the running mode until the restart | | `metadata.upcoming` | the [upcoming releases](#upcoming-releases) job's switch (config `upcoming.enabled`, default `true`). Applies at once: switched on (with metadata on) it runs soon when no run has succeeded for a day; switched off it stops a run in flight. The live `upcoming` [capability](#get-apiv1server) is `metadata` **and** this | | `metadata.upcoming_status` | read-only (2.6.0): how the job's runs went, absent without a metadata service. `last_run_at`, `last_success_at` (both absent before the first run; equal when the last run worked), `last_error` (the last failed run's error, cleared by a success), `next_run_at`, `series` (local series matched to a community series) and `items` (what they lack in all). The console's Settings > Metadata words it | @@ -9907,14 +9925,16 @@ server and this request: "peer_trusted": false }, "config_libraries": ["Audiobooks"], - "mount_hints": ["/audiobooks", "/podcasts"] + "mount_hints": ["/audiobooks", "/podcasts"], + "printed_password": true } ``` - `state` is server-wide (the `server_state` key `setup_state`; see [Data model](../data-model.md#server-state)). The step ids, in the console's - order, are `folder`, `naming`, `metadata`, `reach`, `abs`, `backups`, - `phone` and `bridge`, each `"done"` or `"skipped"`; an absent id is a step + order, are `account`, `folder`, `naming`, `metadata`, `reach`, `abs`, + `backups`, `bridge` and `phone` (2.7.0 added `account` first and moved `phone` + last), each `"done"` or `"skipped"`; an absent id is a step not yet seen. A server with no row (a fresh one) answers `finished: false`, `dismissed: false`, `steps: {}`; migration `0053` stored an upgraded server as finished and dismissed. @@ -9937,6 +9957,13 @@ server and this request: systems, when the table can't be read, and once the flow is finished, unless its folder step was skipped and can still be reopened from the Overview card (only that step offers them). +- `printed_password` (2.7.0) is true while the caller is the admin the first + start made and still has the password it printed in the log + (`auth.PrintedPassword`, the `server_state` key `first_admin`). The console's + Your account step then asks for a new password. It is false for an admin who + chose theirs, on the `--setup` page or since, after `reset-admin` gave that + admin a new password, and when the read fails (the step then asks for + nothing). | Status | Meaning | |---|---| @@ -11898,8 +11925,9 @@ console's [first-run setup flow](#admin-first-run-setup) does the rest. See ### `GET /setup` -*Public (gated).* Serves the setup page. `404` when it was never enabled; -`303 See Other` → `/admin` when enabled but an admin already exists. +*Public (gated).* Serves the setup page while it is open. Otherwise +`303 See Other` → `/admin`, whether it was never enabled or an admin already +exists (2.7.0; an older server answers `404` when it was never enabled). ### `POST /setup` diff --git a/docs-developers/server/auth-and-security.md b/docs-developers/server/auth-and-security.md index e30ca540..c8b29b7b 100644 --- a/docs-developers/server/auth-and-security.md +++ b/docs-developers/server/auth-and-security.md @@ -186,10 +186,16 @@ stays valid while the database's generation alone. So the touch drops that token's cached row itself (`auth.Service.resolve`). - Expiry needs no write, so the cache checks it on every hit. +- A cached row is read again once it is a minute old (`maxCachedTokenAge`). The + generation counts writes through this process's handle only, and + `reset-admin` run beside a running server writes from another process (see + [Commands](configuration.md#cli-flags-cmdaudiosilo)). Such a write is seen + within a minute, at one more read a minute for a token in use. - The cache never holds a cast token (its grant and parent) or a pairing token (its one use), and holds nothing for a refusal. -A write that bypassed the generation would leave a revoked token honoured. +A write through this process that bypassed the generation would leave a +revoked token honoured until the row's minute was up. ### Cast tokens @@ -563,12 +569,19 @@ or change their own password: password-less player user, onboarded by pairing, who wants a way back in. - **Changing** an existing password requires `current_password` (`auth.CheckPassword`), so a stolen session token can't plant a persistent - credential. + credential. A wrong one is `401` with `code: "current_password_incorrect"`, + which a client tells apart from a session that ended: the console keeps its + session and asks again. - An **empty** password is rejected. Only an admin can clear a password, through `PATCH /admin/users/{id}`. The guard that an admin must keep a password still applies underneath: `SetPassword` refuses to clear an admin's password. - A change ends the caller's other sessions (see [Password changes end sessions](#password-changes-end-sessions)). +- An admin's change counts as choosing their own password + (`auth.NoteOwnPassword`), as does `PATCH /admin/users/{id}` on the admin's own + account. For the first-run admin it ends the start's sign-in hint and turns + `GET /admin/setup`'s `printed_password` false (see + [Configuration](configuration.md#cli-flags-cmdaudiosilo)). ## Password changes end sessions @@ -613,7 +626,8 @@ A [cast token](#cast-tokens) needs no write. Its parent session is checked on every request, so it ends with the session that minted it. The write moves the content generation, so the token cache refuses an ended -token on the very next request, media `?token=` included. A shipped app takes +token on the very next request (`reset-admin`, from another process, within a +minute), media `?token=` included. A shipped app takes its existing 401 path: it shows as signed out, and the person pairs again. **The sign-in race.** A password sign-in could verify the old hash while the diff --git a/docs-developers/server/backups-and-notifications.md b/docs-developers/server/backups-and-notifications.md index d571b28f..e59a82d0 100644 --- a/docs-developers/server/backups-and-notifications.md +++ b/docs-developers/server/backups-and-notifications.md @@ -269,7 +269,8 @@ No IP address is stored. Notes on the longer rows: -- **`user.update`.** Details gain `can_add_members` when the request sets it. +- **`user.update`.** Details gain `can_add_members` when the request sets it, + and `username_was` (the name before) when it renames the account. Whenever the request carries `managed_by`, details also gain `managed_by` (the owner's username, or `null`), `member_kind` and `member_access`, as the account is after the change. diff --git a/docs-developers/server/configuration.md b/docs-developers/server/configuration.md index f18cea4f..205b204d 100644 --- a/docs-developers/server/configuration.md +++ b/docs-developers/server/configuration.md @@ -57,11 +57,65 @@ library's root and default view. | `--ffmpeg` | `ffmpeg` | Path to ffmpeg for on-the-fly transcoding, the MP3-in-Matroska remux and the watch renditions. **`""` disables it.** Then `?transcode=1` returns 503 and the `transcode` capability reports false. An `.mka` is served as stored (no remux, `direct_playable: false`). The `renditions` capability is false, and `rendition=` answers `404 rendition_unavailable` | | `--setup` | `false` | First-run **setup page**. Instead of auto-creating the admin and printing credentials once, the server mints a one-time token and enables the guarded `/setup` page, where the admin's username and password are chosen in the browser (see [Built-in web UI](web-ui.md#first-run-setup-page-setup)). Either way, the admin console's [first-run setup flow](web-ui.md#the-first-run-setup-flow) then adds the books folder and the rest on the first `/admin` visit | -**One command** (`command` in `cmd/audiosilo/main.go`). After the flags, -`audiosilo version` prints `audiosilo ` and exits `0` before anything -touches the data directory. Any other positional argument, or `version` with an -argument after it, prints a usage error and exits `2`, as the flag package does -for a bad flag. +**Commands** (`command` in `cmd/audiosilo/main.go`) come after the flags. Any +other positional argument prints a usage error and exits `2`, as the flag +package does for a bad flag. + +- `audiosilo version` prints `audiosilo ` and exits `0` before anything + touches the data directory. An argument after it is a usage error. +- `audiosilo [--data ] reset-admin [username]` (`launcher.ResetAdmin`) sets + a new random password on an admin of the server in `--data` and prints it + once. It is the way back in when the first-run password is lost. A flag after + the command is a usage error, since the flag package stops at the command. + +`reset-admin` picks the only admin, or the one named. It exits `2` (a +`launcher.UsageError`, listing the admins) for an unknown name, an account that +isn't an admin, or several admins with no name. It exits `1` and changes nothing +when: + +- there is no `audiosilo.db` in the data directory; +- the file isn't an AudioSilo database (`store.Inspect`), which it never + migrates; +- a restore is waiting for the next start (`backup.RestorePending`), which would + replace the new password; +- the database has migrations pending (an older release left it: the server + upgrades it, and backs it up, at its next start); +- there is no admin yet (with no username given; a name with no admin to match + is the usage error above), or the admin is disabled. + +The password is set through `auth.SetPassword`, the console's rule: every +session, open pairing code and remembered browser of the account ends. It also +revokes the auth code the first start printed beside the password +(`auth.RevokeInitialAdminCode`: that admin's unlimited, never-expiring invite +labelled `auth.InitialAdminCodeLabel`). It ends the start's sign-in hint and, +for the first-run admin, turns `GET /admin/setup`'s `printed_password` false +(`auth.NoteOwnPassword`, as for a password the admin chose): the password in +the log is gone. If the revoke +fails, the command still prints the new password, since the old one is gone, +then exits `1` with the error and asks for the code to be revoked in the console +(People > Invites). + +It runs beside a running server. That server checks the new password at once, +since a sign-in reads the database. A session it has already checked can last up +to a minute from its token cache (see +[Tokens](auth-and-security.md#tokens-session-pairing-and-api-keys)); a restart +ends them at once. + +**The sign-in hint.** The first-run banner's `ensureAdmin` writes the +`server_state` row `first_admin` (see [Data model](data-model.md#server-state)). +While that admin has not signed in, every later start prints a framed reminder +(`launcher.signInHint`): the console's address, the username, and the +`reset-admin` command for this install. Inside a container +(`fsaccess.InContainer`) the command is prefixed with +`docker exec -u : `, the server's own ids, so it never +makes a root-owned file in the data folder (a server running as root gets no +`-u`). The binary is named by its path when +that name on `$PATH` is not this binary (the LinuxServer.io image's +`/app/audiosilo/audiosilo`). Words are quoted for the system's shell (single +quotes, or double quotes on Windows). An admin's password sign-in, an admin +setting their own password (`POST /auth/password`, or `PATCH /admin/users/{id}` +on their own account) or `reset-admin` ends it. A server with no row, an +upgraded or `--setup` one, never prints it. A bare tool name (no path separator) is resolved **next to the server executable first**, then on `$PATH`. An explicit path is used as it is. If a @@ -441,7 +495,7 @@ of the service's database and answers the same questions itself (see | `metadata.enabled` | bool, `true` | Turn the metadata lookup on. When `false`, the server makes **no outbound metadata calls**, and `GET /libraries/{id}/meta` and `GET /meta/work` both return 404. The `metadata` capability reports false, so players hide the enriched-book material entirely. The key seeds the initial state only. An admin can switch it at runtime (see below) | | `metadata.base_url` | string, `"https://meta.audiosilo.app"` | Base URL of the metadata service (the site is served at `/` and the API at `/api/v1`). **Must be an absolute `http`/`https` URL when metadata is enabled.** Mirror mode still needs it, because it is the fallback and the site links (`web_url`, the attribution's `source_url`) name it | | `metadata.mode` | string, `"remote"` | Where the lookups are answered. `remote` asks `base_url` for each book. `mirror` keeps a local copy of the service's database in `/meta-mirror/` and answers from it (see [Mirror mode](#mirror-mode-metadatamode-mirror)). Any case, stored lower case. Empty (a `config.yaml` written before the key existed) reads as `remote`. Any other value is a config error naming the allowed ones. **Read at start** (a restart setting) | -| `metadata.region` | string, `""` | The Audible marketplace a community match prefers when a recording sells in several. One of `us`, `uk`, `ca`, `au`, `de`, `fr`, `es`, `it`, `jp`, `in`, `br` (the community metadata's region vocabulary), any case, stored lower case. `""` means no preference, so the US store's ASIN comes first. The region orders each match candidate's `asins` (the preferred store's, then `us`, then the rest). It breaks a runtime tie between recordings in favour of the one selling there, and it is what a [bulk match](api/reference.md#bulk-community-matching) repick looks for. It says nothing about a library's language, because a match sends only the book's own. Applies live | +| `metadata.region` | string, `""` | The Audible marketplace a community match prefers when a recording sells in several. One of `us`, `uk`, `ca`, `au`, `de`, `fr`, `es`, `it`, `jp`, `in`, `br` (the community metadata's region vocabulary), any case, stored lower case. `""` means no preference, so the US store's ASIN comes first. The region orders each match candidate's `asins` (the preferred store's, then `us`, then the rest). It breaks a runtime tie between recordings in favour of the one selling there, and it is what a [bulk match](api/reference.md#bulk-community-matching) repick looks for. It says nothing about a library's language, because a match sends only the book's own. Applies live. Until it is chosen here, in the environment or by an admin's save (`metadata.region_chosen` on `GET /admin/settings`), the console offers the store nearest the admin's browser | | `metadata.report_unknown` | bool, `false` | The opt-in daily report of unknown Audible ASINs (see [The unknown-ASIN report](#the-unknown-asin-report-metadatareport_unknown)). Runs only while `metadata.enabled` is on too, and never on a demo server. Applies live. In the console it is **Server > Settings > Community metadata**, *Help complete the community database* (setting id `metadata.report_unknown`), greyed out while the lookup is off. `AUDIOSILO_METADATA_REPORT_UNKNOWN` wins at every start and locks it | **What applies live.** An admin can switch `metadata.enabled` on or off from the @@ -527,6 +581,11 @@ The console words the run from `GET /admin/settings`'s read-only [Data model](data-model.md#unknown-asin-reports): ASINs only, never a book or path. +While the report is on, Health lists the books whose own ASIN it found unknown +as the `unknown_asin` kind (see [Issues](scanner.md#issues)). Those books still +count as matched. The kind is left out while the report is off, since the +lookups that find them stop. + #### Mirror mode (`metadata.mode: mirror`) In mirror mode the server keeps a local copy of the metadata service's own diff --git a/docs-developers/server/data-model.md b/docs-developers/server/data-model.md index 0ddb73df..262d3b01 100644 --- a/docs-developers/server/data-model.md +++ b/docs-developers/server/data-model.md @@ -1347,10 +1347,11 @@ itself, one row per key: `key` (primary key), `value` (the key's own JSON) and `updated_at` (fixed-width millisecond UTC). These are not settings. Nobody types them in `config.yaml`, and no `AUDIOSILO_*` variable sets them. The table is durable but not part of either half. It names no book and can't be rebuilt, so -a backup carries it like every other table. `catalog/server_state.go` reads a -key with `getServerState`, where a value that can't be read counts as none, and -writes one with `putServerState`. Both take a querier or execer, so a caller can -read and write one key in a single transaction. +a backup carries it like every other table. `store.GetState` reads a key, where +a value that can't be read counts as none, and `store.PutState` writes one +(`catalog/server_state.go` names them `getServerState` and `putServerState`). +Both take a querier or execer, so a caller can read and write one key in a +single transaction. Keys are written by code, so a new key needs no migration. Its keys: @@ -1382,6 +1383,22 @@ Its keys: and the flow opens on the first console visit. `catalog.UpdateSetupState` merges a change into it in one writer transaction. Migration `0053` writes it finished and dismissed on an upgraded server. +- `first_admin`: the admin a headless first start made with a printed password, + `{"user_id":1,"signed_in":false,"chose_password":false}`. Only the start that + made the admin writes it (`auth.MarkFirstAdmin`), even one that failed before + printing the banner. While + `signed_in` is false, each later start prints the sign-in hint (see + [Configuration](configuration.md#cli-flags-cmdaudiosilo)). An admin's password + sign-in (`auth.NoteAdminSignIn`), `reset-admin`, or the admin's own password + change sets it. `chose_password` is set once that admin sets their own + password or `reset-admin` gives them a new one (both `auth.NoteOwnPassword`), + and drives `GET /admin/setup`'s + `printed_password`. No row, on an upgraded server or a `--setup` one, means + no hint and no backfill. +- `metadata_region_chosen`: `true` once an admin saved `metadata.region` from the + console, "No preference" included (`catalog.NoteRegionChosen`). With a region + in `config.yaml` or the environment it counts as chosen anyway. It drives + `GET /admin/settings`'s `metadata.region_chosen`. - `upcoming`: the [upcoming releases](api/reference.md#upcoming-releases) job's run status (`catalog.UpcomingRun`). It holds `last_run_at`, `last_success_at`, `last_error`, `failures` (failed runs since the last @@ -1718,7 +1735,8 @@ tables at the request. It names no book, library or path. A row whose ASIN no book holds any more is deleted at the start of the next run. The table is derived and disposable: losing it only means the ASINs are looked up and sent again. It is written -through the user-state path, because no content cache reads it. `MAX(sent_at)` +through the user-state path, under the generation rule in +[Overview](overview.md#internalstore). `MAX(sent_at)` keeps the report to once a day across restarts. The run status is the `unknown_asin_report` key of [`server_state`](#server-state). @@ -1787,7 +1805,8 @@ keeps the report to once a day across restarts. The run status is the (FK to `users`, `ON DELETE SET NULL`), `created_at`. An admin's "ignore this" on a Health issue. `kind` is one of `catalog.IssueKinds` (`scan_error`, `suspect`, `split_discs`, `duplicate`, `no_cover`, `unmatched`, - `no_chapters`, `detailed_chapters`, `transcode`). It is path-keyed like the + `unknown_asin`, `series_spellings`, `no_chapters`, `detailed_chapters`, + `transcode`). It is path-keyed like the rest of this group, so an ignore survives a rebuild, moves with the book (`MoveDurableState`) and needn't point at an indexed book. diff --git a/docs-developers/server/overview.md b/docs-developers/server/overview.md index 57798047..18715340 100644 --- a/docs-developers/server/overview.md +++ b/docs-developers/server/overview.md @@ -184,17 +184,23 @@ that no content cache reads: the queue and reading progress; - the console's bell and the community metadata cache; - a token's last-seen touch (the token cache drops the touched row itself); -- the Bridge's heartbeat, and its job reports' lease and progress. +- the Bridge's heartbeat, and its job reports' lease and progress; +- the unknown-ASIN report's lookups (`unknown_asin_reports`). A user-state write leaves the generation where it was, unless it changed a row outside its table's rule in `userStateTables`. For example, a progress row that is added, removed or re-keyed moves the generation, because the Health summary's -duplicate ranking reads those keys. SQLite's pre-update hook does this check, so +duplicate ranking reads those keys. An `unknown_asin_reports` row added or +removed, or its `unknown` answer changed, moves it too, because Health's +`unknown_asin` kind reads them. A lookup that only renews `checked_at` or +`sent_at` does not. SQLite's pre-update hook does this check, so a misfiled write only empties the caches. `store.GenMap` is the small map the token cache and the library cache use. Its entries all belong to one generation. The caller takes the generation before its -read, and a newer generation empties the map. +read, and a newer generation empties the map. The generation sees only this +process's writes, so the token cache also reads a row again once it is a minute +old (see [The per-request lookup and its cache](auth-and-security.md#the-per-request-lookup-and-its-cache)). ### `internal/auth` diff --git a/docs-developers/server/scanner.md b/docs-developers/server/scanner.md index acd0ade2..4b841118 100644 --- a/docs-developers/server/scanner.md +++ b/docs-developers/server/scanner.md @@ -1771,6 +1771,7 @@ predicate over `books b`, shared by the counts (`IssueCounts`) and the lists | `split_discs` | it is the **first** disc (by `rel_path`) of a book split across disc folders. `split_parent` is set, and that folder has no override of either mode (either one settles how it reads). Its fix sets `book` on `split_parent`, which joins the discs | | `no_cover` | a scan has checked it (`has_cover` not `NULL`), and it has no sidecar image, no embedded art and no custom cover | | `unmatched` | it has no ASIN, no ISBN and no recorded community match (`book_matches`, see [Data model](data-model.md#durable-per-library-config-path-keyed-no-fk-to-books); `NOT catalog.matchedExpr`). [Tag hints](data-model.md#tag-hints) don't count. Offered only while community metadata is on | +| `unknown_asin` | its own ASIN is one the [unknown-ASIN report](configuration.md#the-unknown-asin-report-metadatareport_unknown) found the community database doesn't know (an `unknown_asin_reports` row with `unknown = 1`, matched on the upper-cased, trimmed ASIN; `unknownASINExpr`), and it has no ISBN, no `book_matches` row and no ASIN a community match gave it (`book_overrides` of source `community`), so matching it by hand settles it. It still counts as matched (`matchedExpr`), so Match automatically skips it. The summary lists the kind only while the report is on, since only its lookups fill the table | | `series_spellings` | another book of its library spells its main series another way: the same `series_key`, a different `series`. This takes two range seeks on `idx_books_series_key`, for a spelling below its own or above. `SeriesSpellingGroups` lists them per series from the same predicate, and the console's fix sets one spelling with a bulk edit | | `no_chapters` | it is longer than **2 hours** and has at most one chapter. The console shows why community chapters weren't used from the row's `chapters_check` (a failed [community chapter check](community-chapters.md)) | | `detailed_chapters` | its last community chapter check is `refine` (the community's finer chapters fit), and it has no `chapter_source` choice. Picking either source settles it | diff --git a/docs-developers/server/web-ui.md b/docs-developers/server/web-ui.md index f5cf9b04..92b235a8 100644 --- a/docs-developers/server/web-ui.md +++ b/docs-developers/server/web-ui.md @@ -519,7 +519,13 @@ The Overview is built on `GET /admin/stats`, `GET /admin/sessions/live`, About, cached ten minutes. - **`GET /admin/libraries`** gives the offline-library notices. - **`GET /admin/issues`** gives the "Needs attention" card: up to six - categories with something to fix, each linking to its Health queue. + categories with something to fix, each linking to its Health queue. It also + gives the "*N* books not matched" notice (`UnmatchedPrompt`, + `features/health/unmatched-prompt.tsx`): the `unmatched` category's count, + ignored books left out, with **Match automatically** opening Health's Not + matched list. It shows only while that count is above zero, so never while + community metadata is off. Library > Books shows the same notice while + browsing. - **Listening now** (`GET /admin/sessions/live`) has one card per live session, playing first, each linking to Activity > Live now. A card's second line is the chapter label described under [Activity > Live now](#activity--live-now), else the author or device. @@ -901,8 +907,15 @@ Any other failure keeps the dialog open with its message. host for three notices: loopback (only this computer), a home address by `isHomeHost`, the server's own rule (a private or link-local IP, a `.local`, `.lan`, `.home.arpa` or `.internal` name, or a single-label name; this network - only, the info tone), and `https://` at an IP address (a self-signed - certificate warning). A public host name gets none of them. The link's base is + only, the info tone), and a certificate warning worded by its reason + (`certificate`): `selfsigned` for `https://` while the server runs in self-signed TLS mode + (`runsSelfSigned`, from the settings' `network.tls_mode`; unknown while + settings load or a TLS mode change waits for a restart), at an IP address or a + name like `nas.local` alike, else `address` for `https://` at an IP address in + any other mode, since such an address rarely has a certificate browsers + trust. A public host name gets the `selfsigned` warning while the server runs + self-signed (a reverse proxy with its own certificate in front of it still + reads as self-signed), and none otherwise. The link's base is `baseURL`: the public address, else the address the console was opened at, so a home address set in Settings does not change it. - **A person's page** is `/admin/people/user/`, with its tab in `?tab=`: @@ -1084,6 +1097,9 @@ The review dialog: - words a `none` item from its `detail` (`weak_match` with its `score`, `identifier_unknown`, `work_unknown`), and a `review` item from its own (`length_differs`, `dramatization_only`, `identifier_disagrees`); +- heads the Not found tab with what that means (the books still play) and the + Help complete the community database setting, and gives each `none` row + **Add it** (`AddItLink`), a link to meta.audiosilo.app's Add a book page; - lists a change's `match` as "the community match" when no ASIN or ISBN is written beside it (`changeParts`); - posts the admin's picks as `exclude` and `include` ids. @@ -1095,6 +1111,21 @@ A small apply can finish between two polls. Marketplace names come from `lib/regions.ts` (`Intl.DisplayNames`, with each store's domain), shared with the Settings select. +##### Add it + +`lib/meta-site.ts` `addBookUrl` builds the link to `https://meta.audiosilo.app/add`, +always the public community site, even for a server pointed at another +`metadata.base_url`. The page reads one parameter, `q`, and acts on it at once: +an Audible ASIN (`B0` and eight more letters or digits) opens that record, and +anything else is searched for. The link carries the best way to find the book: +an Audible ASIN (one the admin typed, else the book's own), else the words the +admin searched with, else its title and author, else nothing. The match +dialog's empty state (`NotInDatabase`), Match automatically's Not found rows +(title and author) and Health's `unknown_asin` rows (the book's ASIN) use it. +`unknown_asin` books have their own Health card, listed only while the +unknown-ASIN report is on, and their fix is the match dialog, which finds them +by title. + #### Health > Jobs Health > Jobs (`features/health/jobs-page.tsx`) runs on `GET /admin/jobs`, @@ -1424,7 +1455,10 @@ A **Setup** card shows too (see [The Setup card](#the-setup-card)). ##### The job list The job list (`bridge-jobs.tsx`) has a state filter and cancel. It sums up the -result with `resultSummary` and shows the raw JSON under Details. A cancelled +result with `resultSummary` and shows the raw JSON under Details. A job's +progress shows while it is open, and a failed or cancelled job keeps the last +step it reported as "Last step: *step*" (`listedProgress`), the clue to where +it stopped; a done job shows none. A cancelled row of any kind is `CancelledStatus` (see [Cancelling](../bridge/pairing-and-jobs.md#cancelling)), never its raw `error`. @@ -1835,9 +1869,10 @@ The same sponsor line closes every GitHub Release's notes (the GoReleaser ### The first-run setup flow "Set up your server" (`features/setup/`) is the console's first-run flow. Its -eight steps take a new admin from the first sign-in to a named, matched library -and a paired phone. The account itself still comes from the first-run banner or -the [`/setup` page](#first-run-setup-page-setup). The flow is console-only and +nine steps take a new admin from the first sign-in, through their own username +and password, to a named, matched library and a paired phone. The account itself +still comes from the first-run banner or the +[`/setup` page](#first-run-setup-page-setup). The flow is console-only and admin-only. It has no capability flag, and the player never asks for it. The headless first-run banner (`writeBanner` in `pkg/launcher`) prints the @@ -1851,6 +1886,15 @@ base URL: `public_url`, else one derived from `tls.mode` and `bind`. (API: POST /api/v1/auth/redeem or /api/v1/auth/login.) ``` +Until an admin signs in with a password, each later start prints a reminder with +the `reset-admin` command instead (see +[Configuration](configuration.md#cli-flags-cmdaudiosilo)). The console's sign-in +page says the same as the first-run default: the username `admin`, the password +in the server log (with Docker, the container's log), and `reset-admin` when it +is lost. It shows "open the setup link from the server log" instead only while +the server's `/setup` page is open (`setupPageOpen`), and adds no other signal +about whether the server is claimed. + #### Where it shows The console route `/setup` (so `/admin/setup`) renders as a page of its own, @@ -1908,11 +1952,26 @@ is disabled. #### The steps -The step ids, in order, are `folder naming metadata reach abs backups phone -bridge` (`SETUP_STEPS`). Each step has Skip and, after the first, Back +The step ids, in order, are `account folder naming metadata reach abs backups +bridge phone` (`SETUP_STEPS`). Each step has Skip and, after the first, Back (`StepFooter`). -1. **folder.** A typed path, with no folder picker and no new listing route. +1. **account.** The admin's own username and password (`AccountStep`, rules in + `accountPlan`). The password change goes first, so a refused one (a wrong + current password) leaves the username as it was. A changed username goes through + `PATCH /admin/users/{id}` `{username}`; the session stays, and the console + renames its signed-in user. A new password goes through the caller's own + `POST /auth/password` with the current one, which keeps this session and is + not sent again when the rename after it is refused; its + `401 current_password_incorrect` shows on the Current password field and + never ends the console's session. While the envelope's `printed_password` is + true (the admin still has the password the log printed), a new password is + required and a notice says why; otherwise it is optional. Skip is labelled + "Keep *name* and this password". The step leaves the first-run auth code + alone (the phone step points at it) and says to revoke it in People > + Invites. A password the admin sets on their own account in People counts as + chosen too (`notePasswordChosen`). +2. **folder.** A typed path, with no folder picker and no new listing route. `POST /admin/setup/preview` checks it and returns: - the counts; - `same_root`, shown as a warning; @@ -1923,8 +1982,8 @@ bridge` (`SETUP_STEPS`). Each step has Skip and, after the first, Back listed as already added, and those named in `config_libraries` are marked "From your config". The library is **not** created here. The folder and name become a draft, kept in `localStorage` (`audiosilo.setup.draft`, - `parseDraft`) until step 3. -2. **naming.** The preview's samples, as a two-column table per book (tags, + `parseDraft`) until the metadata step. +3. **naming.** The preview's samples, as a two-column table per book (tags, folders), with the rows that differ highlighted (`compareField`, `disagreements`). Then the `metadata_source` pick (`RadioCards`), with the preview's `suggested` side preselected and badged for the draft. A books @@ -1932,7 +1991,15 @@ bridge` (`SETUP_STEPS`). Each step has Skip and, after the first, Back made) keeps its own source preselected, with the suggestion as a hint. Choosing the other PATCHes it, which re-resolves its books, with the same toast as Libraries. -3. **metadata.** `metadata.enabled`, `region` and `mode`. The step states the +4. **metadata.** `metadata.enabled`, `region` and `mode`. While the settings + envelope's `metadata.region_chosen` is `false` and the region isn't locked + (`offerRegion`), the marketplace starts at the one nearest the browser + (`browserNearestRegion` in `lib/nearest-region.ts`: the region of each of + `navigator.languages` in order, then the time zone, mapped to the nearest + store, else the US store). With the lookup on, Continue then saves the region + even when it equals the saved value, so it counts as chosen. Settings > Community metadata offers the + same store with **Use *store*** or **Keep no preference** + (`RegionSuggestion`), except when it is the US store. The step states the mirror's size up front, and `mode` is restart-only. The draft then becomes the library (`useCreateDraftLibrary`: `POST /admin/libraries` with the draft's source), and its scan starts. Skip keeps the settings but still @@ -1940,7 +2007,7 @@ bridge` (`SETUP_STEPS`). Each step has Skip and, after the first, Back scanning or queued, and the one the flow made once it is done, from `useLibraries`' `scan`. `useScanWatcher` runs on the page, since the shell isn't mounted. -4. **reach.** +5. **reach.** - The browser's `location.origin` is offered as `lan_url`, `public_url` or both (`reachOffers`). A home-network host by the server's own rule (`isHomeHost`) suits the home address first. Loopback suits neither. @@ -1951,16 +2018,12 @@ bridge` (`SETUP_STEPS`). Each step has Skip and, after the first, Back (`addressMismatch`). - The TLS mode, with what it means for phones, plus `tls_home_http` in self-signed mode. These are restart settings, badged. -5. **abs.** Points to Server > Settings > Import (in a new tab) and says that - ABS `metadata.json` files are read. Continue records `done` only once the - import was opened, else `skipped`. -6. **backups.** `backups.schedule` and `keep` (1-365), the backup folder shown +6. **abs.** Points to Server > Settings > Import (in a new tab) and says that + ABS `metadata.json` files are read. An info-only step: Continue records + `done`, Skip `skipped`. +7. **backups.** `backups.schedule` and `keep` (1-365), the backup folder shown read-only, and `general.update_check`. `backups.dir` is restart-only and read-only in the settings API. -7. **phone.** An `api.pairSelf()` QR, single use and valid ten minutes; New - code refetches. The web player in a new tab with its own token - (`usePlayerTab`, only with the `web_player` capability). The ffmpeg line - from `useSystem().tools`, and the household note. 8. **bridge.** What a Bridge is, a compose snippet, and the existing pairing dialog (`usePairCode`, `PairDialog`). - The snippet is `bridgeCompose`, from audiosilo-bridge's @@ -1969,8 +2032,16 @@ bridge` (`SETUP_STEPS`). Each step has Skip and, after the first, Back its one `/audiobooks` volume's comment says it is an example. - Once `useBridge()` reports a paired Bridge, the step shows `BridgeSetupList` from the heartbeat's `setup` block instead. - - Finish setup records `done`, paired or not, because the Bridge is - optional. Skip records `skipped`. + - Continue records `done`, paired or not, because the Bridge is optional. + Skip records `skipped`. +9. **phone**, the last. An `api.pairSelf()` QR, single use and valid ten + minutes; New code refetches. How signing in works: the admin by this code, + or the app's address and password, or the log's auth code; everyone else by + an invite of their own, which needs no password (**Invite someone** opens + People's invite dialog in a new tab). The web player in a new tab with its + own token (`usePlayerTab`, only with the `web_player` capability). The ffmpeg + line from `useSystem().tools`, and the household note. Its primary button is + Finish setup. #### The Overview card @@ -2066,6 +2137,11 @@ What the page shows after a redeem: the phone holding it can't scan it. `showPhone` leads with "Get the app, then come back to this link", moves the store links under it, and puts **Open web player** first. +- A code typed into the form, not one from an invite link's fragment, has no + link to open again. `redeem(code, fromLink)` then swaps the phone lead and the + keep-this-page-open hint for the `*Typed` strings ("come back to this page", + "enter your invite code on this page again"), on `data-i18n` so a language + change keeps them. - Everywhere, a **Get the AudioSilo app** block with the App Store, Google Play and APK links. They are static in `index.html`, so they show before any redeem too, and match the payload's `links` (`TestConnectPageStoreLinks`). @@ -2261,8 +2337,10 @@ It is locked down three ways, so it is safe even on an exposed port: 1. **Off unless enabled.** The page responds only when the launcher called `API.EnableSetup(token)` with a freshly minted one-time token (18 random - bytes, `pkg/launcher`). When it was never enabled, `GET /setup` is a plain - 404, so a normal deployment exposes no setup surface at all. + bytes, `pkg/launcher`). When it was never enabled, `GET /setup` redirects to + `/admin`, the same answer as a closed page, so a normal deployment exposes no + setup surface and doesn't say whether it was ever started with `--setup`. + `POST /setup` there creates nothing. 2. **Self-closes the moment an admin exists** (`setupAvailable`). With a token set but an admin already present, `GET /setup` redirects to `/admin` and `POST /setup` answers `409`. Any error resolving admin state also closes it. diff --git a/docs-users/admin/books.md b/docs-users/admin/books.md index 0dc91740..0b22923c 100644 --- a/docs-users/admin/books.md +++ b/docs-users/admin/books.md @@ -64,8 +64,10 @@ To match many books at once, use **Match automatically** on the Health page If the dialog says "meta.audiosilo.app isn't answering", try again in a minute. If the book isn't in the community database at all, it stays unmatched and -plays as normal. The database has gaps. To add a missing book to it, see -[Contributing](../community/meta-site.md#contributing). +plays as normal. The database has gaps. When nothing matches, the dialog offers +**Add it**, which opens the +[Add a book](../community/meta-site.md#adding-a-missing-book) page on +meta.audiosilo.app with the book's ASIN, or your search, already filled in. The community database holds books, their recordings, authors, narrators and series. Its users add and correct them, and many entries came from a public diff --git a/docs-users/admin/bridge.md b/docs-users/admin/bridge.md index 4f0240a5..8e601647 100644 --- a/docs-users/admin/bridge.md +++ b/docs-users/admin/bridge.md @@ -119,6 +119,8 @@ filters it by state. Each job shows its book, its progress, who asked and when. Jobs the server queues itself say **Scheduled by the server**. When a job is done, it says what it made. +A failed or cancelled job keeps its **Last step**, so you can see where it +stopped. A job that **Needs attention** says why, with **Open in the Bridge**. There, **Retry** sends it on once you've fixed the cause. See diff --git a/docs-users/admin/console-tour.md b/docs-users/admin/console-tour.md index d2b0165e..59ca6dea 100644 --- a/docs-users/admin/console-tour.md +++ b/docs-users/admin/console-tour.md @@ -94,7 +94,10 @@ The overview is the console's home page. It shows: A notice appears at the top when a library's folder can't be read (see [When a library folder goes missing](libraries.md#when-a-library-folder-goes-missing)). Another appears when saved settings wait for a restart (see -[When a change takes effect](server.md#when-a-change-takes-effect)). +[When a change takes effect](server.md#when-a-change-takes-effect)). While +books are not matched to community metadata, a third says how many, with +[Match automatically](health.md#matching-every-book-at-once). +**Library > Books** shows the same notice. A new server with no libraries shows an **Add your first library** button instead. Until you dismiss it, a diff --git a/docs-users/admin/health.md b/docs-users/admin/health.md index df9addf6..d9f57d05 100644 --- a/docs-users/admin/health.md +++ b/docs-users/admin/health.md @@ -38,6 +38,7 @@ Click a card to open its list. | **Likely duplicates** | Copies of one book in the same library. | Compare them side by side. See [Duplicates](#duplicates). | | **Missing covers** | No cover in the book's folder or files. | **Upload a cover** opens the book's page (see [Covers](books.md#covers)). | | **Not matched to community metadata** | The book has no community match, so players can't show its community details. Only shown while community metadata is on. | **Review match** opens the [match dialog](books.md#matching-with-community-metadata). To match the whole list, use [Match automatically](#matching-every-book-at-once). | +| **ASIN not in the community database** | The book has an ASIN the community database doesn't know yet. It counts as matched but gets no community details. Only shown while [Help complete the community database](server.md#helping-complete-the-community-database) is on, since that is what finds them. | **Review match** finds the book by its title. **Add it** opens the [Add a book](../community/meta-site.md#adding-a-missing-book) page with its ASIN filled in. | | **Series spelled several ways** | Books of one series spell its name differently. | **Use this spelling**. See [Series spelled several ways](#series-spelled-several-ways). | | **Long books without chapters** | Books over two hours with no chapters, so listeners can't skip between them. | **Detect chapters** with your [Bridge](bridge.md) (see [Detect chapters](generating-characters-and-recaps.md#detect-chapters-without-characters-and-recaps)), or [match](books.md#matching-with-community-metadata) the book to use its [community chapters](books.md#community-chapters). | | **More detailed chapters available** | The community database has finer chapters that fit this copy. | **Use detailed chapters**. | @@ -93,7 +94,9 @@ was deleted." Once the folder is back, **Retry** scans it. See **Not matched to community metadata** has a **Match automatically** card above its list. It looks up every book in the list in one pass. Nothing changes until -you review and apply what it found. +you review and apply what it found. While there are unmatched books, the +overview and **Library > Books** show how many, with a **Match automatically** +button that opens this list. 1. Pick a library and click **Find matches**. You can leave the page while it runs. @@ -129,8 +132,10 @@ by hand. The [match dialog](books.md#matching-with-community-metadata) lists the closest candidates, however low they scored. The community database has gaps, so some books stay unmatched. They still play -and keep the details from their tags and folders. To add a missing book to the -database, see [Contributing](../community/meta-site.md#contributing). +and keep the details from their tags and folders. Each **Not found** row has +**Add it**, which opens the +[Add a book](../community/meta-site.md#adding-a-missing-book) page on +meta.audiosilo.app with the book's title and author filled in. ### ASIN and ISBN tags are hints diff --git a/docs-users/admin/server.md b/docs-users/admin/server.md index 0ef73b6e..9d041fef 100644 --- a/docs-users/admin/server.md +++ b/docs-users/admin/server.md @@ -167,7 +167,10 @@ The other settings on this topic: [Helping complete the community database](#helping-complete-the-community-database). - **Audible marketplace** (in **Matching**) - the store you buy from. When a recording sells in several stores, a match takes this store's ASIN. It has - nothing to do with your library's language. See + nothing to do with your library's language. Until a marketplace is chosen, + the card offers the one nearest you, from your browser's language and time + zone, unless that is the US store. **Use *store*** saves it, and **Keep no + preference** keeps none. See [Which ASIN a match takes](health.md#which-asin-a-match-takes). - **Service address** - `https://meta.audiosilo.app` by default. Change it only if you run your own. diff --git a/docs-users/admin/users-and-invites.md b/docs-users/admin/users-and-invites.md index cb880ab4..aaad93a6 100644 --- a/docs-users/admin/users-and-invites.md +++ b/docs-users/admin/users-and-invites.md @@ -64,9 +64,11 @@ the console at. A notice under **Share** says when that limits who can open it: open the console at the server's network address, then make a new invite. - **Works on your home network only**: to invite someone elsewhere, set a public address. -- **Their browser will warn about the certificate**: the link is `https://` at - an IP address. They can continue past the warning, or you can set a public - address with a real certificate. +- **Their browser will warn about the certificate**: the server uses its own + self-signed certificate, or the link is `https://` at an IP address, which + rarely has a certificate browsers trust. The notice says which. They can + continue past the warning, or you can set a public address with a real + certificate. ## The People page diff --git a/docs-users/community/meta-site.md b/docs-users/community/meta-site.md index 0a9c378e..5beda3ad 100644 --- a/docs-users/community/meta-site.md +++ b/docs-users/community/meta-site.md @@ -168,6 +168,21 @@ recaps for a book you know well. ![The contribute coverage browser](/img/screenshots/meta/contribute.png) +### Adding a missing book + +The database has gaps. A book it doesn't have still plays in AudioSilo, with the +details from its tags and folders. To add it: + +1. Open [Add a book](https://meta.audiosilo.app/add). +2. Look the book up by its Audible ASIN or its title, in a public copy of + Audible's catalogue. +3. Pick the exact narration and check the details the page would record. +4. The last step opens a prefilled entry on GitHub for the bot to file. + +In the admin console, a book the database doesn't know offers **Add it**, which +opens this page with the book already filled in, by its ASIN or by its title +and author. See [Weak matches](../admin/health.md#weak-matches). + Good to know: - Many entries came from a public Audible catalogue and haven't been checked by diff --git a/docs-users/getting-started/first-run.md b/docs-users/getting-started/first-run.md index 8639dc0d..bbee5de5 100644 --- a/docs-users/getting-started/first-run.md +++ b/docs-users/getting-started/first-run.md @@ -1,9 +1,9 @@ --- title: Set up your server -description: "Create your admin account, then let the admin console walk you through your books folder, addresses, backups and your first phone." +description: "Create your admin account, then let the admin console walk you through your own sign-in, your books folder, addresses, backups and your first phone." --- -A new server is set up in two parts. First you get an admin account. Then the admin console walks you through eight short steps, from your books folder to connecting your phone. Every step can be skipped and changed later. +A new server is set up in two parts. First you get an admin account. Then the admin console walks you through nine short steps, from your own username and password to connecting your phone. Every step can be skipped and changed later. ## Your admin account @@ -29,11 +29,40 @@ Save both now: - The **admin password** signs you in to the admin console at `/admin`. - The **auth code** signs a phone or browser in as the admin, from the connect page. See [Connect the app to your server](../listening/connecting.md). -The server keeps only scrambled copies, so it can't show them again. Under Docker, `docker compose logs` shows the banner until the container is recreated. On a NAS, use the container app's log view (see [Where do I find the server's log?](../troubleshooting.md#where-do-i-find-the-servers-log)). +The server keeps only scrambled copies, so it can't show them again. With Docker the banner is at the start of the container's log: `docker compose logs`, or on a NAS the container app's log view (see [Where do I find the server's log?](../troubleshooting.md#where-do-i-find-the-servers-log)). It stays there until the container is recreated. -:::warning[Missed the banner?] -Before you've added people or listening, the simplest fix is to start over. Stop the server, delete the data folder, and start it again for a fresh banner. Later, see [I lost the first-run admin password](../troubleshooting.md#i-lost-the-first-run-admin-password). -::: +### If you lose the password + +Until an admin signs in with a password, in the console or an app, every later start repeats where the password is: + +```text +======================================================== + AudioSilo: the admin account hasn't signed in yet +======================================================== + Sign in at https://localhost:8080/admin as "admin" with the password printed + when this server first started (the start of the container's log). + Lost it? Set a new one: + docker exec -u 1000:1000 audiosilo --data /data reset-admin +======================================================== +``` + +The `reset-admin` command sets a new admin password and prints it once. Run the line for your install: + +| Install | Command | +| --- | --- | +| Docker Compose | `docker compose exec -u 1000:1000 audiosilo audiosilo --data /data reset-admin` | +| `docker run` | `docker exec -u 1000:1000 audiosilo audiosilo --data /data reset-admin` | +| Unraid (in the terminal) | `docker exec -u 99:100 audiosilo /app/audiosilo/audiosilo --data /config reset-admin` | +| Synology (over SSH, in `/volume1/docker/audiosilo`) | `sudo docker compose exec -u 1026:100 audiosilo audiosilo --data /data reset-admin` | +| Linux package | `sudo -u audiosilo audiosilo --data /var/lib/audiosilo reset-admin` | +| From a terminal | `./audiosilo --data ./data reset-admin` | + +- After `-u`, use your `PUID:PGID`. The reminder prints the right ones. +- `--data` goes before `reset-admin`. With several admins, add the username at the end. +- It works while the server runs. Every session that used the old password is signed out within a minute, and the first-run auth code stops working. If it says revoking that code failed, the new password still works: revoke the code in [People > Invites](../admin/users-and-invites.md). +- It refuses while a restore is waiting for the next start, since the restore would replace the new password. Start the server once, or cancel the restore in [Backups](../admin/backups.md), then run it again. + +`reset-admin` also ends the reminder. A server started with `--setup` never prints it. ### Choose the password in your browser instead @@ -51,29 +80,39 @@ The link stops working as soon as an admin account exists. ## Set up your server -The first time you open the admin console, it opens **Set up your server**, with the steps listed down the side. Until setup is finished, opening `/admin` starts the flow once in each browser session. A link or bookmark to another console page opens that page. +The first time you open the admin console, it opens **Set up your server**, with the steps listed down the side. Until setup is finished, opening `/admin` starts the flow once in each browser session. + +- Every step has **Skip**. A skipped step waits on the Overview (see [The Finish setting up card](#the-finish-setting-up-card)). +- **Finish later** takes you to the console until your next browser session. +- Everything you choose is an ordinary setting, which you can change later in [Server > Settings](../admin/server.md#settings) and [Libraries](../admin/libraries.md). A setting fixed by an `AUDIOSILO_*` environment variable shows a lock. + +### Step 1: Your account -![Step 1 of Set up your server: the books folder](/img/screenshots/setup/folder.png) +![Step 1 of Set up your server: your username and password](/img/screenshots/setup/account.png) -- Every step has **Skip**. A skipped step waits for you on the Overview (see [The Finish setting up card](#the-finish-setting-up-card)). -- **Finish later** takes you to the console. The flow opens again when you next open `/admin` in a new browser session. -- Everything you choose is an ordinary setting. You can change it later in [Server > Settings](../admin/server.md#settings) and [Libraries](../admin/libraries.md). -- A setting fixed by an `AUDIOSILO_*` environment variable shows a lock and can't be changed here. +Choose the username and password you sign in to the console with. The username is also what you type in the apps. -### Step 1: Where are your books? +- If you signed in with the password from the log, the step asks for a new one. Type it twice, then the log's password as **Current password**. +- If you chose your password on the `--setup` page, a new one is optional. +- **Keep admin and this password** skips the step. + +A new password doesn't end the auth code from the log. Once your devices are set up, revoke it in [People > Invites](../admin/users-and-invites.md). + +### Step 2: Where are your books? + +![Step 2: the books folder](/img/screenshots/setup/folder.png) Type the folder that holds your audiobooks and press **Check folder**. AudioSilo shows how many books it found before adding anything. -- **Use the path as the server sees it.** In Docker that's the path inside the container, like `/library`, not the folder on your NAS. If the server says the folder isn't there, this is usually why. -- Folders mounted into the server are offered as buttons under the field. +- **Use the path as the server sees it.** In Docker that's the path inside the container, like `/library`, not the folder on your NAS. Folders mounted into the server are offered as buttons under the field. - **No audio files found here** usually means the wrong folder. -- **Library name** defaults to the folder's name. You can rename it later. +- **Library name** defaults to the folder's name. -The library is created at the end of step 3, so its first scan uses your choices from steps 2 and 3. +The library is created at the end of step 4, so its first scan uses your choices from steps 3 and 4. -### Step 2: How are your books named? +### Step 3: How are your books named? -![Step 2: sample books named from tags and from folders](/img/screenshots/setup/naming.png) +![Step 3: sample books named from tags and from folders](/img/screenshots/setup/naming.png) Titles, authors, narrators and series can come from the files' **tags** or from the **folder names**. This step shows up to eight of your books both ways, side by side. The side with fewer blanks is marked **Suggested**. @@ -82,21 +121,21 @@ Titles, authors, narrators and series can come from the files' **tags** or from Either way, the other fills in what the first leaves empty. Changing this later re-reads the whole library, so it's worth a look now. See [Organize your audiobook files](./organizing-your-library.md#where-titles-authors-and-series-come-from). -### Step 3: Community metadata +### Step 4: Community metadata -![Step 3: community metadata, the marketplace and a local copy](/img/screenshots/setup/metadata.png) +![Step 4: community metadata, the marketplace and a local copy](/img/screenshots/setup/metadata.png) Community metadata adds descriptions, series and covers from the community database on AudioSilo Meta. - **Look up community metadata** turns it on or off. Off, nothing about your books leaves the server for it. -- **Audible marketplace** is the store you buy from. The wrong one matches the wrong edition. +- **Audible marketplace** is the store you buy from. The wrong one matches the wrong edition. Until one is chosen, the step picks the store nearest you from your browser's language and time zone. - **Metadata source** asks AudioSilo Meta for each book, or keeps a [local copy](../admin/server.md#keeping-a-local-copy) (about 1.8 GB). **Continue** (or **Skip**) adds your library and starts its first scan. You can carry on while it runs. -### Step 4: How will people reach the server? +### Step 5: How will people reach the server? -![Step 4: the server's addresses and HTTPS](/img/screenshots/setup/reach.png) +![Step 5: the server's addresses and HTTPS](/img/screenshots/setup/reach.png) Invite links and QR codes use the server's addresses. A wrong address is the usual reason a phone can't connect. @@ -108,17 +147,15 @@ Invite links and QR codes use the server's addresses. A wrong address is the usu See [Reach your server from outside your home](./remote-access.md). -### Step 5: Coming from Audiobookshelf? - -![Step 5: the Audiobookshelf import](/img/screenshots/setup/abs.png) +### Step 6: Coming from Audiobookshelf? -**Open the import** brings each person's listening progress across from Audiobookshelf (see [the import](../admin/import.md)). It opens the import in a new tab, and the flow stays open in this one. +![Step 6: the Audiobookshelf import](/img/screenshots/setup/abs.png) -AudioSilo reads the `metadata.json` files Audiobookshelf leaves beside your books. Audiobookshelf writes them there only with its **Store metadata with item** setting on, which is off by default. [Move over from Audiobookshelf](./coming-from-audiobookshelf.md) covers that, matching your books afterwards, and the folder layouts that read differently. +**Open the import** brings each person's listening progress across from Audiobookshelf (see [the import](../admin/import.md)), in a new tab. AudioSilo also reads the `metadata.json` files Audiobookshelf leaves beside your books. [Move over from Audiobookshelf](./coming-from-audiobookshelf.md) covers the rest. -### Step 6: Backups and updates +### Step 7: Backups and updates -![Step 6: the backup schedule, how many to keep, and the update check](/img/screenshots/setup/backups.png) +![Step 7: the backup schedule, how many to keep, and the update check](/img/screenshots/setup/backups.png) - **Back up** sets when the database is copied. The default is every day at 03:00. - **Scheduled backups to keep** defaults to 7. @@ -126,14 +163,6 @@ AudioSilo reads the `metadata.json` files Audiobookshelf leaves beside your book Copy backups to another disk now and then. See [Backups](../admin/backups.md). -### Step 7: Connect a phone - -![Step 7: a pairing QR code for your phone](/img/screenshots/setup/phone.png) - -Install the AudioSilo app on your phone, open it and tap **Scan a QR code**, then scan this code. The app signs in as you. The phone's camera app opens the web player instead. The code works once, for ten minutes, and **New code** makes another. See [Connect the app to your server](../listening/connecting.md). - -If you opened the console on the server itself (`localhost`), the code needs a home or public address from [step 4](#step-4-how-will-people-reach-the-server). The step tells you if one is missing. - ### Step 8: Add a Bridge ![Step 8: adding a Bridge, with the compose snippet](/img/screenshots/setup/bridge.png) @@ -146,11 +175,21 @@ Optional. A **Bridge** is a companion container that writes to your library fold [Add a Bridge](./running-a-bridge.md) has the full setup. +### Step 9: Connect a phone + +![Step 9: a pairing QR code for your phone, and how signing in works](/img/screenshots/setup/phone.png) + +Install the AudioSilo app on your phone, open it and tap **Scan a QR code**, then scan this code. The app signs in as you. The code works once, for ten minutes, and **New code** makes another. With no camera to hand, enter the server's address in the app and sign in with your username and password, or with the auth code from the log. See [Connect the app to your server](../listening/connecting.md). + +If you opened the console on the server itself (`localhost`), the code needs a home or public address from [step 5](#step-5-how-will-people-reach-the-server). The step tells you if one is missing. + +Everyone else needs an invite of their own. **Invite someone** opens one in People. An invite signs them in, so they don't need a password. + **Finish setup** ends the flow and opens the Overview. If a choice needs a restart (a local metadata copy or an HTTPS setting), the Overview reminds you until the server restarts. ## The Finish setting up card -![The Overview's Finish setting up card, listing two skipped steps](/img/screenshots/setup/finish-card.png) +![The Overview's Finish setting up card, listing a skipped step](/img/screenshots/setup/finish-card.png) If you skipped steps or chose **Finish later**, the Overview shows a **Finish setting up** card. Each line opens that step again. **Dismiss** hides the card for every admin. @@ -158,12 +197,11 @@ If you skipped steps or chose **Finish later**, the Overview shows a **Finish se The data folder (`--data`, or `/data` under Docker) holds: -- `config.yaml`: the server's settings. The admin console writes it for you. +- `config.yaml`: the server's settings, written by the admin console. - `audiosilo.db`: accounts, listening progress, bookmarks and the index of your books. Back this up. - `backups/`: the server's [backups](../admin/backups.md) of `audiosilo.db`, unless you set another backups folder. - `certs/` and `selfsigned-*.pem`: certificates. -- `tools/`: ffmpeg and ffprobe, if the server downloaded them. -- `cache/` and `meta-mirror/`: copies the server makes again when needed. No backup needed. +- `tools/`, `cache/` and `meta-mirror/`: tools and copies the server fetches or makes again when needed. Your audiobooks are never copied here. See [Organize your audiobook files](./organizing-your-library.md). diff --git a/docs-users/getting-started/install-binary.md b/docs-users/getting-started/install-binary.md index fba1fa48..1e01f5a4 100644 --- a/docs-users/getting-started/install-binary.md +++ b/docs-users/getting-started/install-binary.md @@ -24,7 +24,7 @@ Download your system's file from [GitHub releases](https://github.com/KodeStar/a ./audiosilo --data ./data ``` -1. The first start prints the admin password and an auth code **once**. Save them. +1. The first start prints the admin password and an auth code **once**. Save them. If you lose them, `./audiosilo --data ./data reset-admin` sets a new password (see [If you lose the password](./first-run.md#if-you-lose-the-password)). 2. Open `https://localhost:8080/admin`. Expect a certificate warning at first; accept it. 3. Sign in as `admin`. The console walks you through [setting up your server](./first-run.md). diff --git a/docs-users/getting-started/install-synology.md b/docs-users/getting-started/install-synology.md index 5061a83f..df30a4f5 100644 --- a/docs-users/getting-started/install-synology.md +++ b/docs-users/getting-started/install-synology.md @@ -78,15 +78,16 @@ On the very first start, the log shows the admin password and an auth code **onc Config file : /data/config.yaml ``` -Save both now. [Set up your server](./first-run.md) says what each is for. +Save both now. [Set up your server](./first-run.md) says what each is for. If you lose them, see [If you lose the password](./first-run.md#if-you-lose-the-password). If the log starts with `audiosilo: WARNING: the server can't write to /data` or `can't read /library`, fix the folder it names first. See [Permission denied](../troubleshooting.md#permission-denied). ## 6. Open it and add your books 1. Browse to `http://your-nas:8080/admin` and sign in as `admin` with the printed password. -2. **Set up your server** opens. For the books folder, type **`/library`** (the path inside the container, not `/volume1/...`) and press **Check folder**. -3. Follow the remaining steps, or skip any of them. +2. **Set up your server** opens. Choose your own username and password, or keep the printed ones. +3. For the books folder, type **`/library`** (the path inside the container, not `/volume1/...`) and press **Check folder**. +4. Follow the remaining steps, or skip any of them. [Set up your server](./first-run.md#set-up-your-server) walks through every step. Then [get the apps](../listening/mobile-apps.md) and [connect a phone or browser](../listening/connecting.md). diff --git a/docs-users/getting-started/install-unraid.md b/docs-users/getting-started/install-unraid.md index 9ebf91da..f51cbe32 100644 --- a/docs-users/getting-started/install-unraid.md +++ b/docs-users/getting-started/install-unraid.md @@ -29,14 +29,15 @@ On the very first start, the log shows the admin password and an auth code **onc Config file : /config/config.yaml ``` -Save both now. [Set up your server](./first-run.md) says what each is for. +Save both now. [Set up your server](./first-run.md) says what each is for. If you lose them, see [If you lose the password](./first-run.md#if-you-lose-the-password). ## 3. Open it and add your books 1. Click the AudioSilo icon and choose **WebUI**. Your browser warns about the certificate the first time; accept it. 2. Go to `/admin` and sign in as `admin` with the printed password. -3. **Set up your server** opens. For the books folder, type **`/audiobooks`** (the path inside the container, not the `/mnt/user/...` share) and press **Check folder**. -4. Follow the remaining steps, or skip any of them. +3. **Set up your server** opens. Choose your own username and password, or keep the printed ones. +4. For the books folder, type **`/audiobooks`** (the path inside the container, not the `/mnt/user/...` share) and press **Check folder**. +5. Follow the remaining steps, or skip any of them. [Set up your server](./first-run.md#set-up-your-server) walks through every step. Then [connect a phone or browser](../listening/connecting.md). The web player is at `/web`. diff --git a/docs-users/getting-started/quickstart-docker.md b/docs-users/getting-started/quickstart-docker.md index 5855b6fc..144fe8e7 100644 --- a/docs-users/getting-started/quickstart-docker.md +++ b/docs-users/getting-started/quickstart-docker.md @@ -67,7 +67,7 @@ On the very first start, the log shows the admin password and an auth code **onc Config file : /data/config.yaml ``` -Save both now. [Set up your server](./first-run.md) says what each is for. +Save both now. [Set up your server](./first-run.md) says what each is for. If you lose them, see [If you lose the password](./first-run.md#if-you-lose-the-password). ## 3. Open it @@ -81,10 +81,10 @@ The iOS and Android apps connect over plain HTTP at home instead (see [Plain HTT ## 4. Set up your server -The first time you sign in to the admin console, it opens **Set up your server**. Its first step asks for your books folder: +The first time you sign in to the admin console, it opens **Set up your server**: -1. Type **`/library`**, the path inside the container. -2. Press **Check folder**. +1. Choose your own username and password, or keep the printed ones. +2. For the books folder, type **`/library`**, the path inside the container, and press **Check folder**. 3. Follow the remaining steps, or skip any of them. The server scans your books in the background. [Set up your server](./first-run.md#set-up-your-server) walks through every step. Then [get the apps](../listening/mobile-apps.md) and [connect a phone or browser](../listening/connecting.md). diff --git a/docs-users/listening/connecting.md b/docs-users/listening/connecting.md index 99b4db32..a1167639 100644 --- a/docs-users/listening/connecting.md +++ b/docs-users/listening/connecting.md @@ -35,7 +35,7 @@ Each device can scan the same invite's QR code, up to the invite's number of use ![The server's connect page with a pairing QR code, Open in app and Open web player, and the app store links](/img/screenshots/server/connect-page.png) -Opened on a phone, the page leads with getting the app instead of a QR code. Install AudioSilo from the store links on the page, then open the invite link again and tap **Open in app**. Or tap **Open web player** to listen in the browser now. An Android phone without Google Play can download the app's APK from the link under the store buttons. +Opened on a phone, the page leads with getting the app instead of a QR code. Install AudioSilo from the store links on the page, then open the invite link again and tap **Open in app**. If you typed the code in, keep the page open and come back to it, or enter the code there again. Or tap **Open web player** to listen in the browser now. An Android phone without Google Play can download the app's APK from the link under the store buttons. ![The invite page on a phone: get the app first, the store links, then Open web player and Open in app](/img/screenshots/server/phone-connect-invite.png) diff --git a/docs-users/troubleshooting.md b/docs-users/troubleshooting.md index bd83c8b7..ce954577 100644 --- a/docs-users/troubleshooting.md +++ b/docs-users/troubleshooting.md @@ -59,11 +59,11 @@ The server prints it only once. From least to most drastic: 1. **Check the logs.** `docker compose logs` often still has it, unless the container was recreated. 2. **Ask another admin** to set a new password for you in [Invite people](./admin/users-and-invites.md). -3. **Use the auth code**, if you saved it. It still signs the player in as the admin, but not the admin console. -4. **Reset the database.** Stop the server, move `audiosilo.db` (and any `audiosilo.db-wal` / `audiosilo.db-shm`) out of the data folder, and start it again. You get a new password. +3. **Run `reset-admin`.** It sets a new admin password and prints it, and your books, people and listening stay as they are. [If you lose the password](./getting-started/first-run.md#if-you-lose-the-password) has the command for each install, and what to do if it refuses. +4. **Reset the database**, only if `reset-admin` can't run. Stop the server, move `audiosilo.db` (and any `audiosilo.db-wal` / `audiosilo.db-shm`) out of the data folder, and start it again. You get a new password. :::warning -Resetting never touches your audiobooks, but it erases all accounts, listening progress, bookmarks, notes and shares. Move the files aside rather than deleting them. +Resetting the database never touches your audiobooks, but it erases all accounts, listening progress, bookmarks, notes and shares. Move the files aside rather than deleting them. ::: ### I went back to an older server version diff --git a/screenshots/capture-setup.mjs b/screenshots/capture-setup.mjs index 507cddfd..f9b9a4ab 100644 --- a/screenshots/capture-setup.mjs +++ b/screenshots/capture-setup.mjs @@ -16,11 +16,12 @@ // naming step shows the two sources disagreeing and suggests the folders. The // flow is driven through its real UI with the exact English labels of the // server's admin-ui/src/i18n/locales/en.json (setup.*): a renamed button there -// needs the same rename here. Every step is continued except the -// Audiobookshelf step (left unopened, so it counts as skipped) and the Bridge -// step (no Bridge, so "Skip and finish"), which leaves the Overview card two -// steps to list. Run once per fresh --setup server: the account it makes -// closes the wizard. +// needs the same rename here. Every step is continued except the Bridge step +// (no Bridge, so Skip), which leaves the Overview card one step to list; the +// phone step comes last and ends the flow with Finish setup. The browser +// says it is in Britain (locale en-GB), so the metadata step preselects the +// United Kingdom marketplace the way a real admin's browser would. Run once +// per fresh --setup server: the account it makes closes the wizard. import {mkdir, rm} from 'node:fs/promises'; import path from 'node:path'; import {chromium} from 'playwright'; @@ -83,7 +84,8 @@ await step('build the setup books folder', async () => { // ── Capture ──────────────────────────────────────────────────────────────── const browser = await chromium.launch({args: [`--host-resolver-rules=MAP ${HOST} 127.0.0.1`]}); -const ctx = await browser.newContext(DESKTOP_CONTEXT); +// en-GB: the metadata step's marketplace is preselected from the browser's language. +const ctx = await browser.newContext({...DESKTOP_CONTEXT, locale: 'en-GB'}); await showDockerPaths(ctx, SHOWN, [['"os":"darwin"', '"os":"linux"'], ['"arch":"arm64"', '"arch":"amd64"']], {[HOST]: '127.0.0.1'}); const page = await ctx.newPage(); @@ -120,9 +122,17 @@ await step('the console flow', async () => { await page.getByRole('link', {name: 'Continue setup', exact: true}).click(); await submitSignIn(page, PASSWORD); // The console sends a server that hasn't finished setting up to the flow. - await heading('Where are your books?').waitFor({timeout: 20000}); + await heading('Your account').waitFor({timeout: 20000}); - // 1. Books folder: typed, checked, named. + // 1. Your account: the --setup admin chose their password, so a new one is + // optional; Continue with nothing changed keeps both. + await page.getByLabel('Username', {exact: true}).waitFor({timeout: 10000}); + await page.mouse.move(0, 0); + await sleep(600); + await shoot(page, 'setup/account.png'); + await leave('Continue', 'Where are your books?'); + + // 2. Books folder: typed, checked, named. await page.getByLabel('Books folder', {exact: true}).fill(SHOWN_BOOKS); await button('Check folder').click(); await page.getByText(/^Found \d+ audio files$/).waitFor({timeout: 30000}); @@ -132,17 +142,17 @@ await step('the console flow', async () => { await shoot(page, 'setup/folder.png'); await leave('Continue', 'How are your books named?'); - // 2. Naming: the samples side by side, the folders suggested. + // 3. Naming: the samples side by side, the folders suggested. await page.getByRole('list', {name: 'Sample books'}).waitFor({timeout: 30000}); await sleep(600); await tall('setup/naming.png'); await leave('Continue', 'Community metadata'); - // 3. Metadata, then the library is added and its first scan starts. + // 4. Metadata (the marketplace preselected from en-GB), then the library is added and its first scan starts. await shoot(page, 'setup/metadata.png'); await leave('Continue', 'How will people reach the server?'); - // 4. Addresses: the scan bar above the step, once the scan has finished. + // 5. Addresses: the scan bar above the step, once the scan has finished. await page.getByText(/^\d+ books ready$/).waitFor({timeout: 60000}); // ... and the "Added Audiobooks" toast has gone. await page.getByText('Added Audiobooks', {exact: true}).waitFor({state: 'hidden', timeout: 20000}).catch(() => {}); @@ -150,25 +160,25 @@ await step('the console flow', async () => { await tall('setup/reach.png'); await leave('Continue', 'Coming from Audiobookshelf?'); - // 5. Audiobookshelf: the import never opened, so Continue records it skipped. + // 6. Audiobookshelf: an info-only step, so Continue records it done. await shoot(page, 'setup/abs.png'); await leave('Continue', 'Backups and updates'); - // 6. Backups and updates. + // 7. Backups and updates. await shoot(page, 'setup/backups.png'); - await leave('Continue', 'Connect a phone'); - - // 7. Phone: the pairing QR code drawn. - await page.getByRole('img', {name: 'Pairing QR code for your phone'}).waitFor({timeout: 20000}); - await sleep(600); - await shoot(page, 'setup/phone.png'); await leave('Continue', 'Add a Bridge'); - // 8. Bridge: none paired, so the flow ends with "Skip and finish". + // 8. Bridge: none paired, so Skip. await tall('setup/bridge.png'); - await button('Skip and finish').click(); + await leave('Skip', 'Connect a phone'); + + // 9. Phone, the last step: the pairing QR code drawn, then Finish setup. + await page.getByRole('img', {name: 'Pairing QR code for your phone'}).waitFor({timeout: 20000}); + await sleep(600); + await tall('setup/phone.png'); + await button('Finish setup').click(); - // The Overview card lists the two skipped steps. + // The Overview card lists the skipped Bridge step. const card = page.locator('[aria-labelledby="finish-setup-heading"]'); await card.waitFor({timeout: 20000}); await card.getByRole('link', {name: 'Add a Bridge'}).waitFor({timeout: 10000}); diff --git a/screenshots/manifest.mjs b/screenshots/manifest.mjs index 9ca7a9c7..d89a7727 100644 --- a/screenshots/manifest.mjs +++ b/screenshots/manifest.mjs @@ -184,15 +184,16 @@ export const SHOTS = [ // ── First-run setup (capture-setup.mjs: a fresh --setup server; desktop 1440 wide, dark) ── {file: 'server/setup-wizard.png', capture: 'setup', title: 'Setup page', hint: "the --setup page: Welcome - let's set up your server, the admin username and password, Create account"}, {file: 'server/setup-wizard-done.png', capture: 'setup', title: 'Setup page - account ready', hint: 'the --setup page after Create account: Your account is ready, and Continue setup into the console'}, - {file: 'setup/folder.png', capture: 'setup', title: 'Setup - Books folder', hint: 'step 1 of the console\'s Set up your server flow: the typed books folder checked (Found 8 audio files, 4 book folders) and the library name'}, - {file: 'setup/naming.png', capture: 'setup', title: 'Setup - Book names', hint: 'step 2: four sample books named from tags and from folders side by side (the disagreeing rows marked), Folder names first preselected as Suggested (as tall as the page)'}, - {file: 'setup/metadata.png', capture: 'setup', title: 'Setup - Community metadata', hint: 'step 3: community metadata on, the region, the service or a local copy, and the line saying Continue adds the library'}, - {file: 'setup/reach.png', capture: 'setup', title: 'Setup - Addresses', hint: 'step 4, the library\'s scan done above it: the address the browser uses (a .local name) with Use as the home / public address, the two address fields, HTTPS: Off set by the config'}, - {file: 'setup/abs.png', capture: 'setup', title: 'Setup - Audiobookshelf', hint: 'step 5: what the Audiobookshelf import brings across, metadata.json read as it is, Open the import'}, - {file: 'setup/backups.png', capture: 'setup', title: 'Setup - Backups and updates', hint: 'step 6: the backup schedule (daily at 03:00), how many to keep (7), the backup folder (shown, set in config) and the update check'}, - {file: 'setup/phone.png', capture: 'setup', title: 'Setup - Connect a phone', hint: 'step 7: the pairing QR code, New code, Open the web player, the ffmpeg line and the household note'}, - {file: 'setup/bridge.png', capture: 'setup', title: 'Setup - Add a Bridge', hint: 'step 8 with no Bridge paired: what a Bridge does, the compose snippet, Connect a bridge, Skip and finish (as tall as the page)'}, - {file: 'setup/finish-card.png', capture: 'setup', title: 'Setup - Finish setting up card', hint: "the Overview's Finish setting up card (clipped) after the flow finished with the Audiobookshelf and Bridge steps skipped"}, + {file: 'setup/account.png', capture: 'setup', title: 'Setup - Your account', hint: 'step 1 of the console\'s Set up your server flow, as an admin who chose their password on the --setup page: the username, an optional new password, Keep admin and this password'}, + {file: 'setup/folder.png', capture: 'setup', title: 'Setup - Books folder', hint: 'step 2: the typed books folder checked (Found 8 audio files, 4 book folders) and the library name'}, + {file: 'setup/naming.png', capture: 'setup', title: 'Setup - Book names', hint: 'step 3: four sample books named from tags and from folders side by side (the disagreeing rows marked), Folder names first preselected as Suggested (as tall as the page)'}, + {file: 'setup/metadata.png', capture: 'setup', title: 'Setup - Community metadata', hint: 'step 4: community metadata on, the marketplace preselected from the browser (en-GB: United Kingdom), the service or a local copy, and the line saying Continue adds the library'}, + {file: 'setup/reach.png', capture: 'setup', title: 'Setup - Addresses', hint: 'step 5, the library\'s scan done above it: the address the browser uses (a .local name) with Use as the home / public address, the two address fields, HTTPS: Off set by the config'}, + {file: 'setup/abs.png', capture: 'setup', title: 'Setup - Audiobookshelf', hint: 'step 6: what the Audiobookshelf import brings across, metadata.json read as it is, Open the import'}, + {file: 'setup/backups.png', capture: 'setup', title: 'Setup - Backups and updates', hint: 'step 7: the backup schedule (daily at 03:00), how many to keep (7), the backup folder (shown, set in config) and the update check'}, + {file: 'setup/bridge.png', capture: 'setup', title: 'Setup - Add a Bridge', hint: 'step 8 with no Bridge paired: what a Bridge does, the compose snippet, Connect a bridge, Skip (as tall as the page)'}, + {file: 'setup/phone.png', capture: 'setup', title: 'Setup - Connect a phone', hint: 'step 9, the last: the pairing QR code, New code, How signing in works with Invite someone, Open the web player, the ffmpeg line and the household note (as tall as the page)'}, + {file: 'setup/finish-card.png', capture: 'setup', title: 'Setup - Finish setting up card', hint: "the Overview's Finish setting up card (clipped) after the flow finished with the Bridge step skipped"}, // ── The Bridge's page (desktop 1440x900, dark; as tall as the page) ───── {file: 'bridge/sign-in.png', capture: 'bridge', title: 'Bridge - Sign in', hint: "the Bridge page's sign-in card on a fresh Bridge: Password, Sign in, where the first password was printed, and the reset-password line"}, diff --git a/static/img/screenshots/admin/bell.png b/static/img/screenshots/admin/bell.png index 454faa4a..ad3ffcea 100644 Binary files a/static/img/screenshots/admin/bell.png and b/static/img/screenshots/admin/bell.png differ diff --git a/static/img/screenshots/admin/books-table.png b/static/img/screenshots/admin/books-table.png index 11b6089d..989b92a7 100644 Binary files a/static/img/screenshots/admin/books-table.png and b/static/img/screenshots/admin/books-table.png differ diff --git a/static/img/screenshots/admin/books.png b/static/img/screenshots/admin/books.png index a34f7114..006b9a9b 100644 Binary files a/static/img/screenshots/admin/books.png and b/static/img/screenshots/admin/books.png differ diff --git a/static/img/screenshots/admin/login.png b/static/img/screenshots/admin/login.png index a31055f1..5def4916 100644 Binary files a/static/img/screenshots/admin/login.png and b/static/img/screenshots/admin/login.png differ diff --git a/static/img/screenshots/admin/overview-phone.png b/static/img/screenshots/admin/overview-phone.png index d2a5f7ba..43d898b8 100644 Binary files a/static/img/screenshots/admin/overview-phone.png and b/static/img/screenshots/admin/overview-phone.png differ diff --git a/static/img/screenshots/admin/overview.png b/static/img/screenshots/admin/overview.png index cac6e9c8..1d2eee8c 100644 Binary files a/static/img/screenshots/admin/overview.png and b/static/img/screenshots/admin/overview.png differ diff --git a/static/img/screenshots/admin/palette-search.png b/static/img/screenshots/admin/palette-search.png index 71f92495..eba4d61d 100644 Binary files a/static/img/screenshots/admin/palette-search.png and b/static/img/screenshots/admin/palette-search.png differ diff --git a/static/img/screenshots/admin/palette.png b/static/img/screenshots/admin/palette.png index 42989429..a2fd756b 100644 Binary files a/static/img/screenshots/admin/palette.png and b/static/img/screenshots/admin/palette.png differ diff --git a/static/img/screenshots/server/setup-wizard-done.png b/static/img/screenshots/server/setup-wizard-done.png index bc5bcd28..afe9887d 100644 Binary files a/static/img/screenshots/server/setup-wizard-done.png and b/static/img/screenshots/server/setup-wizard-done.png differ diff --git a/static/img/screenshots/server/setup-wizard.png b/static/img/screenshots/server/setup-wizard.png index 683757b2..2fef19dd 100644 Binary files a/static/img/screenshots/server/setup-wizard.png and b/static/img/screenshots/server/setup-wizard.png differ diff --git a/static/img/screenshots/setup/abs.png b/static/img/screenshots/setup/abs.png index 774840d3..0a929ace 100644 Binary files a/static/img/screenshots/setup/abs.png and b/static/img/screenshots/setup/abs.png differ diff --git a/static/img/screenshots/setup/account.png b/static/img/screenshots/setup/account.png new file mode 100644 index 00000000..6d6bd622 Binary files /dev/null and b/static/img/screenshots/setup/account.png differ diff --git a/static/img/screenshots/setup/backups.png b/static/img/screenshots/setup/backups.png index ba874ba6..64808b2e 100644 Binary files a/static/img/screenshots/setup/backups.png and b/static/img/screenshots/setup/backups.png differ diff --git a/static/img/screenshots/setup/bridge.png b/static/img/screenshots/setup/bridge.png index f2e328c8..dec396b9 100644 Binary files a/static/img/screenshots/setup/bridge.png and b/static/img/screenshots/setup/bridge.png differ diff --git a/static/img/screenshots/setup/finish-card.png b/static/img/screenshots/setup/finish-card.png index f888c318..36249e2a 100644 Binary files a/static/img/screenshots/setup/finish-card.png and b/static/img/screenshots/setup/finish-card.png differ diff --git a/static/img/screenshots/setup/folder.png b/static/img/screenshots/setup/folder.png index 6cbd927a..86919644 100644 Binary files a/static/img/screenshots/setup/folder.png and b/static/img/screenshots/setup/folder.png differ diff --git a/static/img/screenshots/setup/metadata.png b/static/img/screenshots/setup/metadata.png index 18cf3200..d4ecce78 100644 Binary files a/static/img/screenshots/setup/metadata.png and b/static/img/screenshots/setup/metadata.png differ diff --git a/static/img/screenshots/setup/naming.png b/static/img/screenshots/setup/naming.png index 6a0e9b01..002183ac 100644 Binary files a/static/img/screenshots/setup/naming.png and b/static/img/screenshots/setup/naming.png differ diff --git a/static/img/screenshots/setup/phone.png b/static/img/screenshots/setup/phone.png index cbb9800e..25615975 100644 Binary files a/static/img/screenshots/setup/phone.png and b/static/img/screenshots/setup/phone.png differ diff --git a/static/img/screenshots/setup/reach.png b/static/img/screenshots/setup/reach.png index 0ddceb7d..56e23165 100644 Binary files a/static/img/screenshots/setup/reach.png and b/static/img/screenshots/setup/reach.png differ