From cfc9e464d983b051e7ed4da1620fae61dc73cff2 Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Sat, 29 Nov 2025 21:14:23 +0100 Subject: [PATCH] fix: automatically create parent directory of Sqlite db --- backend/internal/bootstrap/db_bootstrap.go | 27 +++++++++++++++++++ .../internal/bootstrap/db_bootstrap_test.go | 25 +++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/backend/internal/bootstrap/db_bootstrap.go b/backend/internal/bootstrap/db_bootstrap.go index 7f020b07..0f756941 100644 --- a/backend/internal/bootstrap/db_bootstrap.go +++ b/backend/internal/bootstrap/db_bootstrap.go @@ -155,6 +155,12 @@ func connectDatabase() (db *gorm.DB, err error) { return nil, err } + if !isMemoryDB { + if err := ensureSqliteDatabaseDir(dbPath); err != nil { + return nil, err + } + } + // Before we connect, also make sure that there's a temporary folder for SQLite to write its data err = ensureSqliteTempDir(filepath.Dir(dbPath)) if err != nil { @@ -388,6 +394,27 @@ func isSqliteInMemory(connString string) bool { return len(qs["mode"]) > 0 && qs["mode"][0] == "memory" } +// ensureSqliteDatabaseDir creates the parent directory for the SQLite database file if it doesn't exist yet +func ensureSqliteDatabaseDir(dbPath string) error { + dir := filepath.Dir(dbPath) + + info, err := os.Stat(dir) + switch { + case err == nil: + if !info.IsDir() { + return fmt.Errorf("SQLite database directory '%s' is not a directory", dir) + } + return nil + case os.IsNotExist(err): + if err := os.MkdirAll(dir, 0700); err != nil { + return fmt.Errorf("failed to create SQLite database directory '%s': %w", dir, err) + } + return nil + default: + return fmt.Errorf("failed to check SQLite database directory '%s': %w", dir, err) + } +} + // ensureSqliteTempDir ensures that SQLite has a directory where it can write temporary files if needed // The default directory may not be writable when using a container with a read-only root file system // See: https://www.sqlite.org/tempfiles.html diff --git a/backend/internal/bootstrap/db_bootstrap_test.go b/backend/internal/bootstrap/db_bootstrap_test.go index 55eae92c..81fc0e3f 100644 --- a/backend/internal/bootstrap/db_bootstrap_test.go +++ b/backend/internal/bootstrap/db_bootstrap_test.go @@ -2,6 +2,8 @@ package bootstrap import ( "net/url" + "os" + "path/filepath" "testing" "github.com/stretchr/testify/assert" @@ -84,6 +86,29 @@ func TestIsSqliteInMemory(t *testing.T) { } } +func TestEnsureSqliteDatabaseDir(t *testing.T) { + t.Run("creates missing directory", func(t *testing.T) { + tempDir := t.TempDir() + dbPath := filepath.Join(tempDir, "nested", "pocket-id.db") + + err := ensureSqliteDatabaseDir(dbPath) + require.NoError(t, err) + + info, err := os.Stat(filepath.Dir(dbPath)) + require.NoError(t, err) + assert.True(t, info.IsDir()) + }) + + t.Run("fails when parent is file", func(t *testing.T) { + tempDir := t.TempDir() + filePath := filepath.Join(tempDir, "file.txt") + require.NoError(t, os.WriteFile(filePath, []byte("test"), 0o600)) + + err := ensureSqliteDatabaseDir(filepath.Join(filePath, "data.db")) + require.Error(t, err) + }) +} + func TestConvertSqlitePragmaArgs(t *testing.T) { tests := []struct { name string