-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig.go
More file actions
159 lines (131 loc) · 6.76 KB
/
config.go
File metadata and controls
159 lines (131 loc) · 6.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package peerdb
import (
"github.com/alecthomas/kong"
mapset "github.com/deckarep/golang-set/v2"
"gitlab.com/tozd/go/cli"
"gitlab.com/tozd/go/errors"
"gitlab.com/tozd/go/zerolog"
"gitlab.com/tozd/waf"
)
const (
// DefaultListen is the default TCP listen address.
DefaultListen = ":8080"
// DefaultProxyTo is the default URL to proxy to during development.
DefaultProxyTo = "http://localhost:5173"
// DefaultElastic is the default Elasticsearch URL.
DefaultElastic = "http://127.0.0.1:9200"
// DefaultSchema is the default database schema name.
DefaultSchema = "peerdb"
// DefaultIndex is the default Elasticsearch index name.
DefaultIndex = "peerdb"
// DefaultShards is the default number of Elasticsearch shards.
DefaultShards = "10"
// DefaultTitle is the default application title.
DefaultTitle = "PeerDB"
)
// PostgresConfig contains configuration for PostgreSQL database connection.
//
//nolint:lll
type PostgresConfig struct {
URL kong.FileContentFlag ` env:"URL_PATH" help:"File with PostgreSQL database URL." placeholder:"PATH" required:"" short:"d" yaml:"database"`
Schema string `default:"${defaultSchema}" help:"Name of PostgreSQL schema to use when sites are not configured." placeholder:"NAME" yaml:"schema"`
}
// ElasticConfig contains configuration for ElasticSearch connection.
type ElasticConfig struct {
URL string `default:"${defaultElastic}" help:"URL of the ElasticSearch instance." placeholder:"URL" short:"e" yaml:"elastic"`
Index string `default:"${defaultIndex}" help:"Name of ElasticSearch index to use when sites are not configured." placeholder:"NAME" yaml:"index"`
Shards int `default:"${defaultShards}" help:"Number of ElasticSearch shards when initializing indices." placeholder:"NUM" yaml:"shards"`
}
// Globals describes top-level (global) flags.
type Globals struct {
zerolog.LoggingConfig `yaml:",inline"`
Version kong.VersionFlag `help:"Show program's version and exit." short:"V" yaml:"-"`
Config cli.ConfigFlag `help:"Load configuration from a JSON or YAML file." name:"config" placeholder:"PATH" short:"c" yaml:"-"`
Postgres PostgresConfig `embed:"" envprefix:"POSTGRES_" prefix:"postgres." yaml:"postgres"`
Elastic ElasticConfig `embed:"" envprefix:"ELASTIC_" prefix:"elastic." yaml:"elastic"`
Sites []Site `help:"Site configuration as JSON or YAML. Can be provided multiple times." name:"site" placeholder:"SITE" sep:"none" short:"s" yaml:"sites"`
}
// Validate validates the global configuration.
func (g *Globals) Validate() error {
domains := mapset.NewThreadUnsafeSet[string]()
for i, site := range g.Sites {
// This is not validated when Site is not populated by Kong.
if site.Domain == "" {
errE := errors.New("domain is required for site")
errors.Details(errE)["index"] = i
return errE
}
// To make sure validation is called.
err := site.Validate()
if err != nil {
return errors.WithStack(err)
}
// We cannot use kong to set these defaults, so we do it here.
if site.Index == "" {
site.Index = DefaultIndex
}
if site.Title == "" {
site.Title = DefaultTitle
}
if !domains.Add(site.Domain) {
errE := errors.New("duplicate site for domain")
errors.Details(errE)["domain"] = site.Domain
return errE
}
// Site might have been changed, so we assign it back.
g.Sites[i] = site
}
return nil
}
// Config provides configuration.
// It is used as configuration for Kong command-line parser as well.
type Config struct {
Globals `yaml:"globals"`
Serve ServeCommand `cmd:"" default:"withargs" help:"Run HTTP server. Default command." yaml:"serve"`
Populate PopulateCommand `cmd:"" help:"Populate database with documents." yaml:"populate"`
DB DBCommand `cmd:"" help:"Manage database." yaml:"db"`
}
// ServeCommand contains configuration for the serve command.
//
//nolint:lll
type ServeCommand struct {
Server waf.Server[*Site] `embed:"" yaml:",inline"`
Username string ` help:"Require authentication to access all sites. Its username." yaml:"username"`
Password kong.FileContentFlag `env:"PASSWORD_PATH" help:"Require authentication to access all sites. Its password." placeholder:"PATH" yaml:"password"`
Domain string ` group:"Let's Encrypt:" help:"Domain name to request for Let's Encrypt's certificate when sites are not configured." name:"tls.domain" placeholder:"STRING" yaml:"domain"`
Title string `default:"${defaultTitle}" help:"Title to be shown to the users when sites are not configured." placeholder:"STRING" short:"T" yaml:"title"`
}
// Validate validates the serve command configuration.
func (c *ServeCommand) Validate() error {
// We have to call Validate on kong-embedded structs ourselves.
// See: https://github.com/alecthomas/kong/issues/90
err := c.Server.HTTPS.Validate()
if err != nil {
return errors.WithStack(err)
}
if c.Domain != "" && c.Server.HTTPS.LetsEncryptCache == "" {
return errors.New("Let's Encrypt's cache directory is required for Let's Encrypt's certificate")
}
if (c.Username != "" && c.Password == nil) || (c.Username == "" && c.Password != nil) {
return errors.New("both username and password have to be set to require authentication, or neither")
}
return nil
}
// PopulateCommand contains configuration for the populate command.
type PopulateCommand struct {
SaveDir string `help:"Save intermediate structs as files into a directory." name:"save" placeholder:"DIR" short:"S" type:"path" yaml:"saveDir"`
OutputDir string `help:"Save documents as files into a directory." name:"output" placeholder:"DIR" short:"O" type:"path" yaml:"outputDir"`
DryRun bool `help:"Dry run. Do everything, but insert documents into the database." yaml:"dryRun"`
}
// DBWaitCommand waits for pending indexing to complete.
type DBWaitCommand struct{}
// DBReindexCommand forces a full reindex of all documents.
type DBReindexCommand struct{}
// DBWipeCommand drops PostgreSQL schemas and deletes ElasticSearch indices for all sites.
type DBWipeCommand struct{}
// DBCommand contains sub-commands for managing database.
type DBCommand struct {
Wait DBWaitCommand `cmd:"" help:"Wait for pending indexing to complete and exit." yaml:"wait"`
Reindex DBReindexCommand `cmd:"" help:"Force full reindex of all documents." yaml:"reindex"`
Wipe DBWipeCommand `cmd:"" help:"Wipe PostgreSQL schemas and ElasticSearch indices." yaml:"wipe"`
}