lightning-terminal/migration_prompt.go
Viktor Torstensson 371d22ad8e
terminal: enable skipping migration prompt
Add a config option and env variable which allows users to skip the
migration confirmation prompt. This is useful for users who which are
running in a non-interactive environment, and does not have access to
pass input to stdin.
2026-06-08 21:11:51 +02:00

211 lines
6 KiB
Go

package terminal
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/lightninglabs/lightning-terminal/accounts"
"github.com/lightninglabs/lightning-terminal/firewalldb"
"github.com/lightninglabs/lightning-terminal/session"
)
const autoMigrateKVDBEnvVar = "LIT_AUTO_MIGRATE_TO_SQL"
var kvdbToSQLMigrationPromptLines = []string{
"",
"CAUTION: litd is about to migrate your existing data to a new SQL " +
"database.",
"After this, litd will use the SQL database for your existing data " +
"and any new data added after that point.",
"However, after the migration you will not be able to switch back " +
"to your old database, as it will be incompatible with litd " +
"after the migration.",
"NOTE: This also means that you will not be able to downgrade litd " +
"to a version prior to when SQL database support was added " +
"(v0.17.0-alpha).",
"",
"SQL databases are more performant, quicker to start, and much less " +
"prone to database corruption.",
"It is therefore strongly recommended that you proceed with this " +
"database migration.",
"",
"If you want to abort the migration and keep using the old database " +
"type instead, stop now and restart litd with the following " +
"config option set: `databasebackend=bbolt`.",
"Please note though that your old database type (bbolt) is " +
"deprecated, and support for it will be removed in a future " +
"release. Migration to SQL will at that point be mandatory.",
"",
"If your system cannot enter input here, restart litd with the " +
"config option `auto-migrate-to-sql=true` or environment " +
"variable `LIT_AUTO_MIGRATE_TO_SQL=true` set to approve the " +
"migration automatically.",
"",
}
// confirmPendingKVDBToSQLMigration blocks startup until the user explicitly
// acknowledges that litd is about to migrate legacy kvdb state to SQL and
// tombstone the kvdb files afterwards, unless auto migration is enabled.
func (c *Config) confirmPendingKVDBToSQLMigration() error {
return c.confirmPendingKVDBToSQLMigrationWithInput(
os.Stdin, os.Stderr,
)
}
// confirmPendingKVDBToSQLMigrationWithInput is the testable variant of the
// startup migration confirmation.
func (c *Config) confirmPendingKVDBToSQLMigrationWithInput(
input io.Reader, output io.Writer) error {
// The config layer resolves the prompt bypass setting so startup code
// can use the effective value directly.
if c.autoMigrateKVDBApproved {
return nil
}
hasActiveKVDB, err := hasActiveLegacyKVDB(c)
if err != nil {
return err
}
if !hasActiveKVDB {
return nil
}
return promptForKVDBToSQLMigrationConfirmation(input, output)
}
// autoMigrateKVDBFromEnv reads the local environment override that approves
// the legacy kvdb to SQL migration without showing the startup prompt.
func autoMigrateKVDBFromEnv() (bool, error) {
content := strings.TrimSpace(os.Getenv(autoMigrateKVDBEnvVar))
if content == "" {
return false, nil
}
autoMigrate, err := strconv.ParseBool(content)
if err != nil {
return false, fmt.Errorf("environment variable %s is not a "+
"valid boolean: %w", autoMigrateKVDBEnvVar, err)
}
return autoMigrate, nil
}
// hasActiveLegacyKVDB reports whether any legacy LiT kvdb file exists and was
// not already tombstoned by a previous SQL migration.
func hasActiveLegacyKVDB(cfg *Config) (bool, error) {
// The legacy accounts DB follows the macaroon directory, while the
// session and rules DBs live under the network-scoped LiT directory.
// We mirror those runtime locations here so the prompt checks the same
// files that store initialization will later open.
networkDir := filepath.Join(cfg.LitDir, cfg.Network)
accountsDir := filepath.Dir(cfg.MacaroonPath)
checks := []struct {
name string
fn func(string) (bool, error)
dir string
}{
{
name: "accounts",
fn: accounts.HasActiveKVDB,
dir: accountsDir,
},
{
name: "sessions",
fn: session.HasActiveKVDB,
dir: networkDir,
},
{
name: "rules",
fn: firewalldb.HasActiveKVDB,
dir: networkDir,
},
}
for _, check := range checks {
active, err := check.fn(check.dir)
if err != nil {
return false, fmt.Errorf("unable to inspect legacy "+
"%s kvdb: %w", check.name, err)
}
if active {
return true, nil
}
}
return false, nil
}
// promptForKVDBToSQLMigrationConfirmation requires a literal "yes" response
// before litd continues with a pending kvdb-to-SQL migration.
func promptForKVDBToSQLMigrationConfirmation(input io.Reader,
output io.Writer) error {
logKVDBToSQLMigrationPrompt()
for _, line := range kvdbToSQLMigrationPromptLines {
_, err := fmt.Fprintln(output, line)
if err != nil {
return err
}
}
_, err := fmt.Fprint(output,
"Type \"yes\" to continue with the migration. Any other "+
"answer will abort the startup of litd: ",
)
if err != nil {
return err
}
reader := bufio.NewReader(input)
answer, err := reader.ReadString('\n')
if err != nil && !errors.Is(err, io.EOF) {
return fmt.Errorf("manual confirmation required before kvdb "+
"migration can continue: %w", err)
}
if strings.TrimSpace(answer) != "yes" {
return errors.New("manual confirmation declined; refusing to " +
"continue kvdb-to-SQL migration")
}
return nil
}
// logKVDBToSQLMigrationPrompt mirrors the interactive migration warning to
// the configured logger so the full operator guidance is preserved in logs.
func logKVDBToSQLMigrationPrompt() {
for _, line := range kvdbToSQLMigrationPromptLines {
if line == "" {
continue
}
log.Infof("%s", line)
}
}
// sqlMigrationsSkipped reports whether the configured SQL backend will skip
// schema migrations during startup. In that case no kvdb-to-SQL migration is
// attempted and the startup confirmation prompt must not be shown.
func (c *Config) sqlMigrationsSkipped() bool {
switch c.DatabaseBackend {
case DatabaseBackendSqlite:
return c.Sqlite != nil && c.Sqlite.SkipMigrations
case DatabaseBackendPostgres:
return c.Postgres != nil && c.Postgres.SkipMigrations
default:
return false
}
}