tabby-bianbu-mcp 0.6.0 → 0.7.0
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 +24 -0
- package/assets/bianbu_agent_proxy.meta.json +5 -5
- package/assets/bianbu_agent_proxy.sh +132 -39
- package/dist/filesTab.component.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/remoteRelease.d.ts +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `tabby-bianbu-mcp` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.7.0] - 2026-03-23
|
|
6
|
+
|
|
7
|
+
### Added (Remote MCP Server v1.3.0)
|
|
8
|
+
- **server-side rate limiting**: returns HTTP 429 with `Retry-After` header when concurrent requests exceed `MAX_CONCURRENT_REQUESTS` (default 32), enabling client-side adaptive throttling
|
|
9
|
+
- **session concurrency caps**: configurable limits for shell (`MAX_SHELL_SESSIONS=8`), upload (`MAX_UPLOAD_SESSIONS=16`), and download (`MAX_DOWNLOAD_SESSIONS=16`) sessions — prevents resource exhaustion under parallel transfers
|
|
10
|
+
- **ISO 8601 timestamps**: `list_directory`, `fileStat`, and all file operations now return ISO date strings (e.g. `2026-03-23T12:34:56.789Z`) instead of Unix epoch seconds — directly consumable by the client's `formatDate()`
|
|
11
|
+
- **enhanced health endpoint**: now reports `active_sessions` counts, `concurrency` stats (active/total/throttled requests), `uptime_seconds`, `memory` usage (RSS/heap), `node_version`, and `platform` info
|
|
12
|
+
- **graceful shutdown**: SIGINT/SIGTERM now drain active requests, clean up upload temp directories, and close the HTTP server cleanly before exit
|
|
13
|
+
- **`rate_limiting` and `iso_timestamps` capability flags** in health `supports` object for client feature detection
|
|
14
|
+
|
|
15
|
+
### Changed (Remote MCP Server v1.3.0)
|
|
16
|
+
- `logical_session_limits` in health response now reports actual configured caps instead of `null`
|
|
17
|
+
- `exec_shell_session` response now includes `session_cwd` for the client to track the working directory
|
|
18
|
+
- removed dead `registerStatefulSession()` function (was never called)
|
|
19
|
+
- script version bumped to 1.3.0, server version bumped to 1.3.0
|
|
20
|
+
|
|
21
|
+
### Changed (Plugin)
|
|
22
|
+
- bundled remote installer updated to script v1.3.0 / server v1.3.0
|
|
23
|
+
|
|
24
|
+
## [0.6.1] - 2026-03-23
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- Angular JIT compilation error: moved arrow-function-based transfer status expression from pug template into `activeTransferLabel` getter to avoid `Bindings cannot contain assignments` parser error
|
|
28
|
+
|
|
5
29
|
## [0.6.0] - 2026-03-23
|
|
6
30
|
|
|
7
31
|
### Added
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"fileName": "bianbu_agent_proxy.sh",
|
|
3
3
|
"sourceFile": "bianbu_agent_proxy.sh",
|
|
4
|
-
"scriptVersion": "1.
|
|
5
|
-
"serverVersion": "1.
|
|
6
|
-
"sha256": "
|
|
7
|
-
"bytes":
|
|
8
|
-
"generatedAt": "2026-03-
|
|
4
|
+
"scriptVersion": "1.3.0",
|
|
5
|
+
"serverVersion": "1.3.0",
|
|
6
|
+
"sha256": "b6cb60d6796e3e5d6e0a301e80ac17e8f0e9d6d610cf30d7078ca203f9ec2999",
|
|
7
|
+
"bytes": 72162,
|
|
8
|
+
"generatedAt": "2026-03-23T04:06:24.139Z"
|
|
9
9
|
}
|
|
@@ -4,8 +4,8 @@ set -Eeuo pipefail
|
|
|
4
4
|
umask 077
|
|
5
5
|
|
|
6
6
|
SCRIPT_NAME="$(basename "$0")"
|
|
7
|
-
SCRIPT_VERSION="${SCRIPT_VERSION:-1.
|
|
8
|
-
SERVER_VERSION="${SERVER_VERSION:-1.
|
|
7
|
+
SCRIPT_VERSION="${SCRIPT_VERSION:-1.3.0}"
|
|
8
|
+
SERVER_VERSION="${SERVER_VERSION:-1.3.0}"
|
|
9
9
|
APP_NAME="bianbu-mcp-server"
|
|
10
10
|
INSTALL_ROOT="/opt/${APP_NAME}"
|
|
11
11
|
APP_FILE="${INSTALL_ROOT}/server.mjs"
|
|
@@ -25,6 +25,10 @@ ENABLE_PASSWORDLESS_SUDO="${ENABLE_PASSWORDLESS_SUDO:-false}"
|
|
|
25
25
|
MAX_FILE_MB="${MAX_FILE_MB:-64}"
|
|
26
26
|
MAX_COMMAND_OUTPUT_KB="${MAX_COMMAND_OUTPUT_KB:-256}"
|
|
27
27
|
MAX_REQUEST_BODY_MB="${MAX_REQUEST_BODY_MB:-8}"
|
|
28
|
+
MAX_CONCURRENT_REQUESTS="${MAX_CONCURRENT_REQUESTS:-32}"
|
|
29
|
+
MAX_UPLOAD_SESSIONS="${MAX_UPLOAD_SESSIONS:-16}"
|
|
30
|
+
MAX_DOWNLOAD_SESSIONS="${MAX_DOWNLOAD_SESSIONS:-16}"
|
|
31
|
+
MAX_SHELL_SESSIONS="${MAX_SHELL_SESSIONS:-8}"
|
|
28
32
|
TLS_CERT_FILE="${TLS_CERT_FILE:-}"
|
|
29
33
|
TLS_KEY_FILE="${TLS_KEY_FILE:-}"
|
|
30
34
|
MCP_TRANSPORT_MODE="${MCP_TRANSPORT_MODE:-stateless}"
|
|
@@ -102,6 +106,10 @@ MCP tools:
|
|
|
102
106
|
MAX_FILE_MB 上传/下载单文件大小限制,默认: ${MAX_FILE_MB} MB
|
|
103
107
|
MAX_COMMAND_OUTPUT_KB 命令输出截断上限,默认: ${MAX_COMMAND_OUTPUT_KB} KB
|
|
104
108
|
MAX_REQUEST_BODY_MB HTTP JSON 请求体上限,默认: ${MAX_REQUEST_BODY_MB} MB
|
|
109
|
+
MAX_CONCURRENT_REQUESTS 最大并发 MCP 请求数,超出返回 429,默认: ${MAX_CONCURRENT_REQUESTS}
|
|
110
|
+
MAX_UPLOAD_SESSIONS 最大并发上传会话数,默认: ${MAX_UPLOAD_SESSIONS}
|
|
111
|
+
MAX_DOWNLOAD_SESSIONS 最大并发下载会话数,默认: ${MAX_DOWNLOAD_SESSIONS}
|
|
112
|
+
MAX_SHELL_SESSIONS 最大并发 Shell 会话数,默认: ${MAX_SHELL_SESSIONS}
|
|
105
113
|
TLS_CERT_FILE 可选,HTTPS 证书路径
|
|
106
114
|
TLS_KEY_FILE 可选,HTTPS 私钥路径
|
|
107
115
|
|
|
@@ -371,6 +379,7 @@ write_app() {
|
|
|
371
379
|
import { randomBytes, randomUUID } from 'node:crypto';
|
|
372
380
|
import { exec as execCb } from 'node:child_process';
|
|
373
381
|
import { promisify } from 'node:util';
|
|
382
|
+
import os from 'node:os';
|
|
374
383
|
import fs from 'node:fs';
|
|
375
384
|
import path from 'node:path';
|
|
376
385
|
import http from 'node:http';
|
|
@@ -421,12 +430,20 @@ const EXPRESS_JSON_LIMIT = `${MAX_REQUEST_BODY_MB}mb`;
|
|
|
421
430
|
const TLS_CERT_FILE = process.env.TLS_CERT_FILE || '';
|
|
422
431
|
const TLS_KEY_FILE = process.env.TLS_KEY_FILE || '';
|
|
423
432
|
const MCP_TRANSPORT_MODE = (process.env.MCP_TRANSPORT_MODE || 'stateless').toLowerCase();
|
|
433
|
+
const MAX_CONCURRENT_REQUESTS = Number(process.env.MAX_CONCURRENT_REQUESTS || '32');
|
|
434
|
+
const MAX_UPLOAD_SESSIONS = Number(process.env.MAX_UPLOAD_SESSIONS || '16');
|
|
435
|
+
const MAX_DOWNLOAD_SESSIONS = Number(process.env.MAX_DOWNLOAD_SESSIONS || '16');
|
|
436
|
+
const MAX_SHELL_SESSIONS = Number(process.env.MAX_SHELL_SESSIONS || '8');
|
|
424
437
|
const CANONICAL_FILE_ROOT = FILE_ROOT === '/' ? '/' : fs.realpathSync(FILE_ROOT);
|
|
425
438
|
const HAS_SUDO = fs.existsSync('/usr/bin/sudo') || fs.existsSync('/bin/sudo');
|
|
426
439
|
const shellSessions = new Map();
|
|
427
440
|
const uploadSessions = new Map();
|
|
428
441
|
const downloadSessions = new Map();
|
|
429
442
|
const SESSION_IDLE_MS = 60 * 60 * 1000;
|
|
443
|
+
const SERVER_START_TIME = Date.now();
|
|
444
|
+
let activeRequests = 0;
|
|
445
|
+
let totalRequests = 0;
|
|
446
|
+
let throttledRequests = 0;
|
|
430
447
|
|
|
431
448
|
if (!['stateless', 'stateful'].includes(MCP_TRANSPORT_MODE)) {
|
|
432
449
|
throw new Error(`Unsupported MCP_TRANSPORT_MODE: ${MCP_TRANSPORT_MODE}`);
|
|
@@ -458,6 +475,7 @@ function shellQuote(value) {
|
|
|
458
475
|
|
|
459
476
|
function rootHelperScript() {
|
|
460
477
|
return String.raw`import base64, json, os, shutil, stat, sys, tempfile
|
|
478
|
+
from datetime import datetime, timezone
|
|
461
479
|
payload = json.loads(base64.b64decode(sys.argv[1]).decode('utf-8'))
|
|
462
480
|
op = payload['op']
|
|
463
481
|
target = payload.get('path', '')
|
|
@@ -467,7 +485,7 @@ def stat_dict(p):
|
|
|
467
485
|
return {
|
|
468
486
|
'path': p,
|
|
469
487
|
'size': st.st_size,
|
|
470
|
-
'modified':
|
|
488
|
+
'modified': datetime.fromtimestamp(st.st_mtime, tz=timezone.utc).isoformat(),
|
|
471
489
|
'is_dir': stat.S_ISDIR(st.st_mode),
|
|
472
490
|
'is_file': stat.S_ISREG(st.st_mode),
|
|
473
491
|
}
|
|
@@ -696,7 +714,7 @@ async function fileStat(target) {
|
|
|
696
714
|
return {
|
|
697
715
|
path: target,
|
|
698
716
|
size: stat.size,
|
|
699
|
-
modified:
|
|
717
|
+
modified: new Date(stat.mtimeMs).toISOString(),
|
|
700
718
|
is_dir: stat.isDirectory(),
|
|
701
719
|
is_file: stat.isFile(),
|
|
702
720
|
};
|
|
@@ -859,7 +877,7 @@ async function readBinaryChunk(target, offset, chunkBytes, asRoot) {
|
|
|
859
877
|
return {
|
|
860
878
|
path: target,
|
|
861
879
|
size: stat.size,
|
|
862
|
-
modified:
|
|
880
|
+
modified: new Date(stat.mtimeMs).toISOString(),
|
|
863
881
|
is_dir: stat.isDirectory(),
|
|
864
882
|
is_file: stat.isFile(),
|
|
865
883
|
offset,
|
|
@@ -885,6 +903,7 @@ function makeServer() {
|
|
|
885
903
|
'health',
|
|
886
904
|
{ description: 'Return basic MCP server health information.' },
|
|
887
905
|
async () => {
|
|
906
|
+
const mem = process.memoryUsage();
|
|
888
907
|
const payload = {
|
|
889
908
|
ok: true,
|
|
890
909
|
listen: `${HOST}:${PORT}${MCP_PATH}`,
|
|
@@ -898,10 +917,29 @@ function makeServer() {
|
|
|
898
917
|
passwordless_sudo_expected: ENABLE_PASSWORDLESS_SUDO,
|
|
899
918
|
session_idle_ms: SESSION_IDLE_MS,
|
|
900
919
|
logical_session_limits: {
|
|
901
|
-
shell:
|
|
902
|
-
upload:
|
|
903
|
-
download:
|
|
920
|
+
shell: MAX_SHELL_SESSIONS,
|
|
921
|
+
upload: MAX_UPLOAD_SESSIONS,
|
|
922
|
+
download: MAX_DOWNLOAD_SESSIONS,
|
|
904
923
|
},
|
|
924
|
+
active_sessions: {
|
|
925
|
+
shell: shellSessions.size,
|
|
926
|
+
upload: uploadSessions.size,
|
|
927
|
+
download: downloadSessions.size,
|
|
928
|
+
},
|
|
929
|
+
concurrency: {
|
|
930
|
+
max_concurrent_requests: MAX_CONCURRENT_REQUESTS,
|
|
931
|
+
active_requests: activeRequests,
|
|
932
|
+
total_requests: totalRequests,
|
|
933
|
+
throttled_requests: throttledRequests,
|
|
934
|
+
},
|
|
935
|
+
uptime_seconds: Math.floor((Date.now() - SERVER_START_TIME) / 1000),
|
|
936
|
+
memory: {
|
|
937
|
+
rss_mb: Math.round(mem.rss / 1048576 * 10) / 10,
|
|
938
|
+
heap_used_mb: Math.round(mem.heapUsed / 1048576 * 10) / 10,
|
|
939
|
+
heap_total_mb: Math.round(mem.heapTotal / 1048576 * 10) / 10,
|
|
940
|
+
},
|
|
941
|
+
node_version: process.version,
|
|
942
|
+
platform: `${os.platform()} ${os.release()} ${os.arch()}`,
|
|
905
943
|
script_version: INSTALLER_SCRIPT_VERSION,
|
|
906
944
|
server_version: SERVER_VERSION,
|
|
907
945
|
service_name: 'bianbu-mcp-server',
|
|
@@ -911,6 +949,8 @@ function makeServer() {
|
|
|
911
949
|
parallel_chunk_offsets: true,
|
|
912
950
|
rename_path: true,
|
|
913
951
|
shell_session: true,
|
|
952
|
+
rate_limiting: true,
|
|
953
|
+
iso_timestamps: true,
|
|
914
954
|
},
|
|
915
955
|
};
|
|
916
956
|
return textResult(JSON.stringify(payload, null, 2), payload);
|
|
@@ -1186,6 +1226,9 @@ function makeServer() {
|
|
|
1186
1226
|
},
|
|
1187
1227
|
},
|
|
1188
1228
|
async ({ cwd, as_root }) => {
|
|
1229
|
+
if (shellSessions.size >= MAX_SHELL_SESSIONS) {
|
|
1230
|
+
throw new Error(`shell session limit reached (max ${MAX_SHELL_SESSIONS})`);
|
|
1231
|
+
}
|
|
1189
1232
|
const workingDirectory = await resolveRequestedPath(cwd, as_root);
|
|
1190
1233
|
const stat = await fs.promises.stat(workingDirectory).catch(() => null);
|
|
1191
1234
|
if (!stat || !stat.isDirectory()) {
|
|
@@ -1221,6 +1264,7 @@ function makeServer() {
|
|
|
1221
1264
|
session.cwd = payload.cwd || session.cwd;
|
|
1222
1265
|
session.updatedAt = Date.now();
|
|
1223
1266
|
payload.session_id = session_id;
|
|
1267
|
+
payload.session_cwd = session.cwd;
|
|
1224
1268
|
return textResult(JSON.stringify(payload, null, 2), payload);
|
|
1225
1269
|
},
|
|
1226
1270
|
);
|
|
@@ -1253,6 +1297,9 @@ function makeServer() {
|
|
|
1253
1297
|
},
|
|
1254
1298
|
},
|
|
1255
1299
|
async ({ path: inputPath, overwrite, total_size, chunk_bytes, as_root }) => {
|
|
1300
|
+
if (uploadSessions.size >= MAX_UPLOAD_SESSIONS) {
|
|
1301
|
+
throw new Error(`upload session limit reached (max ${MAX_UPLOAD_SESSIONS})`);
|
|
1302
|
+
}
|
|
1256
1303
|
const target = await resolveRequestedPath(inputPath, as_root);
|
|
1257
1304
|
const targetExists = await statAnyPath(target, as_root).then(() => true).catch(() => false);
|
|
1258
1305
|
if (!overwrite && targetExists) {
|
|
@@ -1369,6 +1416,9 @@ function makeServer() {
|
|
|
1369
1416
|
},
|
|
1370
1417
|
},
|
|
1371
1418
|
async ({ path: inputPath, chunk_bytes, as_root }) => {
|
|
1419
|
+
if (downloadSessions.size >= MAX_DOWNLOAD_SESSIONS) {
|
|
1420
|
+
throw new Error(`download session limit reached (max ${MAX_DOWNLOAD_SESSIONS})`);
|
|
1421
|
+
}
|
|
1372
1422
|
const target = await resolveRequestedPath(inputPath, as_root);
|
|
1373
1423
|
const info = await statAnyPath(target, as_root);
|
|
1374
1424
|
if (!info.is_file) {
|
|
@@ -1432,7 +1482,27 @@ app.use(express.json({ limit: EXPRESS_JSON_LIMIT }));
|
|
|
1432
1482
|
const transports = new Map();
|
|
1433
1483
|
const servers = new Map();
|
|
1434
1484
|
|
|
1485
|
+
// Concurrency-based rate limiting middleware for MCP endpoint
|
|
1486
|
+
function rateLimitMiddleware(req, res, next) {
|
|
1487
|
+
totalRequests++;
|
|
1488
|
+
if (activeRequests >= MAX_CONCURRENT_REQUESTS) {
|
|
1489
|
+
throttledRequests++;
|
|
1490
|
+
res.setHeader('Retry-After', '1');
|
|
1491
|
+
res.status(429).json({
|
|
1492
|
+
jsonrpc: '2.0',
|
|
1493
|
+
error: { code: -32000, message: `Too many concurrent requests (limit: ${MAX_CONCURRENT_REQUESTS})` },
|
|
1494
|
+
id: null,
|
|
1495
|
+
});
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
activeRequests++;
|
|
1499
|
+
res.on('finish', () => { activeRequests--; });
|
|
1500
|
+
res.on('close', () => { activeRequests = Math.max(0, activeRequests - 1); });
|
|
1501
|
+
next();
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1435
1504
|
app.get('/health', (_req, res) => {
|
|
1505
|
+
const mem = process.memoryUsage();
|
|
1436
1506
|
res.json({
|
|
1437
1507
|
ok: true,
|
|
1438
1508
|
listen: `${HOST}:${PORT}${MCP_PATH}`,
|
|
@@ -1441,10 +1511,28 @@ app.get('/health', (_req, res) => {
|
|
|
1441
1511
|
max_request_body_bytes: MAX_REQUEST_BODY_BYTES,
|
|
1442
1512
|
session_idle_ms: SESSION_IDLE_MS,
|
|
1443
1513
|
logical_session_limits: {
|
|
1444
|
-
shell:
|
|
1445
|
-
upload:
|
|
1446
|
-
download:
|
|
1514
|
+
shell: MAX_SHELL_SESSIONS,
|
|
1515
|
+
upload: MAX_UPLOAD_SESSIONS,
|
|
1516
|
+
download: MAX_DOWNLOAD_SESSIONS,
|
|
1517
|
+
},
|
|
1518
|
+
active_sessions: {
|
|
1519
|
+
shell: shellSessions.size,
|
|
1520
|
+
upload: uploadSessions.size,
|
|
1521
|
+
download: downloadSessions.size,
|
|
1447
1522
|
},
|
|
1523
|
+
concurrency: {
|
|
1524
|
+
max_concurrent_requests: MAX_CONCURRENT_REQUESTS,
|
|
1525
|
+
active_requests: activeRequests,
|
|
1526
|
+
total_requests: totalRequests,
|
|
1527
|
+
throttled_requests: throttledRequests,
|
|
1528
|
+
},
|
|
1529
|
+
uptime_seconds: Math.floor((Date.now() - SERVER_START_TIME) / 1000),
|
|
1530
|
+
memory: {
|
|
1531
|
+
rss_mb: Math.round(mem.rss / 1048576 * 10) / 10,
|
|
1532
|
+
heap_used_mb: Math.round(mem.heapUsed / 1048576 * 10) / 10,
|
|
1533
|
+
},
|
|
1534
|
+
node_version: process.version,
|
|
1535
|
+
platform: `${os.platform()} ${os.release()} ${os.arch()}`,
|
|
1448
1536
|
script_version: INSTALLER_SCRIPT_VERSION,
|
|
1449
1537
|
server_version: SERVER_VERSION,
|
|
1450
1538
|
tools: SUPPORTED_TOOLS,
|
|
@@ -1453,36 +1541,13 @@ app.get('/health', (_req, res) => {
|
|
|
1453
1541
|
parallel_chunk_offsets: true,
|
|
1454
1542
|
rename_path: true,
|
|
1455
1543
|
shell_session: true,
|
|
1544
|
+
rate_limiting: true,
|
|
1545
|
+
iso_timestamps: true,
|
|
1456
1546
|
},
|
|
1457
1547
|
});
|
|
1458
1548
|
});
|
|
1459
1549
|
|
|
1460
|
-
|
|
1461
|
-
transport.onclose = () => {
|
|
1462
|
-
const sid = transport.sessionId;
|
|
1463
|
-
if (sid) {
|
|
1464
|
-
transports.delete(sid);
|
|
1465
|
-
servers.delete(sid);
|
|
1466
|
-
}
|
|
1467
|
-
};
|
|
1468
|
-
|
|
1469
|
-
return new StreamableHTTPServerTransport({
|
|
1470
|
-
sessionIdGenerator: () => randomUUID(),
|
|
1471
|
-
enableJsonResponse: true,
|
|
1472
|
-
onsessioninitialized: (sessionId) => {
|
|
1473
|
-
transports.set(sessionId, transport);
|
|
1474
|
-
servers.set(sessionId, server);
|
|
1475
|
-
},
|
|
1476
|
-
onsessionclosed: (sessionId) => {
|
|
1477
|
-
if (sessionId) {
|
|
1478
|
-
transports.delete(sessionId);
|
|
1479
|
-
servers.delete(sessionId);
|
|
1480
|
-
}
|
|
1481
|
-
},
|
|
1482
|
-
});
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
app.post(MCP_PATH, async (req, res) => {
|
|
1550
|
+
app.post(MCP_PATH, rateLimitMiddleware, async (req, res) => {
|
|
1486
1551
|
try {
|
|
1487
1552
|
if (MCP_TRANSPORT_MODE === 'stateless') {
|
|
1488
1553
|
const server = makeServer();
|
|
@@ -1619,8 +1684,27 @@ process.on('uncaughtException', (error) => {
|
|
|
1619
1684
|
process.exit(1);
|
|
1620
1685
|
});
|
|
1621
1686
|
|
|
1622
|
-
|
|
1623
|
-
|
|
1687
|
+
async function gracefulShutdown(signal) {
|
|
1688
|
+
console.log(`${signal} received, shutting down gracefully...`);
|
|
1689
|
+
// Clean up upload sessions (remove temp dirs)
|
|
1690
|
+
const cleanups = [];
|
|
1691
|
+
for (const [id, session] of uploadSessions.entries()) {
|
|
1692
|
+
cleanups.push(cleanupUploadSession(session).catch(() => {}));
|
|
1693
|
+
uploadSessions.delete(id);
|
|
1694
|
+
}
|
|
1695
|
+
downloadSessions.clear();
|
|
1696
|
+
shellSessions.clear();
|
|
1697
|
+
await Promise.allSettled(cleanups);
|
|
1698
|
+
httpServer.close(() => {
|
|
1699
|
+
console.log('Server closed');
|
|
1700
|
+
process.exit(0);
|
|
1701
|
+
});
|
|
1702
|
+
// Force exit after 10 seconds
|
|
1703
|
+
setTimeout(() => process.exit(0), 10000).unref();
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
1707
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
1624
1708
|
EOF
|
|
1625
1709
|
run_as_root python3 - "$app_file" "$SERVER_VERSION" "$SCRIPT_VERSION" <<'PY'
|
|
1626
1710
|
from pathlib import Path
|
|
@@ -1707,6 +1791,10 @@ ENABLE_PASSWORDLESS_SUDO=${ENABLE_PASSWORDLESS_SUDO}
|
|
|
1707
1791
|
MAX_FILE_MB=${MAX_FILE_MB}
|
|
1708
1792
|
MAX_COMMAND_OUTPUT_KB=${MAX_COMMAND_OUTPUT_KB}
|
|
1709
1793
|
MAX_REQUEST_BODY_MB=${MAX_REQUEST_BODY_MB}
|
|
1794
|
+
MAX_CONCURRENT_REQUESTS=${MAX_CONCURRENT_REQUESTS}
|
|
1795
|
+
MAX_UPLOAD_SESSIONS=${MAX_UPLOAD_SESSIONS}
|
|
1796
|
+
MAX_DOWNLOAD_SESSIONS=${MAX_DOWNLOAD_SESSIONS}
|
|
1797
|
+
MAX_SHELL_SESSIONS=${MAX_SHELL_SESSIONS}
|
|
1710
1798
|
TLS_CERT_FILE=${TLS_CERT_FILE}
|
|
1711
1799
|
TLS_KEY_FILE=${TLS_KEY_FILE}
|
|
1712
1800
|
EOF
|
|
@@ -1876,6 +1964,11 @@ FILE_ROOT=${FILE_ROOT}
|
|
|
1876
1964
|
ENABLE_PASSWORDLESS_SUDO=${ENABLE_PASSWORDLESS_SUDO}
|
|
1877
1965
|
MAX_FILE_MB=${MAX_FILE_MB}
|
|
1878
1966
|
MAX_COMMAND_OUTPUT_KB=${MAX_COMMAND_OUTPUT_KB}
|
|
1967
|
+
MAX_REQUEST_BODY_MB=${MAX_REQUEST_BODY_MB}
|
|
1968
|
+
MAX_CONCURRENT_REQUESTS=${MAX_CONCURRENT_REQUESTS}
|
|
1969
|
+
MAX_UPLOAD_SESSIONS=${MAX_UPLOAD_SESSIONS}
|
|
1970
|
+
MAX_DOWNLOAD_SESSIONS=${MAX_DOWNLOAD_SESSIONS}
|
|
1971
|
+
MAX_SHELL_SESSIONS=${MAX_SHELL_SESSIONS}
|
|
1879
1972
|
TLS_CERT_FILE=${TLS_CERT_FILE}
|
|
1880
1973
|
TLS_KEY_FILE=${TLS_KEY_FILE}
|
|
1881
1974
|
BACKUP_ROOT=${BACKUP_ROOT}
|
|
@@ -109,6 +109,7 @@ export declare class BianbuCloudFilesTabComponent extends BaseTabComponent {
|
|
|
109
109
|
get totalSize(): string;
|
|
110
110
|
get selectionSummary(): string;
|
|
111
111
|
isSelected(index: number): boolean;
|
|
112
|
+
get activeTransferLabel(): string;
|
|
112
113
|
toggleDetailPane(): void;
|
|
113
114
|
private clearPreview;
|
|
114
115
|
baseName(path: string): string;
|