#!/usr/bin/env bash # Installs Debian packages on a Debian system # with no root access, in the user home if [[ ! $(which apt &> /dev/null) ]]; then echo "This is not a Debian system (or apt is not installed)." exit 1 fi [ -z $DEBLOC_PREFIX ] && DEBLOC_PREFIX=$(dpkg --print-architecture) [ -z $DEBLOC_DB ] && DEBLOC_DB=${XDG_CONFIG_HOME:-$HOME/.config}/$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 mkdir -p $DEBLOC_DB &> /dev/null mkdir -p $DEBLOC_ROOT &> /dev/null # 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 } # 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 } # USER FUNCTIONS function debloc_env { export PATH="$DEBLOC_ROOT/usr/bin:$DEBLOC_ROOT/usr/games/:$DEBLOC_ROOT/usr/lib/git-core:$PATH" export LIBRARY_PATH="$DEBLOC_LD:$LIBRARY_PATH" export C_INCLUDE_PATH="$DEBLOC_ROOT/usr/include:$C_INCLUDE_PATH" 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" export LD_LIBRARY_PATH="$DEBLOC_LD:$LD_LIBRARY_PATH" export PYTHONPATH="$DEBLOC_ROOT/usr/lib/python3/dist-packages:$PYTHONPATH" export QT_QPA_PLATFORM_PLUGIN_PATH="$DEBLOC_ROOT/usr/lib/x86_64-linux-gnu/qt5/plugins/platforms" } 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 pkg=$(_debloc-filterVirtual $pkg) _debloc-exists $pkg if [ $? == 0 ]; then echo "Unknown package $pkg" continue fi _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 _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 fi dest="$DEBLOC_ROOT/usr/bin/$1" alte="$DEBLOC_ROOT/usr/bin/$1.$2" if [ ! -f "$alt" ]; then 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 Sets 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 " 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