* fix(db): fail restore when the backup file does not exist instead of wiping the database
`navidrome backup restore -b <file>` passed the flag value straight to the
SQLite driver, which opens databases with SQLITE_OPEN_CREATE by default. If
the file was not found (for example a file name relative to the working
directory instead of the backup directory), the driver silently created an
empty database and the backup API copied that emptiness over the live
database, reporting 'Restore complete' with an empty instance afterwards.
Two changes:
- db.Restore now opens the backup file read-only, so a missing file is an
error and nothing gets created or overwritten.
- A relative --backup-file is resolved against Backup.Path, the same folder
'backup create' writes to; absolute paths keep working as before.
Fixes#6083
* fix(db): stat the backup file instead of opening it read-only
The read-only DSN added in the previous commit works for the reported case but
breaks on other paths: 'file:' + path is parsed as a URI, so a '#' truncates the
path and a '%' sequence is percent-decoded, and a read-only open of a WAL
database leaves '-shm'/'-wal' sidecars next to the backup. Those sidecars then
matched the unanchored prune regex, so 'backup prune -k 3' right after a restore
deleted real backups and kept one.
Stat the file before opening it and keep passing the plain path to the driver.
Paths containing '?' are rejected, since go-sqlite3 splits the DSN there and
would otherwise open (and create) a different file. The prune regex is anchored
so sidecars are never counted as backups.
Also fixes the restore/backup/prune error logs, which printed BasePath (the web
URL prefix) instead of the backup location.
---------
Co-authored-by: Deluan <deluan@navidrome.org>
* feat(conf): add Dir type with lazy directory creation
Introduces the Dir type that wraps a directory path string and defers
os.MkdirAll until the first call to Path() or MustPath(), using sync.Once
to ensure the creation happens exactly once. Implements fmt.Stringer,
encoding.TextMarshaler, and encoding.TextUnmarshaler for config integration.
Includes Ginkgo/Gomega tests covering all methods and error paths.
* refactor(conf): replace eager dir creation with lazy Dir type
Change DataFolder, CacheFolder, Plugins.Folder, and Backup.Path from
string to Dir. Remove all os.MkdirAll calls from Load() so directories
are created lazily on first Path()/MustPath() call. Artwork folder
creation was already handled at point-of-use in image_upload.go.
Add SnapshotConfig() to conf package for safe test config save/restore
that avoids copying sync.Once inside Dir fields. Fix copy-lock vet
warning in nativeapi/config.go by marshalling pointer instead of value.
* refactor(conf): migrate tests and db init to lazy Dir type
Update all test files to use conf.NewDir() for Dir field assignments.
Ensure DataFolder is created lazily when the database is first opened
in db.Db(). Remove eager directory creation from conf.Load() tests.
* fix(conf): address review findings for Dir type
- Use os.ModePerm for DataFolder/CacheFolder (was 0700, should match
original behavior). Add NewDirWithPerm for PluginsFolder (0700).
- Use Path() instead of MustPath() in db.Prune() to avoid logFatal
from background cron job.
- Panic on marshal/unmarshal errors in SnapshotConfig (test helper).
- Clean up redundant String()/MustPath() calls in plugin manager.
- Remove dead code in dir_test.go.
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(conf): add GoString to Dir for clean config dump output
Implement fmt.GoStringer on Dir so pretty.Sprintf shows the path
string instead of internal struct fields (sync.Once, perm, err).
Also add TODO comment to configtest about removing the indirection.
* fix(dir): improve error logging in MustPath method
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor(tests): remove redundant tests for unwritable DataFolder and CacheFolder
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(conf): address PR review feedback
- Ensure Plugins.Folder always uses 0700, even when user-configured
(previously only the derived default got restrictive permissions).
- Create LogFile parent directory before opening, so LogFile paths
inside a not-yet-created DataFolder work correctly.
---------
Signed-off-by: Deluan <deluan@navidrome.org>
The error-check ordering after backupOp.Step(-1) checked !done before
err, which masked the underlying SQLite error (e.g. SQLITE_BUSY, I/O
errors) with a generic "backup not done with step -1" message. On
failure, Step returns done=false together with a non-nil err, so the
!done branch short-circuited before the real error was ever reported.
Swap the checks so the SQLite error is returned first, making failing
backups actually diagnosable.
Refs https://github.com/navidrome/navidrome/issues/5305#issuecomment-4230470593
* fix(server): backup not working from cli
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(server): make backup-file required for restore
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* [enhancement]: Provide native backup/restore mechanism
- db.go: add Backup/Restore functions that utilize Sqlite's built-in online backup mechanism
- support automatic backup with schedule, limit number of files
- provide commands to manually backup/restore Navidrome
Notes:
`Step(-1)` results in a read-only lock being held for the entire duration of the backup.
This will block out any other write operation (and may hold additional locks.
An alternate implementation that doesn't block but instead retries is available at https://www.sqlite.org/backup.html#:~:text=of%20a%20Running-,database,-%2F*%0A**%20Perform%20an%20online (easily adaptable to go), but has the potential problem of continually getting restarted by background writes.
Additionally, the restore should still only be called when Navidrome is offline, as the restore process does not run migrations that are missing.
* remove empty line
* add more granular backup schedule
* do not remove files when bypass is set
* move prune
* refactor: small nitpicks
* change commands and flags
* tests, return path from backup
* refactor: small nitpicks
---------
Co-authored-by: Deluan <deluan@navidrome.org>