Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

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 err := cleanupOrphanRepos(ctx, d, logger); err != nil { 19 return fmt.Errorf("cleanup orphan repos: %w", err) 20 } 21 if !tapEmbed { 22 logger.Warn("tap not embedded: legacy repos won't auto-resync; trigger external tap resync to migrate secrets/casbin") 23 return nil 24 } 25 if err := nudgeTapForResync(ctx, d, tapDBPath, logger); err != nil { 26 return fmt.Errorf("nudge tap for resync: %w", err) 27 } 28 return nil 29} 30 31func cleanupOrphanRepos(ctx context.Context, d *db.DB, logger *slog.Logger) error { 32 res, err := d.ExecContext(ctx, ` 33 delete from repos 34 where coalesce(repo_did, '') = '' 35 and exists ( 36 select 1 from repos r2 37 where r2.owner = repos.owner 38 and coalesce(r2.repo_did, '') <> '' 39 ) 40 `) 41 if err != nil { 42 return fmt.Errorf("delete orphan repos: %w", err) 43 } 44 n, _ := res.RowsAffected() 45 if n > 0 { 46 logger.Info("cleaned up orphan repos missing repo_did", "deleted", n) 47 } 48 return nil 49} 50 51func nudgeTapForResync(ctx context.Context, d *db.DB, tapDBPath string, logger *slog.Logger) error { 52 if tapDBPath == "" { 53 return fmt.Errorf("tap db path empty in embed mode") 54 } 55 var exists bool 56 if err := d.QueryRowContext(ctx, 57 `select exists (select 1 from migrations where name = ?)`, 58 forceTapResyncFlag, 59 ).Scan(&exists); err != nil { 60 return fmt.Errorf("check %s flag: %w", forceTapResyncFlag, err) 61 } 62 if exists { 63 logger.Warn("skipped migration, already applied", "migration", forceTapResyncFlag) 64 return nil 65 } 66 67 markDone := func() error { 68 if _, err := d.ExecContext(ctx, 69 `insert or ignore into migrations (name) values (?)`, 70 forceTapResyncFlag, 71 ); err != nil { 72 return fmt.Errorf("mark %s done: %w", forceTapResyncFlag, err) 73 } 74 return nil 75 } 76 77 if _, err := os.Stat(tapDBPath); errors.Is(err, os.ErrNotExist) { 78 logger.Info("tap db not yet created, marking resync nudge done", "migration", forceTapResyncFlag, "path", tapDBPath) 79 return markDone() 80 } else if err != nil { 81 return fmt.Errorf("stat tap db: %w", err) 82 } 83 84 tdb, err := sql.Open("sqlite3", tapDBPath+"?_busy_timeout=5000") 85 if err != nil { 86 return fmt.Errorf("open tap db: %w", err) 87 } 88 defer tdb.Close() 89 90 if _, err := tdb.ExecContext(ctx, `delete from repo_records`); err != nil { 91 return fmt.Errorf("clear tap repo_records: %w", err) 92 } 93 res, err := tdb.ExecContext(ctx, 94 `update repos set state = 'desynchronized', retry_after = 0 where state in ('active','error')`, 95 ) 96 if err != nil { 97 return fmt.Errorf("desync tap repos: %w", err) 98 } 99 n, _ := res.RowsAffected() 100 101 if err := markDone(); err != nil { 102 return err 103 } 104 logger.Info("nudged tap to resync", "migration", forceTapResyncFlag, "repos_desynced", n) 105 return nil 106}