@rubytech/create-maxy 1.0.680 → 1.0.682
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/index.js +209 -39
- package/package.json +1 -1
- package/payload/platform/plugins/docs/references/deployment.md +4 -2
- package/payload/platform/plugins/docs/references/troubleshooting.md +2 -0
- package/payload/platform/scripts/vnc.sh +12 -409
- package/payload/platform/templates/dotfiles/.tmux.conf +1 -0
- package/payload/platform/templates/systemd/maxy-ttyd.service +25 -0
- package/payload/server/maxy-edge.js +367 -5
- package/payload/server/public/assets/admin-Bu8EzQH7.js +362 -0
- package/payload/server/public/assets/admin-kHJ-D0s7.css +1 -0
- package/payload/server/public/index.html +2 -1
- package/payload/server/server.js +108 -301
- package/payload/server/public/assets/admin-BBL1no_g.js +0 -352
|
@@ -2,19 +2,17 @@
|
|
|
2
2
|
# VNC + browser lifecycle — single source of truth.
|
|
3
3
|
# Called by systemd ExecStartPre (boot) and lib/vnc.ts ensureVnc() (recovery).
|
|
4
4
|
#
|
|
5
|
-
# Usage: vnc.sh start | stop | start-chrome | start-chrome-native
|
|
6
|
-
# | start-terminal | start-terminal-native | start-terminal-upgrade
|
|
7
|
-
# | kill-terminal | status-terminal | status
|
|
5
|
+
# Usage: vnc.sh start | stop | start-chrome | start-chrome-native | status
|
|
8
6
|
#
|
|
9
7
|
# Components:
|
|
10
8
|
# Xtigervnc :99 — virtual X11 display + VNC server on port 5900
|
|
11
9
|
# websockify :6080 — WebSocket bridge serving noVNC static files
|
|
12
10
|
# Chromium :9222 — headed browser with CDP enabled
|
|
13
11
|
# (Playwright MCP connects via --cdp-endpoint)
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
12
|
+
#
|
|
13
|
+
# Task 657: the admin Terminal overlay and upgrade modal now use the
|
|
14
|
+
# byte-stream xterm.js surface over /ttyd on maxy-edge, not VNC. This
|
|
15
|
+
# script no longer spawns GUI terminal emulators.
|
|
18
16
|
#
|
|
19
17
|
# Display modes (DISPLAY_MODE env var, set by installer --display flag):
|
|
20
18
|
# virtual (default) — Chromium runs on :99 (VNC virtual display)
|
|
@@ -39,19 +37,13 @@ fi
|
|
|
39
37
|
MAXY_DIR="${HOME}/${CONFIG_DIR}"
|
|
40
38
|
LOG_DIR="${MAXY_DIR}/logs"
|
|
41
39
|
LOG_FILE="${LOG_DIR}/vnc-boot.log"
|
|
42
|
-
TERMINAL_LOG="${LOG_DIR}/terminal-launch.log"
|
|
43
40
|
|
|
44
41
|
mkdir -p "$LOG_DIR"
|
|
45
42
|
|
|
46
43
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"; }
|
|
47
|
-
tlog() { echo "[$(date '+%Y-%m-%dT%H:%M:%S%z')] [terminal-launch] $*" >> "$TERMINAL_LOG"; }
|
|
48
44
|
|
|
49
45
|
kill_stale() {
|
|
50
46
|
pkill -f 'chromium.*remote-debugging-port=9222' 2>/dev/null || true
|
|
51
|
-
# Terminal emulators launched by start-terminal[-native] (Task 627).
|
|
52
|
-
# Regex is anchored to avoid killing gnome-terminal-server (D-Bus service
|
|
53
|
-
# always running on GNOME desktops — not ours to manage).
|
|
54
|
-
kill_terminal_emulators
|
|
55
47
|
pkill -f 'Xtigervnc :99' 2>/dev/null || true
|
|
56
48
|
pkill -f 'websockify.*6080' 2>/dev/null || true
|
|
57
49
|
rm -f /tmp/.X99-lock /tmp/.X11-unix/X99
|
|
@@ -174,8 +166,8 @@ start_chrome_on() {
|
|
|
174
166
|
if wait_for_port 9222; then
|
|
175
167
|
# Log-only observability (Task 632): the display-membership invariant is
|
|
176
168
|
# asserted but does NOT gate success here. Chromium does not D-Bus-delegate
|
|
177
|
-
#
|
|
178
|
-
#
|
|
169
|
+
# its window creation, so the failure mode is not currently reachable — but
|
|
170
|
+
# making the invariant visible now means future backend swaps (e.g.
|
|
179
171
|
# snap-installed Chromium on Ubuntu with different IPC) surface the drift
|
|
180
172
|
# in vnc-boot.log rather than as a silent-black iframe.
|
|
181
173
|
if check_window_on_display "${target_display}"; then
|
|
@@ -193,373 +185,12 @@ start_chrome() {
|
|
|
193
185
|
}
|
|
194
186
|
|
|
195
187
|
# ---------------------------------------------------------------------------
|
|
196
|
-
#
|
|
188
|
+
# Task 657 retired the VNC-as-terminal path (Task 627/643): the header
|
|
189
|
+
# TerminalOverlay and UpdateModal now mount xterm.js against /ttyd on
|
|
190
|
+
# maxy-edge. The GUI-terminal spawn pipeline that lived here is gone.
|
|
191
|
+
# Only the Chromium surface remains for BrowserOverlay.
|
|
197
192
|
# ---------------------------------------------------------------------------
|
|
198
193
|
|
|
199
|
-
# Resolve the preferred terminal binary + its required flags. Selection depends
|
|
200
|
-
# on the target display because gnome-terminal is a D-Bus launcher that routes
|
|
201
|
-
# through /usr/libexec/gnome-terminal-server — and that server is bound to the
|
|
202
|
-
# operator's login session bus (typically :0 on a GNOME desktop). Calling
|
|
203
|
-
# `DISPLAY=:99 /usr/bin/gnome-terminal` does NOT open a window on :99; the
|
|
204
|
-
# D-Bus request is delegated to the server on :0 and the window appears there
|
|
205
|
-
# instead (Task 632 root cause). xterm is a standalone X client with no IPC
|
|
206
|
-
# layer, so `DISPLAY=:99 xterm` lands on :99 regardless of session state.
|
|
207
|
-
#
|
|
208
|
-
# Selection rule:
|
|
209
|
-
# target_display == :99 (VNC virtual) → xterm-first, gnome-terminal fallback
|
|
210
|
-
# target_display == :0 (native login) → gnome-terminal-first, xterm fallback
|
|
211
|
-
# (the session's gnome-terminal-server owns :0, so D-Bus delegation lands
|
|
212
|
-
# on the right display here — no bug reachable on loopback)
|
|
213
|
-
#
|
|
214
|
-
# Prints "<bin>\t<flags>" on stdout (tab-separated), or exits non-zero with a
|
|
215
|
-
# loud-fail log if neither is installed. gnome-terminal retains `--wait`
|
|
216
|
-
# (see Task 627 pgrep-visibility fix). xterm takes no flag.
|
|
217
|
-
# xterm flags for the :99 VNC framebuffer. Fontconfig alias `monospace`
|
|
218
|
-
# resolves via /etc/fonts to the distro's default monospace face (DejaVu
|
|
219
|
-
# Sans Mono on Ubuntu/Debian) — no quoting needed, so the flag string
|
|
220
|
-
# survives unquoted $flags word-splitting in start_terminal_on. Geometry
|
|
221
|
-
# 160x45 fills the Xtigervnc display (1280x800 — see Xtigervnc -geometry
|
|
222
|
-
# flag below) at 11pt; the prior empty flag string left xterm at its 80x24
|
|
223
|
-
# default with the 6x13 `fixed` bitmap font, which rendered ~480x312 px of
|
|
224
|
-
# the 1280x800 framebuffer with a black void around it.
|
|
225
|
-
XTERM_VNC_FLAGS='-fa monospace -fs 11 -geometry 160x45'
|
|
226
|
-
|
|
227
|
-
resolve_terminal_bin() {
|
|
228
|
-
local target_display="${1:-:99}"
|
|
229
|
-
local xterm_bin='' gnome_bin=''
|
|
230
|
-
[ -x /usr/bin/xterm ] && xterm_bin='/usr/bin/xterm'
|
|
231
|
-
[ -x /usr/bin/gnome-terminal ] && gnome_bin='/usr/bin/gnome-terminal'
|
|
232
|
-
|
|
233
|
-
if [ "$target_display" = ":99" ]; then
|
|
234
|
-
[ -n "$xterm_bin" ] && { printf '%s\t%s\n' "$xterm_bin" "$XTERM_VNC_FLAGS"; return 0; }
|
|
235
|
-
[ -n "$gnome_bin" ] && { printf '%s\t%s\n' "$gnome_bin" '--wait'; return 0; }
|
|
236
|
-
else
|
|
237
|
-
[ -n "$gnome_bin" ] && { printf '%s\t%s\n' "$gnome_bin" '--wait'; return 0; }
|
|
238
|
-
[ -n "$xterm_bin" ] && { printf '%s\t%s\n' "$xterm_bin" "$XTERM_VNC_FLAGS"; return 0; }
|
|
239
|
-
fi
|
|
240
|
-
tlog "failed err=\"no terminal emulator installed (expected /usr/bin/gnome-terminal or /usr/bin/xterm)\""
|
|
241
|
-
log "ERROR: no terminal emulator binary found — install xterm (apt-get install -y xterm)"
|
|
242
|
-
return 1
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
# Task 634 — runtime preflight for terminal-launch dependencies. Separates
|
|
246
|
-
# exit 127 (xdotool missing from PATH) from exit 1 (xdotool present but no
|
|
247
|
-
# window mapped) in check_window_on_display. The installer now verifies
|
|
248
|
-
# apt deps via `dpkg -s` but an operator can still `apt remove xdotool`
|
|
249
|
-
# post-install, so this is the matching runtime guard. Emits the failure
|
|
250
|
-
# string on stderr so ensureTerminal's extractFailureLine captures it
|
|
251
|
-
# verbatim into server.log's `ensure-terminal err=...` field.
|
|
252
|
-
preflight_terminal_deps() {
|
|
253
|
-
if command -v xdotool >/dev/null 2>&1; then
|
|
254
|
-
log "xdotool: $(command -v xdotool)"
|
|
255
|
-
return 0
|
|
256
|
-
fi
|
|
257
|
-
tlog "failed err=\"xdotool not installed — re-run installer to repair\""
|
|
258
|
-
log "ERROR: xdotool missing — start-terminal cannot assert display-membership (re-run: npx -y @rubytech/create-maxy@latest)"
|
|
259
|
-
echo "[terminal-launch] failed err=\"xdotool not installed — re-run installer to repair\"" >&2
|
|
260
|
-
return 1
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
# Verify that at least one visible window is mapped on $target_display. Closes
|
|
264
|
-
# the "PID visible in pgrep but window is not on target" class of silent fail
|
|
265
|
-
# (Task 632). Uses xdotool's exit code directly — no stdout parsing for
|
|
266
|
-
# control flow (feedback_no_stdout_parsing_for_control_flow.md). `--class '.'`
|
|
267
|
-
# matches WM_CLASS (static for the window's lifetime) instead of WM_NAME
|
|
268
|
-
# (which can be empty in the microseconds between window-create and
|
|
269
|
-
# title-set). Retries 3× at 0.3s to absorb the window-map race.
|
|
270
|
-
#
|
|
271
|
-
# Returns 0 if any visible window appears on the display within ~1s, 1 otherwise.
|
|
272
|
-
check_window_on_display() {
|
|
273
|
-
local target_display="$1"
|
|
274
|
-
# Task 634 — route xdotool stderr to vnc-boot.log instead of /dev/null.
|
|
275
|
-
# preflight_terminal_deps removes the 127-vs-1 conflation from production,
|
|
276
|
-
# but if xdotool ever fails for a non-missing-binary reason (broken X
|
|
277
|
-
# server, malformed DISPLAY), the diagnostic now lands in a grep-addressable
|
|
278
|
-
# log instead of vanishing. Control flow still depends solely on the exit
|
|
279
|
-
# code (feedback_no_stdout_parsing_for_control_flow.md).
|
|
280
|
-
for _ in 1 2 3; do
|
|
281
|
-
if DISPLAY="$target_display" xdotool search --onlyvisible --class '.' >/dev/null 2>>"$LOG_FILE"; then
|
|
282
|
-
return 0
|
|
283
|
-
fi
|
|
284
|
-
sleep 0.3
|
|
285
|
-
done
|
|
286
|
-
return 1
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
# Count visible windows on $target_display (diagnostic; used in failure logs).
|
|
290
|
-
# Prints the integer count. Returns 0 on empty display (not an error).
|
|
291
|
-
_count_windows_on_display() {
|
|
292
|
-
local target_display="$1"
|
|
293
|
-
DISPLAY="$target_display" xdotool search --onlyvisible --class '.' 2>/dev/null | wc -l | tr -d ' '
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
# pgrep pattern that matches operator-launched terminals but NEVER matches
|
|
297
|
-
# /usr/libexec/gnome-terminal-server (D-Bus service, pre-existing). Matches:
|
|
298
|
-
# - /usr/bin/gnome-terminal --wait (Ubuntu's python wrapper, invoked with --wait)
|
|
299
|
-
# - /usr/bin/python3 /usr/bin/gnome-terminal (wrapper as seen via pgrep -f)
|
|
300
|
-
# - /usr/bin/gnome-terminal.real --wait (actual binary, child of wrapper)
|
|
301
|
-
# - /usr/bin/xterm, xterm -geometry ... (xterm, single-process emulator)
|
|
302
|
-
# Rejects:
|
|
303
|
-
# - /usr/libexec/gnome-terminal-server (D-Bus service, not ours to manage)
|
|
304
|
-
#
|
|
305
|
-
# The `(^|/)` alternation anchors on either the start of the cmdline or a
|
|
306
|
-
# preceding `/` path separator, so the python wrapper path `/usr/bin/python3
|
|
307
|
-
# /usr/bin/gnome-terminal --wait` is matched via the inner `/gnome-terminal`.
|
|
308
|
-
# The `(\.real)?` optional suffix catches the actual binary. The trailing
|
|
309
|
-
# `([[:space:]]|$)` breaks the match on `-server` (dash is not whitespace).
|
|
310
|
-
# POSIX ERE — procps `pgrep` does not support `\s`, so use [[:space:]].
|
|
311
|
-
_terminal_pgrep_pattern() {
|
|
312
|
-
echo '(^|/)(gnome-terminal(\.real)?([[:space:]]|$)|xterm([[:space:]]|$))'
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
# Return 0 if a launched terminal is alive, 1 otherwise.
|
|
316
|
-
# ensureTerminal() uses this as its post-spawn liveness probe (the
|
|
317
|
-
# terminal-domain analogue of waitForPort — terminals have no port).
|
|
318
|
-
terminal_alive() {
|
|
319
|
-
pgrep -f "$(_terminal_pgrep_pattern)" >/dev/null 2>&1
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
# Return the first matching PID (used in logs). Empty string if none alive.
|
|
323
|
-
terminal_pid() {
|
|
324
|
-
pgrep -f "$(_terminal_pgrep_pattern)" 2>/dev/null | head -n 1
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
# Kill all operator-launched terminal emulators. Pre-existing
|
|
328
|
-
# gnome-terminal-server is unaffected by the anchored regex.
|
|
329
|
-
kill_terminal_emulators() {
|
|
330
|
-
pkill -f "$(_terminal_pgrep_pattern)" 2>/dev/null || true
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
# Wait up to 1s for the spawned terminal to show up in pgrep.
|
|
334
|
-
# Mirrors wait_for_port's deadline semantics (3 × 0.3s = 0.9s wall-clock max).
|
|
335
|
-
wait_for_terminal() {
|
|
336
|
-
for _ in 1 2 3; do
|
|
337
|
-
if terminal_alive; then
|
|
338
|
-
return 0
|
|
339
|
-
fi
|
|
340
|
-
sleep 0.3
|
|
341
|
-
done
|
|
342
|
-
return 1
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
start_terminal_on() {
|
|
346
|
-
local target_display="$1"
|
|
347
|
-
local label="$2" # "vnc" | "native"
|
|
348
|
-
|
|
349
|
-
# Idempotency: if a terminal is already alive, verify its window is actually
|
|
350
|
-
# mapped on $target_display before accepting. The pgrep-based liveness probe
|
|
351
|
-
# cannot distinguish a terminal on :0 (stale from an unclean close, or
|
|
352
|
-
# D-Bus-delegated by a pre-Task-632 gnome-terminal call) from one on :99.
|
|
353
|
-
# Self-heal: if the PID exists but no window appears on the target display,
|
|
354
|
-
# kill the stale emulator and fall through to respawn.
|
|
355
|
-
if terminal_alive; then
|
|
356
|
-
local existing_pid
|
|
357
|
-
existing_pid="$(terminal_pid)"
|
|
358
|
-
if check_window_on_display "$target_display"; then
|
|
359
|
-
tlog "already-running pid=${existing_pid} display=${target_display} windowPresent=true"
|
|
360
|
-
log "Terminal already running pid=${existing_pid} (${label})"
|
|
361
|
-
return 0
|
|
362
|
-
fi
|
|
363
|
-
local observed
|
|
364
|
-
observed="$(_count_windows_on_display "$target_display")"
|
|
365
|
-
tlog "self-heal pid=${existing_pid} display=${target_display} observed_windows=${observed} transport=${label} reason=\"window absent on target, respawning\""
|
|
366
|
-
log "Terminal pid=${existing_pid} alive but no window on ${target_display} — self-healing"
|
|
367
|
-
kill_terminal_emulators
|
|
368
|
-
sleep 0.3
|
|
369
|
-
fi
|
|
370
|
-
|
|
371
|
-
local resolved bin flags
|
|
372
|
-
if ! resolved="$(resolve_terminal_bin "$target_display")"; then
|
|
373
|
-
# resolve_terminal_bin already echoed the diagnostic via tlog; mirror it
|
|
374
|
-
# to stderr so the Node caller (ensureTerminal) captures the exact string.
|
|
375
|
-
echo "[terminal-launch] failed err=\"no terminal emulator installed\" transport=${label}" >&2
|
|
376
|
-
return 1
|
|
377
|
-
fi
|
|
378
|
-
bin="${resolved%%$'\t'*}"
|
|
379
|
-
flags="${resolved#*$'\t'}"
|
|
380
|
-
|
|
381
|
-
log "Starting ${bin} ${flags} on ${target_display} (${label})"
|
|
382
|
-
|
|
383
|
-
# setsid -f detaches the process from our controlling terminal and this
|
|
384
|
-
# script's process group, so the spawned terminal survives vnc.sh exiting.
|
|
385
|
-
# Output redirected to the terminal-launch log (not /dev/null) so any
|
|
386
|
-
# spawn-time stderr is captured. Flags is unquoted on purpose so an empty
|
|
387
|
-
# value (xterm's case) does not produce a stray "" arg.
|
|
388
|
-
if [ -n "$flags" ]; then
|
|
389
|
-
DISPLAY="${target_display}" setsid -f "$bin" $flags >> "$TERMINAL_LOG" 2>&1 || true
|
|
390
|
-
else
|
|
391
|
-
DISPLAY="${target_display}" setsid -f "$bin" >> "$TERMINAL_LOG" 2>&1 || true
|
|
392
|
-
fi
|
|
393
|
-
|
|
394
|
-
if ! wait_for_terminal; then
|
|
395
|
-
local diag="failed err=\"spawn detached but no terminal PID visible within 1s\" transport=${label} cmd=\"${bin} ${flags}\""
|
|
396
|
-
tlog "$diag"
|
|
397
|
-
echo "[terminal-launch] $diag" >&2
|
|
398
|
-
log "ERROR: terminal failed to appear in pgrep within 1s on ${target_display} (${label}) — investigate setsid -f detachment / --wait flag"
|
|
399
|
-
return 1
|
|
400
|
-
fi
|
|
401
|
-
|
|
402
|
-
local pid
|
|
403
|
-
pid="$(terminal_pid)"
|
|
404
|
-
|
|
405
|
-
# Post-spawn invariant (Task 632): PID presence is necessary but not
|
|
406
|
-
# sufficient — assert the window actually landed on the target display.
|
|
407
|
-
if ! check_window_on_display "$target_display"; then
|
|
408
|
-
local observed
|
|
409
|
-
observed="$(_count_windows_on_display "$target_display")"
|
|
410
|
-
local diag="failed err=\"window absent from target display after spawn\" pid=${pid} display=${target_display} observed_windows=${observed} transport=${label} cmd=\"${bin} ${flags}\""
|
|
411
|
-
tlog "$diag"
|
|
412
|
-
echo "[terminal-launch] $diag" >&2
|
|
413
|
-
log "ERROR: terminal pid=${pid} spawned but no window on ${target_display} (${label}) — likely D-Bus delegation to another display"
|
|
414
|
-
return 1
|
|
415
|
-
fi
|
|
416
|
-
|
|
417
|
-
tlog "started pid=${pid} display=${target_display} cmd=\"${bin} ${flags}\" transport=${label} windowPresent=true"
|
|
418
|
-
log "Terminal ready (${label}) pid=${pid}"
|
|
419
|
-
return 0
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
start_terminal() {
|
|
423
|
-
preflight_terminal_deps || return 1
|
|
424
|
-
start_terminal_on ":99" "vnc"
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
start_terminal_native() {
|
|
428
|
-
preflight_terminal_deps || return 1
|
|
429
|
-
discover_native_session
|
|
430
|
-
start_terminal_on "${NATIVE_DISPLAY}" "native"
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
# Task 643 — upgrade-specialised terminal spawn. Sibling of start_terminal_on
|
|
434
|
-
# rather than a parameterised branch because two things diverge:
|
|
435
|
-
#
|
|
436
|
-
# (1) idempotency inverts. start_terminal_on short-circuits when a terminal
|
|
437
|
-
# is already alive on the target display; the upgrade variant MUST kill
|
|
438
|
-
# any pre-existing shell and spawn a fresh one so npx runs in a clean
|
|
439
|
-
# environment (no operator aliases, cwd, or half-typed commands).
|
|
440
|
-
#
|
|
441
|
-
# (2) binary invocation grows a command argument. Both binaries keep their
|
|
442
|
-
# existing flags (xterm: $XTERM_VNC_FLAGS, gnome-terminal: --wait), then
|
|
443
|
-
# append a binary-specific "run this command" dispatcher:
|
|
444
|
-
# xterm -e bash -c "<cmd>; exec bash"
|
|
445
|
-
# gnome-terminal -- bash -c "<cmd>; exec bash"
|
|
446
|
-
# The trailing `exec bash` replaces the shell process after the upgrade
|
|
447
|
-
# exits, so the terminal window stays open for scrollback review.
|
|
448
|
-
#
|
|
449
|
-
# Everything else — preflight, binary resolution, setsid -f detach,
|
|
450
|
-
# wait_for_terminal, check_window_on_display (Task 632 invariant) — is the
|
|
451
|
-
# same pipeline as start_terminal_on. The two functions share helpers, not
|
|
452
|
-
# bodies, so a future invariant addition (e.g. post-spawn keepalive) requires
|
|
453
|
-
# two edits, not one — an intentional tradeoff for readability.
|
|
454
|
-
start_terminal_upgrade_on() {
|
|
455
|
-
local target_display="$1"
|
|
456
|
-
local label="$2" # "vnc" | "native"
|
|
457
|
-
|
|
458
|
-
# Unconditional kill: no "already running" short-circuit. An upgrade grafted
|
|
459
|
-
# onto a pre-existing operator shell would inherit that shell's env, cwd,
|
|
460
|
-
# and interactive state — not acceptable for an installer run.
|
|
461
|
-
if terminal_alive; then
|
|
462
|
-
local existing_pid
|
|
463
|
-
existing_pid="$(terminal_pid)"
|
|
464
|
-
tlog "upgrade-kill pid=${existing_pid} reason=\"pre-upgrade fresh shell required\""
|
|
465
|
-
log "Terminal pid=${existing_pid} killed to spawn fresh upgrade shell (${label})"
|
|
466
|
-
kill_terminal_emulators
|
|
467
|
-
sleep 0.3
|
|
468
|
-
fi
|
|
469
|
-
|
|
470
|
-
local resolved bin flags
|
|
471
|
-
if ! resolved="$(resolve_terminal_bin "$target_display")"; then
|
|
472
|
-
echo "[terminal-launch] failed err=\"no terminal emulator installed\" transport=${label} reason=upgrade" >&2
|
|
473
|
-
return 1
|
|
474
|
-
fi
|
|
475
|
-
bin="${resolved%%$'\t'*}"
|
|
476
|
-
flags="${resolved#*$'\t'}"
|
|
477
|
-
|
|
478
|
-
# Binary-specific command dispatcher. Both variants wrap the upgrade in
|
|
479
|
-
# `bash -c "<cmd>; exec bash"` so the shell persists after npx exits.
|
|
480
|
-
local upgrade_cmd='npx -y @rubytech/create-maxy@latest'
|
|
481
|
-
local bash_wrapper="${upgrade_cmd}; exec bash"
|
|
482
|
-
local dispatch_flag
|
|
483
|
-
case "$bin" in
|
|
484
|
-
*/xterm) dispatch_flag='-e' ;;
|
|
485
|
-
*/gnome-terminal) dispatch_flag='--' ;;
|
|
486
|
-
*)
|
|
487
|
-
# resolve_terminal_bin only returns xterm or gnome-terminal paths. This
|
|
488
|
-
# branch is defensive; a future binary addition (e.g. kitty) would need
|
|
489
|
-
# its own dispatcher here.
|
|
490
|
-
tlog "failed err=\"unknown terminal binary dispatcher for ${bin}\" reason=upgrade"
|
|
491
|
-
echo "[terminal-launch] failed err=\"unknown terminal binary dispatcher for ${bin}\" transport=${label} reason=upgrade" >&2
|
|
492
|
-
return 1
|
|
493
|
-
;;
|
|
494
|
-
esac
|
|
495
|
-
|
|
496
|
-
# Task 647: the upgrade command (`npx -y @rubytech/create-maxy@latest`) calls
|
|
497
|
-
# `systemctl --user restart maxy-ui` partway through. If the terminal shell
|
|
498
|
-
# lives in maxy-ui's cgroup it gets SIGKILL'd by that restart and the install
|
|
499
|
-
# aborts. `systemd-run --user --scope` places the shell in its own transient
|
|
500
|
-
# scope unit whose lifetime is independent of whichever service triggered
|
|
501
|
-
# vnc.sh. `setsid -f` is no longer needed — scope units handle detachment.
|
|
502
|
-
# Fallback (no systemd-run): `setsid -f`, with the known caveat that the
|
|
503
|
-
# caller must not be inside a unit scheduled for restart during the install.
|
|
504
|
-
local run_prefix=""
|
|
505
|
-
if command -v systemd-run >/dev/null 2>&1; then
|
|
506
|
-
run_prefix="systemd-run --user --quiet --scope --unit=maxy-upgrade-terminal-$$.scope --collect --"
|
|
507
|
-
log "Wrapping upgrade terminal in systemd-run --user --scope (Task 647)"
|
|
508
|
-
else
|
|
509
|
-
log "WARNING: systemd-run not available — falling back to setsid -f (terminal may die on maxy-ui restart)"
|
|
510
|
-
fi
|
|
511
|
-
log "Starting ${run_prefix} ${bin} ${flags} ${dispatch_flag} bash -c \"${upgrade_cmd}; exec bash\" on ${target_display} (${label}) reason=upgrade"
|
|
512
|
-
|
|
513
|
-
# $flags stays unquoted for word-splitting (xterm's flags have multiple
|
|
514
|
-
# tokens); bash_wrapper stays quoted so the whole command string reaches
|
|
515
|
-
# bash -c as one argument. $run_prefix is unquoted so its words expand.
|
|
516
|
-
if [ -n "$run_prefix" ]; then
|
|
517
|
-
if [ -n "$flags" ]; then
|
|
518
|
-
DISPLAY="${target_display}" $run_prefix "$bin" $flags "$dispatch_flag" bash -c "$bash_wrapper" >> "$TERMINAL_LOG" 2>&1 &
|
|
519
|
-
else
|
|
520
|
-
DISPLAY="${target_display}" $run_prefix "$bin" "$dispatch_flag" bash -c "$bash_wrapper" >> "$TERMINAL_LOG" 2>&1 &
|
|
521
|
-
fi
|
|
522
|
-
disown 2>/dev/null || true
|
|
523
|
-
else
|
|
524
|
-
if [ -n "$flags" ]; then
|
|
525
|
-
DISPLAY="${target_display}" setsid -f "$bin" $flags "$dispatch_flag" bash -c "$bash_wrapper" >> "$TERMINAL_LOG" 2>&1 || true
|
|
526
|
-
else
|
|
527
|
-
DISPLAY="${target_display}" setsid -f "$bin" "$dispatch_flag" bash -c "$bash_wrapper" >> "$TERMINAL_LOG" 2>&1 || true
|
|
528
|
-
fi
|
|
529
|
-
fi
|
|
530
|
-
|
|
531
|
-
if ! wait_for_terminal; then
|
|
532
|
-
local diag="failed err=\"spawn detached but no terminal PID visible within 1s\" transport=${label} cmd=\"${bin} ${flags} ${dispatch_flag} bash -c '${upgrade_cmd}; exec bash'\" reason=upgrade"
|
|
533
|
-
tlog "$diag"
|
|
534
|
-
echo "[terminal-launch] $diag" >&2
|
|
535
|
-
log "ERROR: upgrade terminal failed to appear in pgrep within 1s on ${target_display} (${label})"
|
|
536
|
-
return 1
|
|
537
|
-
fi
|
|
538
|
-
|
|
539
|
-
local pid
|
|
540
|
-
pid="$(terminal_pid)"
|
|
541
|
-
|
|
542
|
-
# Task 632 invariant: PID presence is necessary but not sufficient.
|
|
543
|
-
if ! check_window_on_display "$target_display"; then
|
|
544
|
-
local observed
|
|
545
|
-
observed="$(_count_windows_on_display "$target_display")"
|
|
546
|
-
local diag="failed err=\"window absent from target display after spawn\" pid=${pid} display=${target_display} observed_windows=${observed} transport=${label} cmd=\"${bin} ${flags} ${dispatch_flag} bash -c '${upgrade_cmd}; exec bash'\" reason=upgrade"
|
|
547
|
-
tlog "$diag"
|
|
548
|
-
echo "[terminal-launch] $diag" >&2
|
|
549
|
-
log "ERROR: upgrade terminal pid=${pid} spawned but no window on ${target_display} (${label})"
|
|
550
|
-
return 1
|
|
551
|
-
fi
|
|
552
|
-
|
|
553
|
-
tlog "started pid=${pid} display=${target_display} cmd=\"${bin} ${flags} ${dispatch_flag} bash -c '${upgrade_cmd}; exec bash'\" transport=${label} windowPresent=true reason=upgrade"
|
|
554
|
-
log "Upgrade terminal ready (${label}) pid=${pid} cmd=\"${upgrade_cmd}\""
|
|
555
|
-
return 0
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
start_terminal_upgrade() {
|
|
559
|
-
preflight_terminal_deps || return 1
|
|
560
|
-
start_terminal_upgrade_on ":99" "vnc"
|
|
561
|
-
}
|
|
562
|
-
|
|
563
194
|
start_chrome_native() {
|
|
564
195
|
discover_native_session
|
|
565
196
|
|
|
@@ -678,34 +309,6 @@ case "${1:-}" in
|
|
|
678
309
|
start_chrome_native
|
|
679
310
|
;;
|
|
680
311
|
|
|
681
|
-
start-terminal)
|
|
682
|
-
start_terminal
|
|
683
|
-
;;
|
|
684
|
-
|
|
685
|
-
start-terminal-native)
|
|
686
|
-
start_terminal_native
|
|
687
|
-
;;
|
|
688
|
-
|
|
689
|
-
start-terminal-upgrade)
|
|
690
|
-
start_terminal_upgrade
|
|
691
|
-
;;
|
|
692
|
-
|
|
693
|
-
kill-terminal)
|
|
694
|
-
pid="$(terminal_pid)"
|
|
695
|
-
kill_terminal_emulators
|
|
696
|
-
if [ -n "$pid" ]; then
|
|
697
|
-
tlog "killed pid=${pid} reason=overlay-close"
|
|
698
|
-
else
|
|
699
|
-
tlog "killed-noop"
|
|
700
|
-
fi
|
|
701
|
-
;;
|
|
702
|
-
|
|
703
|
-
status-terminal)
|
|
704
|
-
# Exit 0 if an operator-launched terminal is running, 1 otherwise.
|
|
705
|
-
# Used by ensureTerminal() in vnc.ts as the in-process liveness probe.
|
|
706
|
-
terminal_alive && exit 0 || exit 1
|
|
707
|
-
;;
|
|
708
|
-
|
|
709
312
|
stop)
|
|
710
313
|
log "Stopping VNC stack"
|
|
711
314
|
kill_stale
|
|
@@ -717,7 +320,7 @@ case "${1:-}" in
|
|
|
717
320
|
;;
|
|
718
321
|
|
|
719
322
|
*)
|
|
720
|
-
echo "Usage: vnc.sh start | stop | start-chrome | start-chrome-native |
|
|
323
|
+
echo "Usage: vnc.sh start | stop | start-chrome | start-chrome-native | status" >&2
|
|
721
324
|
exit 1
|
|
722
325
|
;;
|
|
723
326
|
esac
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
set -g history-limit 50000
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Maxy admin terminal (ttyd + tmux) — persistent PTY for admin UI
|
|
3
|
+
After=default.target
|
|
4
|
+
|
|
5
|
+
[Service]
|
|
6
|
+
Type=simple
|
|
7
|
+
# ttyd binary lives at /usr/local/bin/ttyd — installed from pinned upstream
|
|
8
|
+
# GitHub releases (SHA256-verified) by create-maxy's step 11 (Task 602),
|
|
9
|
+
# because Debian Bookworm's apt does NOT carry a ttyd package. /usr/local/bin
|
|
10
|
+
# is the standard location for binaries installed outside the distro package
|
|
11
|
+
# manager; /usr/bin is reserved for apt-owned files.
|
|
12
|
+
# -p 7681 listen on 127.0.0.1:7681 (same-origin proxy in maxy-ui binds to it)
|
|
13
|
+
# -i 127.0.0.1 reject non-loopback connections at the ttyd layer as well
|
|
14
|
+
# -W writable (allow client → server input bytes)
|
|
15
|
+
# tmux new-session -A -s maxy-pty attach if session exists, create otherwise.
|
|
16
|
+
# Lifetime = user session / device lifetime. Outlives maxy-ui restarts because
|
|
17
|
+
# this unit has no After=maxy-ui.service and no Requires= — independent.
|
|
18
|
+
# -x 200 -y 50 initial geometry; xterm.js fit-addon drives runtime resizes.
|
|
19
|
+
ExecStart=/usr/local/bin/ttyd -p 7681 -i 127.0.0.1 -W tmux new-session -A -s maxy-pty -x 200 -y 50
|
|
20
|
+
Environment=TERM=xterm-256color
|
|
21
|
+
Restart=always
|
|
22
|
+
RestartSec=2
|
|
23
|
+
|
|
24
|
+
[Install]
|
|
25
|
+
WantedBy=default.target
|