Skip to content

Commit

Permalink
feat(config): store server config in file
Browse files Browse the repository at this point in the history
  • Loading branch information
aymanbagabas committed May 2, 2023
1 parent 0bfce9c commit 9a80cdb
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 26 deletions.
103 changes: 77 additions & 26 deletions server/config/config.go
Original file line number Diff line number Diff line change
@@ -1,97 +1,148 @@
package config

import (
"os"
"path/filepath"

"github.com/caarlos0/env/v6"
"github.com/charmbracelet/log"
"github.com/charmbracelet/soft-serve/server/backend"
"gopkg.in/yaml.v3"
)

// SSHConfig is the configuration for the SSH server.
type SSHConfig struct {
// ListenAddr is the address on which the SSH server will listen.
ListenAddr string `env:"LISTEN_ADDR" envDefault:":23231"`
ListenAddr string `env:"LISTEN_ADDR" yaml:"listen_addr"`

// PublicURL is the public URL of the SSH server.
PublicURL string `env:"PUBLIC_URL" envDefault:"ssh://localhost:23231"`
PublicURL string `env:"PUBLIC_URL" yaml:"public_url"`

// KeyPath is the path to the SSH server's private key.
KeyPath string `env:"KEY_PATH"`
KeyPath string `env:"KEY_PATH" yaml:"key_path"`

// InternalKeyPath is the path to the SSH server's internal private key.
InternalKeyPath string `env:"INTERNAL_KEY_PATH"`
InternalKeyPath string `env:"INTERNAL_KEY_PATH" yaml:"internal_key_path"`

// MaxTimeout is the maximum number of seconds a connection can take.
MaxTimeout int `env:"MAX_TIMEOUT" envDefault:"0"`
MaxTimeout int `env:"MAX_TIMEOUT" yaml:"max_timeout`

// IdleTimeout is the number of seconds a connection can be idle before it is closed.
IdleTimeout int `env:"IDLE_TIMEOUT" envDefault:"120"`
IdleTimeout int `env:"IDLE_TIMEOUT" yaml:"idle_timeout"`
}

// GitConfig is the Git daemon configuration for the server.
type GitConfig struct {
// ListenAddr is the address on which the Git daemon will listen.
ListenAddr string `env:"LISTEN_ADDR" envDefault:":9418"`
ListenAddr string `env:"LISTEN_ADDR" yaml:"listen_addr"`

// MaxTimeout is the maximum number of seconds a connection can take.
MaxTimeout int `env:"MAX_TIMEOUT" envDefault:"0"`
MaxTimeout int `env:"MAX_TIMEOUT" yaml:"max_timeout"`

// IdleTimeout is the number of seconds a connection can be idle before it is closed.
IdleTimeout int `env:"IDLE_TIMEOUT" envDefault:"3"`
IdleTimeout int `env:"IDLE_TIMEOUT" yaml:"idle_timeout"`

// MaxConnections is the maximum number of concurrent connections.
MaxConnections int `env:"MAX_CONNECTIONS" envDefault:"32"`
MaxConnections int `env:"MAX_CONNECTIONS" yaml:"max_connections"`
}

// HTTPConfig is the HTTP configuration for the server.
type HTTPConfig struct {
// ListenAddr is the address on which the HTTP server will listen.
ListenAddr string `env:"LISTEN_ADDR" envDefault:":8080"`
ListenAddr string `env:"LISTEN_ADDR" yaml:"listen_addr"`

// PublicURL is the public URL of the HTTP server.
PublicURL string `env:"PUBLIC_URL" envDefault:"http://localhost:8080"`
PublicURL string `env:"PUBLIC_URL" yaml:"public_url"`
}

// Config is the configuration for Soft Serve.
type Config struct {
// Name is the name of the server.
Name string `env:"NAME" envDefault:"Soft Serve"`
Name string `env:"NAME" yaml:"name"`

// SSH is the configuration for the SSH server.
SSH SSHConfig `envPrefix:"SSH_"`
SSH SSHConfig `envPrefix:"SSH_" yaml:"ssh"`

// Git is the configuration for the Git daemon.
Git GitConfig `envPrefix:"GIT_"`
Git GitConfig `envPrefix:"GIT_" yaml:"git"`

// HTTP is the configuration for the HTTP server.
HTTP HTTPConfig `envPrefix:"HTTP_"`
HTTP HTTPConfig `envPrefix:"HTTP_" yaml:"http"`

// InitialAdminKeys is a list of public keys that will be added to the list of admins.
InitialAdminKeys []string `env:"INITIAL_ADMIN_KEY" envSeparator:"\n"`
InitialAdminKeys []string `env:"INITIAL_ADMIN_KEY" envSeparator:"\n" yaml:"initial_admin_keys"`

// DataPath is the path to the directory where Soft Serve will store its data.
DataPath string `env:"DATA_PATH" envDefault:"data"`
DataPath string `env:"DATA_PATH" envDefault:"data" yaml:"-"`

// Backend is the Git backend to use.
Backend backend.Backend
Backend backend.Backend `yaml:"-"`
}

