Repository navigation
Setup deep links open their page; Docker permission failures name the folder, the user and the fix - #381
Merged
Conversation
While setup was unfinished, any console page opened in a fresh browser session was redirected to the flow, so the ABS step's "Open the import" (a new tab, which starts its own session) reopened the same setup step and the import was never reached. The same caught the Reach step's HTTPS settings link, bookmarks and reloads. The redirect now fires only from the Overview, where a plain visit to /admin lands. The first page a session shows with the setup state loaded marks the session either way, so moving on to the Overview from a deep link does not pull the admin back into the flow. Part of #365.
A data folder the server could not write to stopped the start with
SQLite's "unable to open database file" and nothing else, and a backups
folder it could not write to failed every backup as a generic "failed"
(VACUUM INTO's error wraps no permission error), so a NAS user was left
to work out ownership over SSH.
internal/fsaccess words such a folder once: the path, the user the
server runs as, the folder's owner and the fix (PUID/PGID set to the
owner in a container, never PUID=0 for a root-owned folder). It checks by
writing a file of its own, not by reading SQLite's error.
- Start-up: the data folder and its config and database files are
checked before config.Load and store.Open ("the data folder isn't
usable: ..."); a backups folder that refuses is a warning.
- Backups: the folder is checked before VACUUM INTO, so a refused one is
permission_denied, and the notification names the folder and the fix
(the event carries it as detail). The console's wording names the
owner problem in every language.
- Scans: a library root the server can reach but not list is unavailable
with the same words, rather than an empty library scanned OK.
Part of #368.
… fix The entrypoint ran chown -R on /data with its errors thrown away, so on a NAS share that refuses chown (Synology ACLs) nothing said why the server then could not start, and AUDIOSILO_BACKUP_DIR and the audiobooks mounts were never looked at. It now also chowns AUDIOSILO_BACKUP_DIR (absolute, never /), then checks as the user the server will run as that the data and backups folders can be written and every other mounted folder can be read. For each one that can't, it prints the folder, the user, the folder's owner, whether the chown failed, and the fix (PUID/PGID set to the owner, or the compose user: under --user). It warns and carries on: the server's own start-up error says the same about the data folder. scripts/test-entrypoint.sh runs the cases in Docker on the image's Alpine base; README, docker-compose.yml and CONVENTIONS describe it. Part of #368.
The data folder check demanded write access to config.yaml, but a read-only one (mounted so, or edited as root) started before and only a setting saved from the console needs to write it. It is now checked for reading only, worded the same way when the server can't read it. A backups folder the server can't list now names the folder, the user and the fix in the log behind the console's failed list. Part of #368.
…nt checks AUDIOSILO_BACKUP_DIR may be a NAS share other apps keep their backups in, so the entrypoint now changes the owner of the folder and of AudioSilo's own backups in it only, not the whole tree. The read check of each mounted folder, and the stat that names its owner, now give up after 5 seconds: a hard-mounted share whose server is gone can block for minutes and must not hold the server's start. Part of #368.
The hook repeated the model's guards and owned the half of the rule that keeps a deep link from being pulled into the flow later, so only a full-app test could express it. redirectDecision now answers go, stay or none; the hook marks the session on go or stay and writes sessionStorage once, not on every navigation while setup is unfinished. Part of #365.
fsaccess.cause was a copy of library's pathErrText. One exported fsaccess.Cause serves both, so the scan log and the permission wording cannot drift. Hint builds its 'give user ... access' clause once. Part of #368.
The notification's detail was taken from the error's text whenever the console code was permission_denied, but only the write check produced the sentence naming the folder, the user and the fix. A chmod or rename refused with EPERM (common on SMB shares) put 'chmod /backups/.audiosilo-...: operation not permitted' in the notification. The backup now wraps any permission refusal once, before the code, the log and the detail. Part of #368.
The start-up write check of the backups folder only logs a warning, but ran before the listener: with AUDIOSILO_BACKUP_DIR on a hard-mounted share whose server is gone it could hold the start for minutes. It runs in its own goroutine now. The data folder's error prefix is written once. Part of #368.
The read check listed the whole folder with ls, which stats every entry: on a FUSE or cloud mount with a large flat library root that can pass the 5 s bound and print a false 'can't read' warning. find stops at the first entry. The fix clause is built once and only the mounts kept are unescaped. Part of #368.
A hand-written config.yaml mounted read-only (or owned by root) with no server_id has one minted at start; the save then failed with a bare 'open /data/config.yaml: permission denied', with none of the folder, user and fix wording the data folder's other failures get. The save's error is worded the same way, and the dataFiles comment says a read-only config starts only when it names the id. Part of #368.
CheckFolder took every stat error as 'not made yet', so a backups folder behind a parent that refuses the server (EACCES on stat) passed the start-up check and the first backup failed with no earlier warning. Only a missing folder passes now. Part of #368.
Cause dropped a PathError's path but not a LinkError's, so a backup whose rename was refused read 'the server can't write to /backups: rename /backups/.x.db.tmp /backups/x.db: permission denied' in the log and the notification. Part of #368.
The entrypoint runs as root before it drops privileges, and several inputs took that further than meant: - usable_path refused only the literal '/', so --data // (or /., /data/..) ran chown -R on the container's root, mounted libraries included. A '..' component and anything that is the same inode as / are refused, and trailing slashes are trimmed (they also defeated the mount exclusions, so /backups/ was read again as a folder of books). - The backups chown followed links: in a share other apps write to, a link named audiosilo-x.db had its target re-owned at every start. Regular files only, and chown -h. - The write check made a file at a predictable name with ': >', which follows a link planted there and empties its target as the server's user. It uses mktemp. - mkdir, chown, find, the -d test and the write check on the backups folder were unbounded, so AUDIOSILO_BACKUP_DIR on a hard-mounted share whose server is gone held the container's start. Each has the 5 s bound the read check has; the data folder's chown -R stays unbounded (a large folder needs longer). - BusyBox's timeout leaves a watcher behind each command; one alive at the exec stayed under the server as a zombie for the container's life. They are ended and collected first. - The owner match compared uid:gid, so a folder the server's user owns with another group got PUID/PGID advice that changes nothing; it compares the uid, as fsaccess.Hint does. scripts/test-entrypoint.sh has cases for the root spellings, the links, the trailing slash, the owner match and the watchers; the write check's file name and the bounds on the backups folder have none (a hung share is not something the script can mount). CONVENTIONS.md's Folder permissions entry says the rules. Part of #368.
…roblem A folder check cut off by the 5 s bound (timeout's 124 or BusyBox's 143) now says the share did not answer within 5 seconds, with no PUID/PGID advice, instead of explaining it as a folder owned by another user.
This was referenced Nov 13, 2026
KodeStar
added a commit
to KodeStar/audiosilo-frontend
that referenced
this pull request
Nov 14, 2026
…#294) Closes #167. For servers behind a sign-in proxy (Authelia, Authentik, Cloudflare Access service tokens and similar), a connection can carry custom HTTP headers that the proxy checks. ## What it does - **Entry:** a Proxy headers section on the connect screen (type the address and headers before Continue or a QR scan; pairing keeps them when the address matches) and a Proxy headers card on the Account page (pick the address, add, change, remove; a saved value is never shown again, "Kept as it is"). Strings in all 6 locales. - **Storage:** secure-store per connection (`audiosilo.proxy-headers.<id>`, the same place and keychain setting as the token; on the web, where the token also lives in localStorage). AsyncStorage holds only a `hasProxyHeaders` marker. Deleted wherever the token is; the identity migration copies with a read-back check and deletes the old entry only at clean-up. - **Scope:** sent with requests to the https address (scheme+host+port) the set was entered for, which may be the home address; never on a plain-http request, and not added to requests for other addresses. A 3xx to another origin is followed by the platform: on Android every stack sends the headers on to the new origin (drops Authorization); iOS unchecked (follow-up #295). Reserved names (Authorization, Cookie, Host, Range, Content-Type, X-AudioSilo-Client, framing headers) and control characters are refused. - **Where:** every API request; expo-image, the native players and the reader's EPUB download via `authHeaders()`; downloads, car and widget covers via `proxyHeadersFor(url)`. On the web only same-origin fetches carry them (`<img>`/`<audio>` rely on the proxy's browser sign-in). Not carried: Chromecast (cast refused, reason `proxy`), the watch apps, calendar apps. No server change. - **Sharing on one server:** a new connection (another person, a link re-pair with no set of its own) keeps the set that carried its successful sign-in or exchange; a reconnect with a known identity keeps only its own or the typed set. ## Tests and review New unit tests for storage, origin scoping, https rule, migration, reconnect/re-pair, hydrate clean-up, editor and card, and a locale test that refuses "nowhere else" style claims. Full gate green (559 suites, 7730 tests). Reviewed at the security level (two full /code-review passes) plus two fix rounds. ## Verified live (web, served player over https) Headers on 31/31 API requests to the origin after relaunch, none on covers or the stream (as documented), none to another origin even with CSP stripped; masked after save and absent from HTML, inputs and console; reserved names and control characters refused; http-only connection shows the https caption and no Add; Remove and sign-out clear storage; sign-in, browse and playback unchanged without headers. ## Verified on Android (Pixel 6a, release build, behind a header-checking https proxy) API (264/264 requests), expo-image covers, Media3 playback and downloads all carried the header and worked; without it the proxy refused and the app showed an error; an http address shows the https caption. A GET 302 to a second https origin: okhttp fetch, expo-image, downloads and Media3 all followed it and sent the custom header there, dropping Authorization. **Still owed (iOS device):** URLSession, SDWebImage covers, downloads, AVURLAsset, CarPlay cold start, the widget, and the same redirect check. Android Auto, notification artwork and the reader's EPUB download were not exercised. Docs: KodeStar/audiosilo-docs#305. Pairs with KodeStar/audiosilo-server#381 (same batch, independent).
KodeStar
added a commit
to KodeStar/audiosilo-docs
that referenced
this pull request
Nov 14, 2026
… for a sign-in proxy (#305) Docs for v2.7.0 row 15b item 2 and row 15's custom headers. - **Install on a NAS (closes #277):** a new Synology page (Container Manager project, the folder's owner as PUID / PGID, Shared Folder permissions, where the log is; DSM labels not yet checked on a real NAS, and the page says so). The Docker quickstart creates the data folder first, gains a `docker run` example, and links Get the apps. Backups live in `data/backups`; a backups folder elsewhere needs the same owner and is never a share mounted at `/data/backups`. - **Troubleshooting `#permission-denied`:** quotes the container's warning lines, the start error, the "didn't answer within 5 seconds" line and the library offline line verbatim from the server's verified output. - **First run:** only a plain visit to `/admin` opens setup; the setup step's links open their page (server#365). - **Proxy headers (fe#167):** "Behind a sign-in proxy" in Remote access, the Account page's Proxy headers card, https only, what can't send them (web cross-origin, browser images and audio, Chromecast, watches, calendar apps). Developer docs: storage key, API client, connect screen, cast block reason `proxy`. - **Developer docs:** a Folder permissions section (the server's own checks and the Docker entrypoint), the backup `detail` field, scanner offline rule, setup redirect rule. - Screenshot: `web-player/account.png` recaptured with the Proxy headers card. Build green (links and anchors checked). Reviewed with /simplify and /code-review. **Merge after** KodeStar/audiosilo-server#381 and KodeStar/audiosilo-frontend#294; **before** KodeStar/audiosilo-site#69 (its links point here).
KodeStar
added a commit
to KodeStar/audiosilo-site
that referenced
this pull request
Nov 14, 2026
…roid APK and Obtainium (#69) From a new user's first install on a Synology NAS (v2.7.0 row 15b item 2). - Install pages (closes #60): drop "works regardless of ownership"; create the data folder before the first start; Synology owners are usually 1026 / 100, so set PUID / PGID to the folder's owner; backups live in `data/backups` and a separate `AUDIOSILO_BACKUP_DIR` needs the same owner. The shared install snippets start with `mkdir -p data` and the docker run one gets a PUID / PGID line (1000, the entrypoint's default). - Android outside Google Play (closes #27): the Android card (download page and /player) and a new home page "Get the app" block (App Store and Google Play badges as equals, plus a hero link) offer the APK from the latest GitHub release and Obtainium, gated by `ANDROID_LIVE`. `StoreBadge` owns each store's link and wording; `ApkNote` is shared. Build and check pass (139 pages, 0 errors). Verified in a browser at 390 and 1440 px: installation, download and home pages, the anchors, no horizontal overflow, no console errors. **Merge after the docs PR:** it links docs pages that the docs PR adds (`/users/getting-started/install-synology`, `/users/troubleshooting#permission-denied`), and master deploys live. Pairs with KodeStar/audiosilo-server#381.
KodeStar
added a commit
that referenced
this pull request
Nov 14, 2026
…te page, scan-with-the-app wording; LibriVox add keeps an admin's match (#384) Closes #367, #379, #380. What they leave open moved to #385 (LibriVox identity edges) and #386 (invites and pairing polish). ## Invites and pairing (#367) A new user (Synology, Android, coming from Audiobookshelf) scanned the pairing QR with the phone's camera, landed in the web player, and found the app still signed out. Our copy said the camera opens the app. It doesn't: the app claims no domains (and can't on self-hosted addresses), so only the app's own Scan a QR code pairs it. - Copy fixed in every console locale and the connect page: "Open the AudioSilo app and tap Scan a QR code. The phone's camera opens the web player instead." The invite QR encodes `/connect#code=`, so for invites the copy says the camera opens the invite page. - Store links (App Store, Google Play, APK from the latest release) on `/` and `/connect`, before and after a redeem. `links` gains `ios`, `android` and a new `apk` (additive). - The invite page on a phone leads with "Get the app, then come back to this link", the store links and the web player, and hides the self-scan QR. The Admin button shows only for an admin's code: the redeem payload gains `admin: true` (from the code's own user, absent for a listener). - Invite dialog: 7 days by default (the API's default is unchanged), a Share button (share sheet, copy fallback on plain http), an optional password for a new person (the existing `createUser` field), and warnings for a localhost link, a home-network-only link and https at an IP address. ## LibriVox add (#379, #380) - The fold reads the Bridge's new `adopted` result field (KodeStar/audiosilo-bridge, fix/librivox-fetch-followups). If the folder was already there, an existing match (an admin's rematch) is kept. If it is newly placed, the match is replaced and a deleted book's leftover ASIN/ISBN overrides and enrichment at that path are dropped. If the field is absent (an older Bridge), behaviour is unchanged. - The finished event carries `librivox_match` (matched, kept or unmatched). The notification claims the recording only when matched, and an unmatched fetch goes out as a warning. - Fetch params carry `title`, so a failed or parked fetch is named by its book. Work and recording ids are checked against the Bridge's slug pattern. A finished job no longer shows its last step. The `not_found` toast is in the console's words. es/fr/it/pt use the feminine for the Bridge. - Not in this PR: rebuilding a book's identity from `.audiosilo/librivox.json`, which needs scanner work (follow-up). ## Review and verification - Review: /simplify, then two `/code-review high --fix` passes on the whole branch. One data-loss finding across the seam (a resumed placement reported `adopted:false`) was fixed on the Bridge side. Gate green, including the admin console build and tests, `go test -race`, golangci-lint and the native-engine run. - Verified live against a scratch server with the real Bridge on its branch: - the invite dialog: 7 days, the password, Share copying on http, all three warnings; - the setup and Pair a device copy; - store links on `/` and `/connect`; - the phone invite page under iPhone 15 and Pixel 7 device profiles, with the Admin button only for an admin; - a LibriVox add reporting matched, then Add again keeping an ASIN and an admin rematch set in between ("kept"). - Not verified live: the unmatched notification (unit-tested). A real NAS run from #381 is still owed. Pairs with KodeStar/audiosilo-frontend (feat/new-user-b), KodeStar/audiosilo-bridge (fix/librivox-fetch-followups) and KodeStar/audiosilo-docs (docs/new-user-b, to follow).
KodeStar
added a commit
to KodeStar/audiosilo-site
that referenced
this pull request
Nov 14, 2026
A `draft: true` feature post, `nas-and-sign-in-proxy`, for the work merged in KodeStar/audiosilo-frontend#294 (proxy headers), KodeStar/audiosilo-server#381 (setup deep links, Docker permission failures that name the folder, the user and the fix) and KodeStar/audiosilo-docs#305 (Install on a Synology NAS, permission troubleshooting). Drafts are excluded from the build, so this can merge now; the 2.7.0 release publishes it. - Claims checked against the code on main and the docs (one review at high). It quotes the log text and console labels verbatim from the docs, scopes proxy headers to the one https address they were entered for, and covers Android passing them on through a redirect. - Before publishing: the iOS device run for proxy headers (fe#294) and a real NAS run (server#381) are still owed. The Synology steps were written for DSM 7.2 and the post says they have not been checked on a NAS. - Gate: `yarn build && yarn run check` green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two items from a new user's first install (Synology NAS, coming from Audiobookshelf), v2.7.0 row 15b items 1 and 2.
Setup deep links (closes #365)
The console's setup redirect ran on any page, and a new tab starts with empty sessionStorage, so "Open the import" in the Audiobookshelf step (and any bookmark) reopened setup instead of the linked page. The redirect now fires only from a plain visit to the Overview; the first page a session shows once setup state loads marks the session, so moving from a deep link to the Overview does not pull the admin back (
admin-ui/src/features/setup).Docker permission failures explain themselves (closes #368)
internal/fsaccess: one test-write probe and one sentence naming the folder, the user the server runs as, the folder's owner and the fix (PUID/PGID to the owner, or give that user access).config.yamlstill starts); an unwritable backups folder warns at start.permission_denied(it was reported as a genericfailed), and the bell and notifications carry the sentence.docker-entrypoint.sh: after the chown, checks /data and the backups folder for writing and every other mount for reading as the server's user, warns and starts. Each mount check is bounded at 5 s and a share that does not answer says so (no owner advice).AUDIOSILO_BACKUP_DIRis now chowned (AudioSilo's own backups only, no symlinks followed). Refuses to chown a data path that resolves to/(//,/.,/data/..).Tests and verification
scripts/test-entrypoint.sh(Docker, 19 cases, not in CI).Docs: KodeStar/audiosilo-docs PR to follow (Synology page, permissions troubleshooting). Site: KodeStar/audiosilo-site fix/new-user-a.