2022-12-09 15:31:47 +00:00
pub mod api_routes_http ;
2020-07-10 18:15:41 +00:00
pub mod code_migrations ;
2023-07-05 11:25:19 +00:00
#[ cfg(feature = " prometheus-metrics " ) ]
pub mod prometheus_metrics ;
2021-12-06 14:54:47 +00:00
pub mod root_span_builder ;
2021-01-29 16:38:27 +00:00
pub mod scheduled_tasks ;
2023-08-29 14:47:57 +00:00
pub mod session_middleware ;
2022-02-04 17:31:38 +00:00
#[ cfg(feature = " console " ) ]
2022-05-10 12:06:32 +00:00
pub mod telemetry ;
2023-08-29 14:47:57 +00:00
use crate ::{
code_migrations ::run_advanced_migrations ,
root_span_builder ::QuieterRootSpanBuilder ,
session_middleware ::SessionMiddleware ,
} ;
2023-03-21 15:03:05 +00:00
use activitypub_federation ::config ::{ FederationConfig , FederationMiddleware } ;
2023-05-29 21:14:00 +00:00
use actix_cors ::Cors ;
2023-07-10 20:44:14 +00:00
use actix_web ::{
2023-10-04 22:19:58 +00:00
dev ::{ ServerHandle , ServiceResponse } ,
middleware ::{ self , ErrorHandlerResponse , ErrorHandlers } ,
2023-07-10 20:44:14 +00:00
web ::Data ,
App ,
2023-10-04 22:19:58 +00:00
HttpResponse ,
2023-07-10 20:44:14 +00:00
HttpServer ,
Result ,
} ;
2023-09-09 16:25:03 +00:00
use clap ::{ ArgAction , Parser } ;
2022-12-19 15:54:42 +00:00
use lemmy_api_common ::{
context ::LemmyContext ,
lemmy_db_views ::structs ::SiteView ,
request ::build_user_agent ,
2023-09-18 14:25:35 +00:00
send_activity ::{ ActivityChannel , MATCH_OUTGOING_ACTIVITIES } ,
2022-12-19 15:54:42 +00:00
utils ::{
check_private_instance_and_federation_enabled ,
local_site_rate_limit_to_rate_limit_config ,
} ,
} ;
2023-07-19 13:49:41 +00:00
use lemmy_apub ::{
2023-09-18 14:25:35 +00:00
activities ::{ handle_outgoing_activities , match_outgoing_activities } ,
2023-07-19 13:49:41 +00:00
VerifyUrlData ,
FEDERATION_HTTP_FETCH_LIMIT ,
} ;
2022-12-19 15:54:42 +00:00
use lemmy_db_schema ::{
source ::secret ::Secret ,
utils ::{ build_db_pool , get_database_url , run_migrations } ,
} ;
2023-09-09 16:25:03 +00:00
use lemmy_federate ::{ start_stop_federation_workers_cancellable , Opts } ;
2022-12-19 15:54:42 +00:00
use lemmy_routes ::{ feeds , images , nodeinfo , webfinger } ;
2023-07-10 10:27:49 +00:00
use lemmy_utils ::{
error ::LemmyError ,
rate_limit ::RateLimitCell ,
2023-07-10 20:44:14 +00:00
response ::jsonify_plain_text_errors ,
2023-09-09 16:25:03 +00:00
settings ::{ structs ::Settings , SETTINGS } ,
2023-07-10 10:27:49 +00:00
} ;
2022-12-19 15:54:42 +00:00
use reqwest ::Client ;
2023-09-09 16:25:03 +00:00
use reqwest_middleware ::{ ClientBuilder , ClientWithMiddleware } ;
2022-12-19 15:54:42 +00:00
use reqwest_tracing ::TracingMiddleware ;
2023-10-04 22:19:58 +00:00
use serde_json ::json ;
2023-09-11 09:12:16 +00:00
use std ::{ env , ops ::Deref , time ::Duration } ;
2023-09-09 16:25:03 +00:00
use tokio ::signal ::unix ::SignalKind ;
2021-11-23 12:16:47 +00:00
use tracing ::subscriber ::set_global_default ;
2022-12-19 15:54:42 +00:00
use tracing_actix_web ::TracingLogger ;
2021-11-23 12:16:47 +00:00
use tracing_error ::ErrorLayer ;
use tracing_log ::LogTracer ;
2022-01-07 14:53:45 +00:00
use tracing_subscriber ::{ filter ::Targets , layer ::SubscriberExt , Layer , Registry } ;
2022-07-11 20:38:37 +00:00
use url ::Url ;
2023-07-05 11:25:19 +00:00
#[ cfg(feature = " prometheus-metrics " ) ]
use {
actix_web_prom ::PrometheusMetricsBuilder ,
prometheus ::default_registry ,
prometheus_metrics ::serve_prometheus ,
} ;
2021-11-23 12:16:47 +00:00
2023-09-09 16:25:03 +00:00
#[ derive(Parser, Debug) ]
#[ command(
version ,
about = " A link aggregator for the fediverse " ,
long_about = " A link aggregator for the fediverse. \n \n This is the Lemmy backend API server. This will connect to a PostgreSQL database, run any pending migrations and start accepting API requests. "
) ]
pub struct CmdArgs {
#[ arg(long, default_value_t = false) ]
/// Disables running scheduled tasks.
///
/// If you are running multiple Lemmy server processes,
/// you probably want to disable scheduled tasks on all but one of the processes,
/// to avoid running the tasks more often than intended.
disable_scheduled_tasks : bool ,
/// Whether or not to run the HTTP server.
///
/// This can be used to run a Lemmy server process that only runs scheduled tasks.
#[ arg(long, default_value_t = true, action=ArgAction::Set) ]
http_server : bool ,
/// Whether or not to emit outgoing ActivityPub messages.
///
/// Set to true for a simple setup. Only set to false for horizontally scaled setups.
/// See https://join-lemmy.org/docs/administration/horizontal_scaling.html for detail.
#[ arg(long, default_value_t = true, action=ArgAction::Set) ]
federate_activities : bool ,
/// The index of this outgoing federation process.
///
/// Defaults to 1/1. If you want to split the federation workload onto n servers, run each server 1≤i≤n with these args:
/// --federate-process-index i --federate-process-count n
///
/// Make you have exactly one server with each `i` running, otherwise federation will randomly send duplicates or nothing.
///
/// See https://join-lemmy.org/docs/administration/horizontal_scaling.html for more detail.
#[ arg(long, default_value_t = 1) ]
federate_process_index : i32 ,
/// How many outgoing federation processes you are starting in total.
///
/// If set, make sure to set --federate-process-index differently for each.
#[ arg(long, default_value_t = 1) ]
federate_process_count : i32 ,
}
2022-12-19 15:54:42 +00:00
/// Max timeout for http requests
2023-02-18 14:36:12 +00:00
pub ( crate ) const REQWEST_TIMEOUT : Duration = Duration ::from_secs ( 10 ) ;
2022-12-19 15:54:42 +00:00
/// Placing the main function in lib.rs allows other crates to import it and embed Lemmy
2023-09-09 16:25:03 +00:00
pub async fn start_lemmy_server ( args : CmdArgs ) -> Result < ( ) , LemmyError > {
2022-12-19 15:54:42 +00:00
let settings = SETTINGS . to_owned ( ) ;
2023-10-05 11:41:05 +00:00
// return error 503 while running db migrations and startup tasks
2023-10-04 22:19:58 +00:00
let mut startup_server_handle = None ;
if args . http_server {
startup_server_handle = Some ( create_startup_server ( ) ? ) ;
}
2023-02-28 21:45:37 +00:00
// Run the DB migrations
2022-12-19 15:54:42 +00:00
let db_url = get_database_url ( Some ( & settings ) ) ;
run_migrations ( & db_url ) ;
2023-02-28 21:45:37 +00:00
// Set up the connection pool
2022-12-19 15:54:42 +00:00
let pool = build_db_pool ( & settings ) . await ? ;
2023-02-28 21:45:37 +00:00
// Run the Code-required migrations
2023-07-11 13:09:59 +00:00
run_advanced_migrations ( & mut ( & pool ) . into ( ) , & settings ) . await ? ;
2022-12-19 15:54:42 +00:00
// Initialize the secrets
2023-07-11 13:09:59 +00:00
let secret = Secret ::init ( & mut ( & pool ) . into ( ) )
2022-12-19 15:54:42 +00:00
. await
. expect ( " Couldn't initialize secrets. " ) ;
// Make sure the local site is set up.
2023-07-11 13:09:59 +00:00
let site_view = SiteView ::read_local ( & mut ( & pool ) . into ( ) )
2022-12-19 15:54:42 +00:00
. await
. expect ( " local site not set up " ) ;
let local_site = site_view . local_site ;
let federation_enabled = local_site . federation_enabled ;
if federation_enabled {
println! ( " federation enabled, host is {} " , & settings . hostname ) ;
}
check_private_instance_and_federation_enabled ( & local_site ) ? ;
// Set up the rate limiter
let rate_limit_config =
local_site_rate_limit_to_rate_limit_config ( & site_view . local_site_rate_limit ) ;
let rate_limit_cell = RateLimitCell ::new ( rate_limit_config ) . await ;
println! (
" Starting http server at {}:{} " ,
settings . bind , settings . port
) ;
2023-02-18 14:36:12 +00:00
let user_agent = build_user_agent ( & settings ) ;
2022-12-19 15:54:42 +00:00
let reqwest_client = Client ::builder ( )
2023-02-18 14:36:12 +00:00
. user_agent ( user_agent . clone ( ) )
2022-12-19 15:54:42 +00:00
. timeout ( REQWEST_TIMEOUT )
2023-05-18 11:11:06 +00:00
. connect_timeout ( REQWEST_TIMEOUT )
2022-12-19 15:54:42 +00:00
. build ( ) ? ;
let client = ClientBuilder ::new ( reqwest_client . clone ( ) )
. with ( TracingMiddleware ::default ( ) )
. build ( ) ;
// Pictrs cannot use the retry middleware
let pictrs_client = ClientBuilder ::new ( reqwest_client . clone ( ) )
. with ( TracingMiddleware ::default ( ) )
. build ( ) ;
2023-06-21 08:28:20 +00:00
let context = LemmyContext ::create (
pool . clone ( ) ,
client . clone ( ) ,
secret . clone ( ) ,
rate_limit_cell . clone ( ) ,
) ;
2023-10-04 22:19:58 +00:00
if ! args . disable_scheduled_tasks {
2023-06-15 09:29:12 +00:00
// Schedules various cleanup tasks for the DB
2023-09-11 09:12:16 +00:00
let _scheduled_tasks = tokio ::task ::spawn ( scheduled_tasks ::setup ( context . clone ( ) ) ) ;
2023-06-15 09:29:12 +00:00
}
2023-02-18 14:36:12 +00:00
2023-07-05 11:25:19 +00:00
#[ cfg(feature = " prometheus-metrics " ) ]
serve_prometheus ( settings . prometheus . as_ref ( ) , context . clone ( ) ) ;
2023-06-22 12:35:12 +00:00
let federation_config = FederationConfig ::builder ( )
. domain ( settings . hostname . clone ( ) )
. app_data ( context . clone ( ) )
. client ( client . clone ( ) )
. http_fetch_limit ( FEDERATION_HTTP_FETCH_LIMIT )
2023-09-09 16:25:03 +00:00
. debug ( cfg! ( debug_assertions ) )
2023-06-22 12:35:12 +00:00
. http_signature_compat ( true )
2023-07-11 13:09:59 +00:00
. url_verifier ( Box ::new ( VerifyUrlData ( context . inner_pool ( ) . clone ( ) ) ) )
2023-06-22 12:35:12 +00:00
. build ( )
2023-06-26 08:24:11 +00:00
. await ? ;
2023-06-22 12:35:12 +00:00
2023-09-09 16:25:03 +00:00
MATCH_OUTGOING_ACTIVITIES
. set ( Box ::new ( move | d , c | {
Box ::pin ( match_outgoing_activities ( d , c ) )
} ) )
. expect ( " set function pointer " ) ;
2023-09-18 14:25:35 +00:00
let request_data = federation_config . to_request_data ( ) ;
let outgoing_activities_task = tokio ::task ::spawn ( handle_outgoing_activities ( request_data ) ) ;
2023-09-09 16:25:03 +00:00
let server = if args . http_server {
2023-10-04 22:19:58 +00:00
if let Some ( startup_server_handle ) = startup_server_handle {
startup_server_handle . stop ( true ) . await ;
}
2023-09-09 16:25:03 +00:00
Some ( create_http_server (
federation_config . clone ( ) ,
settings . clone ( ) ,
federation_enabled ,
pictrs_client ,
) ? )
} else {
None
} ;
let federate = args . federate_activities . then ( | | {
start_stop_federation_workers_cancellable (
Opts {
process_index : args . federate_process_index ,
process_count : args . federate_process_count ,
} ,
pool . clone ( ) ,
federation_config . clone ( ) ,
)
} ) ;
let mut interrupt = tokio ::signal ::unix ::signal ( SignalKind ::interrupt ( ) ) ? ;
let mut terminate = tokio ::signal ::unix ::signal ( SignalKind ::terminate ( ) ) ? ;
tokio ::select! {
_ = tokio ::signal ::ctrl_c ( ) = > {
tracing ::warn! ( " Received ctrl-c, shutting down gracefully... " ) ;
}
_ = interrupt . recv ( ) = > {
tracing ::warn! ( " Received interrupt, shutting down gracefully... " ) ;
}
_ = terminate . recv ( ) = > {
tracing ::warn! ( " Received terminate, shutting down gracefully... " ) ;
}
}
if let Some ( server ) = server {
server . stop ( true ) . await ;
}
if let Some ( federate ) = federate {
federate . cancel ( ) . await ? ;
}
2023-09-18 14:25:35 +00:00
// Wait for outgoing apub sends to complete
ActivityChannel ::close ( outgoing_activities_task ) . await ? ;
2023-09-09 16:25:03 +00:00
Ok ( ( ) )
}
2023-10-04 22:19:58 +00:00
/// Creates temporary HTTP server which returns status 503 for all requests.
fn create_startup_server ( ) -> Result < ServerHandle , LemmyError > {
let startup_server = HttpServer ::new ( move | | {
App ::new ( ) . wrap ( ErrorHandlers ::new ( ) . default_handler ( move | req | {
let ( req , _ ) = req . into_parts ( ) ;
let response =
HttpResponse ::ServiceUnavailable ( ) . json ( json! ( { " error " : " Lemmy is currently starting " } ) ) ;
let service_response = ServiceResponse ::new ( req , response ) ;
Ok ( ErrorHandlerResponse ::Response (
service_response . map_into_right_body ( ) ,
) )
} ) )
} )
. bind ( ( SETTINGS . bind , SETTINGS . port ) ) ?
. run ( ) ;
let startup_server_handle = startup_server . handle ( ) ;
tokio ::task ::spawn ( startup_server ) ;
Ok ( startup_server_handle )
}
2023-09-09 16:25:03 +00:00
fn create_http_server (
federation_config : FederationConfig < LemmyContext > ,
settings : Settings ,
federation_enabled : bool ,
pictrs_client : ClientWithMiddleware ,
) -> Result < ServerHandle , LemmyError > {
2023-07-05 11:25:19 +00:00
// this must come before the HttpServer creation
// creates a middleware that populates http metrics for each path, method, and status code
#[ cfg(feature = " prometheus-metrics " ) ]
let prom_api_metrics = PrometheusMetricsBuilder ::new ( " lemmy_api " )
. registry ( default_registry ( ) . clone ( ) )
. build ( )
2023-07-17 15:04:14 +00:00
. expect ( " Should always be buildable " ) ;
2023-07-05 11:25:19 +00:00
2023-09-09 16:25:03 +00:00
let context : LemmyContext = federation_config . deref ( ) . clone ( ) ;
let rate_limit_cell = federation_config . settings_updated_channel ( ) . clone ( ) ;
let self_origin = settings . get_protocol_and_hostname ( ) ;
2022-12-19 15:54:42 +00:00
// Create Http server with websocket support
2023-09-09 16:25:03 +00:00
let server = HttpServer ::new ( move | | {
2023-07-19 13:49:41 +00:00
let cors_origin = env ::var ( " LEMMY_CORS_ORIGIN " ) ;
2023-07-06 11:25:19 +00:00
let cors_config = match ( cors_origin , cfg! ( debug_assertions ) ) {
( Ok ( origin ) , false ) = > Cors ::default ( )
. allowed_origin ( & origin )
2023-09-09 16:25:03 +00:00
. allowed_origin ( & self_origin ) ,
2023-07-06 11:25:19 +00:00
_ = > Cors ::default ( )
. allow_any_origin ( )
. allow_any_method ( )
. allow_any_header ( )
. expose_any_header ( )
. max_age ( 3600 ) ,
2023-05-29 21:14:00 +00:00
} ;
2023-07-05 11:25:19 +00:00
let app = App ::new ( )
2023-06-22 07:34:51 +00:00
. wrap ( middleware ::Logger ::new (
// This is the default log format save for the usage of %{r}a over %a to guarantee to record the client's (forwarded) IP and not the last peer address, since the latter is frequently just a reverse proxy
" %{r}a '%r' %s %b '%{Referer}i' '%{User-Agent}i' %T " ,
) )
2023-06-26 10:54:41 +00:00
. wrap ( middleware ::Compress ::default ( ) )
2023-05-29 21:14:00 +00:00
. wrap ( cors_config )
2022-12-19 15:54:42 +00:00
. wrap ( TracingLogger ::< QuieterRootSpanBuilder > ::new ( ) )
2023-07-10 20:44:14 +00:00
. wrap ( ErrorHandlers ::new ( ) . default_handler ( jsonify_plain_text_errors ) )
2023-06-26 08:24:11 +00:00
. app_data ( Data ::new ( context . clone ( ) ) )
2022-12-19 15:54:42 +00:00
. app_data ( Data ::new ( rate_limit_cell . clone ( ) ) )
2023-08-29 14:47:57 +00:00
. wrap ( FederationMiddleware ::new ( federation_config . clone ( ) ) )
. wrap ( SessionMiddleware ::new ( context . clone ( ) ) ) ;
2023-07-05 11:25:19 +00:00
#[ cfg(feature = " prometheus-metrics " ) ]
let app = app . wrap ( prom_api_metrics . clone ( ) ) ;
// The routes
app
2023-09-09 16:25:03 +00:00
. configure ( | cfg | api_routes_http ::config ( cfg , & rate_limit_cell ) )
2022-12-19 15:54:42 +00:00
. configure ( | cfg | {
if federation_enabled {
lemmy_apub ::http ::routes ::config ( cfg ) ;
webfinger ::config ( cfg ) ;
}
} )
. configure ( feeds ::config )
2023-09-09 16:25:03 +00:00
. configure ( | cfg | images ::config ( cfg , pictrs_client . clone ( ) , & rate_limit_cell ) )
2022-12-19 15:54:42 +00:00
. configure ( nodeinfo ::config )
} )
2023-09-09 16:25:03 +00:00
. disable_signals ( )
. bind ( ( settings . bind , settings . port ) ) ?
. run ( ) ;
let handle = server . handle ( ) ;
tokio ::task ::spawn ( server ) ;
Ok ( handle )
2022-12-19 15:54:42 +00:00
}
2022-07-11 20:38:37 +00:00
pub fn init_logging ( opentelemetry_url : & Option < Url > ) -> Result < ( ) , LemmyError > {
2021-11-23 12:16:47 +00:00
LogTracer ::init ( ) ? ;
2022-01-07 14:53:45 +00:00
let log_description = std ::env ::var ( " RUST_LOG " ) . unwrap_or_else ( | _ | " info " . into ( ) ) ;
let targets = log_description
. trim ( )
. trim_matches ( '"' )
. parse ::< Targets > ( ) ? ;
2023-07-04 11:11:47 +00:00
let format_layer = {
#[ cfg(feature = " json-log " ) ]
let layer = tracing_subscriber ::fmt ::layer ( ) . json ( ) ;
#[ cfg(not(feature = " json-log " )) ]
let layer = tracing_subscriber ::fmt ::layer ( ) ;
layer . with_filter ( targets . clone ( ) )
} ;
2022-01-07 14:53:45 +00:00
2021-11-23 12:16:47 +00:00
let subscriber = Registry ::default ( )
. with ( format_layer )
2022-02-04 17:31:38 +00:00
. with ( ErrorLayer ::default ( ) ) ;
2022-05-10 12:06:32 +00:00
if let Some ( _url ) = opentelemetry_url {
#[ cfg(feature = " console " ) ]
2022-07-11 20:38:37 +00:00
telemetry ::init_tracing ( _url . as_ref ( ) , subscriber , targets ) ? ;
2022-05-10 12:06:32 +00:00
#[ cfg(not(feature = " console " )) ]
tracing ::error! ( " Feature `console` must be enabled for opentelemetry tracing " ) ;
2022-01-06 19:10:20 +00:00
} else {
set_global_default ( subscriber ) ? ;
}
2021-11-23 12:16:47 +00:00
Ok ( ( ) )
}