Allow existing NAS photo read group and reject unreadable catalogs
This commit is contained in:
@@ -20,3 +20,6 @@ BUILD_TIME=unknown
|
||||
# PHOTO_ROOT=/photos
|
||||
# Optional WebDAV credential reference; prefer a secret file under /config.
|
||||
# FRAME_FLOW_SOURCE_CLOUD_FILE=/config/webdav-cloud.secret
|
||||
|
||||
# Optional existing group with photo read permission; does not change NAS ACLs.
|
||||
# NAS_PHOTOS_GID=101
|
||||
|
||||
@@ -3,6 +3,8 @@ services:
|
||||
pigway-frame-flow:
|
||||
environment:
|
||||
PHOTO_ROOT: /photos
|
||||
group_add:
|
||||
- "${NAS_PHOTOS_GID:-10333}"
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ${NAS_PHOTOS_DIR:?Set NAS_PHOTOS_DIR to your existing photo directory}
|
||||
|
||||
@@ -10,6 +10,8 @@ Add the actual existing photo directory to `.env`:
|
||||
|
||||
```dotenv
|
||||
NAS_PHOTOS_DIR=/volume2/photo
|
||||
# Optional: existing NAS group allowed to read that folder (check its ACL).
|
||||
# NAS_PHOTOS_GID=101
|
||||
```
|
||||
|
||||
The path above is an example, not a detected directory. Enable the optional mount:
|
||||
@@ -20,7 +22,12 @@ docker compose -f docker-compose.yml -f deploy/docker-compose.photos.yml up -d
|
||||
```
|
||||
|
||||
The folder is mounted **read-only** at `/photos`. The existing container user must
|
||||
already have read/traverse permission. Frame Flow does not change NAS permissions.
|
||||
already have read/traverse permission. If the Synology ACL grants a group access,
|
||||
set `NAS_PHOTOS_GID` to that existing group ID; the optional Compose overlay adds
|
||||
only container group membership, keeping UID 10333 and the photo bind mount read-only.
|
||||
Group 101 is a deployment example, not a universal default. Frame Flow does not
|
||||
change NAS permissions. Unreadable roots return an explicit HTTP 403 rather than
|
||||
an empty catalog.
|
||||
Missing host folders fail explicitly rather than being created automatically.
|
||||
|
||||
Open **Gallery → Photo sources → Add source**, choose **NAS local folder**,
|
||||
|
||||
@@ -157,6 +157,15 @@ class PhotoSources:
|
||||
"Photo directory is unavailable; check its read-only mount",
|
||||
404,
|
||||
)
|
||||
try:
|
||||
with os.scandir(directory):
|
||||
pass
|
||||
except PermissionError:
|
||||
raise FlowError(
|
||||
E.FORBIDDEN,
|
||||
"Photo directory is not readable; configure the container's existing NAS read group",
|
||||
403,
|
||||
) from None
|
||||
return directory
|
||||
|
||||
def headers(self, source):
|
||||
|
||||
@@ -204,3 +204,21 @@ def test_scheduled_random_photo_runs_once_with_restore(app, service, photos):
|
||||
op = service.operation(rows[0]["id"])
|
||||
assert op["status"] in ("SYNCING", "COMPLETED")
|
||||
assert service.state("living-room")["previous_image_id"] == original
|
||||
|
||||
|
||||
def test_unreadable_source_is_forbidden_not_empty(
|
||||
client, photos, settings, monkeypatch
|
||||
):
|
||||
import os
|
||||
|
||||
original = os.scandir
|
||||
|
||||
def restricted(path):
|
||||
if str(path) == str(settings.photo_root / "family"):
|
||||
raise PermissionError("permission denied")
|
||||
return original(path)
|
||||
|
||||
monkeypatch.setattr(os, "scandir", restricted)
|
||||
response = client.get("/api/v1/photo-sources/family/images")
|
||||
assert response.status_code == 403
|
||||
assert response.json()["error"]["code"] == "FORBIDDEN"
|
||||
|
||||
Reference in New Issue
Block a user