config.go

  1package config
  2
  3import (
  4	"fmt"
  5	"log"
  6	"net"
  7	"net/url"
  8	"os"
  9	"path/filepath"
 10
 11	"github.com/caarlos0/env/v6"
 12	"github.com/charmbracelet/soft-serve/proto"
 13	"github.com/charmbracelet/soft-serve/server/db"
 14	"github.com/charmbracelet/soft-serve/server/db/sqlite"
 15)
 16
 17// Callbacks provides an interface that can be used to run callbacks on different events.
 18type Callbacks interface {
 19	Tui(action string)
 20	Push(repo string)
 21	Fetch(repo string)
 22}
 23
 24// SSHConfig is the SSH configuration for the server.
 25type SSHConfig struct {
 26	Port          int    `env:"PORT" envDefault:"23231"`
 27	AllowKeyless  bool   `env:"ALLOW_KEYLESS" envDefault:"true"`
 28	AllowPassword bool   `env:"ALLOW_PASSWORD" envDefault:"false"`
 29	Password      string `env:"PASSWORD"`
 30	MaxTimeout    int    `env:"MAX_TIMEOUT" envDefault:"0"`
 31	IdleTimeout   int    `env:"IDLE_TIMEOUT" envDefault:"300"`
 32}
 33
 34// GitConfig is the Git protocol configuration for the server.
 35type GitConfig struct {
 36	Port           int `env:"PORT" envDefault:"9418"`
 37	MaxTimeout     int `env:"MAX_TIMEOUT" envDefault:"0"`
 38	IdleTimeout    int `env:"IDLE_TIMEOUT" envDefault:"3"`
 39	MaxConnections int `env:"SOFT_SERVE_GIT_MAX_CONNECTIONS" envDefault:"32"`
 40}
 41
 42// DBConfig is the database configuration for the server.
 43type DBConfig struct {
 44	Driver   string `env:"DRIVER" envDefault:"sqlite"`
 45	User     string `env:"USER"`
 46	Password string `env:"PASSWORD"`
 47	Host     string `env:"HOST"`
 48	Port     string `env:"PORT"`
 49	Name     string `env:"NAME"`
 50	SSLMode  bool   `env:"SSL_MODE" envDefault:"false"`
 51}
 52
 53// URL returns a database URL for the configuration.
 54func (d *DBConfig) URL() *url.URL {
 55	switch d.Driver {
 56	case "sqlite":
 57		return &url.URL{
 58			Scheme: "sqlite",
 59			Path:   filepath.Join(d.Name),
 60		}
 61	default:
 62		ssl := "disable"
 63		if d.SSLMode {
 64			ssl = "require"
 65		}
 66		var user *url.Userinfo
 67		if d.User != "" && d.Password != "" {
 68			user = url.UserPassword(d.User, d.Password)
 69		} else if d.User != "" {
 70			user = url.User(d.User)
 71		}
 72		return &url.URL{
 73			Scheme:   d.Driver,
 74			Host:     net.JoinHostPort(d.Host, d.Port),
 75			User:     user,
 76			Path:     d.Name,
 77			RawQuery: fmt.Sprintf("sslmode=%s", ssl),
 78		}
 79	}
 80}
 81
 82// Config is the configuration for Soft Serve.
 83type Config struct {
 84	Host string    `env:"HOST" envDefault:"localhost"`
 85	SSH  SSHConfig `env:"SSH" envPrefix:"SSH_"`
 86	Git  GitConfig `env:"GIT" envPrefix:"GIT_"`
 87	Db   DBConfig  `env:"DB" envPrefix:"DB_"`
 88
 89	ServerName string            `env:"SERVER_NAME" envDefault:"Soft Serve"`
 90	AnonAccess proto.AccessLevel `env:"ANON_ACCESS" envDefault:"read-only"`
 91	DataPath   string            `env:"DATA_PATH" envDefault:"data"`
 92
 93	// Deprecated: use SOFT_SERVE_SSH_PORT instead.
 94	Port int `env:"PORT"`
 95	// Deprecated: use DataPath instead.
 96	KeyPath string `env:"KEY_PATH"`
 97	// Deprecated: use DataPath instead.
 98	ReposPath string `env:"REPO_PATH"`
 99
100	InitialAdminKeys []string `env:"INITIAL_ADMIN_KEY" envSeparator:"\n"`
101	Callbacks        Callbacks
102	ErrorLog         *log.Logger
103
104	db db.Store
105}
106
107// RepoPath returns the path to the repositories.
108func (c *Config) RepoPath() string {
109	return filepath.Join(c.DataPath, "repos")
110}
111
112// SSHPath returns the path to the SSH directory.
113func (c *Config) SSHPath() string {
114	return filepath.Join(c.DataPath, "ssh")
115}
116
117// PrivateKeyPath returns the path to the SSH key.
118func (c *Config) PrivateKeyPath() string {
119	return filepath.Join(c.SSHPath(), "soft_serve")
120}
121
122// DefaultConfig returns a Config with the values populated with the defaults
123// or specified environment variables.
124func DefaultConfig() *Config {
125	var err error
126	var migrateWarn bool
127	cfg := &Config{ErrorLog: log.Default()}
128	if err = env.Parse(cfg, env.Options{
129		Prefix: "SOFT_SERVE_",
130	}); err != nil {
131		log.Fatalln(err)
132	}
133	if cfg.Port != 0 {
134		log.Printf("warning: SOFT_SERVE_PORT is deprecated, use SOFT_SERVE_SSH_PORT instead.")
135		migrateWarn = true
136	}
137	if cfg.KeyPath != "" {
138		log.Printf("warning: SOFT_SERVE_KEY_PATH is deprecated, use SOFT_SERVE_DATA_PATH instead.")
139		migrateWarn = true
140	}
141	if cfg.ReposPath != "" {
142		log.Printf("warning: SOFT_SERVE_REPO_PATH is deprecated, use SOFT_SERVE_DATA_PATH instead.")
143		migrateWarn = true
144	}
145	if migrateWarn {
146		log.Printf("warning: please run `soft serve --migrate` to migrate your server and configuration.")
147	}
148	var db db.Store
149	switch cfg.Db.Driver {
150	case "sqlite":
151		if err := os.MkdirAll(filepath.Join(cfg.DataPath, "db"), 0755); err != nil {
152			log.Fatalln(err)
153		}
154		db, err = sqlite.New(filepath.Join(cfg.DataPath, "db", "soft-serve.db"))
155		if err != nil {
156			log.Fatalln(err)
157		}
158	}
159	return cfg.WithDB(db)
160}
161
162// DB returns the database for the configuration.
163func (c *Config) DB() db.Store {
164	return c.db
165}
166
167// WithCallbacks applies the given Callbacks to the configuration.
168func (c *Config) WithCallbacks(callbacks Callbacks) *Config {
169	c.Callbacks = callbacks
170	return c
171}
172
173// WithErrorLogger sets the error logger for the configuration.
174func (c *Config) WithErrorLogger(logger *log.Logger) *Config {
175	c.ErrorLog = logger
176	return c
177}
178
179// WithDB sets the database for the configuration.
180func (c *Config) WithDB(db db.Store) *Config {
181	c.db = db
182	return c
183}