stim 1.25.0 → 1.26.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/README.md +1 -1
- package/dist/{acquire-artifact-zA4S7RHE.mjs → acquire-artifact-DVsvSwf7.mjs} +1 -1
- package/dist/{act-BbHS3oV8.mjs → act-C-BqJEpu.mjs} +157 -35
- package/dist/{activity-BpjwrQXU.mjs → activity-Lnb_EoRY.mjs} +4 -5
- package/dist/{agent-device-usage-output-Bnkerfod.mjs → agent-device-usage-output-DbXy5tg8.mjs} +4 -4
- package/dist/{android-saBeB4lr.mjs → android-B5P81yI-.mjs} +1 -1
- package/dist/{android-CNZgRC-4.mjs → android-CSbLRD53.mjs} +5 -5
- package/dist/{android-BGwuK3Mc.mjs → android-CiNQw3kq.mjs} +73 -49
- package/dist/{android-Cjcj8MCw.mjs → android-DTAcRwBC.mjs} +81 -34
- package/dist/{android-cas-BVqh2Zep.mjs → android-cas-BTVhZ0Qs.mjs} +4 -4
- package/dist/{android-project-CTYaETc0.d.mts → android-project-DUGMbq28.d.mts} +55 -1
- package/dist/api.d.mts +8 -5
- package/dist/api.mjs +27 -27
- package/dist/apk-signed-tail-B46U9hXc.mjs +142 -0
- package/dist/{artifact-Co8zixVx.mjs → artifact-BxREhAaV.mjs} +23 -18
- package/dist/{artifact-DN8JNwqS.mjs → artifact-CsAP6AGK.mjs} +35 -20
- package/dist/{attempt-DW-mQqM7.mjs → attempt-ClyJlZPT.mjs} +4 -2
- package/dist/{browser-web-CCgZsHmI.mjs → browser-web-rYPyqhTf.mjs} +11 -12
- package/dist/{budget-HyShrIpL.mjs → budget-k2mZGvvk.mjs} +196 -33
- package/dist/{build-CAiw8Ezm.mjs → build-DPhApsjV.mjs} +26 -23
- package/dist/{build-cache-CKCzxzxA.mjs → build-cache-DrXNCy3T.mjs} +103 -21
- package/dist/{build-plan-zCuHGlA8.mjs → build-plan-DrMMdayj.mjs} +5 -5
- package/dist/{cache-manifest-BPXRONJz.mjs → cache-manifest-BEZ9Bpl1.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{ccache-h1FKhZeg.mjs → ccache-Bx5TbxwG.mjs} +6 -6
- package/dist/{cli-C4w607Pz.mjs → cli-BBKFGOFl.mjs} +19 -19
- package/dist/cli.mjs +1 -1
- package/dist/{client-BQsmyp_-.mjs → client-D9CaRks-.mjs} +36 -61
- package/dist/collector-run.mjs +4 -4
- package/dist/{command-output-BBvS1H6v.mjs → command-output-BF5AR2dL.mjs} +1 -1
- package/dist/{config-BZhXY15H.mjs → config-BktkY_zC.mjs} +321 -15
- package/dist/{launch-DscF1QfM.mjs → dependencies-Dw6wfQdv.mjs} +258 -255
- package/dist/{dependency-state-BwNCYG5Q.mjs → dependency-state-DFB41vfr.mjs} +1 -1
- package/dist/{dev-client-CHoXXZeg.mjs → dev-client-DXR_tPVY.mjs} +2 -2
- package/dist/{device-B9FqTBYW.mjs → device-BK-zwyd_.mjs} +13 -13
- package/dist/{device-F5-4z9ef.mjs → device-BcYeHSFu.mjs} +14 -14
- package/dist/{device-capacity-ZYaRALv5.mjs → device-capacity-DCSvUF0l.mjs} +10 -17
- package/dist/device-host-worker.mjs +11 -13
- package/dist/{device-ios-DkHBrmvH.mjs → device-ios-BLGfXQeK.mjs} +49 -17
- package/dist/{device-lease-run-ogEeJ62K.mjs → device-lease-run-BmvIl7bI.mjs} +4 -4
- package/dist/{device-pool-BIg8MU8l.mjs → device-pool-Dluix4H3.mjs} +3 -3
- package/dist/{doctor-k3P3PizH.mjs → doctor-Bhg1ii70.mjs} +14 -14
- package/dist/{doctor-A_Nr_fDe.mjs → doctor-Cp-BhzTS.mjs} +1 -1
- package/dist/{doctor-Bm8TVpGn.mjs → doctor-OnwP94ne.mjs} +72 -23
- package/dist/{doctor-fingerprint-zlujA1Gs.mjs → doctor-fingerprint-D3a04Mhu.mjs} +8 -2
- package/dist/doctor-fingerprint.mjs +1 -1
- package/dist/{doctor-watchman-BovI1y3-.mjs → doctor-watchman-BStK05nt.mjs} +4 -4
- package/dist/{eas-build-CyDKNTBP.mjs → eas-build-Dsj93iM9.mjs} +2 -2
- package/dist/{eas-fallback-gaZi-dJL.mjs → eas-fallback-FwkeqqGi.mjs} +10 -10
- package/dist/{eas-session-ledger-BMCA1gdO.mjs → eas-session-ledger-B5vygTiD.mjs} +5 -5
- package/dist/{error-diagnostics-B1Mj_-7h.mjs → error-diagnostics-BOOm9Db7.mjs} +8 -8
- package/dist/{gc-eaz403W6.mjs → gc-C_79zvz4.mjs} +2 -2
- package/dist/{gc-B9jcnmIk.mjs → gc-ldgr-hXU.mjs} +74 -43
- package/dist/{guide-DR0K0xW9.mjs → guide-D7K0X6KG.mjs} +272 -52
- package/dist/{hosted-android-Bvb2-ZxD.mjs → hosted-android-Bk54ej6n.mjs} +3 -3
- package/dist/{hosted-client-rsRqysJc.mjs → hosted-client-fHO8yqzc.mjs} +5 -5
- package/dist/{hosted-ios-RtVcJH3u.mjs → hosted-ios-D55QhYaD.mjs} +1 -1
- package/dist/{hosted-logs-CWKGYWNX.mjs → hosted-logs-BfbmDM4M.mjs} +17 -11
- package/dist/hosted-logs-sync-B0pvmOk-.mjs +195 -0
- package/dist/{hosted-macos-d0pv9Tth.mjs → hosted-macos-CPl0OZ9h.mjs} +5 -5
- package/dist/{hosted-native-BxTizGU2.mjs → hosted-native-CpDB3-PO.mjs} +6 -7
- package/dist/{idle-shutdown-BIyfl0Zj.mjs → idle-shutdown-FfxL1KdG.mjs} +22 -9
- package/dist/{ios-Dvt97HgQ.mjs → ios-B7-lToDu.mjs} +8 -8
- package/dist/{ios-BQzvJlzw.mjs → ios-WxpDJ5T1.mjs} +72 -5
- package/dist/{ios-device-CKSvk0bu.mjs → ios-device-CsyryvpT.mjs} +1 -1
- package/dist/{ios-BRVM8b0P.mjs → ios-ftxdQbJS.mjs} +74 -46
- package/dist/{ios-project-DCUDo06I.mjs → ios-project-Ty5pHl9O.mjs} +6 -6
- package/dist/{ios-state-CT9nV5SP.mjs → ios-state-Cw7-WQf2.mjs} +1 -1
- package/dist/{js-swap-Z9HbC4cj.mjs → js-swap-BMhXYVDS.mjs} +4 -4
- package/dist/{launch-D0lPvFFu.mjs → launch-DOp8RjDz.mjs} +31 -26
- package/dist/{logs-BsIRre8x.mjs → logs-dQDki6Tt.mjs} +9 -173
- package/dist/{logs-Crs9y34Z.mjs → logs-s1dRIG1E.mjs} +85 -36
- package/dist/{macos-CcUlJPMu.mjs → macos-B0VEV3Gf.mjs} +1 -1
- package/dist/{macos-D477nfzH.mjs → macos-DBBPNnIA.mjs} +18 -19
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.mjs +28 -11
- package/dist/{metro-1pJgCX4U.mjs → metro-CjC10t6E.mjs} +2 -2
- package/dist/{metro-gateway-Bp9gJQAN.mjs → metro-gateway-B6_PgD0L.mjs} +2 -3
- package/dist/{named-ports-BVbn8SvC.mjs → named-ports-BG0j1X4g.mjs} +2 -2
- package/dist/{native-android-BvMizJn1.mjs → native-android-CL2NGiik.mjs} +17 -17
- package/dist/{native-gradle-artifact-inputs-BJscK4vN.mjs → native-gradle-artifact-inputs-btSUulme.mjs} +3 -3
- package/dist/{native-run-D5OJsGwA.mjs → native-run-BfIDFKLH.mjs} +2 -3
- package/dist/{native-runtime-B9w_Qyz3.mjs → native-runtime-Dq3RBcON.mjs} +6 -7
- package/dist/{native-xcode-inputs-Cv4l-obn.mjs → native-xcode-inputs-BW7H8JGu.mjs} +2 -2
- package/dist/{native-xcode-ios-DGrg90hE.mjs → native-xcode-ios-CBXJAxfC.mjs} +15 -16
- package/dist/offload-worker.d.mts +3 -1
- package/dist/offload-worker.mjs +31 -21
- package/dist/{ownership-jxxz-19m.mjs → ownership-BjYvMNX_.mjs} +2 -2
- package/dist/{phone-reverses-CYb7RRAx.mjs → phone-reverses-lmj6kcPm.mjs} +3 -3
- package/dist/{placement-log-DjOXJRIH.mjs → placement-log-DIFPQAXp.mjs} +1 -1
- package/dist/{plan-placement-BqKgKHCi.mjs → plan-placement-IiiSQhtz.mjs} +3 -3
- package/dist/{ports-DtS1Amb0.mjs → ports-C2_w1Z0Z.mjs} +3 -3
- package/dist/prebuild-gmouvmJZ.mjs +1247 -0
- package/dist/{project-BiC68mIp.mjs → project-BUu6Is4k.mjs} +15 -16
- package/dist/{react-native-android-DRYqoJc4.mjs → react-native-android-Cgg99REN.mjs} +47 -33
- package/dist/{react-native-build-CwZB-agu.mjs → react-native-build-oNwqF5Wb.mjs} +7 -7
- package/dist/{react-native-doctor-BCrytK7h.mjs → react-native-doctor-BhA3KUte.mjs} +49 -8
- package/dist/{react-native-ios-Cr1buNFo.mjs → react-native-ios-B-nf4rjN.mjs} +37 -23
- package/dist/{recordings-BaoQ0t-J.mjs → recordings-BldzVKq0.mjs} +1 -1
- package/dist/{reload-Beftw2X3.mjs → reload-C9OduiNc.mjs} +1 -1
- package/dist/{reload-FbSgMF8x.mjs → reload-CCR7KC_l.mjs} +14 -14
- package/dist/{remote-BuZj6AyU.mjs → remote-C8S7FlPF.mjs} +7 -7
- package/dist/{result-CR2vk2cj.mjs → result-7F5hEwQJ.mjs} +16 -16
- package/dist/{run-BtzE09Dl.mjs → run-BqB0v3pt.mjs} +1 -1
- package/dist/{selection-D5cxEl3t.mjs → selection-BNPBZ6T5.mjs} +155 -42
- package/dist/{server-bare-CH9X0RLn.mjs → server-bare-A75rDIdD.mjs} +1 -1
- package/dist/{server-command-CaBbDNU2.mjs → server-command-CssKFOD7.mjs} +4 -4
- package/dist/{server-expo-Ck8EIidM.mjs → server-expo-eQfOdydb.mjs} +2 -2
- package/dist/server-expo-wvcj5tiZ.mjs +2 -0
- package/dist/{settings-B5tCoq9N.mjs → settings-CosW5X-S.mjs} +1 -1
- package/dist/{settings-DzH9mGQZ.mjs → settings-OaGQF-ri.mjs} +1 -1
- package/dist/{settings-write-n0zwfohx.mjs → settings-write-QjYfSrjF.mjs} +5 -5
- package/dist/settings.schema.json +33 -6
- package/dist/{simslim-BjxszUAb.mjs → simslim-Bax1qcxu.mjs} +3 -2
- package/dist/{slot-launch-QeuJMlnJ.mjs → slot-launch-CYCEhfBY.mjs} +6 -6
- package/dist/{stage-D8xlRU2D.mjs → stage-jKQ7CP_B.mjs} +1 -1
- package/dist/{start-C1JLjkNw.mjs → start-C_wefNKB.mjs} +1 -1
- package/dist/{start-YMpNwLhh.mjs → start-_BdJ32cZ.mjs} +9 -10
- package/dist/{state-DMRNP7-4.mjs → state-C1i2nMOP.mjs} +2 -3
- package/dist/{state-DPaM3u5K.mjs → state-C4OdpOTF.mjs} +1 -2
- package/dist/{stats-CP7STzZw.mjs → stats-tEpEEH-u.mjs} +4 -4
- package/dist/{status-cNw6i0_t.mjs → status-B9M7x4CZ.mjs} +1 -1
- package/dist/{status-Fect2w9R.mjs → status-CRXz-Gtj.mjs} +1 -1
- package/dist/{status-CRLiJ-O_.mjs → status-DQrqodLj.mjs} +22 -23
- package/dist/{status-watch-Bc2J4TZn.mjs → status-watch-B5wDIH_o.mjs} +4 -4
- package/dist/status-worker.mjs +1 -1
- package/dist/{stim-desktop-DS0mzR8U.mjs → stim-desktop-6Dqp-gjX.mjs} +1 -1
- package/dist/{stop-Br7LKxBE.mjs → stop-CmdNhGlC.mjs} +2 -2
- package/dist/{stop-Db9Kh-UA.mjs → stop-DRqQVqDJ.mjs} +21 -19
- package/dist/{stop-COP6PQqw.mjs → stop-ySNQHXMG.mjs} +4 -5
- package/dist/supervisor-run.d.mts +1 -0
- package/dist/supervisor-run.mjs +14 -15
- package/dist/{support-Cxj9CmII.mjs → support-BGSCBoib.mjs} +4 -4
- package/dist/{support-qKcbgmI5.mjs → support-CVOCp0Ft.mjs} +9 -9
- package/dist/{swiftpm-artifact-inputs-iY9fsmOc.mjs → swiftpm-artifact-inputs-CfUzt_0Z.mjs} +1 -1
- package/dist/{swiftpm-macos-Bp_sn9P4.mjs → swiftpm-macos-x6HPpl_2.mjs} +9 -9
- package/dist/toolchain-DoavrvIA.mjs +2 -0
- package/dist/toolchain-J4J8MrAr.mjs +2107 -0
- package/dist/trigger-DzJu208Q.mjs +2 -0
- package/dist/{trigger-CBMe2YLH.mjs → trigger-KVaWWNyn.mjs} +1 -1
- package/dist/{warm-progress-BV3ZPmcY.mjs → warm-progress-DvhTX_eu.mjs} +3 -4
- package/dist/web-BFLueSh-.mjs +2 -0
- package/dist/{web-B4Yjyt1G.mjs → web-Bihbf7-u.mjs} +8 -9
- package/dist/web-run.mjs +4 -4
- package/dist/{workspace-state-CPSp3mav.mjs → workspace-state-pUg3_RRA.mjs} +45 -7
- package/dist/{workspaces-C9w4aeXF.mjs → workspaces-g2lhdBXF.mjs} +17 -17
- package/dist/{worktree-CclFIe1Z.mjs → worktree-BKuklFh_.mjs} +34 -31
- package/dist/{worktree-Ct9Gk3KL.mjs → worktree-Bm4cb-nU.mjs} +1 -1
- package/package.json +8 -8
- package/dist/created-devices-D2Aca-xu.mjs +0 -52
- package/dist/paths-DQtTgsBf.mjs +0 -42
- package/dist/prebuild-JUaqOLpc.mjs +0 -501
- package/dist/server-expo-C16Zf2k7.mjs +0 -2
- package/dist/toolchain-DB1aeqxO.mjs +0 -2
- package/dist/toolchain-VzLJzZ7O.mjs +0 -908
- package/dist/trigger-BLsHgAGR.mjs +0 -2
- package/dist/web-DS4UDWvf.mjs +0 -2
- package/dist/xcode-CiIIDnuD.mjs +0 -910
|
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { ambientEnv, configDir, ensurePrivateDir } from "@stim-cli/core";
|
|
5
5
|
import { allMetroPorts, cannotHost, clientIdentity, configCorruptRepair, endpoint, findEnclosingWorktreeRoot, findPeer as findPeer$1, getConcurrencyLimits, getConfigPath, getConfigPath as getConfigPath$1, getProject, getProjectSettings, getRepoSettings, isJsonObject, isPathPrefix, loadConfig, loadConfig as loadConfig$1, machineEndpoint, parseMachine, parseMachine as parseMachine$1, validateAutomaticPool, withConfigLock, withConfigLock as withConfigLock$1, withConfigLockAsync, withConfigLockAsync as withConfigLockAsync$1 } from "@stim-cli/core/state";
|
|
6
6
|
import { dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, normalize, parse, sep as sep$1 } from "path";
|
|
7
|
+
import { request } from "node:https";
|
|
7
8
|
import { WebSocket } from "ws";
|
|
8
9
|
import { HandshakeRefusal, MAX_SERVER_MESSAGE_BYTES, TRANSPORT_OVERHEAD_BYTES, decodeSecret, initiateNoise, newSecret } from "@stim-cli/core/noise-session";
|
|
9
10
|
import { existsSync, lstatSync, readdirSync, readlinkSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -23,19 +24,246 @@ function readHostPermissions(value) {
|
|
|
23
24
|
} : void 0;
|
|
24
25
|
}
|
|
25
26
|
//#endregion
|
|
27
|
+
//#region src/offload/diagnosis.ts
|
|
28
|
+
const SILENT_ERRORS = /* @__PURE__ */ new Set([
|
|
29
|
+
"ETIMEDOUT",
|
|
30
|
+
"EHOSTUNREACH",
|
|
31
|
+
"ENETUNREACH",
|
|
32
|
+
"EHOSTDOWN"
|
|
33
|
+
]);
|
|
34
|
+
function classifyTransport(error, status) {
|
|
35
|
+
if (status !== void 0) return {
|
|
36
|
+
kind: "http",
|
|
37
|
+
status
|
|
38
|
+
};
|
|
39
|
+
const code = error.code ?? "";
|
|
40
|
+
if (code === "ECONNREFUSED") return { kind: "refused" };
|
|
41
|
+
if (SILENT_ERRORS.has(code) || /handshake has timed out/i.test(error.message)) return { kind: "silent" };
|
|
42
|
+
if (/^(ERR_TLS_|CERT_|UNABLE_TO_|DEPTH_ZERO|SELF_SIGNED|ERR_SSL_)/.test(code) || /certificate|SSL|TLS/i.test(error.message)) return {
|
|
43
|
+
kind: "tls",
|
|
44
|
+
message: error.message
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
kind: "other",
|
|
48
|
+
message: error.message
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** The `startup` a 503 `/health` body from a peer carries, when it is the shape stim-server sends. */
|
|
52
|
+
function parsePeerStartup(body) {
|
|
53
|
+
const startup = isJsonObject(body) && isJsonObject(body.startup) ? body.startup : null;
|
|
54
|
+
if (!startup || startup.state !== "pending" && startup.state !== "degraded") return null;
|
|
55
|
+
const cause = startup.cause === "home-unreadable" || startup.cause === "start-failed" ? startup.cause : void 0;
|
|
56
|
+
return {
|
|
57
|
+
state: startup.state,
|
|
58
|
+
...cause ? { cause } : {}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function readTailscale(status) {
|
|
62
|
+
if (!isJsonObject(status)) return {
|
|
63
|
+
running: false,
|
|
64
|
+
code: "tailscale-unavailable"
|
|
65
|
+
};
|
|
66
|
+
switch (status.BackendState) {
|
|
67
|
+
case "Running": return { running: true };
|
|
68
|
+
case "NeedsLogin":
|
|
69
|
+
case "NeedsMachineAuth": return {
|
|
70
|
+
running: false,
|
|
71
|
+
code: "tailscale-logged-out"
|
|
72
|
+
};
|
|
73
|
+
default: return {
|
|
74
|
+
running: false,
|
|
75
|
+
code: "tailscale-stopped"
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function tailscaleDiagnosis(code) {
|
|
80
|
+
switch (code) {
|
|
81
|
+
case "tailscale-logged-out": return {
|
|
82
|
+
layer: "tailscale",
|
|
83
|
+
code,
|
|
84
|
+
title: "Tailscale signed out",
|
|
85
|
+
message: "Tailscale on this Mac is signed out, so it cannot reach any remote machine.",
|
|
86
|
+
remedy: "Sign in to Tailscale on this Mac (its menu bar app, or `tailscale up`), then run `stim doctor` again."
|
|
87
|
+
};
|
|
88
|
+
case "tailscale-stopped": return {
|
|
89
|
+
layer: "tailscale",
|
|
90
|
+
code,
|
|
91
|
+
title: "Tailscale is off",
|
|
92
|
+
message: "Tailscale is turned off on this Mac, so it cannot reach any remote machine.",
|
|
93
|
+
remedy: "Turn on Tailscale on this Mac (its menu bar app, or `tailscale up`), then run `stim doctor` again."
|
|
94
|
+
};
|
|
95
|
+
case "tailscale-unavailable": return {
|
|
96
|
+
layer: "tailscale",
|
|
97
|
+
code,
|
|
98
|
+
title: "Tailscale is off",
|
|
99
|
+
message: "Tailscale is not running on this Mac or is not installed, so it cannot reach any remote machine.",
|
|
100
|
+
remedy: "Install Tailscale or open it on this Mac and sign in, then run `stim doctor` again."
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function notOnTailnetDiagnosis(entry, ambiguous) {
|
|
105
|
+
return {
|
|
106
|
+
layer: "peer",
|
|
107
|
+
code: ambiguous ? "peer-ambiguous" : "peer-not-found",
|
|
108
|
+
title: ambiguous ? "Name is ambiguous" : "Not on the tailnet",
|
|
109
|
+
message: ambiguous ? `Several peers on this tailnet match ${entry}.` : `No peer on this tailnet is named ${entry}.`,
|
|
110
|
+
remedy: ambiguous ? `Use the full MagicDNS name in remote.machines (see \`tailscale status\`).` : `Check the name in \`tailscale status\`, or sign in to Tailscale on ${entry} with the same account.`
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function ago(iso, now) {
|
|
114
|
+
const then = iso ? Date.parse(iso) : NaN;
|
|
115
|
+
if (!Number.isFinite(then) || then <= 0) return null;
|
|
116
|
+
const minutes = Math.max(0, Math.round((now - then) / 6e4));
|
|
117
|
+
if (minutes < 1) return "less than a minute ago";
|
|
118
|
+
if (minutes < 60) return `${minutes} min ago`;
|
|
119
|
+
const hours = Math.round(minutes / 60);
|
|
120
|
+
if (hours < 48) return `${hours} h ago`;
|
|
121
|
+
return `${Math.round(hours / 24)} days ago`;
|
|
122
|
+
}
|
|
123
|
+
const sentence = (text) => text.replace(/\s+$/, "").replace(/([^.!?])$/, "$1.");
|
|
124
|
+
/**
|
|
125
|
+
* Tailscale's `Online` lags a reachable peer, so `peer-offline` needs a silent connection; any other outcome,
|
|
126
|
+
* including one past the transport (no `transport`), shows the peer answered.
|
|
127
|
+
*/
|
|
128
|
+
function diagnoseUnreachable(input) {
|
|
129
|
+
const { entry, peer, port, transport, now = Date.now() } = input;
|
|
130
|
+
const route = `https port ${port}`;
|
|
131
|
+
if (transport?.kind === "silent" && input.pinged === true) return {
|
|
132
|
+
layer: "server",
|
|
133
|
+
code: "server-no-answer",
|
|
134
|
+
title: "Server not answering",
|
|
135
|
+
message: `${entry} answers on Tailscale, but its serve route and stim-server sent no reply in time. stim-server may be starting, stalled or restarting.`,
|
|
136
|
+
remedy: `On ${entry}, run \`stim-server service status\`; it reports a restart loop or a Stim home it is still reading, and the log file to read.`
|
|
137
|
+
};
|
|
138
|
+
if (peer.online === false && transport?.kind === "silent") {
|
|
139
|
+
const seen = ago(peer.lastSeen, now);
|
|
140
|
+
return {
|
|
141
|
+
layer: "peer",
|
|
142
|
+
code: "peer-offline",
|
|
143
|
+
title: "Mac is offline",
|
|
144
|
+
message: `${entry} is offline on Tailscale${seen ? ` (last seen ${seen})` : ""}.`,
|
|
145
|
+
remedy: `Wake ${entry} or check that it is powered on, connected to a network and signed in to Tailscale, then run \`stim doctor\` again.`
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (transport?.kind === "refused") return {
|
|
149
|
+
layer: "route",
|
|
150
|
+
code: "route-missing",
|
|
151
|
+
title: "No serve route",
|
|
152
|
+
message: `${entry} answers on the tailnet, but nothing accepts connections on ${route}, so it has no tailscale serve route for stim-server.`,
|
|
153
|
+
remedy: `On ${entry}, run \`stim-server service status\`; \`stim-server service install --serve\` creates the route.`
|
|
154
|
+
};
|
|
155
|
+
if (transport?.kind === "silent") return {
|
|
156
|
+
layer: "peer",
|
|
157
|
+
code: "peer-no-answer",
|
|
158
|
+
title: "Mac not answering",
|
|
159
|
+
message: `${entry} did not answer the connection, and Tailscale does not list it as offline.`,
|
|
160
|
+
remedy: `Check that ${entry} is awake and its network is up; \`tailscale ping ${entry}\` on this Mac tests the path.`
|
|
161
|
+
};
|
|
162
|
+
if (transport?.kind === "tls") return {
|
|
163
|
+
layer: "route",
|
|
164
|
+
code: "route-tls",
|
|
165
|
+
title: "Route certificate",
|
|
166
|
+
message: `The tailscale serve route on ${entry} (${route}) did not present a certificate this Mac accepts: ${sentence(transport.message)}`,
|
|
167
|
+
remedy: `On ${entry}, run \`tailscale serve status\`, then \`stim-server service install --serve\` to recreate the route.`
|
|
168
|
+
};
|
|
169
|
+
if (transport?.kind === "http") {
|
|
170
|
+
const { status } = transport;
|
|
171
|
+
if (status === 502) return {
|
|
172
|
+
layer: "server",
|
|
173
|
+
code: "server-not-running",
|
|
174
|
+
title: "Server not running",
|
|
175
|
+
message: `Tailscale and the serve route on ${entry} work, but nothing listens behind the route: stim-server is not running there (HTTP 502).`,
|
|
176
|
+
remedy: `On ${entry}, run \`stim-server service status\`; \`stim-server service install --serve\` starts it and keeps it running.`
|
|
177
|
+
};
|
|
178
|
+
if (status === 503) {
|
|
179
|
+
const startup = input.startup;
|
|
180
|
+
const why = startup?.cause === "home-unreadable" ? " It cannot read its Stim home, usually because the disk holding ~/.stim is disconnected or not responding." : startup?.cause === "start-failed" ? " It failed while starting." : startup?.state === "pending" ? " It is still starting." : "";
|
|
181
|
+
const check = startup?.cause === "home-unreadable" ? `Check the disk that holds the Stim home on ${entry} (reconnect or wake an external drive); stim-server serves again by itself once it answers. \`stim-server service status\` there shows the exact reason.` : startup?.state === "pending" ? `Wait a few seconds and run \`stim doctor\` again.` : `On ${entry}, run \`stim-server service status\` for the reason; the log file it prints has the rest.`;
|
|
182
|
+
return {
|
|
183
|
+
layer: "server",
|
|
184
|
+
code: "server-not-ready",
|
|
185
|
+
title: "Server not ready",
|
|
186
|
+
message: `stim-server on ${entry} is running but is not serving clients (HTTP 503).${why}`,
|
|
187
|
+
remedy: check
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
layer: "route",
|
|
192
|
+
code: "route-http-error",
|
|
193
|
+
title: `Route answers ${status}`,
|
|
194
|
+
message: `The tailscale serve route on ${entry} (${route}) answered HTTP ${status}, which stim-server does not send for a connection.`,
|
|
195
|
+
remedy: `On ${entry}, run \`tailscale serve status\` and check what the route on port ${port} points to.`
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
layer: "server",
|
|
200
|
+
code: "connect-failed",
|
|
201
|
+
title: "Not answering",
|
|
202
|
+
message: `Could not connect to stim-server on ${entry}: ${sentence(input.reason)}`,
|
|
203
|
+
remedy: `On ${entry}, run \`stim-server service status\`. To keep it running there, run \`stim-server service install --serve\`.`
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function diagnosisFinding(code, entry, diagnosis) {
|
|
207
|
+
return {
|
|
208
|
+
code,
|
|
209
|
+
level: "note",
|
|
210
|
+
title: `Remote machine ${entry}: ${diagnosis.title}`,
|
|
211
|
+
detail: diagnosis.message,
|
|
212
|
+
fix: diagnosis.remedy
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
//#endregion
|
|
26
216
|
//#region src/offload/tailnet.ts
|
|
27
217
|
const MAC_APP_TAILSCALE = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
|
|
28
218
|
const HELLO_TIMEOUT_MS = 1e4;
|
|
219
|
+
const HEALTH_TIMEOUT_MS = 3e3;
|
|
29
220
|
/** How long a setup ticket `stim doctor --fix` generates stays valid; `stim-server setup` takes at most 2 hours. */
|
|
30
221
|
const GENERATED_TICKET_MS = 714e4;
|
|
31
|
-
function
|
|
222
|
+
function parseJson(output) {
|
|
32
223
|
try {
|
|
33
|
-
|
|
34
|
-
return isJsonObject(status) && status.BackendState === "Running" ? status : null;
|
|
224
|
+
return JSON.parse(output);
|
|
35
225
|
} catch {
|
|
36
226
|
return null;
|
|
37
227
|
}
|
|
38
228
|
}
|
|
229
|
+
function parseTailscaleStatus(output) {
|
|
230
|
+
const status = parseJson(output);
|
|
231
|
+
return isJsonObject(status) && status.BackendState === "Running" ? status : null;
|
|
232
|
+
}
|
|
233
|
+
function rawTailscaleStatus() {
|
|
234
|
+
for (const binary of ["tailscale", MAC_APP_TAILSCALE]) {
|
|
235
|
+
const output = getExecutor().runFileQuiet(binary, ["status", "--json"], { timeoutMs: 5e3 });
|
|
236
|
+
if (output !== null) return parseJson(output);
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
/** Reads the `startup` of a peer's `/health`, which stim-server answers with 503 while it is not serving clients. */
|
|
241
|
+
function peerStartup({ url, servername, host }) {
|
|
242
|
+
return new Promise((resolve) => {
|
|
243
|
+
const base = new URL(url);
|
|
244
|
+
const request$1 = request({
|
|
245
|
+
host: base.hostname.replace(/^\[|\]$/g, ""),
|
|
246
|
+
port: base.port || 443,
|
|
247
|
+
path: "/health",
|
|
248
|
+
servername,
|
|
249
|
+
headers: { Host: host },
|
|
250
|
+
timeout: HEALTH_TIMEOUT_MS
|
|
251
|
+
}, (response) => {
|
|
252
|
+
const chunks = [];
|
|
253
|
+
let size = 0;
|
|
254
|
+
response.on("data", (chunk) => {
|
|
255
|
+
size += chunk.length;
|
|
256
|
+
if (size > 4096) request$1.destroy();
|
|
257
|
+
else chunks.push(chunk);
|
|
258
|
+
});
|
|
259
|
+
response.on("end", () => resolve(parsePeerStartup(parseJson(Buffer.concat(chunks).toString("utf8")))));
|
|
260
|
+
response.on("error", () => resolve(null));
|
|
261
|
+
});
|
|
262
|
+
request$1.on("timeout", () => request$1.destroy());
|
|
263
|
+
request$1.on("error", () => resolve(null));
|
|
264
|
+
request$1.end();
|
|
265
|
+
});
|
|
266
|
+
}
|
|
39
267
|
function tailscaleStatus() {
|
|
40
268
|
for (const binary of ["tailscale", MAC_APP_TAILSCALE]) {
|
|
41
269
|
const output = getExecutor().runFileQuiet(binary, ["status", "--json"], { timeoutMs: 5e3 });
|
|
@@ -43,7 +271,7 @@ function tailscaleStatus() {
|
|
|
43
271
|
}
|
|
44
272
|
return null;
|
|
45
273
|
}
|
|
46
|
-
async function
|
|
274
|
+
async function tailscaleOutputAsync(signal) {
|
|
47
275
|
for (const binary of ["tailscale", MAC_APP_TAILSCALE]) {
|
|
48
276
|
signal?.throwIfAborted();
|
|
49
277
|
let output;
|
|
@@ -57,10 +285,42 @@ async function tailscaleStatusAsync(signal) {
|
|
|
57
285
|
continue;
|
|
58
286
|
}
|
|
59
287
|
signal?.throwIfAborted();
|
|
60
|
-
return
|
|
288
|
+
return output;
|
|
61
289
|
}
|
|
62
290
|
return null;
|
|
63
291
|
}
|
|
292
|
+
async function tailscaleStatusAsync(signal) {
|
|
293
|
+
const output = await tailscaleOutputAsync(signal);
|
|
294
|
+
return output === null ? null : parseTailscaleStatus(output);
|
|
295
|
+
}
|
|
296
|
+
async function pingPeer(address, signal) {
|
|
297
|
+
for (const binary of ["tailscale", MAC_APP_TAILSCALE]) {
|
|
298
|
+
signal?.throwIfAborted();
|
|
299
|
+
try {
|
|
300
|
+
await getExecutor().runFileAsync(binary, [
|
|
301
|
+
"ping",
|
|
302
|
+
"--c",
|
|
303
|
+
"1",
|
|
304
|
+
"--until-direct=false",
|
|
305
|
+
"--timeout",
|
|
306
|
+
"4s",
|
|
307
|
+
address
|
|
308
|
+
], {
|
|
309
|
+
timeoutMs: 6e3,
|
|
310
|
+
signal
|
|
311
|
+
});
|
|
312
|
+
return true;
|
|
313
|
+
} catch (error) {
|
|
314
|
+
signal?.throwIfAborted();
|
|
315
|
+
if (!/ENOENT|not found/i.test(String(error))) return false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
async function rawTailscaleStatusAsync(signal) {
|
|
321
|
+
const output = await tailscaleOutputAsync(signal);
|
|
322
|
+
return output === null ? null : parseJson(output);
|
|
323
|
+
}
|
|
64
324
|
const TURNED_AWAY = /* @__PURE__ */ new Set([
|
|
65
325
|
"unauthorized",
|
|
66
326
|
"forbidden",
|
|
@@ -106,6 +366,7 @@ function openAuthenticated(target, auth, timeoutMs, signal, operation) {
|
|
|
106
366
|
socket.removeAllListeners("open");
|
|
107
367
|
socket.removeAllListeners("message");
|
|
108
368
|
socket.removeAllListeners("error");
|
|
369
|
+
socket.removeAllListeners("unexpected-response");
|
|
109
370
|
socket.on("error", () => {});
|
|
110
371
|
socket.terminate();
|
|
111
372
|
}
|
|
@@ -116,18 +377,33 @@ function openAuthenticated(target, auth, timeoutMs, signal, operation) {
|
|
|
116
377
|
refused: false,
|
|
117
378
|
code: "STIM_CANCELLED"
|
|
118
379
|
});
|
|
380
|
+
let opened = false;
|
|
119
381
|
const timer = setTimeout(() => finish({
|
|
120
382
|
failure: "no reply in time",
|
|
121
|
-
refused: false
|
|
383
|
+
refused: false,
|
|
384
|
+
transport: opened ? {
|
|
385
|
+
kind: "other",
|
|
386
|
+
message: "the handshake got no reply"
|
|
387
|
+
} : { kind: "silent" }
|
|
122
388
|
}), timeoutMs);
|
|
123
389
|
signal?.addEventListener("abort", cancel, { once: true });
|
|
124
390
|
if (signal?.aborted) return cancel();
|
|
125
|
-
const failed = (error) => finish({
|
|
126
|
-
failure:
|
|
127
|
-
refused: false
|
|
391
|
+
const failed = (error, status) => finish({
|
|
392
|
+
failure: status === 502 ? "no stim-server answers behind its tailscale serve route (HTTP 502)" : status === 503 ? "stim-server answers behind its tailscale serve route but is not serving clients (HTTP 503)" : status !== void 0 ? `its tailscale serve route answered HTTP ${status}` : error.message,
|
|
393
|
+
refused: false,
|
|
394
|
+
transport: opened && status === void 0 ? {
|
|
395
|
+
kind: "other",
|
|
396
|
+
message: error.message
|
|
397
|
+
} : classifyTransport(error, status)
|
|
398
|
+
});
|
|
399
|
+
socket.once("unexpected-response", (request, response) => {
|
|
400
|
+
response.resume();
|
|
401
|
+
request.destroy();
|
|
402
|
+
failed(/* @__PURE__ */ new Error(`Unexpected server response: ${response.statusCode}`), response.statusCode);
|
|
128
403
|
});
|
|
129
404
|
socket.once("error", failed);
|
|
130
405
|
socket.once("open", () => {
|
|
406
|
+
opened = true;
|
|
131
407
|
const secret = "ticket" in auth ? decodeSecret(auth.ticket) : null;
|
|
132
408
|
let keyPair;
|
|
133
409
|
try {
|
|
@@ -215,7 +491,10 @@ async function hello(target, auth, operation) {
|
|
|
215
491
|
if (!("send" in socket)) return socket.code ? { error: {
|
|
216
492
|
code: socket.code,
|
|
217
493
|
message: socket.failure
|
|
218
|
-
} } : {
|
|
494
|
+
} } : {
|
|
495
|
+
failed: socket.failure,
|
|
496
|
+
...socket.transport ? { transport: socket.transport } : {}
|
|
497
|
+
};
|
|
219
498
|
try {
|
|
220
499
|
if ("onServerKey" in auth) await auth.onServerKey?.(socket.remoteKey.toString("base64url"));
|
|
221
500
|
} catch (error) {
|
|
@@ -241,14 +520,39 @@ async function hello(target, auth, operation) {
|
|
|
241
520
|
}
|
|
242
521
|
const realIo = {
|
|
243
522
|
status: tailscaleStatus,
|
|
244
|
-
hello
|
|
523
|
+
hello,
|
|
524
|
+
rawStatus: rawTailscaleStatus,
|
|
525
|
+
startup: peerStartup,
|
|
526
|
+
ping: (address) => pingPeer(address)
|
|
245
527
|
};
|
|
246
528
|
function doctorIo(operation) {
|
|
247
529
|
return {
|
|
248
530
|
status: () => tailscaleStatusAsync(operation?.signal),
|
|
249
|
-
hello: (target, auth) => hello(target, auth, operation)
|
|
531
|
+
hello: (target, auth) => hello(target, auth, operation),
|
|
532
|
+
rawStatus: () => rawTailscaleStatusAsync(operation?.signal),
|
|
533
|
+
startup: peerStartup,
|
|
534
|
+
ping: (address) => pingPeer(address, operation?.signal)
|
|
250
535
|
};
|
|
251
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* Names the layer an unreachable `entry` stopped at. A 503 reads the peer's `/health` for why the server is not
|
|
539
|
+
* serving; an older stim-server answers without it, and the diagnosis then says only that it is not ready.
|
|
540
|
+
*/
|
|
541
|
+
async function diagnoseOutcome(io, input) {
|
|
542
|
+
const { target, ...rest } = input;
|
|
543
|
+
const startup = rest.transport?.kind === "http" && rest.transport.status === 503 ? await io.startup?.(target) : null;
|
|
544
|
+
const pinged = rest.transport?.kind === "silent" ? await io.ping?.(rest.peer.address) : void 0;
|
|
545
|
+
return diagnoseUnreachable({
|
|
546
|
+
...rest,
|
|
547
|
+
startup,
|
|
548
|
+
pinged
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
/** Why `status` is null, as a diagnosis of the Tailscale layer; falls back to the daemon being unavailable. */
|
|
552
|
+
async function diagnoseTailscale(io) {
|
|
553
|
+
const reading = readTailscale(io.rawStatus ? await io.rawStatus() : null);
|
|
554
|
+
return tailscaleDiagnosis(reading.running ? "tailscale-unavailable" : reading.code);
|
|
555
|
+
}
|
|
252
556
|
/** Where to reach a paired machine now, or why Stim cannot; the handshake then checks its pinned key. */
|
|
253
557
|
async function resolveEndpoint(credential, status = () => tailscaleStatusAsync(signal), signal) {
|
|
254
558
|
return machineEndpoint(credential.machine, parseMachine(credential.machine) ? await status() : null);
|
|
@@ -357,7 +661,8 @@ async function reachMachine(input) {
|
|
|
357
661
|
return {
|
|
358
662
|
state: "unreachable",
|
|
359
663
|
credential: pending,
|
|
360
|
-
reason: "failed" in reply ? reply.failed : "error" in reply ? reply.error.message : "it did not grant this access"
|
|
664
|
+
reason: "failed" in reply ? reply.failed : "error" in reply ? reply.error.message : "it did not grant this access",
|
|
665
|
+
..."failed" in reply && reply.transport ? { transport: reply.transport } : {}
|
|
361
666
|
};
|
|
362
667
|
};
|
|
363
668
|
if (!credential) return input.fix ? ask() : { state: "not-asked" };
|
|
@@ -389,7 +694,8 @@ async function reachMachine(input) {
|
|
|
389
694
|
return {
|
|
390
695
|
state: "unreachable",
|
|
391
696
|
credential,
|
|
392
|
-
reason: "failed" in reply ? reply.failed : reply.error.message
|
|
697
|
+
reason: "failed" in reply ? reply.failed : reply.error.message,
|
|
698
|
+
..."failed" in reply && reply.transport ? { transport: reply.transport } : {}
|
|
393
699
|
};
|
|
394
700
|
}
|
|
395
701
|
if (input.fix && given && given !== credential.ticket) return ask();
|
|
@@ -952,4 +1258,4 @@ function allConsolePortsAndSerials({ isMounted = isOnMountedVolume } = {}) {
|
|
|
952
1258
|
return result;
|
|
953
1259
|
}
|
|
954
1260
|
//#endregion
|
|
955
|
-
export {
|
|
1261
|
+
export { findPeer$1 as $, writeConfigSettingAsync as A, deviceSlotFileKey as B, saveConfig as C, withConfigLock$1 as D, upsertProject as E, isOnMountedVolume as F, projectDeviceSlots as G, deviceSlotPlatforms as H, listMountedVolumes as I, SERVER_OUTDATED as J, removeSlotDevice as K, measuredDirectorySizeAsync as L, directorySize as M, directorySizeAsync as N, withConfigLockAsync$1 as O, formatBytes as P, endpoint as Q, volumeRootFor as R, removeProject as S, setSupervisor as T, parseDeviceSlotKey as U, deviceSlotKey as V, parseDeviceSlotOption as W, diagnoseTailscale as X, diagnoseOutcome as Y, doctorIo as Z, loadConfig$1 as _, clearDevice as a, resolveEndpoint as at, releaseAndroidConsolePort as b, ensureConfig as c, tailscaleStatusAsync as ct, getConfigDir as d, readHostPermissions as dt, openAuthenticated as et, getConfigPath$1 as f, isPathPrefix as g, getRepoSettings as h, clearAndroidBootPending as i, realIo as it, acquireAvdClaim as j, writeConfigSetting as k, findEnclosingWorktreeRoot as l, diagnosisFinding as lt, getProjectSettings as m, allMetroPorts as n, parseMachine$1 as nt, clearSupervisor as o, sayHello as ot, getProject as p, validateDeviceSlot as q, claimMetroPort as r, reachMachine as rt, configCorruptRepair as s, setupRemedy as st, allConsolePortsAndSerials as t, outdatedServer as tt, getConcurrencyLimits as u, notOnTailnetDiagnosis as ut, recordIosSchemeApprovals as v, setDevice as w, releaseMetroPort as x, refuseRelativeStimPaths as y, assignSlotDevice as z };
|