@rubytech/create-maxy-code 0.1.84 → 0.1.85
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/package.json
CHANGED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# setup-tunnel.sh OAuth display precedence — three-case test.
|
|
3
|
+
#
|
|
4
|
+
# Verifies that the chromium spawn in Step 1 (OAuth login) ALWAYS uses the
|
|
5
|
+
# brand-declared VNC display from brand.json, never the PTY-inherited
|
|
6
|
+
# DISPLAY env var. Covers the regression filed as task 221: PTY admin
|
|
7
|
+
# agent inherits DISPLAY=:0 from systemd, which would route the OAuth
|
|
8
|
+
# Authorize page onto the system display rather than the operator-visible
|
|
9
|
+
# brand VNC.
|
|
10
|
+
#
|
|
11
|
+
# Cases:
|
|
12
|
+
# 1. Positive — brand.json vncDisplay=100, DISPLAY=:0 in env
|
|
13
|
+
# → stream log shows `step=browser-spawn result=ok display=:100`.
|
|
14
|
+
# 2. Negative — brand.json missing vncDisplay field, DISPLAY=:0 in env
|
|
15
|
+
# → exit non-zero with `result=error reason=brand-vnc-display-unresolved`.
|
|
16
|
+
# 3. Negative — brand.json file absent (MAXY_PLATFORM_ROOT set, file missing)
|
|
17
|
+
# → exit non-zero with same reason.
|
|
18
|
+
#
|
|
19
|
+
# Mocks cloudflared (echo URL + sleep), systemd-run (no-op success), and
|
|
20
|
+
# the brand chromium binary via PATH override. SETUP_TUNNEL_LOGIN_TIMEOUT=1
|
|
21
|
+
# short-circuits the cert-poll so the positive case exits in ~1 s.
|
|
22
|
+
#
|
|
23
|
+
# Run: bash setup-tunnel-display-precedence.test.sh
|
|
24
|
+
|
|
25
|
+
set -uo pipefail
|
|
26
|
+
|
|
27
|
+
TEST_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
|
|
28
|
+
SCRIPT_PATH="$(dirname "${TEST_DIR}")/setup-tunnel.sh"
|
|
29
|
+
|
|
30
|
+
if [ ! -x "${SCRIPT_PATH}" ] && [ ! -r "${SCRIPT_PATH}" ]; then
|
|
31
|
+
echo "FAIL: setup-tunnel.sh not found at ${SCRIPT_PATH}"
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
36
|
+
echo "SKIP: jq is required for brand.json vncDisplay parsing in setup-tunnel.sh"
|
|
37
|
+
exit 0
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
PASS=0
|
|
41
|
+
FAIL=0
|
|
42
|
+
|
|
43
|
+
run_case() {
|
|
44
|
+
local case_name="$1"
|
|
45
|
+
local brand_json_content="$2" # empty means "do not write the file"
|
|
46
|
+
local expected_exit="$3" # "nonzero" or a specific code; we only check non-zero vs result via grep
|
|
47
|
+
local expected_grep="$4" # pattern to grep in stream log
|
|
48
|
+
|
|
49
|
+
WORK="$(mktemp -d -t maxy-display-test.XXXXXX)"
|
|
50
|
+
trap 'rm -rf "$WORK"' RETURN
|
|
51
|
+
|
|
52
|
+
# Fake $HOME for ${CFG_DIR}=${HOME}/.${BRAND}/cloudflared paths
|
|
53
|
+
export HOME="${WORK}/home"
|
|
54
|
+
mkdir -p "${HOME}/.testbrand/cloudflared"
|
|
55
|
+
# Ensure default OAuth cert path also empty so pre-flight cert-promotion is skipped
|
|
56
|
+
mkdir -p "${HOME}/.cloudflared"
|
|
57
|
+
|
|
58
|
+
# MAXY_PLATFORM_ROOT layout
|
|
59
|
+
export MAXY_PLATFORM_ROOT="${WORK}/platform"
|
|
60
|
+
mkdir -p "${MAXY_PLATFORM_ROOT}/config"
|
|
61
|
+
# Path INSIDE the file is the chromium binary; the file itself does not
|
|
62
|
+
# need to be executable. The script's check is `[ -x "${path}" ]` which
|
|
63
|
+
# requires an absolute path — `command -v true` returns the shell
|
|
64
|
+
# builtin name, not a path. Pick the first existing absolute file.
|
|
65
|
+
if [ -x /usr/bin/true ]; then TRUE_BIN=/usr/bin/true
|
|
66
|
+
elif [ -x /bin/true ]; then TRUE_BIN=/bin/true
|
|
67
|
+
else
|
|
68
|
+
echo "SKIP: no /usr/bin/true or /bin/true available for chromium-binary mock"
|
|
69
|
+
return 0
|
|
70
|
+
fi
|
|
71
|
+
printf '%s\n' "${TRUE_BIN}" > "${MAXY_PLATFORM_ROOT}/config/chromium-binary.path"
|
|
72
|
+
|
|
73
|
+
if [ -n "${brand_json_content}" ]; then
|
|
74
|
+
printf '%s' "${brand_json_content}" > "${MAXY_PLATFORM_ROOT}/config/brand.json"
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
export STREAM_LOG_PATH="${WORK}/stream.log"
|
|
78
|
+
: > "${STREAM_LOG_PATH}"
|
|
79
|
+
|
|
80
|
+
# PATH-override mocks
|
|
81
|
+
PATH_DIR="${WORK}/bin"
|
|
82
|
+
mkdir -p "${PATH_DIR}"
|
|
83
|
+
|
|
84
|
+
cat > "${PATH_DIR}/cloudflared" <<'MOCK'
|
|
85
|
+
#!/usr/bin/env bash
|
|
86
|
+
# Mock cloudflared: emit a URL once, then sleep long enough that the
|
|
87
|
+
# parent's cert-poll timeout fires.
|
|
88
|
+
echo "Please open the following URL in your browser:"
|
|
89
|
+
echo "https://dash.cloudflare.com/argotunnel?aud=test&callback=https%3A%2F%2Flogin.cloudflareaccess.org%2F"
|
|
90
|
+
sleep 30
|
|
91
|
+
MOCK
|
|
92
|
+
chmod +x "${PATH_DIR}/cloudflared"
|
|
93
|
+
|
|
94
|
+
cat > "${PATH_DIR}/systemd-run" <<'MOCK'
|
|
95
|
+
#!/usr/bin/env bash
|
|
96
|
+
# Mock systemd-run: succeed without launching anything.
|
|
97
|
+
exit 0
|
|
98
|
+
MOCK
|
|
99
|
+
chmod +x "${PATH_DIR}/systemd-run"
|
|
100
|
+
|
|
101
|
+
cat > "${PATH_DIR}/systemctl" <<'MOCK'
|
|
102
|
+
#!/usr/bin/env bash
|
|
103
|
+
# Mock systemctl: stop returns 5 (unit auto-collected) on the happy
|
|
104
|
+
# `--user stop` path; list-unit-files prints nothing.
|
|
105
|
+
case "${*}" in
|
|
106
|
+
*"stop"*) exit 5 ;;
|
|
107
|
+
*) exit 0 ;;
|
|
108
|
+
esac
|
|
109
|
+
MOCK
|
|
110
|
+
chmod +x "${PATH_DIR}/systemctl"
|
|
111
|
+
|
|
112
|
+
# stdbuf shim — macOS lacks GNU coreutils stdbuf. The script invokes
|
|
113
|
+
# `stdbuf -oL -eL cloudflared ...` purely for line-buffering of the
|
|
114
|
+
# cloudflared output; in this test the output is one short URL line so
|
|
115
|
+
# buffering doesn't matter. The shim strips the -o/-e flags and execs
|
|
116
|
+
# the rest of argv directly.
|
|
117
|
+
cat > "${PATH_DIR}/stdbuf" <<'MOCK'
|
|
118
|
+
#!/usr/bin/env bash
|
|
119
|
+
while [ $# -gt 0 ]; do
|
|
120
|
+
case "$1" in
|
|
121
|
+
-i*|-o*|-e*) shift ;;
|
|
122
|
+
--) shift; break ;;
|
|
123
|
+
*) break ;;
|
|
124
|
+
esac
|
|
125
|
+
done
|
|
126
|
+
exec "$@"
|
|
127
|
+
MOCK
|
|
128
|
+
chmod +x "${PATH_DIR}/stdbuf"
|
|
129
|
+
|
|
130
|
+
# Force DISPLAY=:0 to simulate PTY admin agent inheriting from systemd.
|
|
131
|
+
export DISPLAY=":0"
|
|
132
|
+
# Short-circuit cert-poll so positive case finishes in ~1 s.
|
|
133
|
+
export SETUP_TUNNEL_LOGIN_TIMEOUT=1
|
|
134
|
+
# Required by Step 2+3, but Step 1 cert-poll timeout exits before we
|
|
135
|
+
# get there; still set them so any drift surfaces a different failure.
|
|
136
|
+
export TUNNEL_NAME="setup-tunnel-display-test"
|
|
137
|
+
|
|
138
|
+
PATH="${PATH_DIR}:${PATH}" "${SCRIPT_PATH}" \
|
|
139
|
+
testbrand 19200 admin.test.bot \
|
|
140
|
+
> "${WORK}/stdout.log" 2> "${WORK}/stderr.log" &
|
|
141
|
+
SCRIPT_PID=$!
|
|
142
|
+
|
|
143
|
+
# Bound the wait — kill after 30 s as a safety net.
|
|
144
|
+
for _ in $(seq 1 60); do
|
|
145
|
+
if ! kill -0 "${SCRIPT_PID}" 2>/dev/null; then break; fi
|
|
146
|
+
sleep 0.5
|
|
147
|
+
done
|
|
148
|
+
if kill -0 "${SCRIPT_PID}" 2>/dev/null; then
|
|
149
|
+
kill -TERM "${SCRIPT_PID}" 2>/dev/null || true
|
|
150
|
+
sleep 1
|
|
151
|
+
kill -KILL "${SCRIPT_PID}" 2>/dev/null || true
|
|
152
|
+
fi
|
|
153
|
+
wait "${SCRIPT_PID}" 2>/dev/null
|
|
154
|
+
ACTUAL_EXIT=$?
|
|
155
|
+
|
|
156
|
+
if grep -qE "${expected_grep}" "${STREAM_LOG_PATH}"; then
|
|
157
|
+
echo "PASS: ${case_name}"
|
|
158
|
+
PASS=$((PASS + 1))
|
|
159
|
+
else
|
|
160
|
+
echo "FAIL: ${case_name}"
|
|
161
|
+
echo " expected stream log to match: ${expected_grep}"
|
|
162
|
+
echo " exit=${ACTUAL_EXIT}"
|
|
163
|
+
echo " --- stream log ---"
|
|
164
|
+
sed 's/^/ /' "${STREAM_LOG_PATH}" || true
|
|
165
|
+
echo " --- stderr ---"
|
|
166
|
+
sed 's/^/ /' "${WORK}/stderr.log" || true
|
|
167
|
+
FAIL=$((FAIL + 1))
|
|
168
|
+
fi
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# Case 1 — Positive. vncDisplay=100, DISPLAY=:0 inherited.
|
|
172
|
+
# Expect: chromium-spawn phase line carries display=:100, NOT :0.
|
|
173
|
+
run_case "positive — brand.json vncDisplay=100 overrides inherited DISPLAY=:0" \
|
|
174
|
+
'{"vncDisplay": 100}' \
|
|
175
|
+
"nonzero" \
|
|
176
|
+
'step=browser-spawn result=ok.*display=:100'
|
|
177
|
+
|
|
178
|
+
# Case 2 — Negative. brand.json present but no vncDisplay field.
|
|
179
|
+
# Expect: hard-fail with reason=brand-vnc-display-unresolved.
|
|
180
|
+
run_case "negative — brand.json missing vncDisplay → hard-fail" \
|
|
181
|
+
'{"port": 19200}' \
|
|
182
|
+
"nonzero" \
|
|
183
|
+
'step=browser-spawn result=error reason=brand-vnc-display-unresolved'
|
|
184
|
+
|
|
185
|
+
# Case 3 — Negative. brand.json file absent altogether.
|
|
186
|
+
# Expect: same hard-fail.
|
|
187
|
+
run_case "negative — brand.json file absent → hard-fail" \
|
|
188
|
+
'' \
|
|
189
|
+
"nonzero" \
|
|
190
|
+
'step=browser-spawn result=error reason=brand-vnc-display-unresolved'
|
|
191
|
+
|
|
192
|
+
echo ""
|
|
193
|
+
echo "Summary: ${PASS} passed, ${FAIL} failed"
|
|
194
|
+
[ "${FAIL}" -eq 0 ]
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
#
|
|
17
17
|
# Step 1 owns the browser-spawn deterministically. The wrapper extracts
|
|
18
18
|
# cloudflared's argotunnel URL from its stdout (regex below) and the moment
|
|
19
|
-
# the URL surfaces, mechanically opens it on the
|
|
20
|
-
# `DISPLAY=${
|
|
19
|
+
# the URL surfaces, mechanically opens it on the brand's VNC chromium via
|
|
20
|
+
# `DISPLAY=${BRAND_VNC_DISPLAY} /usr/bin/chromium <url> &` — no reliance on
|
|
21
21
|
# cloudflared's optimistic xdg-open, no CDP auto-click, no DOM matcher.
|
|
22
22
|
# The operator clicks the zone row + Authorize on the VNC themselves.
|
|
23
23
|
# This is the wrappers-faithfully-relay-third-party-cli doctrine;
|
|
@@ -99,17 +99,22 @@ phase_line setup-tunnel step=start brand="${BRAND}" port="${PORT}" hostnames="${
|
|
|
99
99
|
CFG_DIR="${HOME}/.${BRAND}/cloudflared"
|
|
100
100
|
mkdir -p "${CFG_DIR}"
|
|
101
101
|
|
|
102
|
-
# Per-brand X display. Each brand owns its own Xtigervnc display
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
# device.
|
|
106
|
-
# (
|
|
107
|
-
#
|
|
102
|
+
# Per-brand X display. Each brand owns its own Xtigervnc display so the
|
|
103
|
+
# Chromium tab opened to drive the OAuth Authorize click lands in THIS
|
|
104
|
+
# brand's isolated VNC, not whichever brand booted first on a shared
|
|
105
|
+
# device. brand.json `.vncDisplay` is the sole source of truth; the
|
|
106
|
+
# PTY-agent's inherited DISPLAY (typically `:0` from the systemd unit) is
|
|
107
|
+
# the WRONG surface and is never consulted. If brand.json is absent or
|
|
108
|
+
# does not declare a numeric `vncDisplay`, the chromium-spawn site in
|
|
109
|
+
# Step 1 hard-fails with `step=browser-spawn result=error
|
|
110
|
+
# reason=brand-vnc-display-unresolved` rather than silently falling back
|
|
111
|
+
# to a system display. Re-runs that skip Step 1 (cert.pem already in the
|
|
112
|
+
# brand path) tolerate an unresolved display because no chromium spawns.
|
|
108
113
|
SETUP_TUNNEL_BRAND_JSON=""
|
|
109
114
|
if [ -n "${MAXY_PLATFORM_ROOT:-}" ] && [ -f "${MAXY_PLATFORM_ROOT}/config/brand.json" ]; then
|
|
110
115
|
SETUP_TUNNEL_BRAND_JSON="${MAXY_PLATFORM_ROOT}/config/brand.json"
|
|
111
116
|
fi
|
|
112
|
-
BRAND_VNC_DISPLAY="
|
|
117
|
+
BRAND_VNC_DISPLAY=""
|
|
113
118
|
if [ -n "${SETUP_TUNNEL_BRAND_JSON}" ] && command -v jq >/dev/null 2>&1; then
|
|
114
119
|
_bvd=$(jq -r '.vncDisplay // empty' "${SETUP_TUNNEL_BRAND_JSON}" 2>/dev/null || true)
|
|
115
120
|
if [ -n "${_bvd}" ] && [ "${_bvd}" -eq "${_bvd}" ] 2>/dev/null; then
|
|
@@ -185,8 +190,8 @@ fi
|
|
|
185
190
|
# $STREAM_LOG_PATH with prefix [script:setup-tunnel:cloudflared]
|
|
186
191
|
# (the chat-surface namespace — see _stream-log.sh header).
|
|
187
192
|
# 2. Extract the authorize URL with a tolerant regex as it streams.
|
|
188
|
-
# 3. The instant the URL is extracted, mechanically open it on the
|
|
189
|
-
# VNC chromium via `DISPLAY=${
|
|
193
|
+
# 3. The instant the URL is extracted, mechanically open it on the brand
|
|
194
|
+
# VNC chromium via `DISPLAY=${BRAND_VNC_DISPLAY} /usr/bin/chromium <url> &`.
|
|
190
195
|
# Fire-and-forget — chromium is already running with CDP enabled at
|
|
191
196
|
# :9222, so the invocation IPCs the URL into the running instance as
|
|
192
197
|
# a new tab. The spawn is intentionally NOT tracked in the EXIT trap:
|
|
@@ -237,7 +242,22 @@ if [ ! -f "${CFG_DIR}/cert.pem" ] && [ -f "${HOME}/.cloudflared/cert.pem" ]; the
|
|
|
237
242
|
fi
|
|
238
243
|
|
|
239
244
|
if [ ! -f "${CFG_DIR}/cert.pem" ]; then
|
|
240
|
-
|
|
245
|
+
# Brand owns the OAuth chromium display unconditionally. If brand.json
|
|
246
|
+
# didn't declare a numeric vncDisplay, the install is broken — refuse to
|
|
247
|
+
# spawn chromium on whatever DISPLAY the PTY agent happened to inherit
|
|
248
|
+
# (typically :0 from the systemd unit, which is NOT the operator-visible
|
|
249
|
+
# brand VNC). Doctrine block at top of file: brand wins, every time.
|
|
250
|
+
if [ -z "${BRAND_VNC_DISPLAY}" ]; then
|
|
251
|
+
phase_line setup-tunnel step=browser-spawn result=error \
|
|
252
|
+
reason=brand-vnc-display-unresolved \
|
|
253
|
+
brand_json="${SETUP_TUNNEL_BRAND_JSON:-unset}"
|
|
254
|
+
echo "ERROR: setup-tunnel.sh: brand VNC display could not be resolved." >&2
|
|
255
|
+
echo " brand.json: ${SETUP_TUNNEL_BRAND_JSON:-unset}" >&2
|
|
256
|
+
echo " Required: brand.json must declare numeric 'vncDisplay'." >&2
|
|
257
|
+
exit 1
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
phase_line setup-tunnel step=oauth-login cert_path="${CFG_DIR}/cert.pem" display="${BRAND_VNC_DISPLAY}"
|
|
241
261
|
|
|
242
262
|
URL_FILE="$(mktemp -t maxy-setup-tunnel-url.XXXXXX)"
|
|
243
263
|
LAST_LINE_FILE="$(mktemp -t maxy-setup-tunnel-last.XXXXXX)"
|
|
@@ -267,7 +287,7 @@ if [ ! -f "${CFG_DIR}/cert.pem" ]; then
|
|
|
267
287
|
# extracted as it streams. The subshell holds the whole pipeline so
|
|
268
288
|
# PIPESTATUS[0] (cloudflared's exit code) is reachable later.
|
|
269
289
|
(
|
|
270
|
-
DISPLAY="${
|
|
290
|
+
DISPLAY="${BRAND_VNC_DISPLAY}" stdbuf -oL -eL cloudflared \
|
|
271
291
|
--origincert "${CFG_DIR}/cert.pem" tunnel login 2>&1 |
|
|
272
292
|
while IFS= read -r line; do
|
|
273
293
|
ts="$(stream_log_ts)"
|
|
@@ -342,7 +362,7 @@ if [ ! -f "${CFG_DIR}/cert.pem" ]; then
|
|
|
342
362
|
# `/usr/bin/chromium` here would re-introduce the AppArmor SingletonLock
|
|
343
363
|
# failure on the laptop.
|
|
344
364
|
CHROMIUM_UNIT="maxy-oauth-chromium-${BRAND}-$$.service"
|
|
345
|
-
CHROMIUM_LAUNCH_DISPLAY="${
|
|
365
|
+
CHROMIUM_LAUNCH_DISPLAY="${BRAND_VNC_DISPLAY}"
|
|
346
366
|
CHROMIUM_SPAWN_ERR="$(mktemp -t maxy-oauth-chromium-err.XXXXXX)"
|
|
347
367
|
if systemd-run --user \
|
|
348
368
|
--unit="${CHROMIUM_UNIT}" \
|
|
@@ -100,6 +100,15 @@ The YAML and JSON rendering live in a pure Node helper at
|
|
|
100
100
|
|
|
101
101
|
The agent invokes the script directly via the Bash tool — there is no form, no endpoint relay. Stream the script's stdout into chat verbatim as it arrives; if an `ACTION REQUIRED` block appears, quote it exactly — the operator needs the specific dashboard instructions it contains.
|
|
102
102
|
|
|
103
|
+
### Reading the stream log before narrating progress
|
|
104
|
+
|
|
105
|
+
Chat narration is contingent on phase-line evidence, not on the script's overall `result=ok`. The script tees structured phase lines into the stream log as it runs (`[setup-tunnel] step=<phase> <key=value …>`). The agent reads them and narrates from them:
|
|
106
|
+
|
|
107
|
+
- **Before claiming "the authorise page is open on the device's screen"** (or any equivalent), the agent must have seen `step=browser-spawn result=ok display=:<N>` in the stream log AND must have confirmed `:<N>` matches the brand VNC display recorded in `brand.json` `.vncDisplay` (the chat surface the operator watches). If the operator is on a Pi where the admin chat shares brand VNC `:100`, then `display=:100` is the only value consistent with "page is open on the device". Any other value means the page is on a screen the operator cannot see.
|
|
108
|
+
- **If `display=:<N>` does not match `vncDisplay`,** the agent surfaces the literal `OAUTH_URL: <url>` line from the script's stdout and asks the operator to open it in their shared browser themselves. No claim that the spawn succeeded. The script's chromium spawn is treated as best-effort that may have landed on the wrong screen.
|
|
109
|
+
- **If `step=browser-spawn result=error`,** the agent restates the literal `reason=…` and any `stderr=…` fields and stops, per the discipline rule below. The most common error in this site is `reason=brand-vnc-display-unresolved`, which means `brand.json` does not declare a numeric `vncDisplay` and the installer has to be re-run to fix the brand manifest before another setup attempt makes sense.
|
|
110
|
+
- **The script's `result=ok` flag alone is not evidence that the page is operator-visible.** The display field is the gate. Read it, compare it to brand state, then narrate.
|
|
111
|
+
|
|
103
112
|
### When the script exits non-zero
|
|
104
113
|
|
|
105
114
|
Relay the script's stdout to the operator verbatim, name the literal exit code, and cite `references/reset-guide.md` for the next action. Do not attempt a second invocation under a different flag combination, a Playwright-driven dashboard inspection, or an alternative `cloudflared` command sequence. The discipline rule below applies.
|