2024-04-15 20:03:10 +02:00
import { dualLogger } from "@loggers" ;
2024-04-07 07:30:49 +02:00
import { connectMeili } from "@meilisearch" ;
2024-05-06 10:19:42 +02:00
import { errorResponse } from "@response" ;
2024-04-07 06:16:54 +02:00
import { config } from "config-manager" ;
2024-05-06 09:16:33 +02:00
import { Hono } from "hono" ;
2024-04-15 07:08:16 +02:00
import { LogLevel , LogManager , type MultiLogManager } from "log-manager" ;
2024-05-06 09:16:33 +02:00
import { setupDatabase } from "~drizzle/db" ;
2024-05-06 10:19:42 +02:00
import { agentBans } from "~middlewares/agent-bans" ;
import { bait } from "~middlewares/bait" ;
import { ipBans } from "~middlewares/ip-bans" ;
import { logger } from "~middlewares/logger" ;
2024-05-06 09:16:33 +02:00
import { Note } from "~packages/database-interface/note" ;
2024-05-06 10:19:42 +02:00
import { handleGlitchRequest } from "~packages/glitch-server/main" ;
2024-05-06 09:16:33 +02:00
import type { APIRouteExports } from "~packages/server-handler" ;
import { routes } from "~routes" ;
2024-05-06 10:19:42 +02:00
import { createServer } from "~server" ;
2023-11-23 00:04:31 +01:00
2023-11-30 05:16:58 +01:00
const timeAtStart = performance . now ( ) ;
2023-09-11 05:54:14 +02:00
2024-04-15 07:08:16 +02:00
const isEntry = import . meta . path === Bun . main ;
2024-04-15 07:13:11 +02:00
let dualServerLogger : LogManager | MultiLogManager = new LogManager (
2024-04-15 07:08:16 +02:00
Bun . file ( "/dev/null" ) ,
) ;
if ( isEntry ) {
2024-04-15 07:13:11 +02:00
dualServerLogger = dualLogger ;
2024-04-15 07:08:16 +02:00
}
2024-04-15 07:13:11 +02:00
await dualServerLogger . log ( LogLevel . INFO , "Lysand" , "Starting Lysand..." ) ;
2023-12-09 04:32:45 +01:00
2024-04-15 07:13:11 +02:00
await setupDatabase ( dualServerLogger ) ;
2023-12-09 02:51:48 +01:00
2023-12-03 05:11:30 +01:00
if ( config . meilisearch . enabled ) {
2024-04-15 07:13:11 +02:00
await connectMeili ( dualServerLogger ) ;
2023-12-03 05:11:30 +01:00
}
2023-11-21 00:58:39 +01:00
// Check if database is reachable
2024-05-06 09:16:33 +02:00
const postCount = await Note . getCount ( ) ;
2024-04-15 03:35:56 +02:00
2024-04-18 10:42:12 +02:00
if ( isEntry ) {
// Check if JWT private key is set in config
if ( ! config . oidc . jwt_key ) {
await dualServerLogger . log (
LogLevel . CRITICAL ,
"Server" ,
"The JWT private key is not set in the config" ,
) ;
await dualServerLogger . log (
LogLevel . CRITICAL ,
"Server" ,
2024-04-19 19:55:32 +02:00
"Below is a generated key for you to copy in the config at oidc.jwt_key" ,
2024-04-18 10:42:12 +02:00
) ;
// Generate a key for them
const keys = await crypto . subtle . generateKey ( "Ed25519" , true , [
"sign" ,
"verify" ,
] ) ;
const privateKey = Buffer . from (
await crypto . subtle . exportKey ( "pkcs8" , keys . privateKey ) ,
) . toString ( "base64" ) ;
const publicKey = Buffer . from (
await crypto . subtle . exportKey ( "spki" , keys . publicKey ) ,
) . toString ( "base64" ) ;
await dualServerLogger . log (
LogLevel . CRITICAL ,
"Server" ,
` ${ privateKey } ; ${ publicKey } ` ,
) ;
process . exit ( 1 ) ;
}
// Try and import the key
const privateKey = await crypto . subtle
. importKey (
"pkcs8" ,
Buffer . from ( config . oidc . jwt_key . split ( ";" ) [ 0 ] , "base64" ) ,
"Ed25519" ,
false ,
[ "sign" ] ,
)
. catch ( ( e ) = > e as Error ) ;
// Try and import the key
const publicKey = await crypto . subtle
. importKey (
"spki" ,
Buffer . from ( config . oidc . jwt_key . split ( ";" ) [ 1 ] , "base64" ) ,
"Ed25519" ,
false ,
[ "verify" ] ,
)
. catch ( ( e ) = > e as Error ) ;
if ( privateKey instanceof Error || publicKey instanceof Error ) {
await dualServerLogger . log (
LogLevel . CRITICAL ,
"Server" ,
"The JWT key could not be imported! You may generate a new one by removing the old one from the config and restarting the server (this will invalidate all current JWTs)." ,
) ;
process . exit ( 1 ) ;
}
}
2024-05-06 09:16:33 +02:00
const app = new Hono ( ) ;
2024-05-06 10:19:42 +02:00
app . use ( ipBans ) ;
app . use ( agentBans ) ;
app . use ( bait ) ;
app . use ( logger ) ;
2024-05-06 09:16:33 +02:00
// Inject own filesystem router
for ( const [ route , path ] of Object . entries ( routes ) ) {
// use app.get(path, handler) to add routes
const route : APIRouteExports = await import ( path ) ;
if ( ! route . meta || ! route . default ) {
throw new Error ( ` Route ${ path } does not have the correct exports. ` ) ;
}
route . default ( app ) ;
}
2024-05-06 10:19:42 +02:00
app . all ( "*" , async ( context ) = > {
if ( config . frontend . glitch . enabled ) {
const glitch = await handleGlitchRequest ( context . req . raw , dualLogger ) ;
if ( glitch ) {
return glitch ;
}
}
const base_url_with_http = config . http . base_url . replace (
"https://" ,
"http://" ,
) ;
const replacedUrl = context . req . url
. replace ( config . http . base_url , config . frontend . url )
. replace ( base_url_with_http , config . frontend . url ) ;
const proxy = await fetch ( replacedUrl , {
headers : {
// Include for SSR
"X-Forwarded-Host" : ` ${ config . http . bind } : ${ config . http . bind_port } ` ,
"Accept-Encoding" : "identity" ,
} ,
} ) . catch ( async ( e ) = > {
await dualLogger . logError ( LogLevel . ERROR , "Server.Proxy" , e as Error ) ;
await dualLogger . log (
LogLevel . ERROR ,
"Server.Proxy" ,
` The Frontend is not running or the route is not found: ${ replacedUrl } ` ,
) ;
return null ;
} ) ;
proxy ? . headers . set ( "Cache-Control" , "max-age=31536000" ) ;
if ( ! proxy || proxy . status === 404 ) {
return errorResponse ( "Route not found on proxy or API route" , 404 ) ;
}
return proxy ;
} ) ;
2024-05-06 09:16:33 +02:00
createServer ( config , app ) ;
2023-11-11 03:36:06 +01:00
2024-04-15 07:13:11 +02:00
await dualServerLogger . log (
2024-04-07 07:30:49 +02:00
LogLevel . INFO ,
"Server" ,
` Lysand started at ${ config . http . bind } : ${ config . http . bind_port } in ${ (
performance . now ( ) - timeAtStart
) . toFixed ( 0 ) } ms ` ,
2023-10-23 02:32:17 +02:00
) ;
2023-11-21 00:58:39 +01:00
2024-04-15 07:13:11 +02:00
await dualServerLogger . log (
2024-04-07 07:30:49 +02:00
LogLevel . INFO ,
"Database" ,
` Database is online, now serving ${ postCount } posts ` ,
2023-11-21 00:58:39 +01:00
) ;
2024-04-15 20:50:06 +02:00
if ( config . frontend . enabled ) {
if ( ! URL . canParse ( config . frontend . url ) ) {
await dualServerLogger . log (
LogLevel . ERROR ,
"Server" ,
` Frontend URL is not a valid URL: ${ config . frontend . url } ` ,
) ;
process . exit ( 1 ) ;
}
2024-04-15 03:35:56 +02:00
2024-04-15 20:50:06 +02:00
// Check if frontend is reachable
const response = await fetch ( new URL ( "/" , config . frontend . url ) )
. then ( ( res ) = > res . ok )
. catch ( ( ) = > false ) ;
if ( ! response ) {
await dualServerLogger . log (
LogLevel . ERROR ,
"Server" ,
` Frontend is unreachable at ${ config . frontend . url } ` ,
) ;
await dualServerLogger . log (
LogLevel . ERROR ,
"Server" ,
"Please ensure the frontend is online and reachable" ,
) ;
}
} else {
2024-04-15 07:13:11 +02:00
await dualServerLogger . log (
2024-04-15 20:50:06 +02:00
LogLevel . WARNING ,
2024-04-15 03:35:56 +02:00
"Server" ,
2024-04-15 20:50:06 +02:00
"Frontend is disabled, skipping check" ,
2024-04-15 03:35:56 +02:00
) ;
}
2024-05-06 09:16:33 +02:00
export { app } ;