tmex-cli 0.4.2 → 0.4.4
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/runtime/server.js +491 -94
- package/package.json +1 -1
- package/resources/fe-dist/assets/DevicePage-Di11uzbS.js +26 -0
- package/resources/fe-dist/assets/{DevicePage-CKaPUo7L.js.map → DevicePage-Di11uzbS.js.map} +1 -1
- package/resources/fe-dist/assets/DevicesPage-dj7Pel6f.js +17 -0
- package/resources/fe-dist/assets/DevicesPage-dj7Pel6f.js.map +1 -0
- package/resources/fe-dist/assets/SettingsPage-DwpbDA_e.js +17 -0
- package/resources/fe-dist/assets/{SettingsPage-BfkOW0fc.js.map → SettingsPage-DwpbDA_e.js.map} +1 -1
- package/resources/fe-dist/assets/index-40zyi_9K.css +1 -0
- package/resources/fe-dist/assets/index-61hq_aNX.js +215 -0
- package/resources/fe-dist/assets/index-61hq_aNX.js.map +1 -0
- package/resources/fe-dist/assets/select-DI4HQZd4.js +17 -0
- package/resources/fe-dist/assets/{select-CNlE6MiW.js.map → select-DI4HQZd4.js.map} +1 -1
- package/resources/fe-dist/assets/switch-DsfDIUrU.js +12 -0
- package/resources/fe-dist/assets/{switch-CxkzOIL6.js.map → switch-DsfDIUrU.js.map} +1 -1
- package/resources/fe-dist/assets/useValueChanged-AVaPYvln.js +7 -0
- package/resources/fe-dist/assets/{useValueChanged-CO2U5MoL.js.map → useValueChanged-AVaPYvln.js.map} +1 -1
- package/resources/fe-dist/index.html +2 -2
- package/resources/gateway-drizzle/0003_glamorous_lizard.sql +1 -0
- package/resources/gateway-drizzle/meta/0003_snapshot.json +542 -0
- package/resources/gateway-drizzle/meta/_journal.json +7 -0
- package/resources/fe-dist/assets/DevicePage-CKaPUo7L.js +0 -26
- package/resources/fe-dist/assets/DevicesPage-FqU-Dxhu.js +0 -17
- package/resources/fe-dist/assets/DevicesPage-FqU-Dxhu.js.map +0 -1
- package/resources/fe-dist/assets/SettingsPage-BfkOW0fc.js +0 -17
- package/resources/fe-dist/assets/index-EgHfq93I.js +0 -449
- package/resources/fe-dist/assets/index-EgHfq93I.js.map +0 -1
- package/resources/fe-dist/assets/index-Ytlj3p_q.css +0 -1
- package/resources/fe-dist/assets/select-CNlE6MiW.js +0 -17
- package/resources/fe-dist/assets/switch-CxkzOIL6.js +0 -12
- package/resources/fe-dist/assets/useValueChanged-CO2U5MoL.js +0 -7
package/dist/runtime/server.js
CHANGED
|
@@ -20524,6 +20524,8 @@ var I18N_RESOURCES = {
|
|
|
20524
20524
|
chatId: "Chat ID",
|
|
20525
20525
|
applyTime: "Application Time",
|
|
20526
20526
|
gatewayOnline: "\uD83D\uDFE2 Gateway online @ {{siteName}}",
|
|
20527
|
+
deviceConnectionError: `\uD83D\uDD34 {{siteName}}: Connection error on device "{{deviceName}}" ({{host}}) [{{category}}]
|
|
20528
|
+
{{error}}`,
|
|
20527
20529
|
authSuccess: "\u2705 Authorized. You will now receive notifications.",
|
|
20528
20530
|
authPending: "\u23F3 Authorization request received. Please approve in tmex settings.",
|
|
20529
20531
|
authFailed: "\u274C Authorization request failed. Please contact administrator.",
|
|
@@ -20564,11 +20566,30 @@ Time: {{time}}`,
|
|
|
20564
20566
|
hostNotFound: "Host not found: Unable to resolve hostname. Please check DNS or hostname configuration.",
|
|
20565
20567
|
handshakeFailed: "Handshake failed: Unable to establish secure connection. Possibly incompatible key exchange algorithm.",
|
|
20566
20568
|
tmuxUnavailable: "Remote tmux unavailable or failed to start. Please ensure tmux is installed and available in the remote shell PATH.",
|
|
20569
|
+
connectionClosed: "Connection closed, attempting to reconnect",
|
|
20567
20570
|
unknown: "Connection failed: {{message}}",
|
|
20568
20571
|
reconnecting: "Connection interrupted, reconnecting in {{delay}} seconds ({{attempt}}/{{maxRetries}})",
|
|
20569
20572
|
reconnectFailed: "Auto-reconnect failed, please retry manually",
|
|
20570
20573
|
reconnected: "Device reconnected automatically"
|
|
20571
20574
|
},
|
|
20575
|
+
deviceStatus: {
|
|
20576
|
+
reconnecting: "Reconnecting {{delay}}s",
|
|
20577
|
+
offline: "Offline",
|
|
20578
|
+
errorBadge: {
|
|
20579
|
+
authFailed: "Auth failed",
|
|
20580
|
+
agentUnavailable: "Agent unavailable",
|
|
20581
|
+
agentNoIdentity: "Agent has no keys",
|
|
20582
|
+
configRefNotSupported: "SSH Config unsupported",
|
|
20583
|
+
networkUnreachable: "Network unreachable",
|
|
20584
|
+
connectionRefused: "Refused",
|
|
20585
|
+
timeout: "Timeout",
|
|
20586
|
+
hostNotFound: "Host not found",
|
|
20587
|
+
handshakeFailed: "Handshake failed",
|
|
20588
|
+
tmuxUnavailable: "Tmux unavailable",
|
|
20589
|
+
connectionClosed: "Disconnected",
|
|
20590
|
+
unknown: "Connection error"
|
|
20591
|
+
}
|
|
20592
|
+
},
|
|
20572
20593
|
websocket: {
|
|
20573
20594
|
error: "WebSocket connection error",
|
|
20574
20595
|
checkGateway: "Please check Gateway status",
|
|
@@ -20862,6 +20883,8 @@ Time: {{time}}`,
|
|
|
20862
20883
|
chatId: "chatId",
|
|
20863
20884
|
applyTime: "\u7533\u8BF7\u65F6\u95F4",
|
|
20864
20885
|
gatewayOnline: "\uD83D\uDFE2 Gateway online @ {{siteName}}",
|
|
20886
|
+
deviceConnectionError: `\uD83D\uDD34 {{siteName}}\uFF1A\u8BBE\u5907\u300C{{deviceName}}\u300D({{host}}) \u8FDE\u63A5\u5F02\u5E38 [{{category}}]
|
|
20887
|
+
{{error}}`,
|
|
20865
20888
|
authSuccess: "\u2705 \u5DF2\u6388\u6743\uFF0C\u53EF\u63A5\u6536\u901A\u77E5\u3002",
|
|
20866
20889
|
authPending: "\u23F3 \u5DF2\u6536\u5230\u6388\u6743\u7533\u8BF7\uFF0C\u8BF7\u5728 tmex \u8BBE\u7F6E\u9875\u5BA1\u6279\u3002",
|
|
20867
20890
|
authFailed: "\u274C \u6388\u6743\u7533\u8BF7\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002",
|
|
@@ -20902,11 +20925,30 @@ Bot\uFF1A{{botName}}
|
|
|
20902
20925
|
hostNotFound: "\u4E3B\u673A\u672A\u627E\u5230\uFF1A\u65E0\u6CD5\u89E3\u6790\u4E3B\u673A\u5730\u5740\uFF0C\u8BF7\u68C0\u67E5 DNS \u6216\u4E3B\u673A\u540D\u662F\u5426\u6B63\u786E",
|
|
20903
20926
|
handshakeFailed: "\u63E1\u624B\u5931\u8D25\uFF1A\u65E0\u6CD5\u5EFA\u7ACB\u5B89\u5168\u8FDE\u63A5\uFF0C\u53EF\u80FD\u662F\u5BC6\u94A5\u4EA4\u6362\u7B97\u6CD5\u4E0D\u517C\u5BB9",
|
|
20904
20927
|
tmuxUnavailable: "\u8FDC\u7AEF tmux \u4E0D\u53EF\u7528\u6216\u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u8FDC\u7AEF\u662F\u5426\u5DF2\u5B89\u88C5 tmux\uFF0C\u4E14\u8FDC\u7AEF shell PATH \u53EF\u627E\u5230 tmux",
|
|
20928
|
+
connectionClosed: "\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF0C\u5C1D\u8BD5\u91CD\u8FDE\u4E2D",
|
|
20905
20929
|
unknown: "\u8FDE\u63A5\u5931\u8D25\uFF1A{{message}}",
|
|
20906
20930
|
reconnecting: "\u8FDE\u63A5\u4E2D\u65AD\uFF0C{{delay}} \u79D2\u540E\u81EA\u52A8\u91CD\u8FDE\uFF08{{attempt}}/{{maxRetries}}\uFF09",
|
|
20907
20931
|
reconnectFailed: "\u81EA\u52A8\u91CD\u8FDE\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u91CD\u8BD5",
|
|
20908
20932
|
reconnected: "\u8BBE\u5907\u5DF2\u81EA\u52A8\u91CD\u8FDE"
|
|
20909
20933
|
},
|
|
20934
|
+
deviceStatus: {
|
|
20935
|
+
reconnecting: "\u91CD\u8FDE\u4E2D {{delay}}s",
|
|
20936
|
+
offline: "\u79BB\u7EBF",
|
|
20937
|
+
errorBadge: {
|
|
20938
|
+
authFailed: "\u8BA4\u8BC1\u5931\u8D25",
|
|
20939
|
+
agentUnavailable: "Agent \u4E0D\u53EF\u7528",
|
|
20940
|
+
agentNoIdentity: "Agent \u65E0\u5BC6\u94A5",
|
|
20941
|
+
configRefNotSupported: "\u4E0D\u652F\u6301 SSH Config",
|
|
20942
|
+
networkUnreachable: "\u7F51\u7EDC\u4E0D\u53EF\u8FBE",
|
|
20943
|
+
connectionRefused: "\u8FDE\u63A5\u88AB\u62D2",
|
|
20944
|
+
timeout: "\u8FDE\u63A5\u8D85\u65F6",
|
|
20945
|
+
hostNotFound: "\u4E3B\u673A\u672A\u627E\u5230",
|
|
20946
|
+
handshakeFailed: "\u63E1\u624B\u5931\u8D25",
|
|
20947
|
+
tmuxUnavailable: "tmux \u4E0D\u53EF\u7528",
|
|
20948
|
+
connectionClosed: "\u8FDE\u63A5\u5DF2\u65AD\u5F00",
|
|
20949
|
+
unknown: "\u8FDE\u63A5\u5F02\u5E38"
|
|
20950
|
+
}
|
|
20951
|
+
},
|
|
20910
20952
|
websocket: {
|
|
20911
20953
|
error: "WebSocket \u8FDE\u63A5\u9519\u8BEF",
|
|
20912
20954
|
checkGateway: "\u8BF7\u68C0\u67E5 Gateway \u72B6\u6001",
|
|
@@ -21200,6 +21242,8 @@ Bot\uFF1A{{botName}}
|
|
|
21200
21242
|
chatId: "Chat ID",
|
|
21201
21243
|
applyTime: "\u7533\u8ACB\u6642\u9593",
|
|
21202
21244
|
gatewayOnline: "\uD83D\uDFE2 Gateway online @ {{siteName}}",
|
|
21245
|
+
deviceConnectionError: `\uD83D\uDD34 {{siteName}}\uFF1A\u30C7\u30D0\u30A4\u30B9\u300C{{deviceName}}\u300D({{host}}) \u3067\u63A5\u7D9A\u30A8\u30E9\u30FC [{{category}}]
|
|
21246
|
+
{{error}}`,
|
|
21203
21247
|
authSuccess: "\u2705 \u627F\u8A8D\u3055\u308C\u307E\u3057\u305F\u3002\u901A\u77E5\u3092\u53D7\u4FE1\u3067\u304D\u307E\u3059\u3002",
|
|
21204
21248
|
authPending: "\u23F3 \u8A8D\u8A3C\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u53D7\u4FE1\u3057\u307E\u3057\u305F\u3002tmex \u8A2D\u5B9A\u30DA\u30FC\u30B8\u3067\u627F\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
21205
21249
|
authFailed: "\u274C \u8A8D\u8A3C\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u7BA1\u7406\u8005\u306B\u9023\u7D61\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
@@ -21240,11 +21284,30 @@ Bot\uFF1A{{botName}}
|
|
|
21240
21284
|
hostNotFound: "\u30DB\u30B9\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\uFF1A\u30DB\u30B9\u30C8\u540D\u3092\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093\u3002DNS \u307E\u305F\u306F\u30DB\u30B9\u30C8\u540D\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
21241
21285
|
handshakeFailed: "\u30CF\u30F3\u30C9\u30B7\u30A7\u30A4\u30AF\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF1A\u5B89\u5168\u306A\u63A5\u7D9A\u3092\u78BA\u7ACB\u3067\u304D\u307E\u305B\u3093\u3002\u30AD\u30FC\u4EA4\u63DB\u30A2\u30EB\u30B4\u30EA\u30BA\u30E0\u304C\u4E92\u63DB\u6027\u304C\u306A\u3044\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
21242
21286
|
tmuxUnavailable: "\u30EA\u30E2\u30FC\u30C8 tmux \u304C\u5229\u7528\u3067\u304D\u306A\u3044\u304B\u8D77\u52D5\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002tmux \u304C\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3055\u308C\u3001\u30EA\u30E2\u30FC\u30C8 shell \u306E PATH \u304B\u3089\u53C2\u7167\u3067\u304D\u308B\u3053\u3068\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
21287
|
+
connectionClosed: "\u63A5\u7D9A\u304C\u5207\u65AD\u3055\u308C\u307E\u3057\u305F\u3002\u518D\u63A5\u7D9A\u3092\u8A66\u307F\u3066\u3044\u307E\u3059",
|
|
21243
21288
|
unknown: "\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF1A{{message}}",
|
|
21244
21289
|
reconnecting: "\u63A5\u7D9A\u304C\u4E2D\u65AD\u3055\u308C\u307E\u3057\u305F\u3002{{delay}} \u79D2\u5F8C\u306B\u518D\u63A5\u7D9A\u3057\u307E\u3059\uFF08{{attempt}}/{{maxRetries}}\uFF09",
|
|
21245
21290
|
reconnectFailed: "\u81EA\u52D5\u518D\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u624B\u52D5\u3067\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
21246
21291
|
reconnected: "\u30C7\u30D0\u30A4\u30B9\u304C\u81EA\u52D5\u7684\u306B\u518D\u63A5\u7D9A\u3055\u308C\u307E\u3057\u305F"
|
|
21247
21292
|
},
|
|
21293
|
+
deviceStatus: {
|
|
21294
|
+
reconnecting: "\u518D\u63A5\u7D9A\u4E2D {{delay}}s",
|
|
21295
|
+
offline: "\u30AA\u30D5\u30E9\u30A4\u30F3",
|
|
21296
|
+
errorBadge: {
|
|
21297
|
+
authFailed: "\u8A8D\u8A3C\u5931\u6557",
|
|
21298
|
+
agentUnavailable: "Agent \u5229\u7528\u4E0D\u53EF",
|
|
21299
|
+
agentNoIdentity: "Agent \u306B\u9375\u306A\u3057",
|
|
21300
|
+
configRefNotSupported: "SSH Config \u975E\u5BFE\u5FDC",
|
|
21301
|
+
networkUnreachable: "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u4E0D\u53EF\u9054",
|
|
21302
|
+
connectionRefused: "\u63A5\u7D9A\u62D2\u5426",
|
|
21303
|
+
timeout: "\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8",
|
|
21304
|
+
hostNotFound: "\u30DB\u30B9\u30C8\u672A\u691C\u51FA",
|
|
21305
|
+
handshakeFailed: "\u30CF\u30F3\u30C9\u30B7\u30A7\u30A4\u30AF\u5931\u6557",
|
|
21306
|
+
tmuxUnavailable: "tmux \u5229\u7528\u4E0D\u53EF",
|
|
21307
|
+
connectionClosed: "\u5207\u65AD",
|
|
21308
|
+
unknown: "\u63A5\u7D9A\u30A8\u30E9\u30FC"
|
|
21309
|
+
}
|
|
21310
|
+
},
|
|
21248
21311
|
websocket: {
|
|
21249
21312
|
error: "WebSocket \u63A5\u7D9A\u30A8\u30E9\u30FC",
|
|
21250
21313
|
checkGateway: "Gateway \u72B6\u614B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
@@ -28884,7 +28947,8 @@ var deviceRuntimeStatus = sqliteTable("device_runtime_status", {
|
|
|
28884
28947
|
deviceId: text("device_id").primaryKey().references(() => devices.id, { onDelete: "cascade" }),
|
|
28885
28948
|
lastSeenAt: text("last_seen_at"),
|
|
28886
28949
|
tmuxAvailable: integer("tmux_available", { mode: "boolean" }).notNull().default(false),
|
|
28887
|
-
lastError: text("last_error")
|
|
28950
|
+
lastError: text("last_error"),
|
|
28951
|
+
lastErrorType: text("last_error_type")
|
|
28888
28952
|
});
|
|
28889
28953
|
var webhookEndpoints = sqliteTable("webhook_endpoints", {
|
|
28890
28954
|
id: text("id").primaryKey(),
|
|
@@ -29057,7 +29121,8 @@ function createDevice(device) {
|
|
|
29057
29121
|
deviceId: device.id,
|
|
29058
29122
|
lastSeenAt: null,
|
|
29059
29123
|
tmuxAvailable: false,
|
|
29060
|
-
lastError: null
|
|
29124
|
+
lastError: null,
|
|
29125
|
+
lastErrorType: null
|
|
29061
29126
|
}).onConflictDoNothing({ target: deviceRuntimeStatus.deviceId }).run();
|
|
29062
29127
|
});
|
|
29063
29128
|
}
|
|
@@ -29114,6 +29179,26 @@ function deleteDevice(id) {
|
|
|
29114
29179
|
const orm = getDb();
|
|
29115
29180
|
orm.delete(devices).where(eq(devices.id, id)).run();
|
|
29116
29181
|
}
|
|
29182
|
+
function getDeviceRuntimeStatus(deviceId) {
|
|
29183
|
+
const orm = getDb();
|
|
29184
|
+
const row = orm.select().from(deviceRuntimeStatus).where(eq(deviceRuntimeStatus.deviceId, deviceId)).get();
|
|
29185
|
+
if (!row) {
|
|
29186
|
+
return {
|
|
29187
|
+
deviceId,
|
|
29188
|
+
lastSeenAt: null,
|
|
29189
|
+
tmuxAvailable: false,
|
|
29190
|
+
lastError: null,
|
|
29191
|
+
lastErrorType: null
|
|
29192
|
+
};
|
|
29193
|
+
}
|
|
29194
|
+
return {
|
|
29195
|
+
deviceId: row.deviceId,
|
|
29196
|
+
lastSeenAt: row.lastSeenAt,
|
|
29197
|
+
tmuxAvailable: row.tmuxAvailable,
|
|
29198
|
+
lastError: row.lastError,
|
|
29199
|
+
lastErrorType: row.lastErrorType
|
|
29200
|
+
};
|
|
29201
|
+
}
|
|
29117
29202
|
function updateDeviceRuntimeStatus(deviceId, status) {
|
|
29118
29203
|
const orm = getDb();
|
|
29119
29204
|
const setValues = {};
|
|
@@ -29126,6 +29211,9 @@ function updateDeviceRuntimeStatus(deviceId, status) {
|
|
|
29126
29211
|
if (status.lastError !== undefined) {
|
|
29127
29212
|
setValues.lastError = status.lastError;
|
|
29128
29213
|
}
|
|
29214
|
+
if (status.lastErrorType !== undefined) {
|
|
29215
|
+
setValues.lastErrorType = status.lastErrorType;
|
|
29216
|
+
}
|
|
29129
29217
|
if (Object.keys(setValues).length === 0) {
|
|
29130
29218
|
return;
|
|
29131
29219
|
}
|
|
@@ -52033,6 +52121,241 @@ var eventNotifier = new EventNotifier;
|
|
|
52033
52121
|
import { mkdirSync, rmSync } from "fs";
|
|
52034
52122
|
import { homedir } from "os";
|
|
52035
52123
|
|
|
52124
|
+
// ../../apps/gateway/src/ws/error-classify.ts
|
|
52125
|
+
function classifySshError(error) {
|
|
52126
|
+
const msg = error.message.toLowerCase();
|
|
52127
|
+
if (msg.includes("ssh_config_ref_not_supported")) {
|
|
52128
|
+
return {
|
|
52129
|
+
type: "ssh_config_ref_not_supported",
|
|
52130
|
+
messageKey: "sshError.configRefNotSupported"
|
|
52131
|
+
};
|
|
52132
|
+
}
|
|
52133
|
+
if (msg.includes("ssh_auth_sock") || msg.includes("auth_sock")) {
|
|
52134
|
+
return {
|
|
52135
|
+
type: "agent_unavailable",
|
|
52136
|
+
messageKey: "sshError.agentUnavailable"
|
|
52137
|
+
};
|
|
52138
|
+
}
|
|
52139
|
+
if (msg.includes("agent") && (msg.includes("no identities") || msg.includes("failure"))) {
|
|
52140
|
+
return {
|
|
52141
|
+
type: "agent_no_identity",
|
|
52142
|
+
messageKey: "sshError.agentNoIdentities"
|
|
52143
|
+
};
|
|
52144
|
+
}
|
|
52145
|
+
if (msg.includes("permission denied")) {
|
|
52146
|
+
return {
|
|
52147
|
+
type: "auth_failed",
|
|
52148
|
+
messageKey: "sshError.authFailed"
|
|
52149
|
+
};
|
|
52150
|
+
}
|
|
52151
|
+
if (msg.includes("all configured authentication methods failed")) {
|
|
52152
|
+
return {
|
|
52153
|
+
type: "auth_failed",
|
|
52154
|
+
messageKey: "sshError.authFailedGeneric"
|
|
52155
|
+
};
|
|
52156
|
+
}
|
|
52157
|
+
if (msg.includes("enetunreach") || msg.includes("ehostunreach")) {
|
|
52158
|
+
return {
|
|
52159
|
+
type: "network_unreachable",
|
|
52160
|
+
messageKey: "sshError.networkUnreachable"
|
|
52161
|
+
};
|
|
52162
|
+
}
|
|
52163
|
+
if (msg.includes("connect refused") || msg.includes("connection refused") || msg.includes("econnrefused")) {
|
|
52164
|
+
return {
|
|
52165
|
+
type: "connection_refused",
|
|
52166
|
+
messageKey: "sshError.connectionRefused"
|
|
52167
|
+
};
|
|
52168
|
+
}
|
|
52169
|
+
if (msg.includes("timeout") || msg.includes("etimedout")) {
|
|
52170
|
+
return {
|
|
52171
|
+
type: "timeout",
|
|
52172
|
+
messageKey: "sshError.connectionTimeout"
|
|
52173
|
+
};
|
|
52174
|
+
}
|
|
52175
|
+
if (msg.includes("host not found") || msg.includes("getaddrinfo") || msg.includes("enotfound")) {
|
|
52176
|
+
return {
|
|
52177
|
+
type: "host_not_found",
|
|
52178
|
+
messageKey: "sshError.hostNotFound"
|
|
52179
|
+
};
|
|
52180
|
+
}
|
|
52181
|
+
if (msg.includes("handshake failed") || msg.includes("unable to verify")) {
|
|
52182
|
+
return {
|
|
52183
|
+
type: "handshake_failed",
|
|
52184
|
+
messageKey: "sshError.handshakeFailed"
|
|
52185
|
+
};
|
|
52186
|
+
}
|
|
52187
|
+
if (msg.includes("remote tmux unavailable") || msg.includes("tmux_not_found") || msg.includes("tmux: command not found") || msg.includes("tmux control mode not ready") || msg.includes("tmux exited") || msg.includes("tmux_exec_failed")) {
|
|
52188
|
+
return {
|
|
52189
|
+
type: "tmux_unavailable",
|
|
52190
|
+
messageKey: "sshError.tmuxUnavailable"
|
|
52191
|
+
};
|
|
52192
|
+
}
|
|
52193
|
+
if (msg.includes("ssh_connection_closed") || msg.includes("connection closed") || msg.includes("ssh command channel not ready") || msg.includes("ssh connection not ready") || msg.includes("channel closed")) {
|
|
52194
|
+
return {
|
|
52195
|
+
type: "connection_closed",
|
|
52196
|
+
messageKey: "sshError.connectionClosed"
|
|
52197
|
+
};
|
|
52198
|
+
}
|
|
52199
|
+
return {
|
|
52200
|
+
type: "unknown",
|
|
52201
|
+
messageKey: "sshError.unknown",
|
|
52202
|
+
messageParams: { message: error.message }
|
|
52203
|
+
};
|
|
52204
|
+
}
|
|
52205
|
+
|
|
52206
|
+
// ../../apps/gateway/src/push/connection-alerts.ts
|
|
52207
|
+
var NOTIFY_THROTTLE_MS = 5 * 60 * 1000;
|
|
52208
|
+
function toErrorObject(err) {
|
|
52209
|
+
if (err instanceof Error) {
|
|
52210
|
+
return err;
|
|
52211
|
+
}
|
|
52212
|
+
if (typeof err === "string") {
|
|
52213
|
+
return new Error(err);
|
|
52214
|
+
}
|
|
52215
|
+
try {
|
|
52216
|
+
return new Error(JSON.stringify(err));
|
|
52217
|
+
} catch {
|
|
52218
|
+
return new Error(String(err));
|
|
52219
|
+
}
|
|
52220
|
+
}
|
|
52221
|
+
|
|
52222
|
+
class ConnectionAlertNotifier {
|
|
52223
|
+
throttleMap = new Map;
|
|
52224
|
+
broadcaster = null;
|
|
52225
|
+
settingsProvider = () => getSiteSettings();
|
|
52226
|
+
persister = (deviceId, friendlyMessage, errorType) => {
|
|
52227
|
+
updateDeviceRuntimeStatus(deviceId, {
|
|
52228
|
+
lastSeenAt: new Date().toISOString(),
|
|
52229
|
+
lastError: friendlyMessage,
|
|
52230
|
+
lastErrorType: errorType
|
|
52231
|
+
});
|
|
52232
|
+
};
|
|
52233
|
+
telegramSender = (text2) => telegramService.sendToAuthorizedChats({ text: text2 });
|
|
52234
|
+
setBroadcaster(broadcaster) {
|
|
52235
|
+
this.broadcaster = broadcaster;
|
|
52236
|
+
}
|
|
52237
|
+
setSettingsProvider(provider) {
|
|
52238
|
+
this.settingsProvider = provider;
|
|
52239
|
+
}
|
|
52240
|
+
setPersister(persister) {
|
|
52241
|
+
this.persister = persister;
|
|
52242
|
+
}
|
|
52243
|
+
setTelegramSender(sender) {
|
|
52244
|
+
this.telegramSender = sender;
|
|
52245
|
+
}
|
|
52246
|
+
async notify(alert) {
|
|
52247
|
+
const { device, error, source, silentTelegram = false, persist = true } = alert;
|
|
52248
|
+
const errObj = toErrorObject(error);
|
|
52249
|
+
const classified = classifySshError(errObj);
|
|
52250
|
+
const friendlyMessage = t2(classified.messageKey, { ...classified.messageParams });
|
|
52251
|
+
const rawMessage = errObj.message;
|
|
52252
|
+
console.error(`[conn-alert] device ${device.id} (${device.name}) source=${source} type=${classified.type}: ${rawMessage}`);
|
|
52253
|
+
if (persist) {
|
|
52254
|
+
try {
|
|
52255
|
+
this.persister(device.id, friendlyMessage, classified.type);
|
|
52256
|
+
} catch (dbErr) {
|
|
52257
|
+
console.error("[conn-alert] failed to persist runtime status:", dbErr);
|
|
52258
|
+
}
|
|
52259
|
+
}
|
|
52260
|
+
if (this.broadcaster) {
|
|
52261
|
+
try {
|
|
52262
|
+
this.broadcaster(device.id, {
|
|
52263
|
+
deviceId: device.id,
|
|
52264
|
+
type: "error",
|
|
52265
|
+
errorType: classified.type,
|
|
52266
|
+
message: friendlyMessage,
|
|
52267
|
+
rawMessage
|
|
52268
|
+
});
|
|
52269
|
+
} catch (broadcastErr) {
|
|
52270
|
+
console.error("[conn-alert] failed to broadcast:", broadcastErr);
|
|
52271
|
+
}
|
|
52272
|
+
}
|
|
52273
|
+
if (!silentTelegram && this.shouldSendTelegram(device.id, classified.type)) {
|
|
52274
|
+
await this.sendTelegram(device, classified.type, friendlyMessage, rawMessage);
|
|
52275
|
+
}
|
|
52276
|
+
return {
|
|
52277
|
+
errorType: classified.type,
|
|
52278
|
+
messageKey: classified.messageKey,
|
|
52279
|
+
message: friendlyMessage,
|
|
52280
|
+
rawMessage
|
|
52281
|
+
};
|
|
52282
|
+
}
|
|
52283
|
+
clear(deviceId) {
|
|
52284
|
+
for (const key of this.throttleMap.keys()) {
|
|
52285
|
+
if (key.startsWith(`${deviceId}:`)) {
|
|
52286
|
+
this.throttleMap.delete(key);
|
|
52287
|
+
}
|
|
52288
|
+
}
|
|
52289
|
+
}
|
|
52290
|
+
shouldSendTelegram(deviceId, errorType) {
|
|
52291
|
+
const key = `${deviceId}:${errorType}`;
|
|
52292
|
+
const now = Date.now();
|
|
52293
|
+
const last = this.throttleMap.get(key) ?? 0;
|
|
52294
|
+
if (now - last < NOTIFY_THROTTLE_MS) {
|
|
52295
|
+
return false;
|
|
52296
|
+
}
|
|
52297
|
+
this.throttleMap.set(key, now);
|
|
52298
|
+
for (const [otherKey, ts] of this.throttleMap) {
|
|
52299
|
+
if (otherKey !== key && otherKey.startsWith(`${deviceId}:`) && now - ts >= NOTIFY_THROTTLE_MS) {
|
|
52300
|
+
this.throttleMap.delete(otherKey);
|
|
52301
|
+
}
|
|
52302
|
+
}
|
|
52303
|
+
return true;
|
|
52304
|
+
}
|
|
52305
|
+
async sendTelegram(device, errorType, friendlyMessage, rawMessage) {
|
|
52306
|
+
let settings;
|
|
52307
|
+
try {
|
|
52308
|
+
settings = this.settingsProvider();
|
|
52309
|
+
} catch (err) {
|
|
52310
|
+
console.error("[conn-alert] failed to read site settings:", err);
|
|
52311
|
+
return;
|
|
52312
|
+
}
|
|
52313
|
+
const categoryKey = `deviceStatus.errorBadge.${toBadgeKey(errorType)}`;
|
|
52314
|
+
const translatedCategory = t2(categoryKey, { defaultValue: errorType });
|
|
52315
|
+
const text2 = t2("telegram.deviceConnectionError", {
|
|
52316
|
+
siteName: settings.siteName,
|
|
52317
|
+
deviceName: device.name,
|
|
52318
|
+
host: device.host ?? "-",
|
|
52319
|
+
category: translatedCategory,
|
|
52320
|
+
error: friendlyMessage || rawMessage
|
|
52321
|
+
});
|
|
52322
|
+
try {
|
|
52323
|
+
await this.telegramSender(text2);
|
|
52324
|
+
} catch (notifyErr) {
|
|
52325
|
+
console.error("[conn-alert] telegram send failed:", notifyErr);
|
|
52326
|
+
}
|
|
52327
|
+
}
|
|
52328
|
+
}
|
|
52329
|
+
function toBadgeKey(errorType) {
|
|
52330
|
+
switch (errorType) {
|
|
52331
|
+
case "auth_failed":
|
|
52332
|
+
return "authFailed";
|
|
52333
|
+
case "agent_unavailable":
|
|
52334
|
+
return "agentUnavailable";
|
|
52335
|
+
case "agent_no_identity":
|
|
52336
|
+
return "agentNoIdentity";
|
|
52337
|
+
case "ssh_config_ref_not_supported":
|
|
52338
|
+
return "configRefNotSupported";
|
|
52339
|
+
case "network_unreachable":
|
|
52340
|
+
return "networkUnreachable";
|
|
52341
|
+
case "connection_refused":
|
|
52342
|
+
return "connectionRefused";
|
|
52343
|
+
case "timeout":
|
|
52344
|
+
return "timeout";
|
|
52345
|
+
case "host_not_found":
|
|
52346
|
+
return "hostNotFound";
|
|
52347
|
+
case "handshake_failed":
|
|
52348
|
+
return "handshakeFailed";
|
|
52349
|
+
case "tmux_unavailable":
|
|
52350
|
+
return "tmuxUnavailable";
|
|
52351
|
+
case "connection_closed":
|
|
52352
|
+
return "connectionClosed";
|
|
52353
|
+
default:
|
|
52354
|
+
return "unknown";
|
|
52355
|
+
}
|
|
52356
|
+
}
|
|
52357
|
+
var connectionAlertNotifier = new ConnectionAlertNotifier;
|
|
52358
|
+
|
|
52036
52359
|
// ../../apps/gateway/src/tmux/local-shell-path.ts
|
|
52037
52360
|
import { existsSync } from "fs";
|
|
52038
52361
|
import { delimiter, join as join2 } from "path";
|
|
@@ -52491,6 +52814,8 @@ class LocalExternalTmuxConnection {
|
|
|
52491
52814
|
hookReadAbort = null;
|
|
52492
52815
|
hookBuffer = "";
|
|
52493
52816
|
bellDedup = new Map;
|
|
52817
|
+
closeNotified = false;
|
|
52818
|
+
cleanupPromise = null;
|
|
52494
52819
|
fsPaths = createRuntimeFsPaths({
|
|
52495
52820
|
deviceId: "pending",
|
|
52496
52821
|
sessionName: "pending",
|
|
@@ -52507,6 +52832,7 @@ class LocalExternalTmuxConnection {
|
|
|
52507
52832
|
}
|
|
52508
52833
|
async connect() {
|
|
52509
52834
|
this.manualDisconnect = false;
|
|
52835
|
+
this.closeNotified = false;
|
|
52510
52836
|
this.device = this.deps.getDevice(this.deviceId);
|
|
52511
52837
|
if (!this.device) {
|
|
52512
52838
|
throw new Error(`Device not found: ${this.deviceId}`);
|
|
@@ -52530,7 +52856,8 @@ class LocalExternalTmuxConnection {
|
|
|
52530
52856
|
updateDeviceRuntimeStatus(this.deviceId, {
|
|
52531
52857
|
lastSeenAt: new Date().toISOString(),
|
|
52532
52858
|
tmuxAvailable: true,
|
|
52533
|
-
lastError: null
|
|
52859
|
+
lastError: null,
|
|
52860
|
+
lastErrorType: null
|
|
52534
52861
|
});
|
|
52535
52862
|
await this.requestSnapshotInternal();
|
|
52536
52863
|
}
|
|
@@ -52814,6 +53141,20 @@ class LocalExternalTmuxConnection {
|
|
|
52814
53141
|
])
|
|
52815
53142
|
]);
|
|
52816
53143
|
if (sessionRes.exitCode !== 0 || windowsRes.exitCode !== 0 || panesRes.exitCode !== 0) {
|
|
53144
|
+
const stderrBlob = `${sessionRes.stderr}
|
|
53145
|
+
${windowsRes.stderr}
|
|
53146
|
+
${panesRes.stderr}`;
|
|
53147
|
+
if (this.connected && !this.manualDisconnect && this.isTmuxServerGoneMessage(stderrBlob)) {
|
|
53148
|
+
const message = stderrBlob.trim().split(/\r?\n/).find((line) => line.trim())?.trim() ?? "tmux server gone";
|
|
53149
|
+
console.warn(`[local] tmux server gone during snapshot on ${this.deviceId}: ${message}`);
|
|
53150
|
+
updateDeviceRuntimeStatus(this.deviceId, {
|
|
53151
|
+
lastSeenAt: new Date().toISOString(),
|
|
53152
|
+
tmuxAvailable: false,
|
|
53153
|
+
lastError: message
|
|
53154
|
+
});
|
|
53155
|
+
this.shutdownInternal(true);
|
|
53156
|
+
return;
|
|
53157
|
+
}
|
|
52817
53158
|
this.callbacks.onSnapshot({ deviceId: this.deviceId, session: null });
|
|
52818
53159
|
return;
|
|
52819
53160
|
}
|
|
@@ -53058,13 +53399,30 @@ class LocalExternalTmuxConnection {
|
|
|
53058
53399
|
this.recoverFromTargetMissingError(message);
|
|
53059
53400
|
return result;
|
|
53060
53401
|
}
|
|
53061
|
-
|
|
53062
|
-
|
|
53063
|
-
|
|
53064
|
-
|
|
53065
|
-
}
|
|
53402
|
+
this.notifyRuntimeError(message);
|
|
53403
|
+
if (this.connected && !this.manualDisconnect && this.isTmuxServerGoneMessage(message)) {
|
|
53404
|
+
console.warn(`[local] tmux server gone on ${this.deviceId}: ${message}`);
|
|
53405
|
+
this.shutdownInternal(true);
|
|
53406
|
+
}
|
|
53066
53407
|
throw new Error(message);
|
|
53067
53408
|
}
|
|
53409
|
+
async notifyRuntimeError(message) {
|
|
53410
|
+
const device = getDeviceById(this.deviceId);
|
|
53411
|
+
if (!device) {
|
|
53412
|
+
updateDeviceRuntimeStatus(this.deviceId, {
|
|
53413
|
+
lastSeenAt: new Date().toISOString(),
|
|
53414
|
+
tmuxAvailable: false,
|
|
53415
|
+
lastError: message
|
|
53416
|
+
});
|
|
53417
|
+
return;
|
|
53418
|
+
}
|
|
53419
|
+
await connectionAlertNotifier.notify({
|
|
53420
|
+
device,
|
|
53421
|
+
error: new Error(message),
|
|
53422
|
+
source: "runtime",
|
|
53423
|
+
silentTelegram: true
|
|
53424
|
+
});
|
|
53425
|
+
}
|
|
53068
53426
|
async runTmuxAllowFailure(argv) {
|
|
53069
53427
|
return this.deps.run(["tmux", ...argv]);
|
|
53070
53428
|
}
|
|
@@ -53072,6 +53430,40 @@ class LocalExternalTmuxConnection {
|
|
|
53072
53430
|
const normalized = message.toLowerCase();
|
|
53073
53431
|
return normalized.includes("can't find window") || normalized.includes("can't find pane") || normalized.includes("no such window") || normalized.includes("no such pane");
|
|
53074
53432
|
}
|
|
53433
|
+
isTmuxServerGoneMessage(message) {
|
|
53434
|
+
const normalized = message.toLowerCase();
|
|
53435
|
+
return normalized.includes("no server running on") || normalized.includes("no sessions") || normalized.includes("lost server") || normalized.includes("can't find session") || normalized.includes("session not found") || normalized.includes("no such session");
|
|
53436
|
+
}
|
|
53437
|
+
async shutdownInternal(notifyClose) {
|
|
53438
|
+
if (this.cleanupPromise) {
|
|
53439
|
+
await this.cleanupPromise;
|
|
53440
|
+
if (notifyClose && !this.closeNotified && !this.manualDisconnect) {
|
|
53441
|
+
this.closeNotified = true;
|
|
53442
|
+
this.callbacks.onClose();
|
|
53443
|
+
}
|
|
53444
|
+
return;
|
|
53445
|
+
}
|
|
53446
|
+
this.connected = false;
|
|
53447
|
+
this.cleanupPromise = (async () => {
|
|
53448
|
+
await this.stopAllPipeReaders().catch(() => {
|
|
53449
|
+
return;
|
|
53450
|
+
});
|
|
53451
|
+
if (this.deps.enableHooks) {
|
|
53452
|
+
await this.stopHooks().catch(() => {
|
|
53453
|
+
return;
|
|
53454
|
+
});
|
|
53455
|
+
}
|
|
53456
|
+
try {
|
|
53457
|
+
rmSync(this.fsPaths.rootDir, { recursive: true, force: true });
|
|
53458
|
+
} catch {}
|
|
53459
|
+
})();
|
|
53460
|
+
await this.cleanupPromise;
|
|
53461
|
+
this.cleanupPromise = null;
|
|
53462
|
+
if (notifyClose && !this.closeNotified && !this.manualDisconnect) {
|
|
53463
|
+
this.closeNotified = true;
|
|
53464
|
+
this.callbacks.onClose();
|
|
53465
|
+
}
|
|
53466
|
+
}
|
|
53075
53467
|
recoverFromTargetMissingError(message) {
|
|
53076
53468
|
const normalized = message.toLowerCase();
|
|
53077
53469
|
if (normalized.includes("window")) {
|
|
@@ -53474,7 +53866,8 @@ class SshExternalTmuxConnection {
|
|
|
53474
53866
|
updateDeviceRuntimeStatus(this.deviceId, {
|
|
53475
53867
|
lastSeenAt: new Date().toISOString(),
|
|
53476
53868
|
tmuxAvailable: true,
|
|
53477
|
-
lastError: null
|
|
53869
|
+
lastError: null,
|
|
53870
|
+
lastErrorType: null
|
|
53478
53871
|
});
|
|
53479
53872
|
await this.requestSnapshotInternal();
|
|
53480
53873
|
}
|
|
@@ -53706,9 +54099,11 @@ class SshExternalTmuxConnection {
|
|
|
53706
54099
|
this.handleHookChunk(data.toString());
|
|
53707
54100
|
},
|
|
53708
54101
|
onClose: () => {
|
|
53709
|
-
if (
|
|
53710
|
-
|
|
54102
|
+
if (this.manualDisconnect) {
|
|
54103
|
+
return;
|
|
53711
54104
|
}
|
|
54105
|
+
console.error("[ssh] hook reader channel closed unexpectedly, tearing down");
|
|
54106
|
+
this.shutdownInternal(true);
|
|
53712
54107
|
}
|
|
53713
54108
|
});
|
|
53714
54109
|
this.hookReadAbort = () => {
|
|
@@ -53836,6 +54231,20 @@ class SshExternalTmuxConnection {
|
|
|
53836
54231
|
])
|
|
53837
54232
|
]);
|
|
53838
54233
|
if (sessionRes.exitCode !== 0 || windowsRes.exitCode !== 0 || panesRes.exitCode !== 0) {
|
|
54234
|
+
const stderrBlob = `${sessionRes.stderr}
|
|
54235
|
+
${windowsRes.stderr}
|
|
54236
|
+
${panesRes.stderr}`;
|
|
54237
|
+
if (this.connected && !this.manualDisconnect && this.isTmuxServerGoneMessage(stderrBlob)) {
|
|
54238
|
+
const message = stderrBlob.trim().split(/\r?\n/).find((line) => line.trim())?.trim() ?? "tmux server gone";
|
|
54239
|
+
console.warn(`[ssh] tmux server gone during snapshot on ${this.deviceId}: ${message}`);
|
|
54240
|
+
updateDeviceRuntimeStatus(this.deviceId, {
|
|
54241
|
+
lastSeenAt: new Date().toISOString(),
|
|
54242
|
+
tmuxAvailable: false,
|
|
54243
|
+
lastError: message
|
|
54244
|
+
});
|
|
54245
|
+
this.shutdownInternal(true);
|
|
54246
|
+
return;
|
|
54247
|
+
}
|
|
53839
54248
|
this.callbacks.onSnapshot({ deviceId: this.deviceId, session: null });
|
|
53840
54249
|
return;
|
|
53841
54250
|
}
|
|
@@ -53995,9 +54404,17 @@ class SshExternalTmuxConnection {
|
|
|
53995
54404
|
}
|
|
53996
54405
|
},
|
|
53997
54406
|
onClose: () => {
|
|
53998
|
-
if (
|
|
53999
|
-
|
|
54407
|
+
if (this.manualDisconnect) {
|
|
54408
|
+
return;
|
|
54000
54409
|
}
|
|
54410
|
+
const existing = this.paneReaders.get(paneId);
|
|
54411
|
+
if (!existing) {
|
|
54412
|
+
return;
|
|
54413
|
+
}
|
|
54414
|
+
console.warn(`[ssh] pane reader channel closed for ${paneId}, resync on next snapshot`);
|
|
54415
|
+
this.paneReaders.delete(paneId);
|
|
54416
|
+
this.runShellAllowFailure(`rm -f ${quoteShellArg(existing.fifoPath)}`);
|
|
54417
|
+
this.requestSnapshot();
|
|
54001
54418
|
}
|
|
54002
54419
|
});
|
|
54003
54420
|
const handle = {
|
|
@@ -54053,7 +54470,9 @@ class SshExternalTmuxConnection {
|
|
|
54053
54470
|
const next = this.pipeTransition.catch(() => {
|
|
54054
54471
|
return;
|
|
54055
54472
|
}).then(task);
|
|
54056
|
-
this.pipeTransition = next
|
|
54473
|
+
this.pipeTransition = next.catch(() => {
|
|
54474
|
+
return;
|
|
54475
|
+
});
|
|
54057
54476
|
return next;
|
|
54058
54477
|
}
|
|
54059
54478
|
async runTmux(argv, allowTargetMissing = false, timeoutMs = 1e4) {
|
|
@@ -54071,6 +54490,10 @@ class SshExternalTmuxConnection {
|
|
|
54071
54490
|
tmuxAvailable: false,
|
|
54072
54491
|
lastError: message
|
|
54073
54492
|
});
|
|
54493
|
+
if (this.connected && !this.manualDisconnect && this.isTmuxServerGoneMessage(message)) {
|
|
54494
|
+
console.warn(`[ssh] tmux server gone on ${this.deviceId}: ${message}`);
|
|
54495
|
+
this.shutdownInternal(true);
|
|
54496
|
+
}
|
|
54074
54497
|
throw new Error(message);
|
|
54075
54498
|
}
|
|
54076
54499
|
async runTmuxAllowFailure(argv, timeoutMs = 1e4) {
|
|
@@ -54096,6 +54519,8 @@ class SshExternalTmuxConnection {
|
|
|
54096
54519
|
}).then(() => this.executeShellCommand(command, timeoutMs));
|
|
54097
54520
|
this.commandQueue = next.then(() => {
|
|
54098
54521
|
return;
|
|
54522
|
+
}, () => {
|
|
54523
|
+
return;
|
|
54099
54524
|
});
|
|
54100
54525
|
return next;
|
|
54101
54526
|
}
|
|
@@ -54196,6 +54621,10 @@ printf '\\036TMEX_END %s %d\\036\\n' ${quoteShellArg(commandId)} $?
|
|
|
54196
54621
|
const normalized = message.toLowerCase();
|
|
54197
54622
|
return normalized.includes("can't find window") || normalized.includes("can't find pane") || normalized.includes("no such window") || normalized.includes("no such pane");
|
|
54198
54623
|
}
|
|
54624
|
+
isTmuxServerGoneMessage(message) {
|
|
54625
|
+
const normalized = message.toLowerCase();
|
|
54626
|
+
return normalized.includes("no server running on") || normalized.includes("no sessions") || normalized.includes("lost server") || normalized.includes("can't find session") || normalized.includes("session not found") || normalized.includes("no such session");
|
|
54627
|
+
}
|
|
54199
54628
|
recoverFromTargetMissingError(message) {
|
|
54200
54629
|
const normalized = message.toLowerCase();
|
|
54201
54630
|
if (normalized.includes("window")) {
|
|
@@ -54657,9 +55086,14 @@ class PushSupervisor {
|
|
|
54657
55086
|
},
|
|
54658
55087
|
onError: (error) => {
|
|
54659
55088
|
console.error(`[push] tmux error on device ${entry.deviceId}:`, error);
|
|
55089
|
+
connectionAlertNotifier.notify({
|
|
55090
|
+
device,
|
|
55091
|
+
error,
|
|
55092
|
+
source: "runtime"
|
|
55093
|
+
});
|
|
54660
55094
|
},
|
|
54661
55095
|
onClose: () => {
|
|
54662
|
-
this.handleClose(entry.deviceId, generation, runtime);
|
|
55096
|
+
this.handleClose(entry.deviceId, generation, runtime, device);
|
|
54663
55097
|
}
|
|
54664
55098
|
});
|
|
54665
55099
|
entry.runtime = runtime;
|
|
@@ -54683,6 +55117,11 @@ class PushSupervisor {
|
|
|
54683
55117
|
return;
|
|
54684
55118
|
}
|
|
54685
55119
|
console.error(`[push] failed connecting device ${entry.deviceId}:`, err);
|
|
55120
|
+
await connectionAlertNotifier.notify({
|
|
55121
|
+
device,
|
|
55122
|
+
error: err,
|
|
55123
|
+
source: "connect"
|
|
55124
|
+
});
|
|
54686
55125
|
detachRuntime();
|
|
54687
55126
|
entry.detachRuntime = null;
|
|
54688
55127
|
entry.runtime = null;
|
|
@@ -54720,11 +55159,16 @@ class PushSupervisor {
|
|
|
54720
55159
|
this.connectEntry(entry);
|
|
54721
55160
|
}, delayMs);
|
|
54722
55161
|
}
|
|
54723
|
-
async handleClose(deviceId, generation, runtime) {
|
|
55162
|
+
async handleClose(deviceId, generation, runtime, device) {
|
|
54724
55163
|
const entry = this.entries.get(deviceId);
|
|
54725
55164
|
if (!entry || entry.generation !== generation || entry.runtime !== runtime) {
|
|
54726
55165
|
return;
|
|
54727
55166
|
}
|
|
55167
|
+
await connectionAlertNotifier.notify({
|
|
55168
|
+
device,
|
|
55169
|
+
error: new Error("ssh_connection_closed"),
|
|
55170
|
+
source: "close"
|
|
55171
|
+
});
|
|
54728
55172
|
entry.detachRuntime?.();
|
|
54729
55173
|
entry.detachRuntime = null;
|
|
54730
55174
|
entry.runtime = null;
|
|
@@ -54785,82 +55229,6 @@ class PushSupervisor {
|
|
|
54785
55229
|
}
|
|
54786
55230
|
var pushSupervisor = new PushSupervisor;
|
|
54787
55231
|
|
|
54788
|
-
// ../../apps/gateway/src/ws/error-classify.ts
|
|
54789
|
-
function classifySshError(error) {
|
|
54790
|
-
const msg = error.message.toLowerCase();
|
|
54791
|
-
if (msg.includes("ssh_config_ref_not_supported")) {
|
|
54792
|
-
return {
|
|
54793
|
-
type: "ssh_config_ref_not_supported",
|
|
54794
|
-
messageKey: "sshError.configRefNotSupported"
|
|
54795
|
-
};
|
|
54796
|
-
}
|
|
54797
|
-
if (msg.includes("ssh_auth_sock") || msg.includes("auth_sock")) {
|
|
54798
|
-
return {
|
|
54799
|
-
type: "agent_unavailable",
|
|
54800
|
-
messageKey: "sshError.agentUnavailable"
|
|
54801
|
-
};
|
|
54802
|
-
}
|
|
54803
|
-
if (msg.includes("agent") && (msg.includes("no identities") || msg.includes("failure"))) {
|
|
54804
|
-
return {
|
|
54805
|
-
type: "agent_no_identity",
|
|
54806
|
-
messageKey: "sshError.agentNoIdentities"
|
|
54807
|
-
};
|
|
54808
|
-
}
|
|
54809
|
-
if (msg.includes("permission denied")) {
|
|
54810
|
-
return {
|
|
54811
|
-
type: "auth_failed",
|
|
54812
|
-
messageKey: "sshError.authFailed"
|
|
54813
|
-
};
|
|
54814
|
-
}
|
|
54815
|
-
if (msg.includes("all configured authentication methods failed")) {
|
|
54816
|
-
return {
|
|
54817
|
-
type: "auth_failed",
|
|
54818
|
-
messageKey: "sshError.authFailedGeneric"
|
|
54819
|
-
};
|
|
54820
|
-
}
|
|
54821
|
-
if (msg.includes("enetunreach") || msg.includes("ehostunreach")) {
|
|
54822
|
-
return {
|
|
54823
|
-
type: "network_unreachable",
|
|
54824
|
-
messageKey: "sshError.networkUnreachable"
|
|
54825
|
-
};
|
|
54826
|
-
}
|
|
54827
|
-
if (msg.includes("connect refused") || msg.includes("connection refused") || msg.includes("econnrefused")) {
|
|
54828
|
-
return {
|
|
54829
|
-
type: "connection_refused",
|
|
54830
|
-
messageKey: "sshError.connectionRefused"
|
|
54831
|
-
};
|
|
54832
|
-
}
|
|
54833
|
-
if (msg.includes("timeout") || msg.includes("etimedout")) {
|
|
54834
|
-
return {
|
|
54835
|
-
type: "timeout",
|
|
54836
|
-
messageKey: "sshError.connectionTimeout"
|
|
54837
|
-
};
|
|
54838
|
-
}
|
|
54839
|
-
if (msg.includes("host not found") || msg.includes("getaddrinfo") || msg.includes("enotfound")) {
|
|
54840
|
-
return {
|
|
54841
|
-
type: "host_not_found",
|
|
54842
|
-
messageKey: "sshError.hostNotFound"
|
|
54843
|
-
};
|
|
54844
|
-
}
|
|
54845
|
-
if (msg.includes("handshake failed") || msg.includes("unable to verify")) {
|
|
54846
|
-
return {
|
|
54847
|
-
type: "handshake_failed",
|
|
54848
|
-
messageKey: "sshError.handshakeFailed"
|
|
54849
|
-
};
|
|
54850
|
-
}
|
|
54851
|
-
if (msg.includes("remote tmux unavailable") || msg.includes("tmux_not_found") || msg.includes("tmux: command not found") || msg.includes("tmux control mode not ready") || msg.includes("tmux exited") || msg.includes("tmux_exec_failed")) {
|
|
54852
|
-
return {
|
|
54853
|
-
type: "tmux_unavailable",
|
|
54854
|
-
messageKey: "sshError.tmuxUnavailable"
|
|
54855
|
-
};
|
|
54856
|
-
}
|
|
54857
|
-
return {
|
|
54858
|
-
type: "unknown",
|
|
54859
|
-
messageKey: "sshError.unknown",
|
|
54860
|
-
messageParams: { message: error.message }
|
|
54861
|
-
};
|
|
54862
|
-
}
|
|
54863
|
-
|
|
54864
55232
|
// ../../apps/gateway/src/api/test-connection.ts
|
|
54865
55233
|
function inferFailurePhase(errorType) {
|
|
54866
55234
|
if (errorType === "tmux_unavailable") {
|
|
@@ -55094,8 +55462,18 @@ function handleApiRequest(req, _server) {
|
|
|
55094
55462
|
}
|
|
55095
55463
|
return json2({ error: t2("apiError.notFound") }, 404);
|
|
55096
55464
|
}
|
|
55465
|
+
function enrichDeviceWithRuntime(device) {
|
|
55466
|
+
const status = getDeviceRuntimeStatus(device.id);
|
|
55467
|
+
return {
|
|
55468
|
+
...device,
|
|
55469
|
+
lastSeenAt: status.lastSeenAt,
|
|
55470
|
+
lastError: status.lastError,
|
|
55471
|
+
lastErrorType: status.lastErrorType,
|
|
55472
|
+
tmuxAvailable: status.tmuxAvailable
|
|
55473
|
+
};
|
|
55474
|
+
}
|
|
55097
55475
|
async function handleGetDevices() {
|
|
55098
|
-
const devices2 = getAllDevices();
|
|
55476
|
+
const devices2 = getAllDevices().map(enrichDeviceWithRuntime);
|
|
55099
55477
|
return json2({ devices: devices2 });
|
|
55100
55478
|
}
|
|
55101
55479
|
async function handleGetDevice(id) {
|
|
@@ -55103,7 +55481,7 @@ async function handleGetDevice(id) {
|
|
|
55103
55481
|
if (!device) {
|
|
55104
55482
|
return json2({ error: t2("apiError.deviceNotFound") }, 404);
|
|
55105
55483
|
}
|
|
55106
|
-
return json2({ device });
|
|
55484
|
+
return json2({ device: enrichDeviceWithRuntime(device) });
|
|
55107
55485
|
}
|
|
55108
55486
|
async function handleCreateDevice(req) {
|
|
55109
55487
|
const body = await req.json();
|
|
@@ -56675,6 +57053,15 @@ class WebSocketServer {
|
|
|
56675
57053
|
this.sendEnvelope(client, exports_ws_borsh.KIND_DEVICE_EVENT, payloadBytes);
|
|
56676
57054
|
}
|
|
56677
57055
|
}
|
|
57056
|
+
broadcastDeviceError(deviceId, payload) {
|
|
57057
|
+
const entry = this.connections.get(deviceId);
|
|
57058
|
+
if (!entry)
|
|
57059
|
+
return;
|
|
57060
|
+
const payloadBytes = exports_ws_borsh.encodeDeviceEventPayload(payload);
|
|
57061
|
+
for (const client of entry.clients) {
|
|
57062
|
+
this.sendEnvelope(client, exports_ws_borsh.KIND_DEVICE_EVENT, payloadBytes);
|
|
57063
|
+
}
|
|
57064
|
+
}
|
|
56678
57065
|
broadcastDeviceEvent(entry, payload) {
|
|
56679
57066
|
const payloadBytes = exports_ws_borsh.encodeDeviceEventPayload(payload);
|
|
56680
57067
|
for (const client of entry.clients) {
|
|
@@ -56767,6 +57154,9 @@ async function createGatewayRuntime(options = {}) {
|
|
|
56767
57154
|
runtimeController.reset();
|
|
56768
57155
|
primeLocalShellPath();
|
|
56769
57156
|
const wsServer = new WebSocketServer;
|
|
57157
|
+
connectionAlertNotifier.setBroadcaster((deviceId, payload) => {
|
|
57158
|
+
wsServer.broadcastDeviceError(deviceId, payload);
|
|
57159
|
+
});
|
|
56770
57160
|
await telegramService.refresh();
|
|
56771
57161
|
await pushSupervisor.start();
|
|
56772
57162
|
try {
|
|
@@ -56809,6 +57199,7 @@ async function createGatewayRuntime(options = {}) {
|
|
|
56809
57199
|
runtimeController.onRestart(listener);
|
|
56810
57200
|
},
|
|
56811
57201
|
async stop() {
|
|
57202
|
+
connectionAlertNotifier.setBroadcaster(null);
|
|
56812
57203
|
wsServer.closeAll();
|
|
56813
57204
|
await pushSupervisor.stopAll();
|
|
56814
57205
|
await tmuxRuntimeRegistry.shutdownAll();
|
|
@@ -57115,6 +57506,12 @@ async function main() {
|
|
|
57115
57506
|
});
|
|
57116
57507
|
console.log(`[tmex] ${t3("runtime.started", { url: `http://${host}:${port}` })}`);
|
|
57117
57508
|
}
|
|
57509
|
+
process.on("unhandledRejection", (reason) => {
|
|
57510
|
+
console.error("[tmex][unhandledRejection]", reason);
|
|
57511
|
+
});
|
|
57512
|
+
process.on("uncaughtException", (error) => {
|
|
57513
|
+
console.error("[tmex][uncaughtException]", error);
|
|
57514
|
+
});
|
|
57118
57515
|
try {
|
|
57119
57516
|
await main();
|
|
57120
57517
|
} catch (error) {
|