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