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}