-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.go
More file actions
192 lines (176 loc) · 5.45 KB
/
server.go
File metadata and controls
192 lines (176 loc) · 5.45 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
package main
/*
Package main is the entry point for the Defacto2 server application.
Use the Task runner / build tool (https://taskfile.dev) to build or run the source code.
$ task --list
Repository: https://github.com/Defacto2/server
Website: https://defacto2.net
License:
© Defacto2, 2024
*/
import (
"context"
"database/sql"
"embed"
"errors"
"fmt"
"io"
_ "net/http/pprof" // pprof is used for profiling and can be commented out.
"os"
"runtime"
"slices"
"strings"
"github.com/Defacto2/helper"
"github.com/Defacto2/server/flags"
"github.com/Defacto2/server/handler"
"github.com/Defacto2/server/internal/config"
"github.com/Defacto2/server/internal/postgres"
"github.com/Defacto2/server/internal/zaplog"
"github.com/caarlos0/env/v11"
_ "github.com/lib/pq"
"go.uber.org/zap"
)
var (
//go:embed public/text/defacto2.txt
brand []byte
//go:embed public/**/*
public embed.FS
//go:embed view/**/*
view embed.FS
version string // version is generated by the GoReleaser ldflags.
)
var ErrLog = errors.New("cannot save logs")
// Main is the entry point for the application.
// By default the web server runs when no arguments are provided,
// otherwise, the command-line arguments are parsed and the application exits.
func main() {
const exit = 0
// initialize a temporary logger, get and print the environment variable configurations.
logger, configs := environmentVars()
if exitCode := parseFlags(logger, configs); exitCode >= exit {
os.Exit(exitCode)
}
var w io.Writer = os.Stdout
if configs.Quiet {
w = io.Discard
}
fmt.Fprintf(w, "%s\n", configs)
// connect to the database and perform some repairs and sanity checks.
// if the database is cannot connect, the web server will continue.
db, err := postgres.Open()
if err != nil {
logger.Errorf("main could not initialize the database data: %s", err)
}
defer db.Close()
var database postgres.Version
if err := database.Query(db); err != nil {
logger.Errorf("postgres version query: %w", err)
}
config.SanityTmpDir()
fmt.Fprintln(w)
// start the web server and the sugared logger.
ctx := context.Background()
website := newInstance(ctx, db, configs)
logger = serverLog(configs, website.RecordCount)
router := website.Controller(db, logger)
website.Info(logger, w)
if err := website.Start(router, logger, configs); err != nil {
logger.Fatalf("%s: please check the environment variables", err)
}
go func() {
// get the owner and group of the current process and print them to the console.
groups, usr, err := helper.Owner()
if err != nil {
logger.Errorf("owner in main: %s", err)
}
clean := slices.DeleteFunc(groups, func(e string) bool {
return e == ""
})
fmt.Fprintf(w, "Running as %s for the groups, %s.\n",
usr, strings.Join(clean, ","))
// get the local IP addresses and print them to the console.
localIPs, err := configs.Addresses()
if err != nil {
logger.Errorf("configs addresses in main: %s", err)
}
fmt.Fprintf(w, "%s\n", localIPs)
}()
// shutdown the web server after a signal is received.
website.ShutdownHTTP(router, logger)
}
// environmentVars is used to parse the environment variables and set the Go runtime.
// Defaults are used if the environment variables are not set.
func environmentVars() (*zap.SugaredLogger, config.Config) {
logger := zaplog.Status().Sugar()
configs := config.Config{
Compression: true,
DatabaseURL: postgres.DefaultURL,
HTTPPort: config.HTTPPort,
ProdMode: true,
ReadOnly: true,
SessionMaxAge: config.SessionHours,
}
if err := env.Parse(&configs); err != nil {
logger.Fatalf("could not parse the environment variable, it probably contains an invalid value: %s", err)
}
configs.Override()
if i := configs.MaxProcs; i > 0 {
runtime.GOMAXPROCS(int(i))
}
return logger, configs
}
// newInstance is used to create the server controller instance.
func newInstance(ctx context.Context, db *sql.DB, configs config.Config) handler.Configuration {
c := handler.Configuration{
Brand: brand,
Environment: configs,
Public: public,
Version: version,
View: view,
}
if c.Version == "" {
c.Version = flags.Commit("")
}
if ctx != nil && db != nil {
c.RecordCount = config.RecordCount(ctx, db)
}
return c
}
// parseFlags is used to parse the commandline arguments.
// If an error is returned, the application will exit with the error code.
// Otherwise, a negative value is returned to indicate the application should continue.
func parseFlags(logger *zap.SugaredLogger, configs config.Config) int {
if logger == nil {
return -1
}
code, err := flags.Run(version, &configs)
if err != nil {
logger.Errorf("run command, parse flags: %s", err)
return int(code)
}
useExitCode := code >= flags.ExitOK
if useExitCode {
return int(code)
}
return -1
}
// serverLog is used to setup the logger for the server and print the startup message.
func serverLog(configs config.Config, count int) *zap.SugaredLogger {
logger := zaplog.Timestamp().Sugar()
const welcome = "Welcome to the Defacto2 web application"
switch {
case count == 0:
logger.Error(welcome + " with no database records")
case config.MinimumFiles > count:
logger.Warnf(welcome+" with only %d records, expecting at least %d+", count, config.MinimumFiles)
default:
logger.Infof(welcome+" containing %d records", count)
}
if configs.ProdMode {
if err := configs.LogStore(); err != nil {
logger.Fatalf("%w using server log: %s", ErrLog, err)
}
logger = zaplog.Store(configs.AbsLog).Sugar()
}
return logger
}