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