#! /bin/bash shopt -s nullglob [ -e /dracut-state.sh ] && . /dracut-state.sh # Later we could disable "-x" or enable it on request, let's be more verbose in # the initial implementation. Since we have enabled using the plymouth splash # screen the verbose details will not be visible to users by default. set -ex . "$(dirname "$0")"/../lib/live-self-update/conf.sh RESPONSE_FILE="$RUN_DIR/server_response" REG_REPO_FILE="$RUN_DIR/reg_repos" OSR="/etc/os-release" ARCH=$(uname -m) # do the cleanup at the end, trap ensures it runs even if something fails trap cleanup_chroot EXIT # helper function to log important steps (highlighted in the journalctl output) # use "set +x" to avoid logging the message again (it is already logged in the # original caller place so it would be actually logged three times...) # see https://www.freedesktop.org/software/systemd/man/latest/sd-daemon.html # for the log level numbers milestone() { ({ set +x; } 2>/dev/null; echo "<5>$1") } # log warning warning() { ({ set +x; } 2>/dev/null; echo "<4>$1") } # log error error() { ({ set +x; } 2>/dev/null; echo "<3>$1") } # normal message info() { ({ set +x; } 2>/dev/null; echo "<6>$1") } # the proxy might contain a password, make sure it is not displayed or logged # when processing it set +x # get the configured network proxy (empty string if not configured) # see ../lib/dracut/modules.d/98dracut-menu/dracut-cmdline-menu.sh # get the last found, it has the highest priority PROXY=$(grep "\bproxy=" /proc/cmdline /etc/cmdline.d/* | tail -n 1 | sed "s/.*\bproxy=\([^ \t]*\).*/\1/") if [ -n "$PROXY" ]; then if [[ "$PROXY" == *@* ]]; then # looks like there is a username or password in the URL, do not print it milestone "Using authenticated HTTP proxy" else milestone "Using proxy server $PROXY" fi # set the proxy for all zypper and curl calls, for security reasons curl # uses only the lower case name, zypper accepts that as well, # see https://everything.curl.dev/usingcurl/proxies/env.html export http_proxy="$PROXY" export https_proxy="$PROXY" # hide the original URL to avoid leaking the password in URL later PROXY=1 fi set -x # helper for running a command in the live root system, works both when running # in initramfs ($NEWROOT is set) and in already mounted root ($NEWROOT unset) run_command() { if [ -z "$NEWROOT" ]; then "$@" else "$NEWROOT/usr/bin/chroot" "$NEWROOT" "$@" fi } # Prepare the system root to be used by scripts before switching to it. prepare_chroot() { if [ -n "$NEWROOT" ]; then milestone "Preparing for the self update..." # make system directories active also in the chroot directory mount -o bind /run "$NEWROOT"/run mount -o bind /sys "$NEWROOT"/sys mount -o bind /proc "$NEWROOT"/proc mount -o bind /dev "$NEWROOT"/dev [ -f /run/NetworkManager/resolv.conf ] && cp /run/NetworkManager/resolv.conf "$NEWROOT"/etc/resolv.conf fi # remove any existing self-update repositories, probably left-overs from the previous run rm -f "$NEWROOT"/etc/zypp/repos.d/self-update-*.repo } # Revert the changes done by prepare_chroot() and restore the system to the # original state, called from bash "trap" handler cleanup_chroot() { local result="$?" if [ "$result" != "0" ]; then error "Error: Self update failed! (exit status: $result)" else milestone "Self update successfully finished" fi # log the result to be checked by openQA or Agama itself echo "$result" > "$RESULT_FILE" if [ -n "$NEWROOT" ]; then rm -f "$NEWROOT"/etc/resolv.conf # do not fail if not mounted findmnt "$NEWROOT"/run > /dev/null && umount "$NEWROOT"/run findmnt "$NEWROOT"/sys > /dev/null && umount "$NEWROOT"/sys findmnt "$NEWROOT"/proc > /dev/null && umount "$NEWROOT"/proc findmnt "$NEWROOT"/dev > /dev/null && umount "$NEWROOT"/dev fi # return the original exit value exit "$result" } # read the data from /etc/os-release file (source it) read_os_release() { # get the product data if [ -f "$OSR" ]; then . "$OSR" else error "File $OSR is missing!" exit 1 fi } # ask the registration server (SCC or RMT) for the self-update URL query_reg_server() { local server # custom server (RMT) if [ -f "$SERVER_FILE" ]; then server=$(< "$SERVER_FILE") # default server (SCC) elif [ -f "$CONFIG_DEFAULT_REG_SERVER_FILE" ]; then server=$(< "$CONFIG_DEFAULT_REG_SERVER_FILE") fi if [ -n "$server" ]; then read_os_release local query query="$server/connect/repositories/installer?identifier=$NAME&version=$VERSION_ID&arch=$ARCH" info "Running query $query...." local curl_options curl_options=(--silent --globoff --location --retry 3 --retry-connrefused \ --connect-timeout 20 --max-time 10 --fail --show-error --output "$RESPONSE_FILE") if grep -q "\binst.self_update_ssl=0\b" /run/agama/cmdline.d/agama.conf; then warning "Disabling SSL check for $server" curl_options+=("--insecure") fi if [ -n "$PROXY" ]; then # automatically detect the proxy authentication method (basic/digest) curl_options+=("--proxy-anyauth") fi if curl "${curl_options[@]}" -- "$query" && [ -s "$RESPONSE_FILE" ]; then info "Found self-update repository: $(jq -r ".[] | select(.installer_updates==true) | .description" "$RESPONSE_FILE") " jq -r ".[] | select(.installer_updates==true) | .url" "$RESPONSE_FILE" > "$REG_REPO_FILE" else warning "Contacting the registration server failed" # SCC/RMT query failed and no fallback configured if [ ! -s "$CONFIG_FALLBACK_FILE" ]; then error "Cannot obtain the self-update repository" exit 1 fi fi else milestone "Registration server not defined" fi } # add the self-update repositories into the system add_update_repos() { milestone "Adding self update repositories..." # add the self-update repositories while read -r update_url; do if [ -n "$update_url" ]; then local repo repo="self-update-$index" if grep -q "\binst.self_update_ssl=0\b" /run/agama/cmdline.d/agama.conf; then warning "Disabling SSL check for repository $update_url" # add "ssl_verify=no" query to the repository URL update_url=$(url_add_query "$update_url" "ssl_verify=no") fi local zypper_options zypper_options=() # allow using unsigned repository if grep -q "\binst.self_update_unsigned_repo=1\b" /run/agama/cmdline.d/agama.conf; then warning "Unsigned repository is allowed" zypper_options+=("--gpgcheck-allow-unsigned-repo") fi # completely disable GPG checks, not recommended, if possible the options # above should be preferred if grep -q "\binst.self_update_gpg=0\b" /run/agama/cmdline.d/agama.conf; then warning "Disabling all GPG checks!" zypper_options+=("--no-gpgcheck") fi run_command zypper addrepo "${zypper_options[@]}" -f "$update_url" "$repo" self_update_repos+=("$repo") ((++index)) fi done < "$1" } # Reads the configured fallback repository, the URL might contain several # placeholders. This way, it works across multiple architectures and does not # contain a hardcoded version (the fallback URL should work with SLES-16.0, # SLES-16.1, SLES-16.2...) read_fallback_repos() { # Substituting $arch pattern with the architecture of the current system. # Substituting these variables with the /etc/os-release content: # $os_release_name => NAME # $os_release_id => ID # $os_release_version => VERSION # $os_release_version_id => VERSION_ID # IMPORTANT NOTE: the "VERSION_ID" replacement must happen *before* # the "VERSION" replacement because it partially matches that string! sed -e "s/\$os_release_name/$NAME/g" -e "s/\$os_release_id/$ID/g" \ -e "s/\$os_release_version_id/$VERSION_ID/g" \ -e "s/\$os_release_version/$VERSION/g" \ -e "s/\$arch/$ARCH/g" "$CONFIG_FALLBACK_FILE" > "$FALLBACK_REPOS_FILE" } # Applies all the updates # # Reads the URL of the updates from $AGAMA_DUD_INFO and process each one. install_updates() { index=0 self_update_repos=() if [ -f "$REPO_FILE" ]; then prepare_chroot add_update_repos "$REPO_FILE" else # try SCC/RMT query_reg_server # repo from the registration server if [ -f "$REG_REPO_FILE" ]; then if [ -s "$REG_REPO_FILE" ]; then prepare_chroot add_update_repos "$REG_REPO_FILE" else milestone "No repository found in the registration server, skipping self-update" exit 0 fi # use a fallback URL elif [ -s "$CONFIG_FALLBACK_FILE" ]; then milestone "Using fallback repository" read_fallback_repos prepare_chroot add_update_repos "$FALLBACK_REPOS_FILE" # no self-update repo found, exit (in the openSUSE installer) else milestone "No self-update repository configured, skipping self-update" exit 0 fi fi local zypper_options # automatically import the repository GPG key if grep -q "\binst.self_update_import_key=1\b" /run/agama/cmdline.d/agama.conf; then warning "Enabled GPG key autoimport" zypper_options="--gpg-auto-import-keys" fi milestone "Loading repositories..." run_command zypper $zypper_options refresh "${self_update_repos[@]}" # build the list of repository options passed to zypper repo_options=() for repo in "${self_update_repos[@]}"; do repo_options+=("--repo" "$repo") done milestone "Updating packages..." # update the system only using the predefined self-update repositories, # allow downgrading and changing vendor (can be useful for testing) run_command zypper --non-interactive update --allow-downgrade --allow-vendor-change --details --no-recommends "${repo_options[@]}" milestone "Cleaning the system..." # delete the repository caches, not needed anymore, saves space in RAM disk run_command zypper clean --all # disable the self update repositories (keep them for debugging purposes) for repo in "${self_update_repos[@]}"; do run_command zypper modifyrepo --disable "$repo" done } # add a query parameter to URL # $1 - base URL ("http://example.com") # $2 - the query part ("foo=bar") # returns "http://example.com?foo=bar" url_add_query() { local base local query # split the string at the "?" separator query="${1#*\?}" base="${1%\?*}" # if the "?" separator is missing then "$query" equals to "$base" if [ -z "$query" ] || [ "$query" = "$base" ]; then # no query present, add a query part to the URL echo "${base}?${2}" else # a query is already present, append the new part echo "${base}?${query}&${2}" fi } install_updates