| #!/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. | |
| ## --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 $? | |
| fx-config-read | |
| fx-standard-switches "$@" | |
| set -- "${FX_ARGV[@]}" | |
| serve=true | |
| check_ssh_keys=true | |
| verbose=false | |
| host="" | |
| dir="" | |
| 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 | |
| ;; | |
| -v) | |
| verbose=true | |
| ;; | |
| --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" | |
| target_addr=$(get-fuchsia-device-addr) | |
| if [[ $? -ne 0 || -z "${target_addr}" ]]; then | |
| fx-error "unable to discover device. Is the target up?" | |
| exit 1 | |
| fi | |
| echo "Using remote ${host}:${dir}" | |
| echo "Using target device $(get-device-name)" | |
| # 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 8083). | |
| 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 "\*:8083:localhost:8083" # fx serve | |
| -R "8022:[${target_addr}]:22" # fx shell | |
| -R "2345:[${target_addr}]:2345" # fx debug | |
| -R "8007:[${target_addr}]:8007" # Google-specific | |
| -R "8443:[${target_addr}]:8443" # Google-specific | |
| -R "9080:[${target_addr}]:80" # SL4F_HTTP_PORT | |
| -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 | |
| ) | |
| # Add additional ports to tunnel if specified. | |
| if "${has_tunnel_ports}"; then | |
| for port in "${tunnel_ports[@]}"; do | |
| ssh_tunnel_args+=(-R "${port}:[${target_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 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 "${serve}" && ssh "${host}" "${ssh_base_args[@]}" 'ss -ln | grep :8083' > /dev/null; then | |
| ssh "${ssh_base_args[@]}" "${host}" 'pkill -x -u $USER pm' | |
| 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" | |
| 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 ./.jiri_root/bin/fx set-device '${_FX_REMOTE_WORKFLOW_DEVICE_ADDR}' && FX_REMOTE_INVOCATION=1 ./.jiri_root/bin/fx serve-updates${serve_verbose_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. | |
| read -r -d '' _ </dev/tty | |
| fi |