Skip to content

Instantly share code, notes, and snippets.

@nhed
Last active February 14, 2022 04:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nhed/bee12a0f80e0ed4fe1b22482d7968cfe to your computer and use it in GitHub Desktop.
Save nhed/bee12a0f80e0ed4fe1b22482d7968cfe to your computer and use it in GitHub Desktop.
#!/usr/bin/bash
#
# NYH
# Created as wrapper for PIA's installer, then feed to FPM
#
# Modifies installer's hardcoded PATH to start with directory with our fake
# bins like `sudo` & `getent`
#
# progname: as invoked, base-name of this file or symlink
declare progname="${0##*/}"
# full path to this file w/o symlinks
declare realprogpath=$(realpath "${0}")
function die {
echo "${*}" >&2
exit 1
}
function usage {
cat<<_EOF_ >&2
${progname} piavpn
PreReqs:
- 'fpm' (Effing Package Management, https://github.com/jordansissel/fpm/)
Installed with "gem install fpm".
- 'rpmbuild', installed with "sudo dnf install rpm-build"
_EOF_
}
# usually just prepend with "${INSTALL_PREFIX}", but have exceptions
function xlate_path {
local ret="${INSTALL_PREFIX}${1}"
case "${1}" in
/etc/iproute2/rt_tables)
ret="${INSTALL_PREFIX}/etc/iproute2/rt_tables.d/${SHORT_APP_ID}.conf"
mkdir -p "${ret%/*}"
;;
esac
echo "${ret}"
}
# Arg: copy target, could be dir or file
function maybe_mkdir {
local tgt="${1}"; shift
local tgtdir="${tgt%/*}"
# we cant tell if tgt is supposed to be a file or a dir if the
# installed create it already with a mkdir, install etc - nothing for
# us to do here
[ -e "$(xlate_path "${tgt}")" ] && return 0
[ -e "$(xlate_path "${tgtdir}")" ] && return 0
# if however the installer assumed or tested target dir exists we
# should repeat their test and create a dir in install cp allows copy
# dest to be a dir name or a file name if the last are is not on the
# target as a directory
if [ -d "${tgt}" ]; then
/usr/bin/fakeroot mkdir -p "$(xlate_path "${tgt}")"
elif [ -d "${tgtdir}" ]; then
/usr/bin/fakeroot mkdir -p "$(xlate_path "${tgtdir}")"
else
# do the same thing but whine about it becaus eits probably wrong
echo "Making ${tgtdir} under ${INSTALL_PREFIX}," \
"should not have to do that" >&2
/usr/bin/fakeroot mkdir -p "$(xlate_path "${tgtdir}")"
fi
}
# sudo cmd + args
# Augents commands so they can operate relative to INSTALL_PREFIX
# (where original installer had no notion of installing into a
# different location).
# invoked with INSTALL_PREFIX env var
function instfix_sudo {
[[ -z "${1}" ]] && die "Must have at least one arg, the command"
[[ "${1}" =~ ^- ]] && die "Not supporting args to sudo beyond the command"
declare -a args=()
[[ -z "${INSTALL_PREFIX}" ]] && die "Need the INSTALL_PREFIX env var"
mkdir -p "${INSTALL_PREFIX}"
# just primary command checks
# knew to look for them with
# grep sudo .../install.sh| sed 's@^.*sudo \([^ ]*\).*$@\1@'|sort -u
case "${1}" in
# packagae managers we dont (yet?) handle
pacman|zypper|apt-get)
die "Cant do that: \"${*}\", maybe declare dependencies"
;;
# packagae managers we can handle
# assuming only "install" subcommand
dnf|yum)
shift
while [ ${#} -gt 0 ]; do
if [[ "${1}" != "-y" ]] && [[ "${1}" != "install" ]]; then
echo "${1}" >> "${INSTALL_DEPS}"
fi
shift
done
exit 0
;;
# TODO: check of the setcap perist, doubt it, in unpacked files or
# need postinstall/or?
update-desktop-database|systemctl|service|rc-update|rc-service|update-rc.d)
echo "${@}" >> "${INSTALL_SCRIPTS}/postinstall"
exit 0
;;
groupadd)
echo "getent group ${@:(-1)} >/dev/null || ${*} || true" >> \
"${INSTALL_SCRIPTS}/preinstall"
exit 0
;;
useradd)
echo "getent passwd ${@:(-1)} >/dev/null || ${*} || true" >> \
"${INSTALL_SCRIPTS}/preinstall"
exit 0
;;
# make sure that if directory exists in "real" life make it also
# in INSTALL_PREFIX.
cp|/bin/cp|tee)
args+=("${1}")
maybe_mkdir "${@:(-1)}"
shift
;;
# we do not want the target to point to an absolute path starting
# with INSTALL_PREFIX. While there are several possible solution
# the easiest is probably to make them into relative links, adding
# `-r` (yes, assuming a symlink).
ln)
args+=("${1}" "-r")
maybe_mkdir "${@:(-1)}"
shift
;;
# these are "blessed" let pass as is
chmod|touch|mkdir|rm)
args+=("${1}")
shift
;;
*)
die "Hmmm, this wasnt on the test: ${*}"
;;
esac
# rest of args
while [ ${#} -gt 0 ]; do
case "${1}" in
# not sure we need this *shrug*
/dev/null)
args+=("${1}")
;;
# We unfortunatley need to use the eval here because the caller,
# not aware of INSTALL_PREFIX is unable to expand wildcards when
# files were copied into the INSTALL_PREFIX
/opt/*|/usr/*|/etc/*)
args+=($(eval echo $(xlate_path "${1}")))
;;
*)
args+=("${1}")
;;
esac
shift
done
exec /usr/bin/fakeroot "${args[@]}"
}
# fake getent it so it always looks like reuqired users and groups are not
# installed yet.
function instfix_getent {
case "${1}" in
group|passwd)
return 1
;;
*)
exec /usr/bin/getent "${@}"
;;
esac
}
# fake ldconfig to always return an empty list, triggering the installer
# to attempt and demand all the deps - which we will collate regardkess of
# what is currently installed.
function instfix_ldconfig {
return 0
}
# locinst <tmpdir> <orig-installer> <short-id> [installer-args]
# Caller creates <tmpdir>. After succesful run of modified installer
# <tmpdir>/dest will be the root of all the files to be installed and
# <tmpdir>/scripts will have various scripts
function instfix_locinst {
local tmpdir="${1}"; shift
local orig_inst="${1}"; shift
export SHORT_APP_ID="${1}"; shift
[[ -z "${tmpdir}" ]] && \
die "Missing temp directory path."
[[ -d "${tmpdir}" ]] || \
die "Temp dir \"${tmpdir}\" does not exist (or not dir)."
[[ -z "${orig_inst}" ]] && \
die "Missing installed script."
[[ -e "${orig_inst}" ]] || \
die "Specified installer script (${orig_inst}) does not exist."
[[ "$(file -b --mime-type "${orig_inst}")" == "text/x-shellscript" ]] || \
die "Expected a shell script, ${orig_inst}"
# use absolute path as script could cd arond
tmpdir="$(realpath "${tmpdir}")"
export INSTALL_PREFIX="${tmpdir}/dest"
mkdir -p "${INSTALL_PREFIX}"
export INSTALL_SCRIPTS="${tmpdir}/scripts"
mkdir -p "${INSTALL_SCRIPTS}"
export INSTALL_DEPS="${tmpdir}/deps"
local fakebins="${tmpdir}/fake"
mkdir -p "${fakebins}"
for bin in "sudo" "getent" "ldconfig"; do
ln -s "${realprogpath}" "${fakebins}/${bin}"
done
# Just in case installed calls sudo in a way that bypases our search
# path we don't want cached passwods to allow it to proceed without
# prompting.
/usr/bin/sudo -k
# Prepend the fakebins dir in frot of PATH in both
# - installer script itself if it overrides it.
# - in the callling environment in case it does not override.
sed 's@\(PATH="\)@\1'"${fakebins}:@" "${orig_inst}" > \
"${orig_inst}-mod"
chmod +x "${orig_inst}-mod"
PATH="${fakebins}:${PATH}" "${orig_inst}-mod" "${@}"
for unitfile in \
"${INSTALL_PREFIX}/etc/systemd/system/"*.{service,timer,socket} \
"${INSTALL_PREFIX}/usr/lib/systemd/system/"*.{service,timer,socket}
do
[ -e "${unitfile}" ] || continue
echo "systemctl stop ${unitfile##*/}" >> \
"${INSTALL_SCRIPTS}/preremove"
echo "systemctl disable ${unitfile##*/}" >> \
"${INSTALL_SCRIPTS}/preremove"
done
for script in "${INSTALL_SCRIPTS}/"*; do
[ -e "${script}" ] || continue
{ echo '#!/usr/bin/bash'; cat "${script}"; } > "${script}.new"
mv "${script}.new" "${script}"
chmod +x "${script}"
done
}
# package <tmpdir> <short-id> <src-url>
# where <tmpdir> is expected to have the following:
# - `dest`: A directory that is the base of the package contents.
# - `scripts`: A directory with pre/post install etc scripts.
# - `deps`: A file listing dependencies, one dependency per line.
# <short-id> is a short name for the package
# <src-url>: used for package metadata
function instfix_package {
local dir="${1}"; shift
local short_id="${1}"; shift
local src_url="${1}"; shift
local -a fpmcmd=("fpm" "-s" "dir" "-t" "rpm")
local version
case "${short_id}" in
piavpn)
version=$(head -1 "${dir}/dest/opt/piavpn/share/version.txt")
#fpmcmd+=("--config-files" "/opt/piavpn/etc/")
fpmcmd+=("-x" "/opt/piavpn/bin/pia-uninstall.sh")
fpmcmd+=("-x" "/opt/piavpn/bin/install-wireguard.sh")
fpmcmd+=("--url" "https://www.privateinternetaccess.com/download/linux-vpn")
fpmcmd+=(
"--description"
"Private Internet Access (PIA) VPN
Repackaged into an RPM from ${src_url}
$(cat ${dir}/dest/opt/piavpn/share/version.txt)"
)
;;
*)
version="0.0.1"
;;
esac
fpmcmd+=("-n" "${short_id}")
fpmcmd+=("-v" "${version}")
fpmcmd+=("--directories" "/opt/piavpn")
fpmcmd+=("-C" "${dir}/dest")
if [ -f "${dir}/deps" ]; then
while read -r dep ; do
fpmcmd+=("-d" "${dep}")
done < "${dir}/deps"
fi
for script in "${dir}/scripts/"*; do
[[ -e "${script}" ]] || continue
case "${script}" in
*/postinstall)
fpmcmd+=("--after-install" "${script}")
;;
*/preinstall)
fpmcmd+=("--before-install" "${script}")
;;
*/preremove)
fpmcmd+=("--before-remove" "${script}")
;;
*)
die "Oops did not handle script \"${script}\""
;;
esac
done
mkdir -p "${dir}/fpmwork/"
fpmcmd+=("--workdir" "${dir}/fpmwork/")
echo "Running FPM" >&2
"${fpmcmd[@]}"
}
function instfix_piavpn {
local tmpdir="$(mktemp -d)"
trap "rm -rf ${tmpdir}" EXIT
# download & expand
local script_url=$(
curl -sG https://www.privateinternetaccess.com/download/linux-vpn | \
grep download_linux | \
sed 's@^.* href="\([^"]*\)".*$@\1@' | sort -u | \
tail -1
)
mkdir "${tmpdir}/src"
curl -sGL "${script_url}" > "${tmpdir}/piavpn.run"
chmod +x "${tmpdir}/piavpn.run"
"${tmpdir}/piavpn.run" --noexec --target "${tmpdir}/src"
# locinstall & package
instfix_locinst "${tmpdir}" "${tmpdir}/src/install.sh" piavpn --systemd
instfix_package "${tmpdir}" piavpn "${script_url}"
}
# MAIN
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
declare func
if [[ -h "${0}" ]] && type -t "instfix_${progname}" > /dev/null; then
func="instfix_${progname}"
elif [ "${1}" ] && type -t "instfix_${1}" > /dev/null; then
func="instfix_${1}"
shift
elif [ -z "${1}" ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
usage
exit 1
else
echo "Invalid subcommand: \"${1}\"" >&2
usage
exit 2
fi
"${func}" "${@}"
fi
PIA's linux installer is not utilizing packages. Supposed to be run as non root with sudo prompts.
There might be a better way to do this.
THis script mocks few executables while running PIAs installer and then feeds data to FPM to build an RPM.
Probably could use more error checking and code cleanup.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment