Archive location (Gitea issue 7): ARCHIVE_ROOT, Admin file-locations API, archive-copy job, doctor check

A filed packet's sealed data book, documents and a manifest are copied to the archive folder by the worker.
A failed copy never blocks filing: it is retried with backoff and raises the archive_failed condition.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-30 17:50:04 -06:00
co-authored by Claude Opus 5.5
parent 3fdf063b70
commit da31b0a87a
21 changed files with 1537 additions and 11 deletions
+4 -2
View File
@@ -33,7 +33,7 @@ def read_secret(path: Path | str, what: str = "secret") -> str:
return value
_OPTIONAL_PATHS = ("ldap_ca_bundle", "smtp_ca_bundle")
_OPTIONAL_PATHS = ("ldap_ca_bundle", "smtp_ca_bundle", "archive_root")
class Settings(BaseSettings):
@@ -61,6 +61,8 @@ class Settings(BaseSettings):
assets_dir: Path = Path("/data/assets")
backup_dir: Path = Path("/backups")
# The archive share (Gitea issue 7). Unset or blank turns the archive off. The Admin picks a folder under it.
archive_root: Path | None = None
session_key_file: Path = Path("/run/secrets/session_key")
seal_key_file: Path = Path("/run/secrets/seal_key")
static_dir: Path = Path("/app/static")
@@ -125,7 +127,7 @@ class Settings(BaseSettings):
if isinstance(data, dict):
data = dict(data)
for key in list(data):
if key.lower() in _OPTIONAL_PATHS and data[key] in ("", None):
if key.lower() in _OPTIONAL_PATHS and (data[key] is None or not str(data[key]).strip()):
data[key] = None
for key in list(data):
if key.lower() in ("stack", "feed_source", "drawing_source") and isinstance(data[key], str):