// ParseConfig parses the configuration from the given file.
func ParseConfig(path string) (*Config, error) {
cfg := &Config{}
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
if err := yaml.NewDecoder(f).Decode(cfg); err != nil {
return nil, err
}

return cfg, nil
}

// DefaultConfig returns a Config with the values populated with the defaults
// or specified environment variables.
func DefaultConfig() *Config {
cfg := &Config{}
dataPath := os.Getenv("SOFT_SERVE_DATA_PATH")
if dataPath == "" {
dataPath = "data"
}

cfg := &Config{
Name: "Soft Serve",
DataPath: dataPath,
SSH: SSHConfig{
ListenAddr: ":23231",
PublicURL: "ssh://localhost:23231",
KeyPath: filepath.Join("ssh", "soft_serve"),
InternalKeyPath: filepath.Join("ssh", "soft_serve_internal"),
MaxTimeout: 0,
IdleTimeout: 120,
},
Git: GitConfig{
ListenAddr: ":9418",
MaxTimeout: 0,
IdleTimeout: 3,
MaxConnections: 32,
},
HTTP: HTTPConfig{
ListenAddr: ":8080",
PublicURL: "http://localhost:8080",
},
}
cp := filepath.Join(cfg.DataPath, "config.yaml")
f, err := os.Open(cp)
if err == nil {
defer f.Close()
if err := yaml.NewDecoder(f).Decode(cfg); err != nil {
log.Error("failed to decode config", "err", err)
}
} else {
defer func() {
os.WriteFile(cp, []byte(newConfigFile(cfg)), 0o600) // nolint: errcheck
}()
}

if err := env.Parse(cfg, env.Options{
Prefix: "SOFT_SERVE_",
}); err != nil {
log.Fatal(err)
}
if cfg.SSH.KeyPath == "" {
cfg.SSH.KeyPath = filepath.Join(cfg.DataPath, "ssh", "soft_serve")
}
if cfg.SSH.InternalKeyPath == "" {
cfg.SSH.InternalKeyPath = filepath.Join(cfg.DataPath, "ssh", "soft_serve_internal")
}

return cfg
}

Expand Down
68 changes: 68 additions & 0 deletions server/config/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package config

import (
"bytes"
"text/template"
)

var (
configFileTmpl = template.Must(template.New("config").Parse(`# Soft Serve Server configurations
# The name of the server.
# This is the name that will be displayed in the UI.
name: "{{ .Name }}"
# The SSH server configuration.
ssh:
# The address on which the SSH server will listen.
listen_addr: "{{ .SSH.ListenAddr }}"
# The public URL of the SSH server.
# This is the address will be used to clone repositories.
public_url: "{{ .SSH.PublicURL }}"
# The relative path to the SSH server's private key.
key_path: "{{ .SSH.KeyPath }}"
# The relative path to the SSH server's internal api private key.
internal_key_path: "{{ .SSH.InternalKeyPath }}"
# The maximum number of seconds a connection can take.
# A value of 0 means no timeout.
max_timeout: {{ .SSH.MaxTimeout }}
# The number of seconds a connection can be idle before it is closed.
idle_timeout: {{ .SSH.IdleTimeout }}
# The Git daemon configuration.
git:
# The address on which the Git daemon will listen.
listen_addr: "{{ .Git.ListenAddr }}"
# The maximum number of seconds a connection can take.
# A value of 0 means no timeout.
max_timeout: {{ .Git.MaxTimeout }}
# The number of seconds a connection can be idle before it is closed.
idle_timeout: {{ .Git.IdleTimeout }}
# The maximum number of concurrent connections.
max_connections: {{ .Git.MaxConnections }}
# The HTTP server configuration.
http:
# The address on which the HTTP server will listen.
listen_addr: "{{ .HTTP.ListenAddr }}"
# The public URL of the HTTP server.
# This is the address will be used to clone repositories.
public_url: "{{ .HTTP.PublicURL }}"
`))
)

func newConfigFile(cfg *Config) string {
var b bytes.Buffer
configFileTmpl.Execute(&b, cfg)
return b.String()
}

0 comments on commit 9a80cdb

Please sign in to comment.