@nightowne/tas-cli 3.0.0 → 3.0.2
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 +1 -1
- package/QUICKSTART.md +1 -1
- package/README.md +6 -6
- package/package.json +4 -3
- package/src/cli.js +7 -6
- package/src/fuse/macfuse.cjs +265 -0
- package/src/fuse/mount.js +10 -8
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.2] - 2026-09-25
|
|
6
|
+
|
|
7
|
+
### Fixed — Apple Silicon install recovery
|
|
8
|
+
- **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.
|
|
9
|
+
|
|
10
|
+
## [3.0.1] - 2026-09-25
|
|
11
|
+
|
|
12
|
+
### Fixed — current macFUSE mount support (#4)
|
|
13
|
+
- **System macFUSE build** — TAS no longer accepts the bundled OSXFUSE 3 library. On macOS it detects `/Library/Filesystems/macfuse.fs`, selects the installed `libfuse` headers and library, and rebuilds the optional `fuse-native` addon for the active CPU.
|
|
14
|
+
- **Apple Silicon support path** — the rebuilt addon uses macFUSE's supported v2 API surface rather than the legacy binary with no arm64 slice. TAS never installs, replaces, or removes a FUSE kernel extension.
|
|
15
|
+
- **Strict runtime guard** — TAS inspects the loaded native addon and refuses mount if it still links to `libosxfuse` or lacks a system-macFUSE build. `tas doctor` then performs the real mount → readdir → unmount smoke test.
|
|
16
|
+
- **Install guidance** — macOS requires current macFUSE and Xcode Command Line Tools before TAS is installed. A failed native build leaves push, pull, sync, and share available while mount stays disabled.
|
|
17
|
+
|
|
5
18
|
## [3.0.0] - 2026-09-24
|
|
6
19
|
|
|
7
20
|
### Fixed — FUSE and path correctness
|
package/FAQ.md
CHANGED
|
@@ -30,4 +30,4 @@ No. Each chunk has one owning bot. TAS records that bot so reads and deletes rou
|
|
|
30
30
|
|
|
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.
|
package/QUICKSTART.md
CHANGED
|
@@ -44,4 +44,4 @@ Multi-bot mode distributes chunks. It is not redundancy, quota assurance, ban pr
|
|
|
44
44
|
|
|
45
45
|
## Mount support
|
|
46
46
|
|
|
47
|
-
`tas mount`
|
|
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.
|
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-101%20passing-success" alt="101 Tests Passing">
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
24
|
<p align="center">
|
|
@@ -113,7 +113,7 @@ cp report.pdf ~/cloud/ # Drop files in — auto-encrypted and uploaded
|
|
|
113
113
|
tas unmount ~/cloud # Clean unmount when done
|
|
114
114
|
```
|
|
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
118
|
---
|
|
119
119
|
|
|
@@ -193,7 +193,7 @@ tas doctor
|
|
|
193
193
|
# ✓ Database: 42 files, 1.3 GB total across 28 chunks
|
|
194
194
|
# ✓ Disk space: 50 GB free (32% used)
|
|
195
195
|
# ✓ Encryption: AES-256-GCM · PBKDF2-SHA512 · 600,000 iterations (OWASP 2025)
|
|
196
|
-
# ✓ FUSE runtime: mount → readdir → unmount passed
|
|
196
|
+
# ✓ FUSE runtime: mount → readdir → unmount passed
|
|
197
197
|
# ✓ Telegram connectivity: 2/2 bots OK
|
|
198
198
|
# ✨ All systems go!
|
|
199
199
|
```
|
|
@@ -319,7 +319,7 @@ Reliability mechanisms implemented by TAS (not an SLA):
|
|
|
319
319
|
| **Resume Uploads** | Network-stage chunks are staged on disk and persisted in `pending_uploads`; `tas resume` continues them |
|
|
320
320
|
| **Index Recovery** | Authenticated encrypted remote manifest; `tas index rebuild` restores file/chunk ownership |
|
|
321
321
|
| **Graceful Shutdown** | SIGINT/SIGTERM handled; staged chunks and SQLite WAL reduce partial-state risk |
|
|
322
|
-
| **Self-Diagnostics** | Checks config/database/chunk limits, all bots, and a real
|
|
322
|
+
| **Self-Diagnostics** | Checks config/database/chunk limits, all bots, and a real native FUSE smoke mount |
|
|
323
323
|
|
|
324
324
|
---
|
|
325
325
|
|
|
@@ -351,7 +351,7 @@ tas bot add|list|enable|disable|remove # 🤖 Manage experimental bot pool
|
|
|
351
351
|
<summary><strong>Mount & Sync</strong></summary>
|
|
352
352
|
|
|
353
353
|
```bash
|
|
354
|
-
# FUSE Mount (Linux
|
|
354
|
+
# FUSE Mount (Linux/libfuse or macOS/current macFUSE)
|
|
355
355
|
tas mount <path> # Mount Telegram storage as a local folder
|
|
356
356
|
tas unmount <path> # Clean unmount
|
|
357
357
|
|
|
@@ -462,7 +462,7 @@ Use TAS only at your own risk, do not use multiple bots to evade limits, follow
|
|
|
462
462
|
| 📌 **Not a replacement for backups** | Telegram can purge old messages. Use TAS alongside, not instead of, real backup solutions. |
|
|
463
463
|
| 📌 **19 MiB payload chunks** | Hosted Bot API uploads permit more, but `getFile` documents only 20 MB downloads. TAS stays below the read limit. |
|
|
464
464
|
| 📌 **Multi-bot is experimental** | It distributes chunks and preserves ownership mapping; it is not redundancy or ban protection. |
|
|
465
|
-
| 📌 **
|
|
465
|
+
| 📌 **macOS needs a native build** | Install current macFUSE and Xcode Command Line Tools before installing TAS. `tas doctor` must pass its real FUSE smoke test before you mount data. |
|
|
466
466
|
| 📌 **Recovery needs config** | `index.db` can be rebuilt from the encrypted manifest only if `config.json`, password, manifest message, and owning bot survive. |
|
|
467
467
|
| 📌 **No versioning (yet)** | Overwriting a file replaces the previous version. |
|
|
468
468
|
| 📌 **Internet required** | Telegram-backed — offline access requires files pulled locally first. |
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nightowne/tas-cli",
|
|
3
|
-
"version": "3.0.
|
|
4
|
-
"description": "Experimental local-first encrypted file transport over Telegram bots with resumable chunks, index recovery, and
|
|
3
|
+
"version": "3.0.2",
|
|
4
|
+
"description": "Experimental local-first encrypted file transport over Telegram bots with resumable chunks, index recovery, and FUSE mount",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"start": "node src/cli.js",
|
|
12
12
|
"init": "node src/cli.js init",
|
|
13
13
|
"test": "node --test tests/*.test.js",
|
|
14
|
-
"prepublishOnly": "npm test"
|
|
14
|
+
"prepublishOnly": "npm test",
|
|
15
|
+
"postinstall": "node src/fuse/macfuse.cjs"
|
|
15
16
|
},
|
|
16
17
|
"keywords": [
|
|
17
18
|
"telegram",
|
package/src/cli.js
CHANGED
|
@@ -814,7 +814,7 @@ program
|
|
|
814
814
|
// ============== MOUNT COMMAND ==============
|
|
815
815
|
program
|
|
816
816
|
.command('mount <mountpoint>')
|
|
817
|
-
.description('🔥 Mount Telegram storage as a local folder (Linux FUSE
|
|
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
819
|
.action(async (mountpoint, options) => {
|
|
820
820
|
console.log(chalk.cyan('\n🗂️ Mounting Telegram as filesystem...\n'));
|
|
@@ -872,7 +872,7 @@ program
|
|
|
872
872
|
console.log(chalk.dim('\nNote: FUSE requires libfuse to be installed:'));
|
|
873
873
|
console.log(chalk.dim(' Ubuntu/Debian: sudo apt install fuse libfuse-dev'));
|
|
874
874
|
console.log(chalk.dim(' Fedora: sudo dnf install fuse fuse-devel'));
|
|
875
|
-
console.log(chalk.dim(' macOS:
|
|
875
|
+
console.log(chalk.dim(' macOS: install current macFUSE and Xcode Command Line Tools, then reinstall TAS\n'));
|
|
876
876
|
process.exit(1);
|
|
877
877
|
}
|
|
878
878
|
});
|
|
@@ -890,16 +890,17 @@ program
|
|
|
890
890
|
try {
|
|
891
891
|
const { execFileSync } = await import('child_process');
|
|
892
892
|
|
|
893
|
-
//
|
|
893
|
+
// Match fuse-native's platform unmount mechanism without invoking a shell.
|
|
894
894
|
const isMac = process.platform === 'darwin';
|
|
895
|
-
const executable = isMac ? '
|
|
896
|
-
const args = isMac ? [absMount] : ['-u', absMount];
|
|
895
|
+
const executable = isMac ? 'diskutil' : 'fusermount';
|
|
896
|
+
const args = isMac ? ['unmount', 'force', absMount] : ['-u', absMount];
|
|
897
897
|
execFileSync(executable, args, { stdio: 'pipe' });
|
|
898
898
|
|
|
899
899
|
spinner.succeed(`Unmounted ${chalk.green(absMount)}`);
|
|
900
900
|
} catch (err) {
|
|
901
901
|
spinner.fail(`Unmount failed: ${err.message}`);
|
|
902
|
-
|
|
902
|
+
const retry = process.platform === 'darwin' ? `diskutil unmount force ${absMount}` : `fusermount -u ${absMount}`;
|
|
903
|
+
console.log(chalk.dim('\nTry: ' + retry));
|
|
903
904
|
process.exit(1);
|
|
904
905
|
}
|
|
905
906
|
});
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Modern macFUSE integration for fuse-native 2.x.
|
|
4
|
+
//
|
|
5
|
+
// fuse-native bundles an OSXFUSE 3 dylib on Darwin. That dylib has no arm64
|
|
6
|
+
// slice and its setup helper installs an obsolete kernel extension. We never
|
|
7
|
+
// install or load it. Instead, when macFUSE is already installed by the user,
|
|
8
|
+
// the TAS postinstall hook rebuilds fuse-native against that system library.
|
|
9
|
+
|
|
10
|
+
const childProcess = require('child_process')
|
|
11
|
+
const fs = require('fs')
|
|
12
|
+
const path = require('path')
|
|
13
|
+
|
|
14
|
+
const MACFUSE_BUNDLE = '/Library/Filesystems/macfuse.fs'
|
|
15
|
+
|
|
16
|
+
const LIBRARY_CANDIDATES = [
|
|
17
|
+
'/usr/local/lib/libfuse.dylib',
|
|
18
|
+
'/opt/homebrew/lib/libfuse.dylib',
|
|
19
|
+
'/usr/local/lib/libfuse.2.dylib',
|
|
20
|
+
'/opt/homebrew/lib/libfuse.2.dylib',
|
|
21
|
+
'/Library/Frameworks/macfUSE.framework/Versions/Current/lib/libfuse.dylib',
|
|
22
|
+
'/Library/Frameworks/macfUSE.framework/lib/libfuse.dylib'
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
const INCLUDE_ROOTS = [
|
|
26
|
+
'/usr/local/include',
|
|
27
|
+
'/opt/homebrew/include',
|
|
28
|
+
'/Library/Frameworks/macfUSE.framework/Headers'
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
function findMacFuseInstallation ({ exists = fs.existsSync } = {}) {
|
|
32
|
+
if (!exists(MACFUSE_BUNDLE)) {
|
|
33
|
+
return {
|
|
34
|
+
ready: false,
|
|
35
|
+
reason: 'current macFUSE is not installed at /Library/Filesystems/macfuse.fs'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const library = LIBRARY_CANDIDATES.find(exists)
|
|
40
|
+
if (!library) {
|
|
41
|
+
return {
|
|
42
|
+
ready: false,
|
|
43
|
+
reason: 'macFUSE is installed but its libfuse.dylib was not found'
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const include = findIncludeDirectory(exists)
|
|
48
|
+
if (!include) {
|
|
49
|
+
return {
|
|
50
|
+
ready: false,
|
|
51
|
+
reason: 'macFUSE is installed but its FUSE development headers were not found'
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { ready: true, bundle: MACFUSE_BUNDLE, library, include }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function findIncludeDirectory (exists) {
|
|
59
|
+
for (const root of INCLUDE_ROOTS) {
|
|
60
|
+
for (const candidate of [
|
|
61
|
+
path.join(root, 'fuse.h'),
|
|
62
|
+
path.join(root, 'osxfuse', 'fuse.h'),
|
|
63
|
+
path.join(root, 'fuse', 'fuse.h')
|
|
64
|
+
]) {
|
|
65
|
+
if (exists(candidate)) return path.dirname(candidate)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getFuseNativeDirectory (packageRoot) {
|
|
72
|
+
try {
|
|
73
|
+
return path.dirname(require.resolve('fuse-native/package.json', { paths: [packageRoot] }))
|
|
74
|
+
} catch {
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getDarwinLibraryDirectory (packageRoot) {
|
|
80
|
+
try {
|
|
81
|
+
return path.dirname(require.resolve('fuse-shared-library-darwin/package.json', { paths: [packageRoot] }))
|
|
82
|
+
} catch {
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
}
|
|
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
|
+
|
|
104
|
+
function validateMacFuseNativeBinding ({
|
|
105
|
+
packageRoot = path.resolve(__dirname, '..', '..'),
|
|
106
|
+
exists = fs.existsSync,
|
|
107
|
+
execFileSync = childProcess.execFileSync
|
|
108
|
+
} = {}) {
|
|
109
|
+
const installation = findMacFuseInstallation({ exists })
|
|
110
|
+
if (!installation.ready) return installation
|
|
111
|
+
|
|
112
|
+
const fuseNativeDir = getFuseNativeDirectory(packageRoot)
|
|
113
|
+
if (!fuseNativeDir) {
|
|
114
|
+
return { ready: false, reason: 'fuse-native is not installed' }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const addon = path.join(fuseNativeDir, 'build', 'Release', 'fuse.node')
|
|
118
|
+
if (!exists(addon)) {
|
|
119
|
+
return {
|
|
120
|
+
ready: false,
|
|
121
|
+
reason: 'the macFUSE native addon was not rebuilt from source; reinstall TAS after installing Xcode Command Line Tools'
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let linkedLibraries
|
|
126
|
+
try {
|
|
127
|
+
linkedLibraries = execFileSync('/usr/bin/otool', ['-L', addon], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] })
|
|
128
|
+
} catch {
|
|
129
|
+
return { ready: false, reason: 'could not inspect the macFUSE native addon' }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (linkedLibraries.includes('libosxfuse')) {
|
|
133
|
+
return { ready: false, reason: 'the legacy OSXFUSE binary is still loaded; reinstall TAS to rebuild against macFUSE' }
|
|
134
|
+
}
|
|
135
|
+
if (!linkedLibraries.includes('libfuse')) {
|
|
136
|
+
return { ready: false, reason: 'the native addon is not linked to macFUSE libfuse' }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { ready: true, ...installation, addon }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function macFuseAdapterSource () {
|
|
143
|
+
return `'use strict'
|
|
144
|
+
const path = require('path')
|
|
145
|
+
const macfuse = require(path.join(__dirname, '..', '..', 'src', 'fuse', 'macfuse.cjs'))
|
|
146
|
+
const installation = macfuse.findMacFuseInstallation()
|
|
147
|
+
|
|
148
|
+
function unavailable () {
|
|
149
|
+
return new Error('Current macFUSE is required for TAS mount: ' + installation.reason)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
get lib () {
|
|
154
|
+
if (!installation.ready) throw unavailable()
|
|
155
|
+
return installation.library
|
|
156
|
+
},
|
|
157
|
+
get include () {
|
|
158
|
+
if (!installation.ready) throw unavailable()
|
|
159
|
+
return installation.include
|
|
160
|
+
},
|
|
161
|
+
beforeMount (cb) { process.nextTick(cb || (() => {})) },
|
|
162
|
+
beforeUnmount (cb) { process.nextTick(cb || (() => {})) },
|
|
163
|
+
configure (cb) { process.nextTick(() => (cb || (() => {}))(installation.ready ? null : unavailable())) },
|
|
164
|
+
unconfigure (cb) { process.nextTick(() => (cb || (() => {}))(new Error('TAS never installs or removes macFUSE'))) },
|
|
165
|
+
isConfigured (cb) { process.nextTick(() => cb(null, installation.ready)) }
|
|
166
|
+
}
|
|
167
|
+
`
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function patchFuseSource (fuseNativeDir) {
|
|
171
|
+
const sourcePath = path.join(fuseNativeDir, 'fuse-native.c')
|
|
172
|
+
const source = fs.readFileSync(sourcePath, 'utf8')
|
|
173
|
+
const oldDefine = '#define FUSE_USE_VERSION 29'
|
|
174
|
+
const newDefine = '#ifdef __APPLE__\n#define FUSE_USE_VERSION 26\n#else\n#define FUSE_USE_VERSION 29\n#endif'
|
|
175
|
+
|
|
176
|
+
if (source.includes(newDefine)) return
|
|
177
|
+
if (!source.includes(oldDefine)) throw new Error('Unsupported fuse-native source layout')
|
|
178
|
+
fs.writeFileSync(sourcePath, source.replace(oldDefine, newDefine))
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function addLibraryRpath (addon, library) {
|
|
182
|
+
const result = childProcess.spawnSync(
|
|
183
|
+
'/usr/bin/install_name_tool',
|
|
184
|
+
['-add_rpath', path.dirname(library), addon],
|
|
185
|
+
{ stdio: 'ignore' }
|
|
186
|
+
)
|
|
187
|
+
// A duplicate rpath produces a nonzero exit code and is already safe.
|
|
188
|
+
return result.status === 0 || result.status === 1
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function installMacFuseAdapter ({
|
|
192
|
+
packageRoot = path.resolve(__dirname, '..', '..'),
|
|
193
|
+
platform = process.platform,
|
|
194
|
+
log = console
|
|
195
|
+
} = {}) {
|
|
196
|
+
if (platform !== 'darwin') return { skipped: true, reason: 'not macOS' }
|
|
197
|
+
|
|
198
|
+
const installation = findMacFuseInstallation()
|
|
199
|
+
if (!installation.ready) {
|
|
200
|
+
log.warn(`[tas] macOS FUSE was not built: ${installation.reason}`)
|
|
201
|
+
return { skipped: true, ...installation }
|
|
202
|
+
}
|
|
203
|
+
|
|
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
|
+
}
|
|
218
|
+
if (!fuseNativeDir || !darwinLibraryDir) {
|
|
219
|
+
const reason = 'optional fuse-native dependencies are not installed'
|
|
220
|
+
log.warn(`[tas] macOS FUSE was not built: ${reason}`)
|
|
221
|
+
return { ready: false, reason }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
fs.writeFileSync(path.join(darwinLibraryDir, 'index.js'), macFuseAdapterSource())
|
|
226
|
+
patchFuseSource(fuseNativeDir)
|
|
227
|
+
|
|
228
|
+
const npmCli = process.env.npm_execpath
|
|
229
|
+
if (!npmCli) throw new Error('npm did not provide its rebuild executable')
|
|
230
|
+
|
|
231
|
+
const rebuild = childProcess.spawnSync(
|
|
232
|
+
process.execPath,
|
|
233
|
+
[npmCli, 'rebuild', 'fuse-native', '--build-from-source', '--foreground-scripts'],
|
|
234
|
+
{
|
|
235
|
+
cwd: packageRoot,
|
|
236
|
+
stdio: 'inherit',
|
|
237
|
+
env: { ...process.env, npm_config_build_from_source: 'true' }
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
if (rebuild.status !== 0) throw new Error('fuse-native could not compile against macFUSE')
|
|
241
|
+
|
|
242
|
+
const addon = path.join(fuseNativeDir, 'build', 'Release', 'fuse.node')
|
|
243
|
+
addLibraryRpath(addon, installation.library)
|
|
244
|
+
|
|
245
|
+
const status = validateMacFuseNativeBinding({ packageRoot })
|
|
246
|
+
if (!status.ready) throw new Error(status.reason)
|
|
247
|
+
log.log(`[tas] macFUSE native addon rebuilt for ${process.arch}`)
|
|
248
|
+
return status
|
|
249
|
+
} catch (error) {
|
|
250
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
251
|
+
log.warn(`[tas] macOS FUSE was not built: ${reason}`)
|
|
252
|
+
return { ready: false, reason }
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
module.exports = {
|
|
257
|
+
MACFUSE_BUNDLE,
|
|
258
|
+
findMacFuseInstallation,
|
|
259
|
+
getFuseNativeDirectory,
|
|
260
|
+
installOptionalFuseNative,
|
|
261
|
+
validateMacFuseNativeBinding,
|
|
262
|
+
installMacFuseAdapter
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (require.main === module) installMacFuseAdapter()
|
package/src/fuse/mount.js
CHANGED
|
@@ -9,6 +9,7 @@ import path from 'path';
|
|
|
9
9
|
import fs from 'fs';
|
|
10
10
|
import os from 'os';
|
|
11
11
|
import { pipeline } from 'stream/promises';
|
|
12
|
+
import macFuse from './macfuse.cjs';
|
|
12
13
|
|
|
13
14
|
let Fuse;
|
|
14
15
|
try {
|
|
@@ -47,10 +48,8 @@ function withTimeout(promise, ms, label) {
|
|
|
47
48
|
/** Verify the native FUSE stack, not merely that the JS module imports. */
|
|
48
49
|
export async function checkFuseRuntime() {
|
|
49
50
|
if (process.platform === 'darwin') {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
reason: 'macOS mount is unsupported: fuse-native 2.x targets obsolete OSXFUSE APIs and current macFUSE/Apple Silicon is not validated'
|
|
53
|
-
};
|
|
51
|
+
const macStatus = macFuse.validateMacFuseNativeBinding();
|
|
52
|
+
if (!macStatus.ready) return { supported: false, reason: macStatus.reason };
|
|
54
53
|
}
|
|
55
54
|
if (!Fuse) return { supported: false, reason: 'fuse-native is not installed or failed to load' };
|
|
56
55
|
|
|
@@ -94,10 +93,13 @@ export async function checkFuseRuntime() {
|
|
|
94
93
|
export class TelegramFS {
|
|
95
94
|
constructor(options) {
|
|
96
95
|
if (process.platform === 'darwin') {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
const macStatus = macFuse.validateMacFuseNativeBinding();
|
|
97
|
+
if (!macStatus.ready) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
'TAS mount needs current macFUSE plus a rebuilt native addon: ' + macStatus.reason +
|
|
100
|
+
'. Install Xcode Command Line Tools, reinstall TAS, then run tas doctor.'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
101
103
|
}
|
|
102
104
|
if (!Fuse) {
|
|
103
105
|
throw new Error(
|