startup.go
⎇
Raw
1package web
2
3import (
4 "context"
5 "log"
6 "time"
7
8 "hearthforge/internal/db"
9)
10
11// SyncRepos reconciles the repositories table with the repos on disk.
12// Disk wins: new bare repos get a row, rows without a directory are removed.
13func (s *Server) SyncRepos(ctx context.Context) error {
14 names, err := s.Git.SyncStartup(ctx)
15 if err != nil {
16 return err
17 }
18 onDisk := make(map[string]bool, len(names))
19 for _, n := range names {
20 onDisk[n] = true
21 }
22 rows, err := s.DB.AllRepoRefs(ctx)
23 if err != nil {
24 return err
25 }
26 inDB := make(map[string]bool, len(rows))
27 var stale []int64
28 for _, r := range rows {
29 inDB[r.Name] = true
30 if !onDisk[r.Name] {
31 stale = append(stale, r.ID)
32 log.Printf("repo %q missing on disk, removing row", r.Name)
33 }
34 }
35 if len(stale) > 0 {
36 if err := s.DB.DeleteRepos(ctx, stale); err != nil {
37 return err
38 }
39 }
40 for _, n := range names {
41 if inDB[n] {
42 continue
43 }
44 branch := s.Git.DefaultBranch(ctx, n)
45 if _, err := s.DB.CreateRepo(ctx, n, nil, s.Cfg.ScannedRepoPrivate, branch, db.NowISO()); err != nil {
46 return err
47 }
48 log.Printf("registered scanned repo %q", n)
49 }
50 return nil
51}
52
53// staleSignupAge is how long a passwordless signup may sit without a passkey
54// before the account is removed.
55const staleSignupAge = 15 * time.Minute
56
57// StartSessionCleanup deletes expired sessions and abandoned passwordless
58// signups once an hour.
59func (s *Server) StartSessionCleanup(ctx context.Context) {
60 go func() {
61 t := time.NewTicker(time.Hour)
62 defer t.Stop()
63 for {
64 select {
65 case <-ctx.Done():
66 return
67 case <-t.C:
68 if err := s.DB.DeleteExpiredSessions(ctx); err != nil {
69 log.Printf("session cleanup: %v", err)
70 }
71 cutoff := time.Now().UTC().Add(-staleSignupAge).Format(db.ISOLayout)
72 if err := s.DB.DeleteStalePasskeySignups(ctx, cutoff); err != nil {
73 log.Printf("stale signup cleanup: %v", err)
74 }
75 }
76 }
77 }()
78}
79