1package config
2
3import (
4 "os"
5 "path/filepath"
6
7 "github.com/caarlos0/env/v7"
8 "github.com/charmbracelet/log"
9 "github.com/charmbracelet/soft-serve/server/backend"
10 "gopkg.in/yaml.v3"
11)
12
13// SSHConfig is the configuration for the SSH server.
14type SSHConfig struct {
15 // ListenAddr is the address on which the SSH server will listen.
16 ListenAddr string `env:"LISTEN_ADDR" yaml:"listen_addr"`
17
18 // PublicURL is the public URL of the SSH server.
19 PublicURL string `env:"PUBLIC_URL" yaml:"public_url"`
20
21 // KeyPath is the path to the SSH server's private key.
22 KeyPath string `env:"KEY_PATH" yaml:"key_path"`
23
24 // ClientKeyPath is the path to the SSH server's client private key.
25 ClientKeyPath string `env:"CLIENT_KEY_PATH" yaml:"client_key_path"`
26
27 // InternalKeyPath is the path to the SSH server's internal private key.
28 InternalKeyPath string `env:"INTERNAL_KEY_PATH" yaml:"internal_key_path"`
29
30 // MaxTimeout is the maximum number of seconds a connection can take.
31 MaxTimeout int `env:"MAX_TIMEOUT" yaml:"max_timeout`
32
33 // IdleTimeout is the number of seconds a connection can be idle before it is closed.
34 IdleTimeout int `env:"IDLE_TIMEOUT" yaml:"idle_timeout"`
35}
36
37// GitConfig is the Git daemon configuration for the server.
38type GitConfig struct {
39 // ListenAddr is the address on which the Git daemon will listen.
40 ListenAddr string `env:"LISTEN_ADDR" yaml:"listen_addr"`
41
42 // MaxTimeout is the maximum number of seconds a connection can take.
43 MaxTimeout int `env:"MAX_TIMEOUT" yaml:"max_timeout"`
44
45 // IdleTimeout is the number of seconds a connection can be idle before it is closed.
46 IdleTimeout int `env:"IDLE_TIMEOUT" yaml:"idle_timeout"`
47
48 // MaxConnections is the maximum number of concurrent connections.
49 MaxConnections int `env:"MAX_CONNECTIONS" yaml:"max_connections"`
50}
51
52// HTTPConfig is the HTTP configuration for the server.
53type HTTPConfig struct {
54 // ListenAddr is the address on which the HTTP server will listen.
55 ListenAddr string `env:"LISTEN_ADDR" yaml:"listen_addr"`
56
57 // TLSKeyPath is the path to the TLS private key.
58 TLSKeyPath string `env:"TLS_KEY_PATH" yaml:"tls_key_path"`
59
60 // TLSCertPath is the path to the TLS certificate.
61 TLSCertPath string `env:"TLS_CERT_PATH" yaml:"tls_cert_path"`
62
63 // PublicURL is the public URL of the HTTP server.
64 PublicURL string `env:"PUBLIC_URL" yaml:"public_url"`
65}
66
67// StatsConfig is the configuration for the stats server.
68type StatsConfig struct {
69 // ListenAddr is the address on which the stats server will listen.
70 ListenAddr string `env:"LISTEN_ADDR" yaml:"listen_addr"`
71}
72
73// Config is the configuration for Soft Serve.
74type Config struct {
75 // Name is the name of the server.
76 Name string `env:"NAME" yaml:"name"`
77
78 // SSH is the configuration for the SSH server.
79 SSH SSHConfig `envPrefix:"SSH_" yaml:"ssh"`
80
81 // Git is the configuration for the Git daemon.
82 Git GitConfig `envPrefix:"GIT_" yaml:"git"`
83
84 // HTTP is the configuration for the HTTP server.
85 HTTP HTTPConfig `envPrefix:"HTTP_" yaml:"http"`
86
87 // Stats is the configuration for the stats server.
88 Stats StatsConfig `envPrefix:"STATS_" yaml:"stats"`
89
90 // InitialAdminKeys is a list of public keys that will be added to the list of admins.
91 InitialAdminKeys []string `env:"INITIAL_ADMIN_KEY" envSeparator:"\n" yaml:"initial_admin_keys"`
92
93 // DataPath is the path to the directory where Soft Serve will store its data.
94 DataPath string `env:"DATA_PATH" envDefault:"data" yaml:"-"`
95
96 // Backend is the Git backend to use.
97 Backend backend.Backend `yaml:"-"`
98}
99
100// ParseConfig parses the configuration from the given file.
101func ParseConfig(path string) (*Config, error) {
102 cfg := &Config{}
103 f, err := os.Open(path)
104 if err != nil {
105 return nil, err
106 }
107 defer f.Close() // nolint: errcheck
108 if err := yaml.NewDecoder(f).Decode(cfg); err != nil {
109 return nil, err
110 }
111
112 return cfg, nil
113}
114
115// WriteConfig writes the configuration to the given file.
116func WriteConfig(path string, cfg *Config) error {
117 return os.WriteFile(path, []byte(newConfigFile(cfg)), 0o600) // nolint: errcheck
118}
119
120// DefaultConfig returns a Config with the values populated with the defaults
121// or specified environment variables.
122func DefaultConfig() *Config {
123 dataPath := os.Getenv("SOFT_SERVE_DATA_PATH")
124 if dataPath == "" {
125 dataPath = "data"
126 }
127
128 dp, _ := filepath.Abs(dataPath)
129 if dp != "" {
130 dataPath = dp
131 }
132
133 cfg := &Config{
134 Name: "Soft Serve",
135 DataPath: dataPath,
136 SSH: SSHConfig{
137 ListenAddr: ":23231",
138 PublicURL: "ssh://localhost:23231",
139 KeyPath: filepath.Join("ssh", "soft_serve_host"),
140 ClientKeyPath: filepath.Join("ssh", "soft_serve_client"),
141 InternalKeyPath: filepath.Join("ssh", "soft_serve_internal"),
142 MaxTimeout: 0,
143 IdleTimeout: 120,
144 },
145 Git: GitConfig{
146 ListenAddr: ":9418",
147 MaxTimeout: 0,
148 IdleTimeout: 3,
149 MaxConnections: 32,
150 },
151 HTTP: HTTPConfig{
152 ListenAddr: ":8080",
153 PublicURL: "http://localhost:8080",
154 },
155 Stats: StatsConfig{
156 ListenAddr: ":8081",
157 },
158 }
159 cp := filepath.Join(cfg.DataPath, "config.yaml")
160 f, err := os.Open(cp)
161 if err == nil {
162 defer f.Close() // nolint: errcheck
163 if err := yaml.NewDecoder(f).Decode(cfg); err != nil {
164 log.Error("failed to decode config", "err", err)
165 }
166 } else {
167 defer func() {
168 os.WriteFile(cp, []byte(newConfigFile(cfg)), 0o600) // nolint: errcheck
169 }()
170 }
171
172 if err := env.Parse(cfg, env.Options{
173 Prefix: "SOFT_SERVE_",
174 }); err != nil {
175 log.Fatal(err)
176 }
177
178 return cfg
179}
180
181// WithBackend sets the backend for the configuration.
182func (c *Config) WithBackend(backend backend.Backend) *Config {
183 c.Backend = backend
184 return c
185}