1package jobs
2
3import (
4 "context"
5 "fmt"
6 "path/filepath"
7 "runtime"
8
9 "github.com/charmbracelet/log"
10 "github.com/charmbracelet/soft-serve/git"
11 "github.com/charmbracelet/soft-serve/server/backend"
12 "github.com/charmbracelet/soft-serve/server/config"
13 "github.com/charmbracelet/soft-serve/server/db"
14 "github.com/charmbracelet/soft-serve/server/lfs"
15 "github.com/charmbracelet/soft-serve/server/store"
16 "github.com/charmbracelet/soft-serve/server/sync"
17)
18
19func init() {
20 Register("mirror-pull", "@every 10m", mirrorPull)
21}
22
23// mirrorPull runs the (pull) mirror job task.
24func mirrorPull(ctx context.Context) func() {
25 cfg := config.FromContext(ctx)
26 logger := log.FromContext(ctx).WithPrefix("jobs.mirror")
27 b := backend.FromContext(ctx)
28 dbx := db.FromContext(ctx)
29 datastore := store.FromContext(ctx)
30 return func() {
31 repos, err := b.Repositories(ctx)
32 if err != nil {
33 logger.Error("error getting repositories", "err", err)
34 return
35 }
36
37 // Divide the work up among the number of CPUs.
38 wq := sync.NewWorkPool(ctx, runtime.GOMAXPROCS(0),
39 sync.WithWorkPoolLogger(logger.Errorf),
40 )
41
42 logger.Debug("updating mirror repos")
43 for _, repo := range repos {
44 if repo.IsMirror() {
45 r, err := repo.Open()
46 if err != nil {
47 logger.Error("error opening repository", "repo", repo.Name(), "err", err)
48 continue
49 }
50
51 name := repo.Name()
52 wq.Add(name, func() {
53 repo := repo
54 cmd := git.NewCommand("remote", "update", "--prune").WithContext(ctx)
55 cmd.AddEnvs(
56 fmt.Sprintf(`GIT_SSH_COMMAND=ssh -o UserKnownHostsFile="%s" -o StrictHostKeyChecking=no -i "%s"`,
57 filepath.Join(cfg.DataPath, "ssh", "known_hosts"),
58 cfg.SSH.ClientKeyPath,
59 ),
60 )
61
62 if _, err := cmd.RunInDir(r.Path); err != nil {
63 logger.Error("error running git remote update", "repo", name, "err", err)
64 }
65
66 if cfg.LFS.Enabled {
67 rcfg, err := r.Config()
68 if err != nil {
69 logger.Error("error getting git config", "repo", name, "err", err)
70 return
71 }
72
73 lfsEndpoint := rcfg.Section("lfs").Option("url")
74 if lfsEndpoint == "" {
75 // If there is no LFS url defined, means the repo
76 // doesn't use LFS and we can skip it.
77 return
78 }
79
80 ep, err := lfs.NewEndpoint(lfsEndpoint)
81 if err != nil {
82 logger.Error("error creating LFS endpoint", "repo", name, "err", err)
83 return
84 }
85
86 client := lfs.NewClient(ep)
87 if client == nil {
88 logger.Errorf("failed to create lfs client: unsupported endpoint %s", lfsEndpoint)
89 return
90 }
91
92 if err := backend.StoreRepoMissingLFSObjects(ctx, repo, dbx, datastore, client); err != nil {
93 logger.Error("failed to store missing lfs objects", "err", err, "path", r.Path)
94 return
95 }
96 }
97 })
98 }
99 }
100
101 wq.Run()
102 }
103}