all repos

mugit @ 3d4f6c6fc25fc48f445cdfc41fe9e384e4d8d62a

🐮 git server that your cow will love

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

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
ssh: print modt on clone/push, 28 days ago
1
package config
2
3
import (
4
	"errors"
5
	"fmt"
6
	"os"
7
	"path/filepath"
8
	"strings"
9
	"time"
10
11
	"gopkg.in/yaml.v2"
12
)
13
14
var (
15
	ErrConfigNotFound = errors.New("no config file found")
16
	ErrUnsetEnv       = errors.New("environment variable is not set")
17
	ErrFileNotFound   = errors.New("provided file path is invalid")
18
)
19
20
type ServerConfig struct {
21
	Host    string `yaml:"host"`
22
	Port    int    `yaml:"port"`
23
	LogFile string `yaml:"log_file"`
24
}
25
26
type MetaConfig struct {
27
	Title       string `yaml:"title"`
28
	Description string `yaml:"description"`
29
	Host        string `yaml:"host"`
30
	Modt        string `yaml:"modt"`
31
}
32
33
type RepoConfig struct {
34
	Dir     string   `yaml:"dir"`
35
	Readmes []string `yaml:"readmes"`
36
}
37
38
type SSHConfig struct {
39
	Enable bool     `yaml:"enable"`
40
	User   string   `yaml:"user"`
41
	Keys   []string `yaml:"keys"`
42
}
43
44
type MirrorConfig struct {
45
	Enable      bool          `yaml:"enable"`
46
	Interval    time.Duration `yaml:"interval"`
47
	GithubToken string        `yaml:"github_token"`
48
}
49
50
type CacheConfig struct {
51
	HomePage time.Duration `yaml:"home_page"`
52
	Readme   time.Duration `yaml:"readme"`
53
	Diff     time.Duration `yaml:"diff"`
54
}
55
56
type Config struct {
57
	Server ServerConfig `yaml:"server"`
58
	Meta   MetaConfig   `yaml:"meta"`
59
	Repo   RepoConfig   `yaml:"repo"`
60
	SSH    SSHConfig    `yaml:"ssh"`
61
	Mirror MirrorConfig `yaml:"mirror"`
62
	Cache  CacheConfig  `yaml:"cache"`
63
}
64
65
func Load(fpath string) (*Config, error) {
66
	configBytes, err := os.ReadFile(fpath)
67
	if err != nil {
68
		return nil, err
69
	}
70
71
	var config Config
72
	if cerr := yaml.Unmarshal(configBytes, &config); cerr != nil {
73
		return nil, fmt.Errorf("parsing config: %w", cerr)
74
	}
75
76
	if config.Repo.Dir, err = filepath.Abs(config.Repo.Dir); err != nil {
77
		return nil, err
78
	}
79
80
	config.ensureDefaults()
81
82
	if perr := config.parseValues(); perr != nil {
83
		return nil, perr
84
	}
85
86
	if verr := config.validate(); verr != nil {
87
		return nil, verr
88
	}
89
90
	return &config, nil
91
}
92
93
// PathOrDefault uses userPath, if it's "", or invalid path, will default to one of those(in priority order)
94
// 1. ./config.yaml
95
// 2. /etc/mugit.yaml
96
// 3. /var/lib/mugit/config.yaml
97
func PathOrDefault(userPath string) string {
98
	return pathOrDefaultWithCandidates(userPath, []string{
99
		"./config.yaml",
100
		"/etc/mugit.yaml",
101
		"/var/lib/mugit/config.yaml",
102
	})
103
}
104
105
func pathOrDefaultWithCandidates(path string, candidates []string) string {
106
	if isFileExists(path) {
107
		return path
108
	}
109
110
	for _, fpath := range candidates {
111
		if isFileExists(fpath) {
112
			return fpath
113
		}
114
	}
115
116
	return ""
117
}
118
119
func (c *Config) ensureDefaults() {
120
	if c.Server.LogFile == "" {
121
		c.Server.LogFile = filepath.Join(c.Repo.Dir, "mugit.log")
122
	}
123
124
	// http
125
	if c.Server.Port == 0 {
126
		c.Server.Port = 8080
127
	}
128
129
	// meta
130
	if c.Meta.Title == "" {
131
		c.Meta.Title = "my mugit"
132
	}
133
134
	// repos
135
	if len(c.Repo.Readmes) == 0 {
136
		c.Repo.Readmes = []string{
137
			"README.md", "readme.md",
138
			"README.html", "readme.html",
139
			"README.txt", "readme.txt",
140
			"readme",
141
		}
142
	}
143
144
	// ssh
145
	if c.SSH.User == "" {
146
		c.SSH.User = "git"
147
	}
148
149
	// mirroring
150
	if c.Mirror.Interval == 0 {
151
		c.Mirror.Interval = 8 * time.Hour
152
	}
153
154
	// cache
155
	if c.Cache.HomePage == 0 {
156
		c.Cache.HomePage = 5 * time.Minute
157
	}
158
159
	if c.Cache.Readme == 0 {
160
		c.Cache.Readme = 1 * time.Minute
161
	}
162
163
	if c.Cache.Diff == 0 {
164
		c.Cache.Diff = 15 * time.Minute
165
	}
166
}
167
168
func (c *Config) parseValues() error {
169
	if c.Mirror.Enable {
170
		ghToken, err := parseValue(c.Mirror.GithubToken)
171
		if err != nil {
172
			return err
173
		}
174
		c.Mirror.GithubToken = ghToken
175
	}
176
	return nil
177
}
178
179
func parseValue(value string) (string, error) {
180
	envPrefix := "$env:"
181
	filePrefix := "$file:"
182
183
	switch {
184
	case strings.HasPrefix(value, envPrefix):
185
		env := os.Getenv(os.ExpandEnv(value[len(envPrefix):]))
186
		if env == "" {
187
			return "", ErrUnsetEnv
188
		}
189
		return env, nil
190
191
	case strings.HasPrefix(value, filePrefix):
192
		// supports only absolute paths
193
194
		fpath := value[len(filePrefix):]
195
		if !isFileExists(fpath) {
196
			return "", ErrFileNotFound
197
		}
198
199
		data, err := os.ReadFile(fpath)
200
		if err != nil {
201
			return "", err
202
		}
203
204
		return strings.TrimSpace(string(data)), nil
205
206
	default:
207
		return value, nil
208
	}
209
}
210
211
func isFileExists(path string) bool {
212
	_, err := os.Stat(path)
213
	return err == nil
214
}
215
216
func isDirExists(path string) bool {
217
	i, err := os.Stat(path)
218
	if err != nil {
219
		return false
220
	}
221
	return i.IsDir()
222
}