all repos

mugit @ 1a0952a

🐮 git server that your cow will love

mugit/internal/cli/cli.go(view raw)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package cli

import (
	"context"
	"log/slog"
	"net"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"syscall"

	"github.com/urfave/cli/v3"
	"olexsmir.xyz/mugit/internal/config"
	"olexsmir.xyz/mugit/internal/handlers"
	"olexsmir.xyz/mugit/internal/mirror"
	"olexsmir.xyz/mugit/internal/ssh"
)

type Cli struct {
	cfg *config.Config
}

func New() *Cli {
	return &Cli{}
}

func (c *Cli) Run(ctx context.Context, args []string) error {
	cmd := &cli.Command{
		Name:                  "mugit",
		Usage:                 "a frontend for git repos",
		EnableShellCompletion: true,
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "config",
				Aliases: []string{"c"},
				Usage:   "path to config file",
			},
		},
		Before: func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
			loadedCfg, err := config.Load(cmd.String("config"))
			if err != nil {
				return ctx, err
			}
			c.cfg = loadedCfg
			return ctx, nil
		},
		Commands: []*cli.Command{
			{
				Name:   "serve",
				Usage:  "starts the server",
				Action: c.serveAction,
			},
			{
				Name: "repo",
				Commands: []*cli.Command{
					{
						Name:   "new",
						Usage:  "create new repo",
						Action: c.repoNewAction,
						Arguments: []cli.Argument{
							&cli.StringArg{Name: "name"},
						},
					},
				},
			},
		},
	}
	return cmd.Run(ctx, args)
}

func (c *Cli) serveAction(ctx context.Context, cmd *cli.Command) error {
	httpServer := &http.Server{
		Addr:    net.JoinHostPort(c.cfg.Server.Host, strconv.Itoa(c.cfg.Server.Port)),
		Handler: handlers.InitRoutes(c.cfg),
	}
	go func() {
		slog.Info("starting http server", "host", c.cfg.Server.Host, "port", c.cfg.Server.Port)
		if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			slog.Error("HTTP server error", "err", err)
		}
	}()

	if c.cfg.SSH.Enable {
		sshServer := ssh.NewServer(c.cfg)
		go func() {
			slog.Info("starting ssh server", "port", c.cfg.SSH.Port)
			if err := sshServer.Start(); err != nil {
				slog.Error("ssh server error", "err", err)
			}
		}()
	}

	if c.cfg.Mirror.Enable {
		mirrorer := mirror.NewWorker(c.cfg)
		go func() {
			slog.Info("starting mirroring worker")
			mirrorer.Start(context.TODO())
		}()
	}

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	sig := <-sigChan
	slog.Info("received signal, starting graceful shutdown", "signal", sig)

	if err := httpServer.Shutdown(context.TODO()); err != nil {
		slog.Error("HTTP server shutdown error", "err", err)
	} else {
		slog.Info("HTTP server shutdown complete")
	}

	return nil
}