2024-05-29 02:59:49 +02:00
import { dualLogger } from "@/loggers" ;
import { connectMeili } from "@/meilisearch" ;
import { errorResponse , response } from "@/response" ;
2024-05-16 04:37:25 +02:00
import chalk from "chalk" ;
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-29 02:59:49 +02:00
import { setupDatabase } from "~/drizzle/db" ;
import { agentBans } from "~/middlewares/agent-bans" ;
import { bait } from "~/middlewares/bait" ;
import { boundaryCheck } from "~/middlewares/boundary-check" ;
import { ipBans } from "~/middlewares/ip-bans" ;
import { logger } from "~/middlewares/logger" ;
import { Note } from "~/packages/database-interface/note" ;
import { handleGlitchRequest } from "~/packages/glitch-server/main" ;
import { routes } from "~/routes" ;
import { createServer } from "~/server" ;
import type { APIRouteExports } from "~/types/api" ;
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-05-13 23:36:46 +02:00
const isEntry =
import . meta . path === Bun . main && ! process . argv . includes ( "--silent" ) ;
2024-04-15 07:08:16 +02:00
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" ,
2024-05-16 04:37:25 +02:00
chalk . gray ( ` ${ privateKey } ; ${ publicKey } ` ) ,
2024-04-18 10:42:12 +02:00
) ;
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 19:31:12 +02:00
const app = new Hono ( {
strict : false ,
} ) ;
2024-05-06 09:16:33 +02:00
2024-05-06 10:19:42 +02:00
app . use ( ipBans ) ;
app . use ( agentBans ) ;
app . use ( bait ) ;
app . use ( logger ) ;
2024-05-13 01:21:06 +02:00
app . use ( boundaryCheck ) ;
2024-05-17 23:42:42 +02:00
// Disabled as federation now checks for this
// app.use(urlCheck);
2024-05-06 10:19:42 +02:00
2024-05-06 09:16:33 +02:00
// Inject own filesystem router
2024-05-29 03:14:24 +02:00
for ( const [ , path ] of Object . entries ( routes ) ) {
2024-05-06 09:16:33 +02:00
// 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:54:57 +02:00
app . options ( "*" , async ( ) = > {
return response ( null ) ;
} ) ;
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 ) ;
2024-06-08 01:52:11 +02:00
await dualLogger . log (
LogLevel . DEBUG ,
"Server.Proxy" ,
` Proxying ${ replacedUrl } ` ,
) ;
2024-05-06 10:19:42 +02:00
const proxy = await fetch ( replacedUrl , {
headers : {
// Include for SSR
"X-Forwarded-Host" : ` ${ config . http . bind } : ${ config . http . bind_port } ` ,
"Accept-Encoding" : "identity" ,
} ,
2024-06-08 01:58:49 +02:00
redirect : "manual" ,
2024-05-06 10:19:42 +02:00
} ) . 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 ) {
2024-05-12 03:27:28 +02:00
return errorResponse (
"Route not found on proxy or API route. Are you using the correct HTTP method?" ,
404 ,
) ;
2024-05-06 10:19:42 +02:00
}
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 } ;