@kdonev/termscape 0.1.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/LICENSE +21 -0
- package/README.md +288 -0
- package/dist/agents/env.d.ts +36 -0
- package/dist/agents/env.d.ts.map +1 -0
- package/dist/agents/env.js +61 -0
- package/dist/agents/env.js.map +1 -0
- package/dist/agents/profiles.d.ts +45 -0
- package/dist/agents/profiles.d.ts.map +1 -0
- package/dist/agents/profiles.js +123 -0
- package/dist/agents/profiles.js.map +1 -0
- package/dist/agents/resolve.d.ts +16 -0
- package/dist/agents/resolve.d.ts.map +1 -0
- package/dist/agents/resolve.js +71 -0
- package/dist/agents/resolve.js.map +1 -0
- package/dist/agents/router.d.ts +29 -0
- package/dist/agents/router.d.ts.map +1 -0
- package/dist/agents/router.js +83 -0
- package/dist/agents/router.js.map +1 -0
- package/dist/agents/tokens.d.ts +21 -0
- package/dist/agents/tokens.d.ts.map +1 -0
- package/dist/agents/tokens.js +39 -0
- package/dist/agents/tokens.js.map +1 -0
- package/dist/agents/wiring.d.ts +30 -0
- package/dist/agents/wiring.d.ts.map +1 -0
- package/dist/agents/wiring.js +110 -0
- package/dist/agents/wiring.js.map +1 -0
- package/dist/browser.d.ts +13 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +34 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +200 -0
- package/dist/cli.js.map +1 -0
- package/dist/db/index.d.ts +14 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +24 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrations.d.ts +15 -0
- package/dist/db/migrations.d.ts.map +1 -0
- package/dist/db/migrations.js +176 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/store.d.ts +71 -0
- package/dist/db/store.d.ts.map +1 -0
- package/dist/db/store.js +341 -0
- package/dist/db/store.js.map +1 -0
- package/dist/hub.d.ts +188 -0
- package/dist/hub.d.ts.map +1 -0
- package/dist/hub.js +636 -0
- package/dist/hub.js.map +1 -0
- package/dist/hub.tgz +0 -0
- package/dist/mcp/server.d.ts +31 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +66 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/paths.d.ts +27 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +31 -0
- package/dist/paths.js.map +1 -0
- package/dist/protocol/address.d.ts +15 -0
- package/dist/protocol/address.d.ts.map +1 -0
- package/dist/protocol/address.js +39 -0
- package/dist/protocol/address.js.map +1 -0
- package/dist/protocol/domain.d.ts +151 -0
- package/dist/protocol/domain.d.ts.map +1 -0
- package/dist/protocol/domain.js +104 -0
- package/dist/protocol/domain.js.map +1 -0
- package/dist/protocol/index.d.ts +7 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +7 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/protocol/inject.d.ts +29 -0
- package/dist/protocol/inject.d.ts.map +1 -0
- package/dist/protocol/inject.js +40 -0
- package/dist/protocol/inject.js.map +1 -0
- package/dist/protocol/mcp-tools.d.ts +45 -0
- package/dist/protocol/mcp-tools.d.ts.map +1 -0
- package/dist/protocol/mcp-tools.js +46 -0
- package/dist/protocol/mcp-tools.js.map +1 -0
- package/dist/protocol/peer.d.ts +304 -0
- package/dist/protocol/peer.d.ts.map +1 -0
- package/dist/protocol/peer.js +183 -0
- package/dist/protocol/peer.js.map +1 -0
- package/dist/protocol/ws.d.ts +416 -0
- package/dist/protocol/ws.d.ts.map +1 -0
- package/dist/protocol/ws.js +119 -0
- package/dist/protocol/ws.js.map +1 -0
- package/dist/remote/deployer.d.ts +78 -0
- package/dist/remote/deployer.d.ts.map +1 -0
- package/dist/remote/deployer.js +216 -0
- package/dist/remote/deployer.js.map +1 -0
- package/dist/remote/enroll.d.ts +14 -0
- package/dist/remote/enroll.d.ts.map +1 -0
- package/dist/remote/enroll.js +244 -0
- package/dist/remote/enroll.js.map +1 -0
- package/dist/remote/join-script.d.ts +29 -0
- package/dist/remote/join-script.d.ts.map +1 -0
- package/dist/remote/join-script.js +709 -0
- package/dist/remote/join-script.js.map +1 -0
- package/dist/remote/join.d.ts +19 -0
- package/dist/remote/join.d.ts.map +1 -0
- package/dist/remote/join.js +195 -0
- package/dist/remote/join.js.map +1 -0
- package/dist/remote/lan.d.ts +45 -0
- package/dist/remote/lan.d.ts.map +1 -0
- package/dist/remote/lan.js +105 -0
- package/dist/remote/lan.js.map +1 -0
- package/dist/remote/peer-serve.d.ts +27 -0
- package/dist/remote/peer-serve.d.ts.map +1 -0
- package/dist/remote/peer-serve.js +274 -0
- package/dist/remote/peer-serve.js.map +1 -0
- package/dist/remote/peer.d.ts +62 -0
- package/dist/remote/peer.d.ts.map +1 -0
- package/dist/remote/peer.js +250 -0
- package/dist/remote/peer.js.map +1 -0
- package/dist/remote/registry.d.ts +104 -0
- package/dist/remote/registry.d.ts.map +1 -0
- package/dist/remote/registry.js +359 -0
- package/dist/remote/registry.js.map +1 -0
- package/dist/remote/tarball.d.ts +9 -0
- package/dist/remote/tarball.d.ts.map +1 -0
- package/dist/remote/tarball.js +22 -0
- package/dist/remote/tarball.js.map +1 -0
- package/dist/server.d.ts +54 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +438 -0
- package/dist/server.js.map +1 -0
- package/dist/session/manager.d.ts +104 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +421 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/session/pty.d.ts +114 -0
- package/dist/session/pty.d.ts.map +1 -0
- package/dist/session/pty.js +300 -0
- package/dist/session/pty.js.map +1 -0
- package/package.json +49 -0
- package/web/assets/index-B2JtCtHa.css +1 -0
- package/web/assets/index-BOqZOoep.js +107 -0
- package/web/index.html +13 -0
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The installers a joining machine downloads and runs.
|
|
3
|
+
*
|
|
4
|
+
* They are generated rather than shipped as files so the hub's own origin and
|
|
5
|
+
* a freshly minted single-use token are baked in — the person running this has
|
|
6
|
+
* nothing to configure and nothing to paste twice.
|
|
7
|
+
*
|
|
8
|
+
* Both scripts follow the same order, and it matters: work out what is missing
|
|
9
|
+
* first, and say precisely what is happening *before* changing anything. The
|
|
10
|
+
* SSH deploy path could only report a failure as a wall of build output in a
|
|
11
|
+
* tooltip; here the message lands in the terminal of the person standing at
|
|
12
|
+
* the keyboard.
|
|
13
|
+
*
|
|
14
|
+
* They are also deliberately chatty. Two steps take a minute or more — pulling
|
|
15
|
+
* ~30MB of Node, and an `npm install` that may compile native modules — and a
|
|
16
|
+
* terminal that prints nothing for that long is indistinguishable from one
|
|
17
|
+
* that has hung. Every long step therefore says what it is about to do, ticks
|
|
18
|
+
* while it runs, and reports how long it took.
|
|
19
|
+
*
|
|
20
|
+
* When Node is missing or too old, a private copy is fetched into
|
|
21
|
+
* `~/.termscape/node` rather than installed system-wide. That is deliberate:
|
|
22
|
+
* it needs no administrator rights, no package manager, and no PATH surgery
|
|
23
|
+
* that a fresh shell would have to pick up; and uninstalling is deleting one
|
|
24
|
+
* directory. The download is checksum-verified against nodejs.org's own
|
|
25
|
+
* SHASUMS256.txt, because this is a binary we are about to execute.
|
|
26
|
+
*/
|
|
27
|
+
/** Shell-safe: our tokens are base64url and origins are URLs, but be explicit. */
|
|
28
|
+
function sq(s) {
|
|
29
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
30
|
+
}
|
|
31
|
+
/** The Node line we fetch from. 22 is the minimum the hub supports. */
|
|
32
|
+
const NODE_DIST = 'https://nodejs.org/dist/latest-v22.x';
|
|
33
|
+
export function joinScriptPosix(origin, token) {
|
|
34
|
+
return `#!/bin/sh
|
|
35
|
+
# Joins this machine to the Termscape canvas at ${origin}.
|
|
36
|
+
set -eu
|
|
37
|
+
|
|
38
|
+
HUB_URL=${sq(origin)}
|
|
39
|
+
JOIN_TOKEN=${sq(token)}
|
|
40
|
+
HOME_DIR="\${TERMSCAPE_HOME:-$HOME/.termscape}"
|
|
41
|
+
NODE_DIST=${sq(NODE_DIST)}
|
|
42
|
+
|
|
43
|
+
say() { printf '%s\\n' "$*"; }
|
|
44
|
+
note() { printf ' %s\\n' "$*"; }
|
|
45
|
+
step() { printf '\\n[%s] %s\\n' "$1" "$2"; }
|
|
46
|
+
fail() { printf '\\n%s\\n' "$*" >&2; exit 1; }
|
|
47
|
+
|
|
48
|
+
now() { date +%s; }
|
|
49
|
+
since() { printf '%ss' "$(( $(now) - $1 ))"; }
|
|
50
|
+
human() {
|
|
51
|
+
if [ "$1" -ge 1048576 ]; then printf '%s MB' "$(( $1 / 1048576 ))"
|
|
52
|
+
elif [ "$1" -ge 1024 ]; then printf '%s KB' "$(( $1 / 1024 ))"
|
|
53
|
+
else printf '%s B' "$1"; fi
|
|
54
|
+
}
|
|
55
|
+
size_of() { wc -c < "$1" 2>/dev/null | tr -d ' ' || echo 0; }
|
|
56
|
+
|
|
57
|
+
# $1 url, $2 destination, $3 "quiet" to suppress the progress bar
|
|
58
|
+
fetch() {
|
|
59
|
+
if command -v curl >/dev/null 2>&1; then
|
|
60
|
+
if [ "\${3:-}" = quiet ]; then curl -fsSL "$1" -o "$2"; else curl -f -# -L "$1" -o "$2"; fi
|
|
61
|
+
elif command -v wget >/dev/null 2>&1; then
|
|
62
|
+
if [ "\${3:-}" = quiet ]; then wget -qO "$2" "$1"; else wget -O "$2" "$1"; fi
|
|
63
|
+
else
|
|
64
|
+
fail "Neither curl nor wget is available; cannot download anything."
|
|
65
|
+
fi
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fetch_stdout() {
|
|
69
|
+
if command -v curl >/dev/null 2>&1; then curl -fsSL "$1"; else wget -qO- "$1"; fi
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
sha256_of() {
|
|
73
|
+
if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1
|
|
74
|
+
elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | cut -d' ' -f1
|
|
75
|
+
else echo ''; fi
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Run a long command in the background, ticking so the terminal never looks
|
|
79
|
+
# hung. A finished child stays a zombie until it is reaped, so kill -0 would
|
|
80
|
+
# spin forever; a marker file is the reliable POSIX signal that it is done.
|
|
81
|
+
run_ticking() {
|
|
82
|
+
LABEL="$1"; shift
|
|
83
|
+
RC_FILE="$HOME_DIR/.step.rc"
|
|
84
|
+
rm -f "$RC_FILE"
|
|
85
|
+
( "$@" >>"$HOME_DIR/install.log" 2>&1; echo $? >"$RC_FILE" ) &
|
|
86
|
+
T0=$(now)
|
|
87
|
+
printf ' %s ' "$LABEL"
|
|
88
|
+
while [ ! -f "$RC_FILE" ]; do
|
|
89
|
+
printf '.'
|
|
90
|
+
sleep 2
|
|
91
|
+
done
|
|
92
|
+
RC=$(cat "$RC_FILE"); rm -f "$RC_FILE"
|
|
93
|
+
printf ' %s\\n' "$(since "$T0")"
|
|
94
|
+
return "$RC"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# A hub from an earlier join is still running and still holds its own install
|
|
98
|
+
# open, so it has to go before those files can be replaced. It also stops this
|
|
99
|
+
# machine appearing twice on the canvas.
|
|
100
|
+
stop_running_hub() {
|
|
101
|
+
OLD_PID=""
|
|
102
|
+
[ -f "$HOME_DIR/hub.pid" ] && OLD_PID=$(cat "$HOME_DIR/hub.pid" 2>/dev/null || true)
|
|
103
|
+
# A hub predating the pid file, or one whose file was lost.
|
|
104
|
+
if [ -z "$OLD_PID" ] && command -v pgrep >/dev/null 2>&1; then
|
|
105
|
+
OLD_PID=$(pgrep -f "$HOME_DIR/hub/dist/cli.js" 2>/dev/null | head -1 || true)
|
|
106
|
+
[ -n "$OLD_PID" ] || OLD_PID=$(pgrep -f "TERMSCAPE_HOME=$HOME_DIR" 2>/dev/null | head -1 || true)
|
|
107
|
+
fi
|
|
108
|
+
[ -n "$OLD_PID" ] || return 0
|
|
109
|
+
kill -0 "$OLD_PID" 2>/dev/null || return 0
|
|
110
|
+
|
|
111
|
+
note "stopping the hub already running here (pid $OLD_PID)"
|
|
112
|
+
kill "$OLD_PID" 2>/dev/null || true
|
|
113
|
+
i=0
|
|
114
|
+
while [ $i -lt 40 ]; do
|
|
115
|
+
kill -0 "$OLD_PID" 2>/dev/null || { rm -f "$HOME_DIR/hub.pid"; return 0; }
|
|
116
|
+
i=$((i + 1))
|
|
117
|
+
sleep 0.25
|
|
118
|
+
done
|
|
119
|
+
kill -9 "$OLD_PID" 2>/dev/null || true
|
|
120
|
+
sleep 1
|
|
121
|
+
rm -f "$HOME_DIR/hub.pid"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
mkdir -p "$HOME_DIR"
|
|
125
|
+
say ""
|
|
126
|
+
say "Termscape - joining $HUB_URL"
|
|
127
|
+
|
|
128
|
+
# --- node -------------------------------------------------------------------
|
|
129
|
+
NODE_BIN=node
|
|
130
|
+
NPM_BIN=npm
|
|
131
|
+
|
|
132
|
+
node_major() { "$1" -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0; }
|
|
133
|
+
|
|
134
|
+
use_private_node() {
|
|
135
|
+
NODE_BIN="$HOME_DIR/node/bin/node"
|
|
136
|
+
NPM_BIN="$HOME_DIR/node/bin/npm"
|
|
137
|
+
PATH="$HOME_DIR/node/bin:$PATH"
|
|
138
|
+
export PATH
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
install_node() {
|
|
142
|
+
case "$(uname -s)" in
|
|
143
|
+
Darwin) OS=darwin ;;
|
|
144
|
+
Linux) OS=linux ;;
|
|
145
|
+
*) fail "Unsupported platform $(uname -s); install Node 22 yourself and re-run." ;;
|
|
146
|
+
esac
|
|
147
|
+
case "$(uname -m)" in
|
|
148
|
+
x86_64|amd64) ARCH=x64 ;;
|
|
149
|
+
arm64|aarch64) ARCH=arm64 ;;
|
|
150
|
+
*) fail "Unsupported CPU $(uname -m); install Node 22 yourself and re-run." ;;
|
|
151
|
+
esac
|
|
152
|
+
|
|
153
|
+
note "looking up the latest Node 22 for \${OS}-\${ARCH}"
|
|
154
|
+
SUMS=$(fetch_stdout "$NODE_DIST/SHASUMS256.txt") \\
|
|
155
|
+
|| fail "Could not reach $NODE_DIST - check this machine's internet access."
|
|
156
|
+
|
|
157
|
+
LINE=$(printf '%s\\n' "$SUMS" | grep -- "-\${OS}-\${ARCH}\\.tar\\.gz\$" | head -1)
|
|
158
|
+
[ -n "$LINE" ] || fail "nodejs.org has no \${OS}-\${ARCH} build on the v22 line."
|
|
159
|
+
WANT_SHA=$(printf '%s' "$LINE" | awk '{print $1}')
|
|
160
|
+
TARBALL=$(printf '%s' "$LINE" | awk '{print $2}')
|
|
161
|
+
|
|
162
|
+
note "downloading $TARBALL (about 30 MB)"
|
|
163
|
+
T0=$(now)
|
|
164
|
+
fetch "$NODE_DIST/$TARBALL" "$HOME_DIR/$TARBALL"
|
|
165
|
+
note "downloaded $(human "$(size_of "$HOME_DIR/$TARBALL")") in $(since "$T0")"
|
|
166
|
+
|
|
167
|
+
# We are about to execute this, so verify it rather than trusting the pipe.
|
|
168
|
+
printf ' verifying checksum ... '
|
|
169
|
+
GOT_SHA=$(sha256_of "$HOME_DIR/$TARBALL")
|
|
170
|
+
if [ -z "$GOT_SHA" ]; then
|
|
171
|
+
say "skipped (no sha256 tool on this machine)"
|
|
172
|
+
elif [ "$GOT_SHA" != "$WANT_SHA" ]; then
|
|
173
|
+
say "FAILED"
|
|
174
|
+
rm -f "$HOME_DIR/$TARBALL"
|
|
175
|
+
fail "Checksum mismatch on $TARBALL. Refusing to run it."
|
|
176
|
+
else
|
|
177
|
+
say "ok"
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
note "unpacking into $HOME_DIR/node"
|
|
181
|
+
rm -rf "$HOME_DIR/node"
|
|
182
|
+
mkdir -p "$HOME_DIR/node"
|
|
183
|
+
tar xzf "$HOME_DIR/$TARBALL" -C "$HOME_DIR/node" --strip-components=1
|
|
184
|
+
rm -f "$HOME_DIR/$TARBALL"
|
|
185
|
+
|
|
186
|
+
use_private_node
|
|
187
|
+
note "installed $("$NODE_BIN" --version)"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
step 1/4 "Node 22"
|
|
191
|
+
if [ -x "$HOME_DIR/node/bin/node" ] && [ "$(node_major "$HOME_DIR/node/bin/node")" -ge 22 ]; then
|
|
192
|
+
use_private_node
|
|
193
|
+
note "$("$NODE_BIN" --version) already here (private copy)"
|
|
194
|
+
elif command -v node >/dev/null 2>&1 && [ "$(node_major node)" -ge 22 ]; then
|
|
195
|
+
note "$(node --version) already on this machine"
|
|
196
|
+
elif command -v node >/dev/null 2>&1; then
|
|
197
|
+
note "$(node --version) is older than 22; fetching a private copy"
|
|
198
|
+
install_node
|
|
199
|
+
else
|
|
200
|
+
note "not installed; fetching a private copy"
|
|
201
|
+
install_node
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
# --- the hub ----------------------------------------------------------------
|
|
205
|
+
step 2/4 "hub package"
|
|
206
|
+
note "downloading from $HUB_URL"
|
|
207
|
+
fetch "$HUB_URL/hub.tgz" "$HOME_DIR/hub.tgz" quiet
|
|
208
|
+
note "got $(human "$(size_of "$HOME_DIR/hub.tgz")")"
|
|
209
|
+
|
|
210
|
+
stop_running_hub
|
|
211
|
+
|
|
212
|
+
# The dependency tree is the slowest thing here to rebuild and the tarball
|
|
213
|
+
# never carries one, so it is moved aside rather than deleted along with the
|
|
214
|
+
# rest of the install. Whether it can be kept is step 3's question.
|
|
215
|
+
KEPT_MODULES="$HOME_DIR/node_modules.kept"
|
|
216
|
+
rm -rf "$KEPT_MODULES"
|
|
217
|
+
if [ -d "$HOME_DIR/hub/node_modules" ]; then
|
|
218
|
+
mv "$HOME_DIR/hub/node_modules" "$KEPT_MODULES"
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
rm -rf "$HOME_DIR/hub"
|
|
222
|
+
mkdir -p "$HOME_DIR/hub"
|
|
223
|
+
tar xzf "$HOME_DIR/hub.tgz" -C "$HOME_DIR/hub" --strip-components=1
|
|
224
|
+
if [ -d "$KEPT_MODULES" ]; then
|
|
225
|
+
mv "$KEPT_MODULES" "$HOME_DIR/hub/node_modules"
|
|
226
|
+
fi
|
|
227
|
+
note "unpacked into $HOME_DIR/hub"
|
|
228
|
+
|
|
229
|
+
# --- install ----------------------------------------------------------------
|
|
230
|
+
step 3/4 "dependencies"
|
|
231
|
+
note "full log: $HOME_DIR/install.log"
|
|
232
|
+
cd "$HOME_DIR/hub"
|
|
233
|
+
: >"$HOME_DIR/install.log"
|
|
234
|
+
|
|
235
|
+
# What the tree already here would have to match to be worth keeping: the
|
|
236
|
+
# fingerprint the tarball shipped, plus the two things it cannot know - the
|
|
237
|
+
# Node ABI these modules were built against, and the platform.
|
|
238
|
+
STAMP_FILE="$HOME_DIR/deps.stamp"
|
|
239
|
+
FINGERPRINT=$(cat "$HOME_DIR/hub/deps.fingerprint" 2>/dev/null || echo none)
|
|
240
|
+
NODE_ABI=$("$NODE_BIN" -p 'process.versions.node.split(".")[0] + "-" + process.platform + "-" + process.arch')
|
|
241
|
+
WANT_STAMP="$FINGERPRINT-$NODE_ABI"
|
|
242
|
+
|
|
243
|
+
# The stamp claims this tree still works. Confirming it does costs one Node
|
|
244
|
+
# start, and is the whole difference between an optimisation and a machine
|
|
245
|
+
# that joins with a hub unable to load its own modules.
|
|
246
|
+
deps_reusable() {
|
|
247
|
+
[ -d "$HOME_DIR/hub/node_modules" ] || return 1
|
|
248
|
+
[ "$(cat "$STAMP_FILE" 2>/dev/null || true)" = "$WANT_STAMP" ] || return 1
|
|
249
|
+
"$NODE_BIN" -e "require('node-pty'); require('better-sqlite3')" >>"$HOME_DIR/install.log" 2>&1
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
install_deps() {
|
|
253
|
+
note "this is the slow part - a minute or two on a first run"
|
|
254
|
+
# A half-finished tree must not inherit the last run's stamp.
|
|
255
|
+
rm -f "$STAMP_FILE"
|
|
256
|
+
|
|
257
|
+
# Prebuilt binaries first: node-pty and better-sqlite3 publish them for the
|
|
258
|
+
# mainstream platforms, and downloading one beats compiling it every time.
|
|
259
|
+
if run_ticking "installing" "$NPM_BIN" install --omit=dev --no-audit --no-fund; then
|
|
260
|
+
return 0
|
|
261
|
+
fi
|
|
262
|
+
note "no prebuilt binaries for this platform; compiling instead"
|
|
263
|
+
if run_ticking "compiling" "$NPM_BIN" install --omit=dev --no-audit --no-fund --build-from-source; then
|
|
264
|
+
return 0
|
|
265
|
+
fi
|
|
266
|
+
|
|
267
|
+
tail -20 "$HOME_DIR/install.log" >&2
|
|
268
|
+
case "$(uname -s)" in
|
|
269
|
+
Darwin) TOOLCHAIN="xcode-select --install" ;;
|
|
270
|
+
*) TOOLCHAIN="sudo apt install build-essential python3 # or your distro's equivalent" ;;
|
|
271
|
+
esac
|
|
272
|
+
fail \\
|
|
273
|
+
"Could not install node-pty and better-sqlite3.
|
|
274
|
+
This machine has no prebuilt binaries and no C++ toolchain:
|
|
275
|
+
$TOOLCHAIN
|
|
276
|
+
Full log: $HOME_DIR/install.log
|
|
277
|
+
Then re-run this command."
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if deps_reusable; then
|
|
281
|
+
note "unchanged since the last run - keeping the modules already here"
|
|
282
|
+
else
|
|
283
|
+
install_deps
|
|
284
|
+
echo "$WANT_STAMP" >"$STAMP_FILE"
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
# --- join -------------------------------------------------------------------
|
|
288
|
+
step 4/4 "connecting to $HUB_URL"
|
|
289
|
+
TERMSCAPE_HOME="$HOME_DIR" nohup "$NODE_BIN" "$HOME_DIR/hub/dist/cli.js" \\
|
|
290
|
+
--headless --port 0 --join "$HUB_URL" --join-token "$JOIN_TOKEN" \\
|
|
291
|
+
>"$HOME_DIR/hub.log" 2>&1 &
|
|
292
|
+
HUB_PID=$!
|
|
293
|
+
|
|
294
|
+
# Wait for the join itself, not merely for the hub to start listening. A hub
|
|
295
|
+
# that is up but was refused by the canvas is not joined, and saying otherwise
|
|
296
|
+
# is how someone ends up staring at a host that never appears.
|
|
297
|
+
T0=$(now)
|
|
298
|
+
printf ' starting the hub '
|
|
299
|
+
i=0
|
|
300
|
+
while [ $i -lt 360 ]; do
|
|
301
|
+
if grep -q TERMSCAPE_JOINED= "$HOME_DIR/hub.log" 2>/dev/null; then
|
|
302
|
+
printf ' %s\\n' "$(since "$T0")"
|
|
303
|
+
say ""
|
|
304
|
+
say "Joined. This machine is on the canvas at $HUB_URL"
|
|
305
|
+
note "it keeps running in the background, and rejoins by itself"
|
|
306
|
+
note "log: $HOME_DIR/hub.log"
|
|
307
|
+
say ""
|
|
308
|
+
exit 0
|
|
309
|
+
fi
|
|
310
|
+
if grep -q TERMSCAPE_JOIN_FAILED= "$HOME_DIR/hub.log" 2>/dev/null; then
|
|
311
|
+
printf '\\n'
|
|
312
|
+
REASON=$(grep -m1 TERMSCAPE_JOIN_FAILED= "$HOME_DIR/hub.log" | sed 's/.*TERMSCAPE_JOIN_FAILED=//')
|
|
313
|
+
fail \
|
|
314
|
+
"$HUB_URL refused this machine:
|
|
315
|
+
$REASON
|
|
316
|
+
Open $HUB_URL/join on this machine for a fresh command - a join key is
|
|
317
|
+
single-use and expires after 15 minutes."
|
|
318
|
+
fi
|
|
319
|
+
# Checked last: a refusal, read just above, explains an exit far better
|
|
320
|
+
# than the bare fact of the exit does.
|
|
321
|
+
if ! kill -0 "$HUB_PID" 2>/dev/null; then
|
|
322
|
+
printf '\\n'
|
|
323
|
+
tail -20 "$HOME_DIR/hub.log" >&2 || true
|
|
324
|
+
fail "The hub exited without joining. See $HOME_DIR/hub.log"
|
|
325
|
+
fi
|
|
326
|
+
printf '.'
|
|
327
|
+
i=$((i + 1))
|
|
328
|
+
sleep 0.5
|
|
329
|
+
done
|
|
330
|
+
|
|
331
|
+
printf '\\n'
|
|
332
|
+
tail -20 "$HOME_DIR/hub.log" >&2 || true
|
|
333
|
+
fail "The hub started but never reported joining $HUB_URL. See $HOME_DIR/hub.log"
|
|
334
|
+
`;
|
|
335
|
+
}
|
|
336
|
+
export function joinScriptPowerShell(origin, token) {
|
|
337
|
+
return `# Joins this machine to the Termscape canvas at ${origin}.
|
|
338
|
+
$ErrorActionPreference = 'Stop'
|
|
339
|
+
$ProgressPreference = 'SilentlyContinue' # PS5.1's progress bar cripples Invoke-WebRequest
|
|
340
|
+
|
|
341
|
+
$HubUrl = ${sq(origin)}
|
|
342
|
+
$JoinToken = ${sq(token)}
|
|
343
|
+
$HomeDir = if ($env:TERMSCAPE_HOME) { $env:TERMSCAPE_HOME } else { Join-Path $HOME '.termscape' }
|
|
344
|
+
$NodeDist = ${sq(NODE_DIST)}
|
|
345
|
+
|
|
346
|
+
# Write-Error, not exit: with $ErrorActionPreference = 'Stop' this ends the
|
|
347
|
+
# script, and "exit" inside "irm ... | iex" closes the user's whole console -
|
|
348
|
+
# taking the message they need with it.
|
|
349
|
+
function Fail($msg) { Write-Host ""; Write-Error $msg }
|
|
350
|
+
function Note($msg) { Write-Host " $msg" }
|
|
351
|
+
function Step($n, $title) { Write-Host ""; Write-Host "[$n] $title" }
|
|
352
|
+
function Human($bytes) {
|
|
353
|
+
if ($bytes -ge 1MB) { return "{0:N1} MB" -f ($bytes / 1MB) }
|
|
354
|
+
if ($bytes -ge 1KB) { return "{0:N0} KB" -f ($bytes / 1KB) }
|
|
355
|
+
return "$bytes B"
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function Get-NodeMajor($exe) {
|
|
359
|
+
# Deliberately not "node -p <expression>": PowerShell strips the inner
|
|
360
|
+
# quotes on the way to a native exe, so node would see .split(.)[0].
|
|
361
|
+
try {
|
|
362
|
+
$v = & $exe --version
|
|
363
|
+
if ($v -match '^v(\\d+)\\.') { return [int]$Matches[1] }
|
|
364
|
+
return 0
|
|
365
|
+
} catch { return 0 }
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
# curl.exe ships with Windows 10 1803 and newer - the same vintage as tar,
|
|
369
|
+
# which this script already needs - and unlike Invoke-WebRequest it can show a
|
|
370
|
+
# progress bar without the PS5.1 slowdown.
|
|
371
|
+
$CurlExe = Get-Command curl.exe -ErrorAction SilentlyContinue
|
|
372
|
+
|
|
373
|
+
function Fetch($url, $dest, $quiet) {
|
|
374
|
+
if ($CurlExe -and -not $quiet) {
|
|
375
|
+
& $CurlExe.Source -f -# -L $url -o $dest
|
|
376
|
+
if ($LASTEXITCODE -ne 0) { Fail "Download failed: $url" }
|
|
377
|
+
} else {
|
|
378
|
+
Invoke-WebRequest -Uri $url -OutFile $dest -UseBasicParsing
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
# Long commands run through cmd.exe as a detached process, for two reasons.
|
|
383
|
+
#
|
|
384
|
+
# The tick: a terminal that prints nothing for a minute looks like one that has
|
|
385
|
+
# hung, so we poll and print a dot.
|
|
386
|
+
#
|
|
387
|
+
# And the redirection has to happen outside PowerShell. In 5.1, sending a
|
|
388
|
+
# native command's stderr through a PowerShell stream ("npm ... *> $log") wraps
|
|
389
|
+
# every line in an ErrorRecord, so with $ErrorActionPreference = 'Stop' a
|
|
390
|
+
# perfectly healthy "npm notice" on stderr kills the script. Start-Process
|
|
391
|
+
# redirects at the OS level and never touches PowerShell's error stream.
|
|
392
|
+
#
|
|
393
|
+
# node running npm's own entrypoint, rather than npm.cmd: CreateProcess cannot
|
|
394
|
+
# launch a .cmd without an interpreter once UseShellExecute is off, which is
|
|
395
|
+
# what redirecting switches off, and going through cmd.exe would drag in its
|
|
396
|
+
# quoting rules for no benefit. $argString is one pre-quoted string because
|
|
397
|
+
# -ArgumentList joins an array on spaces without quoting anything.
|
|
398
|
+
function Invoke-Ticking($label, $exe, $argString, $log) {
|
|
399
|
+
Write-Host " $label " -NoNewline
|
|
400
|
+
$sw = [Diagnostics.Stopwatch]::StartNew()
|
|
401
|
+
$p = Start-Process -FilePath $exe -ArgumentList $argString \`
|
|
402
|
+
-WorkingDirectory (Get-Location).Path -NoNewWindow -PassThru \`
|
|
403
|
+
-RedirectStandardOutput $log -RedirectStandardError "$log.err"
|
|
404
|
+
# Touching .Handle caches it. Without that, a process from
|
|
405
|
+
# Start-Process -PassThru reports a null ExitCode once it has exited,
|
|
406
|
+
# and a successful install reads as a failure.
|
|
407
|
+
$null = $p.Handle
|
|
408
|
+
while (-not $p.HasExited) {
|
|
409
|
+
Write-Host "." -NoNewline
|
|
410
|
+
Start-Sleep -Seconds 2
|
|
411
|
+
}
|
|
412
|
+
$p.WaitForExit()
|
|
413
|
+
Write-Host " $([int]$sw.Elapsed.TotalSeconds)s"
|
|
414
|
+
if ($null -eq $p.ExitCode) { Fail "Could not read the exit code of $label" }
|
|
415
|
+
return $p.ExitCode
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
# A hub from an earlier join is still running and still holds its own install
|
|
419
|
+
# open. On Windows a loaded .node cannot be deleted at all, so without this the
|
|
420
|
+
# whole re-join dies on an access-denied removing better-sqlite3. It also stops
|
|
421
|
+
# this machine appearing twice on the canvas.
|
|
422
|
+
function Stop-RunningHub($hubDir) {
|
|
423
|
+
$pidFile = Join-Path $HomeDir 'hub.pid'
|
|
424
|
+
$targets = @()
|
|
425
|
+
|
|
426
|
+
if (Test-Path $pidFile) {
|
|
427
|
+
$recorded = (Get-Content $pidFile -Raw).Trim()
|
|
428
|
+
if ($recorded -match '^\\d+$') {
|
|
429
|
+
$p = Get-Process -Id ([int]$recorded) -ErrorAction SilentlyContinue
|
|
430
|
+
if ($p) { $targets += $p }
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
# A hub predating the pid file, or one whose file was lost: find it by the
|
|
435
|
+
# install it was started from.
|
|
436
|
+
if (-not $targets) {
|
|
437
|
+
$targets = @(Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue |
|
|
438
|
+
Where-Object {
|
|
439
|
+
$_.CommandLine -and
|
|
440
|
+
($_.CommandLine -like "*$hubDir*" -or $_.CommandLine -like "*$HomeDir*")
|
|
441
|
+
} |
|
|
442
|
+
ForEach-Object { Get-Process -Id $_.ProcessId -ErrorAction SilentlyContinue })
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
foreach ($p in $targets) {
|
|
446
|
+
Note "stopping the hub already running here (pid $($p.Id))"
|
|
447
|
+
try {
|
|
448
|
+
$p.CloseMainWindow() | Out-Null
|
|
449
|
+
Stop-Process -Id $p.Id -Force -ErrorAction Stop
|
|
450
|
+
$p.WaitForExit(10000) | Out-Null
|
|
451
|
+
} catch {
|
|
452
|
+
Note "could not stop pid $($p.Id): $($_.Exception.Message)"
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
# Windows releases the file handles a moment after the process goes.
|
|
456
|
+
if ($targets) { Start-Sleep -Milliseconds 750 }
|
|
457
|
+
Remove-Item $pidFile -Force -ErrorAction SilentlyContinue
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
New-Item -ItemType Directory -Force -Path $HomeDir | Out-Null
|
|
461
|
+
Write-Host ""
|
|
462
|
+
Write-Host "Termscape - joining $HubUrl"
|
|
463
|
+
|
|
464
|
+
# --- node -------------------------------------------------------------------
|
|
465
|
+
# A private copy under ~/.termscape, not a system install: no administrator
|
|
466
|
+
# rights, no package manager, and no new shell needed to pick up a PATH change.
|
|
467
|
+
function Install-Node {
|
|
468
|
+
$arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'x64' }
|
|
469
|
+
Note "looking up the latest Node 22 for win-$arch"
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
$sums = (Invoke-WebRequest -Uri "$NodeDist/SHASUMS256.txt" -UseBasicParsing).Content
|
|
473
|
+
} catch {
|
|
474
|
+
Fail "Could not reach $NodeDist - check this machine's internet access.\`n$($_.Exception.Message)"
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
$line = $sums -split "\`n" | Where-Object { $_ -match "node-v[\\d.]+-win-$arch\\.zip\\s*$" } | Select-Object -First 1
|
|
478
|
+
if (-not $line) { Fail "nodejs.org has no win-$arch build on the v22 line." }
|
|
479
|
+
$fields = ($line.Trim() -split '\\s+')
|
|
480
|
+
$wantSha = $fields[0]
|
|
481
|
+
$zipName = $fields[1]
|
|
482
|
+
|
|
483
|
+
$zip = Join-Path $HomeDir $zipName
|
|
484
|
+
Note "downloading $zipName (about 30 MB)"
|
|
485
|
+
$sw = [Diagnostics.Stopwatch]::StartNew()
|
|
486
|
+
Fetch "$NodeDist/$zipName" $zip $false
|
|
487
|
+
Note "downloaded $(Human (Get-Item $zip).Length) in $([int]$sw.Elapsed.TotalSeconds)s"
|
|
488
|
+
|
|
489
|
+
# We are about to execute this, so verify it rather than trusting the pipe.
|
|
490
|
+
Write-Host " verifying checksum ... " -NoNewline
|
|
491
|
+
$gotSha = (Get-FileHash -Path $zip -Algorithm SHA256).Hash.ToLower()
|
|
492
|
+
if ($gotSha -ne $wantSha.ToLower()) {
|
|
493
|
+
Write-Host "FAILED"
|
|
494
|
+
Remove-Item $zip -Force
|
|
495
|
+
Fail "Checksum mismatch on $zipName. Refusing to run it."
|
|
496
|
+
}
|
|
497
|
+
Write-Host "ok"
|
|
498
|
+
|
|
499
|
+
Note "unpacking into $HomeDir\\node"
|
|
500
|
+
$staging = Join-Path $HomeDir 'node-unzip'
|
|
501
|
+
if (Test-Path $staging) { Remove-Item -Recurse -Force $staging }
|
|
502
|
+
Expand-Archive -Path $zip -DestinationPath $staging -Force
|
|
503
|
+
Remove-Item $zip -Force
|
|
504
|
+
|
|
505
|
+
# The zip wraps everything in node-vX.Y.Z-win-arch\\; lift that up a level so
|
|
506
|
+
# the path is stable across versions.
|
|
507
|
+
$inner = Get-ChildItem -Path $staging -Directory | Select-Object -First 1
|
|
508
|
+
$target = Join-Path $HomeDir 'node'
|
|
509
|
+
if (Test-Path $target) { Remove-Item -Recurse -Force $target }
|
|
510
|
+
Move-Item -Path $inner.FullName -Destination $target
|
|
511
|
+
Remove-Item -Recurse -Force $staging
|
|
512
|
+
|
|
513
|
+
Note "installed $(& (Join-Path $target 'node.exe') --version)"
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
Step '1/4' 'Node 22'
|
|
517
|
+
$privateNode = Join-Path $HomeDir 'node\\node.exe'
|
|
518
|
+
|
|
519
|
+
if ((Test-Path $privateNode) -and (Get-NodeMajor $privateNode) -ge 22) {
|
|
520
|
+
Note "$(& $privateNode --version) already here (private copy)"
|
|
521
|
+
} elseif ((Get-Command node -ErrorAction SilentlyContinue) -and (Get-NodeMajor 'node') -ge 22) {
|
|
522
|
+
Note "$(node --version) already on this machine"
|
|
523
|
+
} else {
|
|
524
|
+
if (Get-Command node -ErrorAction SilentlyContinue) {
|
|
525
|
+
Note "$(node --version) is older than 22; fetching a private copy"
|
|
526
|
+
} else {
|
|
527
|
+
Note "not installed; fetching a private copy"
|
|
528
|
+
}
|
|
529
|
+
Install-Node
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (Test-Path $privateNode) {
|
|
533
|
+
$NodeExe = $privateNode
|
|
534
|
+
$NodeHome = Join-Path $HomeDir 'node'
|
|
535
|
+
# Anything npm shells out to must find the private copy first.
|
|
536
|
+
$env:PATH = $NodeHome + ';' + $env:PATH
|
|
537
|
+
} else {
|
|
538
|
+
$NodeExe = (Get-Command node).Source
|
|
539
|
+
$NodeHome = Split-Path $NodeExe -Parent
|
|
540
|
+
}
|
|
541
|
+
$NpmCli = Join-Path $NodeHome 'node_modules\\npm\\bin\\npm-cli.js'
|
|
542
|
+
if (-not (Test-Path $NpmCli)) { Fail "Could not find npm alongside $NodeExe" }
|
|
543
|
+
|
|
544
|
+
# --- the hub ----------------------------------------------------------------
|
|
545
|
+
Step '2/4' 'hub package'
|
|
546
|
+
Note "downloading from $HubUrl"
|
|
547
|
+
$tgz = Join-Path $HomeDir 'hub.tgz'
|
|
548
|
+
Fetch "$HubUrl/hub.tgz" $tgz $true
|
|
549
|
+
Note "got $(Human (Get-Item $tgz).Length)"
|
|
550
|
+
|
|
551
|
+
$hubDir = Join-Path $HomeDir 'hub'
|
|
552
|
+
$hubModules = Join-Path $hubDir 'node_modules'
|
|
553
|
+
Stop-RunningHub $hubDir
|
|
554
|
+
|
|
555
|
+
# The dependency tree is the slowest thing here to rebuild and the tarball
|
|
556
|
+
# never carries one, so it is moved aside rather than deleted along with the
|
|
557
|
+
# rest of the install. Whether it can be kept is step 3's question. It also
|
|
558
|
+
# means far less for the removal below to fail on: a loaded .node is the file
|
|
559
|
+
# Windows refuses to delete.
|
|
560
|
+
$keptModules = Join-Path $HomeDir 'node_modules.kept'
|
|
561
|
+
if (Test-Path $keptModules) { Remove-Item -Recurse -Force $keptModules }
|
|
562
|
+
if (Test-Path $hubModules) { Move-Item $hubModules $keptModules }
|
|
563
|
+
|
|
564
|
+
if (Test-Path $hubDir) {
|
|
565
|
+
try {
|
|
566
|
+
Remove-Item -Recurse -Force $hubDir
|
|
567
|
+
} catch {
|
|
568
|
+
Fail ("Could not replace the previous install at $hubDir - something still has a file open there." + [Environment]::NewLine +
|
|
569
|
+
"A hub from an earlier join is probably still running. Close it and re-run this command." + [Environment]::NewLine +
|
|
570
|
+
$_.Exception.Message)
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
New-Item -ItemType Directory -Force -Path $hubDir | Out-Null
|
|
574
|
+
# tar ships with Windows 10 1803 and newer.
|
|
575
|
+
tar xzf $tgz -C $hubDir --strip-components=1
|
|
576
|
+
if (Test-Path $keptModules) { Move-Item $keptModules $hubModules }
|
|
577
|
+
Note "unpacked into $hubDir"
|
|
578
|
+
|
|
579
|
+
# --- install ----------------------------------------------------------------
|
|
580
|
+
Step '3/4' 'dependencies'
|
|
581
|
+
$log = Join-Path $HomeDir 'install.log'
|
|
582
|
+
Note "full log: $log"
|
|
583
|
+
Push-Location $hubDir
|
|
584
|
+
|
|
585
|
+
# What the tree already here would have to match to be worth keeping: the
|
|
586
|
+
# fingerprint the tarball shipped, plus the two things it cannot know - the
|
|
587
|
+
# Node ABI these modules were built against, and the platform.
|
|
588
|
+
$stampFile = Join-Path $HomeDir 'deps.stamp'
|
|
589
|
+
$fingerprintFile = Join-Path $hubDir 'deps.fingerprint'
|
|
590
|
+
$fingerprint = 'none'
|
|
591
|
+
if (Test-Path $fingerprintFile) {
|
|
592
|
+
$fingerprint = ((Get-Content $fingerprintFile -TotalCount 1) + '').Trim()
|
|
593
|
+
}
|
|
594
|
+
# Single quotes inside a double-quoted string, deliberately: PowerShell
|
|
595
|
+
# strips a double quote out of an argument on its way to a native command,
|
|
596
|
+
# and this expression would reach node as unparseable JavaScript.
|
|
597
|
+
$abiJs = "process.versions.node.split('.')[0] + '-' + process.platform + '-' + process.arch"
|
|
598
|
+
$nodeAbi = & $NodeExe -p $abiJs
|
|
599
|
+
$wantStamp = "$fingerprint-$nodeAbi"
|
|
600
|
+
$haveStamp = ''
|
|
601
|
+
if (Test-Path $stampFile) { $haveStamp = ((Get-Content $stampFile -TotalCount 1) + '').Trim() }
|
|
602
|
+
|
|
603
|
+
$reusable = $false
|
|
604
|
+
if ((Test-Path $hubModules) -and ($haveStamp -eq $wantStamp)) {
|
|
605
|
+
# The stamp claims this tree still works. Confirming it does costs one Node
|
|
606
|
+
# start, and is the whole difference between an optimisation and a machine
|
|
607
|
+
# that joins with a hub unable to load its own modules. The probe catches
|
|
608
|
+
# its own errors and reports through stdout and an exit code, so nothing
|
|
609
|
+
# reaches PowerShell's error stream - where 5.1 turns a native command's
|
|
610
|
+
# stderr into a terminating error.
|
|
611
|
+
$probe = "try { require('node-pty'); require('better-sqlite3') } catch (e) { console.log('dependency probe failed: ' + e.message); process.exit(1) }"
|
|
612
|
+
& $NodeExe -e $probe > $log
|
|
613
|
+
$reusable = ($LASTEXITCODE -eq 0)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if ($reusable) {
|
|
617
|
+
Note "unchanged since the last run - keeping the modules already here"
|
|
618
|
+
} else {
|
|
619
|
+
Note "this is the slow part - a minute or two on a first run"
|
|
620
|
+
# A half-finished tree must not inherit the last run's stamp.
|
|
621
|
+
if (Test-Path $stampFile) { Remove-Item $stampFile -Force }
|
|
622
|
+
|
|
623
|
+
# Prebuilt binaries first; compiling is the fallback, not the default.
|
|
624
|
+
$npmArgs = '"' + $NpmCli + '" install --omit=dev --no-audit --no-fund'
|
|
625
|
+
$rc = Invoke-Ticking 'installing' $NodeExe $npmArgs $log
|
|
626
|
+
if ($rc -ne 0) {
|
|
627
|
+
Note "no prebuilt binaries for this platform; compiling instead"
|
|
628
|
+
$rc = Invoke-Ticking 'compiling' $NodeExe ($npmArgs + ' --build-from-source') $log
|
|
629
|
+
if ($rc -ne 0) {
|
|
630
|
+
Get-Content $log -Tail 20 | Write-Host
|
|
631
|
+
Pop-Location
|
|
632
|
+
Fail "Could not install node-pty and better-sqlite3.\`nThis machine has no prebuilt binaries and no C++ toolchain. Install the Visual Studio Build Tools with the 'Desktop development with C++' workload:\`n https://visualstudio.microsoft.com/visual-cpp-build-tools/\`nFull log: $log\`nThen re-run this command."
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
Set-Content -Path $stampFile -Value $wantStamp -Encoding ascii
|
|
637
|
+
}
|
|
638
|
+
Pop-Location
|
|
639
|
+
|
|
640
|
+
# --- join -------------------------------------------------------------------
|
|
641
|
+
Step '4/4' "connecting to $HubUrl"
|
|
642
|
+
$hubLog = Join-Path $HomeDir 'hub.log'
|
|
643
|
+
$env:TERMSCAPE_HOME = $HomeDir
|
|
644
|
+
# cli.js is named absolutely so this hub is findable in the process list by
|
|
645
|
+
# the install it came from - which is how a later re-join knows what to stop.
|
|
646
|
+
$cliArgs = '"' + (Join-Path $hubDir 'dist/cli.js') + '"' +
|
|
647
|
+
' --headless --port 0 --join "' + $HubUrl + '" --join-token "' + $JoinToken + '"'
|
|
648
|
+
$hubProc = Start-Process -FilePath $NodeExe -ArgumentList $cliArgs -PassThru \`
|
|
649
|
+
-WorkingDirectory $hubDir -RedirectStandardOutput $hubLog \`
|
|
650
|
+
-RedirectStandardError (Join-Path $HomeDir 'hub.err.log') -WindowStyle Hidden
|
|
651
|
+
# Same trap as the install: without touching .Handle, HasExited on a process
|
|
652
|
+
# from Start-Process -PassThru is not reliable.
|
|
653
|
+
$null = $hubProc.Handle
|
|
654
|
+
|
|
655
|
+
# Wait for the join itself, not merely for the hub to start listening. A hub
|
|
656
|
+
# that is up but was refused by the canvas is not joined, and saying otherwise
|
|
657
|
+
# is how someone ends up staring at a host that never appears.
|
|
658
|
+
Write-Host " starting the hub " -NoNewline
|
|
659
|
+
$sw = [Diagnostics.Stopwatch]::StartNew()
|
|
660
|
+
for ($i = 0; $i -lt 360; $i++) {
|
|
661
|
+
$joined = $false
|
|
662
|
+
$refused = $null
|
|
663
|
+
# The hub is writing this file as we read it, so a locked read is a retry,
|
|
664
|
+
# not a failure.
|
|
665
|
+
try {
|
|
666
|
+
if (Test-Path $hubLog) {
|
|
667
|
+
$joined = Select-String -Path $hubLog -Pattern 'TERMSCAPE_JOINED=' -Quiet
|
|
668
|
+
$refused = Select-String -Path $hubLog -Pattern 'TERMSCAPE_JOIN_FAILED=(.*)' |
|
|
669
|
+
Select-Object -First 1
|
|
670
|
+
}
|
|
671
|
+
} catch { $joined = $false }
|
|
672
|
+
|
|
673
|
+
if ($joined) {
|
|
674
|
+
Write-Host " $([int]$sw.Elapsed.TotalSeconds)s"
|
|
675
|
+
Write-Host ""
|
|
676
|
+
Write-Host "Joined. This machine is on the canvas at $HubUrl"
|
|
677
|
+
Note "it keeps running in the background, and rejoins by itself"
|
|
678
|
+
Note "log: $hubLog"
|
|
679
|
+
Write-Host ""
|
|
680
|
+
return
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if ($refused) {
|
|
684
|
+
$reason = $refused.Matches[0].Groups[1].Value.Trim()
|
|
685
|
+
Write-Host ""
|
|
686
|
+
Fail ("$HubUrl refused this machine:" + [Environment]::NewLine +
|
|
687
|
+
" $reason" + [Environment]::NewLine +
|
|
688
|
+
"Open $HubUrl/join on this machine for a fresh command - a join key is" +
|
|
689
|
+
[Environment]::NewLine + "single-use and expires after 15 minutes.")
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
# Checked last: a refusal, read just above, explains an exit far better
|
|
693
|
+
# than the bare fact of the exit does.
|
|
694
|
+
if ($hubProc.HasExited) {
|
|
695
|
+
Write-Host ""
|
|
696
|
+
if (Test-Path $hubLog) { Get-Content $hubLog -Tail 20 | Write-Host }
|
|
697
|
+
Fail "The hub exited without joining. See $hubLog"
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
Write-Host "." -NoNewline
|
|
701
|
+
Start-Sleep -Milliseconds 500
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
Write-Host ""
|
|
705
|
+
if (Test-Path $hubLog) { Get-Content $hubLog -Tail 20 | Write-Host }
|
|
706
|
+
Fail "The hub started but never reported joining $HubUrl. See $hubLog"
|
|
707
|
+
`;
|
|
708
|
+
}
|
|
709
|
+
//# sourceMappingURL=join-script.js.map
|