blob: 2bd53ecea8edfb0b481932423a87132bc446c7cf [file] [log] [blame]
#!/bin/bash
# Copyright 2018 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=Software delivery
### start the update server and attach to a running fuchsia device
## usage: fx serve [-v] [-l host[:port]] [-c version] [--no-auto-config] [--name NAME]
##
## -l port port that "fx serve" will listen on
## --no-auto-config do not configure this host as a package server on the device
## --name NAME Name the generated update source config NAME.
## --[no-]persist enable or disable persistence of repository metadata. Disabled
## by default.
## -v verbose mode, shows info and debug messages from "ffx repository serve"
## -C|--clean clean the package repository first. This flag is only
## valid if the incremental package publishing is enabled.
##
## This command supports:
## - incremental package publishing. If enabled, it will auto-publish packages as they
## are created or modified.
## - foreground repository serving. If enabled, the repository server and device registration
## flows will be ran as a foreground process.
##
## To enable incremental package serving, run "fx --enable=incremental serve ..."
## To enable foreground repository serving, run "fx --enable=foreground_repo_server serve ..."
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/updates.sh || exit $?
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/lib/fx-optional-features.sh || exit $?
fx-config-read
function usage {
fx-command-help serve
}
fx-standard-switches "$@"
set -- "${FX_ARGV[@]}"
port=""
verbose=false
auto_config=true
clean_first=false
source_name=""
persist="--no-persist"
while (($#)); do
case "$1" in
-l)
port="${2##*:}"
shift
;;
--name)
source_name="$2"
shift
;;
--no-persist)
;;
--persist)
persist="--persist"
;;
--no-auto-config)
auto_config=false
;;
-v|--verbose)
verbose=true
;;
-C|--clean)
clean_first=true
;;
*)
echo "Unrecognized option: $1"
usage
exit 1
;;
esac
shift
done
if fx-is-bringup; then
fx-error "$0 is not supported in the bringup build configuration, as there are no package features in bringup."
exit 1
fi
sunset_resolutions() {
fx-error "Please consider switching to foreground serving via ffx, and report"
fx-error "any blocking issues at your earliest convenience."
fx-error "To serve repositories, please pick between the following two options:"
fx-error ""
fx-error "1. Disable pm and switch to ffx foreground server (aka supported):"
fx-error ""
fx-error " Ensure your shell does *not* export the environment variable"
fx-error " FUCHSIA_DISABLED_foreground_repo_server"
fx-error ""
fx-error " $ ffx config remove repository.server.mode"
fx-error " $ ffx config remove repository.server.enabled"
fx-error " $ ffx doctor --restart-daemon"
fx-error ""
fx-error " Then restart \"fx serve\""
fx-error ""
fx-error "2. Strongly discouraged, this option is going away at the end"
fx-error " of 2024Q2: Ensure your shell *does* export the following"
fx-error " environment variable wherever you use fx and ffx."
fx-error ""
fx-error " $ export FUCHSIA_DISABLED_foreground_repo_server=1"
fx-error ""
fx-error " Enable the ffx background server:"
fx-error ""
fx-error " $ ffx config remove repository.server.mode"
fx-error " $ ffx config set repository.server.enabled true"
fx-error " $ ffx doctor --restart-daemon"
fx-error ""
fx-error "In both cases, please note that an already running foreground server is"
fx-error "not always detected correctly. Consider running the following commands"
fx-error "in case of problems:"
fx-error ""
fx-error "$ ffx config remove repository.server.listen"
fx-error "$ ffx doctor --restart-daemon"
fx-error ""
exit 1
}
# Determine which server is configured.
server_mode="$(package-server-mode)"
err=$?
if [[ "${err}" -ne 0 ]]; then
exit 1
fi
# pm is no longer supported
if [[ "${server_mode}" = "pm" ]] ; then
fx-error "Invoking pm via fx serve is no longer supported. Additionally,"
fx-error "the pm is slated for removal, targeted for 2024 Q2."
sunset_resolutions
fi
package_tool_pid=
ffx_server_started=
foreground_repo_server_pid=
cleanup() {
if [[ -n "${package_tool_pid}" ]]; then
if kill -0 "${package_tool_pid}" 2> /dev/null; then
kill -TERM "${package_tool_pid}" 2> /dev/null
wait "${package_tool_pid}" 2> /dev/null
fi
fi
if [[ -n "${foreground_repo_server_pid}" ]]; then
if kill -0 "${foreground_repo_server_pid}" 2> /dev/null; then
kill -TERM "${foreground_repo_server_pid}" 2> /dev/null
wait "${foreground_repo_server_pid}" 2> /dev/null
fi
fi
if [[ ! -z "${ffx_server_started}" ]]; then
ffx-stop-server
fi
}
trap cleanup EXIT
log() {
# This format matches bootserver so that `fx serve` ui is easier to read.
echo "$(date '+%Y-%m-%d %H:%M:%S') [serve] $@"
}
# In any multi-homing scenario (two target interfaces, or two host interfaces),
# the resolve process can return different results at different times. As such
# we pin the address here, and let the ssh check loop below perform a clear
# whenever it is in the "discovery" state. This stabilizes the "connection"
# under multi-homed conditions.
serve_updates_target_addr=""
target-addr() {
if [[ -z "${serve_updates_target_addr}" ]]; then
serve_updates_target_addr="$(get-device-addr-resource)"
if [[ -n "${serve_updates_target_addr}" && -n "$(get-fuchsia-device-port)" ]]; then
serve_updates_target_addr="${serve_updates_target_addr}:$(get-fuchsia-device-port)"
fi
fi
if [[ -n "${serve_updates_target_addr}" ]]; then
echo "${serve_updates_target_addr}"
return 0
fi
return 1
}
clear-target-addr() {
serve_updates_target_addr=""
}
with-pinned-target() {
# Ensure we pin in the running shell before making a subshell. We also want
# to make sure we get an address at all.
if ! target-addr > /dev/null; then
return 1
fi
(
export FUCHSIA_DEVICE_NAME="$(target-addr)"
"$@"
)
}
repo_dir="${FUCHSIA_BUILD_DIR}/amber-files"
if is_feature_enabled "incremental" || is_feature_enabled "incremental_new"; then
# macOS in particular has a low default for number of open file descriptors
# per process, which is prohibitive for higher job counts. Here we raise
# the number of allowed file descriptors per process if it appears to be
# low in order to avoid failures due to the limit. See `getrlimit(2)` for
# more information.
if [[ $(ulimit -n) -lt 1000 ]]; then
ulimit -n 32768
fi
if $clean_first; then
$verbose && echo -n >&2 "Cleaning the package repository..."
if [[ -d "${repo_dir}" ]]; then
rm -Rf "${repo_dir}"
fi
$verbose && echo >&2 "done"
fi
if [[
! -e "${repo_dir}/keys/snapshot.json" ||
! -e "${repo_dir}/keys/targets.json" ||
! -e "${repo_dir}/keys/timestamp.json" ||
! -e "${repo_dir}/repository/1.root.json" ||
! -e "${repo_dir}/repository/root.json"
]]; then
echo >&2 "Preparing the package repository..."
fx-command-run build build/images/updates:prepare_publish
if [[ $? -ne 0 ]]; then
exit $?
fi
echo >&2 "done"
fi
else
if $clean_first; then
fx-error "Flag '-C' or '--clean' can only be used if the incremental feature is enabled"
exit 1
fi
fi
# Default the port to 8083 if it is unset.
if is_feature_enabled "foreground_repo_server"; then
if [[ -z "${port}" ]]; then
port="8083"
fi
fi
if [[ "$(ffx-repository-server-enabled)" == "true" ]]; then
if is_feature_enabled "foreground_repo_server"; then
fx-error "Both ffx foreground and background serving are enabled. This is mutually incompatible."
else
fx-error "Starting an ffx background server from \"fx serve\" is no longer supported,"
fx-error "preceding the upcoming removal of background serving at the end of 2024Q2."
fi
sunset_resolutions
fi
# Error out if we can't start a package server.
check-if-we-can-start-package-server "${server_mode}" "" "${port}"
err=$?
if [[ "${err}" -ne 0 ]]; then
exit 1
fi
if [[ "${auto_config}" == false ]]; then
log "Flag '--no-auto-config' used, automatic device configuration disabled."
log "Use 'fx -d DEVICE add-update-source --port ${port}' to reconfigure devices as needed, since it is not persistent accross reboots."
log "Serving packages on port ${port}..."
log "Repository is being served in the background by ffx"
exit 0
fi
if is_feature_enabled "incremental" || is_feature_enabled "incremental_new"; then
fx-info "Incremental package auto-publishing is enabled for ffx."
package_tool_flags=(
--watch
--package-list "all_package_manifests.list"
--trusted-root "${repo_dir}/repository/9.root.json"
--ignore-missing-packages
--time-versioning
)
package_tool_args=( repository publish "${repo_dir}" "${package_tool_flags[@]}" )
# Check for erroneously running instances of package-tool
package_tool_pids=$(pgrep -f "package-tool repository publish $(fx-command-run get-build-dir)/amber-files --watch")
if [[ -n $package_tool_pids ]]; then
fx-info "Incremental auto-publisher for this build config is/are already running under pid(s): $(echo $package_tool_pids)"
fx-info "This is likely a side effect from a previous run. Restarting."
kill -TERM $package_tool_pids
fi
# the manifest list file has relative paths and package manifest watcher looks for them
# from PWD, so the command needs to be executed in "$FUCHSIA_BUILD_DIR"
if [[ "${verbose}" == true ]]; then
cd "${FUCHSIA_BUILD_DIR}" && fx-command-exec host-tool package-tool "${package_tool_args[@]}" &
else
cd "${FUCHSIA_BUILD_DIR}" && fx-command-exec host-tool package-tool "${package_tool_args[@]}" >/dev/null &
fi
package_tool_pid=$!
fi
# Check if device is set via fx set-device before the discovery loop is started
device_name="$(get-device-name 2>/dev/null)"
if [[ -z "${device_name}" ]]; then
fx-warn ""
fx-warn "No default device set. If 'fx serve' gets stuck on"
fx-warn "\"Discovery...\" you can set a default device by running"
fx-warn "'fx list-devices' to get a list of devices, and then running"
fx-warn "'fx set-device'."
fx-warn "\n"
else
fx-info ""
fx-info "Default device set: ${device_name}"
fx-info "\n"
fi
# Make sure the ssh config is present and has the expected private key
check-ssh-config
log "Discovery..."
# State is used to prevent too much output
state="discover"
# Keep track if we reported any discovery failures
reported_discovery_failed=0
# exponential backoff in seconds.
backoff=1
while true; do
if [[ "${state}" == "discover" ]]; then
# While we're still trying to connect to the device, clear the target
# address state so we re-resolve.
clear-target-addr
with-pinned-target fx-command-run shell exit 2>/dev/null
ping_result=$?
if [[ "${ping_result}" == 0 ]]; then
log "Device up"
reported_discovery_failed=0
backoff=1
state="config"
elif [[ "${reported_discovery_failed}" == 0 ]]; then
reported_discovery_failed=1
# Log if we don't know the device's address yet, or if we don't have a device configured.
device_addr="$(get-fuchsia-device-addr 2>/dev/null)"
if [[ -z "${device_addr}" ]]; then
device_name="$(get-device-name 2>/dev/null)"
if [[ -n "${device_name}" ]]; then
log "Trying to resolve '${device_name}'..."
else
log "Either no device or multiple devices are discoverable. Run 'fx list-devices' and if necessary run 'fx set-device'."
fi
else
log "Trying to connect to ${device_addr}..."
fi
fi
if [[ "${ping_result}" != 0 ]]; then
sleep "${backoff}"
backoff=$(( $backoff * 2 ))
if [[ "${backoff}" -gt 10 ]]; then
backoff=10
fi
fi
else
with-pinned-target fx-command-run shell -O check > /dev/null 2>&1
ping_result=$?
fi
if [[ "${state}" == "config" ]]; then
log "Registering devhost as update source"
if is_feature_enabled "foreground_repo_server"; then
ffx_flags=( --config repository.foreground.enabled=true )
if [[ "${verbose}" == true ]]; then
ffx_flags+=( -v )
fi
if [[ -z "${source_name}" ]]; then
source_name="$(ffx-default-repository-name)"
fi
# Start repository server in foreground, wait until it
# voluntarily exits
fx-command-run ffx ${ffx_flags[@]} repository serve \
--address [::]:${port} \
--repository "${source_name}" \
--repo-path "${repo_dir}" \
--alias "fuchsia.com" \
--alias "chromium.org"
exit 0
else
add_update_source_args=( "${persist}" --port "${port}" --server-mode "${server_mode}" )
if [[ ! -z "${source_name}" ]]; then
add_update_source_args+=( --name "${source_name}" )
fi
if with-pinned-target fx-command-run add-update-source "${add_update_source_args[@]}"; then
log "Ready to push packages!"
# Log the uptime so that it is clear(er) from fx serve if the device rebooted.
# The tr is present as the output sometimes contains spurious control characters.
clock=$(with-pinned-target fx-command-run shell clock --monotonic)
if [[ -n ${clock} ]]; then
uptime=$(echo $clock | tr '[[:cntrl:]]' ' ' )
log "Target uptime: $(( $uptime / 1000000000 ))"
fi
state="ready"
else
log "Device lost while configuring update source"
state="discover"
fi
fi
fi
if [[ "${state}" == "ready" ]]; then
if [[ "${ping_result}" != 0 ]]; then
log "Device lost"
# Ensure foreground repo session stops before spinning another.
if [[ -n "${foreground_repo_server_pid}" ]]; then
if kill -0 "${foreground_repo_server_pid}" 2> /dev/null; then
kill -TERM "${foreground_repo_server_pid}" 2> /dev/null
wait "${foreground_repo_server_pid}" 2> /dev/null
fi
foreground_repo_server_pid=
fi
state="discover"
else
sleep 1
fi
fi
done
# See EXIT trap above for cleanup that occurs