feat(file-manager): default to the remote login directory (#1512)

This commit is contained in:
ZacharyZcR authored and GitHub committed 2026-09-30 09:47:02 -05:00
1 parent e69029fbf7
commit fdb2f74259
6 files changed
+195 -9

No files matched your search

@@ -0,0 +1,35 @@
import { execChannel, getSessionSftp, type SSHSession } from "./session.js";
/** The SFTP login directory also respects custom homes and chroot roots. */
export async function resolveHomeDirectory(
session: SSHSession,
): Promise<string> {
try {
const sftp = await getSessionSftp(session);
return await new Promise<string>((resolve, reject) => {
sftp.realpath(".", (error, path) => {
if (error) reject(error);
else if (!path) reject(new Error("Empty SFTP login directory"));
else resolve(path);
});
});
} catch {
// Keep the existing shell-only file browser usable without SFTP.
return new Promise<string>((resolve, reject) => {
execChannel(session, "pwd -P", (error, stream) => {
if (error) return reject(error);
let output = "";
stream.on("data", (chunk: Buffer) => {
output += chunk.toString();
});
stream.on("error", reject);
stream.on("close", (code: number) => {
const path = output.replace(/[\r\n]+$/, "");
if (code !== 0 || !path.startsWith("/")) {
reject(new Error("Could not resolve the remote login directory"));
} else resolve(path);
});
});
});
}
}
@@ -1,3 +1,4 @@
import { resolveHomeDirectory } from "./home-directory.js";
import { getErrorMessage } from "./error-message.js";
import type { Express } from "express";
import type { PluginContext } from "@termix/plugin-sdk/backend";
@@ -52,10 +53,10 @@ export function registerFileListingRoutes(
* 500:
* description: Failed to list files.
*/
app.get("/listFiles", (req, res) => {
app.get("/listFiles", async (req, res) => {
const sessionId = req.query.sessionId as string;
const sshConn = sshSessions[sessionId];
const sshPath = (req.query.path as string) || "/";
let sshPath = (req.query.path as string) || ".";
const userId = ctx.currentActor()!;
if (!sessionId) {
@@ -70,6 +71,16 @@ export function registerFileListingRoutes(
return res.status(403).json({ error: "Session access denied" });
}
if (sshPath === ".") {
try {
sshPath = await resolveHomeDirectory(sshConn);
} catch (error) {
return res.status(500).json({
error: getErrorMessage(error, "Could not resolve home directory"),
});
}
}
// Drop concurrent requests for the same session+path — each would open
// a new SSH channel and can exceed the server's per-connection channel limit.
const listKey = `${sessionId}:${sshPath}`;