mirror of
https://github.com/flatcar/scripts.git
synced 2025-08-08 05:26:58 +02:00
ability to filter or manipulate content as it's passed through. Create two example tests using this utility that cause updates to close early to test resumes, and to add delays to make sure the update still completes correctly. What other tests should be created for this? BUG=chromium-os:8207 TEST=Run by hand Review URL: http://codereview.chromium.org/5632002 Change-Id: Iefb8c8e223fb2ba6bad2c551f7c4403a0bec6ecf
400 lines
12 KiB
Bash
Executable File
400 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Copyright (c) 2009-2010 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
# Script to update an image onto a live running ChromiumOS instance.
|
|
|
|
# Load common constants. This should be the first executable line.
|
|
# The path to common.sh should be relative to your script's location.
|
|
|
|
. "$(dirname $0)/common.sh"
|
|
. "$(dirname $0)/remote_access.sh"
|
|
|
|
# Flags to control image_to_live.
|
|
DEFINE_boolean ignore_hostname ${FLAGS_TRUE} \
|
|
"Ignore existing AU hostname on running instance use this hostname."
|
|
DEFINE_boolean ignore_version ${FLAGS_TRUE} \
|
|
"Ignore existing version on running instance and always update."
|
|
DEFINE_string server_log "dev_server.log" \
|
|
"Path to log for the devserver."
|
|
DEFINE_boolean update "${FLAGS_TRUE}" \
|
|
"Perform update of root partition."
|
|
DEFINE_boolean update_known_hosts ${FLAGS_FALSE} \
|
|
"Update your known_hosts with the new remote instance's key."
|
|
DEFINE_string update_log "update_engine.log" \
|
|
"Path to log for the update_engine."
|
|
DEFINE_boolean verify ${FLAGS_TRUE} "Verify image on device after update."
|
|
|
|
# Flags for devserver.
|
|
DEFINE_string archive_dir "" \
|
|
"Update using the test image in the image.zip in this directory." a
|
|
DEFINE_string board "" "Override the board reported by the target"
|
|
DEFINE_integer devserver_port 8080 \
|
|
"Port to use for devserver."
|
|
DEFINE_boolean for_vm ${FLAGS_FALSE} "Image is for a vm."
|
|
DEFINE_string image "" \
|
|
"Update with this image path that is in this source checkout." i
|
|
DEFINE_string payload "" \
|
|
"Update with this update payload, ignoring specified images."
|
|
DEFINE_string proxy_port "" \
|
|
"Have the client request from this proxy instead of devserver."
|
|
DEFINE_string src_image "" \
|
|
"Create a delta update by passing in the image on the remote machine."
|
|
DEFINE_boolean update_stateful ${FLAGS_TRUE} \
|
|
"Perform update of stateful partition e.g. /var /usr/local."
|
|
DEFINE_string update_url "" "Full url of an update image."
|
|
|
|
# Flags for stateful update.
|
|
DEFINE_string stateful_update_flag "" \
|
|
"Flag to pass to stateful update e.g. old, clean, etc." s
|
|
|
|
UPDATER_BIN="/usr/bin/update_engine_client"
|
|
UPDATER_IDLE="UPDATE_STATUS_IDLE"
|
|
UPDATER_NEED_REBOOT="UPDATE_STATUS_UPDATED_NEED_REBOOT"
|
|
UPDATER_UPDATE_CHECK="UPDATE_STATUS_CHECKING_FOR_UPDATE"
|
|
UPDATER_DOWNLOADING="UPDATE_STATUS_DOWNLOADING"
|
|
|
|
IMAGE_PATH=""
|
|
|
|
function kill_all_devservers {
|
|
# Using ! here to avoid exiting with set -e is insufficient, so use
|
|
# || true instead.
|
|
sudo pkill -f devserver\.py || true
|
|
}
|
|
|
|
function cleanup {
|
|
if [ -z "${FLAGS_update_url}" ]; then
|
|
kill_all_devservers
|
|
fi
|
|
cleanup_remote_access
|
|
rm -rf "${TMP}"
|
|
}
|
|
|
|
function remote_reboot_sh {
|
|
rm -f "${TMP_KNOWN_HOSTS}"
|
|
remote_sh "$@"
|
|
}
|
|
|
|
# Returns the hostname of this machine.
|
|
# It tries to find the ipaddress using ifconfig, however, it will
|
|
# default to $HOSTNAME on failure. We try to use the ip address first as
|
|
# some targets may have dns resolution issues trying to contact back
|
|
# to us.
|
|
function get_hostname {
|
|
local hostname
|
|
# Try to parse ifconfig for ip address
|
|
hostname=$(ifconfig eth0 \
|
|
| grep 'inet addr' \
|
|
| sed 's/.\+inet addr:\(\S\+\).\+/\1/') || hostname=${HOSTNAME}
|
|
echo ${hostname}
|
|
}
|
|
|
|
# Reinterprets path from outside the chroot for use inside.
|
|
# Returns "" if "" given.
|
|
# $1 - The path to reinterpret.
|
|
function reinterpret_path_for_chroot() {
|
|
if [ -z "${1}" ]; then
|
|
echo ""
|
|
else
|
|
local path_abs_path=$(readlink -f "${1}")
|
|
local gclient_root_abs_path=$(readlink -f "${GCLIENT_ROOT}")
|
|
|
|
# Strip the repository root from the path.
|
|
local relative_path=$(echo ${path_abs_path} \
|
|
| sed s:${gclient_root_abs_path}/::)
|
|
|
|
if [ "${relative_path}" = "${path_abs_path}" ]; then
|
|
die "Error reinterpreting path. Path ${1} is not within source tree."
|
|
fi
|
|
|
|
# Prepend the chroot repository path.
|
|
echo "/home/${USER}/trunk/${relative_path}"
|
|
fi
|
|
}
|
|
|
|
function start_dev_server {
|
|
kill_all_devservers
|
|
local devserver_flags="--pregenerate_update"
|
|
# Parse devserver flags.
|
|
if [ -n "${FLAGS_image}" ]; then
|
|
devserver_flags="${devserver_flags} \
|
|
--image $(reinterpret_path_for_chroot ${FLAGS_image})"
|
|
IMAGE_PATH="${FLAGS_image}"
|
|
elif [ -n "${FLAGS_archive_dir}" ]; then
|
|
devserver_flags="${devserver_flags} \
|
|
--archive_dir $(reinterpret_path_for_chroot ${FLAGS_archive_dir}) -t"
|
|
IMAGE_PATH="${FLAGS_archive_dir}/chromiumos_test_image.bin"
|
|
else
|
|
# IMAGE_PATH should be the newest image and learn the board from
|
|
# the target.
|
|
learn_board
|
|
IMAGE_PATH="$($(dirname "$0")/get_latest_image.sh --board="${FLAGS_board}")"
|
|
IMAGE_PATH="${IMAGE_PATH}/chromiumos_image.bin"
|
|
devserver_flags="${devserver_flags} \
|
|
--image $(reinterpret_path_for_chroot ${IMAGE_PATH})"
|
|
fi
|
|
|
|
if [ -n "${FLAGS_payload}" ]; then
|
|
devserver_flags="${devserver_flags} \
|
|
--payload $(reinterpret_path_for_chroot ${FLAGS_payload})"
|
|
fi
|
|
|
|
if [ -n "${FLAGS_proxy_port}" ]; then
|
|
devserver_flags="${devserver_flags} \
|
|
--proxy_port ${FLAGS_proxy_port}"
|
|
fi
|
|
|
|
[ ${FLAGS_for_vm} -eq ${FLAGS_TRUE} ] && \
|
|
devserver_flags="${devserver_flags} --for_vm"
|
|
|
|
devserver_flags="${devserver_flags} \
|
|
--src_image=\"$(reinterpret_path_for_chroot ${FLAGS_src_image})\""
|
|
|
|
info "Starting devserver with flags ${devserver_flags}"
|
|
./enter_chroot.sh "sudo ./start_devserver ${devserver_flags} \
|
|
--client_prefix=ChromeOSUpdateEngine \
|
|
--board=${FLAGS_board} \
|
|
--port=${FLAGS_devserver_port} > ${FLAGS_server_log} 2>&1" &
|
|
|
|
info "Waiting on devserver to start"
|
|
info "note: be patient as the server generates the update before starting."
|
|
until netstat -anp 2>&1 | grep 0.0.0.0:${FLAGS_devserver_port} > /dev/null
|
|
do
|
|
sleep 5
|
|
echo -n "."
|
|
if ! pgrep -f start_devserver > /dev/null; then
|
|
echo "Devserver failed, see dev_server.log."
|
|
exit 1
|
|
fi
|
|
done
|
|
echo ""
|
|
}
|
|
|
|
# Copies stateful update script which fetches the newest stateful update
|
|
# from the dev server and prepares the update. chromeos_startup finishes
|
|
# the update on next boot.
|
|
function run_stateful_update {
|
|
local dev_url=$(get_devserver_url)
|
|
local stateful_url=""
|
|
local stateful_update_args=""
|
|
|
|
# Parse stateful update flag.
|
|
if [ -n "${FLAGS_stateful_update_flag}" ]; then
|
|
stateful_update_args="${stateful_update_args} \
|
|
--stateful_change ${FLAGS_stateful_update_flag}"
|
|
fi
|
|
|
|
# Assume users providing an update url are using an archive_dir path.
|
|
if [ -n "${FLAGS_update_url}" ]; then
|
|
stateful_url=$(echo ${dev_url} | sed -e "s/update/static\/archive/")
|
|
else
|
|
stateful_url=$(echo ${dev_url} | sed -e "s/update/static/")
|
|
fi
|
|
|
|
info "Starting stateful update using URL ${stateful_url}"
|
|
|
|
# Copy over update script and run update.
|
|
local dev_dir="${SCRIPTS_DIR}/../platform/dev"
|
|
remote_cp_to "${dev_dir}/stateful_update" "/tmp"
|
|
remote_sh "/tmp/stateful_update ${stateful_update_args} ${stateful_url}"
|
|
}
|
|
|
|
function get_update_args {
|
|
if [ -z ${1} ]; then
|
|
die "No url provided for update."
|
|
fi
|
|
local update_args="--omaha_url ${1}"
|
|
if [[ ${FLAGS_ignore_version} -eq ${FLAGS_TRUE} ]]; then
|
|
info "Forcing update independent of the current version"
|
|
update_args="--update ${update_args}"
|
|
fi
|
|
|
|
echo "${update_args}"
|
|
}
|
|
|
|
function get_devserver_url {
|
|
local devserver_url=""
|
|
local port=${FLAGS_devserver_port}
|
|
|
|
if [[ -n ${FLAGS_proxy_port} ]]; then
|
|
port=${FLAGS_proxy_port}
|
|
fi
|
|
|
|
if [ ${FLAGS_ignore_hostname} -eq ${FLAGS_TRUE} ]; then
|
|
if [ -z ${FLAGS_update_url} ]; then
|
|
devserver_url="http://$(get_hostname):${port}/update"
|
|
else
|
|
devserver_url="${FLAGS_update_url}"
|
|
fi
|
|
fi
|
|
echo "${devserver_url}"
|
|
}
|
|
|
|
function truncate_update_log {
|
|
remote_sh "> /var/log/update_engine.log"
|
|
}
|
|
|
|
function get_update_log {
|
|
remote_sh "cat /var/log/update_engine.log"
|
|
echo "${REMOTE_OUT}" > "${FLAGS_update_log}"
|
|
}
|
|
|
|
|
|
# Returns ${1} reported by the update client e.g. PROGRESS, CURRENT_OP.
|
|
function get_update_var {
|
|
remote_sh "${UPDATER_BIN} --status 2> /dev/null |
|
|
grep ${1} |
|
|
cut -f 2 -d ="
|
|
echo "${REMOTE_OUT}"
|
|
}
|
|
|
|
# Returns the current status / progress of the update engine.
|
|
# This is expected to run in its own thread.
|
|
function status_thread {
|
|
local timeout=5
|
|
# Let update engine receive call to ping the dev server.
|
|
info "Devserver handling ping. Check ${FLAGS_server_log} for more info."
|
|
sleep ${timeout}
|
|
|
|
# The devserver generates images when the update engine checks for updates.
|
|
while [ $(get_update_var CURRENT_OP) = ${UPDATER_UPDATE_CHECK} ]; do
|
|
echo -n "." && sleep ${timeout}
|
|
done
|
|
|
|
info "Update generated. Update engine downloading update."
|
|
while [ $(get_update_var CURRENT_OP) = ${UPDATER_DOWNLOADING} ]; do
|
|
echo "Download progress $(get_update_var PROGRESS)" && sleep ${timeout}
|
|
done
|
|
|
|
info "Download complete."
|
|
}
|
|
|
|
|
|
function run_auto_update {
|
|
# Truncate the update log so our log file is clean.
|
|
truncate_update_log
|
|
|
|
local update_args="$(get_update_args "$(get_devserver_url)")"
|
|
info "Starting update using args ${update_args}"
|
|
|
|
# Sets up a secondary thread to track the update progress.
|
|
status_thread &
|
|
local status_thread_pid=$!
|
|
trap "kill ${status_thread_pid} && cleanup" EXIT
|
|
|
|
# Actually run the update. This is a blocking call.
|
|
remote_sh "${UPDATER_BIN} ${update_args}"
|
|
|
|
# Clean up secondary thread.
|
|
! kill ${status_thread_pid} 2> /dev/null
|
|
trap cleanup EXIT
|
|
|
|
# We get the log file now.
|
|
get_update_log
|
|
|
|
local update_status="$(get_update_var CURRENT_OP)"
|
|
if [ "${update_status}" = ${UPDATER_NEED_REBOOT} ]; then
|
|
info "Autoupdate was successful."
|
|
return 0
|
|
else
|
|
warn "Autoupdate was unsuccessful. Status returned was ${update_status}."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function verify_image {
|
|
info "Verifying image."
|
|
"${SCRIPTS_DIR}/mount_gpt_image.sh" --from "$(dirname ${IMAGE_PATH})" \
|
|
--image "$(basename ${IMAGE_PATH})" \
|
|
--read_only
|
|
|
|
local lsb_release=$(cat /tmp/m/etc/lsb-release)
|
|
info "Verifying image with release:"
|
|
echo ${lsb_release}
|
|
|
|
"${SCRIPTS_DIR}/mount_gpt_image.sh" --unmount
|
|
|
|
remote_sh "cat /etc/lsb-release"
|
|
info "Remote image reports:"
|
|
echo ${REMOTE_OUT}
|
|
|
|
if [ "${lsb_release}" = "${REMOTE_OUT}" ]; then
|
|
info "Update was successful and image verified as ${lsb_release}."
|
|
return 0
|
|
else
|
|
warn "Image verification failed."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function main() {
|
|
assert_outside_chroot
|
|
|
|
cd $(dirname "$0")
|
|
|
|
FLAGS "$@" || exit 1
|
|
eval set -- "${FLAGS_ARGV}"
|
|
|
|
set -e
|
|
|
|
if [ ${FLAGS_verify} -eq ${FLAGS_TRUE} ] && \
|
|
[ -n "${FLAGS_update_url}" ]; then
|
|
warn "Verify is not compatible with setting an update url."
|
|
FLAGS_verify=${FLAGS_FALSE}
|
|
fi
|
|
|
|
trap cleanup EXIT
|
|
|
|
TMP=$(mktemp -d /tmp/image_to_live.XXXX)
|
|
|
|
remote_access_init
|
|
|
|
if [ "$(get_update_var CURRENT_OP)" != "${UPDATER_IDLE}" ]; then
|
|
warn "Machine is in a bad state. Rebooting it now."
|
|
remote_reboot
|
|
fi
|
|
|
|
if [ -z "${FLAGS_update_url}" ]; then
|
|
# Start local devserver if no update url specified.
|
|
start_dev_server
|
|
fi
|
|
|
|
if [ ${FLAGS_update} -eq ${FLAGS_TRUE} ] && ! run_auto_update; then
|
|
warn "Dumping update_engine.log for debugging and/or bug reporting."
|
|
tail -n 200 "${FLAGS_update_log}" >&2
|
|
die "Update was not successful."
|
|
fi
|
|
|
|
if [ ${FLAGS_update_stateful} -eq ${FLAGS_TRUE} ] && \
|
|
! run_stateful_update; then
|
|
die "Stateful update was not successful."
|
|
fi
|
|
|
|
remote_reboot
|
|
|
|
if [[ ${FLAGS_update_hostkey} -eq ${FLAGS_TRUE} ]]; then
|
|
local known_hosts="${HOME}/.ssh/known_hosts"
|
|
cp "${known_hosts}" "${known_hosts}~"
|
|
grep -v "^${FLAGS_remote} " "${known_hosts}" > "${TMP}/new_known_hosts"
|
|
cat "${TMP}/new_known_hosts" "${TMP_KNOWN_HOSTS}" > "${known_hosts}"
|
|
chmod 0640 "${known_hosts}"
|
|
info "New updated in ${known_hosts}, backup made."
|
|
fi
|
|
|
|
remote_sh "grep ^CHROMEOS_RELEASE_DESCRIPTION= /etc/lsb-release"
|
|
if [ ${FLAGS_verify} -eq ${FLAGS_TRUE} ]; then
|
|
verify_image
|
|
else
|
|
local release_description=$(echo ${REMOTE_OUT} | cut -d '=' -f 2)
|
|
info "Update was successful and rebooted to $release_description"
|
|
fi
|
|
|
|
print_time_elapsed
|
|
|
|
exit 0
|
|
}
|
|
|
|
main $@
|