@@ -18,35 +18,53 @@ import (
1818 "github.com/1f349/violet/servers/api"
1919 "github.com/1f349/violet/servers/conf"
2020 "github.com/1f349/violet/utils"
21+ "github.com/charmbracelet/log"
22+ "github.com/cloudflare/tableflip"
2123 "github.com/google/subcommands"
22- "github.com/mrmelon54/exit-reload"
2324 "github.com/prometheus/client_golang/prometheus"
2425 "github.com/prometheus/client_golang/prometheus/collectors"
2526 "io/fs"
2627 "net/http"
2728 "os"
29+ "os/signal"
2830 "path/filepath"
31+ "syscall"
32+ "time"
2933)
3034
3135type serveCmd struct {
3236 configPath string
33- cpuprofile string
37+ debugLog bool
38+ pidFile string
3439}
3540
3641func (s * serveCmd ) Name () string { return "serve" }
3742func (s * serveCmd ) Synopsis () string { return "Serve reverse proxy server" }
3843func (s * serveCmd ) SetFlags (f * flag.FlagSet ) {
3944 f .StringVar (& s .configPath , "conf" , "" , "/path/to/config.json : path to the config file" )
45+ f .BoolVar (& s .debugLog , "debug" , false , "enable debug logging" )
46+ f .StringVar (& s .pidFile , "pid-file" , "" , "path to pid file" )
4047}
4148func (s * serveCmd ) Usage () string {
42- return `serve [-conf <config file>]
49+ return `serve [-conf <config file>] [-debug] [-pid-file <pid file>]
4350 Serve reverse proxy server using information from config file
4451`
4552}
4653
4754func (s * serveCmd ) Execute (_ context.Context , _ * flag.FlagSet , _ ... interface {}) subcommands.ExitStatus {
55+ if s .debugLog {
56+ logger .Logger .SetLevel (log .DebugLevel )
57+ }
4858 logger .Logger .Info ("Starting..." )
4959
60+ upg , err := tableflip .New (tableflip.Options {
61+ PIDFile : s .pidFile ,
62+ })
63+ if err != nil {
64+ panic (err )
65+ }
66+ defer upg .Stop ()
67+
5068 if s .configPath == "" {
5169 logger .Logger .Info ("Error: config flag is missing" )
5270 return subcommands .ExitUsageError
@@ -71,13 +89,9 @@ func (s *serveCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{})
7189
7290 // working directory is the parent of the config file
7391 wd := filepath .Dir (s .configPath )
74- normalLoad (config , wd )
75- return subcommands .ExitSuccess
76- }
7792
78- func normalLoad (startUp startUpConfig , wd string ) {
7993 // the cert and key paths are useless in self-signed mode
80- if ! startUp .SelfSigned {
94+ if ! config .SelfSigned {
8195 // create path to cert dir
8296 err := os .MkdirAll (filepath .Join (wd , "certs" ), os .ModePerm )
8397 if err != nil {
@@ -92,11 +106,11 @@ func normalLoad(startUp startUpConfig, wd string) {
92106
93107 // errorPageDir stores an FS interface for accessing the error page directory
94108 var errorPageDir fs.FS
95- if startUp .ErrorPagePath != "" {
96- errorPageDir = os .DirFS (startUp .ErrorPagePath )
97- err := os .MkdirAll (startUp .ErrorPagePath , os .ModePerm )
109+ if config .ErrorPagePath != "" {
110+ errorPageDir = os .DirFS (config .ErrorPagePath )
111+ err := os .MkdirAll (config .ErrorPagePath , os .ModePerm )
98112 if err != nil {
99- logger .Logger .Fatal ("Failed to create error page" , "path" , startUp .ErrorPagePath )
113+ logger .Logger .Fatal ("Failed to create error page" , "path" , config .ErrorPagePath )
100114 }
101115 }
102116
@@ -123,75 +137,113 @@ func normalLoad(startUp startUpConfig, wd string) {
123137 )
124138
125139 ws := websocket .NewServer ()
126- allowedDomains := domains .New (db ) // load allowed domains
127- acmeChallenges := utils .NewAcmeChallenge () // load acme challenge store
128- allowedCerts := certs .New (certDir , keyDir , startUp .SelfSigned ) // load certificate manager
129- hybridTransport := proxy .NewHybridTransport (ws ) // load reverse proxy
130- dynamicFavicons := favicons .New (db , startUp .InkscapeCmd ) // load dynamic favicon provider
131- dynamicErrorPages := errorPages .New (errorPageDir ) // load dynamic error page provider
132- dynamicRouter := router .NewManager (db , hybridTransport ) // load dynamic router manager
140+ allowedDomains := domains .New (db ) // load allowed domains
141+ acmeChallenges := utils .NewAcmeChallenge () // load acme challenge store
142+ allowedCerts := certs .New (certDir , keyDir , config .SelfSigned ) // load certificate manager
143+ hybridTransport := proxy .NewHybridTransport (ws ) // load reverse proxy
144+ dynamicFavicons := favicons .New (db , config .InkscapeCmd ) // load dynamic favicon provider
145+ dynamicErrorPages := errorPages .New (errorPageDir ) // load dynamic error page provider
146+ dynamicRouter := router .NewManager (db , hybridTransport ) // load dynamic router manager
133147
134148 // struct containing config for the http servers
135149 srvConf := & conf.Conf {
136- ApiListen : startUp .Listen .Api ,
137- HttpListen : startUp .Listen .Http ,
138- HttpsListen : startUp .Listen .Https ,
139- RateLimit : startUp .RateLimit ,
140- DB : db ,
141- Domains : allowedDomains ,
142- Acme : acmeChallenges ,
143- Certs : allowedCerts ,
144- Favicons : dynamicFavicons ,
145- Signer : mJwtVerify ,
146- ErrorPages : dynamicErrorPages ,
147- Router : dynamicRouter ,
150+ RateLimit : config .RateLimit ,
151+ DB : db ,
152+ Domains : allowedDomains ,
153+ Acme : acmeChallenges ,
154+ Certs : allowedCerts ,
155+ Favicons : dynamicFavicons ,
156+ Signer : mJwtVerify ,
157+ ErrorPages : dynamicErrorPages ,
158+ Router : dynamicRouter ,
148159 }
149160
150161 // create the compilable list and run a first time compile
151162 allCompilables := utils.MultiCompilable {allowedDomains , allowedCerts , dynamicFavicons , dynamicErrorPages , dynamicRouter }
152163 allCompilables .Compile ()
153164
165+ _ , httpsPort , ok := utils .SplitDomainPort (config .Listen .Https , 443 )
166+ if ! ok {
167+ httpsPort = 443
168+ }
169+
154170 var srvApi , srvHttp , srvHttps * http.Server
155- if srvConf .ApiListen != "" {
171+ if config .Listen .Api != "" {
172+ // Listen must be called before Ready
173+ lnApi , err := upg .Listen ("tcp" , config .Listen .Api )
174+ if err != nil {
175+ logger .Logger .Fatal ("Listen failed" , "err" , err )
176+ }
156177 srvApi = api .NewApiServer (srvConf , allCompilables , promRegistry )
157178 srvApi .SetKeepAlivesEnabled (false )
158179 l := logger .Logger .With ("server" , "API" )
159- l .Info ("Starting server" , "addr" , srvApi . Addr )
160- go utils .RunBackgroundHttp (l , srvApi )
180+ l .Info ("Starting server" , "addr" , config . Listen . Api )
181+ go utils .RunBackgroundHttp (l , srvApi , lnApi )
161182 }
162- if srvConf .HttpListen != "" {
163- srvHttp = servers .NewHttpServer (srvConf , promRegistry )
183+ if config .Listen .Http != "" {
184+ // Listen must be called before Ready
185+ lnHttp , err := upg .Listen ("tcp" , config .Listen .Http )
186+ if err != nil {
187+ logger .Logger .Fatal ("Listen failed" , "err" , err )
188+ }
189+ srvHttp = servers .NewHttpServer (uint16 (httpsPort ), srvConf , promRegistry )
164190 srvHttp .SetKeepAlivesEnabled (false )
165191 l := logger .Logger .With ("server" , "HTTP" )
166- l .Info ("Starting server" , "addr" , srvHttp . Addr )
167- go utils .RunBackgroundHttp (l , srvHttp )
192+ l .Info ("Starting server" , "addr" , config . Listen . Http )
193+ go utils .RunBackgroundHttp (l , srvHttp , lnHttp )
168194 }
169- if srvConf .HttpsListen != "" {
195+ if config .Listen .Https != "" {
196+ // Listen must be called before Ready
197+ lnHttps , err := upg .Listen ("tcp" , config .Listen .Https )
198+ if err != nil {
199+ logger .Logger .Fatal ("Listen failed" , "err" , err )
200+ }
170201 srvHttps = servers .NewHttpsServer (srvConf , promRegistry )
171202 srvHttps .SetKeepAlivesEnabled (false )
172203 l := logger .Logger .With ("server" , "HTTPS" )
173- l .Info ("Starting server" , "addr" , srvHttps . Addr )
174- go utils .RunBackgroundHttps (l , srvHttps )
204+ l .Info ("Starting server" , "addr" , config . Listen . Https )
205+ go utils .RunBackgroundHttps (l , srvHttps , lnHttps )
175206 }
176207
177- exit_reload .ExitReload ("Violet" , func () {
178- allCompilables .Compile ()
179- }, func () {
180- // stop updating certificates
181- allowedCerts .Stop ()
208+ // Do an upgrade on SIGHUP
209+ go func () {
210+ sig := make (chan os.Signal , 1 )
211+ signal .Notify (sig , syscall .SIGHUP )
212+ for range sig {
213+ err := upg .Upgrade ()
214+ if err != nil {
215+ logger .Logger .Error ("Failed upgrade" , "err" , err )
216+ }
217+ }
218+ }()
182219
183- // close websockets first
184- ws .Shutdown ()
220+ logger .Logger .Info ("Ready" )
221+ if err := upg .Ready (); err != nil {
222+ panic (err )
223+ }
224+ <- upg .Exit ()
185225
186- // close http servers
187- if srvApi != nil {
188- _ = srvApi .Close ()
189- }
190- if srvHttp != nil {
191- _ = srvHttp .Close ()
192- }
193- if srvHttps != nil {
194- _ = srvHttps .Close ()
195- }
226+ time .AfterFunc (30 * time .Second , func () {
227+ logger .Logger .Warn ("Graceful shutdown timed out" )
228+ os .Exit (1 )
196229 })
230+
231+ // stop updating certificates
232+ allowedCerts .Stop ()
233+
234+ // close websockets first
235+ ws .Shutdown ()
236+
237+ // close http servers
238+ if srvApi != nil {
239+ _ = srvApi .Close ()
240+ }
241+ if srvHttp != nil {
242+ _ = srvHttp .Close ()
243+ }
244+ if srvHttps != nil {
245+ _ = srvHttps .Close ()
246+ }
247+
248+ return subcommands .ExitSuccess
197249}
0 commit comments