GitRoot

Craft your forge, Build your project, Grow your community freely
  1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
  2//
  3// SPDX-License-Identifier: EUPL-1.2
  4
  5package main
  6
  7import (
  8	"context"
  9	"flag"
 10	"fmt"
 11	"os"
 12	"path/filepath"
 13	"time"
 14
 15	"github.com/go-git/go-git/v6/plumbing"
 16	"github.com/goccy/go-yaml"
 17	"gitroot.dev/server/background"
 18	"gitroot.dev/server/configuration"
 19	"gitroot.dev/server/exec"
 20	"gitroot.dev/server/logger"
 21	"gitroot.dev/server/plugin"
 22	"gitroot.dev/server/repository"
 23	"gitroot.dev/server/scheduler"
 24	"gitroot.dev/server/user"
 25)
 26
 27var version string = "next" // set at build time
 28var commit string           // set at build time
 29
 30func main() {
 31	log := logger.NewLoggerCtx("root", context.Background())
 32	var rootPath string
 33	flag.StringVar(&rootPath, "data", "./data", "path to store data")
 34	var rootRemote string
 35	flag.StringVar(&rootRemote, "remote", "", "optional repository for root repo, only used at init")
 36	var showVersion bool
 37	flag.BoolVar(&showVersion, "version", false, "show current version and build time")
 38	var initConfig string
 39	flag.StringVar(&initConfig, "initConfig", "", "generate a config file to configure GitRoot before the first start")
 40	var config string
 41	flag.StringVar(&config, "config", "", "use a config file to configure GitRoot before the first start")
 42	flag.Parse()
 43
 44	conf := configuration.NewConfiguration(rootPath)
 45
 46	if config != "" {
 47		configContent, err := os.ReadFile(config)
 48		if err != nil {
 49			log.Error("can't read conf", err, logger.NewLoggerPair("path", config))
 50			os.Exit(1)
 51			return
 52		}
 53		conf.LoadFromFile(configContent)
 54	}
 55
 56	if showVersion || initConfig != "" {
 57		if showVersion {
 58			if commit == "" {
 59				commit = plumbing.ZeroHash.String()
 60			}
 61			log.Warn(fmt.Sprintf("Version `%s` for commit `%s`", version, commit))
 62		}
 63		if initConfig != "" {
 64			confMarshalled, err := yaml.Marshal(conf)
 65			if err != nil {
 66				log.Error("can't marshal conf", err)
 67				os.Exit(1)
 68				return
 69			}
 70			if err := os.WriteFile(initConfig, confMarshalled, 0666); err != nil {
 71				log.Error("can't write initConfif file", err, logger.NewLoggerPair("path", initConfig))
 72				os.Exit(1)
 73				return
 74			}
 75			log.Warn(fmt.Sprintf("File %s created, please edit it and launch gitroot with --config=%s to use it", initConfig, initConfig))
 76		}
 77		return
 78	}
 79
 80	needUpdateConf, err := conf.LoadFromOrStoreToRootConf(filepath.Join(rootPath, "gitroot.yml"), version)
 81	if err != nil {
 82		log.Error("Initialisation of root conf failed", err)
 83		os.Exit(1)
 84	}
 85
 86	userManager, err := user.NewManager(conf)
 87	if err != nil {
 88		log.Error("Initialisation failed", err)
 89		os.Exit(1)
 90	}
 91
 92	var backgroundManager *background.Manager
 93	repoManager := repository.NewManager(conf, userManager)
 94
 95	if !repoManager.Exists(conf.ForgeConfigName()) {
 96		go func() {
 97			if err := repoManager.CreateRootRepoIfNeeded(context.Background(), rootRemote); err != nil {
 98				log.Error("Error in CreateRootRepoIfNeeded", err)
 99			}
100		}()
101	} else {
102		rootRepo, err := repoManager.OpenForgeRepo(context.Background())
103		if err != nil {
104			log.Error("Can't open root repo", err)
105			return
106		}
107		c, err := rootRepo.Content(conf.PathFileForgeConfig())
108		if err != nil {
109			log.Error("Can't read root conf", err)
110			return
111		}
112		if err := conf.LoadFromFile(c); err != nil {
113			log.Error("Can't unmarshal root conf", err)
114			return
115		}
116		if needUpdateConf {
117			log.Debug("will write conf", logger.NewLoggerPair("branch", conf.DefaultBranch))
118			rootRepoWrite, err := rootRepo.WillWrite(plumbing.NewBranchReferenceName(conf.DefaultBranch))
119			if err != nil {
120				log.Error("can't open root repository in write mode", err)
121			}
122			confMarshalled, err := conf.Marshall()
123			if err != nil {
124				log.Error("can't marshal conf", err)
125			}
126			err = rootRepoWrite.Write(conf.PathFileForgeConfig(), confMarshalled)
127			if err != nil {
128				log.Error("can't write conf", err)
129			}
130			if _, err := rootRepoWrite.CommitAll("Conf updated", userManager.RootCommiter()); err != nil {
131				log.Error("can't commit new conf", err)
132			}
133			log.Warn("update conf done", logger.NewLoggerPair("branch", conf.DefaultBranch))
134		}
135		rootRepo.Close()
136	}
137
138	execManager := exec.NewManager(context.Background(), conf, userManager)
139	pluginManager := plugin.NewManager(conf, repoManager, userManager, execManager)
140	schedulerManager := scheduler.NewManager(conf, repoManager)
141	backgroundManager = background.NewManager(context.Background(), conf, repoManager, userManager, pluginManager, schedulerManager)
142	pluginManager.SetBackgroundManager(backgroundManager)
143	schedulerManager.SetBackgroundManager(backgroundManager)
144	go func() {
145		time.Sleep(10 * time.Second) //let's root repo born before run
146		schedulerManager.Start(context.Background())
147	}()
148
149	go func() {
150		if err := NewServerHttp(conf, repoManager).ListenAndServe(); err != nil {
151			log.Error("Http closed", err)
152		}
153	}()
154
155	if err := NewServerSsh(conf, repoManager, userManager, pluginManager, backgroundManager).ListenAndServe(); err != nil {
156		log.Error("Ssh closed", err)
157	}
158}