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
|
# PHOTO_ROOT=/photos
|
||||||
# Optional WebDAV credential reference; prefer a secret file under /config.
|
# Optional WebDAV credential reference; prefer a secret file under /config.
|
||||||
# FRAME_FLOW_SOURCE_CLOUD_FILE=/config/webdav-cloud.secret
|
# 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:
|
pigway-frame-flow:
|
||||||
environment:
|
environment:
|
||||||
PHOTO_ROOT: /photos
|
PHOTO_ROOT: /photos
|
||||||
|
group_add:
|
||||||
|
- "${NAS_PHOTOS_GID:-10333}"
|
||||||
volumes:
|
volumes:
|
||||||
- type: bind
|
- type: bind
|
||||||
source: ${NAS_PHOTOS_DIR:?Set NAS_PHOTOS_DIR to your existing photo directory}
|
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
|
```dotenv
|
||||||
NAS_PHOTOS_DIR=/volume2/photo
|
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:
|
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
|
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.
|
Missing host folders fail explicitly rather than being created automatically.
|
||||||
|
|
||||||
Open **Gallery → Photo sources → Add source**, choose **NAS local folder**,
|
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",
|
"Photo directory is unavailable; check its read-only mount",
|
||||||
404,
|
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
|
return directory
|
||||||
|
|
||||||
def headers(self, source):
|
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"])
|
op = service.operation(rows[0]["id"])
|
||||||
assert op["status"] in ("SYNCING", "COMPLETED")
|
assert op["status"] in ("SYNCING", "COMPLETED")
|
||||||
assert service.state("living-room")["previous_image_id"] == original
|
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