all repos

mugit @ 7073ff2

🐮 git server that your cow will love

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

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