bluesky viewer in the terminal
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}