From f28f72551920c04aaa5fddfd07ed53345e3dea67 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 7 Apr 2026 20:46:25 +0300 Subject: [PATCH] fix(server,desktop): not running correctly if placed in dot-hidden directory --- apps/server/src/app.ts | 12 ++++++++---- apps/server/src/routes/assets.ts | 17 ++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index ffd33600dd..15f58afc5c 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -10,6 +10,7 @@ import helmet from "helmet"; import { t } from "i18next"; import path from "path"; import favicon from "serve-favicon"; +import type serveStatic from "serve-static"; import assets from "./routes/assets.js"; import custom from "./routes/custom.js"; @@ -24,6 +25,9 @@ import { RESOURCE_DIR } from "./services/resource_dir.js"; import sql_init from "./services/sql_init.js"; import utils, { getResourceDir, isDev } from "./services/utils.js"; +// Allow serving assets even if the installation path contains a hidden (dot-prefixed) directory. +const STATIC_OPTIONS: serveStatic.ServeStaticOptions = { dotfiles: "allow" }; + export default async function buildApp() { const app = express(); @@ -95,10 +99,10 @@ export default async function buildApp() { // localhost-only guard and does not require Trilium authentication. mcpRoutes.register(app); - app.use(express.static(path.join(publicDir, "root"))); - app.use(`/manifest.webmanifest`, express.static(path.join(publicAssetsDir, "manifest.webmanifest"))); - app.use(`/robots.txt`, express.static(path.join(publicAssetsDir, "robots.txt"))); - app.use(`/icon.png`, express.static(path.join(publicAssetsDir, "icon.png"))); + app.use(express.static(path.join(publicDir, "root"), STATIC_OPTIONS)); + app.use(`/manifest.webmanifest`, express.static(path.join(publicAssetsDir, "manifest.webmanifest"), STATIC_OPTIONS)); + app.use(`/robots.txt`, express.static(path.join(publicAssetsDir, "robots.txt"), STATIC_OPTIONS)); + app.use(`/icon.png`, express.static(path.join(publicAssetsDir, "icon.png"), STATIC_OPTIONS)); const { default: sessionParser, startSessionCleanup } = await import("./routes/session_parser.js"); app.use(sessionParser); diff --git a/apps/server/src/routes/assets.ts b/apps/server/src/routes/assets.ts index a6efe543b0..017a054a45 100644 --- a/apps/server/src/routes/assets.ts +++ b/apps/server/src/routes/assets.ts @@ -9,6 +9,9 @@ import auth from "../services/auth.js"; import { getResourceDir, isDev } from "../services/utils.js"; import { doubleCsrfProtection as csrfMiddleware } from "./csrf_protection.js"; +// Allow serving assets even if the installation path contains a hidden (dot-prefixed) directory. +const STATIC_OPTIONS: serveStatic.ServeStaticOptions = { dotfiles: "allow" }; + const persistentCacheStatic = (root: string, options?: serveStatic.ServeStaticOptions>>) => { if (!isDev) { options = { @@ -16,7 +19,7 @@ const persistentCacheStatic = (root: string, options?: serveStatic.ServeStaticOp ...options }; } - return express.static(root, options); + return express.static(root, { ...STATIC_OPTIONS, ...options }); }; async function register(app: express.Application) { @@ -66,7 +69,7 @@ async function register(app: express.Application) { // broken when closing the browser and coming back in to the page. // The page is restored from cache, but the API call fail. res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - res.sendFile(path.join(publicDir, "index.html")); + res.sendFile(path.join(publicDir, "index.html"), STATIC_OPTIONS); }); app.use("/assets", persistentCacheStatic(path.join(publicDir, "assets"))); app.use(`/src`, persistentCacheStatic(path.join(publicDir, "src"))); @@ -76,14 +79,14 @@ async function register(app: express.Application) { app.use(`/${assetUrlFragment}/translations/`, persistentCacheStatic(path.join(publicDir, "translations"))); app.use(`/node_modules/`, persistentCacheStatic(path.join(publicDir, "node_modules"))); } - app.use(`/share/assets/fonts/`, express.static(path.join(getClientDir(), "fonts"))); - app.use(`/share/assets/`, express.static(getShareThemeAssetDir())); + app.use(`/share/assets/fonts/`, express.static(path.join(getClientDir(), "fonts"), STATIC_OPTIONS)); + app.use(`/share/assets/`, express.static(getShareThemeAssetDir(), STATIC_OPTIONS)); app.use(`/pdfjs/`, persistentCacheStatic(getPdfjsAssetDir())); app.use(`/${assetUrlFragment}/images`, persistentCacheStatic(path.join(resourceDir, "assets", "images"))); app.use(`/${assetUrlFragment}/doc_notes`, persistentCacheStatic(path.join(resourceDir, "assets", "doc_notes"))); - app.use(`/assets/vX/fonts`, express.static(path.join(srcRoot, "public/fonts"))); - app.use(`/assets/vX/images`, express.static(path.join(srcRoot, "..", "images"))); - app.use(`/assets/vX/stylesheets`, express.static(path.join(srcRoot, "public/stylesheets"))); + app.use(`/assets/vX/fonts`, express.static(path.join(srcRoot, "public/fonts"), STATIC_OPTIONS)); + app.use(`/assets/vX/images`, express.static(path.join(srcRoot, "..", "images"), STATIC_OPTIONS)); + app.use(`/assets/vX/stylesheets`, express.static(path.join(srcRoot, "public/stylesheets"), STATIC_OPTIONS)); } export function getShareThemeAssetDir() {