all repos

mugit @ f70e150

🐮 git server that your cow will love

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

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
ssh: write only ssh logs to file (othewise they are shown to user..., 22 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
}
24
25
type MetaConfig struct {
26
	Title       string `yaml:"title"`
27
	Description string `yaml:"description"`
28
	Host        string `yaml:"host"`
29
	Modt        string `yaml:"modt"`
30
}
31
32
type RepoConfig struct {
33
	Dir     string   `yaml:"dir"`
34
	Readmes []string `yaml:"readmes"`
35
}
36
37
type SSHConfig struct {
38
	Enable  bool     `yaml:"enable"`
39
	User    string   `yaml:"user"`
40
	Keys    []string `yaml:"keys"`
41
	LogFile string   `yaml:"log_file"`
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
	// http
121
	if c.Server.Port == 0 {
122
		c.Server.Port = 8080
123
	}
124
125
	// meta
126
	if c.Meta.Title == "" {
127
		c.Meta.Title = "my mugit"
128
	}
129
130
	// repos
131
	if len(c.Repo.Readmes) == 0 {
132
		c.Repo.Readmes = []string{
133
			"README.md", "readme.md",
134
			"README.html", "readme.html",
135
			"README.txt", "readme.txt",
136
			"readme",
137
		}
138
	}
139
140
	// ssh
141
	if c.SSH.User == "" {
142
		c.SSH.User = "git"
143
	}
144
	if c.SSH.LogFile == "" {
145
		c.SSH.LogFile = filepath.Join(c.Repo.Dir, "mugit-ssh.log")
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
}