Preserve Photos deployment configuration and verify source dependencies

This commit is contained in:
Pigway
2026-10-08 19:25:37 +08:00
parent bb452649b8
commit 0b9b548d20
5 changed files with 100 additions and 0 deletions
+2
View File
@@ -56,6 +56,8 @@ jobs:
PROD_DATA_DIR: ${{ vars.PROD_DATA_DIR }}
PROD_CONFIG_DIR: ${{ vars.PROD_CONFIG_DIR }}
PROD_PHOTOS_DIR: ${{ vars.PROD_PHOTOS_DIR }}
SYNOLOGY_PHOTOS_URL: ${{ vars.SYNOLOGY_PHOTOS_URL }}
SYNOLOGY_PHOTOS_CERT_SHA256: ${{ vars.SYNOLOGY_PHOTOS_CERT_SHA256 }}
PROD_WEB_BIND: ${{ vars.PROD_WEB_BIND }}
PROD_WEB_PORT: ${{ vars.PROD_WEB_PORT }}
PROD_STATE_VOLUME: ${{ vars.PROD_STATE_VOLUME }}
+20
View File
@@ -301,3 +301,23 @@ and change the workflow pin. All validation steps remain unchanged.
Python toolcache misses may still require setup-python to download Python from
its upstream release source. This removes Action source download dependence,
not every possible external download. No Runner or NAS network configuration is changed.
### Synology Photos dependencies
For a production photo source using Synology Photos person filtering, configure
`SYNOLOGY_PHOTOS_URL` and `SYNOLOGY_PHOTOS_CERT_SHA256` as Gitea Variables. The
certificate fingerprint must match the configured HTTPS endpoint. Keep the Photos
session in the persistent `/config` directory; never put session credentials in
Variables or repository files.
`PROD_PHOTOS_DIR` must identify the actual NAS photo directory mounted read-only
at `/photos`. Adoption requires that mount to exist on the current container;
changing this Variable alone does not create a new mount. Existing supplementary
permission groups are preserved by deployment.
Restoring SQLite and session files does not restore these runtime dependencies.
Production smoke checks now reject an enabled people-filtered source if its
Photos configuration or stored connection is missing. These checks do not prove
session validity or scan the complete catalog: after restoring a source, also
verify its read-only `/api/v1/photo-sources/{id}/images?limit=1` endpoint before
running an automation task.
+3
View File
@@ -376,6 +376,9 @@ class Deploy:
service["image"] = image
proposed = self.secret_config()
service["environment"].update(proposed)
for key in ("SYNOLOGY_PHOTOS_URL", "SYNOLOGY_PHOTOS_CERT_SHA256"):
if self.env.get(key):
service["environment"][key] = self.env[key]
service["environment"].update(
KOKONNA_MOCK=self.env["PROD_MOCK"],
KOKONNA_WRITE_ENABLED=self.env["PROD_WRITE_ENABLED"],
+19
View File
@@ -1,5 +1,6 @@
"""Read-only HTTP smoke checks. No frame writes; token read from env/private .env."""
import json
import os
from pathlib import Path
from urllib.request import Request, urlopen
@@ -26,4 +27,22 @@ for path in [
body = response.read()
print(path, "HTTP", response.status, "bytes", len(body))
assert response.status == 200
# A restored job is not usable merely because the scheduler is ready.
request = Request(
base + "/api/v1/photo-sources", headers={"Authorization": "Bearer " + token}
)
with urlopen(request, timeout=15) as response:
sources = json.load(response)
if any(source.get("enabled") and source.get("people_filter") for source in sources):
request = Request(
base + "/api/v1/integrations/synology-photos",
headers={"Authorization": "Bearer " + token},
)
with urlopen(request, timeout=15) as response:
photos = json.load(response)
if not photos.get("configured") or not photos.get("connected"):
raise RuntimeError(
"Enabled people-filtered photo source requires Photos connection"
)
print("Synology Photos dependency configured and connected")
print("Read-only smoke checks passed")
+56
View File
@@ -0,0 +1,56 @@
"""Production smoke must reject incomplete restored photo dependencies."""
import io
import json
import runpy
from pathlib import Path
from urllib.request import Request
import pytest
@pytest.mark.parametrize(
"sources,connection,passes",
[
([], {}, True),
([{"enabled": False, "people_filter": True}], {}, True),
(
[{"enabled": True, "people_filter": True}],
{"configured": False, "connected": False},
False,
),
(
[{"enabled": True, "people_filter": True}],
{"configured": True, "connected": False},
False,
),
(
[{"enabled": True, "people_filter": True}],
{"configured": True, "connected": True},
True,
),
],
)
def test_smoke_validates_people_filter_dependency(
monkeypatch, sources, connection, passes
):
calls = []
class Response(io.BytesIO):
status = 200
def read_only(request: Request, timeout):
assert request.get_method() == "GET"
calls.append(request.full_url)
value = sources if request.full_url.endswith("/photo-sources") else connection
return Response(json.dumps(value).encode())
monkeypatch.setenv("FRAME_FLOW_TOKEN", "fixture")
monkeypatch.setattr("urllib.request.urlopen", read_only)
script = Path(__file__).parents[1] / "scripts/smoke-test.py"
if passes:
runpy.run_path(str(script))
else:
with pytest.raises(RuntimeError, match="requires Photos connection"):
runpy.run_path(str(script))
assert len(calls) >= 10