bluesky viewer in the terminal
at main 106 lines 2.9 kB view raw
1package setup 2 3import ( 4 "context" 5 "database/sql" 6 "fmt" 7 "os" 8 9 _ "github.com/mattn/go-sqlite3" 10 "github.com/stormlightlabs/skypanel/cli/internal/config" 11 "github.com/stormlightlabs/skypanel/cli/internal/store" 12) 13 14// EnsurePersistenceReady validates the persistence layer from package [store] is ready for use. 15// On first run, automatically creates directories, database, and runs migrations; on subsequent runs, performs fast validation checks only. 16func EnsurePersistenceReady(ctx context.Context) error { 17 configDir, err := config.GetConfigDir() 18 if err != nil { 19 return fmt.Errorf("failed to get config directory: %w", err) 20 } 21 22 dbPath, err := config.GetCacheDB() 23 if err != nil { 24 return fmt.Errorf("failed to get database path: %w", err) 25 } 26 27 dirInfo, err := os.Stat(configDir) 28 if os.IsNotExist(err) { 29 if err := initializeFirstRun(ctx, configDir, dbPath); err != nil { 30 return fmt.Errorf("first-run initialization failed: %w", err) 31 } 32 return nil 33 } 34 if err != nil { 35 return fmt.Errorf("failed to check config directory: %w", err) 36 } 37 if !dirInfo.IsDir() { 38 return fmt.Errorf("config path exists but is not a directory: %s", configDir) 39 } 40 41 if _, err := os.Stat(dbPath); os.IsNotExist(err) { 42 if err := initializeDatabase(ctx, dbPath); err != nil { 43 return fmt.Errorf("database initialization failed: %w", err) 44 } 45 return nil 46 } 47 48 if err := validateMigrations(dbPath); err != nil { 49 return fmt.Errorf("migration validation failed: %w", err) 50 } 51 52 return nil 53} 54 55// initializeFirstRun creates config directory and initializes database for first-time use 56func initializeFirstRun(ctx context.Context, configDir, dbPath string) error { 57 if err := os.MkdirAll(configDir, 0700); err != nil { 58 return fmt.Errorf("failed to create config directory: %w", err) 59 } 60 61 if err := initializeDatabase(ctx, dbPath); err != nil { 62 return fmt.Errorf("failed to initialize database: %w", err) 63 } 64 65 return nil 66} 67 68// initializeDatabase creates database file and runs all migrations 69func initializeDatabase(_ context.Context, dbPath string) error { 70 db, err := sql.Open("sqlite3", dbPath) 71 if err != nil { 72 return fmt.Errorf("failed to create database: %w", err) 73 } 74 defer db.Close() 75 76 if err := store.RunMigrations(db); err != nil { 77 return fmt.Errorf("failed to run migrations: %w", err) 78 } 79 80 return nil 81} 82 83// validateMigrations performs a fast check that all migrations are applied 84func validateMigrations(dbPath string) error { 85 db, err := sql.Open("sqlite3", dbPath) 86 if err != nil { 87 return fmt.Errorf("failed to open database: %w", err) 88 } 89 defer db.Close() 90 91 status, err := store.GetMigrationStatus(db) 92 if err != nil { 93 return fmt.Errorf("failed to check migration status: %w", err) 94 } 95 96 if !status.IsUpToDate { 97 return fmt.Errorf( 98 "database has %d pending migrations (current: v%d, latest: v%d). Run 'skycli setup' to update", 99 status.PendingCount, 100 status.CurrentVersion, 101 status.LatestVersion, 102 ) 103 } 104 105 return nil 106}