@rubytech/create-realagent 1.0.680 → 1.0.681

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.
@@ -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
- # Terminal emulator — gnome-terminal or xterm, spawned on demand for the
15
- # admin Terminal overlay (Task 627). Isomorphic to the
16
- # Chromium pipeline same :99 VNC display, same
17
- # /vnc-viewer.html iframe surface.
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
- # like gnome-terminal does, so the failure mode is not currently reachable
178
- # — but making the invariant visible now means future backend swaps (e.g.
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
- # Terminal emulator lifecycle (Task 627) isomorphic to Chromium's.
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 | start-terminal | start-terminal-native | start-terminal-upgrade | kill-terminal | status-terminal | status" >&2
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