Commit graph

14 commits

Author SHA1 Message Date
BoulderBadgeDad
aabf1c0e6a Fix #760: chown /app/storage to PUID on every start (album-bundle staging EACCES)
The album-bundle staging area /app/storage is baked into the image owned by the
build-time soulsync UID. The entrypoint only re-chowned it to the runtime PUID
inside the GATED recursive chown (entrypoint.sh:43), which is skipped whenever
/app/data is already owned correctly — and /app/storage was missing from the
UNCONDITIONAL per-start chown (line 85). So on installs whose PUID differs from
the build UID and whose /app/data is already correct, /app/storage kept its
build ownership and wasn't writable, and the Soulseek album-bundle flow died
with:

  PermissionError: [Errno 13] Permission denied: 'storage/album_bundle_staging'

(/app/Stream was added to the unconditional chown after this exact bug;
/app/storage slipped through.)

Add /app/storage — plus /app/MusicVideos and /app/scripts, which were also
missing — to the unconditional mkdir+chown (lines 84-85) and the writability
audit (line 92), matching the Dockerfile's pre-baked dir list. /app/storage is
now chowned to the runtime PUID on every start regardless of the gated
recursive chown. Verified with bash -n; all four dir lists are now consistent.
2026-06-01 13:19:26 -07:00
Tyler Richardson-LaPlume
0b325da3e9 Usenet bundle: writable staging dir + client→local path resolution (#721)
Follow-up to the poll fix, covering the two things that blocked a
successful end-to-end album import once the poll itself stopped
freezing:

1. Staging dir permissions
   The album-bundle private staging path defaults to
   'storage/album_bundle_staging' -> /app/storage, but /app/storage was
   never created or chowned by the image (unlike /app/Staging,
   /app/Transfer, etc.), and /app is root-owned. The copy failed with
   "[Errno 13] Permission denied: 'storage'" under the non-root soulsync
   UID. Added /app/storage to the Dockerfile build-time mkdir+chown and
   the entrypoint PUID/PGID chown, exactly like the sibling runtime dirs.

2. Client->local path resolution
   Usenet/torrent clients report save paths from inside THEIR OWN
   container (e.g. SAB '/data/downloads/music/<album>'); SoulSync often
   mounts the same files at a different point ('/app/downloads/<album>').
   Feeding the client path straight to the audio walker yields
   "No audio files found" though the files are physically present.
   New resolve_reported_save_path():
     a. use the reported path as-is if readable (mirrored mounts),
     b. apply explicit download_source.usenet_path_mappings
        ({from,to}, Sonarr/Radarr-style) for non-shared layouts,
     c. basename fallback under SoulSync's own download roots —
        zero-config for the standard shared-volume arr setup.
   Wired into both call sites in usenet.py AND torrent.py
   (download_album_to_staging + _finalize_download), logging any
   translation and including the resolved path in the no-audio error.

Tests: resolver verbatim / explicit-mapping / basename-fallback /
priority / not-found / empty / mapping-miss-then-basename. ruff +
compileall + pytest green (645 in the download suites).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 02:04:47 -04:00
Broque Thomas
a33faaeb38 fix(docker): pre-bake /app/Stream so basic-search playback works on rootless Docker
`core/streaming/prepare.py:94-97` creates /app/Stream lazily via
`os.makedirs(stream_folder, exist_ok=True)` on first playback. Under
standard Docker this works because the container's `root` writes /app
without restriction. Under rootless Docker / Podman the in-container
soulsync UID maps to a host UID that can't write to /app, so the
mkdir silently fails and the streaming "Play" flow errors out with
no obvious user-facing cause.

Same root cause + same fix shape as the May 2026 /app/Staging restart-
loop fix — pre-bake the directory at image build time (when the layer
is owned by root), and thread it through every entrypoint.sh spot that
touches the canonical app-dir list.

Not added to VOLUME — /app/Stream is a transient single-file cache
(cleared on every new playback), no persistence value.

Touched lines:

- Dockerfile: mkdir + chown line that pre-bakes runtime dirs.
- entrypoint.sh: the recursive chown gated on UID change, the always-runs
  mkdir + chown, and the writability audit loop.

No code change. Streaming tests pass unchanged (they use tmp_path, not
/app/Stream).
2026-05-19 19:30:54 -07:00
Broque Thomas
decb62dcc9 Docker: pre-bake /app/Staging + writability audit (fix restart loop)
Discord report: container refused to start after pulling latest.
Logs showed `mkdir: cannot create directory '/app/Staging':
Permission denied`. `set -e` in entrypoint.sh then aborted the script
and the container restart-looped.

Root cause traced to commit 70e1750 (2026-05-08, image-bloat fix):
the Dockerfile chown was changed from `chown -R /app` to a scoped
chown on specific subdirs to avoid a redundant layer that was
duplicating the entire /app tree. Side effects:

1. `/app` itself went from soulsync:soulsync (via the recursive walk)
   to root:root (Docker WORKDIR default — never re-chowned).
2. `/app/Staging` was the only runtime mount-point dir NOT pre-baked
   into the image — every other bind-mountable dir (config, logs,
   downloads, Transfer, MusicVideos, scripts) was in the Dockerfile's
   `mkdir -p` + `chown` list. Staging was left to the entrypoint.

On rootless Docker / Podman where in-container "root" maps to a host
UID, the entrypoint mkdir on `/app/Staging` could fail with EACCES
depending on the bind-mount path's host ownership.

Fix has three parts:

1. **Dockerfile** — added `/app/Staging` to the runtime mkdir +
   scoped chown list. Closes the asymmetry with the other bind-
   mountable dirs. Image now ships with the directory pre-baked
   owned soulsync:soulsync so the entrypoint mkdir is a guaranteed
   no-op even when bind-mount perms are weird.

2. **entrypoint.sh mkdir + chown** — both now have `|| true` so any
   future bind-mount permission quirk surfaces as a log line, not
   a `set -e` crash + restart loop. Previously only the chown had
   the `|| true` suffix; mkdir was bare.

3. **entrypoint.sh writability audit** — new loop at the end of
   the setup phase runs `gosu soulsync test -w "$dir"` against
   every bind-mountable dir. When a dir isn't writable by the
   soulsync user, logs a loud warning with the exact host-side
   `chown` command needed to fix it. Catches the underlying bind-
   mount perm issue that the restart-loop fix would otherwise mask
   (container starts but auto-import / downloads write into
   unwritable dirs and fail silently). This is the diagnostic that
   would have surfaced the root cause without needing the user to
   share a container-restart screenshot.

Zero behavior change for users whose containers were already
starting fine. Defensive against the rootless/podman config that
broke after the image-bloat refactor.

Verified shell syntax with `bash -n entrypoint.sh`. Full pytest
2693 passed (no Python touched).
2026-05-11 13:35:31 -07:00
dlynas
e4bdb8bc17 fix: skip recursive chown when data directory ownership already matches
After the first startup the data directories are already owned by the
correct PUID:PGID. Subsequent restarts now stat /app/data and skip the
expensive recursive walk when ownership is already correct, even when
PUID/PGID differ from the image defaults.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 02:08:34 -04:00
dlynas
2cd2f9c443 fix: skip recursive chown on startup when UIDs are already correct
The unconditional chown -R on every container start was walking the
entire /app tree (including large music libraries) even when nothing
needed fixing. Now only the directory nodes themselves are chowned at
startup; the recursive walk still runs inside the UID-change branch
where it is actually needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 00:46:45 -04:00
Broque Thomas
77a781caba Pin yt-dlp in requirements.txt, drop pip install from entrypoint
Closes #367 (reported by JohnBaumb).

The Docker entrypoint ran `pip install -U yt-dlp --quiet --no-cache-dir`
on every container start. Three problems with that:

- Non-deterministic startup: each restart could pick up a different
  yt-dlp version, making "works on my machine" debugging harder.
- Network dependency at boot: PyPI being slow/unreachable gated the
  app coming up.
- In-place upgrades inside running containers can race with active
  yt-dlp invocations and aren't a great pattern.

Picked Option A from the issue: pin to an exact version in
requirements.txt (`yt-dlp==2026.3.17`) and remove the entrypoint
install entirely. yt-dlp comes baked into the image now via the
existing `pip install -r requirements.txt` in the Dockerfile.

Tradeoff: YouTube fixes ship via SoulSync releases now instead of
"next container restart". The pin is documented inline with how to
bump it.

Net change: -3 entrypoint lines, requirements.txt pin tightened,
WHATS_NEW '2.4.1' block opened (entries hidden until version bumps).

553 tests pass.
2026-04-26 18:02:20 -07:00
Broque Thomas
9fcbd323a5 Add stream source setting, auto-update yt-dlp on container start
Stream source:
- New setting in Settings → Downloads: "Stream / Preview Source"
- Options: YouTube (instant, default) or Active Download Source
- YouTube streams require no auth and are instant
- If active source is Soulseek, automatically falls back to YouTube
- Uses direct client search (bypasses orchestrator's download mode)
- Config key: download_source.stream_source

Docker:
- entrypoint.sh now runs pip install -U yt-dlp on every container
  start, so Docker users always have the latest yt-dlp without
  rebuilding the image
2026-03-26 19:27:35 -07:00
Broque Thomas
8d46d3746b Fix Docker upgrade crashes from stale volume mounts and partial DB migrations 2026-03-09 11:44:00 -07:00
Broque Thomas
2a40a59da5 Fix entrypoint for Podman rootless compatibility 2026-03-08 12:26:39 -07:00
Broque Thomas
3cf8461560 add staging to entrypoint as well as fix unraid template 2026-02-14 08:30:12 -08:00
Broque Thomas
61a698aefa Move database files to /app/data and use env var for path
Updated Dockerfile, entrypoint.sh, and Python code to store database files in /app/data instead of /app/database, avoiding conflicts with the Python package. The database path is now configurable via the DATABASE_PATH environment variable, improving flexibility for container deployments.
2026-01-01 09:53:39 -08:00
Broque Thomas
14cfbd01f4 unraid fix 2025-11-17 12:58:49 -08:00
Broque Thomas
2f8bad23a8 Add manual track matching and Docker permission docs
Implements manual track matching (discovery fix modal) for YouTube, Tidal, and Beatport platforms, allowing users to search and select Spotify tracks for unmatched results. Adds backend endpoints and frontend logic for updating matches, improves conversion of discovery results for sync/download, and updates Dockerfile/entrypoint for dynamic PUID/PGID/UMASK support. Includes a new DOCKER_PERMISSIONS.md guide.
2025-10-03 10:48:25 -07:00