blob: 40a7d4781594ce22a331875e36aafd1b8017f31b [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.
set -eu
SCRIPT_SRC_DIR="$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd)"
# Fuchsia command common functions.
# shellcheck disable=SC1090
source "${SCRIPT_SRC_DIR}/fuchsia-common.sh" || exit $?
# shellcheck disable=SC1090
source "${SCRIPT_SRC_DIR}/devshell/lib/verify-default-keys.sh" || exit $?
usage() {
cat << EOF
usage: fserve-remote.sh [--no-serve] [--device-name <device hostname>] HOSTNAME REMOTE-PATH
Uses SSH port forwarding to connect to a remote server and forward package serving and other connections to a local device.
--device-name <device hostname>
Connects to a device by looking up the given device hostname.
--image <image name>
Name of prebuilt image packages to serve. Required unless --no-serve is specified.
--bucket <bucket name>
Name of GCS bucket containing the image archive.
--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.
-v
Enable verbose SSH logging.
-x
Enable debug
--ttl
Time to keep the tunnel open. Defaults to "infinity". The format
of the time is the same as the sleep command. This is primarily
intended for testing. Tests should use "0" to return immediately.
HOSTNAME
The hostname of the workstation you want to serve from
REMOTE-PATH
The path to the Fuchsia GN SDK bin directory on "HOSTNAME"
EOF
}
DEBUG_FLAG=""
TTL_TIME="infinity"
START_SERVE=1
CHECK_SSH_KEYS=1
VERBOSE=0
REMOTE_HOST=""
REMOTE_DIR=""
DEVICE_NAME="$(get-fuchsia-property device-name)"
BUCKET="$(get-fuchsia-property bucket)"
IMAGE="$(get-fuchsia-property image)"
while [[ $# -ne 0 ]]; do
case "$1" in
--help|-h)
usage
exit 0
;;
--no-serve)
START_SERVE=0
;;
--no-check-ssh-keys)
CHECK_SSH_KEYS=0
;;
--device-name)
shift
DEVICE_NAME="${1}"
;;
--bucket)
shift
BUCKET="${1}"
;;
--image)
shift
IMAGE="${1}"
;;
-v)
VERBOSE=1
;;
-x)
DEBUG_FLAG="-x"
set -x
;;
--ttl)
shift
TTL_TIME="${1}"
;;
-*)
fx-error "Unknown flag: $1"
usage
exit 1
;;
*)
if [[ -z "${REMOTE_HOST}" ]]; then
REMOTE_HOST="$1"
elif [[ -z "${REMOTE_DIR}" ]]; then
REMOTE_DIR="$1"
else
fx-error "unexpected argument: '$1'"
usage
fi
;;
esac
shift
done
if [[ -z "${REMOTE_HOST}" ]]; then
fx-error "HOSTNAME must be specified"
usage
exit 1
fi
if [[ -z "${REMOTE_DIR}" ]]; then
fx-error "REMOTE-DIR must be specified"
usage
exit 1
fi
if ((START_SERVE)); then
if [[ -z "${IMAGE}" ]]; then
fx-error "--image must be specified when serving packages."
usage
exit 1
fi
fi
if [[ "${DEVICE_NAME}" == "" ]]; then
DEVICE_NAME="$(get-device-name)"
fi
# Determine the local device name/address to use.
if ! DEVICE_IP=$(get-device-ip-by-name "${DEVICE_NAME}"); then
fx-error "unable to discover device. Is the target up?"
exit 1
fi
if [[ -z "${DEVICE_IP}" ]]; then
fx-error "unable to discover device. Is the target up?"
exit 1
fi
if [[ "${BUCKET}" == "" ]]; then
fx-warn "--bucket not set. Using the default bucket."
fi
echo "Using remote ${REMOTE_HOST}:${REMOTE_DIR}"
echo "Using target device ${DEVICE_NAME}"
# Verify that keys match.
if [[ "${CHECK_SSH_KEYS}" == "1" ]]; then
verify_default_keys "" "${REMOTE_HOST}" "" || exit $?
fi
# 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_gn=(
"${REMOTE_HOST}"
-S "${HOME}/.ssh/control-fuchsia-fx-remote"
-o "ControlMaster=auto"
-t
)
if ((VERBOSE)); then
ssh_base_args_gn+=( -o "LogLevel=DEBUG2")
fi
ssh_exit() {
if ssh "${ssh_base_args_gn[@]}" -O check > /dev/null 2>&1; then
if ! ssh "${ssh_base_args_gn[@]}" -O exit > /dev/null 2>&1 ; then
echo "Error exiting session: $?"
fi
fi
}
# When we exit the script, close the background ssh connection.
trap_exit() {
ssh_exit
exit
}
trap trap_exit EXIT
trap trap_exit SIGINT
# First we need to check if we already have a control master for the
# host, if we do, we might already have the forwards and so we don't
# need to worry about tearing down:
if ssh "${ssh_base_args_gn[@]}" -O check > /dev/null 2>&1; then
# 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.
fx-warn "Cleaning up existing remote session"
ssh_exit
fi
# If we didn't have a control master, and the device already has 8022
# bound, then there's a good chance there's a stale sshd instance
# running from another device or another session that will block the
# forward, so we'll check for that and speculatively attempt to clean
# it up. Unfortunately this means authing twice, but it's likely the
# best we can do for now.
if ssh "${ssh_base_args_gn[@]}" 'ss -ln | grep :8022' > /dev/null 2>&1; then
fx-warn "Found existing port forwarding, attempting to kill remote sshd sessions."
if pkill_result="$(ssh "${ssh_base_args_gn[@]}" "pkill -u \$USER sshd" 2>&1)"; then
fx-error "Unexpected message from remote: ${pkill_result}"
else
echo "SSH session cleaned up."
fi
fi
ssh_tunnel_args=(
-6 # We want ipv6 binds for the port forwards
-L "\*:8083:localhost:8083" # requests to the package server address locally go to the workstation
-R "8022:[${DEVICE_IP}]:22" # requests from the workstation to ssh to localhost:8022 will make it to the target
-R "2345:[${DEVICE_IP}]:2345" # requests from the workstation to 2345 are forwarded to the target for zxdb, fidlcat.
-R "8443:[${DEVICE_IP}]:8443" # port 8443 on workstation to target port 8443
-R "9080:[${DEVICE_IP}]:80" # port 9080 on workstation to target port 80
-o "ExitOnForwardFailure=yes"
)
# 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).
ssh "${ssh_base_args_gn[@]}" "${ssh_tunnel_args[@]}" -nT sleep "${TTL_TIME}" &
# 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 "${ssh_base_args_gn[@]}" -q -O check; 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
# Set the configuration properties to match the remote device. Set the IP address,
# and clear the device name to avoid any confusion.
remote_cmds=(
"cd \$HOME" "&&" # change directories to home, to avoid issues if the remote dir was deleted out from under us.
"cd ${REMOTE_DIR}"
)
fconfig_cmd="./tools/x64/fconfig set-device ${DEVICE_NAME} --default --device-ip 127.0.0.1"
if [[ "${BUCKET}" != "" ]]; then
fconfig_cmd="${fconfig_cmd} --bucket ${BUCKET}"
fi
if [[ "${IMAGE}" != "" ]]; then
fconfig_cmd="${fconfig_cmd} --image ${IMAGE}"
fi
remote_cmds+=("&&" "${fconfig_cmd}")
# Run fconfig.sh list to print out the settings, this will help diagnosing any
# problems.
remote_cmds+=("&&" "./bin/fconfig.sh list")
if [[ "${DEBUG_FLAG}" != "" ]]; then
remote_cmds+=("&&" "echo Desktop env is \$(env)")
fi
# The variables here should be expanded locally, disabling the shellcheck lint
# message about ssh and variables.
# shellcheck disable=SC2029
ssh "${ssh_base_args_gn[@]}" "${remote_cmds[@]}"
if ((START_SERVE)); then
# If the user requested serving, then we'll check to see if there's a
# server already running and kill it, this prevents most cases where
# signal propagation seems to sometimes not make it to "pm".
if ssh "${ssh_base_args_gn[@]}" 'ss -ln | grep :8083' > /dev/null; then
fx-warn "Cleaning up \`pm\` running on remote desktop"
if ssh "${ssh_base_args_gn[@]}" "pkill -u \$USER pm"; then
echo "Success"
fi
fi
# Starts a package server
args=(cd "\$HOME" "&&" cd "${REMOTE_DIR}" "&&" ./bin/fserve.sh)
if [[ "${DEBUG_FLAG}" != "" ]]; then
args+=("${DEBUG_FLAG}")
fi
# fserve.sh runs in the background, keep the tunnel open by running sleep.
args+=("&&" "sleep ${TTL_TIME}")
# shellcheck disable=SC2029
ssh "${ssh_base_args_gn[@]}" "${args[@]}"
fi
echo "Press Ctrl-C to stop tunneling."
# Wait for user Ctrl-C. Then script exit will trigger trap_exit to close the ssh connection.
# a TTL_TIME of 0 is used in unit tests.
if [[ "$TTL_TIME" != "0" ]]; then
read -r -d '' _ </dev/tty
fi