diff --git a/config.go b/config.go index f8d49ee6..837eeddd 100644 --- a/config.go +++ b/config.go @@ -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 +} diff --git a/config_dev.go b/config_dev.go index ffc26122..62d83138 100644 --- a/config_dev.go +++ b/config_dev.go @@ -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 -} diff --git a/config_prod.go b/config_prod.go index 621992dc..d5cd3f5b 100644 --- a/config_prod.go +++ b/config_prod.go @@ -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 -}