This repository has no description
1package spindle
2
3import (
4 "context"
5 "database/sql"
6 "errors"
7 "fmt"
8 "log/slog"
9 "os"
10
11 _ "github.com/mattn/go-sqlite3"
12 "tangled.org/core/spindle/db"
13)
14
15const forceTapResyncFlag = "force-tap-repo-resync-v1"
16
17func runStartupMigrations(ctx context.Context, d *db.DB, tapEmbed bool, tapDBPath string, logger *slog.Logger) error {
18 if !tapEmbed {
19 logger.Warn("tap not embedded: legacy repos won't auto-resync; trigger external tap resync to migrate secrets/casbin")
20 return nil
21 }
22 if err := nudgeTapForResync(ctx, d, tapDBPath, logger); err != nil {
23 return fmt.Errorf("nudge tap for resync: %w", err)
24 }
25 return nil
26}
27
28func nudgeTapForResync(ctx context.Context, d *db.DB, tapDBPath string, logger *slog.Logger) error {
29 if tapDBPath == "" {
30 return fmt.Errorf("tap db path empty in embed mode")
31 }
32 var exists bool
33 if err := d.QueryRowContext(ctx,
34 `select exists (select 1 from migrations where name = ?)`,
35 forceTapResyncFlag,
36 ).Scan(&exists); err != nil {
37 return fmt.Errorf("check %s flag: %w", forceTapResyncFlag, err)
38 }
39 if exists {
40 logger.Warn("skipped migration, already applied", "migration", forceTapResyncFlag)
41 return nil
42 }
43
44 markDone := func() error {
45 if _, err := d.ExecContext(ctx,
46 `insert or ignore into migrations (name) values (?)`,
47 forceTapResyncFlag,
48 ); err != nil {
49 return fmt.Errorf("mark %s done: %w", forceTapResyncFlag, err)
50 }
51 return nil
52 }
53
54 if _, err := os.Stat(tapDBPath); errors.Is(err, os.ErrNotExist) {
55 logger.Info("tap db not yet created, marking resync nudge done", "migration", forceTapResyncFlag, "path", tapDBPath)
56 return markDone()
57 } else if err != nil {
58 return fmt.Errorf("stat tap db: %w", err)
59 }
60
61 tdb, err := sql.Open("sqlite3", tapDBPath+"?_busy_timeout=5000")
62 if err != nil {
63 return fmt.Errorf("open tap db: %w", err)
64 }
65 defer tdb.Close()
66
67 if _, err := tdb.ExecContext(ctx, `delete from repo_records`); err != nil {
68 return fmt.Errorf("clear tap repo_records: %w", err)
69 }
70 res, err := tdb.ExecContext(ctx,
71 `update repos set state = 'desynchronized', retry_after = 0 where state in ('active','error')`,
72 )
73 if err != nil {
74 return fmt.Errorf("desync tap repos: %w", err)
75 }
76 n, _ := res.RowsAffected()
77
78 if err := markDone(); err != nil {
79 return err
80 }
81 logger.Info("nudged tap to resync", "migration", forceTapResyncFlag, "repos_desynced", n)
82 return nil
83}