1package jobs23import (4 "context"5 "fmt"6 "path/filepath"7 "runtime"8 "strings"910 "github.com/charmbracelet/log/v2"11 "github.com/charmbracelet/soft-serve/git"12 "github.com/charmbracelet/soft-serve/pkg/backend"13 "github.com/charmbracelet/soft-serve/pkg/config"14 "github.com/charmbracelet/soft-serve/pkg/db"15 "github.com/charmbracelet/soft-serve/pkg/lfs"16 "github.com/charmbracelet/soft-serve/pkg/store"17 "github.com/charmbracelet/soft-serve/pkg/sync"18)1920func init() {21 Register("mirror-pull", mirrorPull{})22}2324type mirrorPull struct{}2526// Spec derives the spec used for pull mirrors and implements Runner.27func (m mirrorPull) Spec(ctx context.Context) string {28 cfg := config.FromContext(ctx)29 if cfg.Jobs.MirrorPull != "" {30 return cfg.Jobs.MirrorPull31 }32 return "@every 10m"33}3435// Func runs the (pull) mirror job task and implements Runner.36func (m mirrorPull) Func(ctx context.Context) func() {37 cfg := config.FromContext(ctx)38 logger := log.FromContext(ctx).WithPrefix("jobs.mirror")39 b := backend.FromContext(ctx)40 dbx := db.FromContext(ctx)41 datastore := store.FromContext(ctx)42 return func() {43 repos, err := b.Repositories(ctx)44 if err != nil {45 logger.Error("error getting repositories", "err", err)46 return47 }4849 // Divide the work up among the number of CPUs.50 wq := sync.NewWorkPool(ctx, runtime.GOMAXPROCS(0),51 sync.WithWorkPoolLogger(logger.Errorf),52 )5354 logger.Debug("updating mirror repos")55 for _, repo := range repos {56 if repo.IsMirror() {57 r, err := repo.Open()58 if err != nil {59 logger.Error("error opening repository", "repo", repo.Name(), "err", err)60 continue61 }6263 name := repo.Name()64 wq.Add(name, func() {65 repo := repo6667 cmds := []string{68 "fetch --prune", // fetch prune before updating remote69 "remote update --prune", // update remote and prune remote refs70 }7172 for _, c := range cmds {73 args := strings.Split(c, " ")74 cmd := git.NewCommand(args...).WithContext(ctx)75 cmd.AddEnvs(76 fmt.Sprintf(`GIT_SSH_COMMAND=ssh -o UserKnownHostsFile="%s" -o StrictHostKeyChecking=no -i "%s"`,77 filepath.Join(cfg.DataPath, "ssh", "known_hosts"),78 cfg.SSH.ClientKeyPath,79 ),80 )8182 if _, err := cmd.RunInDir(r.Path); err != nil {83 logger.Error("error running git remote update", "repo", name, "err", err)84 }85 }8687 if cfg.LFS.Enabled {88 rcfg, err := r.Config()89 if err != nil {90 logger.Error("error getting git config", "repo", name, "err", err)91 return92 }9394 lfsEndpoint := rcfg.Section("lfs").Option("url")95 if lfsEndpoint == "" {96 // If there is no LFS url defined, means the repo97 // doesn't use LFS and we can skip it.98 return99 }100101 ep, err := lfs.NewEndpoint(lfsEndpoint)102 if err != nil {103 logger.Error("error creating LFS endpoint", "repo", name, "err", err)104 return105 }106107 client := lfs.NewClient(ep)108 if client == nil {109 logger.Errorf("failed to create lfs client: unsupported endpoint %s", lfsEndpoint)110 return111 }112113 if err := backend.StoreRepoMissingLFSObjects(ctx, repo, dbx, datastore, client); err != nil {114 logger.Error("failed to store missing lfs objects", "err", err, "path", r.Path)115 return116 }117 }118 })119 }120 }121122 wq.Run()123 }124}