all repos

mugit @ 3cf8623aea4ab18930993b5305f5960b087f05e0

🐮 git server that your cow will love

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

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