-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcheck.go
More file actions
250 lines (230 loc) · 7.04 KB
/
check.go
File metadata and controls
250 lines (230 loc) · 7.04 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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
package config
// Package file check.go contains the sanity check functions for the configuration values.
import (
"context"
"database/sql"
"errors"
"fmt"
"os"
"path/filepath"
"github.com/Defacto2/helper"
"github.com/Defacto2/server/internal/postgres/models"
"github.com/Defacto2/server/model"
"github.com/volatiletech/sqlboiler/v4/queries/qm"
"go.uber.org/zap"
)
const (
PortMax = 65534 // PortMax is the highest valid port number.
PortSys = 1024 // PortSys is the lowest valid port number that does not require system access.
DirWriteWriteBlock = 0o770 // Directory permissions.
)
var (
ErrPortMax = fmt.Errorf("http port value must be between 1-%d", PortMax)
ErrPortSys = fmt.Errorf("http port values between 1-%d require system access", PortSys)
ErrDir = errors.New("the directory path is not set")
ErrDir404 = errors.New("the directory path does not exist")
ErrDirIs = errors.New("the directory path points to the file")
ErrDirRead = errors.New("the directory path could not be read")
ErrDirFew = errors.New("the directory path contains only a few items")
ErrNoOAuth2 = errors.New("the production server requires a google, oauth2 client id to allow admin logins")
ErrNoAccounts = errors.New("the production server has no google oauth2 user accounts to allow admin logins")
ErrZap = errors.New("the zap logger instance is nil")
)
// Checks runs a number of sanity checks for the environment variable configurations.
func (c *Config) Checks(logger *zap.SugaredLogger) error {
if logger == nil {
return ErrZap
}
c.httpPort(logger)
c.tlsPort(logger)
c.production(logger)
// Check the download, preview and thumbnail directories.
if err := CheckDir(c.AbsDownload, "downloads"); err != nil {
s := helper.Capitalize(err.Error())
logger.Error(s)
}
if err := CheckDir(c.AbsPreview, "previews"); err != nil {
s := helper.Capitalize(err.Error())
logger.Error(s)
}
if err := CheckDir(c.AbsThumbnail, "thumbnails"); err != nil {
s := helper.Capitalize(err.Error())
logger.Error(s)
}
if err := CheckDir(c.AbsOrphaned, "orphaned"); err != nil {
s := helper.Capitalize(err.Error())
logger.Error(s)
}
if err := CheckDir(c.AbsExtra, "extra"); err != nil {
s := helper.Capitalize(err.Error())
logger.Error(s)
}
// Reminds for the optional configuration values.
if c.NoCrawl {
logger.Warn("Disallow search engine crawling is enabled")
}
if c.ReadOnly {
logger.Warn("The server is running in read-only mode, edits to the database are not allowed")
}
return c.SetupLogDir(logger)
}
// httpPort returns an error if the HTTP port is invalid.
func (c Config) httpPort(logger *zap.SugaredLogger) {
if c.HTTPPort == 0 {
return
}
if err := Validate(c.HTTPPort); err != nil {
switch {
case errors.Is(err, ErrPortMax):
logger.Fatalf("The server could not use the HTTP port %d, %s.",
c.HTTPPort, err)
case errors.Is(err, ErrPortSys):
logger.Infof("The server HTTP port %d, %s.",
c.HTTPPort, err)
}
}
}
// tlsPort returns an error if the TLS port is invalid.
func (c Config) tlsPort(logger *zap.SugaredLogger) {
if c.TLSPort == 0 {
return
}
if err := Validate(c.TLSPort); err != nil {
switch {
case errors.Is(err, ErrPortMax):
logger.Fatalf("The server could not use the HTTPS port %d, %s.",
c.TLSPort, err)
case errors.Is(err, ErrPortSys):
logger.Infof("The server HTTPS port %d, %s.",
c.TLSPort, err)
}
}
}
// The production mode checks when not in read-only mode. It
// expects the server to be configured with OAuth2 and Google IDs.
// The server should be running over HTTPS and not unencrypted HTTP.
func (c Config) production(logger *zap.SugaredLogger) {
if !c.ProdMode || c.ReadOnly {
return
}
if c.GoogleClientID == "" {
s := helper.Capitalize(ErrNoOAuth2.Error())
logger.Warn(s)
}
if c.GoogleIDs == "" && len(c.GoogleAccounts) == 0 {
s := helper.Capitalize(ErrNoAccounts.Error())
logger.Warn(s)
}
if c.SessionMaxAge == 0 {
logger.Warn("A signed in client session lasts forever, this is a security risk")
}
}
// LogStore determines the local storage path for all log files created by this web application.
func (c *Config) LogStore() error {
logs := c.AbsLog
if logs == "" {
dir, err := os.UserConfigDir()
if err != nil {
return fmt.Errorf("os.UserConfigDir: %w", err)
}
logs = filepath.Join(dir, ConfigDir)
}
if logsExists := helper.Stat(logs); !logsExists {
if err := os.MkdirAll(logs, DirWriteWriteBlock); err != nil {
return fmt.Errorf("%w: %s", err, logs)
}
}
c.AbsLog = logs
return nil
}
// SetupLogDir runs checks against the configured log directory.
// If no log directory is configured, a default directory is used.
// Problems will either log warnings or fatal errors.
func (c *Config) SetupLogDir(logger *zap.SugaredLogger) error {
if logger == nil {
return ErrZap
}
if c.AbsLog == "" {
if err := c.LogStore(); err != nil {
return fmt.Errorf("%w: %w", ErrLog, err)
}
}
dir, err := os.Stat(c.AbsLog)
if os.IsNotExist(err) {
return fmt.Errorf("log directory %w: %s", ErrDirNotExist, c.AbsLog)
}
if !dir.IsDir() {
return fmt.Errorf("log directory %w: %s", ErrNotDir, dir.Name())
}
empty := filepath.Join(c.AbsLog, ".defacto2_touch_test")
if _, err := os.Stat(empty); os.IsNotExist(err) {
f, err := os.Create(empty)
if err != nil {
return fmt.Errorf("log directory %w: %w", ErrTouch, err)
}
defer func(f *os.File) {
f.Close()
if err := os.Remove(empty); err != nil {
logger.Warnf("Could not remove the empty test file in the log directory path: %s: %s", err, empty)
return
}
}(f)
return nil
}
if err := os.Remove(empty); err != nil {
logger.Warnf("Could not remove the empty test file in the log directory path: %s: %s", err, empty)
}
return nil
}
// CheckDir runs checks against the named directory,
// including whether it exists, is a directory, and contains a minimum number of files.
// Problems will either log warnings or fatal errors.
func CheckDir(name, desc string) error {
if name == "" {
return fmt.Errorf("%w: %s", ErrDir, desc)
}
dir, err := os.Stat(name)
if os.IsNotExist(err) {
return fmt.Errorf("%w, %s: %s", ErrDir404, desc, name)
}
if !dir.IsDir() {
return fmt.Errorf("%w, %s: %s", ErrDirIs, desc, dir.Name())
}
return nil
}
// RecordCount returns the number of records in the database.
func RecordCount(ctx context.Context, db *sql.DB) int {
if db == nil {
return 0
}
fs, err := models.Files(qm.Where(model.ClauseNoSoftDel)).Count(ctx, db)
if err != nil {
return 0
}
return int(fs)
}
// SanityTmpDir is used to print the temporary directory and its disk usage.
func SanityTmpDir() {
tmpdir := helper.TmpDir()
du, err := helper.DiskUsage(tmpdir)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
hdu := helper.ByteCountFloat(du)
fmt.Fprintf(os.Stdout, "Temporary directory using, %s: %s\n", hdu, tmpdir)
}
// Validate returns an error if the HTTP or TLS port is invalid.
func Validate(port uint) error {
const disabled = 0
if port == disabled {
return nil
}
if port > PortMax {
return ErrPortMax
}
if port <= PortSys {
return ErrPortSys
}
return nil
}