blob: 105b3d0708463f224c9146cc2cc964a6bd414e7e [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-updates [-v] [-l host[:port]] [-c version] [--no-auto-config] [--name NAME]
##
## -l host:port host and port that "pm serve" will listen on
## -c version configuration format version for the served config.json
## that "pm" will serve. Valid choices: 1 or 2.
## Choosing `1` will serve a file which can be processed by
## `pkgctl repo add ... -f 1`.
## Choosing `2` will serve a file which can be processed by
## `pkgctl repo ...` without the `-f 1` switch.
## --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 "pm"
## -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.
##
## To enable incremental package serving, run "fx --enable=incremental serve-updates ..."
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-updates
}
fx-standard-switches "$@"
set -- "${FX_ARGV[@]}"
port=""
serve_flags=()
verbose=false
auto_config=true
clean_first=false
config_format="2"
source_name=""
persist="--no-persist"
while (($#)); do
case "$1" in
-l)
shift
port="${1##*:}"
serve_flags+=(-l "$1")
;;
-c)
config_format=("$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
serve_flags+=("-c" "${config_format}")
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
if [[ "${verbose}" != true ]]; then
serve_flags+=("-q")
fi
# Determine which server we are using.
server_mode="$(package-server-mode)"
err=$?
if [[ "${err}" -ne 0 ]]; then
exit 1
fi
pm_pid=
ffx_server_started=
cleanup() {
if [[ "${server_mode}" == "pm" ]]; then
if [[ -n "${pm_pid}" ]]; then
if kill -0 "${pm_pid}" 2> /dev/null; then
kill -TERM "${pm_pid}" 2> /dev/null
wait "${pm_pid}" 2> /dev/null
fi
fi
else
if [[ ! -z "${ffx_server_started}" ]]; then
ffx-stop-server
fi
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-updates] $@"
}
# 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"; then
# FIXME(http://fxbug.dev/79636): ffx doesn't yet support incremental package building.
server_mode="pm"
fx-info "Incremental package auto-publishing is enabled."
serve_flags+=( -p "all_package_manifests.list" )
# 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
$verbose && echo -n >&2 "Preparing the package repository..."
# only show output of prepare_publish if it fails
out="$(fx-command-run build build/images/updates:prepare_publish 2>&1)"
if [[ $? -ne 0 ]]; then
echo >&2 "${out}"
exit $?
fi
$verbose && echo >&2 "done"
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
if [[ "${server_mode}" = "pm" ]]; then
# Default the port to 8083 if it is unset.
if [[ -z "${port}" ]]; then
port="8083"
fi
pm_serve_args=( serve -vt -repo "${repo_dir}" "${serve_flags[@]}" )
# the manifest list file has relative paths and "pm serve" looks for them
# from PWD, so pm command needs to be executed in "$FUCHSIA_BUILD_DIR"
cd "${FUCHSIA_BUILD_DIR}"
else
# FIXME(http://fxbug.dev/79636): ffx doesn't yet support incremental package building.
if is_feature_enabled "incremental"; then
fx-error "The feature 'incremental' is not supported yet by ffx."
fx-error "To use the 'incremental' feature, switch to the legacy pm"
fx-error "package 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/79636 for more details."
exit 1
fi
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}..."
if [[ "${server_mode}" = "pm" ]]; then
fx-command-exec host-tool pm "${pm_serve_args[@]}"
else
log "Repository is being served in the background by ffx"
exit 0
fi
fi
if [[ "${server_mode}" = "pm" ]]; then
fx-command-exec host-tool pm "${pm_serve_args[@]}" &
pm_pid=$!
# Allow a little slack for pm serve to startup, that way the first kill -0 will catch a dead server
sleep 0.1
if ! kill -0 "${pm_pid}" 2> /dev/null; then
wait "${pm_pid}"
err=$?
fx-error "Server died"
fx-error "If the server exited with 'address already in use', pm may be colliding with"
fx-error "another 'pm serve', or the ffx repository server."
fx-error ""
fx-error "To disable the ffx repository server, run:"
fx-error ""
fx-error "$ ffx config set repository.server.mode pm"
fx-error "$ ffx doctor --restart-daemon"
wait
exit $err
fi
else
ffx-start-server
err=$?
if [[ "${err}" -ne 0 ]]; then
exit "${err}"
fi
ffx_server_started=1
if [[ "${verbose}" == true ]]; then
# FIXME(http://fxbug.dev/78891): `ffx daemon log` doesn't have a mechanism for
# filtering based off of a specific tag, so we'll grep for the lines that
# denote traffic to the web server.
fx-command-run ffx daemon log --follow --line-count 0 | grep ' \(pkg::repository::server\)\|\(ffx_daemon_service_repo\): ' &
pm_pid=$!
fi
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. fx serve-updates will get stuck on \"Discovery...\""
fx-warn "Run 'fx list-devices' and then 'fx set-device'"
fx-warn "\n"
else
fx-info ""
fx-info "Default device set: ${device_name}"
fx-info "\n"
fi
log "Discovery..."
# State is used to prevent too much output
state="discover"
# Keep track if we reported any discovery failures
reported_discovery_failed=0
while true; do
if [[ -n "${pm_pid}" ]]; then
if ! kill -0 "${pm_pid}" 2> /dev/null; then
log "Server died, exiting"
pm_pid=
exit 1
fi
fi
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
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
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"
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
if [[ "${state}" == "ready" ]]; then
if [[ "${ping_result}" != 0 ]]; then
log "Device lost"
state="discover"
else
sleep 1
fi
fi
done
# See EXIT trap above for cleanup that occurs