all repos

mugit @ 96b3828

🐮 git server that your cow will love

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

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