Setting the file. One moment.
Launch · Wix Harmony Editor · wix/skills · Skills Docs
ContentsBack to the top of the page Script
scripts/ launch.sh
Shell · 361 lines · 16 KB
# against a virtual framebuffer (Xvfb) - this script will start one
15 # automatically on Linux if $DISPLAY isn't already set and Xvfb is
16 # installed. Xvfb itself is a prerequisite install (see the error message
17 # below); this script does not silently `sudo apt-get install` anything on
18 # your behalf.
19 #
20 # If Wix shows a sign-in page while running under a virtual display, you
21 # need some way to actually see and interact with it. Options, from
22 # simplest to most complete:
23 # - Take a screenshot from Playwright (page.screenshot()) to see the
24 # current page - good enough to tell what state the editor is in, but
25 # not interactive.
26 # - Run a lightweight VNC server against the same DISPLAY, e.g.:
27 # x11vnc -display :99 -localhost -nopw
28 # then forward the VNC port (default 5900) to a machine with a VNC
29 # client. This gives full interactive mouse/keyboard control, but you
30 # are responsible for setting up that port forward for your sandbox.
31 set -euo pipefail
32
33 SCRIPT_DIR = "$( cd "$( dirname " $0 ")" && pwd )"
34 PORT = "${ PW_PORT :- 9333 }"
35 PROFILE = "${ WIX_HARMONY_PROFILE_DIR :- ${ TMPDIR :-/ tmp } / wix-harmony-editor-profile }"
36 EDITOR_URL = " ${1 :- about : blank } "
37
38 umask 077
39 mkdir -p " $PROFILE "
40 chmod 700 " $PROFILE "
41
42 CHROME_PID_FILE = "${ PROFILE }/chrome.pid"
43
44 # True when the process recorded at launch is still alive and was started with THIS profile and
45 # port, so a responding CDP endpoint is provably the Chrome this script launched for this profile.
46 launched_chrome_is_ours () {
47 local pid cmd
48 [[ -f " $CHROME_PID_FILE " ]] || return 1
49 pid = "$( tr -d '[:space:]' < " $CHROME_PID_FILE " 2> /dev/null || true )"
50 [[ " $pid " =~ ^[0-9]+$ ]] || return 1
51 kill -0 " $pid " 2> /dev/null || return 1
52 cmd = "$( ps -p " $pid " -o args= 2> /dev/null || true )"
53 [[ " $cmd " == * "--user-data-dir=${ PROFILE }" * && " $cmd " == * "--remote-debugging-port=${ PORT }" * ]]
54 }
55
56 # If a Wix Harmony Chrome is already up on this port, nothing else to do. Any other responding
57 # CDP endpoint (another Chrome, a Playwright browser) is NOT reused: the copy tools would drive the
58 # wrong browser, profile or site.
59 if curl -fsS -m 2 "http://127.0.0.1:${ PORT }/json/version" > /dev/null 2>&1 ; then
60 if launched_chrome_is_ours ; then
61 echo "Wix Harmony Chrome is already running on local CDP port ${ PORT }."
62 exit 0
63 fi
64 echo "Local CDP port ${ PORT } is already served by a browser this script did not launch for" >&2
65 echo "profile ${ PROFILE }. Not reusing it. Stop that browser, or set PW_PORT to a free port" >&2
66 echo "(and use the same PW_PORT for every script), then re-run." >&2
67 exit 1
68 fi
69
70 # --- OS detection -----------------------------------------------------
71 detect_os () {
72 local uname_s
73 uname_s = "$( uname -s 2> /dev/null || echo unknown)"
74 case " $uname_s " in
75 Darwin )
76 echo macos
77 ;;
78 Linux )
79 if grep -qi microsoft /proc/version 2> /dev/null ; then
80 echo wsl
81 else
82 echo linux
83 fi
84 ;;
85 MINGW *| MSYS *| CYGWIN * )
86 echo windows
87 ;;
88 *)
89 echo unknown
90 ;;
91 esac
92 }
93
94 OS_TYPE = "$( detect_os )"
95
96 # --- Chrome/Chromium discovery -----------------------------------------
97 # Prints the resolved binary path on stdout, or nothing if not found.
98 find_chrome_macos () {
99 local candidates = (
100 "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
101 " $HOME /Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
102 "/Applications/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"
103 "/Applications/Chromium.app/Contents/MacOS/Chromium"
104 )
105 local c
106 for c in "${ candidates [ @ ]}" ; do
107 if [[ -x " $c " ]]; then
108 echo " $c "
109 return 0
110 fi
111 done
112 return 1
113 }
114
115 # Finds the newest Playwright-managed Chromium build under its cache dir,
116 # e.g. ~/.cache/ms-playwright/chromium-1234/chrome-linux64/chrome. This is
117 # the browser `npx playwright install` downloads; it's a reasonable
118 # fallback on Linux boxes with no system Chrome package installed.
119 find_chrome_playwright_linux () {
120 local pw_dir = "${ PLAYWRIGHT_BROWSERS_PATH :- $HOME / . cache / ms-playwright }"
121 [[ -d " $pw_dir " ]] || return 1
122 local newest
123 newest = "$( find " $pw_dir " -maxdepth 1 -type d -iname 'chromium-*' 2> /dev/null | sort -V | tail -1 )"
124 [[ -n " $newest " ]] || return 1
125 local sub
126 for sub in chrome-linux64/chrome chrome-linux/chrome ; do
127 if [[ -x " $newest / $sub " ]]; then
128 echo " $newest / $sub "
129 return 0
130 fi
131 done
132 return 1
133 }
134
135 find_chrome_linux () {
136 local bin
137 for bin in google-chrome google-chrome-stable chromium chromium-browser ; do
138 if command -v " $bin " > /dev/null 2>&1 ; then
139 command -v " $bin "
140 return 0
141 fi
142 done
143 find_chrome_playwright_linux && return 0
144 return 1
145 }
146
147 find_chrome_wsl () {
148 # Prefer a native Linux Chrome/Chromium installed inside the WSL distro,
149 # then fall back to the Windows-side install reachable under /mnt/c.
150 find_chrome_linux && return 0
151 local candidates = (
152 "/mnt/c/Program Files/Google/Chrome/Application/chrome.exe"
153 "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe"
154 )
155 local c
156 for c in "${ candidates [ @ ]}" ; do
157 if [[ -x " $c " ]]; then
158 echo " $c "
159 return 0
160 fi
161 done
162 return 1
163 }
164
165 find_chrome_windows () {
166 local bin
167 for bin in chrome.exe chromium.exe ; do
168 if command -v " $bin " > /dev/null 2>&1 ; then
169 command -v " $bin "
170 return 0
171 fi
172 done
173 local candidates = (
174 "/c/Program Files/Google/Chrome/Application/chrome.exe"
175 "/c/Program Files (x86)/Google/Chrome/Application/chrome.exe"
176 )
177 local c
178 for c in "${ candidates [ @ ]}" ; do
179 if [[ -x " $c " ]]; then
180 echo " $c "
181 return 0
182 fi
183 done
184 return 1
185 }
186
187 CHROME = ""
188 case " $OS_TYPE " in
189 macos ) CHROME = "$( find_chrome_macos || true )" ;;
190 linux ) CHROME = "$( find_chrome_linux || true )" ;;
191 wsl ) CHROME = "$( find_chrome_wsl || true )" ;;
192 windows ) CHROME = "$( find_chrome_windows || true )" ;;
193 esac
194
195 if [[ -z " $CHROME " ]]; then
196 case " $OS_TYPE " in
197 macos )
198 echo "No Google Chrome or Chromium install was found under /Applications." >&2
199 echo "Install Google Chrome (https://www.google.com/chrome/) and retry." >&2
200 ;;
201 linux )
202 echo "No Chrome/Chromium binary was found on PATH (checked: google-chrome," >&2
203 echo "google-chrome-stable, chromium, chromium-browser), and no Playwright-managed" >&2
204 echo "Chromium was found under \$ {PLAYWRIGHT_BROWSERS_PATH:-~/.cache/ms-playwright}." >&2
205 echo "Install one of:" >&2
206 echo " sudo apt-get install -y chromium-browser # Debian/Ubuntu" >&2
207 echo " sudo dnf install -y chromium # Fedora/RHEL" >&2
208 echo "or fetch Playwright's bundled browser: npx playwright install chromium" >&2
209 ;;
210 wsl )
211 echo "No Chrome/Chromium binary was found inside WSL or under /mnt/c/Program Files." >&2
212 echo "Install Chrome/Chromium inside the WSL distro, or install Google Chrome on" >&2
213 echo "the Windows host so it is reachable at /mnt/c/Program Files/Google/Chrome/..." >&2
214 ;;
215 windows )
216 echo "No chrome.exe/chromium.exe was found on PATH or under Program Files." >&2
217 echo "Install Google Chrome (https://www.google.com/chrome/) and retry." >&2
218 ;;
219 *)
220 echo "Could not detect this OS (uname -s reported an unrecognized value) and" >&2
221 echo "no Chrome/Chromium binary could be located automatically." >&2
222 ;;
223 esac
224 exit 1
225 fi
226
227 # --- Headless Linux: provide a virtual display if none exists ----------
228 # Real desktops (macOS, WSL with WSLg, Windows) already have a display.
229 # A headless Linux sandbox does not, but headed Chrome still needs one to
230 # start; Xvfb provides a virtual framebuffer it can render into.
231 if [[ " $OS_TYPE " == "linux" && -z "${ DISPLAY :- }" ]]; then
232 XVFB_DISPLAY_NUM = "${ WIX_HARMONY_XVFB_DISPLAY :- 99 }"
233 XVFB_LOCK = "/tmp/.X${ XVFB_DISPLAY_NUM }-lock"
234 # An X11 lock file's mere existence is not proof a usable Xvfb is behind it: if a prior Xvfb
235 # was killed uncleanly (OOM, `kill -9`, a container restart) the lock file survives with no
236 # process left to serve the display, and Chrome would then fail against a dead display with
237 # only a generic ~20s CDP-timeout error instead of this script's own actionable diagnosis. Per
238 # the standard X11 lock-file convention, the file's content IS the owning process's pid (ASCII
239 # decimal, whitespace-padded) -- check it directly rather than trusting the file's presence.
240 if [[ -e " $XVFB_LOCK " ]]; then
241 xvfb_lock_pid = "$( tr -d '[:space:]' < " $XVFB_LOCK " 2> /dev/null || true )"
242 xvfb_lock_stale = 0
243 if [[ -z " $xvfb_lock_pid " ]]; then
244 xvfb_lock_stale = 1
245 elif [[ ! -e "/proc/ $xvfb_lock_pid " ]]; then
246 # BUG FIX (code review, round 3): `kill -0` alone cannot tell "no such process" (ESRCH,
247 # truly stale) apart from "process alive, owned by a different user" (EPERM) -- both return
248 # the same nonzero exit code. Checking /proc/<pid> directly (this branch only runs under
249 # linux, see the OS_TYPE guard above) answers the ESRCH question on its own: no /proc entry
250 # means the process is genuinely gone.
251 # NOTE (code review, round 6): this is the same EPERM/ESRCH distinction as
252 # `skills/wix-replatform/lib/local-state.js`'s `isProcessAlive` (consolidated there with
253 # `execution-health-check.js` in round 5), reimplemented here in bash since JS and bash
254 # can't share code cheaply. A future fix to one should prompt a check of the other.
255 xvfb_lock_stale = 1
256 elif ! kill -0 " $xvfb_lock_pid " 2> /dev/null ; then
257 # /proc/<pid> exists (a real process is running under that pid) but kill -0 still failed:
258 # this is EPERM, not ESRCH -- a live Xvfb owned by a different UID (e.g. a shared
259 # container/sandbox). Misdiagnosing this as dead and deleting its lock would let a second
260 # Xvfb start and race to bind the same display. Fail loudly instead of guessing.
261 echo "Xvfb lock ${ XVFB_LOCK } names pid ${ xvfb_lock_pid }, which is running but owned by a" >&2
262 echo "different user (kill -0 was denied) -- this looks like a LIVE Xvfb this script does" >&2
263 echo "not have permission to inspect or replace, not a stale lock from a dead process." >&2
264 echo "Remove ${ XVFB_LOCK } yourself once you've confirmed it's safe, or export DISPLAY to" >&2
265 echo "point at a display this script can use." >&2
266 exit 1
267 fi
268 if [[ " $xvfb_lock_stale " -eq 1 ]]; then
269 echo "Removing stale Xvfb lock ${ XVFB_LOCK } (pid ${ xvfb_lock_pid :- unknown } is not running)." >&2
270 # `|| true`: under `set -e`, an `rm -f` that genuinely fails (permission denied, or the
271 # path turning out not to be a plain file) would otherwise abort the script right here with
272 # rm's own generic error, before the verification below ever runs. Trust the follow-up
273 # existence check, not rm's exit code, to decide whether removal actually happened.
274 rm -f " $XVFB_LOCK " || true
275 if [[ -e " $XVFB_LOCK " ]]; then
276 # BUG FIX (code review, round 3): `rm -f` exits 0 even when it silently fails to remove a
277 # path it lacks permission for (e.g. a sticky /tmp directory owned by another user, or the
278 # path turning out not to be a plain file at all) -- verify removal actually happened
279 # rather than trusting rm's exit code. Falling through here with the lock still present
280 # would skip starting a fresh Xvfb below and export DISPLAY at a dead display.
281 echo "Failed to remove stale Xvfb lock ${ XVFB_LOCK } (no permission, or it is not a" >&2
282 echo "plain file?). Remove it manually and re-run, or export DISPLAY yourself." >&2
283 exit 1
284 fi
285 fi
286 fi
287 if [[ ! -e " $XVFB_LOCK " ]]; then
288 if ! command -v Xvfb > /dev/null 2>&1 ; then
289 echo "No \$ DISPLAY is set and Xvfb is not installed, so headed Chrome cannot" >&2
290 echo "start on this headless Linux machine. Install it, e.g.:" >&2
291 echo " sudo apt-get install -y xvfb # Debian/Ubuntu" >&2
292 echo " sudo dnf install -y xorg-x11-server-Xvfb # Fedora/RHEL" >&2
293 echo "then re-run this script (or export DISPLAY yourself if you already have" >&2
294 echo "a virtual/real display running)." >&2
295 exit 1
296 fi
297 nohup Xvfb ":${ XVFB_DISPLAY_NUM }" -screen 0 1280x800x24 \
298 > "${ PROFILE }/xvfb.log" 2>&1 &
299 xvfb_pid = $!
300 # An Xvfb that exits at once may already be reaped, and a bare `disown` then fails with "no
301 # such job", which set -e turns into an exit before the liveness check below can explain why.
302 disown " $xvfb_pid " 2> /dev/null || true
303 # A backgrounded Xvfb takes a moment to create its lock file and start serving the display;
304 # `sleep 1` then unconditionally exporting DISPLAY assumed it always makes that window, with no
305 # check that it actually came up. If Xvfb itself failed fast (missing X extension, a stale
306 # /tmp permission problem, port/display already in some other conflicting state), the old code
307 # would still export DISPLAY and hand Chrome a dead display -- Chrome would then fail ~20s
308 # later against a generic CDP-timeout, with nothing pointing back at Xvfb as the real cause.
309 # Poll for the lock file actually appearing (proof Xvfb bound the display), or the backgrounded
310 # pid dying (proof it won't), across a short retry window -- the same diagnostic quality as the
311 # stale-lock check above this block, rather than a blind fixed sleep.
312 xvfb_ready = 0
313 for _ in $( seq 1 30 ); do
314 if [[ -e " $XVFB_LOCK " ]]; then
315 # The lock appearing is not proof Xvfb stays up: it can create the lock and then exit.
316 # Re-check the pid after a short settle before trusting the display.
317 sleep 0.3
318 if kill -0 " $xvfb_pid " 2> /dev/null ; then
319 xvfb_ready = 1
320 fi
321 break
322 fi
323 if ! kill -0 " $xvfb_pid " 2> /dev/null ; then
324 break
325 fi
326 sleep 0.1
327 done
328 if [[ " $xvfb_ready " -ne 1 ]]; then
329 echo "Xvfb did not come up on display :${ XVFB_DISPLAY_NUM } within 3s (lock file" >&2
330 echo "${ XVFB_LOCK } never appeared). Check ${ PROFILE }/xvfb.log for its own error output," >&2
331 echo "e.g. a missing X extension, a permissions problem under /tmp, or the display" >&2
332 echo "number already being used by something else that isn't Xvfb itself." >&2
333 exit 1
334 fi
335 fi
336 export DISPLAY = ":${ XVFB_DISPLAY_NUM }"
337 echo "No \$ DISPLAY was set; using virtual display ${ DISPLAY } (Xvfb) for headed Chrome." >&2
338 echo "If Wix shows a sign-in page, you'll need a way to see/interact with this" >&2
339 echo "virtual display - e.g. 'x11vnc -display ${ DISPLAY } -localhost -nopw' plus a" >&2
340 echo "forwarded VNC port, or take Playwright screenshots to inspect page state." >&2
341 fi
342
343 nohup " $CHROME " \
344 --user-data-dir= " $PROFILE " \
345 --remote-debugging-address=127.0.0.1 \
346 --remote-debugging-port= " $PORT " \
347 --no-first-run \
348 --no-default-browser-check \
349 " $EDITOR_URL " > "${ PROFILE }/chrome.log" 2>&1 &
350 echo " $! " > " $CHROME_PID_FILE "
351
352 for _ in $( seq 1 40 ); do
353 if curl -fsS -m 2 "http://127.0.0.1:${ PORT }/json/version" > /dev/null 2>&1 ; then
354 echo "Wix Harmony Chrome is ready on local CDP port ${ PORT }."
355 exit 0
356 fi
357 sleep 0.5
358 done
359
360 echo "Chrome did not expose its local CDP endpoint. Check ${ PROFILE }/chrome.log." >&2
361 exit 1