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}