config.go

  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	// InternalPublicKey is the public key of the internal SSH key.
100	InternalPublicKey string `yaml:"-"`
101
102	// ClientPublicKey is the public key of the client SSH key.
103	ClientPublicKey string `yaml:"-"`
104}
105
106// ParseConfig parses the configuration from the given file.
107func ParseConfig(path string) (*Config, error) {
108	cfg := &Config{}
109	f, err := os.Open(path)
110	if err != nil {
111		return nil, err
112	}
113	defer f.Close() // nolint: errcheck
114	if err := yaml.NewDecoder(f).Decode(cfg); err != nil {
115		return nil, err
116	}
117
118	return cfg, nil
119}
120
121// WriteConfig writes the configuration to the given file.
122func WriteConfig(path string, cfg *Config) error {
123	return os.WriteFile(path, []byte(newConfigFile(cfg)), 0o600) // nolint: errcheck
124}
125
126// DefaultConfig returns a Config with the values populated with the defaults
127// or specified environment variables.
128func DefaultConfig() *Config {
129	dataPath := os.Getenv("SOFT_SERVE_DATA_PATH")
130	if dataPath == "" {
131		dataPath = "data"
132	}
133
134	dp, _ := filepath.Abs(dataPath)
135	if dp != "" {
136		dataPath = dp
137	}
138
139	cfg := &Config{
140		Name:     "Soft Serve",
141		DataPath: dataPath,
142		SSH: SSHConfig{
143			ListenAddr:      ":23231",
144			PublicURL:       "ssh://localhost:23231",
145			KeyPath:         filepath.Join("ssh", "soft_serve_host"),
146			ClientKeyPath:   filepath.Join("ssh", "soft_serve_client"),
147			InternalKeyPath: filepath.Join("ssh", "soft_serve_internal"),
148			MaxTimeout:      0,
149			IdleTimeout:     120,
150		},
151		Git: GitConfig{
152			ListenAddr:     ":9418",
153			MaxTimeout:     0,
154			IdleTimeout:    3,
155			MaxConnections: 32,
156		},
157		HTTP: HTTPConfig{
158			ListenAddr: ":8080",
159			PublicURL:  "http://localhost:8080",
160		},
161		Stats: StatsConfig{
162			ListenAddr: ":8081",
163		},
164	}
165	cp := filepath.Join(cfg.DataPath, "config.yaml")
166	f, err := os.Open(cp)
167	if err == nil {
168		defer f.Close() // nolint: errcheck
169		if err := yaml.NewDecoder(f).Decode(cfg); err != nil {
170			log.Error("failed to decode config", "err", err)
171		}
172	} else {
173		defer func() {
174			os.WriteFile(cp, []byte(newConfigFile(cfg)), 0o600) // nolint: errcheck
175		}()
176	}
177
178	if err := env.Parse(cfg, env.Options{
179		Prefix: "SOFT_SERVE_",
180	}); err != nil {
181		log.Fatal(err)
182	}
183
184	return cfg
185}
186
187// WithBackend sets the backend for the configuration.
188func (c *Config) WithBackend(backend backend.Backend) *Config {
189	c.Backend = backend
190	return c
191}