import * as plugins from './plugins.js'; import { SshClient } from './classes.sshclient.js'; import type { IMountRequest, TDapMountBackend } from './types.js'; export class MountManager { constructor(private sshClient = new SshClient()) {} public parseRemoteSpec(remoteSpec: string): { host: string; remotePath: string } { const separatorIndex = remoteSpec.indexOf(':'); if (separatorIndex <= 0) { throw new Error('Remote mount spec must look like :'); } const host = remoteSpec.slice(0, separatorIndex); const remotePath = remoteSpec.slice(separatorIndex + 1) || '.'; return { host, remotePath }; } public buildSshfsArgs(request: IMountRequest): string[] { return [`${request.host}:${request.remotePath}`, request.localPath]; } public buildRcloneArgs(request: IMountRequest): string[] { return [ 'mount', `:sftp:${request.remotePath}`, request.localPath, '--sftp-ssh', `ssh ${request.host}`, '--sftp-shell-type', 'none', '--vfs-cache-mode', 'writes', ]; } public async mount(request: IMountRequest): Promise { await plugins.fs.mkdir(request.localPath, { recursive: true }); const backend = request.backend ?? (await this.detectBackend()); if (backend === 'sshfs') { return this.sshClient.spawnInteractive('sshfs', this.buildSshfsArgs(request)); } return this.sshClient.spawnInteractive('rclone', this.buildRcloneArgs(request)); } public async mountDetached(request: IMountRequest): Promise { await plugins.fs.mkdir(request.localPath, { recursive: true }); const backend = request.backend ?? (await this.detectBackend()); if (backend === 'sshfs') { return this.sshClient.spawnInteractive('sshfs', this.buildSshfsArgs(request)); } return this.sshClient.spawnDetached('rclone', this.buildRcloneArgs(request)); } public async unmount(localPath: string): Promise { if (process.platform === 'darwin') { return this.sshClient.spawnInteractive('umount', [localPath]); } if (await this.sshClient.commandExists('fusermount3')) { return this.sshClient.spawnInteractive('fusermount3', ['-u', localPath]); } if (await this.sshClient.commandExists('fusermount')) { return this.sshClient.spawnInteractive('fusermount', ['-u', localPath]); } return this.sshClient.spawnInteractive('umount', [localPath]); } public async detectBackend(): Promise { if (await this.sshClient.commandExists('sshfs')) { return 'sshfs'; } if (await this.sshClient.commandExists('rclone')) { return 'rclone'; } throw new Error('No mount backend found. Install sshfs, or install rclone with FUSE/macFUSE support.'); } }