| #!/bin/bash | 
 | # Copyright 2017 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 | 
 | ### serve from a remote workstation | 
 |  | 
 | ## usage: fx serve-remote [--no-serve] [--tunnel-ports=NNNN,..] HOSTNAME [REMOTE-PATH] | 
 | ## | 
 | ## HOSTNAME     the hostname of the workstation you want to serve from | 
 | ## REMOTE-PATH  defaults to ~/fuchsia. The path on the to FUCHSIA_DIR on the workstation. | 
 | ## | 
 | ##  --no-serve                    only tunnel, do not start a package server | 
 | ##  --no-check-ssh-keys  do not verify that the default SSH | 
 | ##                                credentials are the same before serving. | 
 | ##  --[no-]persist                enable or disable persistence of repository metadata. | 
 | ##                                Disabled by default. | 
 | ##  --tunnel-ports=NNN1,NNN2,NNN3 comma-separated list of additional ports to | 
 | ##                                tunnel. This is used for e2e tests running on | 
 | ##                                remote host that needs to reach the local device. | 
 | ## | 
 | ## 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 $? | 
 | source "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/lib/verify-default-keys.sh || exit $? | 
 | source "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/lib/updates.sh || exit $? | 
 | fx-config-read | 
 |  | 
 | fx-standard-switches "$@" | 
 | set -- "${FX_ARGV[@]}" | 
 |  | 
 | serve=true | 
 | check_ssh_keys=true | 
 | verbose=false | 
 | host="" | 
 | dir="" | 
 | serve_persist_arg="--no-persist" | 
 | package_server_port="8083" | 
 | has_tunnel_ports=false | 
 |  | 
 | while [[ $# -ne 0 ]]; do | 
 |   case "$1" in | 
 |   --help|-h) | 
 |       fx-command-help | 
 |       exit 0 | 
 |       ;; | 
 |   --no-serve) | 
 |     serve=false | 
 |     ;; | 
 |   --no-check-ssh-keys) | 
 |     check_ssh_keys=false | 
 |     ;; | 
 |   --no-persist) | 
 |     ;; | 
 |   --persist) | 
 |     serve_persist_arg="--persist" | 
 |     ;; | 
 |   -v) | 
 |     verbose=true | 
 |     ;; | 
 |   --repo-port) | 
 |     if [[ $# -lt 2 ]]; then | 
 |       fx-error Invalid syntax | 
 |       fx-command-help | 
 |       exit 1 | 
 |     fi | 
 |     package_server_port=$2 | 
 |     shift | 
 |     ;; | 
 |   --tunnel-ports) | 
 |     if [[ $# -lt 2 ]]; then | 
 |       fx-error Invalid syntax | 
 |       fx-command-help | 
 |       exit 1 | 
 |     fi | 
 |     has_tunnel_ports=true | 
 |     # Split comma-separated list of ports to an array. | 
 |     tunnel_ports=(${2//,/ }) | 
 |     shift | 
 |     ;; | 
 |   -*) | 
 |     fx-error "Unknown flag: $1" | 
 |     fx-command-help | 
 |     exit 1 | 
 |     ;; | 
 |   *) | 
 |     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 "HOSTNAME must be specified" | 
 |   fx-command-help | 
 |   exit 1 | 
 | fi | 
 |  | 
 | if "${serve}"; then | 
 |   if [[ -z "${dir}" ]]; then | 
 |     if ssh "${host}" ls \~/fuchsia/.jiri_root/bin/fx > /dev/null; then | 
 |       dir="~/fuchsia" | 
 |     else | 
 |       fx-error "failed to find ~/fuchsia on $host, please specify REMOTE-DIR" | 
 |       fx-command-help | 
 |       exit 1 | 
 |     fi | 
 |   fi | 
 | fi | 
 |  | 
 | save_remote_info "${host}" "${dir}" | 
 |  | 
 | fx-export-device-address | 
 | if [[ $? -ne 0 || -z "${FX_DEVICE_ADDR}" ]]; then | 
 |   fx-error "unable to discover device. Is the target up?" | 
 |   exit 1 | 
 | fi | 
 |  | 
 | if [[ -z "${FX_SSH_PORT}" ]]; then | 
 |   FX_SSH_PORT=22 | 
 | fi | 
 |  | 
 | echo "Using remote ${host}:${dir}" | 
 | echo "Using target device ${FX_DEVICE_NAME} (${FX_SSH_ADDR}:${FX_SSH_PORT})" | 
 |  | 
 |  | 
 | # Use a dedicated ControlPath so script can manage a connection seperately 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 the package server port). | 
 | ssh_base_args=( | 
 |   -S "${HOME}/.ssh/control-fuchsia-fx-remote" | 
 |   -o "ControlMaster=auto" | 
 | ) | 
 |  | 
 | ssh_exit() { | 
 |   # Failure to end existing multiplexed SSH connections is acceptable. | 
 |   ssh "${ssh_base_args[@]}" "${host}" -O exit > /dev/null 2>&1 || true | 
 |   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 | 
 |  | 
 | ssh_tunnel_args=( | 
 |   -6 # We want ipv6 binds for the port forwards | 
 |   -L "\*:${package_server_port}:localhost:${package_server_port}"       # fx serve | 
 |   -R "8022:${FX_SSH_ADDR}:${FX_SSH_PORT}"                               # fx shell | 
 |   -o ExitOnForwardFailure=yes | 
 |   # Match google default server timeout so in spotty network situations the client doesn't timeout | 
 |   # before server (and leave the server process still holding on to tunneling port). | 
 |   -o ServerAliveInterval=30 | 
 |   -o ServerAliveCountMax=20 | 
 | ) | 
 |  | 
 | # This is a rudimentary assumption that the device is reachable at FX_SSH_ADDR | 
 | # iff it is listening on the default SSH port, which is often true, but in no | 
 | # way guaranteed to be true. A better approach here would be to adjust to later | 
 | # perform these forwards inside the device fowrarded link rather than at the | 
 | # host link level. | 
 | if [[ "${FX_SSH_PORT}" == 22 ]]; then | 
 |   ssh_tunnel_args+=( | 
 |     -R "2345:${FX_SSH_ADDR}:2345"             # fx debug | 
 |     -R "8007:${FX_SSH_ADDR}:8007"             # Google-specific | 
 |     -R "8443:${FX_SSH_ADDR}:8443"             # Google-specific | 
 |     -R "9080:${FX_SSH_ADDR}:80"               # SL4F_HTTP_PORT | 
 |   ) | 
 | else | 
 |   echo >&2 "Note: tunnelled targets will not receive forwards for SL4F or debug" | 
 | fi | 
 |  | 
 | # Add additional ports to tunnel if specified. | 
 | if "${has_tunnel_ports}"; then | 
 |   for port in "${tunnel_ports[@]}"; do | 
 |     ssh_tunnel_args+=(-R "${port}:${FX_SSH_ADDR}:${port}") | 
 |   done | 
 | fi | 
 |  | 
 | # Start tunneling session in background. It's started seperately from the command invocations below | 
 | # to allow the script to be consistent on how it is exited for both serve and non-serve cases. It | 
 | # also allows script to explicitly close the control session (to better avoid stale sshd sessions). | 
 |  | 
 | # Verify that keys match. | 
 | if "${check_ssh_keys}"; then | 
 |   verify_default_keys "${FUCHSIA_DIR}" "${host}" "${dir}" "${ssh_base_args[@]}" || exit $? | 
 | fi | 
 |  | 
 | # XXX: had to stop using -Nf because of b/160269794. | 
 | ssh "${ssh_base_args[@]}" "${ssh_tunnel_args[@]}" "${host}" -nT sleep infinity & | 
 | # Attempt to assert that the backgrounded ssh is alive and kicking, emulating -f as best we can. | 
 | ssh_pid=$! | 
 | # If there's a 2fa prompt, we may need a "human time" number of tries, which is why this is high. | 
 | tries=30 | 
 | until ssh -q -O check ${ssh_base_args[@]} "${host}"; do | 
 |   if ! kill -0 ${ssh_pid}; then | 
 |     fx-error "SSH tunnel terminated prematurely" | 
 |     exit 1 | 
 |   fi | 
 |   if ! ((tries--)); then | 
 |     fx-error "SSH tunnel appears not to have succeeded" | 
 |     kill -TERM $ssh_pid | 
 |     exit 1 | 
 |   fi | 
 |   sleep 1 | 
 | done | 
 |  | 
 | if "${serve}"; then | 
 |   remote_server_mode=$(ssh "${host}" "${ssh_base_args[@]}" "cd ${dir} && ./.jiri_root/bin/ffx config get repository.server.mode") || err=$? | 
 |   if [[ "${err}" -ne 0 ]]; then | 
 |     # Warn if we can't look up the remote server mode, which might mean the | 
 |     # remote checkout is out of date. | 
 |     fx-warn "It appears the remote host does not have a value defined for" | 
 |     fx-warn "'ffx config get repository.server.mode', which suggests it might be" | 
 |     fx-warn "out of date." | 
 |     fx-warn "" | 
 |     fx-warn "Run '$ jiri update && fx build ffx' on both the local and remote hosts, then" | 
 |     fx-warn "confirm that '$ ffx config get repository.server.mode' returns a string on" | 
 |     fx-warn "both the local and remote hosts." | 
 |  | 
 |     # Clear the error so later checks won't fail. | 
 |     err=0 | 
 |   fi | 
 |  | 
 |   if [[ "${remote_server_mode}" == \"pm\" ]]; then | 
 |     # If the user requested serving, then we'll check to see if there's a | 
 |     # remote server already running and kill it, this prevents most cases where | 
 |     # signal propagation seems to sometimes not make it to "pm". | 
 |     # TODO(drees) This can be combined with the serve-updates call later to reduce ssh calls. | 
 |     if ssh "${host}" "${ssh_base_args[@]}" "ss -ln | grep :${package_server_port}" > /dev/null; then | 
 |       ssh "${ssh_base_args[@]}" "${host}" 'pkill -x -u $USER pm' || true | 
 |     fi | 
 |   else | 
 |     # FIXME(http://fxbug.dev/82788): Currently ffx will log, but otherwise | 
 |     # ignore, when both a local ffx and a remote ffx are both trying to serve | 
 |     # repositories over the same tunnel port. The first setup will win, even if | 
 |     # that's not what the user expects. | 
 |     # | 
 |     # To work around this, we will error out if both the local and remote ffx | 
 |     # repository servers are trying to use the same port. | 
 |     local_addr=$(fx-command-run ffx config get repository.server.listen) || err=$? | 
 |     if [[ "${err}" -ne 0 ]]; then | 
 |       fx-error "Unable to get the local configured repository server address." | 
 |       exit "${err}" | 
 |     fi | 
 |  | 
 |     remote_addr=$(ssh "${host}" "${ssh_base_args[@]}" "cd ${dir} && ./.jiri_root/bin/ffx config get repository.server.listen") || err=$? | 
 |     if [[ "${err}" -ne 0 ]]; then | 
 |       fx-error "Unable to get the remote configured repository server address." | 
 |       exit "${err}" | 
 |     fi | 
 |  | 
 |     # If the local server is running, error out if we're using the same port as the remote server. | 
 |     if [[ "${local_addr}" != "null" && "${local_addr}" = "${remote_addr}" ]]; then | 
 |       fx-error "The local and remote ffx repository servers cannot both be $local_addr." | 
 |       fx-error "Either change the ffx repository port with:" | 
 |       fx-error "" | 
 |       fx-error "$ ffx config set repository.server.listen \"[::]:\$SOME_OTHER_PORT\"" | 
 |       fx-error "$ ffx doctor --restart-daemon" | 
 |       fx-error "" | 
 |       fx-error "Or disable the ffx repository server with:" | 
 |       fx-error "" | 
 |       fx-error "$ ffx config set repository.server.mode pm" | 
 |       fx-error "$ ffx doctor --restart-daemon" | 
 |       fx-error "" | 
 |       fx-error "See http://fxbug.dev/82788 for more details." | 
 |       exit 1 | 
 |     fi | 
 |   fi | 
 | fi | 
 |  | 
 | if "${serve}"; then | 
 |   # Ctrl-C will exit the ssh remote command and this ssh session. Then script exit will trigger | 
 |   # `trap_exit` to close the ssh connection. | 
 |   echo -e "Press Ctrl-C to stop remote serving and tunneling.\n" | 
 |  | 
 |   # Set the experimental environment variables to ensure they're passed to `fx | 
 |   # set` and `fx serve-updates`. | 
 |   experimental_vars=$(get_env_vars_non_default_features) | 
 |  | 
 |   if "${verbose}"; then | 
 |     serve_verbose_arg=" -v" | 
 |   else | 
 |     serve_verbose_arg="" | 
 |   fi | 
 |   ssh_serve_args=( | 
 |     "-tt" # explicitly force a pty, for HUP'ing on the remote | 
 |     "cd ${dir} && FX_REMOTE_INVOCATION=1 ${experimental_vars} ./.jiri_root/bin/fx set-device '${_FX_REMOTE_WORKFLOW_DEVICE_ADDR}' && FX_REMOTE_INVOCATION=1 ${experimental_vars} ./.jiri_root/bin/fx serve-updates ${serve_verbose_arg} ${serve_persist_arg}" | 
 |   ) | 
 |   ssh "${host}" "${ssh_base_args[@]}" "${ssh_serve_args[@]}" | 
 | else | 
 |   echo "Press Ctrl-C to stop tunneling." | 
 |   # Wait for user Ctrl-C. Then script exit will trigger trap_exit to close the | 
 |   # ssh connection. Use a command on the remote side over ssh with a -tt | 
 |   # parameter to create a pseudo tty on the remote host. That way, the Ctrl-C | 
 |   # hup will go to the remote host which will close the remote ssh connections. | 
 |   ssh "${host}" "${ssh_base_args[@]}" -tt sleep infinity | 
 | fi |