| #!/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/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[@]}" | 
 |  |