blob: d40cb9cf0886cc861745263157df97a54fba544b [file] [log] [blame]
#!/bin/bash
# Copyright 2020 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#### CATEGORY=Other
### Remotely run emulator
## usage: fx emu-remote [--no-build] [--stream [--no-emu] [--no-turn] [--no-open] [--display DPY] [--port PORT]] HOST [DIR] [-- ARGS]
##
## Connect to HOST, run a build using fx from DIR, fetch the artifacts and
## start the emulator. Alternatively, start the emulator on HOST,
## and open an WebRTC connection to it using local browser.
##
## --no-build do not build, just pull artifacts already present
## --stream stream output from remote emulator using WebRTC instead of fetching artifacts
## --no-emu only tunnel, do not start remote emulator
## --no-turn do not use turn configuration for remote emulator
## --no-open do not open https://web-femu.appspot.com, just run remote emulator
## --display DPY do not start remote virtual display, use DPY instead
## --port PORT port used on local machine to connect with remote emulator over HTTP (default: 8080)
##
## HOST the hostname to connect to
## DIR defaults to ~/fuchsia, the path to the FUCHSIA_DIR on HOST
## ARGS arguments to pass to emulator
##
## HOST and DIR are persisted in the file //.fx-remote-config and are reused as
## defaults in future invocations of any 'fx *-remote' tools.
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/lib/vars.sh || exit $?
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/lib/fx-remote.sh || exit $?
fx-config-read
build=true
stream=false
emu=true
turn=true
open=true
display="xvfb-run"
host=""
dir=""
local_port="8080"
remote_port="8080"
while [[ $# -ne 0 ]]; do
case "$1" in
--help|-h)
fx-command-help
exit 0
;;
--no-build)
build=false
;;
--stream)
stream=true
;;
--no-emu)
emu=false
;;
--no-turn)
turn=false
;;
--no-open)
open=false
;;
--display)
shift
display="DISPLAY=$1"
;;
--port)
shift
local_port=$1
;;
--)
shift
break
;;
*)
if [[ -z "${host}" ]]; then
host="$1"
elif [[ -z "${dir}" ]]; then
dir="$1"
else
fx-error "unexpected argument: '$1'"
exit 1
fi
;;
esac
shift
done
if cached=( $(load_remote_info "$host") ); then
host="${cached[0]}"
dir="${cached[1]}"
fi
if [[ -z "${host}" ]]; then
fx-error "HOST must be specified"
fx-command-help
exit 1
fi
# Use a dedicated ControlPath so script can manage a connection separately from the user's. We
# intentionally do not use %h/%p in the control path because there can only be one forwarding
# session at a time (due to the local forward of 8083).
ssh_base_args=(
"${host}"
-S "~/.ssh/control-fuchsia-fx-remote"
-o ControlMaster=auto
)
ssh_exit() {
ssh "${ssh_base_args[@]}" -O exit > /dev/null 2>&1
wait # for ssh to exit
}
# If there is already control master then exit it. We can't be sure its to the right host and it
# also could be stale.
ssh_exit
# When we exit the script, close the background ssh connection.
trap_exit() {
ssh_exit
exit
}
trap trap_exit EXIT
if [[ -z "${dir}" ]]; then
echo ssh "${ssh_base_args[@]}" ls \~/fuchsia/.jiri_root/bin/fx
if ssh "${ssh_base_args[@]}" ls \~/fuchsia/.jiri_root/bin/fx > /dev/null; then
dir="~/fuchsia"
else
fx-error "failed to find ~/fuchsia on $host, please specify DIR"
fx-command-help
exit 1
fi
fi
emu_targets=(multiboot.bin fuchsia.zbi obj/build/images/fuchsia/fuchsia/fvm.blk)
if "${build}"; then
ssh "${ssh_base_args[@]}" "cd ${dir} && ./.jiri_root/bin/fx build ${emu_targets[@]}" || exit $?
fi
build_dir=$(ssh "${ssh_base_args[@]}" "cd ${dir} && ./.jiri_root/bin/fx get-build-dir")
save_remote_info "$host" "$dir"
# Fetch artifacts and run local emulator when streaming is disabled.
if [[ "${stream}" == "false" ]]; then
build_arch=$(ssh "${ssh_base_args[@]}" "cd ${dir} && ./.jiri_root/bin/fx exec sh -c 'echo \${FUCHSIA_ARCH}'")
mkdir -p out/fetched
printf -v files '%s,' "${emu_targets[@]}"
rsync -z -P "${host}":"${build_dir}/{${files%,}}" out/fetched/
echo >&2 "Build images fetched into out/fetched"
fx-command-exec emu \
-K out/fetched/multiboot.bin \
-z out/fetched/fuchsia.zbi \
-f out/fetched/fvm.blk \
-A "${build_arch}" "$@"
fi
#
# The code below handles the case where output from a remote emulator
# is streamed using WebRTC to a local browser instance.
#
# Check if the port given in argument "$1" is listened by other processes.
#
# Returns true if other process is already listening to this port; otherwise
# returns false.
is_port_used() {
# Print all listening ports and check if $1 is listed.
if [[ "$(uname -s)" == "Darwin" ]]; then
# On macOS we parse output from `netstat`.
netstat -anp tcp 2>/dev/null |
awk '$1 ~ /tcp4/ && $6 == "LISTEN" { \
sub(/.*\./, "", $4); print $4 \
}'
else
# On Linux we parse output from `ss`.
ss -lpn4 2>/dev/null | awk '$1 == "tcp" { \
if (match($5, /^[0-9.]+:[0-9]+$/)) { \
sub(/^.*:/, "", $5); print $5 \
} \
}'
fi | grep -q "^$1$"
}
if is_port_used ${local_port}; then
fx-error "Local port ${local_port} is not available. Please try specifying"\
"a different port using --port flag."
exit 1
fi
echo "Using remote ${host}:${dir}"
wait_and_open() {
timeout=10
waited=0
# Loop until connection is live or timeout expires.
while true
do
# Check if we can connect to the forwarded port.
if curl -sI "http://localhost:${local_port}" > /dev/null; then
open_args=("https://web-femu.appspot.com/?port=${local_port}")
# Launch Chrome browser tab for emulator feed.
if [[ "$(uname -s)" == "Darwin" ]]; then
exec open -a "/Applications/Google Chrome.app" "${open_args[@]}"
else
exec google-chrome "${open_args[@]}"
fi
fi
# Continue waiting if we have not reached our timeout.
if [ $waited -gt 0 ]; then
if [ $waited -ge $timeout ]; then
echo "No emulator after waiting $waited seconds, giving up."
break
fi
echo "Waiting for emulator ($(($timeout-$waited)) seconds left).."
fi
sleep 1
waited=$(($waited+1))
done
}
ssh_args=(
-6 # We want ipv6 binds for the port forwards.
# Requests to the WebRTC service address locally go to the workstation.
-L "${local_port}:localhost:${remote_port}"
-o ExitOnForwardFailure=yes
)
if "${emu}"; then
# If the user requested emulator, then we'll check to see if there's a
# emulator already running and kill it, this prevents most cases where
# signal propagation not make it to "qemu-system".
if ssh "${ssh_base_args[@]}" 'pgrep -u $USER goldfish-webrtc' > /dev/null; then
ssh "${ssh_base_args[@]}" 'pkill -u $USER qemu-system; pkill -u $USER goldfish-webrtc'
fi
emu_args=(-x ${remote_port})
# Add turn configuration if enabled.
if "${turn}"; then
apikey="AIzaSyBl6TgfdN6FKAQ3nK2GvKNcKNjWLeRGVYQ"
emu_args+=(-t "\"curl -s -X POST https://networktraversal.googleapis.com/v1alpha/iceconfig?key=${apikey}\"")
fi
# Starts emulator.
ssh_args+=("cd ${dir} && ${display} ./.jiri_root/bin/fx emu ${emu_args[@]} $@")
# Wait and open page for emulator when ready.
if "${open}"; then
wait_and_open &
fi
else
# Starts nothing, just goes to sleep.
ssh_args+=("-nNT")
fi
# Cleanup after ssh command ends:
# - Terminate all child processes;
# - Close port forwarding if we use ssh multiplexing.
cleanup_processes_and_ports() {
child_pids=$(jobs -p)
if [[ -n "${child_pids}" ]]; then
# Note: child_pids must be expanded to args here.
kill ${child_pids} 2> /dev/null
wait 2> /dev/null
fi
# Cancel port forwarding.
if ssh -O check "${host}" > /dev/null 2>&1; then
ssh -O cancel -L "${local_port}:localhost:${remote_port}" "${host}"
fi
}
trap cleanup_processes_and_ports EXIT
ssh "${ssh_base_args[@]}" "${ssh_args[@]}"