#!/bin/sh #description: get static linux binaries from s.minos.io and mirrors #usage: static-get package #example: static-get git #git-1.8.2.1-3.tar.gz:sha512sum VERSION="2020.02.11-00:21" LATEST_URI_BASENAME="https://raw.githubusercontent.com/minos-org/minos-static/master" _usage() { printf "%b\\n" "Usage: ${progname} [OPTION]... PACKAGE ..." printf "%b\\n" "Retrieve static linux binaries from s.minos.io and mirrors." printf "\\n" printf "%b\\n" " -d, --download [dir] write in the specified directory" printf "%b\\n" " -o, --output [file] write to file" printf "%b\\n" " -s, --search [pattern] search packages by pattern" printf "%b\\n" " -x, --extract extract after download" printf "%b\\n" " -i, --install install after download" printf "%b\\n" " -p, --prefix [dir] specifies an install location for the package" printf "%b\\n" " -n, --dry-run perform a trial run with no changes made" printf "%b\\n" " -c, --clean-cache remove temporal files" printf "%b\\n" " -r, --remove removes an installed package" printf "\\n" printf "%b\\n" " -m, --mirror [s.minos.io] set mirror" printf "%b\\n" " -a, --arch [x86_64|i686] set architecture" printf "%b\\n" " -f, --format [gz|bz2|xz] set compress format" printf "%b\\n" " -k, --checksum [sha512|md5] set checksum" printf "%b\\n" " -t, --distro [all|bifrost|morpheus|rlsd2|misc] set distro" printf "\\n" printf "%b\\n" " -U, --update update this program to latest version" printf "%s\\n" " -V, --version display version" printf "%b\\n" " -v, --verbose turn on verbose mode" printf "%b\\n" " -q, --quiet suppress non-error messages" printf "%b\\n" " -h, --help show this message and exit" } _basename() { [ -z "${1}" ] && return 1 || _basename__name="${1}" [ -z "${2}" ] || _basename__suffix="${2}" case "${_basename__name}" in /*|*/*) _basename__name="${_basename__name##*/}" esac if [ -n "${_basename__suffix}" ] && [ "${#_basename__name}" -gt "${#2}" ]; then _basename__name="${_basename__name%$_basename__suffix}" fi printf "%s\\n" "${_basename__name}" } _die() { [ -z "${1}" ] || printf "%s\\n" "${*}" >&2 _usage >&2; exit 1 } _update() { _u__current_file="$(cd "$(dirname "${0}")" && pwd)/${progname}" _u__current_version_long="$(awk -F\" \ '/VERSION=/{print $2;exit}' "${_u__current_file}" 2>/dev/null)" _u__current_version="$(printf "%s\\n" "${_u__current_version_long}" | \ awk '{gsub(/[\.:]/,"");gsub(/-/,"");print;exit}' 2>/dev/null)" [ -z "${_u__current_version}" ] && printf "%s\\n%s\\n%s\\n" \ "ERROR: Failed to detect current version, please update manually" \ "${progname} = ${_u__current_file}" \ "${progname} version = ${_u__current_version}" >&2 && return 1 command -v "wget" >/dev/null 2>&1 || command -v "curl" >/dev/null 2>&1 || \ { printf "%s\\n" "ERROR: Install either 'wget' or 'curl' to upgrade" >&2; return 1; } _u__url="${LATEST_URI_BASENAME}/${progname}" _u__tmpfile="/tmp/${progname}.${$}.update" _u__error_msg="$(wget -q -O- --no-check-certificate "${_u__url}" \ > "${_u__tmpfile}" 2>&1 || curl -L -s "${_u__url}" \ > "${_u__tmpfile}" 2>&1)" || { printf "%s\\n%s\\n" \ "ERROR: Failed to fetch update, please try later or update manually" \ "${_u__error_msg}" >&2; return 1; } _u__update_version_long="$(awk -F\" \ '/VERSION=/{print $2;exit}' "${_u__tmpfile}" 2>/dev/null)" _u__update_version="$(printf "%s\\n" "${_u__update_version_long}" | awk \ '{gsub(/[\.:]/,"");gsub(/-/,"");print;exit}' 2>/dev/null)" [ -n "${_u__update_version}" ] || _u__update_version="0" if [ "${_u__current_version}" -lt "${_u__update_version}" ]; then printf "%s %s\\n" "Updating from version" \ "${_u__current_version_long} to ${_u__update_version_long} ..." chmod +x "${_u__tmpfile}" if ! mv -f "${_u__tmpfile}" "${_u__current_file}" 2>/dev/null; then printf "%s\\n" "ERROR: no write permissions on ${_u__current_file}" >&2 printf "%s\\n" "INFO : trying with sudo ..." >&2 if command -v "sudo" >/dev/null 2>&1; then sudo mv "${_u__tmpfile}" "${_u__current_file}" || return 1 else printf "%s\\n" "ERROR: sudo isn't available, exiting ..." >&2 rm -rf "${_u__tmpfile}" return 1 fi fi printf "%s\\n" "${progname} is up-to-date (${_u__update_version_long})" return 0 fi printf "%s\\n" "${progname} is up-to-date (${_u__current_version_long})" rm -rf "${_u__tmpfile}" } _show_version() { printf "%s\\n" "${progname} ${VERSION}" } _mkdir_p() { #portable mkdir -p [ -n "${1}" ] || return 1 for _mkdir_p__dir in "${@}"; do [ -d "${_mkdir_p__dir}" ] && continue _mkdir_p__IFS="${IFS}" IFS="/" #shellcheck disable=SC2086 set -- ${_mkdir_p__dir} IFS="${_mkdir_p__IFS}" ( case "${_mkdir_p__dir}" in /*) cd / || return; shift ;; esac for _mkdir_p__subdir in "${@}"; do [ -z "${_mkdir_p__subdir}" ] && continue if [ -d "${_mkdir_p__subdir}" ] || mkdir "${_mkdir_p__subdir}"; then if cd "${_mkdir_p__subdir}"; then : else printf "%s\\n" "_mkdir_p: Can't enter ${_mkdir_p__subdir} while creating ${_mkdir_p__dir}" return 1 fi else return 1 fi done ) done } _verbose() { [ -z "${1}" ] && return 1 [ -n "${verbose}" ] && printf "%s\\n" "${*}" || : } _cat() { [ -z "${1}" ] && return 1 [ -z "${global_output}" ] && output_fname="$(_basename "${1}")" || { [ -z "${output_counter}" ] && output_counter="0" } if [ -z "${global_output}" ]; then cat "${1}" > "${directory}/${output_fname}" else case "${output_fname}" in -) cat "${1}"; return 0 ;; *\{#\}*) output_counter="$((output_counter + 1))" cat "${1}" >> "${directory}/$(printf "%s\\n" "${output_fname}" | \ sed "s:{#}:${output_counter}:")" ;; *) cat "${1}" >> "${directory}/${output_fname}" ;; esac fi printf "%s\\n" "${directory}/${output_fname}" | sed 's:\./::' | sed "s:{#}:${output_counter}:" } _uniq_id_mirror() { printf "%s\\n" "${1%/}" | sed 's|/|##|g' } _fetch() { case "${#}" in 1) $retriever_bin "${1}" ;; 2) case "${retriever_bin}" in wget*) $retriever_bin -O "${2}" "${1}" >/dev/null || \ $retriever_bin -O "${2}" "http://${1}" >/dev/null ;; curl*) ${retriever_bin% -O} -o "${2}" "${1}" >/dev/null ;; *) return 1 ;; esac ;; *) return 1 ;; esac } _get_available_distros() { #$1 => mirror, eg: s.minos.io|s.minos.io/archive [ -z "${1}" ] && return 1 [ -z "${uniq_id_mirror}" ] && uniq_id_mirror="$(_uniq_id_mirror "${1}")" _mkdir_p "${tmpcache}.${uniq_id_mirror}" if [ ! -f "${tmpcache}.${uniq_id_mirror}/index.html" ]; then cd "${tmpcache}.${uniq_id_mirror}" || return _fetch "${1}" index.html fi awk -F'"' '/href/ {if ($2 != "../" && $2 != "robots.txt") {sub(/\//,"",$2);print $2}}' \ "${tmpcache}.${uniq_id_mirror}/index.html" 2>/dev/null } _get_available_archs() { #$1 => mirror, eg: s.minos.io|s.minos.io/archive #$2 => distro, eg: bifrost [ -z "${2}" ] && return 1 [ -z "${uniq_id_mirror}" ] && uniq_id_mirror="$(_uniq_id_mirror "${1}")" _mkdir_p "${tmpcache}.${uniq_id_mirror}/${2}" if [ ! -f "${tmpcache}.${uniq_id_mirror}/${2}/index.html" ]; then cd "${tmpcache}.${uniq_id_mirror}/${2}" || return _fetch "${1}/${2}" index.html fi awk -F'"' '/href/ {if ($2 != "../") {sub(/\//,"",$2);print $2}}' \ "${tmpcache}.${uniq_id_mirror}/${2}/index.html" 2>/dev/null | \ grep -v "lock" | grep -v "archive/" } _get_package_indexes() { #$1 => mirror, eg: s.minos.io|s.minos.io/archive [ -z "${1}" ] && return 1 [ -z "${available_distros}" ] && available_distros="$(_get_available_distros "${1}")" [ -z "${uniq_id_mirror}" ] && uniq_id_mirror="$(_uniq_id_mirror "${1}")" for gdistro in ${available_distros}; do available_arch="$(_get_available_archs "${1}" "${gdistro}")" for garch in ${available_arch}; do _mkdir_p "${tmpcache}.${uniq_id_mirror}/${gdistro}/${garch}" if [ ! -f "${tmpcache}.${uniq_id_mirror}/${gdistro}/${garch}/${checksum}sum.txt" ]; then ( cd "${tmpcache}.${uniq_id_mirror}/${gdistro}/${garch}" || return if _fetch "${1}/${gdistro}/${garch}/${checksum}sum.tar.${compress_format}"; then ${compress_bin} < "${checksum}sum.tar.${compress_format}" | tar -xf - elif _fetch "${1}/${gdistro}/${garch}/${checksum}sum.txt"; then : else head -3 "${checksum}sum.tar.${compress_format}" >&2 fi ) fi done done } _set_defaults() { [ -z "${mirror}" ] && mirror="s.minos.io/archive" [ -z "${uniq_id_mirror}" ] && uniq_id_mirror="$(_uniq_id_mirror "${mirror}")" [ -z "${distro}" ] && distro="all" [ -z "${arch}" ] && arch="$(uname -m)" [ -z "${directory}" ] && directory="." [ X"${output_fname}" = X"-" ] && directory="." if [ -z "${dry_run}" ]; then _mkdir_p "${directory}" || { printf "%s\\n" "unable to create ${directory}, exiting"; exit 1; } fi case "${arch}" in x86|386|486|586|i86|32) arch="i686" ;; amd64|64) arch="x86_64" ;; i686|x86_64) : ;; *) _verbose "${arch} is not a valid architecture, using system default: $(uname -m)"; arch="$(uname -m)" ;; esac case "${compress_format}" in gz) compress_bin="zcat" ;; bz2) compress_bin="bzcat" ;; xz) compress_bin="xzcat" ;; *) if command -v "xzcat" >/dev/null 2>&1; then compress_format="xz" compress_bin="xzcat" elif command -v "xz" >/dev/null 2>&1; then compress_format="xz" compress_bin="xz -cd" elif command -v "bzcat" >/dev/null 2>&1; then compress_format="bz2" compress_bin="bzcat" elif command -v "bzip2" >/dev/null 2>&1; then compress_format="bz2" compress_bin="bzip2 -cd" elif command -v "zcat" >/dev/null 2>&1; then compress_format="gz" compress_bin="zcat" elif command -v "gzip" >/dev/null 2>&1; then compress_format="gz" compress_bin="gzip -cd" else printf "%s\\n" "install either 'gzip', 'bzip2' or 'xz' to run this program" >&2 exit 1 fi ;; esac case "${checksum}" in sha512) checksum_bin="shasum -a 512" ;; md5) checksum_bin="md5sum" ;; *) if command -v "shasum" >/dev/null 2>&1; then checksum="sha512" checksum_bin="shasum -a 512" elif command -v "md5sum" >/dev/null 2>&1; then checksum="md5" checksum_bin="md5sum" else printf "%s\\n" "install either 'md5sum' or 'shasum' to run this program" >&2 exit 1 fi ;; esac if command -v "wget" >/dev/null 2>&1; then retriever_bin="wget -q" elif command -v "curl" >/dev/null 2>&1; then retriever_bin="curl -L -s -O" elif command -v "fetch" >/dev/null 2>&1; then retriever_bin="fetch" else printf "%s\\n" "install either 'wget', 'curl' or 'fetch' to run this program" >&2 exit 1 fi available_distros="$(_get_available_distros "${mirror}")" if [ -z "${available_distros}" ]; then printf "%s\\n" "no available packages at ${mirror}, it may be an incorrect url or could be down temporarily" >&2 exit 1 fi } _static_search() { #$1 => search pattern [ -z "${1}" ] && return 1 _set_defaults _get_package_indexes "${mirror}" _ssearch__sanitazed_pattern="$(printf "%s\\n" "${1}" | \ tr -cd 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890.-')" [ X"${distro}" = X"all" ] && distro="" _ssearch__result="$(find "${tmpcache}.${uniq_id_mirror}"/ -name "${checksum}sum.txt" | \ grep -- "${distro}" | xargs grep -- "${_ssearch__sanitazed_pattern}" | grep -- "${arch}" | \ grep -- "tar.${compress_format}" | sed "s:${tmpcache}.::g;s:##:/:g;s:/${checksum}sum.txt\:: :g")" if [ -n "${_ssearch__result}" ]; then if [ "${verbose}" ]; then printf "%s\\n" "${_ssearch__result}" | awk '{printf "%s %s %s\n", $3, $2, $1}' | \ grep -- "${1}" | awk '{printf "%s:%s:%s\n", $3, $1, $2}' else printf "%s\\n" "${_ssearch__result}" | awk '{printf "%s:%s\n", $3, $2}' | \ grep -- "${1}" | cat fi else return 1 fi } _extract() { _mkdir_p "${directory}/${_sget__bfile%%.tar*}" (cd "${directory}/${_sget__bfile%%.tar*}" && \ ${compress_bin} < "../${_sget__bfile}" | tar -xf -) [ "${1}" ] && printf "%s\\n" "${directory}/${_sget__bfile%%.tar*}/" | sed 's:\./::' } _static_get() { [ -z "${1}" ] && return 1 || _set_defaults for package in "${@}"; do #extra_params="-m ${mirror} -a ${arch} -f ${compress_format} -c ${checksum} -t ${distro}" #_sget__file="$("${0}" ${extra_params} -v -n -s "^${package}" | sort -nr |head -1)" _sget__file="$(verbose=1 dry_run=1 _static_search "^${package}" | sort -nr |head -1)" [ -z "${_sget__file}" ] && _verbose "No matches for '${package}', skipping..." && continue [ -z "${uniq_id_mirror}" ] && uniq_id_mirror="$(_uniq_id_mirror "${mirror}")" _sget__hash="${_sget__file##*:}" _sget__file="${_sget__file%:$_sget__hash}" #remove hash _sget__file="$(printf "%s\\n" "${_sget__file}" | tr ':' '/')" _sget__bfile="$(_basename "${_sget__file}")" for i in 1 2; do #download on first run, write/extract on second if [ "${dry_run}" ]; then if [ "${output_fname}" ]; then case "${output_fname}" in *\{#\}*) [ -z "${output_counter}" ] && output_counter="0" output_counter="$((output_counter + 1))" ;; esac printf "%s\\n" "${directory}/${output_fname}" | sed 's:\./::' | sed "s:{#}:${output_counter}:" else printf "%s\\n" "${directory}/${_sget__bfile}" | sed 's:\./::' [ "${extract}" ] && printf "%s\\n" "${directory}/${_sget__bfile%%.tar*}/" | sed 's:\./::' fi break fi if [ -f "${tmpcache}.${uniq_id_mirror}/${_sget__bfile}" ]; then _sget__hash_new="$($checksum_bin "${tmpcache}.${uniq_id_mirror}/${_sget__bfile}" | awk '{print $1}')" _sget__bfile_sub1="$(printf "%s\\n" "${_sget__bfile}" | cut -d "-" -f 1)" if [ "${remove}" ]; then if [ -e ~/.static-get/"${_sget__bfile_sub1}" ]; then if [ -w ~/.static-get/"${_sget__bfile_sub1}" ]; then while read -r line; do rm -rf "${line}" done < ~/.static-get/"${_sget__bfile_sub1}" rm ~/.static-get/"${_sget__bfile_sub1}" elif command -v "sudo" >/dev/null 2>&1; then while read -r line; do sudo rm -rf "${line}" done < ~/.static-get/"${_sget__bfile_sub1}" sudo rm ~/.static-get/"${_sget__bfile_sub1}" else printf "%s\\n" "ERROR: sudo isn't available, exiting ..." >&2 fi elif [ "${i}" = "1" ]; then printf "ERROR: package %s could not be removed.\\n" "${_sget__bfile_sub1}" >&2 fi elif [ "${_sget__hash}" = "${_sget__hash_new}" ]; then _cat "${tmpcache}.${uniq_id_mirror}/${_sget__bfile}" # Install the package if [ "${install}" ]; then _extract "${extract}" _mkdir_p ~/.static-get if [ -w "${prefix}/" ]; then cd "${_sget__bfile%%.tar*}" && \ cp -vr -- * "${prefix}/" | cut -d "'" -f 4 > ~/.static-get/"${_sget__bfile_sub1}" || return 1 elif command -v "sudo" >/dev/null 2>&1; then cd "${_sget__bfile%%.tar*}" && \ sudo cp -vr -- * "${prefix}/" | cut -d "'" -f 4 > ~/.static-get/"${_sget__bfile_sub1}" || return 1 else printf "%s\\n" "ERROR: sudo isn't available, exiting ..." >&2 fi if ! [ "${extract}" ]; then cd "../" && rm -rf "${_sget__bfile%%.tar*}" fi elif [ "${extract}" ]; then _extract "${extract}" fi break else printf "'%s' doesn't match hashsum => '%s', clean the cache (%s) and try again\\n" \ "${directory}/${_sget__bfile}" "${_sget__hash}" \ "$(_usage | awk '/remove temporal/ {sub(/,/, ""); print $1 "|" $2}')" | sed 's:\./::' rm -f "${tmpcache}.${uniq_id_mirror}/${_sget__bfile}" fi else (cd "${tmpcache}.${uniq_id_mirror}/" && _fetch "${_sget__file}") fi done done } progname="$(_basename "${0}")" tmpcache="/tmp/static-get" if [ ! -t 0 ]; then #there is input comming from pipe or file, add it to the end of $@ #shellcheck disable=SC2046 set -- "${@}" $(cat) fi [ "${#}" -eq "0" ] && _die for arg in "${@}"; do #parse options case "${arg}" in -h|--help) _usage; exit ;; -U|--update) _update; exit "${?}" ;; -V|--version) _show_version; exit "${?}" ;; -q|--quiet) quiet="1"; shift ;; -v|--verbose) verbose="1"; shift ;; -x|--extract) extract="1"; shift ;; -i|--install) install="1"; shift ;; -r|--remove) remove="1"; shift ;; -n|--dry-run) dry_run="1"; shift ;; '-p'|'--prefix'|-p*|--prefix*) case "${arg}" in '-p'|'--prefix') if [ "${#}" -gt "1" ]; then case "${2}" in '-') : ;; #special stdout character -*) printf "Option '%s' requires a parameter %s\\n" "${arg}" "${2}";; '.') prefix="${PWD}" ; shift; shift; break ;; '..')prefix="${PWD}/../" ; shift; shift; break ;; esac shift; prefix="${1}"; [ "${1}" ] && shift else prefix="${PWD}" fi ;; -p*) prefix="${1#-p}"; shift ;; --prefix*) prefix="${1#--prefix}"; shift ;; esac ;; '-d'|'--download'|-d*|--download*) case "${arg}" in '-d'|'--download') if [ "${#}" -gt "1" ]; then case "${2}" in -*) _die "Option '${arg}' requires a parameter";; esac shift; directory="${1}"; [ "${1}" ] && shift else _die "Option '${arg}' requires a parameter" fi ;; -d*) directory="${1#-d}"; shift ;; --download*) directory="${1#--download}"; shift ;; esac ;; '-o'|'--output'|-o*|--output*) case "${arg}" in '-o'|'--output') if [ "${#}" -gt "1" ]; then case "${2}" in '-') : ;; #special stdout character -*) _die "Option '${arg}' requires a parameter" ;; esac shift; output_fname="${1}"; global_output="1"; [ "${1}" ] && shift else _die "Option '${arg}' requires a parameter" fi ;; -o*) output_fname="${1#-o}"; shift; global_output="1" ;; --output*) output_fname="${1#--output}"; shift; global_output="1" ;; esac ;; '-s'|'--search'|-s*|--search*) case "${arg}" in '-s'|'--search') if [ "${#}" -gt "1" ]; then case "${1}" in -*) shift; for opt in "${@}"; do #last parameter as search regex search_pattern="${opt}" done ;; *) shift; search_pattern="${1}" [ "${1}" ] && shift ;; esac else search_pattern="." fi ;; -s*) search_pattern="${1#-s}"; shift ;; --search*) search_pattern="${1#--search}"; shift ;; esac ;; '-a'|'--arch'|-a*|--arch*) case "${arg}" in '-a'|'--arch') if [ "${#}" -gt "1" ]; then case "${2}" in -*) _die "Option '${arg}' requires a parameter" ;; esac shift; arch="${1}"; [ "${1}" ] && shift else _die "Option '${arg}' requires a parameter" fi ;; -a*) arch="${1#-a}"; shift ;; --arch*) arch="${1#--arch}"; shift ;; esac ;; '-m'|'--mirror'|-m*|--mirror*) case "${arg}" in '-m'|'--mirror') if [ "${#}" -gt "1" ]; then case "${2}" in -*) _die "Option '${arg}' requires a parameter" ;; esac shift; mirror="${1}"; [ "${1}" ] && shift else _die "Option '${arg}' requires a parameter" fi ;; -m*) mirror="${1#-m}"; shift ;; --mirror*) mirror="${1#--mirror}"; shift ;; esac ;; '-k'|'--checksum'|-k*|--checksum*) case "${arg}" in '-k'|'--checksum') if [ "${#}" -gt "1" ]; then case "${2}" in -*) _die "Option '${arg}' requires a parameter" ;; esac shift; checksum="${1}"; [ "${1}" ] && shift else _die "Option '${arg}' requires a parameter" fi ;; -k*) checksum="${1#-k}"; shift ;; --checksum*) checksum="${1#--checksum}"; shift ;; esac ;; '-t'|'--distro'|-t*|--distro*) case "${arg}" in '-t'|'--distro') if [ "${#}" -gt "1" ]; then case "${2}" in -*) _die "Option '${arg}' requires a parameter" ;; esac shift; distro="${1}"; [ "${1}" ] && shift else _die "Option '${arg}' requires a parameter" fi ;; -t*) distro="${1#-t}"; shift ;; --distro*) distro="${1#--distro}"; shift ;; esac ;; '-f'|'--format'|-f*|--format*) case "${arg}" in '-f'|'--format') if [ "${#}" -gt "1" ]; then case "${2}" in -*) _die "Option '${arg}' requires a parameter" ;; esac shift; compress_format="${1}"; [ "${1}" ] && shift else _die "Option '${arg}' requires a parameter" fi ;; -f*) compress_format="${1#-f}"; shift ;; --format*) compress_format="${1#--format}"; shift ;; esac ;; -c|--clean-cache) rm -rf "${tmpcache}".* && \ [ -z "${quiet}" ] && printf "%s\\n" "Cache cleared successfully" exit ;; '-') : ;; #special stdout character -*) _die "${progname}: unrecognized option '${arg}'" >&2 ;; esac done if [ -z "${search_pattern}" ]; then [ "${#}" -eq "0" ] && _die "${progname}: missing arguments" _static_get "${@}" else _static_search "${search_pattern}" fi