From ac1803fc00c29a1c0af82c7291981b110d414c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= Date: Wed, 29 Oct 2025 17:55:38 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20add=20some=20content?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/extensions.json | 5 + LICENSE | 25 +++- bin/citrix-update | 252 ++++++++++++++++++++++++++++++++++++++++ bin/system-update | 66 +++++++++++ conf/profile | 25 ++++ conf/zshenv | 3 + 6 files changed, 371 insertions(+), 5 deletions(-) create mode 100644 .vscode/extensions.json create mode 100755 bin/citrix-update create mode 100755 bin/system-update create mode 100644 conf/profile create mode 100644 conf/zshenv diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..1b1404b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "timonwong.shellcheck" + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index ff458cb..fdddb29 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,24 @@ -MIT License +This is free and unencumbered software released into the public domain. -Copyright (c) 2025 jmm +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +For more information, please refer to diff --git a/bin/citrix-update b/bin/citrix-update new file mode 100755 index 0000000..ccebff8 --- /dev/null +++ b/bin/citrix-update @@ -0,0 +1,252 @@ +#!/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" + +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" "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} &> /dev/null)" \ + && [ "${version_available}" = "${version_installed}" ]; then + echo "Newest version already installed!" >&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 diff --git a/bin/system-update b/bin/system-update new file mode 100755 index 0000000..9afbc24 --- /dev/null +++ b/bin/system-update @@ -0,0 +1,66 @@ +#!/bin/sh + +# name: system-update +# summary: - runs all relevant update commands for a machine +# - automatically detects and handles apt, snap and flatpak +# - fully POSIX shell compliant +# params: none + +# check if a command is available +has_command() { # $command + command -v "$1" 1>/dev/null 2>/dev/null +} + +# run self as root +if [ "$(id -u)" -ne 0 ]; then + if has_command sudo; then + # shellcheck disable=SC2068 + exec sudo "$(readlink -f "$0")" $@ + else + echo "This script is designed to be run as root!" + exit 1 + fi +fi + +# handle apt packages +if has_command apt-get; then + set -ex + # update package sources + apt-get --yes update + # upgrade installed packages + apt-get --yes dist-upgrade + # remove unused dependencies + apt-get --yes --purge autoremove + # remove leftover local archives + apt-get --yes autoclean + # remove leftover config files + apt-get --yes purge '?config-files' + set +ex +fi + +# handle snaps +if has_command snap; then + set -ex + # upgrade installed snaps + snap refresh + # remove disabled snaps: https://askubuntu.com/a/1040131 + set +x + env LANG=en_US.UTF-8 snap list --all \ + | awk '/disabled/{print $1, $3}' \ + | while read -r snap_name snap_revision; do + set -x + snap remove "${snap_name}" --revision="${snap_revision}" + set +x + done + set +ex +fi + +# handle flatpaks +if has_command flatpak; then + set -ex + # upgrade installed flatpaks + flatpak update --noninteractive + # remove unused flatpaks + flatpak uninstall --noninteractive --unused + set +ex +fi diff --git a/conf/profile b/conf/profile new file mode 100644 index 0000000..655c810 --- /dev/null +++ b/conf/profile @@ -0,0 +1,25 @@ +#!/bin/sh + +_include_bindir_in_path() { + if [ -d "${1}" ]; then + real_bin_dir="$(readlink -f "${1}" )" + export PATH="${real_bin_dir}:${PATH}" + fi +} + +# include ~/bin and ~/bin.d/* in PATH +for bin_candidate in "${HOME}/bin/" "${HOME}/bin.d/"*; do + _include_bindir_in_path "${bin_candidate}" +done + +# ... also include a few well-known bin directories +for bin_location in ".local" ".poetry" ".cargo"; do + _include_bindir_in_path "${HOME}/${bin_location}/bin/" +done + +# ... include ALL OF THE ~/.whatever/bin directories (possibly unsafe) +# for bin_candidate in "${HOME}/."*"/bin/"; do +# _include_bindir_in_path "${bin_candidate}" +# done + +unset -f _include_bindir_in_path diff --git a/conf/zshenv b/conf/zshenv new file mode 100644 index 0000000..e9c28b8 --- /dev/null +++ b/conf/zshenv @@ -0,0 +1,3 @@ +#!/bin/zsh + +alias apt-install-url='function _apt-install-url(){ local destfile="$(mktemp --suffix ".deb")"; wget --quiet --show-progress --output-document "${destfile}" "${1}"; sudo apt install "${destfile}"; rm -f "${destfile}"; unset -f _apt-install-url; }; _apt-install-url'