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}