gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }