#!/bin/bash # name: citrix-update # summary: Startup script: Downloads and installs "Citrix Workspace" # params: none ######## # INIT # ######## # find all download links on update page # find corresponding checksums has_command() { # $command command -v "$1" &>/dev/null } needs_commands() { # $cmd1 $cmd2 ... local cmd for cmd in "${@}"; do if ! has_command "${cmd}"; then echo "Command '${cmd}' not available!" >&2 return 1 fi done return 0 } needs_commands "xidel" "curl" || exit 1 echo -n "Downloading citrix update page ... " >&2 html_data="$(curl -sSfL 'https://www.citrix.com/downloads/workspace-app/linux/workspace-app-for-linux-latest.html')" echo "OK!" >&2 # # XPath playground # echo "${html_data}" | xidel - \ # --silent \ # --printed-node-format text \ # --extract "links:=//div[contains(@class, 'ctx-dl-content')]//a[contains(@class, 'ctx-dl-link') and @rel]/@rel" \ # --extract "csums:=//div[contains(@class, 'ctx-dl-content')]//ul[contains(@class, 'ctx-checksum-list')]/li[1]/text()" # exit 0 # parse using XPath declare -a links csums eval "$( \ echo "${html_data}" | xidel - \ --silent \ --output-format bash \ --extract "links:=//div[contains(@class, 'ctx-dl-content')]//a[contains(@class, 'ctx-dl-link') and @rel]/@rel" \ --extract "csums:=//div[contains(@class, 'ctx-dl-content')]//ul[contains(@class, 'ctx-checksum-list')]/li[1]/text()" \ )" # ensure every link has a checksum if [[ "${!links[*]}" != "${!csums[*]}" ]]; then echo "Links and Checksums don't match up!" >&2 exit 1 fi # postprocess data declare -A citrix_data for key in "${!links[@]}"; do link="${links["${key}"]}" # if link starts with "//" (no protocol), assume https if [[ "${link}" == "//"* ]]; then link="https:${link}" fi # extract checksum only (64 hex digits) csum="$(echo "${csums["${key}"]}" | grep -Eo "\<[[:xdigit:]]{64}\>")" citrix_data["${csum}"]="${link}" done # remove intermediate containers unset links csums ######### # FUNCS # ######### find_link() { # $filename_regex local link_regex="://downloads\.citrix\.com/[[:digit:]]+/${1}\?__gda__=exp=[[:digit:]]+~acl=[^~]+~hmac=[[:digit:]a-f]{64}$" for key in "${!citrix_data[@]}"; do if echo "${citrix_data["${key}"]}" | grep -E "${link_regex}" &>/dev/null; then echo "${key} ${citrix_data["${key}"]}" fi done } find_link_deb() { # $name $arch case "${2}" in x86_64) local arch="amd64" ;; arm64) local arch="arm64" ;; *) local arch="${2}" ;; esac find_link "${1}_[[:digit:]\.]+_${arch}\.deb" } find_link_rpm() { # $name $flavor $arch find_link "${1}(:?-${2})?-[[:digit:]\.]+(:?-[[:digit:]]+)?.${3}\.rpm" } find_link_targz() { # $arch case "${1}" in x86_64) local arch="x64" ;; arm64) local arch="arm64" ;; *) local arch="${1}" ;; esac find_link "linux${arch}-[[:digit:]\.]+\.tar.gz" } # shellcheck disable=SC2155 list_all() { local debs="$(find_link_deb "[[:lower:]]+" "[[:alnum:]]+")" local debc="$(echo "${debs}" | wc -l)" echo "deb:" echo "${debs}" local rpms="$(find_link_rpm "[[:alpha:]]+" "[[:lower:]]+" "[[:alnum:]_]+")" local rpmc="$(echo "${rpms}" | wc -l)" echo "rpm:" echo "${rpms}" local tars="$(find_link_targz "[[:alnum:]]+")" local tarc="$(echo "${tars}" | wc -l)" echo "tar.gz:" echo "${tars}" local anys="$(find_link "[[:alnum:]\._-]+")" local anyc="$(echo "${anys}" | wc -l)" echo "any:" echo "${anys}" if [ $(( debc + rpmc + tarc )) -ne $(( anyc )) ]; then echo "Not all links matched!" >&2 exit 1 fi } download_gui() { # $link $csum $destfile needs_commands "curl" "zenity" "sha256sum" || return 1 echo "Downloading '${1}' to '${3}'" curl -fL -o "${3}" "${1}" & local curlpid=$! yes | zenity \ --progress --pulsate --auto-close \ --title="Downloading" \ --text="Downloading '${3}' ..." & local zenpid=$! while kill -0 "${curlpid}" 2>/dev/null; do if ! kill -0 "${zenpid}" 2>/dev/null; then # progress bar died; ask for abort if zenity \ --question \ --text="Abort Download?"; then kill "${curlpid}" 2>/dev/null || true echo "Download cancelled!" >&2 break fi # restart progress bar yes | zenity \ --progress --pulsate --auto-close \ --title="Downloading" \ --text="Downloading '${3}' ..." & zenpid=$! fi sleep 0.2 done kill "${zenpid}" 2>/dev/null || true wait "${curlpid}" if [ "$( sha256sum "${3}" | cut -d' ' -f1 )" = "${2}" ]; then echo "SHA256 ${2} OK!" >&2 return 0 else echo "SHA256 ${2} mismatch!" >&2 return 1 fi } install_deb() { # $name needs_commands "dpkg-query" "zenity" "gdebi-gtk" || return 1 local arch arch="$(uname -m)" echo "Trying to install ${1} DEB for ${arch}" >&2 local link if ! link="$(find_link_deb "${1}" "${arch}")"; then echo "No DEB found!" >&2 return 1 elif [ "$(echo "${link}" | wc -l)" -ne 1 ]; then echo "More than one DEB found!" >&2 return 1 fi local csum csum="$(echo "${link}" | cut -d' ' -f1)" link="$(echo "${link}" | cut -d' ' -f2)" local version_available version_available="$(echo "${link}" | sed -r 's/^.*\/[^_]+_([[:digit:]\.]+)_[^\.]+\.deb.*$/\1/')" local version_installed if version_installed="$(dpkg-query --show --showformat='${Version}\n' "${1}")" \ && [ "${version_available}" = "${version_installed}" ]; then echo "Newest version already installed!" >&2 zenity \ --notification \ --window-icon="info" \ --text="Citrix \"${1}\": Newest version is installed!" sleep 2 return 0 fi if ! zenity \ --question \ --title="New Version Available" \ --text="Citrix Upgrade Available!\n\nInstall ${1} version ${version_available} now?"; then echo "Installation of ${1} cancelled!" >&2 return 0 fi local destfile destfile="$(mktemp --tmpdir "XXXXX_${1}_${version_available}.deb")" if download_gui "${link}" "${csum}" "${destfile}"; then echo "Download for ${1} successful!" >&2 gdebi-gtk "${destfile}" fi rm -f "${destfile}" echo "Cleaned up '${destfile}'" >&2 return 0 } ######## # MAIN # ######## list_all > /dev/null if has_command "apt"; then install_deb "icaclient" || exit 1 install_deb "ctxusb" || exit 1 fi exit 0