fix: retry recording when storage is slow to mount at boot
A power cut makes the recordings drive's fsck slow, so the mount can appear tens of seconds after nextnvr starts. The recorders then hit the unmounted, root-owned mountpoint, failed mkdir, and exited permanently — recording stayed dead until a manual restart. - recorder: on mkdir failure, log and retry every 15s instead of exiting, so recording resumes once storage returns - storage: add waitForStorage() and call it before the writers start, so startup waits (up to 120s) for the recordings dir to be present and writable; live view still starts if storage never appears
This commit is contained in:
parent
a54ff08ebb
commit
f794c57198
3 changed files with 51 additions and 4 deletions
11
main.go
11
main.go
|
|
@ -62,6 +62,17 @@ func main() {
|
|||
app.go2rtc = go2rtc
|
||||
}
|
||||
|
||||
// Wait for the recordings storage before starting any writers. On a slow
|
||||
// boot (e.g. fsck after a power cut) the mount can appear tens of seconds
|
||||
// late; this avoids the recorders failing against an unmounted, root-owned
|
||||
// mountpoint. If it times out we still start — live view does not need
|
||||
// storage, and the recorder retries on its own.
|
||||
if st := waitForStorage(cfg.Storage.RecordingsPath, 120*time.Second); st.OK {
|
||||
log.Printf("startup: storage ready — %s", cfg.Storage.RecordingsPath)
|
||||
} else {
|
||||
log.Printf("startup: storage NOT ready (%s) — starting anyway; recording will retry", st.Message)
|
||||
}
|
||||
|
||||
// Start snapshot engine for all enabled cameras.
|
||||
snaps := NewSnapshotEngine(cfg.Storage)
|
||||
snaps.StartAll(cfg.Cameras)
|
||||
|
|
|
|||
18
recorder.go
18
recorder.go
|
|
@ -107,10 +107,6 @@ func (rm *RecorderManager) startRecorder(cam CameraConfig) {
|
|||
}
|
||||
|
||||
outDir := filepath.Join(rm.config.RecordingsPath, camDirName)
|
||||
if err := os.MkdirAll(outDir, 0755); err != nil {
|
||||
log.Printf("recorder: %s — mkdir %s failed: %v", cam.ID, outDir, err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
|
|
@ -120,6 +116,20 @@ func (rm *RecorderManager) startRecorder(cam CameraConfig) {
|
|||
default:
|
||||
}
|
||||
|
||||
// Ensure the output directory exists. Storage may not be mounted yet
|
||||
// (e.g. a slow boot after a power cut) or may have vanished mid-run —
|
||||
// retry instead of exiting, so recording resumes automatically once
|
||||
// storage returns and the mount covers the directory again.
|
||||
if err := os.MkdirAll(outDir, 0755); err != nil {
|
||||
log.Printf("recorder: %s — storage not ready at %s: %v — retrying in 15s", cam.ID, outDir, err)
|
||||
select {
|
||||
case <-proc.done:
|
||||
return
|
||||
case <-time.After(15 * time.Second):
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Generate filename: rec_{YYYY-MM-DD-HH-MM-SS}.mp4
|
||||
now := time.Now()
|
||||
timeStr := now.Format("2006-01-02-15-04-05")
|
||||
|
|
|
|||
26
storage.go
26
storage.go
|
|
@ -174,3 +174,29 @@ func isMountPoint(path string) bool {
|
|||
}
|
||||
return cs.Dev != ps.Dev
|
||||
}
|
||||
|
||||
// waitForStorage blocks until the recordings path exists and is writable, or
|
||||
// until the timeout elapses. It is meant to run before the writers start: on a
|
||||
// slow boot (e.g. an fsck on the recordings drive after a power cut) the mount
|
||||
// can appear tens of seconds late, and without this the recorders would stumble
|
||||
// against an unmounted, root-owned mountpoint. Returns the final status; if the
|
||||
// timeout expires the caller may still proceed (live view does not need storage,
|
||||
// and the recorder retries on its own).
|
||||
func waitForStorage(path string, timeout time.Duration) StorageStatus {
|
||||
deadline := time.Now().Add(timeout)
|
||||
warned := false
|
||||
for {
|
||||
st := checkStorage(path, false)
|
||||
if st.OK {
|
||||
return st
|
||||
}
|
||||
if !warned {
|
||||
log.Printf("startup: waiting for recordings storage at %s (%s)", path, st.Message)
|
||||
warned = true
|
||||
}
|
||||
if !time.Now().Before(deadline) {
|
||||
return st
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue