@nightowne/tas-cli 3.0.1 → 3.0.3
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/CHANGELOG.md +13 -0
- package/FAQ.md +4 -0
- package/QUICKSTART.md +2 -0
- package/README.md +11 -1
- package/package.json +1 -1
- package/src/cli.js +7 -1
- package/src/fuse/macfuse.cjs +32 -2
- package/src/fuse/mount.js +186 -11
- package/src/utils/branding.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to TAS (Telegram as Storage) will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [3.0.3] - 2026-09-25
|
|
6
|
+
|
|
7
|
+
### Fixed — desktop and SMB mount interoperability (#3)
|
|
8
|
+
- **Complete directory surface** — FUSE now implements `statfs`, `access`, `opendir`, `releasedir`, flush/sync callbacks, stable inode metadata, and session extended attributes used by Linux file managers and Samba.
|
|
9
|
+
- **Secure shared mounts** — `tas mount <path> --allow-other` opts into access by other local users and services. On Linux TAS requires `user_allow_other` in `/etc/fuse.conf`; private single-user mounts remain the default.
|
|
10
|
+
- **Usable capacity reporting** — desktop clients receive real local staging-disk statistics rather than an empty or unsupported filesystem response. This is not presented as a Telegram quota.
|
|
11
|
+
- **Version consistency** — the CLI banner now matches the package version.
|
|
12
|
+
|
|
13
|
+
## [3.0.2] - 2026-09-25
|
|
14
|
+
|
|
15
|
+
### Fixed — Apple Silicon install recovery
|
|
16
|
+
- **Optional native source recovery** — if the legacy optional addon fails before TAS can patch it on arm64, postinstall restores only its source without lifecycle scripts, then rebuilds it against the user's installed macFUSE library. Failed rebuilds still leave every non-mount TAS command usable.
|
|
17
|
+
|
|
5
18
|
## [3.0.1] - 2026-09-25
|
|
6
19
|
|
|
7
20
|
### Fixed — current macFUSE mount support (#4)
|
package/FAQ.md
CHANGED
|
@@ -31,3 +31,7 @@ No. Each chunk has one owning bot. TAS records that bot so reads and deletes rou
|
|
|
31
31
|
## Does mount work on macOS?
|
|
32
32
|
|
|
33
33
|
Yes with current macFUSE, Xcode Command Line Tools, and a locally rebuilt native addon. TAS never installs or replaces macFUSE; its install hook selects the system `libfuse` and compiles the optional addon for the active CPU. Run `tas doctor` before use. If the real mount/readdir/unmount smoke test does not pass, do not mount data.
|
|
34
|
+
|
|
35
|
+
## Why can I list a mount locally but not open its Samba share?
|
|
36
|
+
|
|
37
|
+
FUSE mounts are private to the mounting user by default. Add `user_allow_other` to `/etc/fuse.conf`, then mount with `tas mount /mnt/tg-drive --allow-other`. TAS keeps this opt-in because it grants other local users and services access subject to Unix permissions. Version 3.0.3 also implements the filesystem statistics, directory handles, sync operations, and extended attributes expected by desktop and SMB clients.
|
package/QUICKSTART.md
CHANGED
|
@@ -45,3 +45,5 @@ Multi-bot mode distributes chunks. It is not redundancy, quota assurance, ban pr
|
|
|
45
45
|
## Mount support
|
|
46
46
|
|
|
47
47
|
`tas mount` needs a native FUSE runtime. On Linux install `fuse` and `libfuse-dev`. On macOS install current macFUSE and Xcode Command Line Tools before installing TAS; TAS rebuilds its optional native addon against the installed macFUSE library. Run `tas doctor` first and mount only when its real mount/readdir/unmount smoke test passes.
|
|
48
|
+
|
|
49
|
+
For Samba or another service account, add `user_allow_other` to `/etc/fuse.conf` and mount with `tas mount /mnt/tg-drive --allow-other`. Shared access is opt-in because it exposes the mount to other local users subject to Unix permissions.
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<a href="https://github.com/ixchio/tas/network/members"><img src="https://img.shields.io/github/forks/ixchio/tas?style=social" alt="GitHub Forks"></a>
|
|
19
19
|
<img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen?logo=node.js" alt="Node.js >= 18">
|
|
20
20
|
<img src="https://img.shields.io/badge/encryption-AES--256--GCM-blueviolet?logo=shield" alt="AES-256-GCM">
|
|
21
|
-
<img src="https://img.shields.io/badge/tests-
|
|
21
|
+
<img src="https://img.shields.io/badge/tests-104%20passing-success" alt="104 Tests Passing">
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
24
|
<p align="center">
|
|
@@ -115,6 +115,15 @@ tas unmount ~/cloud # Clean unmount when done
|
|
|
115
115
|
|
|
116
116
|
> **Native FUSE is verified, not assumed:** Linux needs `fuse`/`libfuse-dev`. macOS uses current macFUSE and rebuilds the optional native addon against the installed system library on first install. Install Xcode Command Line Tools before TAS, then run `tas doctor`; mount is available only when its real mount → readdir → unmount smoke test passes.
|
|
117
117
|
|
|
118
|
+
Desktop file managers work with the normal private mount. Samba and other services run under a different local identity, so shared access must be enabled explicitly:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# Add this exact line to /etc/fuse.conf first: user_allow_other
|
|
122
|
+
tas mount /mnt/tg-drive --allow-other
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`--allow-other` exposes the mount to other local users subject to Unix permissions. Do not enable it on an untrusted multi-user host.
|
|
126
|
+
|
|
118
127
|
---
|
|
119
128
|
|
|
120
129
|
### 🔄 Auto-Sync Folders (Dropbox-style)
|
|
@@ -353,6 +362,7 @@ tas bot add|list|enable|disable|remove # 🤖 Manage experimental bot pool
|
|
|
353
362
|
```bash
|
|
354
363
|
# FUSE Mount (Linux/libfuse or macOS/current macFUSE)
|
|
355
364
|
tas mount <path> # Mount Telegram storage as a local folder
|
|
365
|
+
tas mount <path> --allow-other # Opt in to Samba/service access
|
|
356
366
|
tas unmount <path> # Clean unmount
|
|
357
367
|
|
|
358
368
|
# Dropbox-style Folder Sync
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -816,6 +816,7 @@ program
|
|
|
816
816
|
.command('mount <mountpoint>')
|
|
817
817
|
.description('🔥 Mount Telegram storage as a local folder (Linux FUSE or current macFUSE)')
|
|
818
818
|
.option('-p, --password <password>', 'Encryption password (uses TAS_PASSWORD env var if not provided)')
|
|
819
|
+
.option('--allow-other', 'Allow other local users and services such as Samba to access the mount')
|
|
819
820
|
.action(async (mountpoint, options) => {
|
|
820
821
|
console.log(chalk.cyan('\n🗂️ Mounting Telegram as filesystem...\n'));
|
|
821
822
|
|
|
@@ -836,7 +837,8 @@ program
|
|
|
836
837
|
dataDir: DATA_DIR,
|
|
837
838
|
password,
|
|
838
839
|
config,
|
|
839
|
-
mountPoint: absMount
|
|
840
|
+
mountPoint: absMount,
|
|
841
|
+
allowOther: options.allowOther
|
|
840
842
|
});
|
|
841
843
|
|
|
842
844
|
await tfs.initialize();
|
|
@@ -851,6 +853,9 @@ program
|
|
|
851
853
|
console.log(chalk.dim(` cat ${absMount}/file.txt # Read`));
|
|
852
854
|
console.log(chalk.dim(` rm ${absMount}/file.pdf # Delete`));
|
|
853
855
|
console.log();
|
|
856
|
+
if (options.allowOther) {
|
|
857
|
+
console.log(chalk.yellow('Shared access is enabled; Unix file permissions still apply'));
|
|
858
|
+
}
|
|
854
859
|
console.log(chalk.yellow('Press Ctrl+C to unmount'));
|
|
855
860
|
|
|
856
861
|
// Handle graceful shutdown
|
|
@@ -873,6 +878,7 @@ program
|
|
|
873
878
|
console.log(chalk.dim(' Ubuntu/Debian: sudo apt install fuse libfuse-dev'));
|
|
874
879
|
console.log(chalk.dim(' Fedora: sudo dnf install fuse fuse-devel'));
|
|
875
880
|
console.log(chalk.dim(' macOS: install current macFUSE and Xcode Command Line Tools, then reinstall TAS\n'));
|
|
881
|
+
console.log(chalk.dim(' Samba: enable user_allow_other in /etc/fuse.conf and mount with --allow-other\n'));
|
|
876
882
|
process.exit(1);
|
|
877
883
|
}
|
|
878
884
|
});
|
package/src/fuse/macfuse.cjs
CHANGED
|
@@ -84,6 +84,23 @@ function getDarwinLibraryDirectory (packageRoot) {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
function installOptionalFuseNative (packageRoot) {
|
|
88
|
+
const npmCli = process.env.npm_execpath
|
|
89
|
+
if (!npmCli) return { ok: false, reason: 'npm did not provide its install executable' }
|
|
90
|
+
|
|
91
|
+
const install = childProcess.spawnSync(
|
|
92
|
+
process.execPath,
|
|
93
|
+
[npmCli, 'install', 'fuse-native@2.2.6', '--no-save', '--ignore-scripts', '--no-package-lock'],
|
|
94
|
+
{
|
|
95
|
+
cwd: packageRoot,
|
|
96
|
+
stdio: 'inherit',
|
|
97
|
+
env: { ...process.env, npm_config_ignore_scripts: 'true' }
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
if (install.status !== 0) return { ok: false, reason: 'could not restore optional fuse-native source' }
|
|
101
|
+
return { ok: true }
|
|
102
|
+
}
|
|
103
|
+
|
|
87
104
|
function validateMacFuseNativeBinding ({
|
|
88
105
|
packageRoot = path.resolve(__dirname, '..', '..'),
|
|
89
106
|
exists = fs.existsSync,
|
|
@@ -184,8 +201,20 @@ function installMacFuseAdapter ({
|
|
|
184
201
|
return { skipped: true, ...installation }
|
|
185
202
|
}
|
|
186
203
|
|
|
187
|
-
|
|
188
|
-
|
|
204
|
+
let fuseNativeDir = getFuseNativeDirectory(packageRoot)
|
|
205
|
+
let darwinLibraryDir = getDarwinLibraryDirectory(packageRoot)
|
|
206
|
+
if (!fuseNativeDir || !darwinLibraryDir) {
|
|
207
|
+
// The legacy optional addon can fail to build on arm64 before our root
|
|
208
|
+
// postinstall runs. Restore its source without lifecycle scripts, then
|
|
209
|
+
// patch and rebuild it against the system macFUSE library below.
|
|
210
|
+
const restored = installOptionalFuseNative(packageRoot)
|
|
211
|
+
if (!restored.ok) {
|
|
212
|
+
log.warn(`[tas] macOS FUSE was not built: ${restored.reason}`)
|
|
213
|
+
return { ready: false, reason: restored.reason }
|
|
214
|
+
}
|
|
215
|
+
fuseNativeDir = getFuseNativeDirectory(packageRoot)
|
|
216
|
+
darwinLibraryDir = getDarwinLibraryDirectory(packageRoot)
|
|
217
|
+
}
|
|
189
218
|
if (!fuseNativeDir || !darwinLibraryDir) {
|
|
190
219
|
const reason = 'optional fuse-native dependencies are not installed'
|
|
191
220
|
log.warn(`[tas] macOS FUSE was not built: ${reason}`)
|
|
@@ -228,6 +257,7 @@ module.exports = {
|
|
|
228
257
|
MACFUSE_BUNDLE,
|
|
229
258
|
findMacFuseInstallation,
|
|
230
259
|
getFuseNativeDirectory,
|
|
260
|
+
installOptionalFuseNative,
|
|
231
261
|
validateMacFuseNativeBinding,
|
|
232
262
|
installMacFuseAdapter
|
|
233
263
|
}
|
package/src/fuse/mount.js
CHANGED
|
@@ -45,6 +45,22 @@ function withTimeout(promise, ms, label) {
|
|
|
45
45
|
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function pathInode(logicalPath) {
|
|
49
|
+
if (!logicalPath) return 1;
|
|
50
|
+
let hash = 2166136261;
|
|
51
|
+
for (let index = 0; index < logicalPath.length; index++) {
|
|
52
|
+
hash ^= logicalPath.charCodeAt(index);
|
|
53
|
+
hash = Math.imul(hash, 16777619);
|
|
54
|
+
}
|
|
55
|
+
return (hash >>> 0) || 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function fuseStatNumber(value, fallback = 0) {
|
|
59
|
+
const number = Number(value);
|
|
60
|
+
if (!Number.isFinite(number) || number < 0) return fallback;
|
|
61
|
+
return Math.min(Math.floor(number), 0xffffffff);
|
|
62
|
+
}
|
|
63
|
+
|
|
48
64
|
/** Verify the native FUSE stack, not merely that the JS module imports. */
|
|
49
65
|
export async function checkFuseRuntime() {
|
|
50
66
|
if (process.platform === 'darwin') {
|
|
@@ -113,7 +129,9 @@ export class TelegramFS {
|
|
|
113
129
|
this.password = options.password;
|
|
114
130
|
this.config = options.config;
|
|
115
131
|
this.mountPoint = options.mountPoint;
|
|
132
|
+
this.allowOther = options.allowOther === true;
|
|
116
133
|
this.backupManifest = options.backupManifest || backupRemoteManifest;
|
|
134
|
+
this.mountStartedAt = new Date();
|
|
117
135
|
|
|
118
136
|
this.db = new FileIndex(path.join(this.dataDir, 'index.db'));
|
|
119
137
|
this.db.init();
|
|
@@ -131,6 +149,7 @@ export class TelegramFS {
|
|
|
131
149
|
this.fileByLogicalPath = new Map();
|
|
132
150
|
this.implicitDirs = new Set(['']);
|
|
133
151
|
this.childrenByDir = new Map();
|
|
152
|
+
this.xattrs = new Map();
|
|
134
153
|
this._refreshPathIndex();
|
|
135
154
|
}
|
|
136
155
|
|
|
@@ -185,6 +204,10 @@ export class TelegramFS {
|
|
|
185
204
|
return this.fileByLogicalPath.get(logicalPath);
|
|
186
205
|
}
|
|
187
206
|
|
|
207
|
+
_exists(logicalPath) {
|
|
208
|
+
return this._isDirectory(logicalPath) || this.writeBuffers.has(logicalPath) || Boolean(this._file(logicalPath));
|
|
209
|
+
}
|
|
210
|
+
|
|
188
211
|
_newWritePath(logicalPath) {
|
|
189
212
|
const dir = path.join(this.dataDir, 'fuse-writes');
|
|
190
213
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -220,13 +243,17 @@ export class TelegramFS {
|
|
|
220
243
|
|
|
221
244
|
if (this._isDirectory(logical)) {
|
|
222
245
|
return cb(0, {
|
|
223
|
-
mtime:
|
|
224
|
-
atime:
|
|
225
|
-
ctime:
|
|
246
|
+
mtime: this.mountStartedAt,
|
|
247
|
+
atime: this.mountStartedAt,
|
|
248
|
+
ctime: this.mountStartedAt,
|
|
226
249
|
size: 4096,
|
|
227
250
|
mode: 0o40755, // directory
|
|
228
251
|
uid: process.getuid?.() || 0,
|
|
229
|
-
gid: process.getgid?.() || 0
|
|
252
|
+
gid: process.getgid?.() || 0,
|
|
253
|
+
nlink: 2,
|
|
254
|
+
ino: pathInode(logical),
|
|
255
|
+
blksize: 4096,
|
|
256
|
+
blocks: 1
|
|
230
257
|
});
|
|
231
258
|
}
|
|
232
259
|
|
|
@@ -241,7 +268,10 @@ export class TelegramFS {
|
|
|
241
268
|
size: stats.size,
|
|
242
269
|
mode: 0o100644, // regular file
|
|
243
270
|
uid: process.getuid?.() || 0,
|
|
244
|
-
gid: process.getgid?.() || 0
|
|
271
|
+
gid: process.getgid?.() || 0,
|
|
272
|
+
ino: pathInode(logical),
|
|
273
|
+
blksize: 4096,
|
|
274
|
+
blocks: Math.ceil(stats.size / 512)
|
|
245
275
|
});
|
|
246
276
|
}
|
|
247
277
|
|
|
@@ -259,7 +289,10 @@ export class TelegramFS {
|
|
|
259
289
|
size: file.original_size,
|
|
260
290
|
mode: 0o100644, // regular file
|
|
261
291
|
uid: process.getuid?.() || 0,
|
|
262
|
-
gid: process.getgid?.() || 0
|
|
292
|
+
gid: process.getgid?.() || 0,
|
|
293
|
+
ino: pathInode(logical),
|
|
294
|
+
blksize: 4096,
|
|
295
|
+
blocks: Math.ceil(file.original_size / 512)
|
|
263
296
|
});
|
|
264
297
|
}
|
|
265
298
|
|
|
@@ -278,6 +311,109 @@ export class TelegramFS {
|
|
|
278
311
|
return cb(0, [...names].sort((a, b) => a.localeCompare(b)));
|
|
279
312
|
}
|
|
280
313
|
|
|
314
|
+
/** Report the local staging disk because Telegram exposes no storage quota. */
|
|
315
|
+
statfs(filepath, cb) {
|
|
316
|
+
try {
|
|
317
|
+
const stats = typeof fs.statfsSync === 'function' ? fs.statfsSync(this.dataDir) : null;
|
|
318
|
+
const fileCount = this.filePaths.size + this.virtualDirs.size + this.writeBuffers.size + 1;
|
|
319
|
+
return cb(0, {
|
|
320
|
+
bsize: fuseStatNumber(stats?.bsize, 4096),
|
|
321
|
+
frsize: fuseStatNumber(stats?.bsize, 4096),
|
|
322
|
+
blocks: fuseStatNumber(stats?.blocks, 1),
|
|
323
|
+
bfree: fuseStatNumber(stats?.bfree, 1),
|
|
324
|
+
bavail: fuseStatNumber(stats?.bavail, 1),
|
|
325
|
+
files: fuseStatNumber(stats?.files, fileCount),
|
|
326
|
+
ffree: fuseStatNumber(stats?.ffree, 0),
|
|
327
|
+
favail: fuseStatNumber(stats?.ffree, 0),
|
|
328
|
+
fsid: 0,
|
|
329
|
+
flag: 0,
|
|
330
|
+
namemax: 255
|
|
331
|
+
});
|
|
332
|
+
} catch (error) {
|
|
333
|
+
return cb(Fuse.EIO);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
access(filepath, mode, cb) {
|
|
338
|
+
let logical;
|
|
339
|
+
try { logical = this._logical(filepath, true); } catch { return cb(Fuse.ENOENT); }
|
|
340
|
+
return cb(this._exists(logical) ? 0 : Fuse.ENOENT);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
opendir(filepath, flags, cb) {
|
|
344
|
+
let logical;
|
|
345
|
+
try { logical = this._logical(filepath, true); } catch { return cb(Fuse.ENOENT); }
|
|
346
|
+
return this._isDirectory(logical) ? cb(0, pathInode(logical)) : cb(Fuse.ENOTDIR);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
releasedir(filepath, fd, cb) {
|
|
350
|
+
return cb(0);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
flush(filepath, fd, cb) {
|
|
354
|
+
return cb(0);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
fsync(filepath, datasync, fd, cb) {
|
|
358
|
+
return cb(0);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
fsyncdir(filepath, datasync, fd, cb) {
|
|
362
|
+
return cb(0);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
utimens(filepath, atime, mtime, cb) {
|
|
366
|
+
let logical;
|
|
367
|
+
try { logical = this._logical(filepath, true); } catch { return cb(Fuse.ENOENT); }
|
|
368
|
+
return cb(this._exists(logical) ? 0 : Fuse.ENOENT);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
chmod(filepath, mode, cb) {
|
|
372
|
+
let logical;
|
|
373
|
+
try { logical = this._logical(filepath, true); } catch { return cb(Fuse.ENOENT); }
|
|
374
|
+
return cb(this._exists(logical) ? 0 : Fuse.ENOENT);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
chown(filepath, uid, gid, cb) {
|
|
378
|
+
let logical;
|
|
379
|
+
try { logical = this._logical(filepath, true); } catch { return cb(Fuse.ENOENT); }
|
|
380
|
+
return cb(this._exists(logical) ? 0 : Fuse.ENOENT);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
getxattr(filepath, name, position, cb) {
|
|
384
|
+
let logical;
|
|
385
|
+
try { logical = this._logical(filepath, true); } catch { return cb(Fuse.ENOENT); }
|
|
386
|
+
if (!this._exists(logical)) return cb(Fuse.ENOENT);
|
|
387
|
+
const value = this.xattrs.get(logical)?.get(name);
|
|
388
|
+
return value ? cb(0, Buffer.from(value)) : cb(Fuse.ENODATA);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
setxattr(filepath, name, value, position, flags, cb) {
|
|
392
|
+
let logical;
|
|
393
|
+
try { logical = this._logical(filepath, true); } catch { return cb(Fuse.ENOENT); }
|
|
394
|
+
if (!this._exists(logical)) return cb(Fuse.ENOENT);
|
|
395
|
+
if (!this.xattrs.has(logical)) this.xattrs.set(logical, new Map());
|
|
396
|
+
this.xattrs.get(logical).set(name, Buffer.from(value));
|
|
397
|
+
return cb(0);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
listxattr(filepath, cb) {
|
|
401
|
+
let logical;
|
|
402
|
+
try { logical = this._logical(filepath, true); } catch { return cb(Fuse.ENOENT); }
|
|
403
|
+
if (!this._exists(logical)) return cb(Fuse.ENOENT);
|
|
404
|
+
return cb(0, [...(this.xattrs.get(logical)?.keys() || [])]);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
removexattr(filepath, name, cb) {
|
|
408
|
+
let logical;
|
|
409
|
+
try { logical = this._logical(filepath, true); } catch { return cb(Fuse.ENOENT); }
|
|
410
|
+
if (!this._exists(logical)) return cb(Fuse.ENOENT);
|
|
411
|
+
const attributes = this.xattrs.get(logical);
|
|
412
|
+
if (!attributes?.delete(name)) return cb(Fuse.ENODATA);
|
|
413
|
+
if (attributes.size === 0) this.xattrs.delete(logical);
|
|
414
|
+
return cb(0);
|
|
415
|
+
}
|
|
416
|
+
|
|
281
417
|
/**
|
|
282
418
|
* Open a file (just validates it exists)
|
|
283
419
|
*/
|
|
@@ -398,6 +534,7 @@ export class TelegramFS {
|
|
|
398
534
|
|
|
399
535
|
// Invalidate cache
|
|
400
536
|
this.invalidateCache(logical);
|
|
537
|
+
this.xattrs.delete(logical);
|
|
401
538
|
|
|
402
539
|
return cb(0);
|
|
403
540
|
} catch (err) {
|
|
@@ -517,6 +654,11 @@ export class TelegramFS {
|
|
|
517
654
|
fileCache.delete(oldName);
|
|
518
655
|
fileCache.set(newName, cached);
|
|
519
656
|
}
|
|
657
|
+
const attributes = this.xattrs.get(oldName);
|
|
658
|
+
if (attributes) {
|
|
659
|
+
this.xattrs.delete(oldName);
|
|
660
|
+
this.xattrs.set(newName, attributes);
|
|
661
|
+
}
|
|
520
662
|
|
|
521
663
|
return cb(0);
|
|
522
664
|
}
|
|
@@ -539,6 +681,7 @@ export class TelegramFS {
|
|
|
539
681
|
return cb(Fuse.ENOTEMPTY || Fuse.EINVAL);
|
|
540
682
|
}
|
|
541
683
|
this.virtualDirs.delete(logical);
|
|
684
|
+
this.xattrs.delete(logical);
|
|
542
685
|
return cb(0);
|
|
543
686
|
}
|
|
544
687
|
|
|
@@ -679,10 +822,32 @@ export class TelegramFS {
|
|
|
679
822
|
}
|
|
680
823
|
}
|
|
681
824
|
|
|
825
|
+
_mountOptions() {
|
|
826
|
+
return {
|
|
827
|
+
debug: false,
|
|
828
|
+
force: true,
|
|
829
|
+
mkdir: true,
|
|
830
|
+
allowOther: this.allowOther,
|
|
831
|
+
defaultPermissions: true,
|
|
832
|
+
fsname: 'tas',
|
|
833
|
+
subtype: 'tas'
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
|
|
682
837
|
/**
|
|
683
838
|
* Mount the filesystem
|
|
684
839
|
*/
|
|
685
840
|
mount() {
|
|
841
|
+
if (this.allowOther && process.platform === 'linux' && (process.getuid?.() || 0) !== 0) {
|
|
842
|
+
let fuseConfig = '';
|
|
843
|
+
try { fuseConfig = fs.readFileSync('/etc/fuse.conf', 'utf8'); } catch { }
|
|
844
|
+
if (!/^\s*user_allow_other\s*$/m.test(fuseConfig)) {
|
|
845
|
+
throw new Error(
|
|
846
|
+
'--allow-other requires user_allow_other on its own line in /etc/fuse.conf'
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
686
851
|
// Ensure mount point exists
|
|
687
852
|
if (!fs.existsSync(this.mountPoint)) {
|
|
688
853
|
fs.mkdirSync(this.mountPoint, { recursive: true });
|
|
@@ -691,11 +856,25 @@ export class TelegramFS {
|
|
|
691
856
|
const ops = {
|
|
692
857
|
getattr: this.getattr.bind(this),
|
|
693
858
|
readdir: this.readdir.bind(this),
|
|
859
|
+
statfs: this.statfs.bind(this),
|
|
860
|
+
access: this.access.bind(this),
|
|
861
|
+
opendir: this.opendir.bind(this),
|
|
862
|
+
releasedir: this.releasedir.bind(this),
|
|
694
863
|
open: this.open.bind(this),
|
|
695
864
|
read: this.read.bind(this),
|
|
696
865
|
write: this.write.bind(this),
|
|
697
866
|
create: this.create.bind(this),
|
|
698
867
|
release: this.release.bind(this),
|
|
868
|
+
flush: this.flush.bind(this),
|
|
869
|
+
fsync: this.fsync.bind(this),
|
|
870
|
+
fsyncdir: this.fsyncdir.bind(this),
|
|
871
|
+
utimens: this.utimens.bind(this),
|
|
872
|
+
chmod: this.chmod.bind(this),
|
|
873
|
+
chown: this.chown.bind(this),
|
|
874
|
+
getxattr: this.getxattr.bind(this),
|
|
875
|
+
setxattr: this.setxattr.bind(this),
|
|
876
|
+
listxattr: this.listxattr.bind(this),
|
|
877
|
+
removexattr: this.removexattr.bind(this),
|
|
699
878
|
unlink: this.unlink.bind(this),
|
|
700
879
|
rename: this.rename.bind(this),
|
|
701
880
|
mkdir: this.mkdir.bind(this),
|
|
@@ -704,11 +883,7 @@ export class TelegramFS {
|
|
|
704
883
|
ftruncate: this.ftruncate.bind(this)
|
|
705
884
|
};
|
|
706
885
|
|
|
707
|
-
this.fuse = new Fuse(this.mountPoint, ops,
|
|
708
|
-
debug: false,
|
|
709
|
-
force: true,
|
|
710
|
-
mkdir: true
|
|
711
|
-
});
|
|
886
|
+
this.fuse = new Fuse(this.mountPoint, ops, this._mountOptions());
|
|
712
887
|
|
|
713
888
|
return new Promise((resolve, reject) => {
|
|
714
889
|
this.fuse.mount((err) => {
|