File view with blame information shown in the left gutter beside each line.

barerepo / server / internal/config/config.go
211 lines · 6.2kb · master
log files threads runs releases config jump to file t
133728e barerepo 1mo
1
// Package config reads barerepo.toml, which holds deployment facts only. Chapter 14.
133728e barerepo 1mo
2
package config
133728e barerepo 1mo
3
133728e barerepo 1mo
4
import (
133728e barerepo 1mo
5
"fmt"
133728e barerepo 1mo
6
"net/url"
133728e barerepo 1mo
7
"os"
133728e barerepo 1mo
8
"path/filepath"
133728e barerepo 1mo
9
"strings"
133728e barerepo 1mo
10
133728e barerepo 1mo
11
"github.com/BurntSushi/toml"
133728e barerepo 1mo
12
)
133728e barerepo 1mo
13
133728e barerepo 1mo
14
const DefaultPath = "/etc/barerepo/barerepo.toml"
133728e barerepo 1mo
15
133728e barerepo 1mo
16
type Config struct {
133728e barerepo 1mo
17
Server Server `toml:"server"`
133728e barerepo 1mo
18
Database Database `toml:"database"`
133728e barerepo 1mo
19
Paths Paths `toml:"paths"`
133728e barerepo 1mo
20
Limits Limits `toml:"limits"`
133728e barerepo 1mo
21
Behavior Behavior `toml:"behavior"`
133728e barerepo 1mo
22
133728e barerepo 1mo
23
// Path is where this configuration was read from. Not a config key.
133728e barerepo 1mo
24
Path string `toml:"-"`
133728e barerepo 1mo
25
}
133728e barerepo 1mo
26
133728e barerepo 1mo
27
type Server struct {
133728e barerepo 1mo
28
Listen string `toml:"listen"`
133728e barerepo 1mo
29
ExternalURL string `toml:"external_url"`
133728e barerepo 1mo
30
// RawURL is a separate host for raw content, and empty means a download from the main one.
133728e barerepo 1mo
31
RawURL string `toml:"raw_url"`
133728e barerepo 1mo
32
SSHHost string `toml:"ssh_host"`
133728e barerepo 1mo
33
SSHPort int `toml:"ssh_port"`
133728e barerepo 1mo
34
// SSHUser is the account in a clone url, which another barerepo on the same host may already own.
133728e barerepo 1mo
35
SSHUser string `toml:"ssh_user"`
133728e barerepo 1mo
36
// GitIdentity signs commits the server makes, and empty derives it from the external url.
133728e barerepo 1mo
37
GitIdentity string `toml:"git_identity"`
133728e barerepo 1mo
38
}
133728e barerepo 1mo
39
133728e barerepo 1mo
40
// Database picks SQLite or PostgreSQL by URL scheme, and no feature exists on only one.
133728e barerepo 1mo
41
type Database struct {
133728e barerepo 1mo
42
URL string `toml:"url"`
133728e barerepo 1mo
43
}
133728e barerepo 1mo
44
133728e barerepo 1mo
45
type Paths struct {
133728e barerepo 1mo
46
Repos string `toml:"repos"`
133728e barerepo 1mo
47
Cache string `toml:"cache"`
133728e barerepo 1mo
48
Artifacts string `toml:"artifacts"`
133728e barerepo 1mo
49
// SSHDir holds the authorized_keys the server writes, and empty means it writes none.
133728e barerepo 1mo
50
SSHDir string `toml:"ssh_dir"`
133728e barerepo 1mo
51
}
133728e barerepo 1mo
52
133728e barerepo 1mo
53
type Limits struct {
133728e barerepo 1mo
54
// MaxBlobMB bounds one file, because turning LFS off without it does not hold.
133728e barerepo 1mo
55
MaxBlobMB int `toml:"max_blob_mb"`
133728e barerepo 1mo
56
// MaxPushMB is loose, because the push most likely to hit it is somebody's first import.
133728e barerepo 1mo
57
MaxPushMB int `toml:"max_push_mb"`
133728e barerepo 1mo
58
MaxOpenProposals int `toml:"max_open_proposals"`
133728e barerepo 1mo
59
SignupPerHourPerIP int `toml:"signup_per_hour_per_ip"`
133728e barerepo 1mo
60
// CommentPerHourPerThread is per account as well, since chapter 27 says comments are cheap.
133728e barerepo 1mo
61
CommentPerHourPerThread int `toml:"comment_per_hour_per_thread"`
133728e barerepo 1mo
62
ArtifactRetainDays int `toml:"artifact_retain_days"`
133728e barerepo 1mo
63
}
133728e barerepo 1mo
64
133728e barerepo 1mo
65
type Behavior struct {
133728e barerepo 1mo
66
AllowPushToCreate bool `toml:"allow_push_to_create"`
133728e barerepo 1mo
67
AllowLFS bool `toml:"allow_lfs"`
133728e barerepo 1mo
68
}
133728e barerepo 1mo
69
133728e barerepo 1mo
70
// Default is a working single-host install on SQLite with no config file. Chapter 41.7.
133728e barerepo 1mo
71
func Default() Config {
133728e barerepo 1mo
72
return Config{
133728e barerepo 1mo
73
Server: Server{
133728e barerepo 1mo
74
Listen: "127.0.0.1:3000",
133728e barerepo 1mo
75
ExternalURL: "http://127.0.0.1:3000",
133728e barerepo 1mo
76
RawURL: "",
133728e barerepo 1mo
77
SSHHost: "localhost",
133728e barerepo 1mo
78
SSHPort: 22,
133728e barerepo 1mo
79
SSHUser: "git",
133728e barerepo 1mo
80
},
133728e barerepo 1mo
81
Database: Database{URL: "sqlite:///var/lib/barerepo/forge.db"},
133728e barerepo 1mo
82
Paths: Paths{
133728e barerepo 1mo
83
Repos: "/var/lib/barerepo/repos",
133728e barerepo 1mo
84
Cache: "/var/lib/barerepo/cache",
133728e barerepo 1mo
85
Artifacts: "/var/lib/barerepo/artifacts",
133728e barerepo 1mo
86
},
133728e barerepo 1mo
87
Limits: Limits{
133728e barerepo 1mo
88
MaxBlobMB: 100,
133728e barerepo 1mo
89
MaxPushMB: 2048,
133728e barerepo 1mo
90
MaxOpenProposals: 10,
133728e barerepo 1mo
91
SignupPerHourPerIP: 5,
133728e barerepo 1mo
92
// Generous, because a reviewer working through a diff leaves a lot of comments at once.
133728e barerepo 1mo
93
CommentPerHourPerThread: 30,
133728e barerepo 1mo
94
ArtifactRetainDays: 90,
133728e barerepo 1mo
95
},
133728e barerepo 1mo
96
Behavior: Behavior{
133728e barerepo 1mo
97
AllowPushToCreate: true,
133728e barerepo 1mo
98
AllowLFS: false,
133728e barerepo 1mo
99
},
133728e barerepo 1mo
100
}
133728e barerepo 1mo
101
}
133728e barerepo 1mo
102
133728e barerepo 1mo
103
// Identity is who the server commits as, from config or from the host it answers on.
133728e barerepo 1mo
104
func (c Config) Identity() string {
133728e barerepo 1mo
105
if c.Server.GitIdentity != "" {
133728e barerepo 1mo
106
return c.Server.GitIdentity
133728e barerepo 1mo
107
}
133728e barerepo 1mo
108
user := c.Server.SSHUser
133728e barerepo 1mo
109
if user == "" {
133728e barerepo 1mo
110
user = "git"
133728e barerepo 1mo
111
}
133728e barerepo 1mo
112
host := c.Server.SSHHost
133728e barerepo 1mo
113
if host == "" {
133728e barerepo 1mo
114
if u, err := url.Parse(c.Server.ExternalURL); err == nil && u.Hostname() != "" {
133728e barerepo 1mo
115
host = u.Hostname()
133728e barerepo 1mo
116
}
133728e barerepo 1mo
117
}
133728e barerepo 1mo
118
if host == "" {
133728e barerepo 1mo
119
host = "localhost"
133728e barerepo 1mo
120
}
133728e barerepo 1mo
121
return user + "@" + host
133728e barerepo 1mo
122
}
133728e barerepo 1mo
123
133728e barerepo 1mo
124
// Load reads over the defaults, and a missing file is an operator who has decided nothing yet.
133728e barerepo 1mo
125
func Load(path string) (Config, error) {
133728e barerepo 1mo
126
if path == "" {
133728e barerepo 1mo
127
path = DefaultPath
133728e barerepo 1mo
128
}
133728e barerepo 1mo
129
cfg := Default()
133728e barerepo 1mo
130
cfg.Path = path
133728e barerepo 1mo
131
133728e barerepo 1mo
132
raw, err := os.ReadFile(path)
133728e barerepo 1mo
133
if os.IsNotExist(err) {
133728e barerepo 1mo
134
return cfg, cfg.check()
133728e barerepo 1mo
135
}
133728e barerepo 1mo
136
if err != nil {
133728e barerepo 1mo
137
return cfg, err
133728e barerepo 1mo
138
}
133728e barerepo 1mo
139
var md toml.MetaData
133728e barerepo 1mo
140
if md, err = toml.Decode(string(raw), &cfg); err != nil {
133728e barerepo 1mo
141
return cfg, fmt.Errorf("%s: %w", path, err)
133728e barerepo 1mo
142
}
133728e barerepo 1mo
143
// A silently ignored misspelling is how a limit turns out never to have applied. Say so.
133728e barerepo 1mo
144
if un := md.Undecoded(); len(un) > 0 {
133728e barerepo 1mo
145
keys := make([]string, len(un))
133728e barerepo 1mo
146
for i, k := range un {
133728e barerepo 1mo
147
keys[i] = k.String()
133728e barerepo 1mo
148
}
133728e barerepo 1mo
149
return cfg, fmt.Errorf("%s: unknown setting %s", path, strings.Join(keys, ", "))
133728e barerepo 1mo
150
}
133728e barerepo 1mo
151
return cfg, cfg.check()
133728e barerepo 1mo
152
}
133728e barerepo 1mo
153
133728e barerepo 1mo
154
// check refuses a configuration that would fail later in a confusing place.
133728e barerepo 1mo
155
func (c Config) check() error {
133728e barerepo 1mo
156
for name, p := range map[string]string{
133728e barerepo 1mo
157
"paths.repos": c.Paths.Repos,
133728e barerepo 1mo
158
"paths.cache": c.Paths.Cache,
133728e barerepo 1mo
159
"paths.artifacts": c.Paths.Artifacts,
133728e barerepo 1mo
160
} {
133728e barerepo 1mo
161
if p == "" {
133728e barerepo 1mo
162
return fmt.Errorf("%s: %s is empty", c.Path, name)
133728e barerepo 1mo
163
}
133728e barerepo 1mo
164
if !filepath.IsAbs(p) {
133728e barerepo 1mo
165
return fmt.Errorf("%s: %s must be an absolute path, got %q", c.Path, name, p)
133728e barerepo 1mo
166
}
133728e barerepo 1mo
167
}
133728e barerepo 1mo
168
if c.Server.Listen == "" {
133728e barerepo 1mo
169
return fmt.Errorf("%s: server.listen is empty", c.Path)
133728e barerepo 1mo
170
}
133728e barerepo 1mo
171
if c.Behavior.AllowLFS {
133728e barerepo 1mo
172
return fmt.Errorf("%s: behavior.allow_lfs is on and chapter 20.3 is not built, "+
133728e barerepo 1mo
173
"so nothing would serve it. leave it off", c.Path)
133728e barerepo 1mo
174
}
133728e barerepo 1mo
175
if _, err := c.DatabaseKind(); err != nil {
133728e barerepo 1mo
176
return fmt.Errorf("%s: %w", c.Path, err)
133728e barerepo 1mo
177
}
133728e barerepo 1mo
178
if c.Server.RawURL != "" {
133728e barerepo 1mo
179
u, err := url.Parse(c.Server.RawURL)
133728e barerepo 1mo
180
if err != nil || u.Host == "" || (u.Scheme != "http" && u.Scheme != "https") {
133728e barerepo 1mo
181
return fmt.Errorf("%s: server.raw_url must be a full http or https url", c.Path)
133728e barerepo 1mo
182
}
133728e barerepo 1mo
183
// A shared domain shares cookies, which is the whole thing chapter 42.3 avoids.
133728e barerepo 1mo
184
if ext, err := url.Parse(c.Server.ExternalURL); err == nil && ext.Host == u.Host {
133728e barerepo 1mo
185
return fmt.Errorf("%s: server.raw_url must be a different host from server.external_url", c.Path)
133728e barerepo 1mo
186
}
133728e barerepo 1mo
187
}
133728e barerepo 1mo
188
return nil
133728e barerepo 1mo
189
}
133728e barerepo 1mo
190
133728e barerepo 1mo
191
// Kind names a supported database.
133728e barerepo 1mo
192
type Kind string
133728e barerepo 1mo
193
133728e barerepo 1mo
194
const (
133728e barerepo 1mo
195
SQLite Kind = "sqlite"
133728e barerepo 1mo
196
Postgres Kind = "postgres"
133728e barerepo 1mo
197
)
133728e barerepo 1mo
198
133728e barerepo 1mo
199
// DatabaseKind reports which database database.url selects.
133728e barerepo 1mo
200
func (c Config) DatabaseKind() (Kind, error) {
133728e barerepo 1mo
201
switch {
133728e barerepo 1mo
202
case c.Database.URL == "":
133728e barerepo 1mo
203
return "", fmt.Errorf("database.url is empty")
133728e barerepo 1mo
204
case strings.HasPrefix(c.Database.URL, "sqlite:"):
133728e barerepo 1mo
205
return SQLite, nil
133728e barerepo 1mo
206
case strings.HasPrefix(c.Database.URL, "postgres:"), strings.HasPrefix(c.Database.URL, "postgresql:"):
133728e barerepo 1mo
207
return Postgres, nil
133728e barerepo 1mo
208
default:
133728e barerepo 1mo
209
return "", fmt.Errorf("database.url must start with sqlite: or postgres:, got %q", c.Database.URL)
133728e barerepo 1mo
210
}
133728e barerepo 1mo
211
}
history · rawbarerepo 0.1.0