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
103	db db.Store
104}
105
106// RepoPath returns the path to the repositories.
107func (c *Config) RepoPath() string {
108	return filepath.Join(c.DataPath, "repos")
109}
110
111// SSHPath returns the path to the SSH directory.
112func (c *Config) SSHPath() string {
113	return filepath.Join(c.DataPath, "ssh")
114}
115
116// PrivateKeyPath returns the path to the SSH key.
117func (c *Config) PrivateKeyPath() string {
118	return filepath.Join(c.SSHPath(), "soft_serve")
119}
120
121// DefaultConfig returns a Config with the values populated with the defaults
122// or specified environment variables.
123func DefaultConfig() *Config {
124	var err error
125	var migrateWarn bool
126	var cfg Config
127	if err = env.Parse(&cfg, env.Options{
128		Prefix: "SOFT_SERVE_",
129	}); err != nil {
130		log.Fatalln(err)
131	}
132	if cfg.Port != 0 {
133		log.Printf("warning: SOFT_SERVE_PORT is deprecated, use SOFT_SERVE_SSH_PORT instead.")
134		migrateWarn = true
135	}
136	if cfg.KeyPath != "" {
137		log.Printf("warning: SOFT_SERVE_KEY_PATH is deprecated, use SOFT_SERVE_DATA_PATH instead.")
138		migrateWarn = true
139	}
140	if cfg.ReposPath != "" {
141		log.Printf("warning: SOFT_SERVE_REPO_PATH is deprecated, use SOFT_SERVE_DATA_PATH instead.")
142		migrateWarn = true
143	}
144	if migrateWarn {
145		log.Printf("warning: please run `soft serve --migrate` to migrate your server and configuration.")
146	}
147	var db db.Store
148	switch cfg.Db.Driver {
149	case "sqlite":
150		if err := os.MkdirAll(filepath.Join(cfg.DataPath, "db"), 0755); err != nil {
151			log.Fatalln(err)
152		}
153		db, err = sqlite.New(filepath.Join(cfg.DataPath, "db", "soft-serve.db"))
154		if err != nil {
155			log.Fatalln(err)
156		}
157	}
158	return cfg.WithDB(db)
159}
160
161// DB returns the database for the configuration.
162func (c *Config) DB() db.Store {
163	return c.db
164}
165
166// WithCallbacks applies the given Callbacks to the configuration.
167func (c *Config) WithCallbacks(callbacks Callbacks) *Config {
168	c.Callbacks = callbacks
169	return c
170}
171
172// WithDB sets the database for the configuration.
173func (c *Config) WithDB(db db.Store) *Config {
174	c.db = db
175	return c
176}