mirror of
https://github.com/versia-pub/server.git
synced 2025-12-07 08:48:19 +01:00
refactor(api): ♻️ Serve frontend from static files instead of proxying another process
This commit is contained in:
parent
5f8c57b3e1
commit
58b4d7454f
7
.github/config.workflow.toml
vendored
7
.github/config.workflow.toml
vendored
|
|
@ -89,8 +89,11 @@ banned_user_agents = [
|
|||
# Enable custom frontends (warning: not enabling this will make Versia Server only accessible via the Mastodon API)
|
||||
# Frontends also control the OpenID flow, so if you disable this, you will need to use the Mastodon frontend
|
||||
enabled = true
|
||||
# The URL to reach the frontend at (should be on a local network)
|
||||
url = "http://localhost:3000"
|
||||
# Path that frontend files are served from
|
||||
# Edit this property to serve custom frontends
|
||||
# If this is not set, Versia Server will also check
|
||||
# the VERSIA_FRONTEND_PATH environment variable
|
||||
# path = ""
|
||||
|
||||
[frontend.routes]
|
||||
# Special routes for your frontend, below are the defaults for Versia-FE
|
||||
|
|
|
|||
58
app.ts
58
app.ts
|
|
@ -9,6 +9,7 @@ import { getLogger } from "@logtape/logtape";
|
|||
import { apiReference } from "@scalar/hono-api-reference";
|
||||
import { inspect } from "bun";
|
||||
import chalk from "chalk";
|
||||
import { serveStatic } from "hono/bun";
|
||||
import { cors } from "hono/cors";
|
||||
import { createMiddleware } from "hono/factory";
|
||||
import { prettyJSON } from "hono/pretty-json";
|
||||
|
|
@ -163,49 +164,20 @@ export const appFactory = async (): Promise<OpenAPIHono<HonoEnv>> => {
|
|||
return context.body(null, 204);
|
||||
});
|
||||
|
||||
app.all("*", async (context) => {
|
||||
const replacedUrl = new URL(
|
||||
new URL(context.req.url).pathname,
|
||||
config.frontend.url,
|
||||
).toString();
|
||||
|
||||
serverLogger.debug`Proxying ${replacedUrl}`;
|
||||
|
||||
const proxy = await fetch(replacedUrl, {
|
||||
headers: {
|
||||
// Include for SSR
|
||||
"X-Forwarded-Host": `${config.http.bind}:${config.http.bind_port}`,
|
||||
"Accept-Encoding": "identity",
|
||||
},
|
||||
redirect: "manual",
|
||||
}).catch((e) => {
|
||||
serverLogger.error`${e}`;
|
||||
sentry?.captureException(e);
|
||||
serverLogger.error`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) {
|
||||
throw new ApiError(
|
||||
404,
|
||||
"Route not found on proxy or API route. Are you using the correct HTTP method?",
|
||||
);
|
||||
}
|
||||
|
||||
// Disable CSP upgrade-insecure-requests if an .onion domain is used
|
||||
if (new URL(context.req.url).hostname.endsWith(".onion")) {
|
||||
proxy.headers.set(
|
||||
"Content-Security-Policy",
|
||||
proxy.headers
|
||||
.get("Content-Security-Policy")
|
||||
?.replace("upgrade-insecure-requests;", "") ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
return proxy;
|
||||
});
|
||||
app.all(
|
||||
"*",
|
||||
serveStatic({
|
||||
root: config.frontend.path,
|
||||
}),
|
||||
);
|
||||
// Fallback for SPAs, in case we've hit a route that is client-side
|
||||
app.all(
|
||||
"*",
|
||||
serveStatic({
|
||||
root: config.frontend.path,
|
||||
path: "index.html",
|
||||
}),
|
||||
);
|
||||
|
||||
app.onError((error, c) => {
|
||||
if (error instanceof ApiError) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { cwd } from "node:process";
|
||||
import { z } from "@hono/zod-openapi";
|
||||
import { type BunFile, file } from "bun";
|
||||
import { type BunFile, env, file } from "bun";
|
||||
import ISO6391 from "iso-639-1";
|
||||
import { types as mimeTypes } from "mime-types";
|
||||
import { generateVAPIDKeys } from "web-push";
|
||||
|
|
@ -400,7 +401,7 @@ export const ConfigSchema = z
|
|||
}),
|
||||
frontend: z.strictObject({
|
||||
enabled: z.boolean().default(true),
|
||||
url: url.default("http://localhost:3000"),
|
||||
path: z.string().default(env.VERSIA_FRONTEND_PATH || cwd()),
|
||||
routes: z.strictObject({
|
||||
home: urlPath.default("/"),
|
||||
login: urlPath.default("/oauth/authorize"),
|
||||
|
|
|
|||
|
|
@ -89,8 +89,11 @@ banned_user_agents = [
|
|||
# Enable custom frontends (warning: not enabling this will make Versia Server only accessible via the Mastodon API)
|
||||
# Frontends also control the OpenID flow, so if you disable this, you will need to use the Mastodon frontend
|
||||
enabled = true
|
||||
# The URL to reach the frontend at (should be on a local network)
|
||||
url = "http://localhost:3000"
|
||||
# Path that frontend files are served from
|
||||
# Edit this property to serve custom frontends
|
||||
# If this is not set, Versia Server will also check
|
||||
# the VERSIA_FRONTEND_PATH environment variable
|
||||
# path = ""
|
||||
|
||||
[frontend.routes]
|
||||
# Special routes for your frontend, below are the defaults for Versia-FE
|
||||
|
|
|
|||
|
|
@ -273,9 +273,9 @@
|
|||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"url": {
|
||||
"$ref": "#/properties/http/properties/proxy_address",
|
||||
"default": "http://localhost:3000"
|
||||
"path": {
|
||||
"type": "string",
|
||||
"default": "/home/jessew/Dev/versia-server"
|
||||
},
|
||||
"routes": {
|
||||
"type": "object",
|
||||
|
|
@ -317,7 +317,7 @@
|
|||
"default": {}
|
||||
}
|
||||
},
|
||||
"required": ["enabled", "url", "routes", "settings"],
|
||||
"required": ["enabled", "path", "routes", "settings"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"email": {
|
||||
|
|
@ -597,8 +597,12 @@
|
|||
},
|
||||
"default": [
|
||||
"application/vnd.lotus-1-2-3",
|
||||
"model/step",
|
||||
"application/andrew-inset",
|
||||
"application/appinstaller",
|
||||
"application/applixware",
|
||||
"application/appx",
|
||||
"application/appxbundle",
|
||||
"application/atom+xml",
|
||||
"application/atomcat+xml",
|
||||
"application/atomdeleted+xml",
|
||||
|
|
@ -606,6 +610,8 @@
|
|||
"application/atsc-dwd+xml",
|
||||
"application/atsc-held+xml",
|
||||
"application/atsc-rsat+xml",
|
||||
"application/automationml-aml+xml",
|
||||
"application/automationml-amlx+zip",
|
||||
"application/bdoc",
|
||||
"application/calendar+xml",
|
||||
"application/ccxml+xml",
|
||||
|
|
@ -617,19 +623,21 @@
|
|||
"application/cdmi-queue",
|
||||
"application/cpl+xml",
|
||||
"application/cu-seeme",
|
||||
"application/cwl",
|
||||
"application/dash+xml",
|
||||
"application/dash-patch+xml",
|
||||
"application/davmount+xml",
|
||||
"application/dicom",
|
||||
"application/docbook+xml",
|
||||
"application/dssc+der",
|
||||
"application/dssc+xml",
|
||||
"application/ecmascript",
|
||||
"application/ecmascript",
|
||||
"application/emma+xml",
|
||||
"application/emotionml+xml",
|
||||
"application/epub+zip",
|
||||
"application/exi",
|
||||
"application/express",
|
||||
"application/fdf",
|
||||
"application/fdt+xml",
|
||||
"application/font-tdpfr",
|
||||
"application/geo+json",
|
||||
|
|
@ -648,8 +656,7 @@
|
|||
"application/java-archive",
|
||||
"application/java-serialized-object",
|
||||
"application/java-vm",
|
||||
"application/javascript",
|
||||
"application/javascript",
|
||||
"text/javascript",
|
||||
"application/json",
|
||||
"application/json",
|
||||
"application/json5",
|
||||
|
|
@ -680,6 +687,10 @@
|
|||
"application/mp21",
|
||||
"application/mp4",
|
||||
"application/mp4",
|
||||
"application/mp4",
|
||||
"application/mp4",
|
||||
"application/msix",
|
||||
"application/msixbundle",
|
||||
"application/msword",
|
||||
"application/msword",
|
||||
"application/mxf",
|
||||
|
|
@ -716,6 +727,8 @@
|
|||
"application/onenote",
|
||||
"application/onenote",
|
||||
"application/onenote",
|
||||
"application/onenote",
|
||||
"application/onenote",
|
||||
"application/oxps",
|
||||
"application/p2p-overlay+xml",
|
||||
"application/patch-ops-error+xml",
|
||||
|
|
@ -740,6 +753,7 @@
|
|||
"application/postscript",
|
||||
"application/provenance+xml",
|
||||
"application/prs.cww",
|
||||
"application/prs.xsf+xml",
|
||||
"application/pskc+xml",
|
||||
"application/raml+yaml",
|
||||
"application/rdf+xml",
|
||||
|
|
@ -775,6 +789,7 @@
|
|||
"application/smil+xml",
|
||||
"application/sparql-query",
|
||||
"application/sparql-results+xml",
|
||||
"application/sql",
|
||||
"application/srgs",
|
||||
"application/srgs+xml",
|
||||
"application/sru+xml",
|
||||
|
|
@ -807,7 +822,7 @@
|
|||
"application/vnd.adobe.fxp",
|
||||
"application/vnd.adobe.fxp",
|
||||
"application/vnd.adobe.xdp+xml",
|
||||
"application/vnd.adobe.xfdf",
|
||||
"application/xfdf",
|
||||
"application/vnd.age",
|
||||
"application/vnd.ahead.space",
|
||||
"application/vnd.airzip.filesecure.azf",
|
||||
|
|
@ -828,6 +843,7 @@
|
|||
"application/vnd.aristanetworks.swi",
|
||||
"application/vnd.astraea-software.iota",
|
||||
"application/vnd.audiograph",
|
||||
"application/vnd.autodesk.fbx",
|
||||
"application/vnd.balsamiq.bmml+xml",
|
||||
"application/vnd.blueice.multipass",
|
||||
"application/vnd.bmi",
|
||||
|
|
@ -861,6 +877,7 @@
|
|||
"application/vnd.dart",
|
||||
"application/vnd.data-vision.rdz",
|
||||
"application/vnd.dbf",
|
||||
"application/vnd.dcmp+xml",
|
||||
"application/vnd.dece.data",
|
||||
"application/vnd.dece.data",
|
||||
"application/vnd.dece.data",
|
||||
|
|
@ -891,7 +908,6 @@
|
|||
"application/vnd.eszigno3+xml",
|
||||
"application/vnd.ezpix-album",
|
||||
"application/vnd.ezpix-package",
|
||||
"application/vnd.fdf",
|
||||
"application/vnd.fdsn.mseed",
|
||||
"application/vnd.fdsn.seed",
|
||||
"application/vnd.fdsn.seed",
|
||||
|
|
@ -915,6 +931,7 @@
|
|||
"application/vnd.fuzzysheet",
|
||||
"application/vnd.genomatix.tuxedo",
|
||||
"application/vnd.geogebra.file",
|
||||
"application/vnd.geogebra.slides",
|
||||
"application/vnd.geogebra.tool",
|
||||
"application/vnd.geometry-explorer",
|
||||
"application/vnd.geometry-explorer",
|
||||
|
|
@ -923,10 +940,17 @@
|
|||
"application/vnd.geospace",
|
||||
"application/vnd.gmx",
|
||||
"application/vnd.google-apps.document",
|
||||
"application/vnd.google-apps.drawing",
|
||||
"application/vnd.google-apps.form",
|
||||
"application/vnd.google-apps.jam",
|
||||
"application/vnd.google-apps.map",
|
||||
"application/vnd.google-apps.presentation",
|
||||
"application/vnd.google-apps.script",
|
||||
"application/vnd.google-apps.site",
|
||||
"application/vnd.google-apps.spreadsheet",
|
||||
"application/vnd.google-earth.kml+xml",
|
||||
"application/vnd.google-earth.kmz",
|
||||
"application/vnd.gov.sk.xmldatacontainer+xml",
|
||||
"application/vnd.grafeq",
|
||||
"application/vnd.grafeq",
|
||||
"application/vnd.groove-account",
|
||||
|
|
@ -1051,6 +1075,7 @@
|
|||
"application/vnd.ms-powerpoint.slideshow.macroenabled.12",
|
||||
"application/vnd.ms-powerpoint.template.macroenabled.12",
|
||||
"application/vnd.ms-project",
|
||||
"application/vnd.ms-visio.viewer",
|
||||
"application/vnd.ms-word.document.macroenabled.12",
|
||||
"application/vnd.ms-word.template.macroenabled.12",
|
||||
"application/vnd.ms-works",
|
||||
|
|
@ -1063,6 +1088,7 @@
|
|||
"application/vnd.musician",
|
||||
"application/vnd.muvee.style",
|
||||
"application/vnd.mynfc",
|
||||
"application/vnd.nato.bindingdataobject+xml",
|
||||
"application/vnd.neurolanguage.nlu",
|
||||
"application/vnd.nitf",
|
||||
"application/vnd.nitf",
|
||||
|
|
@ -1120,9 +1146,13 @@
|
|||
"application/vnd.pocketlearn",
|
||||
"application/vnd.powerbuilder6",
|
||||
"application/vnd.previewsystems.box",
|
||||
"application/vnd.procrate.brushset",
|
||||
"application/vnd.procreate.brush",
|
||||
"application/vnd.procreate.dream",
|
||||
"application/vnd.proteus.magazine",
|
||||
"application/vnd.publishare-delta-tree",
|
||||
"application/vnd.pvi.ptid1",
|
||||
"application/vnd.pwg-xhtml-print+xml",
|
||||
"application/vnd.quark.quarkxpress",
|
||||
"application/vnd.quark.quarkxpress",
|
||||
"application/vnd.quark.quarkxpress",
|
||||
|
|
@ -1199,11 +1229,14 @@
|
|||
"application/vnd.umajin",
|
||||
"application/vnd.unity",
|
||||
"application/vnd.uoml+xml",
|
||||
"application/vnd.uoml+xml",
|
||||
"application/vnd.vcx",
|
||||
"application/vnd.visio",
|
||||
"application/vnd.visio",
|
||||
"application/vnd.visio",
|
||||
"application/vnd.visio",
|
||||
"application/vnd.visio",
|
||||
"application/vnd.visio",
|
||||
"application/vnd.visionary",
|
||||
"application/vnd.vsf",
|
||||
"application/vnd.wap.wbxml",
|
||||
|
|
@ -1246,6 +1279,7 @@
|
|||
"application/x-authorware-seg",
|
||||
"application/x-bcpio",
|
||||
"application/x-bittorrent",
|
||||
"application/x-blender",
|
||||
"application/x-blorb",
|
||||
"application/x-blorb",
|
||||
"application/x-bzip",
|
||||
|
|
@ -1302,6 +1336,7 @@
|
|||
"application/x-hdf",
|
||||
"application/x-httpd-php",
|
||||
"application/x-install-instructions",
|
||||
"application/x-ipynb+json",
|
||||
"application/x-java-archive-diff",
|
||||
"application/x-java-jnlp-file",
|
||||
"application/x-keepass2",
|
||||
|
|
@ -1311,7 +1346,7 @@
|
|||
"application/x-lzh-compressed",
|
||||
"application/x-makeself",
|
||||
"application/x-mie",
|
||||
"application/x-mobipocket-ebook",
|
||||
"model/prc",
|
||||
"application/x-mobipocket-ebook",
|
||||
"application/x-ms-application",
|
||||
"application/x-ms-shortcut",
|
||||
|
|
@ -1353,7 +1388,6 @@
|
|||
"application/x-shar",
|
||||
"application/x-shockwave-flash",
|
||||
"application/x-silverlight-app",
|
||||
"application/x-sql",
|
||||
"application/x-stuffit",
|
||||
"application/x-stuffitx",
|
||||
"application/x-subrip",
|
||||
|
|
@ -1387,6 +1421,7 @@
|
|||
"application/xliff+xml",
|
||||
"application/x-xpinstall",
|
||||
"application/x-xz",
|
||||
"application/zip",
|
||||
"application/x-zmachine",
|
||||
"application/x-zmachine",
|
||||
"application/x-zmachine",
|
||||
|
|
@ -1419,8 +1454,10 @@
|
|||
"application/xv+xml",
|
||||
"application/yang",
|
||||
"application/yin+xml",
|
||||
"application/zip",
|
||||
"application/zip+dotlottie",
|
||||
"video/3gpp",
|
||||
"audio/aac",
|
||||
"audio/aac",
|
||||
"audio/adpcm",
|
||||
"audio/amr",
|
||||
"audio/basic",
|
||||
|
|
@ -1433,6 +1470,7 @@
|
|||
"audio/mpeg",
|
||||
"audio/mp4",
|
||||
"audio/mp4",
|
||||
"audio/mp4",
|
||||
"audio/mpeg",
|
||||
"audio/mpeg",
|
||||
"audio/mpeg",
|
||||
|
|
@ -1456,9 +1494,8 @@
|
|||
"audio/vnd.nuera.ecelp7470",
|
||||
"audio/vnd.nuera.ecelp9600",
|
||||
"audio/vnd.rip",
|
||||
"audio/wave",
|
||||
"audio/wav",
|
||||
"audio/webm",
|
||||
"audio/x-aac",
|
||||
"audio/x-aiff",
|
||||
"audio/x-aiff",
|
||||
"audio/x-aiff",
|
||||
|
|
@ -1489,8 +1526,10 @@
|
|||
"image/avcs",
|
||||
"image/avif",
|
||||
"image/bmp",
|
||||
"image/bmp",
|
||||
"image/cgm",
|
||||
"image/dicom-rle",
|
||||
"image/dpx",
|
||||
"image/fits",
|
||||
"image/g3fax",
|
||||
"image/gif",
|
||||
|
|
@ -1499,8 +1538,9 @@
|
|||
"image/heif",
|
||||
"image/heif-sequence",
|
||||
"image/hej2k",
|
||||
"image/hsj2",
|
||||
"image/ief",
|
||||
"image/jaii",
|
||||
"image/jais",
|
||||
"image/jls",
|
||||
"image/jp2",
|
||||
"image/jp2",
|
||||
|
|
@ -1510,8 +1550,10 @@
|
|||
"image/jph",
|
||||
"image/jphc",
|
||||
"image/jpm",
|
||||
"image/jpm",
|
||||
"image/jpx",
|
||||
"image/jpx",
|
||||
"image/jxl",
|
||||
"image/jxr",
|
||||
"image/jxra",
|
||||
"image/jxrs",
|
||||
|
|
@ -1521,8 +1563,10 @@
|
|||
"image/jxss",
|
||||
"image/ktx",
|
||||
"image/ktx2",
|
||||
"image/pjpeg",
|
||||
"image/png",
|
||||
"image/prs.btif",
|
||||
"image/prs.btif",
|
||||
"image/prs.pti",
|
||||
"image/sgi",
|
||||
"image/svg+xml",
|
||||
|
|
@ -1560,6 +1604,7 @@
|
|||
"image/vnd.zbrush.pcx",
|
||||
"image/webp",
|
||||
"image/x-3ds",
|
||||
"image/x-adobe-dng",
|
||||
"image/x-cmu-raster",
|
||||
"image/x-cmx",
|
||||
"image/x-freehand",
|
||||
|
|
@ -1587,28 +1632,41 @@
|
|||
"message/global-headers",
|
||||
"message/rfc822",
|
||||
"message/rfc822",
|
||||
"message/rfc822",
|
||||
"message/rfc822",
|
||||
"message/vnd.wfa.wsc",
|
||||
"model/3mf",
|
||||
"model/gltf+json",
|
||||
"model/gltf-binary",
|
||||
"model/iges",
|
||||
"model/iges",
|
||||
"model/jt",
|
||||
"model/mesh",
|
||||
"model/mesh",
|
||||
"model/mesh",
|
||||
"model/mtl",
|
||||
"model/step",
|
||||
"model/step",
|
||||
"model/step",
|
||||
"model/step",
|
||||
"model/step+xml",
|
||||
"model/step+zip",
|
||||
"model/step-xml+zip",
|
||||
"model/u3d",
|
||||
"model/vnd.bary",
|
||||
"model/vnd.cld",
|
||||
"model/vnd.collada+xml",
|
||||
"model/vnd.dwf",
|
||||
"model/vnd.gdl",
|
||||
"model/vnd.gtw",
|
||||
"model/vnd.mts",
|
||||
"video/mp2t",
|
||||
"model/vnd.opengex",
|
||||
"model/vnd.parasolid.transmit.binary",
|
||||
"model/vnd.parasolid.transmit.text",
|
||||
"model/vnd.pytha.pyox",
|
||||
"model/vnd.pytha.pyox",
|
||||
"model/vnd.sap.vds",
|
||||
"model/vnd.usda",
|
||||
"model/vnd.usdz+zip",
|
||||
"model/vnd.valve.source.compiled-map",
|
||||
"model/vnd.vtu",
|
||||
|
|
@ -1632,6 +1690,7 @@
|
|||
"text/html",
|
||||
"text/html",
|
||||
"text/jade",
|
||||
"text/javascript",
|
||||
"text/jsx",
|
||||
"text/less",
|
||||
"text/markdown",
|
||||
|
|
@ -1683,6 +1742,7 @@
|
|||
"text/vnd.wap.wml",
|
||||
"text/vnd.wap.wmlscript",
|
||||
"text/vtt",
|
||||
"text/wgsl",
|
||||
"text/x-asm",
|
||||
"text/x-asm",
|
||||
"text/x-c",
|
||||
|
|
@ -1723,12 +1783,11 @@
|
|||
"video/h264",
|
||||
"video/iso.segment",
|
||||
"video/jpeg",
|
||||
"video/jpm",
|
||||
"video/mj2",
|
||||
"video/mj2",
|
||||
"video/mp2t",
|
||||
"video/mp4",
|
||||
"video/mp4",
|
||||
"video/mp2t",
|
||||
"video/mp2t",
|
||||
"video/mp4",
|
||||
"video/mpeg",
|
||||
"video/mpeg",
|
||||
|
|
|
|||
|
|
@ -36,20 +36,6 @@ serverLogger.info`Versia Server started at ${config.http.bind}:${config.http.bin
|
|||
|
||||
serverLogger.info`Database is online, now serving ${postCount} posts`;
|
||||
|
||||
if (config.frontend.enabled) {
|
||||
// Check if frontend is reachable
|
||||
const response = await fetch(new URL("/", config.frontend.url))
|
||||
.then((res) => res.ok)
|
||||
.catch(() => false);
|
||||
|
||||
if (!response) {
|
||||
serverLogger.error`Frontend is unreachable at ${config.frontend.url}`;
|
||||
serverLogger.error`Please ensure the frontend is online and reachable`;
|
||||
}
|
||||
} else {
|
||||
serverLogger.warn`Frontend is disabled, skipping check`;
|
||||
}
|
||||
|
||||
// Check if Redis is reachable
|
||||
const connection = new IORedis({
|
||||
host: config.redis.queue.host,
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ cert = "/app/dist/config/${DOMAIN}.pem"
|
|||
|
||||
[frontend]
|
||||
enabled = true
|
||||
url = "http://fe:3000"
|
||||
path = "/app/dist/frontend"
|
||||
|
||||
[media]
|
||||
backend = "local"
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@ export const generateClient = async (
|
|||
token?.data.accessToken,
|
||||
);
|
||||
|
||||
// @ts-expect-error This doesn't include fetch.preconnect, which is a custom property
|
||||
// added by Bun
|
||||
// biome-ignore lint/complexity/useLiteralKeys: Overriding private properties
|
||||
client["fetch"] = (
|
||||
input: RequestInfo | string | URL | Request,
|
||||
|
|
|
|||
Loading…
Reference in a new issue