@awcp/transport-sshfs 0.0.0-dev-202601300724
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/delegator/credential-manager.d.ts +90 -0
- package/dist/delegator/credential-manager.d.ts.map +1 -0
- package/dist/delegator/credential-manager.js +216 -0
- package/dist/delegator/credential-manager.js.map +1 -0
- package/dist/delegator/index.d.ts +2 -0
- package/dist/delegator/index.d.ts.map +1 -0
- package/dist/delegator/index.js +2 -0
- package/dist/delegator/index.js.map +1 -0
- package/dist/executor/index.d.ts +2 -0
- package/dist/executor/index.d.ts.map +1 -0
- package/dist/executor/index.js +2 -0
- package/dist/executor/index.js.map +1 -0
- package/dist/executor/sshfs-client.d.ts +62 -0
- package/dist/executor/sshfs-client.d.ts.map +1 -0
- package/dist/executor/sshfs-client.js +225 -0
- package/dist/executor/sshfs-client.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSH Credential Manager configuration
|
|
3
|
+
*/
|
|
4
|
+
export interface CredentialManagerConfig {
|
|
5
|
+
/** Directory to store temporary keys (default: ~/.awcp/keys) */
|
|
6
|
+
keyDir?: string;
|
|
7
|
+
/** SSH server port (default: 22) */
|
|
8
|
+
sshPort?: number;
|
|
9
|
+
/** SSH server host (default: localhost) */
|
|
10
|
+
sshHost?: string;
|
|
11
|
+
/** SSH user for connections */
|
|
12
|
+
sshUser?: string;
|
|
13
|
+
/** Path to authorized_keys file (default: ~/.ssh/authorized_keys) */
|
|
14
|
+
authorizedKeysPath?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Generated credential
|
|
18
|
+
*/
|
|
19
|
+
export interface GeneratedCredential {
|
|
20
|
+
/** The private key content */
|
|
21
|
+
privateKey: string;
|
|
22
|
+
/** The public key content */
|
|
23
|
+
publicKey: string;
|
|
24
|
+
/** Path to the private key file */
|
|
25
|
+
privateKeyPath: string;
|
|
26
|
+
/** Path to the public key file */
|
|
27
|
+
publicKeyPath: string;
|
|
28
|
+
/** Delegation ID for tracking */
|
|
29
|
+
delegationId: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* SSH Credential Manager
|
|
33
|
+
*
|
|
34
|
+
* Manages temporary SSH keys for AWCP delegations.
|
|
35
|
+
*
|
|
36
|
+
* TODO [Security]: Consider SSH certificates with built-in expiry for production.
|
|
37
|
+
*/
|
|
38
|
+
export declare class CredentialManager {
|
|
39
|
+
private config;
|
|
40
|
+
private activeCredentials;
|
|
41
|
+
constructor(config?: CredentialManagerConfig);
|
|
42
|
+
/**
|
|
43
|
+
* Get the path to authorized_keys file
|
|
44
|
+
*/
|
|
45
|
+
private getAuthorizedKeysPath;
|
|
46
|
+
/**
|
|
47
|
+
* Generate a temporary SSH key pair for a delegation
|
|
48
|
+
*/
|
|
49
|
+
generateCredential(delegationId: string, _ttlSeconds: number): Promise<{
|
|
50
|
+
credential: string;
|
|
51
|
+
endpoint: {
|
|
52
|
+
host: string;
|
|
53
|
+
port: number;
|
|
54
|
+
user: string;
|
|
55
|
+
};
|
|
56
|
+
}>;
|
|
57
|
+
/**
|
|
58
|
+
* Revoke a credential
|
|
59
|
+
*/
|
|
60
|
+
revokeCredential(delegationId: string): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Get credential info for a delegation
|
|
63
|
+
*/
|
|
64
|
+
getCredential(delegationId: string): GeneratedCredential | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Revoke all credentials
|
|
67
|
+
*/
|
|
68
|
+
revokeAll(): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Clean up stale AWCP keys from authorized_keys (call on startup)
|
|
71
|
+
*/
|
|
72
|
+
cleanupStaleKeys(): Promise<number>;
|
|
73
|
+
/**
|
|
74
|
+
* Clean up stale key files from key directory (call on startup)
|
|
75
|
+
*/
|
|
76
|
+
cleanupStaleKeyFiles(): Promise<number>;
|
|
77
|
+
/**
|
|
78
|
+
* Add a public key to authorized_keys
|
|
79
|
+
*/
|
|
80
|
+
private addToAuthorizedKeys;
|
|
81
|
+
/**
|
|
82
|
+
* Remove a public key from authorized_keys by delegation ID
|
|
83
|
+
*/
|
|
84
|
+
private removeFromAuthorizedKeys;
|
|
85
|
+
/**
|
|
86
|
+
* Execute ssh-keygen to generate a key pair
|
|
87
|
+
*/
|
|
88
|
+
private execSshKeygen;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=credential-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-manager.d.ts","sourceRoot":"","sources":["../../src/delegator/credential-manager.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,kCAAkC;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,YAAY,EAAE,MAAM,CAAC;CACtB;AASD;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,iBAAiB,CAA0C;gBAEvD,MAAM,CAAC,EAAE,uBAAuB;IAI5C;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAI7B;;OAEG;IACG,kBAAkB,CACtB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;QACT,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;KACxD,CAAC;IAsCF;;OAEG;IACG,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB3D;;OAEG;IACH,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAIpE;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAMhC;;OAEG;IACG,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IA0BzC;;OAEG;IACG,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC;IAkC7C;;OAEG;YACW,mBAAmB;IAcjC;;OAEG;YACW,wBAAwB;IAkBtC;;OAEG;IACH,OAAO,CAAC,aAAa;CAyBtB"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { unlink, mkdir, readFile, writeFile, appendFile, readdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
const DEFAULT_KEY_DIR = join(homedir(), '.awcp', 'keys');
|
|
6
|
+
/**
|
|
7
|
+
* Marker prefix for AWCP-managed keys in authorized_keys
|
|
8
|
+
*/
|
|
9
|
+
const AWCP_KEY_COMMENT_PREFIX = 'awcp-temp-key-';
|
|
10
|
+
/**
|
|
11
|
+
* SSH Credential Manager
|
|
12
|
+
*
|
|
13
|
+
* Manages temporary SSH keys for AWCP delegations.
|
|
14
|
+
*
|
|
15
|
+
* TODO [Security]: Consider SSH certificates with built-in expiry for production.
|
|
16
|
+
*/
|
|
17
|
+
export class CredentialManager {
|
|
18
|
+
config;
|
|
19
|
+
activeCredentials = new Map();
|
|
20
|
+
constructor(config) {
|
|
21
|
+
this.config = config ?? {};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get the path to authorized_keys file
|
|
25
|
+
*/
|
|
26
|
+
getAuthorizedKeysPath() {
|
|
27
|
+
return this.config.authorizedKeysPath ?? join(homedir(), '.ssh', 'authorized_keys');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Generate a temporary SSH key pair for a delegation
|
|
31
|
+
*/
|
|
32
|
+
async generateCredential(delegationId, _ttlSeconds) {
|
|
33
|
+
const keyDir = this.config.keyDir ?? DEFAULT_KEY_DIR;
|
|
34
|
+
await mkdir(keyDir, { recursive: true, mode: 0o700 });
|
|
35
|
+
const privateKeyPath = join(keyDir, `${delegationId}`);
|
|
36
|
+
const publicKeyPath = join(keyDir, `${delegationId}.pub`);
|
|
37
|
+
// Generate key pair using ssh-keygen with AWCP marker comment
|
|
38
|
+
const keyComment = `${AWCP_KEY_COMMENT_PREFIX}${delegationId}`;
|
|
39
|
+
await this.execSshKeygen(privateKeyPath, keyComment);
|
|
40
|
+
// Read the generated keys
|
|
41
|
+
const privateKey = await readFile(privateKeyPath, 'utf-8');
|
|
42
|
+
const publicKey = await readFile(publicKeyPath, 'utf-8');
|
|
43
|
+
const credential = {
|
|
44
|
+
privateKey,
|
|
45
|
+
publicKey,
|
|
46
|
+
privateKeyPath,
|
|
47
|
+
publicKeyPath,
|
|
48
|
+
delegationId,
|
|
49
|
+
};
|
|
50
|
+
this.activeCredentials.set(delegationId, credential);
|
|
51
|
+
// Add public key to authorized_keys
|
|
52
|
+
await this.addToAuthorizedKeys(publicKey);
|
|
53
|
+
return {
|
|
54
|
+
credential: privateKey,
|
|
55
|
+
endpoint: {
|
|
56
|
+
host: this.config.sshHost ?? 'localhost',
|
|
57
|
+
port: this.config.sshPort ?? 22,
|
|
58
|
+
user: this.config.sshUser ?? process.env['USER'] ?? 'awcp',
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Revoke a credential
|
|
64
|
+
*/
|
|
65
|
+
async revokeCredential(delegationId) {
|
|
66
|
+
const credential = this.activeCredentials.get(delegationId);
|
|
67
|
+
if (!credential) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Remove from authorized_keys first
|
|
71
|
+
await this.removeFromAuthorizedKeys(delegationId);
|
|
72
|
+
// Remove key files
|
|
73
|
+
try {
|
|
74
|
+
await unlink(credential.privateKeyPath);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Ignore errors if file doesn't exist
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
await unlink(credential.publicKeyPath);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Ignore errors if file doesn't exist
|
|
84
|
+
}
|
|
85
|
+
this.activeCredentials.delete(delegationId);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get credential info for a delegation
|
|
89
|
+
*/
|
|
90
|
+
getCredential(delegationId) {
|
|
91
|
+
return this.activeCredentials.get(delegationId);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Revoke all credentials
|
|
95
|
+
*/
|
|
96
|
+
async revokeAll() {
|
|
97
|
+
for (const delegationId of this.activeCredentials.keys()) {
|
|
98
|
+
await this.revokeCredential(delegationId);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Clean up stale AWCP keys from authorized_keys (call on startup)
|
|
103
|
+
*/
|
|
104
|
+
async cleanupStaleKeys() {
|
|
105
|
+
const authorizedKeysPath = this.getAuthorizedKeysPath();
|
|
106
|
+
try {
|
|
107
|
+
const content = await readFile(authorizedKeysPath, 'utf-8');
|
|
108
|
+
const lines = content.split('\n');
|
|
109
|
+
const cleanedLines = lines.filter(line => {
|
|
110
|
+
// Keep lines that don't have AWCP marker
|
|
111
|
+
return !line.includes(AWCP_KEY_COMMENT_PREFIX);
|
|
112
|
+
});
|
|
113
|
+
const removedCount = lines.length - cleanedLines.length;
|
|
114
|
+
if (removedCount > 0) {
|
|
115
|
+
await writeFile(authorizedKeysPath, cleanedLines.join('\n'));
|
|
116
|
+
console.log(`[CredentialManager] Cleaned up ${removedCount} stale AWCP keys from authorized_keys`);
|
|
117
|
+
}
|
|
118
|
+
return removedCount;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// File doesn't exist or can't be read, nothing to clean
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Clean up stale key files from key directory (call on startup)
|
|
127
|
+
*/
|
|
128
|
+
async cleanupStaleKeyFiles() {
|
|
129
|
+
const keyDir = this.config.keyDir ?? DEFAULT_KEY_DIR;
|
|
130
|
+
try {
|
|
131
|
+
const files = await readdir(keyDir);
|
|
132
|
+
let removedCount = 0;
|
|
133
|
+
for (const file of files) {
|
|
134
|
+
// Skip files that are currently active
|
|
135
|
+
const delegationId = file.replace(/\.pub$/, '');
|
|
136
|
+
if (this.activeCredentials.has(delegationId)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
// Remove stale key files
|
|
140
|
+
try {
|
|
141
|
+
await unlink(join(keyDir, file));
|
|
142
|
+
removedCount++;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Ignore errors
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (removedCount > 0) {
|
|
149
|
+
console.log(`[CredentialManager] Cleaned up ${removedCount} stale key files`);
|
|
150
|
+
}
|
|
151
|
+
return removedCount;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// Directory doesn't exist, nothing to clean
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Add a public key to authorized_keys
|
|
160
|
+
*/
|
|
161
|
+
async addToAuthorizedKeys(publicKey) {
|
|
162
|
+
const authorizedKeysPath = this.getAuthorizedKeysPath();
|
|
163
|
+
// Ensure .ssh directory exists
|
|
164
|
+
const sshDir = join(homedir(), '.ssh');
|
|
165
|
+
await mkdir(sshDir, { recursive: true, mode: 0o700 });
|
|
166
|
+
// Ensure the key ends with newline
|
|
167
|
+
const keyLine = publicKey.trim() + '\n';
|
|
168
|
+
// Append to authorized_keys
|
|
169
|
+
await appendFile(authorizedKeysPath, keyLine, { mode: 0o600 });
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Remove a public key from authorized_keys by delegation ID
|
|
173
|
+
*/
|
|
174
|
+
async removeFromAuthorizedKeys(delegationId) {
|
|
175
|
+
const authorizedKeysPath = this.getAuthorizedKeysPath();
|
|
176
|
+
const keyMarker = `${AWCP_KEY_COMMENT_PREFIX}${delegationId}`;
|
|
177
|
+
try {
|
|
178
|
+
const content = await readFile(authorizedKeysPath, 'utf-8');
|
|
179
|
+
const lines = content.split('\n');
|
|
180
|
+
// Filter out lines containing this delegation's key marker
|
|
181
|
+
const filteredLines = lines.filter(line => !line.includes(keyMarker));
|
|
182
|
+
// Write back
|
|
183
|
+
await writeFile(authorizedKeysPath, filteredLines.join('\n'), { mode: 0o600 });
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// File doesn't exist or can't be read, nothing to remove
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Execute ssh-keygen to generate a key pair
|
|
191
|
+
*/
|
|
192
|
+
execSshKeygen(keyPath, comment) {
|
|
193
|
+
return new Promise((resolve, reject) => {
|
|
194
|
+
const proc = spawn('ssh-keygen', [
|
|
195
|
+
'-t', 'ed25519',
|
|
196
|
+
'-f', keyPath,
|
|
197
|
+
'-N', '', // No passphrase
|
|
198
|
+
'-C', comment,
|
|
199
|
+
]);
|
|
200
|
+
let stderr = '';
|
|
201
|
+
proc.stderr.on('data', (data) => {
|
|
202
|
+
stderr += data.toString();
|
|
203
|
+
});
|
|
204
|
+
proc.on('close', (code) => {
|
|
205
|
+
if (code === 0) {
|
|
206
|
+
resolve();
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
reject(new Error(`ssh-keygen failed: ${stderr}`));
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
proc.on('error', reject);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=credential-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-manager.js","sourceRoot":"","sources":["../../src/delegator/credential-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/delegator/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,KAAK,uBAAuB,EAAE,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/delegator/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAA0D,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/executor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/executor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAA2C,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSHFS Mount Client configuration
|
|
3
|
+
*/
|
|
4
|
+
export interface SshfsMountConfig {
|
|
5
|
+
/** Directory to store temporary key files */
|
|
6
|
+
tempKeyDir?: string;
|
|
7
|
+
/** Additional sshfs options */
|
|
8
|
+
defaultOptions?: Record<string, string>;
|
|
9
|
+
/** Timeout for mount operation in ms (default: 30000) */
|
|
10
|
+
mountTimeout?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Mount parameters
|
|
14
|
+
*/
|
|
15
|
+
export interface MountParams {
|
|
16
|
+
endpoint: {
|
|
17
|
+
host: string;
|
|
18
|
+
port: number;
|
|
19
|
+
user: string;
|
|
20
|
+
};
|
|
21
|
+
exportLocator: string;
|
|
22
|
+
credential: string;
|
|
23
|
+
mountPoint: string;
|
|
24
|
+
options?: Record<string, string>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* SSHFS Mount Client
|
|
28
|
+
*
|
|
29
|
+
* Handles mounting remote filesystems via SSHFS on the Remote side.
|
|
30
|
+
*/
|
|
31
|
+
export declare class SshfsMountClient {
|
|
32
|
+
private config;
|
|
33
|
+
private activeMounts;
|
|
34
|
+
constructor(config?: SshfsMountConfig);
|
|
35
|
+
/**
|
|
36
|
+
* Check if sshfs is available
|
|
37
|
+
*/
|
|
38
|
+
checkDependency(): Promise<{
|
|
39
|
+
available: boolean;
|
|
40
|
+
version?: string;
|
|
41
|
+
error?: string;
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Mount a remote filesystem
|
|
45
|
+
*/
|
|
46
|
+
mount(params: MountParams): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Unmount a filesystem
|
|
49
|
+
*/
|
|
50
|
+
unmount(mountPoint: string): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Check if a mount point is currently mounted
|
|
53
|
+
*/
|
|
54
|
+
isMounted(mountPoint: string): Promise<boolean>;
|
|
55
|
+
/**
|
|
56
|
+
* Unmount all active mounts
|
|
57
|
+
*/
|
|
58
|
+
unmountAll(): Promise<void>;
|
|
59
|
+
private execMount;
|
|
60
|
+
private execCommand;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=sshfs-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sshfs-client.d.ts","sourceRoot":"","sources":["../../src/executor/sshfs-client.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAcD;;;;GAIG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,YAAY,CAAkC;gBAE1C,MAAM,CAAC,EAAE,gBAAgB;IAIrC;;OAEG;IACG,eAAe,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAoC1F;;OAEG;IACG,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IA+D/C;;OAEG;IACG,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgChD;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUrD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAMjC,OAAO,CAAC,SAAS;IAyDjB,OAAO,CAAC,WAAW;CAepB"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { writeFile, unlink, mkdir, stat } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { MountFailedError, DependencyMissingError } from '@awcp/core';
|
|
5
|
+
const DEFAULT_TEMP_KEY_DIR = '/tmp/awcp/client-keys';
|
|
6
|
+
const DEFAULT_MOUNT_TIMEOUT = 30000;
|
|
7
|
+
/**
|
|
8
|
+
* SSHFS Mount Client
|
|
9
|
+
*
|
|
10
|
+
* Handles mounting remote filesystems via SSHFS on the Remote side.
|
|
11
|
+
*/
|
|
12
|
+
export class SshfsMountClient {
|
|
13
|
+
config;
|
|
14
|
+
activeMounts = new Map();
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.config = config ?? {};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Check if sshfs is available
|
|
20
|
+
*/
|
|
21
|
+
async checkDependency() {
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
const proc = spawn('sshfs', ['--version']);
|
|
24
|
+
let output = '';
|
|
25
|
+
proc.stdout.on('data', (data) => {
|
|
26
|
+
output += data.toString();
|
|
27
|
+
});
|
|
28
|
+
proc.stderr.on('data', (data) => {
|
|
29
|
+
output += data.toString();
|
|
30
|
+
});
|
|
31
|
+
proc.on('close', (code) => {
|
|
32
|
+
if (code === 0 || output.includes('SSHFS')) {
|
|
33
|
+
const versionMatch = output.match(/SSHFS version ([\d.]+)/);
|
|
34
|
+
resolve({
|
|
35
|
+
available: true,
|
|
36
|
+
version: versionMatch?.[1],
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
resolve({
|
|
41
|
+
available: false,
|
|
42
|
+
error: 'sshfs not found',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
proc.on('error', () => {
|
|
47
|
+
resolve({
|
|
48
|
+
available: false,
|
|
49
|
+
error: 'sshfs not found in PATH',
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Mount a remote filesystem
|
|
56
|
+
*/
|
|
57
|
+
async mount(params) {
|
|
58
|
+
// Check dependency
|
|
59
|
+
const depCheck = await this.checkDependency();
|
|
60
|
+
if (!depCheck.available) {
|
|
61
|
+
throw new DependencyMissingError('sshfs', 'Install sshfs: brew install macfuse && brew install sshfs (macOS) or apt install sshfs (Linux)');
|
|
62
|
+
}
|
|
63
|
+
const tempKeyDir = this.config.tempKeyDir ?? DEFAULT_TEMP_KEY_DIR;
|
|
64
|
+
await mkdir(tempKeyDir, { recursive: true });
|
|
65
|
+
// Write credential to temp file
|
|
66
|
+
const keyPath = join(tempKeyDir, `mount-${Date.now()}`);
|
|
67
|
+
await writeFile(keyPath, params.credential, { mode: 0o600 });
|
|
68
|
+
try {
|
|
69
|
+
// Build sshfs command
|
|
70
|
+
const { host, port, user } = params.endpoint;
|
|
71
|
+
const remoteSpec = `${user}@${host}:${params.exportLocator}`;
|
|
72
|
+
const options = {
|
|
73
|
+
...this.config.defaultOptions,
|
|
74
|
+
...params.options,
|
|
75
|
+
};
|
|
76
|
+
const args = [
|
|
77
|
+
remoteSpec,
|
|
78
|
+
params.mountPoint,
|
|
79
|
+
'-o', `IdentityFile=${keyPath}`,
|
|
80
|
+
'-o', `Port=${port}`,
|
|
81
|
+
'-o', 'StrictHostKeyChecking=no',
|
|
82
|
+
'-o', 'UserKnownHostsFile=/dev/null',
|
|
83
|
+
'-o', 'reconnect',
|
|
84
|
+
'-o', 'ServerAliveInterval=15',
|
|
85
|
+
'-o', 'ServerAliveCountMax=3',
|
|
86
|
+
'-o', 'noappledouble', // Prevent macOS ._* metadata files
|
|
87
|
+
];
|
|
88
|
+
// Add custom options
|
|
89
|
+
for (const [key, value] of Object.entries(options)) {
|
|
90
|
+
args.push('-o', `${key}=${value}`);
|
|
91
|
+
}
|
|
92
|
+
console.log(`[SSHFS] Mounting: sshfs ${args.join(' ')}`);
|
|
93
|
+
// Execute mount (SSHFS will daemonize and exit immediately on success)
|
|
94
|
+
await this.execMount(args, params.mountPoint);
|
|
95
|
+
// Track active mount
|
|
96
|
+
this.activeMounts.set(params.mountPoint, {
|
|
97
|
+
mountPoint: params.mountPoint,
|
|
98
|
+
keyPath,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
// Cleanup key on failure
|
|
103
|
+
await unlink(keyPath).catch(() => { });
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Unmount a filesystem
|
|
109
|
+
*/
|
|
110
|
+
async unmount(mountPoint) {
|
|
111
|
+
const activeMount = this.activeMounts.get(mountPoint);
|
|
112
|
+
// Try different unmount methods
|
|
113
|
+
const unmountCommands = [
|
|
114
|
+
['umount', mountPoint],
|
|
115
|
+
['fusermount', '-u', mountPoint],
|
|
116
|
+
['diskutil', 'unmount', mountPoint], // macOS
|
|
117
|
+
];
|
|
118
|
+
let success = false;
|
|
119
|
+
for (const cmd of unmountCommands) {
|
|
120
|
+
try {
|
|
121
|
+
await this.execCommand(cmd[0], cmd.slice(1));
|
|
122
|
+
success = true;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Try next method
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!success) {
|
|
130
|
+
console.warn(`Failed to unmount ${mountPoint}, may need manual cleanup`);
|
|
131
|
+
}
|
|
132
|
+
// Cleanup
|
|
133
|
+
if (activeMount) {
|
|
134
|
+
await unlink(activeMount.keyPath).catch(() => { });
|
|
135
|
+
this.activeMounts.delete(mountPoint);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Check if a mount point is currently mounted
|
|
140
|
+
*/
|
|
141
|
+
async isMounted(mountPoint) {
|
|
142
|
+
try {
|
|
143
|
+
// Check if the mount point exists and is tracked
|
|
144
|
+
await stat(mountPoint);
|
|
145
|
+
return this.activeMounts.has(mountPoint);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Unmount all active mounts
|
|
153
|
+
*/
|
|
154
|
+
async unmountAll() {
|
|
155
|
+
for (const mountPoint of this.activeMounts.keys()) {
|
|
156
|
+
await this.unmount(mountPoint);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
execMount(args, mountPoint) {
|
|
160
|
+
const timeout = this.config.mountTimeout ?? DEFAULT_MOUNT_TIMEOUT;
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
// SSHFS without -f will daemonize and exit immediately with code 0 on success
|
|
163
|
+
const proc = spawn('sshfs', args, {
|
|
164
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
165
|
+
});
|
|
166
|
+
let stderr = '';
|
|
167
|
+
proc.stderr.on('data', (data) => {
|
|
168
|
+
stderr += data.toString();
|
|
169
|
+
});
|
|
170
|
+
const timer = setTimeout(async () => {
|
|
171
|
+
proc.kill();
|
|
172
|
+
// Clean up possible zombie mount
|
|
173
|
+
try {
|
|
174
|
+
await this.unmount(mountPoint);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// Ignore - mount may not exist
|
|
178
|
+
}
|
|
179
|
+
reject(new MountFailedError(`Mount timeout after ${timeout}ms`));
|
|
180
|
+
}, timeout);
|
|
181
|
+
proc.on('close', async (code) => {
|
|
182
|
+
clearTimeout(timer);
|
|
183
|
+
if (code !== 0) {
|
|
184
|
+
reject(new MountFailedError(stderr || `sshfs exited with code ${code}`));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
// SSHFS daemonized successfully (exit code 0)
|
|
188
|
+
// Now verify the mount is actually there
|
|
189
|
+
try {
|
|
190
|
+
const mountStat = await stat(mountPoint);
|
|
191
|
+
const parentStat = await stat(join(mountPoint, '..'));
|
|
192
|
+
if (mountStat.dev !== parentStat.dev) {
|
|
193
|
+
// Different device = mounted successfully
|
|
194
|
+
resolve();
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
reject(new MountFailedError('SSHFS exited but mount not detected'));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
reject(new MountFailedError(`Mount verification failed: ${error}`));
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
proc.on('error', (error) => {
|
|
205
|
+
clearTimeout(timer);
|
|
206
|
+
reject(new MountFailedError(error.message));
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
execCommand(cmd, args) {
|
|
211
|
+
return new Promise((resolve, reject) => {
|
|
212
|
+
const proc = spawn(cmd, args);
|
|
213
|
+
proc.on('close', (code) => {
|
|
214
|
+
if (code === 0) {
|
|
215
|
+
resolve();
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
reject(new Error(`${cmd} exited with code ${code}`));
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
proc.on('error', reject);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=sshfs-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sshfs-client.js","sourceRoot":"","sources":["../../src/executor/sshfs-client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @awcp/transport-sshfs
|
|
3
|
+
*
|
|
4
|
+
* SSHFS Transport implementation for AWCP data plane
|
|
5
|
+
*/
|
|
6
|
+
export { CredentialManager, type CredentialManagerConfig, type GeneratedCredential, } from './delegator/index.js';
|
|
7
|
+
export { SshfsMountClient, type SshfsMountConfig, type MountParams, } from './executor/index.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,iBAAiB,EACjB,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,GACzB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,KAAK,WAAW,GACjB,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @awcp/transport-sshfs
|
|
3
|
+
*
|
|
4
|
+
* SSHFS Transport implementation for AWCP data plane
|
|
5
|
+
*/
|
|
6
|
+
// Delegator-side exports
|
|
7
|
+
export { CredentialManager, } from './delegator/index.js';
|
|
8
|
+
// Executor-side exports
|
|
9
|
+
export { SshfsMountClient, } from './executor/index.js';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,yBAAyB;AACzB,OAAO,EACL,iBAAiB,GAGlB,MAAM,sBAAsB,CAAC;AAE9B,wBAAwB;AACxB,OAAO,EACL,gBAAgB,GAGjB,MAAM,qBAAqB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@awcp/transport-sshfs",
|
|
3
|
+
"version": "0.0.0-dev-202601300724",
|
|
4
|
+
"description": "AWCP SSHFS Transport - SSH/SFTP based data plane implementation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./host": {
|
|
14
|
+
"types": "./dist/host/index.d.ts",
|
|
15
|
+
"import": "./dist/host/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./remote": {
|
|
18
|
+
"types": "./dist/remote/index.d.ts",
|
|
19
|
+
"import": "./dist/remote/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc -p tsconfig.build.json",
|
|
28
|
+
"clean": "rm -rf dist *.tsbuildinfo",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"test:watch": "vitest"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@awcp/core": "0.0.0-dev-202601300724"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"awcp",
|
|
37
|
+
"sshfs",
|
|
38
|
+
"transport",
|
|
39
|
+
"sftp",
|
|
40
|
+
"mount"
|
|
41
|
+
],
|
|
42
|
+
"license": "Apache-2.0",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/anthropics/awcp.git",
|
|
46
|
+
"directory": "packages/transport-sshfs"
|
|
47
|
+
}
|
|
48
|
+
}
|