| #!/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 | |
| ## `amberctl` and `pkgctl repo add ... -f 1`. | |
| ## Choosing `2` will serve a file which cannot be processed by | |
| ## `amberctl` but 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/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 | |
| background_pid= | |
| cleanup() { | |
| if [[ -n "${background_pid}" ]]; then | |
| if kill -0 "${background_pid}" 2> /dev/null; then | |
| kill -TERM "${background_pid}" 2> /dev/null | |
| wait "${background_pid}" 2> /dev/null | |
| 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 | |
| if is_feature_enabled "legacy_serve"; then | |
| serve_updates_target_addr="$(get-device-addr-resource)" | |
| else | |
| serve_updates_target_addr="$(get-device-raw)" | |
| fi | |
| 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 | |
| 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: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 | |
| # Default the port to 8083 if it is unset. | |
| if [[ -z "${port}" ]]; then | |
| port="8083" | |
| fi | |
| if is_feature_enabled "legacy_serve"; then | |
| 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 "See http://fxbug.dev/79636 for more details." | |
| exit 1 | |
| fi | |
| 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 is_feature_enabled "legacy_serve"; 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 is_feature_enabled "legacy_serve"; then | |
| fx-command-exec host-tool pm "${pm_serve_args[@]}" & | |
| background_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 "${background_pid}" 2> /dev/null; then | |
| err=$? | |
| fx-error "Server died" | |
| fx-error "If the server exited with 'address already in use', pm may be colliding with" | |
| fx-error "the ffx repository server. To disable the ffx repository server, run:" | |
| fx-error "$ ffx config set repository.server.listen \"\" && ffx doctor --restart-daemon" | |
| wait | |
| exit $err | |
| fi | |
| else | |
| 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\): ' & | |
| background_pid=$! | |
| fi | |
| fi | |
| log "Discovery..." | |
| # State is used to prevent too much output | |
| state="discover" | |
| while true; do | |
| if [[ -n "${background_pid}" ]]; then | |
| if ! kill -0 "${background_pid}" 2> /dev/null; then | |
| log "Server died, exiting" | |
| background_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=$? | |
| else | |
| with-pinned-target fx-command-run shell -O check > /dev/null 2>&1 | |
| ping_result=$? | |
| fi | |
| if [[ "$state" == "discover" && "$ping_result" == 0 ]]; then | |
| log "Device up" | |
| state="config" | |
| fi | |
| if [[ "$state" == "config" ]]; then | |
| log "Registering devhost as update source" | |
| add_update_source_args=( "${persist}" --port "${port}" ) | |
| 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 |