#!/usr/bin/env bash # Installs Debian packages on a Debian system # with no root access, in the user home # CONFIGURATION # Verifications if [[ -z $DEBIAN_MIRROR && ! -f /etc/apt/sources.list ]]; then echo "Unable to find a mirror. Try setting DEBIAN_MIRROR (see help)." exit 1 fi if [[ -z $DEBIAN_DB && ! $(which apt &> /dev/null) ]]; then echo "Unable to find a database for packages to install. Try setting DEBIAN_DB (see help)." exit 1 fi # Overrides [ -z $DEBLOC_PREFIX ] && DEBLOC_PREFIX=$(dpkg --print-architecture) [ -z $DEBLOC_DB ] && DEBLOC_DB=${XDG_CONFIG_HOME:-$HOME/.config}/debloc/$DEBLOC_PREFIX [ -z $DEBLOC_ROOT ] && DEBLOC_ROOT=$HOME/.debloc/$DEBLOC_PREFIX DEBLOC_LD=$DEBLOC_ROOT/ld if [ -z "$DEBIAN_MIRROR" ]; then DEBIAN_MIRROR="$(cat /etc/apt/sources.list | grep '^deb ' | grep main | grep -v backports)" DEBIAN_MIRROR="$(echo -e "$DEBIAN_MIRROR" | cut -d ' ' -f 2 | sed 's/\/$//' | sort | uniq)" fi # Preparation mkdir -p $DEBLOC_DB &> /dev/null mkdir -p $DEBLOC_ROOT &> /dev/null # PRIVATE FUNCTIONS # Tell if a package exists function _debloc-exists { # package if [[ -n $DEBIAN_DB && -f $DEBIAN_DB ]]; then grep "^Package: $1\$" $DEBIAN_DB --quiet else LANG=C apt-cache show $1 &> /dev/null fi if [ $? == 0 ]; then return 1 else return 0 fi } # Return the real package associated with a virtual package # If not a virtual package, return the input function _debloc-filterVirtual { # package pkg=$1 if [[ -n $DEBIAN_DB && -f $DEBIAN_DB ]]; then echo $pkg else LANG=C apt-cache policy $1 | grep "Candidate" | grep "(none)" > /dev/null if [ $? == 0 ]; then # TODO This is not really accurate LANG=C apt-cache showpkg $pkg | tail -1 | cut -d ' ' -f 1 else echo $pkg fi fi return 0 } # Tell if a package is installed via debloc function _debloc-locallyInstalled { # package if [ -f $DEBLOC_DB/$1 ]; then return 1 else return 0 fi } # Tell if a package is installed system-wide function _debloc-globallyInstalled { # package STATUS=$(mktemp) LANG=C dpkg --list $1 &> $STATUS if [ $? != 0 ]; then rm -f $STATUS > /dev/null return 0 fi cat $STATUS | grep '^Status:' | grep ' installed' --quiet if [ $? != 0 ]; then rm -f $STATUS > /dev/null return 0 else rm -f $STATUS > /dev/null return 1 fi } # Get informations about a package function _debloc-packageShow { # package pkg=$1 if [[ -n $DEBIAN_DB && -f $DEBIAN_DB ]]; then startline=$(grep "^Package: ${pkg}\$" $DEBIAN_DB --line-number | tail -1 | cut -d ':' -f 1) if [ -z "$startline" ]; then return 0 fi sed -n "$startline,$(expr $startline + 100)p" $DEBIAN_DB | while read line; do if [ -z "$line" ]; then return 0 fi echo $line done return 1 else LANG=C apt-cache show $pkg | while read line; do if [ -z "$line" ]; then return 0 fi echo "$line" done return 0 fi } # Get the path of a package function _debloc-packagePath { # package _debloc-packageShow $1 | grep "^Filename:" | head -1 | cut -d ':' -f 2 | sed -e 's/^[[:space:]]*//' return 0 } # Get the md5sum of a package function _debloc-packageMd5sum { # package _debloc-packageShow $1 | grep "^MD5sum:" | cut -d ':' -f 2 | sed -e 's/^[[:space:]]*//' return 0 } # Update symbolics links in $DEBLOC_ROOT/lib function _debloc-ldconfig { mkdir -p $DEBLOC_LD &> /dev/null rm -f $DEBLOC_LD &> /dev/null find $DEBLOC_ROOT{/usr,}/lib -type f -name "*.so*" | while read lib; do ln --symbolic --force "$lib" "$DEBLOC_LD/$(basename $lib)" done &> /dev/null find $DEBLOC_ROOT{/usr,}/lib -type l -name "*.so*" | while read link; do yes | cp --force --no-dereference --preserve=links "$link" "$DEBLOC_LD" &> /dev/null done &> /dev/null } # Fix absolute symbolic links function _debloc-fixRootSymlinks { find $DEBLOC_ROOT -type l | while read src do dst="$(readlink "$src")" if echo "$dst" | grep '^/' | grep -q -v "^$DEBLOC_ROOT" then newDst="$DEBLOC_ROOT$dst" if [ -f "$newDst" ] then echo "$src → $newDst" rm "$src" ln -s "$newDst" "$src" else echo "Ignoring $src pointing to $dst" fi fi done } function _debloc-fixPkgconfPrefix { sed "s|^prefix=/usr$|prefix=$DEBLOC_ROOT/usr|" $(find $DEBLOC_ROOT -type f -name "*.pc") -i } function debloc_fix { echo "Fixing absolute symbolic links..." _debloc-fixRootSymlinks echo "Linking libraries in /ld" _debloc-ldconfig echo "Fixing prefix in pkg-config files" _debloc-fixPkgconfPrefix } # Install debian archive function _debloc-installDeb { # path TMP_DIR=$(mktemp -d) &> /dev/null $(cd $TMP_DIR; ar x "$1") TAR_FILE=$(find $TMP_DIR -type f -name "data.tar.*" | head -1) if [ -e "$TAR_FILE" ]; then # Output for DB saving tar tf $TAR_FILE tar xf $TAR_FILE -C $DEBLOC_ROOT # _debloc-ldconfig mkdir -p $DEBLOC_LD &> /dev/null tar tf $TAR_FILE | grep '^.\(/usr\)\?/lib/' | grep '\.so' | while read file; do lib=$(readlink -f $DEBLOC_ROOT/$file) if [ -f $lib ]; then ln --symbolic --force "$lib" "$DEBLOC_LD/$(basename $file)" fi if [ -h $lib ]; then yes | cp --force --no-dereference --preserve=links "$(basename $link)" "$DEBLOC_LD/" &> /dev/null fi done fi rm -rf $TMP_DIR &> /dev/null return 0 } # Install package function _debloc-install { # package pkg=$1 DEB_FILE=$(mktemp) &> /dev/null path=$(_debloc-packagePath $pkg) echo -e "${DEBIAN_MIRROR}" | while read mirror; do if [ -z "$mirror" ]; then continue fi url=${mirror}/${path} echo "→ Downloading $url" wget "$url" --quiet -O $DEB_FILE if [ $? == 0 ]; then break fi done if [ ! -s $DEB_FILE ]; then echo "→ Failed (no deb file)!" rm $DEBLOC_DB/$pkg &> /dev/null return 4 fi echo "→ Verifying sums" theo=$(_debloc-packageMd5sum $pkg) real=$(md5sum $DEB_FILE | cut -d ' ' -f 1) if [ "$theo" != "$real" ]; then rm -f $DEB_FILE &> /dev/null echo "→ Failed (sum doesn't match)!" rm $DEBLOC_DB/$pkg &> /dev/null return 5 fi echo "→ Installing" _debloc-installDeb $DEB_FILE > $DEBLOC_DB/$pkg echo "→ Done!" rm -f $DEB_FILE &> /dev/null return 0 } # Get the dependencies of a package function _debloc-packageDeps { # package _debloc-packageShow $1 | grep '^Depends:' | sed 's/Depends: //' | sed 's/, /\n/g' | cut -d ' ' -f 1 return 0 } # Install package with dependencies function _debloc-installDeps { # package pkg=$1 echo "Installing $pkg" touch $DEBLOC_DB/$pkg # To prevent cyclic deps _debloc-packageDeps $pkg | while read dep; do dep=$(_debloc-filterVirtual $dep) _debloc-locallyInstalled $dep if [ $? == 1 ]; then echo "- Dependency $dep is already installed with Debloc" continue fi _debloc-globallyInstalled $dep if [ $? == 1 ]; then echo "- Dependency $dep is already installed on the system" continue fi _debloc-installDeps $dep | while read line; do echo "- $line"; done done _debloc-install $pkg return 0 } # PUBLIC FUNCTIONS function proxy_set_help { echo "Usage: $0 env" echo echo "Examples:" echo ' eval "$(debloc env)"' return 0 } function debloc_env { echo "export PATH=\"$DEBLOC_ROOT/usr/bin:$DEBLOC_ROOT/usr/games/:$DEBLOC_ROOT/usr/lib/git-core:\$PATH\"" echo "export LIBRARY_PATH=\"$DEBLOC_LD:\$LIBRARY_PATH\"" echo "export C_INCLUDE_PATH=\"$DEBLOC_ROOT/usr/include:\$C_INCLUDE_PATH\"" echo "export CPLUS_INCLUDE_PATH=\"$DEBLOC_ROOT/usr/include:$DEBLOC_ROOT/usr/include/python2.7/:$DEBLOC_ROOT/usr/include/x86_64-linux-gnu/python2.7/:\$CPLUS_INCLUDE_PATH\"" echo "export LD_LIBRARY_PATH=\"$DEBLOC_LD:\$LD_LIBRARY_PATH\"" echo "export PYTHONPATH=\"$DEBLOC_ROOT/usr/lib/python2/dist-packages:$DEBLOC_ROOT/usr/lib/python3/dist-packages:$DEBLOC_ROOT/usr/lib/python2.7/dist-packages:$DEBLOC_ROOT/usr/lib/python3.5/dist-packages:\$PYTHONPATH\"" echo "export QT_QPA_PLATFORM_PLUGIN_PATH=\"$DEBLOC_ROOT/usr/lib/x86_64-linux-gnu/qt5/plugins/platforms\"" echo "export PKG_CONFIG_PATH=\"$DEBLOC_ROOT/usr/share/pkgconfig/:$DEBLOC_ROOT/usr/lib/x86_64-linux-gnu/pkgconfig/:$DEBLOC_ROOT/usr/lib/pkgconfig/:\$PKG_CONFIG_PATH\"" } function debloc_info { echo "DEBLOC_PREFIX=$DEBLOC_PREFIX" echo "DEBLOC_ROOT=$DEBLOC_ROOT" echo "DEBLOC_DB=$DEBLOC_DB" echo "DEBLOC_LD=$DEBLOC_LD" echo "DEBIAN_MIRROR='$DEBIAN_MIRROR'" echo "DEBIAN_DB=$DEBIAN_DB" } function debloc_install_help { echo "Usage: $0 install PACKAGE" echo echo "Arguments:" echo " PACKAGE Package name" return 0 } function debloc_install { # package if [ -z $1 ]; then debloc_deb_help fi for pkg in $* do if [ $pkg == '--force' ] || [ $pkg == '-f' ]; then force=0 fi done for pkg in $*; do if [ $pkg == '--force' ] || [ $pkg == '-f' ]; then continue fi pkg=$(_debloc-filterVirtual $pkg) _debloc-exists $pkg if [ $? == 0 ]; then echo "Unknown package $pkg" continue fi if [ ! -v force ]; then _debloc-locallyInstalled $pkg if [ $? == 1 ]; then echo "Package $pkg is already installed with Debloc" continue fi _debloc-globallyInstalled $pkg if [ $? == 1 ]; then echo "Package $pkg is already installed on the system" continue fi fi _debloc-installDeps $pkg done return 0 } function debloc_deb_help { echo "Usage: $0 deb PATH" echo echo "Arguments:" echo " PATH Path to the .deb file" return 0 } function debloc_deb { # path if [ -z $1 ]; then debloc_deb_help fi for path in $*; do if [ ! -f "$path" ]; then echo "$path is not a file" return 6 fi echo "Installing $(basename $path)" _debloc-installDeb "$(readlink -f $path)" > $DEBLOC_DB/$(basename $path) done return 0 } function debloc_altern_help { echo "Usage: $0 altern PROGRAM ALTERNATIVE" echo echo "Arguments:" echo " PROGRAM Program to set the alternative for" echo " ALTERNATIVE Alternative to set" echo echo "Examples:" echo " $0 altern vim nox" echo " $0 altern dmenu xft" return 0 } function debloc_altern { # program alternative if [[ -z $1 || -z $2 ]]; then debloc_altern_help exit 1 fi if [ -f "$DEBLOC_ROOT/usr/bin/$1.$2" ]; then dest="$DEBLOC_ROOT/usr/bin/$1" alte="$DEBLOC_ROOT/usr/bin/$1.$2" elif [ -f "$DEBLOC_ROOT/bin/$1.$2" ]; then dest="$DEBLOC_ROOT/bin/$1" alte="$DEBLOC_ROOT/bin/$1.$2" else echo "Unknown alternative for $1 : $2" exit 1 fi if [ -e "$dest" ]; then rm $dest fi ln -s "$alte" "$dest" } function debloc_flush { rm -rf $DEBLOC_ROOT/* &> /dev/null rm -f $DEBLOC_DB/* &> /dev/null } # TODO Other word for 'fake filesystem' and/or explain what this is function debloc_help { command="$1" if [ -n "$command" ]; then if type "debloc_${command}_help" &> /dev/null; then shift "debloc_${command}_help" "$@" return $? fi fi echo "Usage: $0 COMMAND" echo echo "Commands:" echo " env Provides the environment variables required to run applications from the fake filesystem" echo " info Gives some information about the fake filesystem" echo " install Install a debian package in the fake filesystem" echo " deb Install from a .deb file in the fake filesystem" echo " altern Update alternative" echo " fix Apply some fixes in the fake filesystem" echo " flush Remove every package installed from the fake filesystem" echo " help Get help with commands" echo echo "Environment variables:" echo " DEBLOC_PREFIX Name of the fake filesystem to use (default: uses dpkg architecture)" echo " DEBLOC_ROOT Path of the fake filesystem (default: ~/.debloc/\$DEBLOC_PREFIX/)" echo " DEBLOC_DB Database of the fake filesystem (default: \$XDG_CONFIG_HOME/debloc/\$DEBLOC_PREFIX)" echo " DEBIAN_MIRROR Multiline list of debian mirror (default: uses /etc/apt/sources.list)" echo " DEBIAN_DB Path to a file with all packages description (default: uses apt-cache showpkg)" echo " help Get help with commands" return 0 } # MAIN command="$1" shift if type "debloc_$command" &> /dev/null; then "debloc_$command" "$@" else debloc_help fi