5 files changed,
150 insertions(+),
71 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-01-22 23:27:17 +0200
Authored at:
2026-01-21 23:58:11 +0200
Change ID:
ovqyknqtnunrsvwqttqzxoxosrwpzull
Parent:
c328fa9
jump to
| M | go.mod |
| M | go.sum |
| A | internal/cli/cli.go |
| M | internal/config/config.go |
| M | main.go |
M
go.sum
路路路 65 65 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 66 66 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 67 67 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 68 -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 69 -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 68 +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 69 +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 70 +github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8= 71 +github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= 70 72 github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= 71 73 github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= 72 74 github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
A
internal/cli/cli.go
路路路 1 +package cli 2 + 3 +import ( 4 + "context" 5 + "log/slog" 6 + "net" 7 + "net/http" 8 + "os" 9 + "os/signal" 10 + "strconv" 11 + "syscall" 12 + 13 + "github.com/urfave/cli/v3" 14 + "olexsmir.xyz/mugit/internal/config" 15 + "olexsmir.xyz/mugit/internal/handlers" 16 + "olexsmir.xyz/mugit/internal/mirror" 17 + "olexsmir.xyz/mugit/internal/ssh" 18 +) 19 + 20 +type Cli struct { 21 + cfg *config.Config 22 +} 23 + 24 +func New() *Cli { 25 + return &Cli{} 26 +} 27 + 28 +func (c *Cli) Run(ctx context.Context, args []string) error { 29 + cmd := &cli.Command{ 30 + Name: "mugit", 31 + Usage: "a frontend for git repos", 32 + EnableShellCompletion: true, 33 + Flags: []cli.Flag{ 34 + &cli.StringFlag{ 35 + Name: "config", 36 + Aliases: []string{"c"}, 37 + Usage: "path to config file", 38 + }, 39 + }, 40 + Before: func(ctx context.Context, cmd *cli.Command) (context.Context, error) { 41 + loadedCfg, err := config.Load(cmd.String("config")) 42 + if err != nil { 43 + return ctx, err 44 + } 45 + c.cfg = loadedCfg 46 + return ctx, nil 47 + }, 48 + Commands: []*cli.Command{ 49 + { 50 + Name: "serve", 51 + Usage: "starts the server", 52 + Action: c.serveAction, 53 + }, 54 + }, 55 + } 56 + return cmd.Run(ctx, args) 57 +} 58 + 59 +func (c *Cli) serveAction(ctx context.Context, cmd *cli.Command) error { 60 + httpServer := &http.Server{ 61 + Addr: net.JoinHostPort(c.cfg.Server.Host, strconv.Itoa(c.cfg.Server.Port)), 62 + Handler: handlers.InitRoutes(c.cfg), 63 + } 64 + go func() { 65 + slog.Info("starting http server", "host", c.cfg.Server.Host, "port", c.cfg.Server.Port) 66 + if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { 67 + slog.Error("HTTP server error", "err", err) 68 + } 69 + }() 70 + 71 + if c.cfg.SSH.Enable { 72 + sshServer := ssh.NewServer(c.cfg) 73 + go func() { 74 + slog.Info("starting ssh server", "port", c.cfg.SSH.Port) 75 + if err := sshServer.Start(); err != nil { 76 + slog.Error("ssh server error", "err", err) 77 + } 78 + }() 79 + } 80 + 81 + if c.cfg.Mirror.Enable { 82 + mirrorer := mirror.NewWorker(c.cfg) 83 + go func() { 84 + slog.Info("starting mirroring worker") 85 + mirrorer.Start(context.TODO()) 86 + }() 87 + } 88 + 89 + sigChan := make(chan os.Signal, 1) 90 + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) 91 + 92 + sig := <-sigChan 93 + slog.Info("received signal, starting graceful shutdown", "signal", sig) 94 + 95 + if err := httpServer.Shutdown(context.TODO()); err != nil { 96 + slog.Error("HTTP server shutdown error", "err", err) 97 + } else { 98 + slog.Info("HTTP server shutdown complete") 99 + } 100 + 101 + return nil 102 +}
M
internal/config/config.go
路路路 1 1 package config 2 2 3 3 import ( 4 + "errors" 4 5 "fmt" 5 6 "os" 6 7 "path/filepath" 7 8 8 9 "gopkg.in/yaml.v2" 9 10 ) 11 + 12 +var ErrConfigNotFound = errors.New("no config file found") 10 13 11 14 type Config struct { 12 15 Server struct { 路路路 36 39 } `yaml:"mirror"` 37 40 } 38 41 42 +// Load loads configuration with the following priority: 43 +// 1. User provided fpath (if provided and exists) 44 +// 2. $XDG_CONFIG_HOME/mugit/config.yaml 45 +// 3. $HOME/.config/mugit/config.yaml (fallback if XDG_CONFIG_HOME not set) 46 +// 4. /etc/mugit/config.yaml 39 47 func Load(fpath string) (*Config, error) { 40 - configBytes, err := os.ReadFile(fpath) 48 + configPath, err := findConfigFile(fpath) 41 49 if err != nil { 42 50 return nil, err 43 51 } 44 52 45 - var config *Config 53 + configBytes, err := os.ReadFile(configPath) 54 + if err != nil { 55 + return nil, err 56 + } 57 + 58 + var config Config 46 59 if cerr := yaml.Unmarshal(configBytes, &config); cerr != nil { 47 60 return nil, fmt.Errorf("parsing config: %w", cerr) 48 61 } 路路路 55 68 return nil, verr 56 69 } 57 70 58 - return config, nil 71 + return &config, nil 59 72 } 60 73 61 74 func (c Config) validate() error { 路路路 63 76 // return errors.Join(errs...) 64 77 return nil 65 78 } 79 + 80 +func findConfigFile(userPath string) (string, error) { 81 + if userPath != "" { 82 + if _, err := os.Stat(userPath); err == nil { 83 + return userPath, nil 84 + } 85 + } 86 + 87 + paths := []string{} 88 + if xdg := os.Getenv("XDG_CONFIG_HOME"); xdg != "" { 89 + paths = append(paths, filepath.Join(xdg, "mugit", "config.yaml")) 90 + } else if home := os.Getenv("HOME"); home != "" { 91 + paths = append(paths, filepath.Join(home, ".config", "mugit", "config.yaml")) 92 + } 93 + 94 + paths = append(paths, "/etc/mugit/config.yaml") 95 + for _, p := range paths { 96 + if _, err := os.Stat(p); err == nil { 97 + return p, nil 98 + } 99 + } 100 + 101 + return "", ErrConfigNotFound 102 +}
M
main.go
路路路 2 2 3 3 import ( 4 4 "context" 5 - "log" 6 5 "log/slog" 7 - "net" 8 - "net/http" 9 6 "os" 10 - "os/signal" 11 - "strconv" 12 - "syscall" 13 7 14 - "olexsmir.xyz/mugit/internal/config" 15 - "olexsmir.xyz/mugit/internal/handlers" 16 - "olexsmir.xyz/mugit/internal/mirror" 17 - "olexsmir.xyz/mugit/internal/ssh" 8 + "olexsmir.xyz/mugit/internal/cli" 18 9 ) 19 10 20 11 func main() { 21 - if err := run(); err != nil { 22 - log.Fatalf("main: %s", err) 12 + if err := cli.New().Run(context.TODO(), os.Args); err != nil { 13 + slog.Error("mugit", "err", err) 23 14 os.Exit(1) 24 15 } 25 16 } 26 - 27 -func run() error { 28 - cfg, err := config.Load("/home/olex/mugit-test/config.yml") 29 - if err != nil { 30 - slog.Error("config error", "err", err) 31 - return err 32 - } 33 - 34 - httpServer := &http.Server{ 35 - Addr: net.JoinHostPort(cfg.Server.Host, strconv.Itoa(cfg.Server.Port)), 36 - Handler: handlers.InitRoutes(cfg), 37 - } 38 - 39 - go func() { 40 - slog.Info("starting http server", "host", cfg.Server.Host, "port", cfg.Server.Port) 41 - 42 - if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { 43 - slog.Error("HTTP server error", "err", err) 44 - } 45 - }() 46 - 47 - sshServer := ssh.NewServer(cfg) 48 - if cfg.SSH.Enable { 49 - go func() { 50 - slog.Info("starting ssh server", "port", cfg.SSH.Port) 51 - if err := sshServer.Start(); err != nil { 52 - slog.Error("ssh server error", "err", err) 53 - } 54 - }() 55 - } 56 - 57 - mirrorer := mirror.NewWorker(cfg) 58 - if cfg.Mirror.Enable { 59 - go func() { 60 - slog.Info("starting mirroring worker") 61 - mirrorer.Start(context.TODO()) 62 - }() 63 - } 64 - 65 - // Wait for interrupt signal 66 - sigChan := make(chan os.Signal, 1) 67 - signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) 68 - 69 - sig := <-sigChan 70 - slog.Info("received signal, starting graceful shutdown", "signal", sig) 71 - 72 - if err := httpServer.Shutdown(context.TODO()); err != nil { 73 - slog.Error("HTTP server shutdown error", "err", err) 74 - } else { 75 - slog.Info("HTTP server shutdown complete") 76 - } 77 - 78 - return nil 79 -}