terminal: move databasebackend option to Config

Move the database backend selection from the `dev` config file into the
main `Config` struct, i.e. into production.

This will enable an sql database backend to be used in production.
This commit is contained in:
Viktor Torstensson 2026-05-12 01:18:45 +02:00
parent 10949c7bb8
commit 66e9d3c46d
No known key found for this signature in database
GPG key ID: 961CC8259AE675D4
3 changed files with 215 additions and 279 deletions

220
config.go
View file

@ -1,6 +1,7 @@
package terminal
import (
"context"
"crypto/tls"
"errors"
"fmt"
@ -19,8 +20,13 @@ import (
"github.com/lightninglabs/faraday/frdrpcserver"
"github.com/lightninglabs/lightning-terminal/accounts"
"github.com/lightninglabs/lightning-terminal/autopilotserver"
"github.com/lightninglabs/lightning-terminal/db"
"github.com/lightninglabs/lightning-terminal/db/migsets"
"github.com/lightninglabs/lightning-terminal/db/sqlc"
"github.com/lightninglabs/lightning-terminal/firewall"
"github.com/lightninglabs/lightning-terminal/firewalldb"
mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware"
"github.com/lightninglabs/lightning-terminal/session"
"github.com/lightninglabs/lightning-terminal/subservers"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopd"
@ -29,10 +35,12 @@ import (
"github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/cert"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/signal"
"github.com/lightningnetwork/lnd/sqldb/v2"
"github.com/mwitkow/go-conntrack/connhelpers"
"golang.org/x/crypto/acme/autocert"
)
@ -87,6 +95,19 @@ const (
DefaultMacaroonFilename = "lit.macaroon"
defaultFirstLNCConnTimeout = 10 * time.Minute
// DatabaseBackendSqlite is the name of the SQLite database backend.
DatabaseBackendSqlite = "sqlite"
// DatabaseBackendPostgres is the name of the Postgres database backend.
DatabaseBackendPostgres = "postgres"
// DatabaseBackendBbolt is the name of the bbolt database backend.
DatabaseBackendBbolt = "bbolt"
// defaultSqliteDatabaseFileName is the default name of the SQLite
// database file.
defaultSqliteDatabaseFileName = "litd.db"
)
var (
@ -140,6 +161,12 @@ var (
DefaultMacaroonPath = filepath.Join(
DefaultLitDir, DefaultNetwork, DefaultMacaroonFilename,
)
// defaultSqliteDatabasePath is the default path under which we store
// the SQLite database file.
defaultSqliteDatabasePath = filepath.Join(
DefaultLitDir, DefaultNetwork, defaultSqliteDatabaseFileName,
)
)
// Config is the main configuration struct of lightning-terminal. It contains
@ -176,6 +203,18 @@ type Config struct {
FirstLNCConnDeadline time.Duration `long:"firstlncconndeadline" description:"The duration after a new LNC session will be revoked if no connection is made with it. This only applies for the first connection which is made using the pairing phrase. "`
// DatabaseBackend is the database backend we will use for storing all
// account related data.
DatabaseBackend string `long:"databasebackend" description:"The database backend to use for storing all account related data." choice:"bbolt" choice:"sqlite" choice:"postgres"`
// Sqlite holds the configuration options for a SQLite database
// backend.
Sqlite *db.SqliteConfig `group:"sqlite" namespace:"sqlite"`
// Postgres holds the configuration options for a Postgres database
// backend.
Postgres *db.PostgresConfig `group:"postgres" namespace:"postgres"`
// Network is the Bitcoin network we're running on. This will be parsed
// before the configuration is loaded and will set the correct flag on
// `lnd.bitcoin.mainnet|testnet|testnet4|regtest|signet` and also for
@ -322,15 +361,24 @@ func defaultConfig() *Config {
TLSCertPath: tapDefaultConfig.RpcConf.TLSCertPath,
},
},
Network: DefaultNetwork,
LndMode: DefaultLndMode,
Lnd: &lndDefaultConfig,
LndRPCTimeout: defaultRPCTimeout,
LndConnectInterval: defaultStartupTimeout,
LitDir: DefaultLitDir,
LetsEncryptListen: defaultLetsEncryptListen,
LetsEncryptDir: defaultLetsEncryptDir,
MacaroonPath: DefaultMacaroonPath,
Network: DefaultNetwork,
LndMode: DefaultLndMode,
Lnd: &lndDefaultConfig,
LndRPCTimeout: defaultRPCTimeout,
LndConnectInterval: defaultStartupTimeout,
LitDir: DefaultLitDir,
LetsEncryptListen: defaultLetsEncryptListen,
LetsEncryptDir: defaultLetsEncryptDir,
MacaroonPath: DefaultMacaroonPath,
DatabaseBackend: DatabaseBackendBbolt,
Sqlite: &db.SqliteConfig{
DatabaseFileName: defaultSqliteDatabasePath,
},
Postgres: &db.PostgresConfig{
Host: "localhost",
Port: 5432,
MaxOpenConnections: 10,
},
ConfigFile: defaultConfigFile,
FaradayMode: defaultFaradayMode,
Faraday: &faradayDefaultConfig,
@ -493,6 +541,16 @@ func loadAndValidateConfig(interceptor signal.Interceptor) (*Config, error) {
)
}
// If the cfg.Sqlite.DatabaseFileName was not set, rebuild it from the
// final LiT directory and network. This keeps the default aligned
// with any user overrides that changed lit-dir or network during
// config loading.
if cfg.Sqlite.DatabaseFileName == defaultSqliteDatabasePath {
cfg.Sqlite.DatabaseFileName = filepath.Join(
litDir, cfg.Network, defaultSqliteDatabaseFileName,
)
}
err = cfg.DevConfig.Validate(litDir, cfg.Network)
if err != nil {
return nil, err
@ -1055,3 +1113,147 @@ func parseNetwork(addr net.Addr) string {
return addr.Network()
}
}
// NewStores creates a new stores instance based on the chosen database backend.
func NewStores(ctx context.Context, cfg *Config,
basicClient lnrpc.LightningClient, clock clock.Clock) (*stores, error) {
var (
networkDir = filepath.Join(cfg.LitDir, cfg.Network)
stores = &stores{
closeFns: make(map[string]func() error),
}
)
switch cfg.DatabaseBackend {
case DatabaseBackendSqlite:
// Before we initialize the SQLite store, we'll make sure that
// the directory where we will store the database file exists.
err := makeDirectories(networkDir)
if err != nil {
return stores, err
}
sqlStore, err := sqldb.NewSqliteStore(&sqldb.SqliteConfig{
SkipMigrations: cfg.Sqlite.SkipMigrations,
SkipMigrationDbBackup: cfg.Sqlite.SkipMigrationDbBackup,
}, cfg.Sqlite.DatabaseFileName)
if err != nil {
return stores, err
}
if !cfg.Sqlite.SkipMigrations {
err = sqldb.ApplyAllMigrations(
sqlStore,
migsets.MakeMigrationSets(
ctx, basicClient, cfg.MacaroonPath,
clock,
),
)
if err != nil {
return stores, fmt.Errorf("error applying "+
"migrations to SQLite store: %w", err,
)
}
}
queries := sqlc.NewForType(sqlStore, sqlStore.BackendType)
acctStore := accounts.NewSQLStore(
sqlStore.BaseDB, queries, clock,
)
sessStore := session.NewSQLStore(
sqlStore.BaseDB, queries, clock,
)
firewallStore := firewalldb.NewSQLDB(
sqlStore.BaseDB, queries, clock,
)
stores.accounts = acctStore
stores.sessions = sessStore
stores.firewall = firewalldb.NewDB(firewallStore)
stores.closeFns["sqlite"] = sqlStore.BaseDB.Close
case DatabaseBackendPostgres:
sqlStore, err := sqldb.NewPostgresStore(&sqldb.PostgresConfig{
Dsn: cfg.Postgres.DSN(false),
MaxOpenConnections: cfg.Postgres.MaxOpenConnections,
MaxIdleConnections: cfg.Postgres.MaxIdleConnections,
ConnMaxLifetime: cfg.Postgres.ConnMaxLifetime,
ConnMaxIdleTime: cfg.Postgres.ConnMaxIdleTime,
RequireSSL: cfg.Postgres.RequireSSL,
SkipMigrations: cfg.Postgres.SkipMigrations,
})
if err != nil {
return stores, err
}
if !cfg.Postgres.SkipMigrations {
err = sqldb.ApplyAllMigrations(
sqlStore,
migsets.MakeMigrationSets(
ctx, basicClient, cfg.MacaroonPath,
clock,
),
)
if err != nil {
return stores, fmt.Errorf("error applying "+
"migrations to Postgres store: %w", err,
)
}
}
queries := sqlc.NewForType(sqlStore, sqlStore.BackendType)
acctStore := accounts.NewSQLStore(
sqlStore.BaseDB, queries, clock,
)
sessStore := session.NewSQLStore(
sqlStore.BaseDB, queries, clock,
)
firewallStore := firewalldb.NewSQLDB(
sqlStore.BaseDB, queries, clock,
)
stores.accounts = acctStore
stores.sessions = sessStore
stores.firewall = firewalldb.NewDB(firewallStore)
stores.closeFns["postgres"] = sqlStore.BaseDB.Close
default:
accountStore, err := accounts.NewBoltStore(
filepath.Dir(cfg.MacaroonPath), accounts.DBFilename,
clock,
)
if err != nil {
return stores, err
}
stores.accounts = accountStore
stores.closeFns["bbolt-accounts"] = accountStore.Close
sessionStore, err := session.NewDB(
networkDir, session.DBFilename, clock, accountStore,
)
if err != nil {
return stores, err
}
stores.sessions = sessionStore
stores.closeFns["bbolt-sessions"] = sessionStore.Close
firewallBoltDB, err := firewalldb.NewBoltDB(
networkDir, firewalldb.DBFilename, stores.sessions,
stores.accounts, clock,
)
if err != nil {
return stores, fmt.Errorf("error creating firewall "+
"BoltDB: %v", err)
}
stores.firewall = firewalldb.NewDB(firewallBoltDB)
stores.closeFns["bbolt-firewalldb"] = firewallBoltDB.Close
}
return stores, nil
}

View file

@ -2,232 +2,21 @@
package terminal
import (
"context"
"fmt"
"path/filepath"
"github.com/lightninglabs/lightning-terminal/accounts"
"github.com/lightninglabs/lightning-terminal/db"
"github.com/lightninglabs/lightning-terminal/db/migsets"
"github.com/lightninglabs/lightning-terminal/db/sqlc"
"github.com/lightninglabs/lightning-terminal/firewalldb"
"github.com/lightninglabs/lightning-terminal/session"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/sqldb/v2"
)
const (
// DatabaseBackendSqlite is the name of the SQLite database backend.
DatabaseBackendSqlite = "sqlite"
// DatabaseBackendPostgres is the name of the Postgres database backend.
DatabaseBackendPostgres = "postgres"
// DatabaseBackendBbolt is the name of the bbolt database backend.
DatabaseBackendBbolt = "bbolt"
// defaultSqliteDatabaseFileName is the default name of the SQLite
// database file.
defaultSqliteDatabaseFileName = "litd.db"
)
// defaultSqliteDatabasePath is the default path under which we store
// the SQLite database file.
var defaultSqliteDatabasePath = filepath.Join(
DefaultLitDir, DefaultNetwork, defaultSqliteDatabaseFileName,
)
// DevConfig is a struct that holds the configuration options for a development
// environment. The purpose of this struct is to hold config options for
// features not yet available in production. Since our itests are built with
// the dev tag, we can test these features in our itests.
//
// nolint:ll
type DevConfig struct {
// DatabaseBackend is the database backend we will use for storing all
// account related data. While this feature is still in development, we
// include the bbolt type here so that our itests can continue to be
// tested against a bbolt backend. Once the full bbolt to SQL migration
// is complete, however, we will remove the bbolt option.
DatabaseBackend string `long:"databasebackend" description:"The database backend to use for storing all account related data." choice:"bbolt" choice:"sqlite" choice:"postgres"`
type DevConfig struct{}
// Sqlite holds the configuration options for a SQLite database
// backend.
Sqlite *db.SqliteConfig `group:"sqlite" namespace:"sqlite"`
// Postgres holds the configuration options for a Postgres database
Postgres *db.PostgresConfig `group:"postgres" namespace:"postgres"`
// defaultDevConfig returns a new DevConfig with default values set.
func defaultDevConfig() *DevConfig {
return &DevConfig{}
}
// Validate checks that all the values set in our DevConfig are valid and uses
// the passed parameters to override any defaults if necessary.
func (c *DevConfig) Validate(dbDir, network string) error {
// We'll update the database file location if it wasn't set.
if c.Sqlite.DatabaseFileName == defaultSqliteDatabasePath {
c.Sqlite.DatabaseFileName = filepath.Join(
dbDir, network, defaultSqliteDatabaseFileName,
)
}
return nil
}
// defaultDevConfig returns a new DevConfig with default values set.
func defaultDevConfig() *DevConfig {
return &DevConfig{
Sqlite: &db.SqliteConfig{
DatabaseFileName: defaultSqliteDatabasePath,
},
Postgres: &db.PostgresConfig{
Host: "localhost",
Port: 5432,
MaxOpenConnections: 10,
},
}
}
// NewStores creates a new stores instance based on the chosen database backend.
func NewStores(ctx context.Context, cfg *Config,
basicClient lnrpc.LightningClient, clock clock.Clock) (*stores, error) {
var (
networkDir = filepath.Join(cfg.LitDir, cfg.Network)
stores = &stores{
closeFns: make(map[string]func() error),
}
)
switch cfg.DatabaseBackend {
case DatabaseBackendSqlite:
// Before we initialize the SQLite store, we'll make sure that
// the directory where we will store the database file exists.
err := makeDirectories(networkDir)
if err != nil {
return stores, err
}
sqlStore, err := sqldb.NewSqliteStore(&sqldb.SqliteConfig{
SkipMigrations: cfg.Sqlite.SkipMigrations,
SkipMigrationDbBackup: cfg.Sqlite.SkipMigrationDbBackup,
}, cfg.Sqlite.DatabaseFileName)
if err != nil {
return stores, err
}
if !cfg.Sqlite.SkipMigrations {
err = sqldb.ApplyAllMigrations(
sqlStore,
migsets.MakeMigrationSets(
ctx, basicClient, cfg.MacaroonPath,
clock,
),
)
if err != nil {
return stores, fmt.Errorf("error applying "+
"migrations to SQLite store: %w", err,
)
}
}
queries := sqlc.NewForType(sqlStore, sqlStore.BackendType)
acctStore := accounts.NewSQLStore(
sqlStore.BaseDB, queries, clock,
)
sessStore := session.NewSQLStore(
sqlStore.BaseDB, queries, clock,
)
firewallStore := firewalldb.NewSQLDB(
sqlStore.BaseDB, queries, clock,
)
stores.accounts = acctStore
stores.sessions = sessStore
stores.firewall = firewalldb.NewDB(firewallStore)
stores.closeFns["sqlite"] = sqlStore.BaseDB.Close
case DatabaseBackendPostgres:
sqlStore, err := sqldb.NewPostgresStore(&sqldb.PostgresConfig{
Dsn: cfg.Postgres.DSN(false),
MaxOpenConnections: cfg.Postgres.MaxOpenConnections,
MaxIdleConnections: cfg.Postgres.MaxIdleConnections,
ConnMaxLifetime: cfg.Postgres.ConnMaxLifetime,
ConnMaxIdleTime: cfg.Postgres.ConnMaxIdleTime,
RequireSSL: cfg.Postgres.RequireSSL,
SkipMigrations: cfg.Postgres.SkipMigrations,
})
if err != nil {
return stores, err
}
if !cfg.Postgres.SkipMigrations {
err = sqldb.ApplyAllMigrations(
sqlStore,
migsets.MakeMigrationSets(
ctx, basicClient, cfg.MacaroonPath,
clock,
),
)
if err != nil {
return stores, fmt.Errorf("error applying "+
"migrations to Postgres store: %w", err,
)
}
}
queries := sqlc.NewForType(sqlStore, sqlStore.BackendType)
acctStore := accounts.NewSQLStore(
sqlStore.BaseDB, queries, clock,
)
sessStore := session.NewSQLStore(
sqlStore.BaseDB, queries, clock,
)
firewallStore := firewalldb.NewSQLDB(
sqlStore.BaseDB, queries, clock,
)
stores.accounts = acctStore
stores.sessions = sessStore
stores.firewall = firewalldb.NewDB(firewallStore)
stores.closeFns["postgres"] = sqlStore.BaseDB.Close
default:
accountStore, err := accounts.NewBoltStore(
filepath.Dir(cfg.MacaroonPath), accounts.DBFilename,
clock,
)
if err != nil {
return stores, err
}
stores.accounts = accountStore
stores.closeFns["bbolt-accounts"] = accountStore.Close
sessionStore, err := session.NewDB(
networkDir, session.DBFilename, clock, accountStore,
)
if err != nil {
return stores, err
}
stores.sessions = sessionStore
stores.closeFns["bbolt-sessions"] = sessionStore.Close
firewallBoltDB, err := firewalldb.NewBoltDB(
networkDir, firewalldb.DBFilename, stores.sessions,
stores.accounts, clock,
)
if err != nil {
return stores, fmt.Errorf("error creating firewall "+
"BoltDB: %v", err)
}
stores.firewall = firewalldb.NewDB(firewallBoltDB)
stores.closeFns["bbolt-firewalldb"] = firewallBoltDB.Close
}
return stores, nil
}

View file

@ -2,18 +2,6 @@
package terminal
import (
"context"
"fmt"
"path/filepath"
"github.com/lightninglabs/lightning-terminal/accounts"
"github.com/lightninglabs/lightning-terminal/firewalldb"
"github.com/lightninglabs/lightning-terminal/session"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/lnrpc"
)
// DevConfig is an empty shell struct that allows us to build without the dev
// tag. This struct is embedded in the main Config struct, and it adds no new
// functionality in a production build.
@ -28,46 +16,3 @@ func defaultDevConfig() *DevConfig {
func (c *DevConfig) Validate(_, _ string) error {
return nil
}
// NewStores creates a new instance of the stores struct using the default Bolt
// backend since in production, this is currently the only backend supported.
func NewStores(_ context.Context, cfg *Config,
_ lnrpc.LightningClient, clock clock.Clock) (*stores, error) {
networkDir := filepath.Join(cfg.LitDir, cfg.Network)
stores := &stores{
closeFns: make(map[string]func() error),
}
acctStore, err := accounts.NewBoltStore(
filepath.Dir(cfg.MacaroonPath), accounts.DBFilename, clock,
)
if err != nil {
return stores, err
}
stores.accounts = acctStore
stores.closeFns["accounts"] = acctStore.Close
sessStore, err := session.NewDB(
networkDir, session.DBFilename, clock, acctStore,
)
if err != nil {
return stores, fmt.Errorf("error creating session BoltStore: "+
"%v", err)
}
stores.sessions = sessStore
stores.closeFns["sessions"] = sessStore.Close
firewallDB, err := firewalldb.NewBoltDB(
networkDir, firewalldb.DBFilename, stores.sessions,
stores.accounts, clock,
)
if err != nil {
return stores, fmt.Errorf("error creating firewall DB: %v", err)
}
stores.firewall = firewalldb.NewDB(firewallDB)
stores.closeFns["firewall"] = firewallDB.Close
return stores, nil
}