2022-12-09 10:31:47 -05:00
pub mod api_routes_http ;
2020-07-10 14:15:41 -04:00
pub mod code_migrations ;
2023-07-05 07:25:19 -04:00
pub mod prometheus_metrics ;
2021-12-06 09:54:47 -05:00
pub mod root_span_builder ;
2021-01-29 11:38:27 -05:00
pub mod scheduled_tasks ;
2023-08-29 10:47:57 -04:00
pub mod session_middleware ;
2022-02-04 12:31:38 -05:00
#[ cfg(feature = " console " ) ]
2022-05-10 08:06:32 -04:00
pub mod telemetry ;
2023-08-29 10:47:57 -04:00
use crate ::{
code_migrations ::run_advanced_migrations ,
root_span_builder ::QuieterRootSpanBuilder ,
session_middleware ::SessionMiddleware ,
} ;
2023-03-21 11:03:05 -04:00
use activitypub_federation ::config ::{ FederationConfig , FederationMiddleware } ;
2023-05-29 17:14:00 -04:00
use actix_cors ::Cors ;
2023-07-10 16:44:14 -04:00
use actix_web ::{
2023-10-04 18:19:58 -04:00
dev ::{ ServerHandle , ServiceResponse } ,
2023-12-04 09:53:53 -05:00
middleware ::{ self , Condition , ErrorHandlerResponse , ErrorHandlers } ,
2023-07-10 16:44:14 -04:00
web ::Data ,
App ,
2023-10-04 18:19:58 -04:00
HttpResponse ,
2023-07-10 16:44:14 -04:00
HttpServer ,
Result ,
} ;
2023-12-04 09:53:53 -05:00
use actix_web_prom ::PrometheusMetricsBuilder ;
2023-09-09 12:25:03 -04:00
use clap ::{ ArgAction , Parser } ;
2022-12-19 10:54:42 -05:00
use lemmy_api_common ::{
context ::LemmyContext ,
lemmy_db_views ::structs ::SiteView ,
2023-10-20 11:09:34 -04:00
request ::client_builder ,
2023-09-18 10:25:35 -04:00
send_activity ::{ ActivityChannel , MATCH_OUTGOING_ACTIVITIES } ,
2022-12-19 10:54:42 -05:00
utils ::{
check_private_instance_and_federation_enabled ,
local_site_rate_limit_to_rate_limit_config ,
} ,
} ;
2023-07-19 09:49:41 -04:00
use lemmy_apub ::{
2023-09-18 10:25:35 -04:00
activities ::{ handle_outgoing_activities , match_outgoing_activities } ,
2023-11-06 16:02:01 -05:00
objects ::instance ::ApubSite ,
2023-07-19 09:49:41 -04:00
VerifyUrlData ,
FEDERATION_HTTP_FETCH_LIMIT ,
} ;
2023-10-25 11:34:38 -04:00
use lemmy_db_schema ::{ source ::secret ::Secret , utils ::build_db_pool } ;
2023-09-09 12:25:03 -04:00
use lemmy_federate ::{ start_stop_federation_workers_cancellable , Opts } ;
2022-12-19 10:54:42 -05:00
use lemmy_routes ::{ feeds , images , nodeinfo , webfinger } ;
2023-07-10 06:27:49 -04:00
use lemmy_utils ::{
error ::LemmyError ,
rate_limit ::RateLimitCell ,
2023-07-10 16:44:14 -04:00
response ::jsonify_plain_text_errors ,
2023-09-09 12:25:03 -04:00
settings ::{ structs ::Settings , SETTINGS } ,
2023-07-10 06:27:49 -04:00
} ;
2023-12-04 09:53:53 -05:00
use prometheus ::default_registry ;
2023-10-25 06:54:58 -04:00
use prometheus_metrics ::serve_prometheus ;
2023-09-09 12:25:03 -04:00
use reqwest_middleware ::{ ClientBuilder , ClientWithMiddleware } ;
2022-12-19 10:54:42 -05:00
use reqwest_tracing ::TracingMiddleware ;
2023-10-04 18:19:58 -04:00
use serde_json ::json ;
2023-10-20 11:09:34 -04:00
use std ::{ env , ops ::Deref } ;
2023-09-09 12:25:03 -04:00
use tokio ::signal ::unix ::SignalKind ;
2021-11-23 07:16:47 -05:00
use tracing ::subscriber ::set_global_default ;
2022-12-19 10:54:42 -05:00
use tracing_actix_web ::TracingLogger ;
2021-11-23 07:16:47 -05:00
use tracing_error ::ErrorLayer ;
use tracing_log ::LogTracer ;
2022-01-07 09:53:45 -05:00
use tracing_subscriber ::{ filter ::Targets , layer ::SubscriberExt , Layer , Registry } ;
2022-07-11 16:38:37 -04:00
use url ::Url ;
2021-11-23 07:16:47 -05:00
2023-09-09 12:25:03 -04: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 10:54:42 -05:00
/// Placing the main function in lib.rs allows other crates to import it and embed Lemmy
2023-09-09 12:25:03 -04:00
pub async fn start_lemmy_server ( args : CmdArgs ) -> Result < ( ) , LemmyError > {
2023-10-05 07:41:05 -04:00
// return error 503 while running db migrations and startup tasks
2023-10-04 18:19:58 -04:00
let mut startup_server_handle = None ;
if args . http_server {
startup_server_handle = Some ( create_startup_server ( ) ? ) ;
}
2023-02-28 16:45:37 -05:00
// Set up the connection pool
2023-10-25 11:34:38 -04:00
let pool = build_db_pool ( ) . await ? ;
2023-02-28 16:45:37 -05:00
// Run the Code-required migrations
2023-10-20 11:09:34 -04:00
run_advanced_migrations ( & mut ( & pool ) . into ( ) , & SETTINGS ) . await ? ;
2022-12-19 10:54:42 -05:00
// Initialize the secrets
2023-07-11 09:09:59 -04:00
let secret = Secret ::init ( & mut ( & pool ) . into ( ) )
2022-12-19 10:54:42 -05:00
. await
. expect ( " Couldn't initialize secrets. " ) ;
// Make sure the local site is set up.
2023-07-11 09:09:59 -04:00
let site_view = SiteView ::read_local ( & mut ( & pool ) . into ( ) )
2022-12-19 10:54:42 -05: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 {
2023-10-20 11:09:34 -04:00
println! ( " federation enabled, host is {} " , & SETTINGS . hostname ) ;
2022-12-19 10:54:42 -05:00
}
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 ) ;
2023-10-19 09:31:51 -04:00
let rate_limit_cell = RateLimitCell ::new ( rate_limit_config ) ;
2022-12-19 10:54:42 -05:00
println! (
" Starting http server at {}:{} " ,
2023-10-20 11:09:34 -04:00
SETTINGS . bind , SETTINGS . port
2022-12-19 10:54:42 -05:00
) ;
2023-10-20 11:09:34 -04:00
let client = ClientBuilder ::new ( client_builder ( & SETTINGS ) . build ( ) ? )
2022-12-19 10:54:42 -05:00
. with ( TracingMiddleware ::default ( ) )
. build ( ) ;
2023-06-21 04:28:20 -04:00
let context = LemmyContext ::create (
pool . clone ( ) ,
client . clone ( ) ,
secret . clone ( ) ,
rate_limit_cell . clone ( ) ,
) ;
2023-10-04 18:19:58 -04:00
if ! args . disable_scheduled_tasks {
2023-06-15 05:29:12 -04:00
// Schedules various cleanup tasks for the DB
2023-09-11 05:12:16 -04:00
let _scheduled_tasks = tokio ::task ::spawn ( scheduled_tasks ::setup ( context . clone ( ) ) ) ;
2023-06-15 05:29:12 -04:00
}
2023-02-18 09:36:12 -05:00
2023-10-25 06:54:58 -04:00
if let Some ( prometheus ) = SETTINGS . prometheus . clone ( ) {
serve_prometheus ( prometheus , context . clone ( ) ) ? ;
}
2023-07-05 07:25:19 -04:00
2023-11-06 16:02:01 -05:00
let mut federation_config = FederationConfig ::builder ( ) ;
federation_config
2023-10-20 11:09:34 -04:00
. domain ( SETTINGS . hostname . clone ( ) )
2023-06-22 08:35:12 -04:00
. app_data ( context . clone ( ) )
. client ( client . clone ( ) )
. http_fetch_limit ( FEDERATION_HTTP_FETCH_LIMIT )
2023-09-09 12:25:03 -04:00
. debug ( cfg! ( debug_assertions ) )
2023-06-22 08:35:12 -04:00
. http_signature_compat ( true )
2023-11-06 16:02:01 -05:00
. url_verifier ( Box ::new ( VerifyUrlData ( context . inner_pool ( ) . clone ( ) ) ) ) ;
if local_site . federation_signed_fetch {
let site : ApubSite = site_view . site . into ( ) ;
federation_config . signed_fetch_actor ( & site ) ;
}
let federation_config = federation_config . build ( ) . await ? ;
2023-06-22 08:35:12 -04:00
2023-09-09 12:25:03 -04:00
MATCH_OUTGOING_ACTIVITIES
. set ( Box ::new ( move | d , c | {
Box ::pin ( match_outgoing_activities ( d , c ) )
} ) )
. expect ( " set function pointer " ) ;
2023-09-18 10:25:35 -04:00
let request_data = federation_config . to_request_data ( ) ;
let outgoing_activities_task = tokio ::task ::spawn ( handle_outgoing_activities ( request_data ) ) ;
2023-09-09 12:25:03 -04:00
let server = if args . http_server {
2023-10-04 18:19:58 -04:00
if let Some ( startup_server_handle ) = startup_server_handle {
startup_server_handle . stop ( true ) . await ;
}
2023-10-20 11:09:34 -04:00
// Pictrs cannot use proxy
let pictrs_client = ClientBuilder ::new ( client_builder ( & SETTINGS ) . no_proxy ( ) . build ( ) ? )
. with ( TracingMiddleware ::default ( ) )
. build ( ) ;
2023-09-09 12:25:03 -04:00
Some ( create_http_server (
federation_config . clone ( ) ,
2023-10-20 11:09:34 -04:00
SETTINGS . clone ( ) ,
2023-09-09 12:25:03 -04:00
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 10:25:35 -04:00
// Wait for outgoing apub sends to complete
ActivityChannel ::close ( outgoing_activities_task ) . await ? ;
2023-09-09 12:25:03 -04:00
Ok ( ( ) )
}
2023-10-04 18:19:58 -04: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 12:25:03 -04:00
fn create_http_server (
federation_config : FederationConfig < LemmyContext > ,
settings : Settings ,
federation_enabled : bool ,
pictrs_client : ClientWithMiddleware ,
) -> Result < ServerHandle , LemmyError > {
2023-07-05 07:25:19 -04:00
// this must come before the HttpServer creation
// creates a middleware that populates http metrics for each path, method, and status code
let prom_api_metrics = PrometheusMetricsBuilder ::new ( " lemmy_api " )
. registry ( default_registry ( ) . clone ( ) )
. build ( )
2023-07-17 11:04:14 -04:00
. expect ( " Should always be buildable " ) ;
2023-07-05 07:25:19 -04:00
2023-09-09 12:25:03 -04:00
let context : LemmyContext = federation_config . deref ( ) . clone ( ) ;
2023-10-19 09:31:51 -04:00
let rate_limit_cell = federation_config . rate_limit_cell ( ) . clone ( ) ;
2023-05-29 17:14:00 -04:00
2023-11-14 09:39:13 -05:00
// Create Http server
let bind = ( settings . bind , settings . port ) ;
let server = HttpServer ::new ( move | | {
let cors_config = cors_config ( & settings ) ;
2023-07-05 07:25:19 -04:00
let app = App ::new ( )
2023-06-22 03:34:51 -04: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 06:54:41 -04:00
. wrap ( middleware ::Compress ::default ( ) )
2023-05-29 17:14:00 -04:00
. wrap ( cors_config )
2022-12-19 10:54:42 -05:00
. wrap ( TracingLogger ::< QuieterRootSpanBuilder > ::new ( ) )
2023-07-10 16:44:14 -04:00
. wrap ( ErrorHandlers ::new ( ) . default_handler ( jsonify_plain_text_errors ) )
2023-06-26 04:24:11 -04:00
. app_data ( Data ::new ( context . clone ( ) ) )
2022-12-19 10:54:42 -05:00
. app_data ( Data ::new ( rate_limit_cell . clone ( ) ) )
2023-08-29 10:47:57 -04:00
. wrap ( FederationMiddleware ::new ( federation_config . clone ( ) ) )
2023-12-04 09:53:53 -05:00
. wrap ( SessionMiddleware ::new ( context . clone ( ) ) )
. wrap ( Condition ::new (
SETTINGS . prometheus . is_some ( ) ,
prom_api_metrics . clone ( ) ,
) ) ;
2023-07-05 07:25:19 -04:00
// The routes
app
2023-09-09 12:25:03 -04:00
. configure ( | cfg | api_routes_http ::config ( cfg , & rate_limit_cell ) )
2022-12-19 10:54:42 -05:00
. configure ( | cfg | {
if federation_enabled {
lemmy_apub ::http ::routes ::config ( cfg ) ;
webfinger ::config ( cfg ) ;
}
} )
. configure ( feeds ::config )
2023-09-09 12:25:03 -04:00
. configure ( | cfg | images ::config ( cfg , pictrs_client . clone ( ) , & rate_limit_cell ) )
2022-12-19 10:54:42 -05:00
. configure ( nodeinfo ::config )
} )
2023-09-09 12:25:03 -04:00
. disable_signals ( )
2023-11-14 09:39:13 -05:00
. bind ( bind ) ?
2023-09-09 12:25:03 -04:00
. run ( ) ;
let handle = server . handle ( ) ;
tokio ::task ::spawn ( server ) ;
Ok ( handle )
2022-12-19 10:54:42 -05:00
}
2023-11-14 09:39:13 -05:00
fn cors_config ( settings : & Settings ) -> Cors {
let self_origin = settings . get_protocol_and_hostname ( ) ;
let cors_origin_setting = settings . cors_origin ( ) ;
match ( cors_origin_setting . clone ( ) , cfg! ( debug_assertions ) ) {
( Some ( origin ) , false ) = > {
// Need to call send_wildcard() explicitly, passing this into allowed_origin() results in error
if cors_origin_setting . as_deref ( ) = = Some ( " * " ) {
2023-12-01 09:16:55 -05:00
Cors ::default ( ) . allow_any_origin ( ) . send_wildcard ( )
2023-11-14 09:39:13 -05:00
} else {
Cors ::default ( )
. allowed_origin ( & origin )
. allowed_origin ( & self_origin )
}
}
_ = > Cors ::default ( )
. allow_any_origin ( )
. allow_any_method ( )
. allow_any_header ( )
. expose_any_header ( )
. max_age ( 3600 ) ,
}
}
2022-07-11 16:38:37 -04:00
pub fn init_logging ( opentelemetry_url : & Option < Url > ) -> Result < ( ) , LemmyError > {
2021-11-23 07:16:47 -05:00
LogTracer ::init ( ) ? ;
2023-10-25 11:34:38 -04:00
let log_description = env ::var ( " RUST_LOG " ) . unwrap_or_else ( | _ | " info " . into ( ) ) ;
2022-01-07 09:53:45 -05:00
let targets = log_description
. trim ( )
. trim_matches ( '"' )
. parse ::< Targets > ( ) ? ;
2023-07-04 07:11:47 -04:00
let format_layer = {
#[ cfg(feature = " json-log " ) ]
2023-10-25 04:58:55 -04:00
let layer = tracing_subscriber ::fmt ::layer ( ) . with_ansi ( false ) . json ( ) ;
2023-07-04 07:11:47 -04:00
#[ cfg(not(feature = " json-log " )) ]
2023-10-25 04:58:55 -04:00
let layer = tracing_subscriber ::fmt ::layer ( ) . with_ansi ( false ) ;
2023-07-04 07:11:47 -04:00
layer . with_filter ( targets . clone ( ) )
} ;
2022-01-07 09:53:45 -05:00
2021-11-23 07:16:47 -05:00
let subscriber = Registry ::default ( )
. with ( format_layer )
2022-02-04 12:31:38 -05:00
. with ( ErrorLayer ::default ( ) ) ;
2022-05-10 08:06:32 -04:00
if let Some ( _url ) = opentelemetry_url {
#[ cfg(feature = " console " ) ]
2022-07-11 16:38:37 -04:00
telemetry ::init_tracing ( _url . as_ref ( ) , subscriber , targets ) ? ;
2022-05-10 08:06:32 -04:00
#[ cfg(not(feature = " console " )) ]
tracing ::error! ( " Feature `console` must be enabled for opentelemetry tracing " ) ;
2022-01-06 14:10:20 -05:00
} else {
set_global_default ( subscriber ) ? ;
}
2021-11-23 07:16:47 -05:00
Ok ( ( ) )
}