package database import ( "context" "errors" "fmt" "os" "path/filepath" "go.uber.org/fx" "sneak.berlin/go/vaultik/internal/config" "sneak.berlin/go/vaultik/internal/log" ) // indexDirPerm restricts the local index directory to the owning user; // the index describes the backed-up file tree and must stay private. const indexDirPerm = 0o700 // indexFilePerm restricts the index file to the owning user; it lists every // backed-up path and chunk hash and must stay private. const indexFilePerm = 0o600 // ensureIndexFileMode makes the index file owner-only before the SQLite // driver opens it: it creates the file 0600 if absent, or chmods an existing // one to 0600. Doing this first matters because SQLite creates its -wal and // -shm side files with the mode of the main database file, so a private main // file yields private side files. The driver treats a zero-byte file as an // empty database, so pre-creating it here is safe. func ensureIndexFileMode(path string) error { info, err := os.Stat(path) switch { case err == nil: if info.Mode().Perm() == indexFilePerm { return nil } err = os.Chmod(path, indexFilePerm) if err != nil { return fmt.Errorf("restricting index file permissions: %w", err) } return nil case errors.Is(err, os.ErrNotExist): //nolint:gosec // G304: the index path is operator-configured by design f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, indexFilePerm) if err != nil { return fmt.Errorf("creating index file: %w", err) } return f.Close() default: return fmt.Errorf("checking index file: %w", err) } } // Module provides database dependencies // //nolint:gochecknoglobals // fx module definitions are package globals by convention var Module = fx.Module("database", fx.Provide( provideDatabase, NewRepositories, ), ) func provideDatabase(lc fx.Lifecycle, cfg *config.Config) (*DB, error) { // Ensure the index directory exists indexDir := filepath.Dir(cfg.IndexPath) err := os.MkdirAll(indexDir, indexDirPerm) if err != nil { return nil, fmt.Errorf("creating index directory: %w", err) } err = ensureIndexFileMode(cfg.IndexPath) if err != nil { return nil, err } db, err := New(context.Background(), cfg.IndexPath) if err != nil { return nil, fmt.Errorf("opening database: %w", err) } lc.Append(fx.Hook{ OnStop: func(_ context.Context) error { log.Debug("Database module OnStop hook called") err := db.Close() if err != nil { log.Error("Failed to close database in OnStop hook", "error", err) return err } log.Debug("Database closed successfully in OnStop hook") return nil }, }) return db, nil }