From 4c9f9dac3c1bdafb96c2f13effff7ac6e5bf6434 Mon Sep 17 00:00:00 2001 From: Ahmed Nagy Date: Sat, 28 Mar 2026 00:45:22 +0200 Subject: [PATCH] fix: show external drives in directory browser on Linux - Add mount point detection for /media, /mnt, /run/media - Display mount points as quick-access entries when browsing home dir - Allow navigation to mount points while maintaining security scope Fixes #2908 --- web/app/api/browse-directories/route.ts | 63 ++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/web/app/api/browse-directories/route.ts b/web/app/api/browse-directories/route.ts index 14e33585b..adc875bd0 100644 --- a/web/app/api/browse-directories/route.ts +++ b/web/app/api/browse-directories/route.ts @@ -1,6 +1,6 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"; import { resolve, dirname, join } from "node:path"; -import { homedir } from "node:os"; +import { homedir, platform } from "node:os"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -24,6 +24,42 @@ function getDevRoot(): string { return homedir(); } +/** + * Get available mount points on Linux (external drives, removable media) + * Returns paths like /media, /mnt, /run/media/ + */ +function getLinuxMountPoints(): string[] { + const mountPoints: string[] = []; + const home = homedir(); + + const standardMounts = ["/media", "/mnt", "/run/media"]; + + for (const mp of standardMounts) { + if (existsSync(mp)) { + mountPoints.push(mp); + } + } + + const runMediaUser = `/run/media/${home.split("/").pop()}`; + if (existsSync(runMediaUser)) { + mountPoints.push(runMediaUser); + } + + return mountPoints; +} + +/** + * Get additional root-level directories to show as shortcuts on Linux + * (for accessing external drives and mounted filesystems) + */ +function getAdditionalRoots(): string[] { + const os = platform(); + if (os === "linux") { + return getLinuxMountPoints(); + } + return []; +} + /** * GET /api/browse-directories?path=/some/path * @@ -46,8 +82,15 @@ export async function GET(request: Request): Promise { // if no devRoot is configured. Navigating to the parent of devRoot is // allowed (one level up) so the UI can show the devRoot in context, // but nothing further. + // Also allow navigation to common mount points (/media, /mnt, /run/media) on Linux const devRootParent = dirname(devRoot); - if (!targetPath.startsWith(devRoot) && targetPath !== devRootParent) { + const additionalRoots = getAdditionalRoots(); + const isAllowedPath = + targetPath.startsWith(devRoot) || + targetPath === devRootParent || + additionalRoots.some((root) => targetPath.startsWith(root)); + + if (!isAllowedPath) { return Response.json( { error: "Path outside allowed scope" }, { status: 403 }, @@ -74,6 +117,9 @@ export async function GET(request: Request): Promise { const parentAllowed = parentPath.startsWith(devRootParent) && parentPath !== targetPath; const entries: Array<{ name: string; path: string }> = []; + // On Linux, show mount points as quick-access when browsing from home directory + const showMountPoints = platform() === "linux" && (targetPath === homedir() || targetPath === devRoot); + try { const items = readdirSync(targetPath, { withFileTypes: true }); for (const item of items) { @@ -87,6 +133,19 @@ export async function GET(request: Request): Promise { path: resolve(targetPath, item.name), }); } + + // Add mount points as quick-access entries on Linux + if (showMountPoints) { + for (const mp of additionalRoots) { + if (existsSync(mp)) { + const mpName = mp.split("/").pop() || mp; + entries.push({ + name: mpName, + path: mp, + }); + } + } + } } catch { // Permission denied or other read error — return empty entries }