storage: multi-storage management with per-storage quota
Replace the single fixed /records path with a list of storages and an
active selection (where new recordings go). Quota is now per-storage.
- config: StorageItem(id,name,type,path,quota_gb,nas-meta) + Storage keeps
global retention_days/segment_seconds; active + items. Migration from the
old single {path,quota_gb} schema → one local item.
- recorder: write to the active storage; restart_all() repoints on switch;
NAS-not-mounted guard (never write into the container overlay).
- indexer: scan ALL storage roots so recordings survive an active switch.
- retention: enforce quota PER storage via db.total_size_under /
oldest_recordings_under (path-prefix LIKE).
- api/storages: GET/POST/PUT/DELETE + activate + policy. NAS uses host-mount
(backend stores SMB params, generates the mount/fstab command, reports
ismount status). Retire old POST /api/storage.
- frontend: new "Хранилища" settings tab — table with inline per-row quota,
add form (local folder / NAS SMB), global policy (segment + retention).
- compose: add shared ./storages:/storages root for extra local folders and
host-mounted NAS shares.
VERSION 0.0.127.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+8
-4
@@ -17,7 +17,7 @@ from .recorder.supervisor import Supervisor
|
||||
from .services.retention import Retention
|
||||
from .runtime import runtime
|
||||
from . import auth, prefs
|
||||
from .api import cameras, recordings, system, ws
|
||||
from .api import cameras, recordings, storages, system, ws
|
||||
from .api.ws import ConnectionManager
|
||||
from .services import go2rtc
|
||||
|
||||
@@ -46,15 +46,18 @@ async def lifespan(app: FastAPI):
|
||||
runtime.indexer = Indexer(runtime.config, runtime.db)
|
||||
runtime.retention = Retention(runtime.config, runtime.db)
|
||||
|
||||
os.makedirs(runtime.config.storage.path, exist_ok=True)
|
||||
# каталоги локальных хранилищ; NAS не создаём — ждём монтаж на хосте (guard в recorder_task)
|
||||
for st in runtime.config.storage.items:
|
||||
if st.type == "local":
|
||||
os.makedirs(st.path, exist_ok=True)
|
||||
await go2rtc.sync_all(runtime.config) # потоки (вкл. <cam>_main) в go2rtc для рестрима
|
||||
runtime.reconciler = go2rtc.Reconciler(runtime.config) # самовосстановление потоков go2rtc
|
||||
runtime.supervisor.start_all()
|
||||
runtime.indexer.start()
|
||||
runtime.retention.start()
|
||||
runtime.reconciler.start()
|
||||
log.info("NVR запущен: камер=%d, хранилище=%s",
|
||||
len(runtime.config.cameras), runtime.config.storage.path)
|
||||
log.info("NVR запущен: камер=%d, активное хранилище=%s",
|
||||
len(runtime.config.cameras), runtime.config.active_storage().path)
|
||||
|
||||
try:
|
||||
yield
|
||||
@@ -99,6 +102,7 @@ app.include_router(auth.router)
|
||||
app.include_router(prefs.router)
|
||||
app.include_router(cameras.router)
|
||||
app.include_router(recordings.router)
|
||||
app.include_router(storages.router)
|
||||
app.include_router(system.router)
|
||||
app.include_router(ws.router)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user