Repository: benbjohnson/litestream
Pull request #485 (2023-06-09 15:20:59)

Allow DB.MetaPath to be configured
Litestream is not currently able to be used with Litefs because the fuse filesystem doesn't allow directories. This allows the database metadata path to be configured to be elsewhere.

If breaking changes to the API are okay, it would be cleaner to have `DB.MetaPath` be a struct field rather than a method.


Patch:
diff --git a/db.go b/db.go
index 79d34f3..4ef7312 100644
--- a/db.go
+++ b/db.go
@@ -44,6 +44,7 @@ const BusyTimeout = 1 * time.Second
 type DB struct {
 	mu       sync.RWMutex
 	path     string        // part to database
+	metaPath string        // Path to the database metadata.
 	db       *sql.DB       // target database
 	f        *os.File      // long-running db file descriptor
 	rtx      *sql.Tx       // long running read transaction
@@ -101,9 +102,12 @@ type DB struct {
 
 // NewDB returns a new instance of DB for a given path.
 func NewDB(path string) *DB {
+	dir, file := filepath.Split(path)
+
 	db := &DB{
-		path:   path,
-		notify: make(chan struct{}),
+		path:     path,
+		metaPath: filepath.Join(dir, "."+file+MetaDirSuffix),
+		notify:   make(chan struct{}),
 
 		MinCheckpointPageN: DefaultMinCheckpointPageN,
 		MaxCheckpointPageN: DefaultMaxCheckpointPageN,
@@ -147,20 +151,24 @@ func (db *DB) WALPath() string {
 
 // MetaPath returns the path to the database metadata.
 func (db *DB) MetaPath() string {
-	dir, file := filepath.Split(db.path)
-	return filepath.Join(dir, "."+file+MetaDirSuffix)
+	return db.metaPath
+}
+
+// SetMetaPath sets the path to database metadata.
+func (db *DB) SetMetaPath(mp string) {
+	db.metaPath = mp
 }
 
 // GenerationNamePath returns the path of the name of the current generation.
 func (db *DB) GenerationNamePath() string {
-	return filepath.Join(db.MetaPath(), "generation")
+	return filepath.Join(db.metaPath, "generation")
 }
 
 // GenerationPath returns the path of a single generation.
 // Panics if generation is blank.
 func (db *DB) GenerationPath(generation string) string {
 	assert(generation != "", "generation name required")
-	return filepath.Join(db.MetaPath(), "generations", generation)
+	return filepath.Join(db.metaPath, "generations", generation)
 }
 
 // ShadowWALDir returns the path of the shadow wal directory.
@@ -283,7 +291,7 @@ func (db *DB) Open() (err error) {
 	}
 
 	// Clear old temporary files that my have been left from a crash.
-	if err := removeTmpFiles(db.MetaPath()); err != nil {
+	if err := removeTmpFiles(db.metaPath); err != nil {
 		return fmt.Errorf("cannot remove tmp files: %w", err)
 	}
 
@@ -446,7 +454,7 @@ func (db *DB) init() (err error) {
 	}
 
 	// Ensure meta directory structure exists.
-	if err := internal.MkdirAll(db.MetaPath(), db.dirInfo); err != nil {
+	if err := internal.MkdirAll(db.metaPath, db.dirInfo); err != nil {
 		return err
 	}
 
@@ -522,7 +530,7 @@ func (db *DB) cleanGenerations() error {
 		return err
 	}
 
-	dir := filepath.Join(db.MetaPath(), "generations")
+	dir := filepath.Join(db.metaPath, "generations")
 	fis, err := ioutil.ReadDir(dir)
 	if os.IsNotExist(err) {
 		return nil
@@ -652,7 +660,7 @@ func (db *DB) createGeneration() (string, error) {
 	generation := hex.EncodeToString(buf)
 
 	// Generate new directory.
-	dir := filepath.Join(db.MetaPath(), "generations", generation)
+	dir := filepath.Join(db.metaPath, "generations", generation)
 	if err := internal.MkdirAll(dir, db.dirInfo); err != nil {
 		return "", err
 	}


Test patch:
diff --git a/cmd/litestream/main.go b/cmd/litestream/main.go
index 773cd41..3bb20c1 100644
--- a/cmd/litestream/main.go
+++ b/cmd/litestream/main.go
@@ -247,6 +247,7 @@ func ReadConfigFile(filename string, expandEnv bool) (_ Config, err error) {
 // DBConfig represents the configuration for a single database.
 type DBConfig struct {
 	Path               string         `yaml:"path"`
+	MetaPath           *string        `yaml:"meta-path"`
 	MonitorInterval    *time.Duration `yaml:"monitor-interval"`
 	CheckpointInterval *time.Duration `yaml:"checkpoint-interval"`
 	MinCheckpointPageN *int           `yaml:"min-checkpoint-page-count"`
@@ -266,6 +267,9 @@ func NewDBFromConfig(dbc *DBConfig) (*litestream.DB, error) {
 	db := litestream.NewDB(path)
 
 	// Override default database settings if specified in configuration.
+	if dbc.MetaPath != nil {
+		db.SetMetaPath(*dbc.MetaPath)
+	}
 	if dbc.MonitorInterval != nil {
 		db.MonitorInterval = *dbc.MonitorInterval
 	}