server.go (10749B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package server 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 "net/http" 25 "os" 26 "os/signal" 27 "syscall" 28 29 "github.com/gin-gonic/gin" 30 "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action" 31 "github.com/superseriousbusiness/gotosocial/internal/api" 32 apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" 33 "github.com/superseriousbusiness/gotosocial/internal/gtserror" 34 "github.com/superseriousbusiness/gotosocial/internal/middleware" 35 tlprocessor "github.com/superseriousbusiness/gotosocial/internal/processing/timeline" 36 "github.com/superseriousbusiness/gotosocial/internal/timeline" 37 "github.com/superseriousbusiness/gotosocial/internal/tracing" 38 "github.com/superseriousbusiness/gotosocial/internal/visibility" 39 "go.uber.org/automaxprocs/maxprocs" 40 41 "github.com/superseriousbusiness/gotosocial/internal/config" 42 "github.com/superseriousbusiness/gotosocial/internal/db/bundb" 43 "github.com/superseriousbusiness/gotosocial/internal/email" 44 "github.com/superseriousbusiness/gotosocial/internal/federation" 45 "github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb" 46 "github.com/superseriousbusiness/gotosocial/internal/gotosocial" 47 "github.com/superseriousbusiness/gotosocial/internal/httpclient" 48 "github.com/superseriousbusiness/gotosocial/internal/log" 49 "github.com/superseriousbusiness/gotosocial/internal/media" 50 "github.com/superseriousbusiness/gotosocial/internal/oauth" 51 "github.com/superseriousbusiness/gotosocial/internal/oidc" 52 "github.com/superseriousbusiness/gotosocial/internal/processing" 53 "github.com/superseriousbusiness/gotosocial/internal/router" 54 "github.com/superseriousbusiness/gotosocial/internal/state" 55 gtsstorage "github.com/superseriousbusiness/gotosocial/internal/storage" 56 "github.com/superseriousbusiness/gotosocial/internal/transport" 57 "github.com/superseriousbusiness/gotosocial/internal/typeutils" 58 "github.com/superseriousbusiness/gotosocial/internal/web" 59 60 // Inherit memory limit if set from cgroup 61 _ "github.com/KimMachineGun/automemlimit" 62 ) 63 64 // Start creates and starts a gotosocial server 65 var Start action.GTSAction = func(ctx context.Context) error { 66 if _, err := maxprocs.Set(maxprocs.Logger(nil)); err != nil { 67 log.Infof(ctx, "could not set CPU limits from cgroup: %s", err) 68 } 69 70 var state state.State 71 72 // Initialize caches 73 state.Caches.Init() 74 state.Caches.Start() 75 defer state.Caches.Stop() 76 77 // Initialize Tracing 78 if err := tracing.Initialize(); err != nil { 79 return fmt.Errorf("error initializing tracing: %w", err) 80 } 81 82 // Open connection to the database 83 dbService, err := bundb.NewBunDBService(ctx, &state) 84 if err != nil { 85 return fmt.Errorf("error creating dbservice: %s", err) 86 } 87 88 // Set the state DB connection 89 state.DB = dbService 90 91 if err := dbService.CreateInstanceAccount(ctx); err != nil { 92 return fmt.Errorf("error creating instance account: %s", err) 93 } 94 95 if err := dbService.CreateInstanceInstance(ctx); err != nil { 96 return fmt.Errorf("error creating instance instance: %s", err) 97 } 98 99 // Open the storage backend 100 storage, err := gtsstorage.AutoConfig() 101 if err != nil { 102 return fmt.Errorf("error creating storage backend: %w", err) 103 } 104 105 // Set the state storage driver 106 state.Storage = storage 107 108 // Build HTTP client (TODO: add configurables here) 109 client := httpclient.New(httpclient.Config{}) 110 111 // Initialize workers. 112 state.Workers.Start() 113 defer state.Workers.Stop() 114 115 // Build handlers used in later initializations. 116 mediaManager := media.NewManager(&state) 117 oauthServer := oauth.New(ctx, dbService) 118 typeConverter := typeutils.NewConverter(dbService) 119 filter := visibility.NewFilter(&state) 120 federatingDB := federatingdb.New(&state, typeConverter) 121 transportController := transport.NewController(&state, federatingDB, &federation.Clock{}, client) 122 federator := federation.NewFederator(&state, federatingDB, transportController, typeConverter, mediaManager) 123 124 // Decide whether to create a noop email 125 // sender (won't send emails) or a real one. 126 var emailSender email.Sender 127 if smtpHost := config.GetSMTPHost(); smtpHost != "" { 128 // Host is defined; create a proper sender. 129 emailSender, err = email.NewSender() 130 if err != nil { 131 return fmt.Errorf("error creating email sender: %s", err) 132 } 133 } else { 134 // No host is defined; create a noop sender. 135 emailSender, err = email.NewNoopSender(nil) 136 if err != nil { 137 return fmt.Errorf("error creating noop email sender: %s", err) 138 } 139 } 140 141 // Initialize timelines. 142 state.Timelines.Home = timeline.NewManager( 143 tlprocessor.HomeTimelineGrab(&state), 144 tlprocessor.HomeTimelineFilter(&state, filter), 145 tlprocessor.HomeTimelineStatusPrepare(&state, typeConverter), 146 tlprocessor.SkipInsert(), 147 ) 148 if err := state.Timelines.Home.Start(); err != nil { 149 return fmt.Errorf("error starting home timeline: %s", err) 150 } 151 152 state.Timelines.List = timeline.NewManager( 153 tlprocessor.ListTimelineGrab(&state), 154 tlprocessor.ListTimelineFilter(&state, filter), 155 tlprocessor.ListTimelineStatusPrepare(&state, typeConverter), 156 tlprocessor.SkipInsert(), 157 ) 158 if err := state.Timelines.List.Start(); err != nil { 159 return fmt.Errorf("error starting list timeline: %s", err) 160 } 161 162 // Create the processor using all the other services we've created so far. 163 processor := processing.NewProcessor(typeConverter, federator, oauthServer, mediaManager, &state, emailSender) 164 165 // Set state client / federator worker enqueue functions 166 state.Workers.EnqueueClientAPI = processor.EnqueueClientAPI 167 state.Workers.EnqueueFederator = processor.EnqueueFederator 168 169 /* 170 HTTP router initialization 171 */ 172 173 router, err := router.New(ctx) 174 if err != nil { 175 return fmt.Errorf("error creating router: %s", err) 176 } 177 178 middlewares := []gin.HandlerFunc{ 179 middleware.AddRequestID(config.GetRequestIDHeader()), // requestID middleware must run before tracing 180 } 181 if config.GetTracingEnabled() { 182 middlewares = append(middlewares, tracing.InstrumentGin()) 183 } 184 middlewares = append(middlewares, []gin.HandlerFunc{ 185 // note: hooks adding ctx fields must be ABOVE 186 // the logger, otherwise won't be accessible. 187 middleware.Logger(config.GetLogClientIP()), 188 middleware.UserAgent(), 189 middleware.CORS(), 190 middleware.ExtraHeaders(), 191 }...) 192 193 // attach global middlewares which are used for every request 194 router.AttachGlobalMiddleware(middlewares...) 195 196 // attach global no route / 404 handler to the router 197 router.AttachNoRouteHandler(func(c *gin.Context) { 198 apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(errors.New(http.StatusText(http.StatusNotFound))), processor.InstanceGetV1) 199 }) 200 201 // build router modules 202 var idp oidc.IDP 203 if config.GetOIDCEnabled() { 204 idp, err = oidc.NewIDP(ctx) 205 if err != nil { 206 return fmt.Errorf("error creating oidc idp: %w", err) 207 } 208 } 209 210 routerSession, err := dbService.GetSession(ctx) 211 if err != nil { 212 return fmt.Errorf("error retrieving router session for session middleware: %w", err) 213 } 214 215 sessionName, err := middleware.SessionName() 216 if err != nil { 217 return fmt.Errorf("error generating session name for session middleware: %w", err) 218 } 219 220 var ( 221 authModule = api.NewAuth(dbService, processor, idp, routerSession, sessionName) // auth/oauth paths 222 clientModule = api.NewClient(dbService, processor) // api client endpoints 223 fileserverModule = api.NewFileserver(processor) // fileserver endpoints 224 wellKnownModule = api.NewWellKnown(processor) // .well-known endpoints 225 nodeInfoModule = api.NewNodeInfo(processor) // nodeinfo endpoint 226 activityPubModule = api.NewActivityPub(dbService, processor) // ActivityPub endpoints 227 webModule = web.New(dbService, processor) // web pages + user profiles + settings panels etc 228 ) 229 230 // create required middleware 231 // rate limiting 232 limit := config.GetAdvancedRateLimitRequests() 233 clLimit := middleware.RateLimit(limit) // client api 234 s2sLimit := middleware.RateLimit(limit) // server-to-server (AP) 235 fsLimit := middleware.RateLimit(limit) // fileserver / web templates 236 237 // throttling 238 cpuMultiplier := config.GetAdvancedThrottlingMultiplier() 239 retryAfter := config.GetAdvancedThrottlingRetryAfter() 240 clThrottle := middleware.Throttle(cpuMultiplier, retryAfter) // client api 241 s2sThrottle := middleware.Throttle(cpuMultiplier, retryAfter) // server-to-server (AP) 242 fsThrottle := middleware.Throttle(cpuMultiplier, retryAfter) // fileserver / web templates 243 pkThrottle := middleware.Throttle(cpuMultiplier, retryAfter) // throttle public key endpoint separately 244 245 gzip := middleware.Gzip() // applied to all except fileserver 246 247 // these should be routed in order; 248 // apply throttling *after* rate limiting 249 authModule.Route(router, clLimit, clThrottle, gzip) 250 clientModule.Route(router, clLimit, clThrottle, gzip) 251 fileserverModule.Route(router, fsLimit, fsThrottle) 252 wellKnownModule.Route(router, gzip, s2sLimit, s2sThrottle) 253 nodeInfoModule.Route(router, s2sLimit, s2sThrottle, gzip) 254 activityPubModule.Route(router, s2sLimit, s2sThrottle, gzip) 255 activityPubModule.RoutePublicKey(router, s2sLimit, pkThrottle, gzip) 256 webModule.Route(router, fsLimit, fsThrottle, gzip) 257 258 gts, err := gotosocial.NewServer(dbService, router, federator, mediaManager) 259 if err != nil { 260 return fmt.Errorf("error creating gotosocial service: %s", err) 261 } 262 263 if err := gts.Start(ctx); err != nil { 264 return fmt.Errorf("error starting gotosocial service: %s", err) 265 } 266 267 // catch shutdown signals from the operating system 268 sigs := make(chan os.Signal, 1) 269 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 270 sig := <-sigs // block until signal received 271 log.Infof(ctx, "received signal %s, shutting down", sig) 272 273 // close down all running services in order 274 if err := gts.Stop(ctx); err != nil { 275 return fmt.Errorf("error closing gotosocial service: %s", err) 276 } 277 278 log.Info(ctx, "done! exiting...") 279 return nil 280 }