77 lines
2.7 KiB
TypeScript
77 lines
2.7 KiB
TypeScript
|
|
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 <host>:<remotePath>');
|
||
|
|
}
|
||
|
|
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<number> {
|
||
|
|
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<number> {
|
||
|
|
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<number> {
|
||
|
|
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<TDapMountBackend> {
|
||
|
|
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.');
|
||
|
|
}
|
||
|
|
}
|