diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..8efae1f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: fsquillace +custom: https://github.com/fsquillace/junest/blob/master/README.md#donating diff --git a/.gitignore b/.gitignore index 1377554..b47a408 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ *.swp +*pkg.tar.* +*.tar.gz +*.SRCINFO diff --git a/.travis.yml b/.travis.yml index 4e4536a..fab23ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,30 +1,62 @@ -language: bash - sudo: required +os: linux + +cache: + directories: + - ~/.ccache + - ~/.pkg-cache + +services: +- docker env: + matrix: - TRAVIS_BASH_VERSION="4.0" before_install: - - ./tests/integ-tests/install-bash.sh "$TRAVIS_BASH_VERSION" + - ./ci/install-bash.sh "$TRAVIS_BASH_VERSION" + - sudo apt-get update + - sudo apt-get -y install awscli install: - PATH=$PWD/bin:$PATH - - junest -- echo "Installing JuNest (\$(uname -m))" - - JUNEST_HOME=~/.junest-arm junest -a arm -- echo "Installing JuNest (\$(uname -m))" - # TODO: Remember to enable x86 tests when fixed - #- JUNEST_HOME=~/.junest-x86 junest -a x86 -- echo "Installing JuNest (\$(uname -m))" script: + ####################### + # Unit Tests + ####################### - bash --version - bash ./tests/checkstyle/checkstyle.sh - bash ./tests/unit-tests/unit-tests.sh + - shellcheck bin/junest lib/**/*.sh ci/*.sh tests/**/*.sh - # Multiple tests against different execution modes: - - junest -f -- ${PWD}/lib/checks/check.sh - - junest -u -- ${PWD}/lib/checks/check.sh --skip-aur-tests - - sudo -E ${PWD}/bin/junest -g -- ${PWD}/lib/checks/check.sh --run-root-tests - - yes | junest --delete + # ARM with qemu does seem to work properly. Disabling integ tests for ARM for now. + #- export JUNEST_HOME=~/.junest-arm + #- junest setup --arch arm + #- junest proot --fakeroot -- echo "Installing JuNest (\$(uname -m))" + #- junest proot --fakeroot -- ${PWD}/lib/checks/check.sh --skip-aur-tests + #- junest proot -- ${PWD}/lib/checks/check.sh --skip-aur-tests --use-sudo + #- yes | junest setup --delete - - JUNEST_HOME=~/.junest-arm junest -f -- ./lib/checks/check.sh - - yes | JUNEST_HOME=~/.junest-arm junest --delete + ####################### + # Build and validation + ####################### + - echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin + - docker run --rm -v "$(pwd):/build" -v ~/.ccache:/home/travis/.ccache -v ~/.pkg-cache:/var/cache/pacman/pkg --privileged archlinux:latest bash /build/ci/build_image.sh + + - "echo pacman pkg cache size: $(du -h ~/.pkg-cache|cut -f1) in $(ls ~/.pkg-cache|wc -l) files" + - ls -l + # Test the newly created JuNest image against Ubuntu host + - export JUNEST_HOME=~/.junest + - junest setup -i junest-x86_64.tar.gz + # TODO The check does not work at the moment: https://app.travis-ci.com/github/fsquillace/junest/builds/271706037 + # Disabling it in order to avoid having stale version of junest images. + - ${PWD}/lib/checks/check_all.sh + - yes | junest setup --delete + + +after_success: + ####################### + # Deploy and validation + ####################### + - ./ci/deploy.sh ./junest-x86_64.tar.gz diff --git a/README.md b/README.md index 96c8b0b..1268f8b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ JuNest ====== -The Arch Linux based distro that runs upon any Linux distros without root access. + +> [!IMPORTANT] +> Starting from Ubuntu 23.10+, [unprivileged user namespaces has been restricted](https://ubuntu.com/blog/ubuntu-23-10-restricted-unprivileged-user-namespaces). +> If using JuNest within Ubuntu, you may need root privileges in order to enable it. +> Alternatively, you can access JuNest using the `proot` mode as described +> [below](#Proot-based). + +The lightweight Arch Linux based distro that runs, without root privileges, on top of any other Linux distro.

=4.0)](https://www.gnu.org/software/bash/) - [GNU coreutils](https://www.gnu.org/software/coreutils/) -The minimum recommended Linux kernel of the host OS is 2.6.32 on x86 (32-bit -and 64 bit) and ARM architectures. It is still possible to run JuNest on lower -2.6.x host OS kernels but errors may appear, and some applications may -crash. For further information, read the [Troubleshooting](#troubleshooting) -section below. - - -## Method one (Recommended) ## +## Installation from git repository ## Just clone the JuNest repo somewhere (for example in ~/.local/share/junest): - git clone git://github.com/fsquillace/junest ~/.local/share/junest - export PATH=~/.local/share/junest/bin:$PATH +```sh +git clone https://github.com/fsquillace/junest.git ~/.local/share/junest +export PATH=~/.local/share/junest/bin:$PATH +``` + +Optionally you want to use the wrappers to run commands +installed in JuNest directly from host: + +```sh +export PATH="$PATH:~/.junest/usr/bin_wrappers" +``` +Update your `~/.bashrc` or `~/.zshrc` to get always the wrappers available. ### Installation using AUR (Arch Linux only) ### -If you are using an Arch Linux system you can, alternatively, install JuNest from the [AUR repository](https://aur.archlinux.org/): +If you are using an Arch Linux system you can, alternatively, install JuNest from the [AUR repository](https://aur.archlinux.org/packages/junest-git/). +JuNest will be located in `/opt/junest/` - yaourt -S junest-git - export PATH=/opt/junest/bin:$PATH +Quickstart +========== -## Method two ## -Alternatively, another installation method would be to directly download the JuNest image and place it to the default directory ~/.junest: +Setup environment +----------------- - ARCH= - mkdir ~/.junest - curl https://s3-eu-west-1.amazonaws.com/junest-repo/junest-${ARCH}.tar.gz | tar -xz -C ~/.junest - export PATH=~/.junest/opt/junest/bin:$PATH +The first operation required is to install the JuNest environment in the +location of your choice via `JUNEST_HOME` environment variable +(it must contain an absolute path) which by +default is `~/.junest`: + +```sh +junest setup +``` + +The script will download the image from the repository and will place it to the default directory `~/.junest`. + +Access to environment +--------------------- + +JuNest uses the Linux namespaces (aka `ns`) as the default backend program. To access via `ns` just type: + +```sh +junest +``` + +You can use the command `sudo` to acquire fakeroot privileges and +install/remove packages. + +Alternatively, you can access fakeroot privileges without using `sudo` all the +time with the `-f` (or `--fakeroot`) option: + +```sh +junest -f +``` + +Another execution mode is via [Proot](https://wiki.archlinux.org/index.php/Proot): + +```sh +junest proot [-f] +``` + +There are multiple backend programs, each with its own pros/cons. +To know more about the JuNest execution modes depending on the backend program +used, see the [Usage](#usage) section below. + +Run JuNest installed programs directly from host OS +--------------------------------------- + +Programs installed within JuNest can be accessible directly from host machine +without entering into a JuNest session +(namely, no need to call `junest` command first). +For instance, supposing the host OS is an Ubuntu distro you can directly +run `pacman` by simply updating the `PATH` variable: + +```sh +export PATH="$PATH:~/.junest/usr/bin_wrappers" +sudoj pacman -S htop +htop +``` + +By default the wrappers use `ns` mode. To use the `ns --fakeroot` you can use the convenient command helper `sudoj`. +For more control on backend modes you can use the `JUNEST_ARGS` environment variable too. +For instance, if you want to run `iftop` with real root privileges: + +``` +sudoj pacman -S iftop +sudo JUNEST_ARGS="groot" iftop +``` + +Bin wrappers can be always recreated (e.g. in case for some reasons they get +corrupted) with: + +``` +junest create-bin-wrappers -f +``` + +Bin wrappers are automatically generated each time they get installed inside JuNest. +This only works for executables located in `/usr/bin` path. +For executables in other locations (say `/usr/mybinpath`) you can only create +wrappers manually by executing the command: + +``` +junest create-bin-wrappers --bin-path /usr/mybinpath +``` + +Obviously, to get access to the corresponding bin wrappers you will need to +update your `PATH` variable accordingly: + +``` +export PATH="$PATH:~/.junest/usr/mybinpath_wrappers" +``` + +Install packages from AUR +------------------------- + +In `ns` mode, you can easily install package from [AUR](https://aur.archlinux.org/) repository +using the already available [`yay`](https://aur.archlinux.org/packages/yay/) +command. In `proot` mode, JuNest does no longer support the building of AUR packages. + +**Remember** that in order to build packages from AUR, `base-devel` package group is required +first: + +```sh +pacman -S base-devel +``` + +JuNest uses a modified version of `sudo` provided by `junest/sudo-fake`. And the original `core/sudo` +package will be ignored **(and must not be installed)** during the installation of `base-devel`. + +Have fun! +--------- + +If you are new on Arch Linux and you are not familiar with `pacman` package manager +visit the [pacman rosetta page](https://wiki.archlinux.org/index.php/Pacman_Rosetta). Usage ===== There are three different ways you can run JuNest depending on the backend program you decide to use. -PRoot based ------------ -[Proot](https://wiki.archlinux.org/index.php/Proot) represents the default -program used for accessing to the JuNest environments. -The main reason to choose Proot as default backend program is because -it represents a portable solution that works well in most of GNU/Linux distros available. -One of the major drawbacks is the fact that Proot is not officially -supported anymore, therefore, Proot bugs may no longer be fixed. - -In order to run JuNest via Proot: - -- As normal user - Allow to make basic operations: ```junest``` - -- As fakeroot - Allow to install/remove packages: ```junest -f``` - Linux namespaces based ---------------------- The [Linux namespaces](http://man7.org/linux/man-pages/man7/namespaces.7.html) -represents the next generation backend program for JuNest. -The major drawback about the namespace is portability, as certain requirements -need to be satisfied: 1) Only starting from Linux 3.8, unprivileged processes can -create the required user and mount namespaces. -2) Moreover, the Linux kernel distro must have the user namespace enabled. -Hopefully, in the future the major GNU/Linux distros will start enabling such feature by default. -For instance, Ubuntu (version 14.04+) already has such feature enabled. +represents the default backend program for JuNest. +The requirements for having Linux namespaces working are: + +1. Kernel starting from Linux 3.8 allows unprivileged processes to create +user and mount namespaces. +1. The Linux kernel distro must have the user namespace enabled. + +In the last years, the majority of GNU/Linux distros have the user namespace +enabled by default. This means that you do not need to have root privileges to +access to the JuNest environment via this method. +This +[wiki](https://github.com/fsquillace/junest/wiki/Linux-distros-with-user-namespace-enabled-by-default) +provides the state of the user namespace on several GNU/Linux distros. In order to run JuNest via Linux namespaces: -- As fakeroot - Allow to install/remove packages: ```junest -u``` +- As normal user - Allow to make basic operations or install/remove packages +with `sudo` command: `junest ns` or `junest` +- As fakeroot - Allow to install/remove packages: `junest ns -f` or `junest -f` + +This mode is based on the fantastic +[`bubblewrap`](https://github.com/containers/bubblewrap) command. + +PRoot based +----------- +[Proot](https://wiki.archlinux.org/index.php/Proot) represents a portable +solution which allows unprivileged users to execute programs inside a sandbox +and works well in most of GNU/Linux distros available. + +In order to run JuNest via Proot: + +- As normal user - Allow to make basic operations: `junest proot` + +- As fakeroot - Allow to install/remove packages: `junest proot -f` + +In `proot` mode, the minimum recommended Linux kernel for the host OS is 2.6.32 on x86 (64 bit) +and ARM architectures. It is still possible to run JuNest on lower +2.6.x host OS kernels but errors may appear, and some applications may +crash. For further information, read the [Troubleshooting](#troubleshooting) +section below. Chroot based ------------ This solution suits only for privileged users. JuNest provides the possibility to run the environment via `chroot` program. -In particular, it uses a special program called `GRoot`, an enhanced `chroot` -wrapper that allows to bind mount directories specified by the user, such as -/proc, /sys, /dev, /tmp and $HOME, before +In particular, it uses a special program called `GRoot`, a small and portable +version of +[arch-chroot](https://wiki.archlinux.org/index.php/Chroot) +wrapper, that allows to bind mount directories specified by the user, such as +`/proc`, `/sys`, `/dev`, `/tmp`, `/run/user/` and `$HOME`, before executing any programs inside the JuNest sandbox. In case the mounting will not work, JuNest is even providing the possibility to run the environment directly via the pure `chroot` command. In order to run JuNest via `chroot` solutions: -- As root via `GRoot` - Allow to have fully root privileges inside JuNest environment (you need to be root for executing this): ```junest -g``` +- As root via `GRoot` - Allow to have fully root privileges inside JuNest environment (you need to be root for executing this): `junest groot` -- As root via `chroot` - Allow to have fully root privileges inside JuNest environment (you need to be root for executing this): ```junest -r``` +- As root via `chroot` - Allow to have fully root privileges inside JuNest environment (you need to be root for executing this): `junest root` Execution modes comparison table ---------------- @@ -161,8 +290,8 @@ The following table shows the capabilities that each backend program is able to | | QEMU | Root privileges required | Manage Official Packages | Manage AUR Packages | Portability | Support | User modes | | --- | ---- | ------------------------ | ------------------------ | ------------------- | ----------- | ------- | ---------- | -| **Proot** | YES | NO | YES | YES | YES | Poor | Normal user and `fakeroot` | -| **Linux Namespaces** | NO | NO | YES | NO | Poor | YES | `fakeroot` only | +| **Linux Namespaces** | NO | NO | YES | YES | Poor | YES | Normal user and `fakeroot` | +| **Proot** | YES | NO | YES | NO | YES | YES | Normal user and `fakeroot` | | **Chroot** | NO | YES | YES | YES | YES | YES | `root` only | Advanced usage @@ -170,18 +299,22 @@ Advanced usage ## Build image ## You can build a new JuNest image from scratch by running the following command: - junest -b [-n] +```sh +junest build [-n] +``` The script will create a directory containing all the essentials -files in order to make JuNest working properly (such as pacman, yogurt and proot). -The option **-n** will skip the final validation tests if they are not needed. +files in order to make JuNest working properly (such as `pacman` and `proot`). +The option `-n` will skip the final validation tests if they are not needed. Remember that the script to build the image must run in an Arch Linux OS with -arch-install-scripts, package-query, git and the base-devel packages installed. +arch-install-scripts and the base-devel packages installed. To change the build directory just use the `JUNEST_TEMPDIR` (by default /tmp). -After creating the image junest-x86\_64.tar.gz you can install it by running: +After creating the image `junest-x86_64.tar.gz` you can install it by running: - junest -i junest-x86_64.tar.gz +```sh +junest setup -i junest-x86_64.tar.gz +``` For more details, you can also take a look at [junest-builder](https://github.com/fsquillace/junest-builder) @@ -194,20 +327,34 @@ Related wiki page: ## Run JuNest using a different architecture via QEMU ## The following command will download the ARM JuNest image and will run QEMU in -case the host OS runs on either x86\_64 or x86 architectures: +case the host OS runs on `x86_64` architecture: - $> JUNEST_HOME=~/.junest-arm junest -a arm -- uname -m - armv7l +```sh +$> export JUNEST_HOME=~/.junest-arm +$> junest setup -a arm +$> junest proot -- uname -m +armv7l +``` ## Bind directories ## -To bind a host directory to a guest location, you can use proot arguments: +To bind a host directory to a guest location: - junest -p "-b /mnt/mydata:/home/user/mydata" +```sh +junest -b "--bind /home/user/mydata /mnt/mydata" +``` -This will works with PRoot, Namespace and GRoot backend programs. +Or using proot arguments: + +```sh +junest proot -b "-b /mnt/mydata:/home/user/mydata" +``` + +The option `-b` to provide options to the backend program will work with PRoot, Namespace and GRoot backend programs. Check out the backend program options by passing `--help` option: - junest [-u|-g] -p "--help" +```sh +junest [u|g|p] -b "--help" +``` ## Systemd integration ## Although JuNest has not been designed to be a complete container, it is even possible to @@ -219,7 +366,9 @@ and the container can only be executed using root privileges. To boot a JuNest container: - sudo systemd-nspawn -bD ~/.junest +```sh +sudo systemd-nspawn -bD ~/.junest +``` Related wiki page: @@ -228,30 +377,17 @@ Related wiki page: Internals ========= - -There are two main chroot jail used in JuNest. -The main one is [proot](https://wiki.archlinux.org/index.php/Proot) which -allows unprivileged users to execute programs inside a sandbox and -GRoot, a small and portable version of -[arch-chroot](https://wiki.archlinux.org/index.php/Chroot) which is an -enhanced chroot for privileged users that mounts the primary directories -(i.e. /proc, /sys, /dev and /run) before executing any programs inside -the sandbox. - -## Automatic fallback to classic chroot ## -If GRoot fails for some reasons in the host system (i.e. it is not able to -mount one of the directories), -JuNest automatically tries to fallback to the classic chroot. - ## Automatic fallback for all the dependent host OS executables ## -JuNest attempt first to run the executables in the host OS located in different -positions (/usr/bin, /bin, /usr/sbin and /sbin). +JuNest attempts first to run the executables in the host OS located in different +positions (`/usr/bin`, `/bin`, `/usr/sbin` and `/sbin`). As a fallback it tries to run the same executable if it is available in the JuNest -image. +environment. ## Automatic building of the JuNest images ## -The JuNest images are built every week so that you can always get the most -updated package versions. +There is a periodic automation build of the JuNest images for `x86_64` arch +only. +The JuNest image for `arm` architecture may not be always up to date because +the build is performed manually. ## Static QEMU binaries ## There are static QEMU binaries included in JuNest image that allows to run JuNest @@ -261,18 +397,35 @@ directory. Troubleshooting =============== +For Arch Linux related FAQs take a look at the [General troubleshooting page](https://wiki.archlinux.org/index.php/General_troubleshooting). + ## Cannot use AUR repository ## -> **Q**: Why do I get the following error when I try to install a package with yogurt? +> **Q**: Why do I get the following error when I try to install a package? Cannot find the gzip binary required for compressing man and info pages. > **A**: JuNest comes with a very basic number of packages. -> In order to install packages using yogurt you may need to install the package group **base-devel** -> that contains all the essential packages for compiling source code (such as gcc, make, patch, etc): +> In order to install AUR packages you need to install the package group `base-devel` first +> that contains all the essential packages for compiling from source code (such as gcc, make, patch, etc): #> pacman -S base-devel +> Remember to not install `core/sudo` as it conflicts with `junest/sudo-fake` package. + +## Can't set user and group as root + +> **Q**: In ns mode when installing package I get the following error: + + warning: warning given when extracting /usr/file... (Can't set user=0/group=0 for + /usr/file...) + +> **A**: This is because as fakeroot is not possible to set the owner/group of +> files as root. The package will still be installed correctly even though this +> message is showed. + +## Could not change the root directory in pacman + ## No servers configured for repository ## > **Q**: Why I cannot install packages? @@ -308,6 +461,8 @@ Troubleshooting $> pkgfile getop core/util-linux +> Alternatively, you can use directly `pacman` command only. Take a look [here](https://wiki.archlinux.org/index.php/General_troubleshooting#Message:_%22error_while_loading_shared_libraries%22). + ## Kernel too old ## > **Q**: Why do I get the error: "FATAL: kernel too old"? @@ -322,7 +477,7 @@ Troubleshooting > on to PRoot when *-p* is prepended. For example, to fake a kernel version of > 3.10, issue the following command: - $> junest -p "-k 3.10" + $> junest proot -b "-k 3.10" > As Arch Linux ships binaries for kernel version 2.6.32, the above error is > not unique to the precompiled package from JuNest. It will also appear when @@ -379,7 +534,7 @@ Troubleshooting > have *suid* permission, you can list the commands from your JuNest environment > with the following command: - $> find /usr/bin -perm +4000 + $> find /usr/bin -perm /4000 ## No characters are visible on a graphic application ## @@ -408,17 +563,31 @@ Troubleshooting > since JuNest will try to preserve the JuNest environment by assigning ownership > of the files to the real user. -## Not enabled User namespace or kernel too old ## +## Unprivileged user namespace disable at kernel compile time or kernel too old ## -> **Q**: Why do I get warning when I run JuNest via Linux namespaces? +> **Q**: Why do I get this warning when I run JuNest via Linux namespaces? - $> junest -u - User namespace is not enabled or Kernel too old (<3.8). Proceeding anyway... + $> junest ns + Unprivileged user namespace is disabled at kernel compile time or kernel too old (<3.8). Proceeding anyway... + +> **A**: This means that JuNest detected that the host OS either +> does not have a newer kernel version or the unprivileged user namespace +> is not enabled at kernel compile time. +> JuNest does not stop the execution of the program but it attempts to run it +> anyway. Try to use Proot as backend program in case is not possible to invoke namespaces. + +## Unprivileged user namespace disabled + +> **Q**: Why do I get this warning when I run JuNest via Linux namespaces? + + $> junest ns + Unprivileged user namespace disabled. Root permissions are required to enable it: sudo sysctl kernel.unprivileged_userns_clone=1 > **A**: This means that JuNest detected that the host OS either > does not have a newer Linux version or the user namespace is not enabled. > JuNest does not stop the execution of the program but it attempts to run it -> anyway. Try to use Proot as backend program in case is not possible to invoke namespaces. +> anyway. If you have root permissions try to enable it, otherwise try to use +> Proot as backend program. More documentation ================== @@ -427,13 +596,21 @@ There are additional tutorials in the Contributing ============ -You could help improving JuNest in the following ways: +Contributions are welcome! You could help improving JuNest in the following ways: - [Reporting Bugs](CONTRIBUTING.md#reporting-bugs) - [Suggesting Enhancements](CONTRIBUTING.md#suggesting-enhancements) - [Writing Code](CONTRIBUTING.md#your-first-code-contribution) -Author -====== -Filippo Squillace +Donating +======== +To sustain the project please consider funding by donations through +the [GitHub Sponsors page](https://github.com/sponsors/fsquillace/). +Authors +======= +JuNest was originally created in late 2014 by [Filippo Squillace (feel.sqoox@gmail.com)](https://github.com/fsquillace). + +Here is a list of [**really appreciated contributors**](https://github.com/fsquillace/junest/graphs/contributors)! + +[![](https://sourcerer.io/fame/fsquillace/fsquillace/junest/images/0)](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/0)[![](https://sourcerer.io/fame/fsquillace/fsquillace/junest/images/1)](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/1)[![](https://sourcerer.io/fame/fsquillace/fsquillace/junest/images/2)](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/2)[![](https://sourcerer.io/fame/fsquillace/fsquillace/junest/images/3)](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/3)[![](https://sourcerer.io/fame/fsquillace/fsquillace/junest/images/4)](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/4)[![](https://sourcerer.io/fame/fsquillace/fsquillace/junest/images/5)](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/5)[![](https://sourcerer.io/fame/fsquillace/fsquillace/junest/images/6)](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/6)[![](https://sourcerer.io/fame/fsquillace/fsquillace/junest/images/7)](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/7) diff --git a/VERSION b/VERSION index 9b9a244..ef13716 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.0.2 +7.4.10 diff --git a/bin/groot b/bin/groot deleted file mode 100755 index b2cc2ab..0000000 --- a/bin/groot +++ /dev/null @@ -1,234 +0,0 @@ -#!/bin/bash -# -# This script is the simplified and portable version of arch-chroot -# (https://wiki.archlinux.org/index.php/Change_root#Using_arch-chroot) -# - -set -e - -# JUNEST_BASE can be overridden for testing purposes. -# There is no need for doing it for normal usage. -JUNEST_BASE="${JUNEST_BASE:-$(readlink -f $(dirname $(readlink -f "$0"))/..)}" -NAME='GRoot' -CMD='groot' -DESCRIPTION="I am $NAME!" -CHROOTCMD=${CHROOTCMD:-chroot} -SHELL="/bin/sh" -MOUNT=mount -UMOUNT=umount -MOUNTPOINT=mountpoint -MKDIR=mkdir -TOUCH=touch -CUT=cut -SORT=sort -UNIQ=uniq -CAT=cat -READLINK=readlink -MOUNTS_FILE=/proc/self/mounts - -NOT_EXISTING_FILE=103 -NOT_ABSOLUTE_PATH=111 -NO_ROOT_PRIVILEGES=110 - -source "${JUNEST_BASE}/lib/utils/utils.sh" - - -################################ MAIN FUNCTIONS ########################### - -function chroot_teardown() { - # Remove all mounts starting from the most nested ones. - # Suffix the CHROOTDIR with / to avoid umounting directories not belonging - # to CHROOTDIR. - local normalized_chrootdir="$($READLINK -f ${CHROOTDIR})/" - local final_res=0 - for mp in $($CAT $MOUNTS_FILE | $CUT -f2 -d' ' | $SORT -r | $UNIQ) - do - if [[ $mp =~ ^${normalized_chrootdir}.* ]] - then - $UMOUNT $mp || final_res=$? - fi - done - $UMOUNT ${CHROOTDIR%/} - - return $final_res -} - -function chroot_maybe_add_mount() { - local cond=$1 - shift - if eval "$cond"; then - $MOUNT "$@" - return - fi - return 1 -} - -function chroot_setup() { - $OPT_NO_UMOUNT || check_and_trap 'chroot_teardown' QUIT EXIT ABRT KILL TERM INT - - if ! chroot_maybe_add_mount "! $MOUNTPOINT -q '$CHROOTDIR'" --bind "$CHROOTDIR" "$CHROOTDIR" - then - warn "Failed mount of directories. $CHROOTDIR is already a mountpoint. Skipping it..." - return 0 - fi - - local re='(.*):(.*)' - for binds in ${BINDINGS[@]} - do - local host_path="" - local guest_path="" - if [[ $binds =~ $re ]] - then - local host_path="${BASH_REMATCH[1]}" - local guest_path="${BASH_REMATCH[2]}" - else - local host_path="$binds" - local guest_path="$binds" - fi - - create_node "${host_path}" "${CHROOTDIR}${guest_path}" - mount_directory "${host_path}" "${guest_path}" - done -} - -function mount_directory() { - local host_path=$($READLINK -f "$1") - local guest_path="$2" - - if ! $OPT_AVOID_BIND - then - $MOUNT $OPT_BIND "${host_path}" "${CHROOTDIR}${guest_path}" - return 0 - fi - - case "$host_path" in - /proc) $MOUNT proc "${CHROOTDIR}${guest_path}" -t proc ;; - /sys) $MOUNT sys "${CHROOTDIR}${guest_path}" -t sysfs ;; - /dev) $MOUNT udev "${CHROOTDIR}${guest_path}" -t devtmpfs; $MOUNT devpts "${guest_path}/pts" -t devpts; $MOUNT shm "${guest_path}/shm" -t tmpfs ;; - /run) $MOUNT run "${CHROOTDIR}${guest_path}" -t tmpfs ;; - /tmp) $MOUNT tmp "${CHROOTDIR}${guest_path}" -t tmpfs ;; - *) $MOUNT $OPT_BIND "${host_path}" "${CHROOTDIR}${guest_path}" ;; - esac - - return 0 -} - -function create_node() { - local src="$1" - local dst="$2" - if [[ ! -e $src ]] - then - die_on_status $NOT_EXISTING_FILE "${src} does not exist." - elif [[ $src != /* ]] - then - die_on_status $NOT_ABSOLUTE_PATH "${src} is not an absolute path." - elif [[ -f $src ]] - then - $TOUCH "$dst" - elif [[ -d $src ]] - then - $MKDIR -p "$dst" - fi -} - -function usage() { - cat < [command]] - -Options: - -b, --bind - Make the content of accessible in the guest rootfs. - - This option makes any file or directory of the host rootfs - accessible in the confined environment just as if it were part of - the guest rootfs. By default the host path is bound to the same - path in the guest rootfs but users can specify any other location - with the syntax: -b :. This option can - be invoked multiple times and the paths specified must be absolutes. - - -n, --no-umount - Do not umount after chroot session finished. - - -r, --recursive - Use rbind instead of bind. - - -i, --avoid-bind - Attempt to avoid mount --bind for common directories and use - proper mount fstype instead. Detected directories with - corresponding fstype are: /proc (proc), /sys (sysfs), - /dev (devtmpfs), /tmp (tmpfs), /run (tmpfs). - - -h, --help Print this help message - - -V, --version Show the $NAME version - -If 'command' is unspecified, $CMD will launch $SHELL. - -EOF -} - -version() { - echo -e "$NAME $(cat $JUNEST_BASE/VERSION)" -} - -function parse_arguments() { - BINDINGS=() - OPT_NO_UMOUNT=false - OPT_RECURSIVE=false - OPT_BIND="--bind" - OPT_AVOID_BIND=false - OPT_HELP=false - OPT_VERSION=false - for opt in "$@" - do - case "$1" in - -b|--bind) shift ; BINDINGS+=("$1") ; shift ;; - -n|--no-umount) OPT_NO_UMOUNT=true ; shift ;; - -r|--recursive) OPT_BIND="--rbind" ; shift ;; - -i|--avoid-bind) OPT_AVOID_BIND=true ; shift ;; - -h|--help) OPT_HELP=true ; shift ;; - -V|--version) OPT_VERSION=true ; shift ;; - -*) die "Invalid option $1" ;; - *) break ;; - esac - done - - if [[ ! -z $1 ]] - then - CHROOTDIR="$1" - shift - fi - ARGS=() - for arg in "$@" - do - ARGS+=("$arg") - done -} - -function is_user_root() { - (( EUID == 0 )) -} - -function execute_operation() { - $OPT_HELP && usage && return - $OPT_VERSION && version && return - - is_user_root || die_on_status $NO_ROOT_PRIVILEGES 'This script must be run with root privileges' - - [[ -d $CHROOTDIR ]] || die_on_status $NOT_EXISTING_FILE "Can't create chroot on non-directory $CHROOTDIR" - - chroot_setup "$CHROOTDIR" || die "Failed to setup chroot $CHROOTDIR" - - $CHROOTCMD "$CHROOTDIR" "${ARGS[@]}" -} - - -function main() { - parse_arguments "$@" - execute_operation -} - -main "$@" diff --git a/bin/junest b/bin/junest index a2b96e5..4084fe0 100755 --- a/bin/junest +++ b/bin/junest @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# shellcheck disable=SC1091 # # This file is part of JuNest (https://github.com/fsquillace/junest). # @@ -7,154 +8,140 @@ set -e # JUNEST_BASE can be overridden for testing purposes. # There is no need for doing it for normal usage. -JUNEST_BASE="${JUNEST_BASE:-$(readlink -f $(dirname $(readlink -f "$0"))/..)}" +JUNEST_BASE="${JUNEST_BASE:-$(readlink -f "$(dirname "$(readlink -f "$0")")"/..)}" source "${JUNEST_BASE}/lib/utils/utils.sh" source "${JUNEST_BASE}/lib/core/common.sh" source "${JUNEST_BASE}/lib/core/build.sh" +source "${JUNEST_BASE}/lib/core/setup.sh" source "${JUNEST_BASE}/lib/core/chroot.sh" source "${JUNEST_BASE}/lib/core/namespace.sh" source "${JUNEST_BASE}/lib/core/proot.sh" -source "${JUNEST_BASE}/lib/core/setup.sh" +source "${JUNEST_BASE}/lib/core/wrappers.sh" ################################### ### General functions ### ################################### - usage() { - echo -e "$NAME (v$(cat $JUNEST_BASE/VERSION)): $DESCRIPTION" + echo -e "$NAME (v$(cat "$JUNEST_BASE"/VERSION)): $DESCRIPTION" echo - echo -e "Usage: $CMD [options] [--] [command]" + echo -e "Usage: $CMD [action] [options] [--] [command]" echo - echo -e "Setup options:" - echo -e "-i, --setup-from-file Setup the $NAME image in ${JUNEST_HOME}" - echo -e "-a, --arch $NAME architecture to download (x86_64, x86, arm)" - echo -e " Defaults to the host architecture ($ARCH)" - echo -e "-d, --delete Delete $NAME from ${JUNEST_HOME}" + echo -e "General:" + echo -e "-h, --help Show this help message" + echo -e "-V, --version Show the $NAME version" echo - echo -e "Access options:" - echo -e "-f, --fakeroot Run $NAME with fakeroot privileges" - echo -e "-g, --groot Run $NAME with root privileges via GRoot" - echo -e "-r, --root Run $NAME with root privileges via classic chroot" - echo -e "-u, --namespace Use Linux Namespace (with GRoot) instead of PRoot" - echo -e "-p, --backend-args Arguments for backend program (PRoot or GRoot)" - echo -e " ($CMD -p \"--help\" to check out the PRoot options" - echo -e " $CMD -g -p \"--help\" to check out the GRoot options" - echo -e " $CMD -r -p \"--help\" to check out the chroot options)" + echo -e "Actions and options:" + echo -e " s[etup] Setup $NAME in ${JUNEST_HOME} either from repo or from file" + echo -e " -i, --from-file Setup the $NAME image in ${JUNEST_HOME}" + echo -e " -a, --arch $NAME architecture to download (x86_64, arm)" + echo -e " Defaults to the host architecture ($ARCH)" + echo -e " -d, --delete Delete $NAME from ${JUNEST_HOME}" echo - echo -e "Building options:" - echo -e "-b, --build-image Build a $NAME image (must run in ArchLinux)" - echo -e "-n, --disable-validation Disable the $NAME image validation" + echo -e " n[s] Access via Linux Namespaces using BubbleWrap (Default action)" + echo -e " -f, --fakeroot Run $NAME with fakeroot privileges" + echo -e " --backend-command Bwrap command to use" + echo -e " -b, --backend-args Arguments for bwrap backend program" + echo -e " ($CMD ns -b \"--help\" to check out the bwrap options)" + echo -e " -n, --no-copy-files Do not copy common etc files into $NAME environment" + echo + echo -e " p[root] Access via PRoot" + echo -e " -f, --fakeroot Run $NAME with fakeroot privileges" + echo -e " --backend-command PRoot command to use" + echo -e " -b, --backend-args Arguments for PRoot backend program" + echo -e " ($CMD proot -b \"--help\" to check out the PRoot options)" + echo -e " -n, --no-copy-files Do not copy common etc files into $NAME environment" + echo + echo -e " g[root] Access with root privileges via GRoot" + echo -e " --backend-command GRoot command to use" + echo -e " -b, --backend-args Arguments for GRoot backend program" + echo -e " ($CMD groot -b \"--help\" to check out the GRoot options)" + echo -e " -n, --no-copy-files Do not copy common etc files into $NAME environment" + echo + echo -e " r[oot] Access with root privileges via classic chroot" + echo -e " --backend-command Chroot command to use" + echo -e " -b, --backend-args Arguments for chroot backend program" + echo -e " ($CMD root -b \"--help\" to check out the chroot options)" + echo -e " -n, --no-copy-files Do not copy common etc files into $NAME environment" + echo + echo -e " b[uild] Build a $NAME image (must run in ArchLinux)" + echo -e " -n, --disable-check Disable the $NAME image check" + echo + echo -e " create-bin-wrappers Create a bin wrappers directory according to --bin-path option" + echo -e " Default path is $JUNEST_HOME/usr/bin_wrappers" + echo -e " -f, --force Create the wrapper files even if they already exist" + echo -e " -p, --bin-path The source directory where executable are located in JuNest" + echo -e " Default value is: /usr/bin" echo - echo -e "General options:" - echo -e "-h, --help Show this help message" - echo -e "-V, --version Show the $NAME version" } version() { - echo -e "$NAME $(cat $JUNEST_BASE/VERSION)" + echo -e "$NAME $(cat "$JUNEST_BASE"/VERSION)" } -check_cli(){ - if $OPT_BUILD_IMAGE - then - if $OPT_DELETE || $OPT_HELP || $OPT_VERSION || $OPT_SETUP_FROM_FILE || \ - $OPT_FAKEROOT || $OPT_ROOT - then - die "The build image option must be used exclusively" - fi - fi - if $OPT_DISABLE_VALIDATION - then - if $OPT_DELETE || $OPT_HELP || $OPT_VERSION || $OPT_SETUP_FROM_FILE || \ - $OPT_FAKEROOT || $OPT_ROOT - then - die "The disable validation option must be used with the build image option only" - fi - fi - if $OPT_DELETE - then - if $OPT_BUILD_IMAGE || $OPT_HELP || $OPT_VERSION || $OPT_SETUP_FROM_FILE || \ - $OPT_FAKEROOT || $OPT_ROOT || $OPT_DISABLE_VALIDATION - then - die "The $NAME delete option must be used exclusively" - fi - fi - if $OPT_HELP - then - if $OPT_BUILD_IMAGE || $OPT_DELETE || $OPT_VERSION || $OPT_SETUP_FROM_FILE || \ - $OPT_FAKEROOT || $OPT_ROOT || $OPT_DISABLE_VALIDATION - then - die "The $NAME help option must be used exclusively" - fi - fi - if $OPT_VERSION - then - if $OPT_BUILD_IMAGE || $OPT_DELETE || $OPT_HELP || $OPT_SETUP_FROM_FILE || \ - $OPT_FAKEROOT || $OPT_ROOT || $OPT_DISABLE_VALIDATION - then - die "The $NAME version option must be used exclusively" - fi - fi - if $OPT_FAKEROOT && $OPT_ROOT - then - die "You must access to $NAME with either fakeroot or root permissions" - fi - if $OPT_BACKEND_ARGS || $OPT_ARCH - then - if $OPT_BUILD_IMAGE || $OPT_DELETE || $OPT_HELP || \ - $OPT_ROOT || $OPT_VERSION || $OPT_DISABLE_VALIDATION - then - die "Invalid syntax: Proot and arch args are not allowed with the other options" - fi - fi - if [ "$ARGS" != "" ] - then - if $OPT_BUILD_IMAGE || $OPT_DELETE || $OPT_HELP || $OPT_SETUP_FROM_FILE || \ - $OPT_VERSION || $OPT_DISABLE_VALIDATION - then - die "No arguments are needed. For the CLI syntax run: $CMD --help" - fi - fi - - return 0 -} - - function parse_arguments(){ - OPT_SETUP_FROM_FILE=false - IMAGE_FILE="" - OPT_FAKEROOT=false - OPT_ROOT=false - OPT_GROOT=false - OPT_USER_NAMESPACE=false - OPT_BACKEND_ARGS=false + # Actions + ACT_SETUP=false + ACT_BUILD=false + ACT_CREATE_WRAPPERS=false + ACT_NAMESPACE=false + ACT_PROOT=false + ACT_GROOT=false + ACT_ROOT=false + ACT_HELP=false + ACT_VERSION=false + + case "$1" in + s|setup) ACT_SETUP=true ; shift ;; + b|build) ACT_BUILD=true ; shift ;; + create-bin-wrappers) ACT_CREATE_WRAPPERS=true ; shift ;; + n|ns) ACT_NAMESPACE=true ; shift ;; + p|proot) ACT_PROOT=true ; shift ;; + g|groot) ACT_GROOT=true ; shift ;; + r|root) ACT_ROOT=true ; shift ;; + -h|--help) ACT_HELP=true ; shift ;; + -V|--version) ACT_VERSION=true ; shift ;; + *) ACT_NAMESPACE=true ;; + esac + + if $ACT_SETUP + then + _parse_setup_opts "$@" + elif $ACT_BUILD + then + _parse_build_opts "$@" + elif $ACT_CREATE_WRAPPERS + then + _parse_create_wrappers_opts "$@" + elif $ACT_NAMESPACE + then + _parse_ns_opts "$@" + elif $ACT_PROOT + then + _parse_proot_opts "$@" + elif $ACT_GROOT + then + _parse_root_opts "$@" + elif $ACT_ROOT + then + _parse_root_opts "$@" + fi +} + +function _parse_root_opts() { + # Options: BACKEND_ARGS="" - OPT_ARCH=false - ARCH_ARG="" - OPT_BUILD_IMAGE=false - OPT_DISABLE_VALIDATION=false - CHECK_ARG="" - OPT_DELETE=false - OPT_HELP=false - OPT_VERSION=false - for opt in "$@" + OPT_NO_COPY_FILES=false + BACKEND_COMMAND="" + + while [[ -n "$1" ]] do case "$1" in - -i|--setup-from-file) OPT_SETUP_FROM_FILE=true ; shift ; IMAGE_FILE=$1 ; shift ;; - -f|--fakeroot) OPT_FAKEROOT=true ; shift ;; - -r|--root) OPT_ROOT=true ; shift ;; - -g|--groot) OPT_GROOT=true ; shift ;; - -u|--namespace) OPT_USER_NAMESPACE=true ; shift ;; - -p|--backend-args) OPT_BACKEND_ARGS=true ; shift ; BACKEND_ARGS=$1; shift ;; - -a|--arch) OPT_ARCH=true ; shift ; ARCH_ARG=$1; shift ;; - -b|--build-image) OPT_BUILD_IMAGE=true ; shift ;; - -n|--disable-validation) OPT_DISABLE_VALIDATION=true ; shift ;; - -d|--delete) OPT_DELETE=true ; shift ;; - -h|--help) OPT_HELP=true ; shift ;; - -V|--version) OPT_VERSION=true ; shift ;; + -b|--backend-args) shift ; BACKEND_ARGS=$1; shift ;; + -n|--no-copy-files) OPT_NO_COPY_FILES=true ; shift ;; + --backend-command) shift; BACKEND_COMMAND="$1"; shift ;; --) shift ; break ;; -*) die "Invalid option $1" ;; *) break ;; @@ -168,53 +155,166 @@ function parse_arguments(){ done } -function execute_operation(){ - $OPT_HELP && usage && return - $OPT_VERSION && version && return +function _parse_ns_opts() { + # Options: + OPT_FAKEROOT=false + BACKEND_ARGS="" + OPT_NO_COPY_FILES=false + BACKEND_COMMAND="" - if $OPT_BUILD_IMAGE; then - build_image_env $OPT_DISABLE_VALIDATION - return - elif $OPT_DELETE; then - delete_env + while [[ -n "$1" ]] + do + case "$1" in + -f|--fakeroot) OPT_FAKEROOT=true ; shift ;; + -b|--backend-args) shift ; BACKEND_ARGS=$1; shift ;; + -n|--no-copy-files) OPT_NO_COPY_FILES=true ; shift ;; + --backend-command) shift; BACKEND_COMMAND="$1"; shift ;; + --) shift ; break ;; + -*) die "Invalid option $1" ;; + *) break ;; + esac + done + + ARGS=() + for arg in "$@" + do + ARGS+=("$arg") + done +} + +function _parse_proot_opts() { + # Options: + OPT_FAKEROOT=false + BACKEND_ARGS="" + OPT_NO_COPY_FILES=false + BACKEND_COMMAND="" + + while [[ -n "$1" ]] + do + case "$1" in + -f|--fakeroot) OPT_FAKEROOT=true ; shift ;; + -b|--backend-args) shift ; BACKEND_ARGS=$1; shift ;; + -n|--no-copy-files) OPT_NO_COPY_FILES=true ; shift ;; + --backend-command) shift; BACKEND_COMMAND="$1"; shift ;; + --) shift ; break ;; + -*) die "Invalid option $1" ;; + *) break ;; + esac + done + + ARGS=("$@") +} + +function _parse_build_opts() { + OPT_DISABLE_CHECK=false + while [[ -n "$1" ]] + do + case "$1" in + -n|--disable-check) OPT_DISABLE_CHECK=true ; shift ;; + *) die "Invalid option $1" ;; + esac + done +} + +function _parse_create_wrappers_opts() { + OPT_FORCE=false + OPT_BIN_PATH="" + while [[ -n "$1" ]] + do + case "$1" in + -f|--force) OPT_FORCE=true ; shift ;; + -p|--bin-path) shift ; OPT_BIN_PATH="$1" ; shift ;; + *) die "Invalid option $1" ;; + esac + done +} + +function _parse_setup_opts() { + OPT_FROM_FILE=false + IMAGE_FILE="" + ARCH_ARG="" + OPT_DELETE=false + while [[ -n "$1" ]] + do + case "$1" in + -i|--from-file) OPT_FROM_FILE=true ; shift ; IMAGE_FILE=$1 ; shift ;; + -a|--arch) shift ; ARCH_ARG=$1; shift ;; + -d|--delete) OPT_DELETE=true ; shift ;; + *) die "Invalid option $1" ;; + esac + done +} + +function execute_operation() { + $ACT_HELP && usage && return + $ACT_VERSION && version && return + + if $ACT_BUILD; then + # shellcheck disable=SC2086 + build_image_env $OPT_DISABLE_CHECK return fi + if $ACT_SETUP; then + if $OPT_DELETE; then + delete_env + else + if is_env_installed + then + die "Error: The image cannot be installed since $JUNEST_HOME is not empty." + fi + + if $OPT_FROM_FILE; then + setup_env_from_file "$IMAGE_FILE" + else + setup_env "$ARCH_ARG" + fi + create_wrappers + fi + + return + fi + + if ! is_env_installed then - if $OPT_SETUP_FROM_FILE; then - setup_env_from_file $IMAGE_FILE - else - setup_env $ARCH_ARG - unset ARCH_ARG - fi - elif $OPT_SETUP_FROM_FILE; then - die "Error: The image cannot be installed since $JUNEST_HOME is not empty." + die "Error: The image is still not installed in $JUNEST_HOME. Run this first: $CMD setup" fi - [ -z "${ARCH_ARG}" ] || \ - die "The option --arch cannot be specified since JuNest has already been downloaded in $JUNEST_HOME" + if $ACT_CREATE_WRAPPERS; then + # shellcheck disable=SC2086 + create_wrappers $OPT_FORCE "$OPT_BIN_PATH" + exit + fi local run_env - if $OPT_USER_NAMESPACE; then - run_env=run_env_with_namespace - elif $OPT_FAKEROOT; then - run_env=run_env_as_fakeroot - elif $OPT_ROOT; then - run_env=run_env_as_chroot - elif $OPT_GROOT; then + if $ACT_NAMESPACE; then + if $OPT_FAKEROOT; then + run_env=run_env_as_bwrap_fakeroot + else + run_env=run_env_as_bwrap_user + fi + elif $ACT_PROOT; then + if $OPT_FAKEROOT; then + run_env=run_env_as_proot_fakeroot + else + run_env=run_env_as_proot_user + fi + elif $ACT_GROOT; then run_env=run_env_as_groot - else - run_env=run_env_as_user + elif $ACT_ROOT; then + run_env=run_env_as_chroot fi - $run_env "${BACKEND_ARGS}" "${ARGS[@]}" - + # Call create_wrappers in case new bin files have been created + # shellcheck disable=SC2064 + trap "PATH=$PATH create_wrappers" EXIT QUIT TERM + # shellcheck disable=SC2086 + $run_env "$BACKEND_COMMAND" "${BACKEND_ARGS}" $OPT_NO_COPY_FILES "${ARGS[@]}" } function main() { parse_arguments "$@" - check_cli execute_operation } diff --git a/bin/sudoj b/bin/sudoj new file mode 100755 index 0000000..aa43e15 --- /dev/null +++ b/bin/sudoj @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# +# This file is part of JuNest (https://github.com/fsquillace/junest). +# + +export PATH="${PATH}:${JUNEST_HOME}/usr/bin_wrappers" + +JUNEST_ARGS="ns --fakeroot" "$@" diff --git a/ci/build_image.sh b/ci/build_image.sh new file mode 100755 index 0000000..f9cda95 --- /dev/null +++ b/ci/build_image.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -ex + +pacman -Sy --noconfirm sudo + +# Create a travis user +echo "travis ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/travis +chmod 'u=r,g=r,o=' /etc/sudoers.d/travis +groupadd --gid "2000" "travis" +useradd --create-home --uid "2000" --gid "2000" --shell /usr/bin/false "travis" + +# Here do not make any validation (-n) because it will be done later on in the Ubuntu host directly +cd /build +runuser -u travis -- /build/bin/junest build -n diff --git a/ci/deploy.sh b/ci/deploy.sh new file mode 100755 index 0000000..a45d0a1 --- /dev/null +++ b/ci/deploy.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +set -e + +IMG_PATH=$1 + +set -ux + +MAX_OLD_IMAGES=5 +ENDPOINT="https://8da1bcd84e423c9b013b69fe1e8b4675.r2.cloudflarestorage.com" + +# ARCH can be one of: x86, x86_64, arm +HOST_ARCH=$(uname -m) +if [ "$HOST_ARCH" == "i686" ] || [ "$HOST_ARCH" == "i386" ] +then + ARCH="x86" +elif [ "$HOST_ARCH" == "x86_64" ] +then + ARCH="x86_64" +elif [[ $HOST_ARCH =~ .*(arm).* ]] +then + ARCH="arm" +else + echo "Unknown architecture ${HOST_ARCH}" >&2 + exit 11 +fi + +if [[ "$TRAVIS_BRANCH" == "master" ]] +then + + export AWS_DEFAULT_REGION=auto + # Upload image + # The put is done via a temporary filename in order to prevent outage on the + # production file for a longer period of time. + img_name=$(basename "${IMG_PATH}") + aws s3 --endpoint-url="$ENDPOINT" cp "${IMG_PATH}" s3://junest-repo/junest/ + + DATE=$(date +'%Y-%m-%d-%H-%M-%S') + aws s3 --endpoint-url="$ENDPOINT" cp "${IMG_PATH}" "s3://junest-repo/junest/${img_name}.${DATE}" + + # Cleanup old images + aws s3 --endpoint-url="$ENDPOINT" ls s3://junest-repo/junest/junest-${ARCH}.tar.gz. | awk '{print $4}' | head -n -${MAX_OLD_IMAGES} | xargs -I {} aws s3 --endpoint-url="$ENDPOINT" rm "s3://junest-repo/junest/{}" + + # Test the newly deployed image can be downloaded correctly + junest setup + junest -- echo "Installed JuNest (\$(uname -m))" + yes | junest setup --delete +fi diff --git a/ci/install-bash.sh b/ci/install-bash.sh new file mode 100755 index 0000000..b766123 --- /dev/null +++ b/ci/install-bash.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -ex + +VERSION=$1 + +cd /tmp +wget "http://ftp.gnu.org/gnu/bash/bash-$VERSION.tar.gz" + +tar -zxf "bash-$VERSION.tar.gz" +cd /tmp/bash-"$VERSION"* +./configure +make +sudo make install diff --git a/lib/checks/check.sh b/lib/checks/check.sh index 4a0cd73..789206e 100755 --- a/lib/checks/check.sh +++ b/lib/checks/check.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# shellcheck disable=SC1091 # # This modules is used for: # - Running checks against the building JuNest image @@ -9,21 +10,32 @@ # # vim: ft=sh -set -eu +set -ex + -OPT_RUN_ROOT_TESTS=${1:-false} RUN_ROOT_TESTS=false -[[ ${OPT_RUN_ROOT_TESTS} == "--run-root-tests" ]] && RUN_ROOT_TESTS=true - -OPT_SKIP_AUR_TESTS=${1:-false} SKIP_AUR_TESTS=false -[[ ${OPT_SKIP_AUR_TESTS} == "--skip-aur-tests" ]] && SKIP_AUR_TESTS=true +USE_SUDO=false +while [[ -n "$1" ]] +do + case "$1" in + --run-root-tests) RUN_ROOT_TESTS=true ; shift ;; + --skip-aur-tests) SKIP_AUR_TESTS=true ; shift ;; + --use-sudo) USE_SUDO=true ; shift ;; + *) die "Invalid option $1" ;; + esac +done + +set -u + +SUDO="" +[[ -n $USE_SUDO ]] && SUDO="sudo" JUNEST_HOME=${JUNEST_HOME:-$HOME/.junest} # JUNEST_BASE can be overridden for testing purposes. # There is no need for doing it for normal usage. -JUNEST_BASE="${JUNEST_BASE:-$(readlink -f $(dirname $(readlink -f "$0"))/../..)}" +JUNEST_BASE="${JUNEST_BASE:-$(readlink -f "$(dirname "$(readlink -f "$0")")"/../..)}" source "${JUNEST_BASE}/lib/utils/utils.sh" source "${JUNEST_BASE}/lib/core/common.sh" @@ -31,32 +43,60 @@ source "${JUNEST_BASE}/lib/core/common.sh" info "Validating JuNest located in ${JUNEST_HOME}..." info "Initial JuNest setup..." -echo "Server = ${DEFAULT_MIRROR}" >> /etc/pacman.d/mirrorlist -pacman --noconfirm -Syy -pacman --noconfirm -S base-devel +# The following ensures that the gpg agent gets killed (if exists) +# otherwise it is not possible to exit from the session +trap "[[ -e /etc/pacman.d/gnupg/S.gpg-agent ]] && gpg-connect-agent -S /etc/pacman.d/gnupg/S.gpg-agent killagent /bye" QUIT EXIT ABRT TERM INT -info "Checking essential executables work..." -pacman -Qi pacman 1> /dev/null -yogurt -V 1> /dev/null -/opt/proot/proot-$ARCH --help 1> /dev/null +prepare_archlinux "$SUDO" + +PACMAN_OPTIONS="--noconfirm --disable-download-timeout" +# shellcheck disable=SC2086 +$SUDO pacman $PACMAN_OPTIONS -S grep coreutils +# shellcheck disable=SC2086 +# shellcheck disable=SC2046 +$SUDO pacman $PACMAN_OPTIONS -Syu --ignore sudo base-devel + +info "Checking basic executables work..." +$SUDO pacman -Qi pacman 1> /dev/null +/usr/bin/groot --help 1> /dev/null + +# Test FAKEROOTDONTTRYCHOWN is set to true by default +set +u +if [[ -z $FAKEROOTKEY ]] +then + fakeroot chown root /tmp +else + chown root /tmp +fi +set -u repo_package1=tree echo "Checking ${repo_package1} package from official repo..." -pacman --noconfirm -S ${repo_package1} +# shellcheck disable=SC2086 +$SUDO pacman $PACMAN_OPTIONS -S ${repo_package1} tree -L 1 -pacman --noconfirm -Rsn ${repo_package1} +# shellcheck disable=SC2086 +$SUDO pacman $PACMAN_OPTIONS -Rsn ${repo_package1} repo_package2=iftop info "Checking ${repo_package2} package from official repo..." -pacman --noconfirm -S ${repo_package2} -$RUN_ROOT_TESTS && iftop -t -s 5 -pacman --noconfirm -Rsn ${repo_package2} +# shellcheck disable=SC2086 +$SUDO pacman $PACMAN_OPTIONS -S ${repo_package2} +if $RUN_ROOT_TESTS +then + # Time it out given that sometimes it gets stuck after few seconds. + $SUDO timeout 10 iftop -t -s 5 || true +fi +# shellcheck disable=SC2086 +$SUDO pacman $PACMAN_OPTIONS -Rsn ${repo_package2} if ! $SKIP_AUR_TESTS then aur_package=tcptraceroute info "Checking ${aur_package} package from AUR repo..." - yogurt -A --noconfirm -S ${aur_package} - $RUN_ROOT_TESTS && tcptraceroute localhost - pacman --noconfirm -Rsn ${aur_package} + yay --noconfirm -S ${aur_package} + # shellcheck disable=SC2086 + $SUDO pacman $PACMAN_OPTIONS -Rsn ${aur_package} fi + +exit 0 diff --git a/lib/checks/check_all.sh b/lib/checks/check_all.sh new file mode 100755 index 0000000..13e9237 --- /dev/null +++ b/lib/checks/check_all.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Multiple tests against different execution modes + +set -ex + +# JUNEST_BASE can be overridden for testing purposes. +# There is no need for doing it for normal usage. +JUNEST_BASE="${JUNEST_BASE:-$(readlink -f "$(dirname "$(readlink -f "$0")")"/../..)}" + +JUNEST_SCRIPT=${JUNEST_SCRIPT:-${JUNEST_BASE}/bin/junest} + +CHECK_SCRIPT=${JUNEST_BASE}/lib/checks/check.sh + +$JUNEST_SCRIPT proot --fakeroot -- "$CHECK_SCRIPT" --skip-aur-tests +$JUNEST_SCRIPT proot -- "$CHECK_SCRIPT" --skip-aur-tests --use-sudo +# Test the backend command option +$JUNEST_SCRIPT proot --backend-command "$JUNEST_HOME/usr/bin/proot-x86_64" -- exit +$JUNEST_SCRIPT ns --fakeroot -- "$CHECK_SCRIPT" --skip-aur-tests +$JUNEST_SCRIPT ns -- "$CHECK_SCRIPT" --use-sudo +# Test the backend command option +$JUNEST_SCRIPT ns --backend-command "$JUNEST_HOME/usr/bin/bwrap" -- exit +sudo -E "$JUNEST_SCRIPT" groot -- "$CHECK_SCRIPT" --run-root-tests --skip-aur-tests + +# Test the wrappers work +"$JUNEST_SCRIPT" create-bin-wrappers --force +"$JUNEST_HOME"/usr/bin_wrappers/pacman --help + +"$JUNEST_SCRIPT" create-bin-wrappers --force --bin-path /usr/bin/core_perl/ +"$JUNEST_HOME"/usr/bin/core_perl_wrappers/shasum --help + +"${JUNEST_BASE}/bin/sudoj" pacman -Syu diff --git a/lib/core/build.sh b/lib/core/build.sh index 78b8925..65a9474 100644 --- a/lib/core/build.sh +++ b/lib/core/build.sh @@ -8,93 +8,134 @@ # # vim: ft=sh -function _check_package(){ - if ! pacman -Qq $1 > /dev/null - then - die "Package $1 must be installed" - fi +function _install_pkg(){ + # This function allows to install packages from AUR. + # At the moment is not used. + local maindir=$1 + local pkgbuilddir=$2 + # Generate a working directory because sources will be downloaded to there + working_dir=$(TMPDIR=/tmp mktemp -d -t junest-wd.XXXXXXXXXX) + cp -R "$pkgbuilddir"/* "$working_dir" + builtin cd "${working_dir}" || return 1 + makepkg -sfcd + makepkg --printsrcinfo > "${pkgbuilddir}"/.SRCINFO + sudo pacman --noconfirm --root "${maindir}"/root -U ./*.pkg.tar.* } -function _install_from_aur(){ - local maindir=$1 - local pkgname=$2 - local installname=$3 - mkdir -p ${maindir}/packages/${pkgname} - builtin cd ${maindir}/packages/${pkgname} - $CURL "https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=${pkgname}" - [ -z "${installname}" ] || $CURL "https://aur.archlinux.org/cgit/aur.git/plain/${installname}?h=${pkgname}" - makepkg -sfc - sudo pacman --noconfirm --root ${maindir}/root -U ${pkgname}*.pkg.tar.xz +function _prepare() { + # ArchLinux System initialization + prepare_archlinux + # curl is used to download pacman.conf file + sudo pacman -S --noconfirm git arch-install-scripts haveged curl } function build_image_env(){ + set -x umask 022 # The function must runs on ArchLinux with non-root privileges. + # This is because installing AUR packages can be done by normal users only. (( EUID == 0 )) && \ die "You cannot build with root privileges." - _check_package arch-install-scripts - _check_package gcc - _check_package package-query - _check_package git + _prepare local disable_validation=$1 - local maindir=$(TMPDIR=$JUNEST_TEMPDIR mktemp -d -t ${CMD}.XXXXXXXXXX) - sudo mkdir -p ${maindir}/root - trap - QUIT EXIT ABRT KILL TERM INT - trap "sudo rm -rf ${maindir}; die \"Error occurred when installing ${NAME}\"" EXIT QUIT ABRT KILL TERM INT + local maindir + maindir=$(TMPDIR=$JUNEST_TEMPDIR mktemp -d -t "${CMD}".XXXXXXXXXX) + sudo mkdir -p "${maindir}"/root + trap - QUIT EXIT ABRT TERM INT + # shellcheck disable=SC2064 + trap "sudo rm -rf ${maindir}; die \"Error occurred when installing ${NAME}\"" EXIT QUIT ABRT TERM INT info "Installing pacman and its dependencies..." - # The archlinux-keyring and libunistring are due to missing dependencies declaration in ARM archlinux # All the essential executables (ln, mkdir, chown, etc) are in coreutils - # yaourt requires sed - # localedef (called by locale-gen) requires gzip - # unshare command belongs to util-linux - sudo pacstrap -G -M -d ${maindir}/root pacman coreutils libunistring archlinux-keyring sed gzip util-linux - sudo bash -c "echo 'Server = $DEFAULT_MIRROR' >> ${maindir}/root/etc/pacman.d/mirrorlist" - sudo mkdir -p ${maindir}/root/run/lock + # bwrap command belongs to bubblewrap + sudo pacstrap -G -M "${maindir}"/root pacman coreutils bubblewrap - # AUR packages requires non-root user to be compiled. proot fakes the user to 10 - info "Compiling and installing yaourt..." - _install_from_aur ${maindir} "package-query" - _install_from_aur ${maindir} "yaourt" + if [[ ${ARCH} != "arm" ]] + then + # x86_64 does not have any mirror set by default... + sudo bash -c "echo 'Server = $DEFAULT_MIRROR' >> ${maindir}/root/etc/pacman.d/mirrorlist" + fi + sudo mkdir -p "${maindir}"/root/run/lock - info "Install ${NAME} script..." - sudo pacman --noconfirm --root ${maindir}/root -S git - _install_from_aur ${maindir} "${CMD}-git" "${CMD}.install" - sudo pacman --noconfirm --root ${maindir}/root -Rsn git + # For some reasons, pacstrap does not create the pacman.conf file, + # I could not reproduce the issue locally though: + # https://app.travis-ci.com/github/fsquillace/junest/builds/268216346 + [[ -e "${maindir}"/root/etc/pacman.conf ]] || sudo curl "https://gitlab.archlinux.org/archlinux/packaging/packages/pacman/-/raw/main/pacman.conf" -o "${maindir}/root/etc/pacman.conf" + + # Pacman/pacstrap bug: https://gitlab.archlinux.org/archlinux/packaging/packages/arch-install-scripts/-/issues/3 + sudo sed -i '/^DownloadUser = alpm$/d' "${maindir}"/root/etc/pacman.conf + + sudo tee -a "${maindir}"/root/etc/pacman.conf < ${maindir}/root/etc/${CMD}/info" + # Related to: https://github.com/fsquillace/junest/issues/305 + sudo bash -c "echo 'export FAKEROOTDONTTRYCHOWN=true' > ${maindir}/root/etc/profile.d/junest.sh" info "Generating the locales..." - # sed command is required for locale-gen - sudo ln -sf /usr/share/zoneinfo/posix/UTC ${maindir}/root/etc/localtime + # sed command is required for locale-gen but it is required by fakeroot + # and cannot be removed + # localedef (called by locale-gen) requires gzip but it is supposed to be + # already installed as systemd already depends on it + sudo pacman --noconfirm --root "${maindir}"/root -S sed gzip + sudo ln -sf /usr/share/zoneinfo/posix/UTC "${maindir}"/root/etc/localtime sudo bash -c "echo 'en_US.UTF-8 UTF-8' >> ${maindir}/root/etc/locale.gen" - sudo ${maindir}/root/opt/junest/bin/groot ${maindir}/root locale-gen + sudo "${maindir}"/root/bin/groot "${maindir}"/root locale-gen sudo bash -c "echo LANG=\"en_US.UTF-8\" >> ${maindir}/root/etc/locale.conf" info "Setting up the pacman keyring (this might take a while!)..." - sudo ${maindir}/root/opt/junest/bin/groot -b /dev ${maindir}/root bash -c \ - "pacman-key --init; pacman-key --populate archlinux; [ -e /etc/pacman.d/gnupg/S.gpg-agent ] && gpg-connect-agent -S /etc/pacman.d/gnupg/S.gpg-agent killagent /bye" + if [[ $(uname -m) == *"arm"* ]] + then + sudo pacman -S --noconfirm --root "${maindir}"/root archlinuxarm-keyring + else + sudo pacman -S --noconfirm --root "${maindir}"/root archlinux-keyring + fi + sudo mount --bind "${maindir}"/root "${maindir}"/root + sudo arch-chroot "${maindir}"/root bash -c ' + set -e + pacman-key --init; + for keyring_file in /usr/share/pacman/keyrings/*.gpg; + do + keyring=$(basename $keyring_file | cut -f 1 -d "."); + pacman-key --populate $keyring; + done;' + sudo umount "${maindir}"/root - sudo rm ${maindir}/root/var/cache/pacman/pkg/* + sudo rm "${maindir}"/root/var/cache/pacman/pkg/* + # This is needed on system with busybox tar command. + # If the file does not have write permission, the tar command to extract files fails. + sudo chmod -R u+rw "${maindir}"/root/ - mkdir -p ${maindir}/output - builtin cd ${maindir}/output + mkdir -p "${maindir}"/output + builtin cd "${maindir}"/output || return 1 local imagefile="${CMD}-${ARCH}.tar.gz" info "Compressing image to ${imagefile}..." - sudo $TAR -zcpf ${imagefile} -C ${maindir}/root . + sudo "$TAR" -zcpf "${imagefile}" -C "${maindir}"/root . if ! $disable_validation then - mkdir -p ${maindir}/root_test - $TAR -zxpf ${imagefile} -C "${maindir}/root_test" - JUNEST_HOME="${maindir}/root_test" ${maindir}/root_test/opt/${CMD}/bin/${CMD} -f ${JUNEST_BASE}/lib/checks/check.sh - JUNEST_HOME="${maindir}/root_test" sudo -E ${maindir}/root_test/opt/${CMD}/bin/${CMD} -g ${JUNEST_BASE}/lib/checks/check.sh --run-root-tests + mkdir -p "${maindir}"/root_test + $TAR -zxpf "${imagefile}" -C "${maindir}/root_test" + JUNEST_HOME="${maindir}/root_test" "${JUNEST_BASE}"/lib/checks/check_all.sh fi - sudo cp ${maindir}/output/${imagefile} ${ORIGIN_WD} + sudo cp "${maindir}"/output/"${imagefile}" "${ORIGIN_WD}" - builtin cd ${ORIGIN_WD} + builtin cd "${ORIGIN_WD}" || return 1 trap - QUIT EXIT ABRT KILL TERM INT sudo rm -fr "$maindir" + + set +x } diff --git a/lib/core/chroot.sh b/lib/core/chroot.sh index 9a104aa..c2237a9 100644 --- a/lib/core/chroot.sh +++ b/lib/core/chroot.sh @@ -11,26 +11,30 @@ function _run_env_as_xroot(){ local cmd=$1 local backend_args="$2" - shift 2 + local no_copy_files="$3" + shift 3 check_same_arch local uid=$UID # SUDO_USER is more reliable compared to SUDO_UID - [ -z $SUDO_USER ] || uid=$SUDO_USER:$SUDO_GID + [[ -z $SUDO_USER ]] || uid=$SUDO_USER:$SUDO_GID - local main_cmd="${SH[@]}" - [ "$1" != "" ] && main_cmd="$(insert_quotes_on_spaces "$@")" + local args=() + [[ "$1" != "" ]] && args=("-c" "$(insert_quotes_on_spaces "${@}")") # With chown the ownership of the files is assigned to the real user trap - QUIT EXIT ABRT KILL TERM INT - trap "[ -z $uid ] || chown_cmd -R ${uid} ${JUNEST_HOME};" EXIT QUIT ABRT KILL TERM INT + # shellcheck disable=SC2064 + trap "[ -z $uid ] || chown_cmd -R ${uid} ${JUNEST_HOME};" EXIT QUIT ABRT TERM INT - copy_common_files + if ! $no_copy_files + then + copy_common_files + fi - check_nested_env - - JUNEST_ENV=1 $cmd $backend_args "$JUNEST_HOME" "${SH[@]}" "-c" "${main_cmd}" + # shellcheck disable=SC2086 + JUNEST_ENV=1 $cmd $backend_args "$JUNEST_HOME" "${DEFAULT_SH[@]}" "${args[@]}" } ####################################### @@ -41,25 +45,31 @@ function _run_env_as_xroot(){ # UID (RO) : The user ID. # SUDO_USER (RO) : The sudo user ID. # SUDO_GID (RO) : The sudo group ID. -# SH (RO) : Contains the default command to run in JuNest. +# DEFAULT_SH (RO) : Contains the default command to run in JuNest. # Arguments: -# backend_args ($1) : The arguments to pass to proot -# cmd ($2-?) : The command to run inside JuNest environment. -# Default command is defined by SH variable. +# backend_args ($1) : The arguments to pass to backend program +# no_copy_files ($2?) : If false it will copy some files in /etc +# from host to JuNest environment. +# cmd ($3-?) : The command to run inside JuNest environment. +# Default command is defined by DEFAULT_SH variable. # Returns: # $ARCHITECTURE_MISMATCH : If host and JuNest architecture are different. # Output: # - : The command output. ####################################### function run_env_as_groot(){ - local backend_args="$1" - shift + check_nested_env + + local backend_command="${1:-$GROOT}" + local backend_args="$2" + local no_copy_files="$3" + shift 3 provide_common_bindings local bindings=${RESULT} unset RESULT - _run_env_as_xroot "$GROOT $bindings" "$backend_args" "$@" + _run_env_as_xroot "$backend_command $bindings" "$backend_args" "$no_copy_files" "$@" } ####################################### @@ -70,19 +80,25 @@ function run_env_as_groot(){ # UID (RO) : The user ID. # SUDO_USER (RO) : The sudo user ID. # SUDO_GID (RO) : The sudo group ID. -# SH (RO) : Contains the default command to run in JuNest. +# DEFAULT_SH (RO) : Contains the default command to run in JuNest. # Arguments: -# backend_args ($1) : The arguments to pass to proot -# cmd ($2-?) : The command to run inside JuNest environment. -# Default command is defined by SH variable. +# backend_args ($1) : The arguments to pass to backend program +# no_copy_files ($2?) : If false it will copy some files in /etc +# from host to JuNest environment. +# cmd ($3-?) : The command to run inside JuNest environment. +# Default command is defined by DEFAULT_SH variable. # Returns: # $ARCHITECTURE_MISMATCH : If host and JuNest architecture are different. # Output: # - : The command output. ####################################### function run_env_as_chroot(){ - local backend_args="$1" - shift + check_nested_env - _run_env_as_xroot chroot_cmd "$backend_args" "$@" + local backend_command="${1:-chroot_cmd}" + local backend_args="$2" + local no_copy_files="$3" + shift 3 + + _run_env_as_xroot "$backend_command" "$backend_args" "$no_copy_files" "$@" } diff --git a/lib/core/common.sh b/lib/core/common.sh index d2ab487..df79bec 100644 --- a/lib/core/common.sh +++ b/lib/core/common.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash +# shellcheck disable=SC2034 +# shellcheck disable=SC1091 # # This module contains all common functionalities for JuNest. # @@ -18,30 +20,32 @@ ROOT_ACCESS_ERROR=105 NESTED_ENVIRONMENT=106 VARIABLE_NOT_SET=107 NO_CONFIG_FOUND=108 +UNPRIVILEGED_USERNS_DISABLED=109 JUNEST_HOME=${JUNEST_HOME:-~/.${CMD}} -JUNEST_BASE=${JUNEST_BASE:-${JUNEST_HOME}/opt/junest} JUNEST_TEMPDIR=${JUNEST_TEMPDIR:-/tmp} # The update of the variable PATH ensures that the executables are # found on different locations -PATH=/usr/bin:/bin:/usr/sbin:/sbin:$PATH +PATH=/usr/bin:/bin:/usr/local/bin:/usr/sbin:/sbin:${HOME}/.local/bin:"$PATH" # The executable uname is essential in order to get the architecture # of the host system, so a fallback mechanism cannot be used for it. -UNAME=uname +UNAME="uname" ARCH_LIST=('x86_64' 'x86' 'arm') HOST_ARCH=$($UNAME -m) -if [ $HOST_ARCH == "i686" ] || [ $HOST_ARCH == "i386" ] +# To check all available architectures look here: +# https://wiki.archlinux.org/index.php/PKGBUILD#arch +if [[ $HOST_ARCH == "i686" ]] || [[ $HOST_ARCH == "i386" ]] then ARCH="x86" LD_LIB="${JUNEST_HOME}/lib/ld-linux.so.2" -elif [ $HOST_ARCH == "x86_64" ] +elif [[ $HOST_ARCH == "x86_64" ]] then ARCH="x86_64" LD_LIB="${JUNEST_HOME}/lib64/ld-linux-x86-64.so.2" -elif [[ $HOST_ARCH =~ .*(arm).* ]] +elif [[ $HOST_ARCH =~ .*(arm).* ]] || [[ $HOST_ARCH == "aarch64" ]] then ARCH="arm" LD_LIB="${JUNEST_HOME}/lib/ld-linux-armhf.so.3" @@ -49,9 +53,11 @@ else die "Unknown architecture ${HOST_ARCH}" fi -MAIN_REPO=https://s3-eu-west-1.amazonaws.com/${CMD}-repo +MAIN_REPO=https://link.storjshare.io/s/jvb5tgarnjtt565fffa44spvyuga/junest-repo +MAIN_REPO=https://pub-a2af2344e8554f6c807bc3db355ae622.r2.dev ENV_REPO=${MAIN_REPO}/${CMD} -DEFAULT_MIRROR='https://mirrors.kernel.org/archlinux/$repo/os/$arch' +# shellcheck disable=SC2016 +DEFAULT_MIRROR='https://mirror.rackspace.com/archlinux/$repo/os/$arch' ORIGIN_WD=$(pwd) @@ -62,24 +68,26 @@ ORIGIN_WD=$(pwd) # different locations in the host OS. # List of executables that are run inside JuNest: -SH=("/bin/sh" "--login") +DEFAULT_SH=("/bin/sh" "--login") # List of executables that are run in the host OS: -PROOT="${JUNEST_HOME}/opt/proot/proot-${ARCH}" -GROOT=${JUNEST_BASE}/bin/groot +BWRAP="${JUNEST_HOME}/usr/bin/bwrap" +PROOT="${JUNEST_HOME}/usr/bin/proot-${ARCH}" +GROOT="${JUNEST_HOME}/usr/bin/groot" CLASSIC_CHROOT=chroot -WGET="wget --no-check-certificate" +WGET="wget --content-disposition --no-check-certificate" CURL="curl -L -J -O -k" -TAR=tar +TAR="tar" CHOWN="chown" -LN=ln -RM=rm -MKDIR=mkdir -GETENT=getent -CP=cp +LN="ln" +RM="rm" +MKDIR="mkdir" +GETENT="getent" +CP="cp" +ID="id" # Used for checking user namespace in config.gz file -ZGREP=zgrep -UNSHARE=unshare +ZGREP="zgrep" +UNSHARE="unshare" LD_EXEC="$LD_LIB --library-path ${JUNEST_HOME}/usr/lib:${JUNEST_HOME}/lib" @@ -88,32 +96,32 @@ LD_EXEC="$LD_LIB --library-path ${JUNEST_HOME}/usr/lib:${JUNEST_HOME}/lib" # image. function ln_cmd(){ - $LN "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$LN "$@" + $LN "$@" || $LD_EXEC "${JUNEST_HOME}"/usr/bin/$LN "$@" } function getent_cmd(){ - $GETENT "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$GETENT "$@" + $GETENT "$@" || $LD_EXEC "${JUNEST_HOME}"/usr/bin/$GETENT "$@" } function cp_cmd(){ - $CP "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$CP "$@" + $CP "$@" || $LD_EXEC "${JUNEST_HOME}"/usr/bin/$CP "$@" } function rm_cmd(){ - $RM "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$RM "$@" + $RM "$@" || $LD_EXEC "${JUNEST_HOME}"/usr/bin/$RM "$@" } function chown_cmd(){ - $CHOWN "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$CHOWN "$@" + $CHOWN "$@" || $LD_EXEC "${JUNEST_HOME}"/usr/bin/$CHOWN "$@" } function mkdir_cmd(){ - $MKDIR "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$MKDIR "$@" + $MKDIR "$@" || $LD_EXEC "${JUNEST_HOME}"/usr/bin/$MKDIR "$@" } function zgrep_cmd(){ # No need for LD_EXEC as zgrep is a POSIX shell script - $ZGREP "$@" || ${JUNEST_HOME}/usr/bin/$ZGREP "$@" + $ZGREP "$@" || "${JUNEST_HOME}"/usr/bin/$ZGREP "$@" } function download_cmd(){ @@ -121,7 +129,7 @@ function download_cmd(){ } function chroot_cmd(){ - $CLASSIC_CHROOT "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$CLASSIC_CHROOT "$@" + $CLASSIC_CHROOT "$@" || $LD_EXEC "${JUNEST_HOME}"/usr/bin/$CLASSIC_CHROOT "$@" } function unshare_cmd(){ @@ -129,27 +137,40 @@ function unshare_cmd(){ # with --user option available. # Hence, give priority to the `unshare` executable in JuNest image. # Also, unshare provides an environment in which /bin/sh maps to dash shell, - # therefore it ignores all the remaining SH arguments (i.e. --login) as + # therefore it ignores all the remaining DEFAULT_SH arguments (i.e. --login) as # they are not supported by dash. - if $LD_EXEC ${JUNEST_HOME}/usr/bin/$UNSHARE --user "${SH[0]}" "-c" ":" + if $LD_EXEC "${JUNEST_HOME}"/usr/bin/$UNSHARE --user "${DEFAULT_SH[0]}" "-c" ":" then - $LD_EXEC ${JUNEST_HOME}/usr/bin/$UNSHARE "${@}" - elif $UNSHARE --user "${SH[0]}" "-c" ":" + $LD_EXEC "${JUNEST_HOME}"/usr/bin/$UNSHARE "${@}" + elif $UNSHARE --user "${DEFAULT_SH[0]}" "-c" ":" then $UNSHARE "$@" else - die "Error: Something went wrong with unshare command. Exiting" + die "Error: Something went wrong while executing unshare command. Exiting" + fi +} + +function bwrap_cmd(){ + if $LD_EXEC "$BWRAP" --dev-bind / / "${DEFAULT_SH[0]}" "-c" ":" + then + $LD_EXEC "$BWRAP" "${@}" + else + die "Error: Something went wrong while executing bwrap command. Exiting" fi } function proot_cmd(){ local proot_args="$1" shift - if ${PROOT} ${proot_args} "${SH[@]}" "-c" ":" + # shellcheck disable=SC2086 + if ${PROOT} ${proot_args} "${DEFAULT_SH[@]}" "-c" ":" then + # shellcheck disable=SC2086 ${PROOT} ${proot_args} "${@}" - elif PROOT_NO_SECCOMP=1 ${PROOT} ${proot_args} "${SH[@]}" "-c" ":" + elif PROOT_NO_SECCOMP=1 ${PROOT} ${proot_args} "${DEFAULT_SH[@]}" "-c" ":" then + warn "Warn: Proot is not properly working. Disabling SECCOMP and expect the application to run slowly in particular when it uses syscalls intensively." + warn "Try to use Linux namespace instead as it is more reliable: junest ns" PROOT_NO_SECCOMP=1 ${PROOT} ${proot_args} "${@}" else die "Error: Something went wrong with proot command. Exiting" @@ -178,7 +199,7 @@ function check_nested_env() { if [[ $JUNEST_ENV == "1" ]] then die_on_status $NESTED_ENVIRONMENT "Error: Nested ${NAME} environments are not allowed" - elif [[ ! -z $JUNEST_ENV ]] && [[ $JUNEST_ENV != "0" ]] + elif [[ -n $JUNEST_ENV ]] && [[ $JUNEST_ENV != "0" ]] then die_on_status $VARIABLE_NOT_SET "The variable JUNEST_ENV is not properly set" fi @@ -200,7 +221,7 @@ function check_nested_env() { # None ####################################### function check_same_arch() { - source ${JUNEST_HOME}/etc/junest/info + source "${JUNEST_HOME}"/etc/junest/info [ "$JUNEST_ARCH" != "$ARCH" ] && \ die_on_status $ARCHITECTURE_MISMATCH "The host system architecture is not correct: $ARCH != $JUNEST_ARCH" return 0 @@ -225,7 +246,7 @@ function check_same_arch() { function provide_common_bindings(){ RESULT="" local re='(.*):.*' - for bind in "/dev" "/sys" "/proc" "/tmp" "$HOME" + for bind in "/dev" "/sys" "/proc" "/tmp" "$HOME" "/run/user/$($ID -u)" do if [[ $bind =~ $re ]] then @@ -258,24 +279,26 @@ function copy_passwd_and_group(){ # is configured. # Try to at least get the current user via `getent passwd $USER` since it uses # a more reliable and faster system call (getpwnam(3)). - if ! getent_cmd passwd > ${JUNEST_HOME}/etc/passwd || \ - ! getent_cmd passwd ${USER} >> ${JUNEST_HOME}/etc/passwd + if ! getent_cmd passwd > "${JUNEST_HOME}"/etc/passwd || \ + ! getent_cmd passwd "${USER}" >> "${JUNEST_HOME}"/etc/passwd then warn "getent command failed or does not exist. Binding directly from /etc/passwd." - copy_file /etc/passwd ${JUNEST_HOME}/etc/passwd + copy_file /etc/passwd fi - if ! getent_cmd group > ${JUNEST_HOME}/etc/group + if ! getent_cmd group > "${JUNEST_HOME}"/etc/group then warn "getent command failed or does not exist. Binding directly from /etc/group." - copy_file /etc/group ${JUNEST_HOME}/etc/group + copy_file /etc/group fi return 0 } function copy_file() { local file="${1}" - [[ -r "$file" ]] && cp_cmd "$file" "${JUNEST_HOME}/$file" + # -f option ensure to remove destination file if it cannot be opened + # https://github.com/fsquillace/junest/issues/284 + [[ -r "$file" ]] && cp_cmd -f "$file" "${JUNEST_HOME}/$file" return 0 } @@ -286,3 +309,27 @@ function copy_common_files() { copy_file /etc/resolv.conf return 0 } + +function prepare_archlinux() { + local sudo=${1:-sudo} + local pacman_options="--noconfirm --disable-download-timeout" + + # shellcheck disable=SC2086 + $sudo pacman $pacman_options -Syy + + $sudo pacman-key --init + + if [[ $(uname -m) == *"arm"* ]] + then + # shellcheck disable=SC2086 + $sudo pacman $pacman_options -S archlinuxarm-keyring + $sudo pacman-key --populate archlinuxarm + else + # shellcheck disable=SC2086 + $sudo pacman $pacman_options -S archlinux-keyring + $sudo pacman-key --populate archlinux + fi + + # shellcheck disable=SC2086 + $sudo pacman $pacman_options -Su +} diff --git a/lib/core/namespace.sh b/lib/core/namespace.sh index f472a4c..70763bd 100644 --- a/lib/core/namespace.sh +++ b/lib/core/namespace.sh @@ -1,9 +1,8 @@ #!/usr/bin/env bash # -# This module contains all namespace functionalities for JuNest. +# This module contains functionalities for accessing to JuNest via bubblewrap. # -# http://man7.org/linux/man-pages/man7/namespaces.7.html -# http://man7.org/linux/man-pages/man2/unshare.2.html +# https://github.com/containers/bubblewrap # # Dependencies: # - lib/utils/utils.sh @@ -11,10 +10,29 @@ # # vim: ft=sh +# shellcheck disable=SC2027 +COMMON_BWRAP_OPTION="--bind "$JUNEST_HOME" / --bind "$HOME" "$HOME" --bind /tmp /tmp --bind /sys /sys --bind /proc /proc --dev-bind-try /dev /dev --bind-try "/run/user/$($ID -u)" "/run/user/$($ID -u)" --unshare-user-try" CONFIG_PROC_FILE="/proc/config.gz" CONFIG_BOOT_FILE="/boot/config-$($UNAME -r)" +PROC_USERNS_CLONE_FILE="/proc/sys/kernel/unprivileged_userns_clone" +PROC_USERNS_FILE="/proc/$$/ns/user" function _is_user_namespace_enabled() { + if [[ -L $PROC_USERNS_FILE ]] + then + return 0 + fi + + if [[ -e $PROC_USERNS_CLONE_FILE ]] + then + # `-q` option in zgrep may cause a gzip: stdout: Broken pipe + # Use redirect to /dev/null instead + if zgrep_cmd "1" "$PROC_USERNS_CLONE_FILE" > /dev/null + then + return 0 + fi + fi + local config_file="" if [[ -e $CONFIG_PROC_FILE ]] then @@ -23,78 +41,125 @@ function _is_user_namespace_enabled() { then config_file=$CONFIG_BOOT_FILE else - return $NOT_EXISTING_FILE + return "$NOT_EXISTING_FILE" fi - if ! zgrep_cmd -q "CONFIG_USER_NS=y" $config_file + # `-q` option in zgrep may cause a gzip: stdout: Broken pipe + # Use redirect to /dev/null instead + if ! zgrep_cmd "CONFIG_USER_NS=y" "$config_file" > /dev/null then - return $NO_CONFIG_FOUND + return "$NO_CONFIG_FOUND" fi + + return "$UNPRIVILEGED_USERNS_DISABLED" } function _check_user_namespace() { set +e _is_user_namespace_enabled case $? in - $NOT_EXISTING_FILE) warn "Could not understand if user namespace is enabled. No config.gz file found. Proceeding anyway..." ;; - $NO_CONFIG_FOUND) warn "User namespace is not enabled or Kernel too old (<3.8). Proceeding anyway..." ;; + "$NOT_EXISTING_FILE") warn "Could not understand if user namespace is enabled. No config.gz file found. Proceeding anyway..." ;; + "$NO_CONFIG_FOUND") warn "Unprivileged user namespace is disabled at kernel compile time or kernel too old (<3.8). Proceeding anyway..." ;; + "$UNPRIVILEGED_USERNS_DISABLED") warn "Unprivileged user namespace disabled. Root permissions are required to enable it: sudo sysctl kernel.unprivileged_userns_clone=1" ;; esac set -e } -function _run_env_with_namespace(){ - local backend_args="$1" - shift - - check_nested_env - - provide_common_bindings - local bindings=${RESULT} - unset RESULT - - # Use option -n in groot because umount do not work sometimes. - # As soon as the process terminates, the namespace - # will terminate too with its own mounted directories. - if [[ "$1" != "" ]] - then - JUNEST_ENV=1 unshare_cmd --mount --user --map-root-user $GROOT --no-umount --recursive $bindings $backend_args "$JUNEST_HOME" "${SH[@]}" "-c" "$(insert_quotes_on_spaces "${@}")" - else - JUNEST_ENV=1 unshare_cmd --mount --user --map-root-user $GROOT --no-umount --recursive $bindings $backend_args "$JUNEST_HOME" "${SH[@]}" - fi -} - ####################################### -# Run JuNest as fakeroot user via user namespace. +# Run JuNest as fakeroot via bwrap # # Globals: -# JUNEST_HOME (RO) : The JuNest home directory. -# GROOT (RO) : The groot program. -# SH (RO) : Contains the default command to run in JuNest. +# JUNEST_HOME (RO) : The JuNest home directory. +# DEFAULT_SH (RO) : Contains the default command to run in JuNest. +# BWRAP (RO): : The location of the bwrap binary. # Arguments: -# backend_args ($1) : The arguments to pass to proot -# cmd ($2-?) : The command to run inside JuNest environment. -# Default command is defined by SH variable. +# backend_args ($1) : The arguments to pass to bwrap +# no_copy_files ($2?) : If false it will copy some files in /etc +# from host to JuNest environment. +# cmd ($3-?) : The command to run inside JuNest environment. +# Default command is defined by DEFAULT_SH variable. # Returns: -# $ARCHITECTURE_MISMATCH : If host and JuNest architecture are different. -# Depends on the unshare command outcome. +# $ARCHITECTURE_MISMATCH : If host and JuNest architecture are different. +# $ROOT_ACCESS_ERROR : If the user is the real root. # Output: -# - : The command output. +# - : The command output. ####################################### -function run_env_with_namespace() { - local backend_args="$1" - shift +function run_env_as_bwrap_fakeroot(){ + check_nested_env + + local backend_command="${1:-$BWRAP}" + local backend_args="$2" + local no_copy_files="$3" + shift 3 + _check_user_namespace check_same_arch - copy_common_files - copy_file /etc/hosts.equiv - copy_file /etc/netgroup - copy_file /etc/networks - # No need for localtime as it is setup during the image build - #copy_file /etc/localtime - copy_passwd_and_group + if ! $no_copy_files + then + copy_common_files + fi - _run_env_with_namespace "$backend_args" "$@" + local args=() + [[ "$1" != "" ]] && args=("-c" "$(insert_quotes_on_spaces "${@}")") + + # Fix PATH to /usr/bin to make sudo working and avoid polluting with host related bin paths + # shellcheck disable=SC2086 + PATH="/usr/bin" BWRAP="${backend_command}" JUNEST_ENV=1 bwrap_cmd $COMMON_BWRAP_OPTION --cap-add ALL --uid 0 --gid 0 $backend_args sudo "${DEFAULT_SH[@]}" "${args[@]}" } + + +####################################### +# Run JuNest as normal user via bwrap. +# +# Globals: +# JUNEST_HOME (RO) : The JuNest home directory. +# DEFAULT_SH (RO) : Contains the default command to run in JuNest. +# BWRAP (RO): : The location of the bwrap binary. +# Arguments: +# backend_args ($1) : The arguments to pass to bwrap +# no_copy_files ($2?) : If false it will copy some files in /etc +# from host to JuNest environment. +# cmd ($3-?) : The command to run inside JuNest environment. +# Default command is defined by DEFAULT_SH variable. +# Returns: +# $ARCHITECTURE_MISMATCH : If host and JuNest architecture are different. +# Output: +# - : The command output. +####################################### +function run_env_as_bwrap_user() { + check_nested_env + + local backend_command="${1:-$BWRAP}" + local backend_args="$2" + local no_copy_files="$3" + shift 3 + + _check_user_namespace + + check_same_arch + + if ! $no_copy_files + then + copy_common_files + copy_file /etc/hosts.equiv + copy_file /etc/netgroup + copy_file /etc/networks + # No need for localtime as it is setup during the image build + #copy_file /etc/localtime + copy_passwd_and_group + fi + + local args=() + [[ "$1" != "" ]] && args=("-c" "$(insert_quotes_on_spaces "${@}")") + + # Resets PATH to avoid polluting with host related bin paths + # shellcheck disable=SC2086 + PATH='' BWRAP="${backend_command}" JUNEST_ENV=1 bwrap_cmd $COMMON_BWRAP_OPTION $backend_args "${DEFAULT_SH[@]}" "${args[@]}" +} + + + + diff --git a/lib/core/proot.sh b/lib/core/proot.sh index f678d9d..b6c1c8f 100644 --- a/lib/core/proot.sh +++ b/lib/core/proot.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# shellcheck disable=SC1091 # # This module contains all proot functionalities for JuNest. # @@ -9,36 +10,39 @@ # vim: ft=sh function _run_env_with_proot(){ - local proot_args="$1" - shift + local backend_command="${1:-$PROOT}" + local backend_args="$2" + shift 2 - check_nested_env - if [ "$1" != "" ] - then - JUNEST_ENV=1 proot_cmd "${proot_args}" "${SH[@]}" "-c" "$(insert_quotes_on_spaces "${@}")" - else - JUNEST_ENV=1 proot_cmd "${proot_args}" "${SH[@]}" - fi + local args=() + [[ "$1" != "" ]] && args=("-c" "$(insert_quotes_on_spaces "${@}")") + + # Resets PATH to avoid polluting with host related bin paths + PATH='' PROOT="${backend_command}" JUNEST_ENV=1 proot_cmd "${backend_args}" "${DEFAULT_SH[@]}" "${args[@]}" } function _run_env_with_qemu(){ - local proot_args="$1" - source ${JUNEST_HOME}/etc/junest/info + local backend_command="$1" + local backend_args="$2" + shift 2 + + source "${JUNEST_HOME}"/etc/junest/info if [ "$JUNEST_ARCH" != "$ARCH" ] then local qemu_bin="qemu-$JUNEST_ARCH-static-$ARCH" local qemu_symlink="/tmp/${qemu_bin}-$RANDOM" trap - QUIT EXIT ABRT KILL TERM INT - trap "[ -e ${qemu_symlink} ] && rm_cmd -f ${qemu_symlink}" EXIT QUIT ABRT KILL TERM INT + # shellcheck disable=SC2064 + trap "[ -e ${qemu_symlink} ] && rm_cmd -f ${qemu_symlink}" EXIT QUIT ABRT TERM INT warn "Emulating $NAME via QEMU..." - [ -e ${qemu_symlink} ] || \ - ln_cmd -s ${JUNEST_HOME}/opt/qemu/${qemu_bin} ${qemu_symlink} - proot_args="-q ${qemu_symlink} $proot_args" + [[ -e ${qemu_symlink} ]] || \ + ln_cmd -s "${JUNEST_HOME}/bin/${qemu_bin}" "${qemu_symlink}" + backend_args="-q ${qemu_symlink} $backend_args" fi - shift - _run_env_with_proot "$proot_args" "${@}" + + _run_env_with_proot "${backend_command}" "$backend_args" "${@}" } ####################################### @@ -47,23 +51,32 @@ function _run_env_with_qemu(){ # Globals: # JUNEST_HOME (RO) : The JuNest home directory. # EUID (RO) : The user ID. -# SH (RO) : Contains the default command to run in JuNest. +# DEFAULT_SH (RO) : Contains the default command to run in JuNest. # Arguments: # backend_args ($1) : The arguments to pass to proot -# cmd ($2-?) : The command to run inside JuNest environment. -# Default command is defined by SH variable. +# no_copy_files ($2?) : If false it will copy some files in /etc +# from host to JuNest environment. +# cmd ($3-?) : The command to run inside JuNest environment. +# Default command is defined by DEFAULT_SH variable. # Returns: # $ROOT_ACCESS_ERROR : If the user is the real root. # Output: # - : The command output. ####################################### -function run_env_as_fakeroot(){ +function run_env_as_proot_fakeroot(){ (( EUID == 0 )) && \ - die_on_status $ROOT_ACCESS_ERROR "You cannot access with root privileges. Use --groot option instead." - local backend_args="$1" - shift + die_on_status "$ROOT_ACCESS_ERROR" "You cannot access with root privileges. Use --groot option instead." + check_nested_env - copy_common_files + local backend_command="$1" + local backend_args="$2" + local no_copy_files="$3" + shift 3 + + if ! $no_copy_files + then + copy_common_files + fi provide_common_bindings local bindings=${RESULT} @@ -71,7 +84,7 @@ function run_env_as_fakeroot(){ # An alternative is via -S option: #_run_env_with_qemu "-S ${JUNEST_HOME} $1" "${@:2}" - _run_env_with_qemu "-0 ${bindings} -r ${JUNEST_HOME} $backend_args" "$@" + _run_env_with_qemu "$backend_command" "-0 ${bindings} -r ${JUNEST_HOME} $backend_args" "$@" } ####################################### @@ -80,37 +93,46 @@ function run_env_as_fakeroot(){ # Globals: # JUNEST_HOME (RO) : The JuNest home directory. # EUID (RO) : The user ID. -# SH (RO) : Contains the default command to run in JuNest. +# DEFAULT_SH (RO) : Contains the default command to run in JuNest. # Arguments: # backend_args ($1) : The arguments to pass to proot -# cmd ($2-?) : The command to run inside JuNest environment. -# Default command is defined by SH variable. +# no_copy_files ($2?) : If false it will copy some files in /etc +# from host to JuNest environment. +# cmd ($3-?) : The command to run inside JuNest environment. +# Default command is defined by DEFAULT_SH variable. # Returns: # $ROOT_ACCESS_ERROR : If the user is the real root. # Output: # - : The command output. ####################################### -function run_env_as_user(){ +function run_env_as_proot_user(){ (( EUID == 0 )) && \ - die_on_status $ROOT_ACCESS_ERROR "You cannot access with root privileges. Use --groot option instead." - local backend_args="$1" - shift + die_on_status "$ROOT_ACCESS_ERROR" "You cannot access with root privileges. Use --groot option instead." + check_nested_env - # Files to bind are visible in `proot --help`. - # This function excludes /etc/mtab file so that - # it will not give conflicts with the related - # symlink in the Arch Linux image. - copy_common_files - copy_file /etc/hosts.equiv - copy_file /etc/netgroup - copy_file /etc/networks - # No need for localtime as it is setup during the image build - #copy_file /etc/localtime - copy_passwd_and_group + local backend_command="$1" + local backend_args="$2" + local no_copy_files="$3" + shift 3 + + if ! $no_copy_files + then + # Files to bind are visible in `proot --help`. + # This function excludes /etc/mtab file so that + # it will not give conflicts with the related + # symlink in the Arch Linux image. + copy_common_files + copy_file /etc/hosts.equiv + copy_file /etc/netgroup + copy_file /etc/networks + # No need for localtime as it is setup during the image build + #copy_file /etc/localtime + copy_passwd_and_group + fi provide_common_bindings local bindings=${RESULT} unset RESULT - _run_env_with_qemu "${bindings} -r ${JUNEST_HOME} $backend_args" "$@" + _run_env_with_qemu "$backend_command" "${bindings} -r ${JUNEST_HOME} $backend_args" "$@" } diff --git a/lib/core/setup.sh b/lib/core/setup.sh index f55780b..58c6122 100644 --- a/lib/core/setup.sh +++ b/lib/core/setup.sh @@ -22,7 +22,7 @@ # None ####################################### function is_env_installed(){ - [ -d "$JUNEST_HOME" ] && [ "$(ls -A $JUNEST_HOME)" ] && return 0 + [[ -d "$JUNEST_HOME" ]] && [[ "$(ls -A "$JUNEST_HOME")" ]] && return 0 return 1 } @@ -30,7 +30,7 @@ function is_env_installed(){ function _cleanup_build_directory(){ local maindir=$1 check_not_null "$maindir" - builtin cd $ORIGIN_WD + builtin cd "$ORIGIN_WD" || return 1 trap - QUIT EXIT ABRT KILL TERM INT rm_cmd -fr "$maindir" } @@ -40,7 +40,8 @@ function _prepare_build_directory(){ local maindir=$1 check_not_null "$maindir" trap - QUIT EXIT ABRT KILL TERM INT - trap "rm_cmd -rf ${maindir}; die \"Error occurred when installing ${NAME}\"" EXIT QUIT ABRT KILL TERM INT + # shellcheck disable=SC2064 + trap "rm_cmd -rf ${maindir}; die \"Error occurred when installing ${NAME}\"" EXIT QUIT ABRT TERM INT } @@ -51,11 +52,18 @@ function _setup_env(){ is_env_installed && die "Error: ${NAME} has been already installed in $JUNEST_HOME" mkdir_cmd -p "${JUNEST_HOME}" - $TAR -zxpf ${imagepath} -C ${JUNEST_HOME} - info "The default mirror URL is ${DEFAULT_MIRROR}." + $TAR -zxpf "${imagepath}" -C "${JUNEST_HOME}" + info "${NAME} installed successfully!" + echo + info "Default mirror URL set to: ${DEFAULT_MIRROR}" + info "You can change the pacman mirror URL in /etc/pacman.d/mirrorlist according to your location:" + info " \$EDITOR ${JUNEST_HOME}/etc/pacman.d/mirrorlist" + echo info "Remember to refresh the package databases from the server:" info " pacman -Syy" - info "${NAME} installed successfully" + echo + info "To install packages from AUR follow the wiki here:" + info "https://github.com/fsquillace/junest#install-packages-from-aur" } @@ -70,7 +78,6 @@ function _setup_env(){ # the JuNest system from the image. # ENV_REPO (RO) : URL of the site containing JuNest images. # NAME (RO) : The JuNest name. -# DEFAULT_MIRROR (RO) : Arch Linux URL mirror. # Arguments: # arch ($1?) : The JuNest architecture image to download. # Defaults to the host architecture @@ -81,21 +88,22 @@ function _setup_env(){ ####################################### function setup_env(){ local arch=${1:-$ARCH} - contains_element $arch "${ARCH_LIST[@]}" || \ - die_on_status $NOT_AVAILABLE_ARCH "The architecture is not one of: ${ARCH_LIST[@]}" + contains_element "$arch" "${ARCH_LIST[@]}" || \ + die_on_status "$NOT_AVAILABLE_ARCH" "The architecture is not one of: ${ARCH_LIST[*]}" - local maindir=$(TMPDIR=$JUNEST_TEMPDIR mktemp -d -t ${CMD}.XXXXXXXXXX) - _prepare_build_directory $maindir + local maindir + maindir=$(TMPDIR=$JUNEST_TEMPDIR mktemp -d -t "${CMD}".XXXXXXXXXX) + _prepare_build_directory "$maindir" info "Downloading ${NAME}..." - builtin cd ${maindir} + builtin cd "${maindir}" || return 1 local imagefile=${CMD}-${arch}.tar.gz - download_cmd ${ENV_REPO}/${imagefile} + download_cmd "${ENV_REPO}/${imagefile}" info "Installing ${NAME}..." - _setup_env ${maindir}/${imagefile} + _setup_env "${maindir}/${imagefile}" - _cleanup_build_directory ${maindir} + _cleanup_build_directory "${maindir}" } ####################################### @@ -105,7 +113,6 @@ function setup_env(){ # JUNEST_HOME (RO) : The JuNest home directory in which JuNest needs # to be installed. # NAME (RO) : The JuNest name. -# DEFAULT_MIRROR (RO) : Arch Linux URL mirror. # Arguments: # imagefile ($1) : The JuNest image file. # Returns: @@ -116,10 +123,10 @@ function setup_env(){ function setup_env_from_file(){ local imagefile=$1 check_not_null "$imagefile" - [ ! -e ${imagefile} ] && die_on_status $NOT_EXISTING_FILE "Error: The ${NAME} image file ${imagefile} does not exist" + [[ ! -e ${imagefile} ]] && die_on_status "$NOT_EXISTING_FILE" "Error: The ${NAME} image file ${imagefile} does not exist" info "Installing ${NAME} from ${imagefile}..." - _setup_env ${imagefile} + _setup_env "${imagefile}" } ####################################### @@ -136,18 +143,18 @@ function setup_env_from_file(){ ####################################### function delete_env(){ ! ask "Are you sure to delete ${NAME} located in ${JUNEST_HOME}" "N" && return - if mountpoint -q ${JUNEST_HOME} + if mountpoint -q "${JUNEST_HOME}" then info "There are mounted directories inside ${JUNEST_HOME}" - if ! umount --force ${JUNEST_HOME} + if ! umount --force "${JUNEST_HOME}" then error "Cannot umount directories in ${JUNEST_HOME}" die "Try to delete ${NAME} using root permissions" fi fi # the CA directories are read only and can be deleted only by changing the mod - chmod -R +w ${JUNEST_HOME}/etc/ca-certificates - if rm_cmd -rf ${JUNEST_HOME}/* + chmod -R +w "${JUNEST_HOME}"/etc/ca-certificates + if rm_cmd -rf "${JUNEST_HOME}" then info "${NAME} deleted in ${JUNEST_HOME}" else diff --git a/lib/core/wrappers.sh b/lib/core/wrappers.sh new file mode 100644 index 0000000..1fe955c --- /dev/null +++ b/lib/core/wrappers.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# +# Dependencies: +# None +# +# vim: ft=sh + +####################################### +# Create bin wrappers +# +# Globals: +# JUNEST_HOME (RO) : The JuNest home directory. +# Arguments: +# force ($1?) : Create bin wrappers even if the bin file exists. +# Defaults to false. +# Returns: +# None +# Output: +# None +####################################### +function create_wrappers() { + local force=${1:-false} + local bin_path=${2:-/usr/bin} + bin_path=${bin_path%/} + mkdir -p "${JUNEST_HOME}${bin_path}_wrappers" + # Arguments inside a variable (i.e. `JUNEST_ARGS`) separated by quotes + # are not recognized normally unless using `eval`. More info here: + # https://github.com/fsquillace/junest/issues/262 + # https://github.com/fsquillace/junest/pull/287 + cat < "${JUNEST_HOME}/usr/bin/junest_wrapper" +#!/usr/bin/env bash + +eval "junest_args_array=(\${JUNEST_ARGS:-ns})" +junest "\${junest_args_array[@]}" -- \$(basename \${0}) "\$@" +EOF + chmod +x "${JUNEST_HOME}/usr/bin/junest_wrapper" + + cd "${JUNEST_HOME}${bin_path}" || return 1 + for file in * + do + [[ -d $file ]] && continue + # Symlinks outside junest appear as broken even though they are correct + # within a junest session. The following do not skip broken symlinks: + [[ -x $file || -L $file ]] || continue + if [[ -e ${JUNEST_HOME}${bin_path}_wrappers/$file ]] && ! $force + then + continue + fi + rm -f "${JUNEST_HOME}${bin_path}_wrappers/$file" + ln -s "${JUNEST_HOME}/usr/bin/junest_wrapper" "${JUNEST_HOME}${bin_path}_wrappers/$file" + done + + # Remove wrappers no longer needed + cd "${JUNEST_HOME}${bin_path}_wrappers" || return 1 + for file in * + do + [[ -e ${JUNEST_HOME}${bin_path}/$file || -L ${JUNEST_HOME}${bin_path}/$file ]] || rm -f "$file" + done + +} diff --git a/lib/utils/utils.sh b/lib/utils/utils.sh index 00e2cb6..5659568 100644 --- a/lib/utils/utils.sh +++ b/lib/utils/utils.sh @@ -50,7 +50,7 @@ function echoerr() { # Message printed to stderr. ####################################### function die() { - error $@ + error "$@" exit 1 } @@ -70,8 +70,8 @@ function die() { function die_on_status() { status=$1 shift - error $@ - exit $status + error "$@" + exit "$status" } ####################################### @@ -87,7 +87,7 @@ function die_on_status() { # Message printed to stderr. ####################################### function error() { - echoerr -e "\033[1;31m$@\033[0m" + echoerr -e "\033[1;31m$*\033[0m" } ####################################### @@ -104,7 +104,7 @@ function error() { ####################################### function warn() { # $@: msg (mandatory) - str: Message to print - echoerr -e "\033[1;33m$@\033[0m" + echoerr -e "\033[1;33m$*\033[0m" } ####################################### @@ -120,7 +120,7 @@ function warn() { # Message printed to stdout. ####################################### function info(){ - echo -e "\033[1;36m$@\033[0m" + echo -e "\033[1;36m$*\033[0m" } ####################################### @@ -142,12 +142,12 @@ function info(){ function ask(){ local question=$1 local default_answer=$2 - check_not_null $question + check_not_null "$question" - if [ ! -z "$default_answer" ] + if [ -n "$default_answer" ] then local answers="Y y N n" - [[ "$answers" =~ "$default_answer" ]] || { error "The default answer: $default_answer is wrong."; return $WRONG_ANSWER; } + [[ "$answers" =~ $default_answer ]] || { error "The default answer: $default_answer is wrong."; return $WRONG_ANSWER; } fi local default="Y" @@ -156,12 +156,13 @@ function ask(){ local other="n" [ "$default" == "N" ] && other="y" - local prompt=$(info "$question (${default}/${other})> ") + local prompt + prompt=$(info "$question (${default}/${other})> ") local res="none" while [ "$res" != "Y" ] && [ "$res" != "N" ] && [ "$res" != "" ]; do - read -p "$prompt" res + read -r -p "$prompt" res res=$(echo "$res" | tr '[:lower:]' '[:upper:]') done @@ -170,36 +171,31 @@ function ask(){ [ "$res" == "Y" ] } -function check_and_trap() { - local sigs="${@:2:${#@}}" - local traps="$(trap -p $sigs)" - [[ $traps ]] && die "Attempting to overwrite existing $sigs trap: $traps" - trap $@ -} - -function check_and_force_trap() { - local sigs="${@:2:${#@}}" - local traps="$(trap -p $sigs)" - [[ $traps ]] && warn "Attempting to overwrite existing $sigs trap: $traps" - trap $@ -} - function insert_quotes_on_spaces(){ # It inserts quotes between arguments. # Useful to preserve quotes on command # to be used inside sh -c/bash -c - C='' + local C="" whitespace="[[:space:]]" for i in "$@" do if [[ $i =~ $whitespace ]] then - C="$C \"$i\"" + temp_C="\"$i\"" else - C="$C $i" + temp_C="$i" fi + + # Handle edge case when C is empty to avoid adding an extra space + if [[ -z $C ]] + then + C="$temp_C" + else + C="$C $temp_C" + fi + done - echo $C + echo "$C" } contains_element () { diff --git a/tests/checkstyle/checkstyle.sh b/tests/checkstyle/checkstyle.sh index 27cb82a..4f71965 100755 --- a/tests/checkstyle/checkstyle.sh +++ b/tests/checkstyle/checkstyle.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash +# shellcheck disable=SC1091 -source "$(dirname $0)/../utils/utils.sh" +source "$(dirname "$0")/../utils/utils.sh" # Disable the exiterr set +e @@ -10,12 +11,12 @@ function oneTimeSetUp(){ } function test_check_no_tabs(){ - assertCommandFailOnStatus 1 grep -R "$(printf '\t')" $(dirname $0)/../../bin/* - assertEquals "" "$(cat $STDOUTF)" - assertEquals "" "$(cat $STDERRF)" - assertCommandFailOnStatus 1 grep -R "$(printf '\t')" $(dirname $0)/../../lib/* - assertEquals "" "$(cat $STDOUTF)" - assertEquals "" "$(cat $STDERRF)" + assertCommandFailOnStatus 1 grep -R "$(printf '\t')" "$(dirname "$0")"/../../bin/* + assertEquals "" "$(cat "$STDOUTF")" + assertEquals "" "$(cat "$STDERRF")" + assertCommandFailOnStatus 1 grep -R "$(printf '\t')" "$(dirname "$0")"/../../lib/* + assertEquals "" "$(cat "$STDOUTF")" + assertEquals "" "$(cat "$STDERRF")" } -source $(dirname $0)/../utils/shunit2 +source "$(dirname "$0")"/../utils/shunit2 diff --git a/tests/integ-tests/install-bash.sh b/tests/integ-tests/install-bash.sh deleted file mode 100755 index 575c9c5..0000000 --- a/tests/integ-tests/install-bash.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -set -ex - -VERSION=$1 - -cd /tmp -wget http://ftp.gnu.org/gnu/bash/bash-$VERSION.tar.gz - -tar -zxf bash-$VERSION.tar.gz -cd /tmp/bash-$VERSION* -./configure -make -sudo make install diff --git a/tests/unit-tests/test-chroot.sh b/tests/unit-tests/test-chroot.sh index aa491e5..3739c58 100755 --- a/tests/unit-tests/test-chroot.sh +++ b/tests/unit-tests/test-chroot.sh @@ -1,6 +1,7 @@ #!/bin/bash +# shellcheck disable=SC1091 -JUNEST_ROOT=$(readlink -f $(dirname $0)/../..) +JUNEST_ROOT=$(readlink -f "$(dirname "$0")"/../..) source "$JUNEST_ROOT/tests/utils/utils.sh" @@ -28,40 +29,97 @@ function tearDown(){ function init_mocks() { chroot_cmd() { + # shellcheck disable=SC2317 [ "$JUNEST_ENV" != "1" ] && return 1 - echo "chroot_cmd $@" + # shellcheck disable=SC2317 + echo "chroot_cmd $*" } + # shellcheck disable=SC2034 GROOT=chroot_cmd + mychroot() { + # shellcheck disable=SC2317 + echo mychroot "$*" + } } function test_run_env_as_groot_cmd(){ - assertCommandSuccess run_env_as_groot "" pwd - assertEquals "chroot_cmd -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c pwd" "$(cat $STDOUTF)" + assertCommandSuccess run_env_as_groot "" "" "false" pwd + assertEquals "chroot_cmd -b /run/user/$(id -u) -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c pwd" "$(cat "$STDOUTF")" } function test_run_env_as_groot_no_cmd(){ - assertCommandSuccess run_env_as_groot "" - assertEquals "chroot_cmd -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c /bin/sh --login" "$(cat $STDOUTF)" + assertCommandSuccess run_env_as_groot "" "" "false" "" + assertEquals "chroot_cmd -b /run/user/$(id -u) -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login" "$(cat "$STDOUTF")" +} + +function test_run_env_as_groot_with_backend_command(){ + assertCommandSuccess run_env_as_groot "mychroot" "" "false" "" + assertEquals "mychroot -b /run/user/$(id -u) -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login" "$(cat "$STDOUTF")" +} + +function test_run_env_as_groot_no_copy(){ + assertCommandSuccess run_env_as_groot "" "" "true" pwd + assertEquals "chroot_cmd -b /run/user/$(id -u) -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c pwd" "$(cat "$STDOUTF")" + + [[ ! -e ${JUNEST_HOME}/etc/hosts ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/host.conf ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/nsswitch.conf ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/resolv.conf ]] + assertEquals 0 $? +} + +function test_run_env_as_groot_nested_env(){ + JUNEST_ENV=1 + assertCommandFailOnStatus 106 run_env_as_groot "" "" "false" "" + unset JUNEST_ENV } function test_run_env_as_groot_cmd_with_backend_args(){ - assertCommandSuccess run_env_as_groot "-n -b /home/blah" pwd - assertEquals "chroot_cmd -b $HOME -b /tmp -b /proc -b /sys -b /dev -n -b /home/blah $JUNEST_HOME /bin/sh --login -c pwd" "$(cat $STDOUTF)" + assertCommandSuccess run_env_as_groot "" "-n -b /home/blah" "false" pwd + assertEquals "chroot_cmd -b /run/user/$(id -u) -b $HOME -b /tmp -b /proc -b /sys -b /dev -n -b /home/blah $JUNEST_HOME /bin/sh --login -c pwd" "$(cat "$STDOUTF")" } function test_run_env_as_chroot_cmd(){ - assertCommandSuccess run_env_as_chroot "" pwd - assertEquals "chroot_cmd $JUNEST_HOME /bin/sh --login -c pwd" "$(cat $STDOUTF)" + assertCommandSuccess run_env_as_chroot "" "" "false" pwd + assertEquals "chroot_cmd $JUNEST_HOME /bin/sh --login -c pwd" "$(cat "$STDOUTF")" } function test_run_env_as_chroot_no_cmd(){ - assertCommandSuccess run_env_as_chroot "" - assertEquals "chroot_cmd $JUNEST_HOME /bin/sh --login -c /bin/sh --login" "$(cat $STDOUTF)" + assertCommandSuccess run_env_as_chroot "" "" "false" "" + assertEquals "chroot_cmd $JUNEST_HOME /bin/sh --login" "$(cat "$STDOUTF")" +} + +function test_run_env_as_chroot_with_backend_command(){ + assertCommandSuccess run_env_as_chroot "mychroot" "" "false" "" + assertEquals "mychroot $JUNEST_HOME /bin/sh --login" "$(cat "$STDOUTF")" +} + +function test_run_env_as_chroot_no_copy(){ + assertCommandSuccess run_env_as_chroot "" "" "true" pwd + assertEquals "chroot_cmd $JUNEST_HOME /bin/sh --login -c pwd" "$(cat "$STDOUTF")" + + [[ ! -e ${JUNEST_HOME}/etc/hosts ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/host.conf ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/nsswitch.conf ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/resolv.conf ]] + assertEquals 0 $? +} + +function test_run_env_as_choot_nested_env(){ + JUNEST_ENV=1 + assertCommandFailOnStatus 106 run_env_as_chroot "" "" "false" "" + unset JUNEST_ENV } function test_run_env_as_chroot_cmd_with_backend_args(){ - assertCommandSuccess run_env_as_chroot "-n -b /home/blah" pwd - assertEquals "chroot_cmd -n -b /home/blah $JUNEST_HOME /bin/sh --login -c pwd" "$(cat $STDOUTF)" + assertCommandSuccess run_env_as_chroot "" "-n -b /home/blah" "false" pwd + assertEquals "chroot_cmd -n -b /home/blah $JUNEST_HOME /bin/sh --login -c pwd" "$(cat "$STDOUTF")" } -source $JUNEST_ROOT/tests/utils/shunit2 +source "$JUNEST_ROOT"/tests/utils/shunit2 diff --git a/tests/unit-tests/test-common.sh b/tests/unit-tests/test-common.sh index cfa3475..89c1a1e 100755 --- a/tests/unit-tests/test-common.sh +++ b/tests/unit-tests/test-common.sh @@ -1,6 +1,7 @@ #!/bin/bash +# shellcheck disable=SC1091 -JUNEST_ROOT=$(readlink -f $(dirname $0)/../..) +JUNEST_ROOT=$(readlink -f "$(dirname "$0")"/../..) source "$JUNEST_ROOT/tests/utils/utils.sh" @@ -21,47 +22,59 @@ function oneTimeTearDown(){ function setUp(){ ld_exec_mock() { - echo "ld_exec $@" + # shellcheck disable=SC2317 + echo "ld_exec $*" } + # shellcheck disable=SC2317 ld_exec_mock_false() { - echo "ld_exec $@" + echo "ld_exec $*" return 1 } + # shellcheck disable=SC2034 LD_EXEC=ld_exec_mock unshare_mock() { - echo "unshare $@" + # shellcheck disable=SC2317 + echo "unshare $*" } + # shellcheck disable=SC2034 UNSHARE=unshare_mock + # shellcheck disable=SC2317 + bwrap_mock() { + echo "bwrap $*" + } + # shellcheck disable=SC2034 + BWRAP=bwrap_mock + } function test_ln(){ - LN=echo assertCommandSuccess ln_cmd -s ln_file new_file - assertEquals "-s ln_file new_file" "$(cat $STDOUTF)" + LN="echo" assertCommandSuccess ln_cmd -s ln_file new_file + assertEquals "-s ln_file new_file" "$(cat "$STDOUTF")" LN=false assertCommandSuccess ln_cmd -s ln_file new_file - assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false -s ln_file new_file" "$(cat $STDOUTF)" + assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false -s ln_file new_file" "$(cat "$STDOUTF")" LN=false LD_EXEC=false assertCommandFail ln_cmd } function test_getent(){ - GETENT=echo assertCommandSuccess getent_cmd passwd - assertEquals "passwd" "$(cat $STDOUTF)" + GETENT="echo" assertCommandSuccess getent_cmd passwd + assertEquals "passwd" "$(cat "$STDOUTF")" - GETENT=false assertCommandSuccess getent_cmd passwd - assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false passwd" "$(cat $STDOUTF)" + GETENT="false" assertCommandSuccess getent_cmd passwd + assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false passwd" "$(cat "$STDOUTF")" GETENT=false LD_EXEC=false assertCommandFail getent_cmd } function test_cp(){ - CP=echo assertCommandSuccess cp_cmd passwd - assertEquals "passwd" "$(cat $STDOUTF)" + CP="echo" assertCommandSuccess cp_cmd passwd + assertEquals "passwd" "$(cat "$STDOUTF")" CP=false assertCommandSuccess cp_cmd passwd - assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false passwd" "$(cat $STDOUTF)" + assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false passwd" "$(cat "$STDOUTF")" CP=false LD_EXEC=false assertCommandFail cp_cmd } @@ -71,7 +84,9 @@ function test_download(){ CURL=/bin/false assertCommandSuccess download_cmd + # shellcheck disable=SC2034 WGET=/bin/false + # shellcheck disable=SC2034 CURL=/bin/true assertCommandSuccess download_cmd @@ -79,69 +94,77 @@ function test_download(){ } function test_rm(){ - RM=echo assertCommandSuccess rm_cmd rm_file - assertEquals "rm_file" "$(cat $STDOUTF)" + RM="echo" assertCommandSuccess rm_cmd rm_file + assertEquals "rm_file" "$(cat "$STDOUTF")" - RM=false assertCommandSuccess rm_cmd rm_file - assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false rm_file" "$(cat $STDOUTF)" + RM="false" assertCommandSuccess rm_cmd rm_file + assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false rm_file" "$(cat "$STDOUTF")" RM=false LD_EXEC=false assertCommandFail rm_cmd rm_file } function test_chown(){ - local id=$(id -u) + local id + id=$(id -u) - CHOWN=echo assertCommandSuccess chown_cmd $id chown_file - assertEquals "$id chown_file" "$(cat $STDOUTF)" + CHOWN="echo" assertCommandSuccess chown_cmd "$id" chown_file + assertEquals "$id chown_file" "$(cat "$STDOUTF")" - CHOWN=false assertCommandSuccess chown_cmd $id chown_file - assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false $id chown_file" "$(cat $STDOUTF)" + CHOWN="false" assertCommandSuccess chown_cmd "$id" chown_file + assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false $id chown_file" "$(cat "$STDOUTF")" - CHOWN=false LD_EXEC=false assertCommandFail chown_cmd $id chown_file + CHOWN=false LD_EXEC=false assertCommandFail chown_cmd "$id" chown_file } function test_mkdir(){ - MKDIR=echo assertCommandSuccess mkdir_cmd -p new_dir/new_dir - assertEquals "-p new_dir/new_dir" "$(cat $STDOUTF)" + MKDIR="echo" assertCommandSuccess mkdir_cmd -p new_dir/new_dir + assertEquals "-p new_dir/new_dir" "$(cat "$STDOUTF")" MKDIR=false assertCommandSuccess mkdir_cmd -p new_dir/new_dir - assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false -p new_dir/new_dir" "$(cat $STDOUTF)" + assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false -p new_dir/new_dir" "$(cat "$STDOUTF")" MKDIR=false LD_EXEC=false assertCommandFail mkdir_cmd -p new_dir/new_dir } function test_zgrep(){ - ZGREP=echo assertCommandSuccess zgrep_cmd new_file - assertEquals "new_file" "$(cat $STDOUTF)" + ZGREP="echo" assertCommandSuccess zgrep_cmd new_file + assertEquals "new_file" "$(cat "$STDOUTF")" - mkdir -p ${JUNEST_HOME}/usr/bin - touch ${JUNEST_HOME}/usr/bin/false - chmod +x ${JUNEST_HOME}/usr/bin/false + mkdir -p "${JUNEST_HOME}"/usr/bin + touch "${JUNEST_HOME}"/usr/bin/false + chmod +x "${JUNEST_HOME}"/usr/bin/false - echo -e "#!/bin/bash\necho zgrep" > ${JUNEST_HOME}/usr/bin/false + echo -e "#!/bin/bash\necho zgrep" > "${JUNEST_HOME}"/usr/bin/false ZGREP=false assertCommandSuccess zgrep_cmd new_file - assertEquals "zgrep" "$(cat $STDOUTF)" + assertEquals "zgrep" "$(cat "$STDOUTF")" - echo -e "#!/bin/bash\nexit 1" > ${JUNEST_HOME}/usr/bin/false + echo -e "#!/bin/bash\nexit 1" > "${JUNEST_HOME}"/usr/bin/false ZGREP=false assertCommandFail zgrep_cmd new_file } function test_unshare(){ assertCommandSuccess unshare_cmd new_program - assertEquals "$(echo -e "ld_exec ${JUNEST_HOME}/usr/bin/$UNSHARE --user /bin/sh -c :\nld_exec ${JUNEST_HOME}/usr/bin/$UNSHARE new_program")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "ld_exec ${JUNEST_HOME}/usr/bin/$UNSHARE --user /bin/sh -c :\nld_exec ${JUNEST_HOME}/usr/bin/$UNSHARE new_program")" "$(cat "$STDOUTF")" LD_EXEC=ld_exec_mock_false assertCommandSuccess unshare_cmd new_program - assertEquals "$(echo -e "ld_exec ${JUNEST_HOME}/usr/bin/unshare_mock --user /bin/sh -c :\nunshare --user /bin/sh -c :\nunshare new_program")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "ld_exec ${JUNEST_HOME}/usr/bin/unshare_mock --user /bin/sh -c :\nunshare --user /bin/sh -c :\nunshare new_program")" "$(cat "$STDOUTF")" UNSHARE=false LD_EXEC=false assertCommandFail unshare_cmd new_program } +function test_bwrap(){ + assertCommandSuccess bwrap_cmd new_program + assertEquals "$(echo -e "ld_exec $BWRAP --dev-bind / / /bin/sh -c :\nld_exec $BWRAP new_program")" "$(cat "$STDOUTF")" + + BWRAP=false LD_EXEC=false assertCommandFail bwrap_cmd new_program +} + function test_chroot(){ - CLASSIC_CHROOT=echo assertCommandSuccess chroot_cmd root - assertEquals "root" "$(cat $STDOUTF)" + CLASSIC_CHROOT="echo" assertCommandSuccess chroot_cmd root + assertEquals "root" "$(cat "$STDOUTF")" CLASSIC_CHROOT=false assertCommandSuccess chroot_cmd root - assertEquals "ld_exec $JUNEST_HOME/usr/bin/false root" "$(cat $STDOUTF)" + assertEquals "ld_exec $JUNEST_HOME/usr/bin/false root" "$(cat "$STDOUTF")" CLASSIC_CHROOT=false LD_EXEC=false assertCommandFail chroot_cmd root } @@ -154,39 +177,43 @@ function test_proot_cmd_compat(){ function test_proot_cmd_seccomp(){ envv(){ + # shellcheck disable=SC2317 env } PROOT=envv assertCommandSuccess proot_cmd cmd - assertEquals "" "$(cat $STDOUTF | grep "^PROOT_NO_SECCOMP")" + assertEquals "" "$(grep "^PROOT_NO_SECCOMP" "$STDOUTF")" envv(){ + # shellcheck disable=SC2317 env | grep "^PROOT_NO_SECCOMP" } + # shellcheck disable=SC2034 PROOT=envv - local output=$(proot_cmd | grep "^PROOT_NO_SECCOMP") assertCommandSuccess proot_cmd cmd # The variable PROOT_NO_SECCOMP will be produced # twice due to the fallback mechanism assertEquals "PROOT_NO_SECCOMP=1 -PROOT_NO_SECCOMP=1" "$(cat $STDOUTF | grep "^PROOT_NO_SECCOMP")" +PROOT_NO_SECCOMP=1" "$(grep "^PROOT_NO_SECCOMP" "$STDOUTF")" } function test_copy_passwd_and_group(){ getent_cmd_mock() { - echo $@ + # shellcheck disable=SC2317 + echo "$*" } GETENT=getent_cmd_mock assertCommandSuccess copy_passwd_and_group - assertEquals "$(echo -e "passwd\npasswd $USER")" "$(cat $JUNEST_HOME/etc/passwd)" - assertEquals "group" "$(cat $JUNEST_HOME/etc/group)" + assertEquals "$(echo -e "passwd\npasswd $USER")" "$(cat "$JUNEST_HOME"/etc/passwd)" + assertEquals "group" "$(cat "$JUNEST_HOME"/etc/group)" } function test_copy_passwd_and_group_fallback(){ cp_cmd_mock() { - echo $@ + # shellcheck disable=SC2317 + echo "$*" } CP=cp_cmd_mock GETENT=false LD_EXEC=false assertCommandSuccess copy_passwd_and_group - assertEquals "$(echo -e "/etc/passwd $JUNEST_HOME//etc/passwd\n/etc/group $JUNEST_HOME//etc/group")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "-f /etc/passwd $JUNEST_HOME//etc/passwd\n-f /etc/group $JUNEST_HOME//etc/group")" "$(cat "$STDOUTF")" } function test_copy_passwd_and_group_failure(){ @@ -202,14 +229,14 @@ function test_nested_env_not_set_variable(){ } function test_check_same_arch_not_same(){ - echo "JUNEST_ARCH=XXX" > ${JUNEST_HOME}/etc/junest/info + echo "JUNEST_ARCH=XXX" > "${JUNEST_HOME}"/etc/junest/info assertCommandFailOnStatus 104 check_same_arch } function test_check_same_arch(){ - echo "JUNEST_ARCH=$ARCH" > ${JUNEST_HOME}/etc/junest/info + echo "JUNEST_ARCH=$ARCH" > "${JUNEST_HOME}"/etc/junest/info assertCommandSuccess check_same_arch } -source $JUNEST_ROOT/tests/utils/shunit2 +source "$JUNEST_ROOT"/tests/utils/shunit2 diff --git a/tests/unit-tests/test-groot.sh b/tests/unit-tests/test-groot.sh deleted file mode 100755 index 784452d..0000000 --- a/tests/unit-tests/test-groot.sh +++ /dev/null @@ -1,230 +0,0 @@ -#!/bin/bash -source "$(dirname $0)/../utils/utils.sh" - -JUNEST_BASE="$(readlink -f $(dirname $(readlink -f "$0"))/../..)" - -# Disable the exiterr -set +e - -function oneTimeSetUp(){ - setUpUnitTests -} - -function setUp(){ - # Attempt to source the files under test to revert variable overrides - source $JUNEST_BASE/bin/groot -h &> /dev/null - set +e - - cwdSetUp - mkdir -p chrootdir - - init_mocks -} - -function tearDown(){ - cwdTearDown -} - -## Mock functions ## -function init_mocks() { - function usage(){ - echo "usage" - } - function is_user_root() { - return 0 - } - function chroot() { - echo "chroot($@)" - } - function mountpoint() { - echo "mountpoint($@)" - # As default suppose the mountpoint does not exist - return 1 - } - function mount() { - echo "mount($@)" - } - function umount() { - echo "umount($@)" - } - function check_and_trap() { - echo "check_and_trap($@)" - } -} - -function test_help(){ - assertCommandSuccess main -h - assertEquals "usage" "$(cat $STDOUTF)" - assertCommandSuccess main --help - assertEquals "usage" "$(cat $STDOUTF)" -} -function test_version(){ - assertCommandSuccess main -V - assertEquals "$NAME $(cat $JUNEST_BASE/VERSION)" "$(cat $STDOUTF)" - assertCommandSuccess main --version - assertEquals "$NAME $(cat $JUNEST_BASE/VERSION)" "$(cat $STDOUTF)" -} -function test_groot_no_root(){ - is_user_root() { - return 1 - } - assertCommandFailOnStatus $NO_ROOT_PRIVILEGES main -} -function test_groot_no_directory(){ - assertCommandFailOnStatus $NOT_EXISTING_FILE main no-directory -} -function test_groot_mountpoint_exist(){ - mountpoint_mock() { - echo "mountpoint($@)" - } - MOUNTPOINT=mountpoint_mock - assertCommandSuccess main chrootdir - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} -function test_groot_mountpoint_does_not_exist(){ - assertCommandSuccess main chrootdir - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} -function test_groot_with_bind(){ - assertCommandSuccess main -b /tmp chrootdir - [[ -d chrootdir/tmp ]] - assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--bind /tmp chrootdir/tmp)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} -function test_groot_with_bind_file(){ - touch file_src - assertCommandSuccess main -b ${PWD}/file_src:/file_src chrootdir - [[ -f chrootdir/file_src ]] - assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--bind ${PWD}/file_src chrootdir/file_src)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} -function test_groot_with_bind_not_existing_node(){ - assertCommandFailOnStatus $NOT_EXISTING_FILE main -b ${PWD}/file_src:/file_src chrootdir - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)")" "$(cat $STDOUTF)" -} -function test_groot_with_bind_not_absolute_path_node(){ - touch file_src - assertCommandFailOnStatus $NOT_ABSOLUTE_PATH main -b file_src:/file_src chrootdir - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)")" "$(cat $STDOUTF)" -} -function test_groot_with_bind_guest_host(){ - assertCommandSuccess main -b /tmp:/home/tmp chrootdir - [[ -d chrootdir/home/tmp ]] - assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--bind /tmp chrootdir/home/tmp)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} -function test_groot_with_multiple_bind(){ - assertCommandSuccess main -b /tmp:/home/tmp -b /dev chrootdir - [[ -d chrootdir/home/tmp ]] - assertEquals 0 $? - [[ -d chrootdir/dev ]] - assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--bind /tmp chrootdir/home/tmp)\nmount(--bind /dev chrootdir/dev)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} -function test_groot_with_command(){ - assertCommandSuccess main chrootdir ls -la -h - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nchroot(chrootdir ls -la -h)")" "$(cat $STDOUTF)" -} -function test_groot_with_bind_and_command(){ - assertCommandSuccess main -b /tmp:/home/tmp -b /dev chrootdir ls -la -h - [[ -d chrootdir/home/tmp ]] - assertEquals 0 $? - [[ -d chrootdir/dev ]] - assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--bind /tmp chrootdir/home/tmp)\nmount(--bind /dev chrootdir/dev)\nchroot(chrootdir ls -la -h)")" "$(cat $STDOUTF)" -} -function test_groot_with_bind_no_umount(){ - assertCommandSuccess main -n chrootdir - assertEquals "$(echo -e "mountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} -function test_groot_with_chroot_teardown(){ - echo -e "1 /home/mychroot/dev\n1 /home/mychroot/proc/fs1\n1 /home/mychroot\n1 /home/mychroot-no/dev\n1 /home/mychroot/dev/shm\n1 /home/mychroot/proc\n" > ./mounts - MOUNTS_FILE=./mounts - CHROOTDIR=/home/mychroot assertCommandSuccess chroot_teardown - assertEquals "$(echo -e "umount(/home/mychroot/proc/fs1) -umount(/home/mychroot/proc) -umount(/home/mychroot/dev/shm) -umount(/home/mychroot/dev) -umount(/home/mychroot)")" "$(cat $STDOUTF)" -} - -function test_groot_with_chroot_teardown_umount_failure(){ - function umount() { - echo "umount($@)" - [[ "$1" == "/home/mychroot/dev/shm" ]] && return 128 - return 0 - } - UMOUNT=umount - echo -e "1 /home/mychroot/dev\n1 /home/mychroot/proc/fs1\n1 /home/mychroot\n1 /home/mychroot-no/dev\n1 /home/mychroot/dev/shm\n1 /home/mychroot/proc\n" > ./mounts - MOUNTS_FILE=./mounts - CHROOTDIR=/home/mychroot assertCommandFailOnStatus 128 chroot_teardown - assertEquals "$(echo -e "umount(/home/mychroot/proc/fs1) -umount(/home/mychroot/proc) -umount(/home/mychroot/dev/shm) -umount(/home/mychroot/dev) -umount(/home/mychroot)")" "$(cat $STDOUTF)" -} -function test_groot_with_chroot_teardown_with_trailing_slash(){ - echo -e "1 /home/mychroot/dev\n1 /home/mychroot/proc/fs1\n1 /home/mychroot\n1 /home/mychroot-no/dev\n1 /home/mychroot/dev/shm\n1 /home/mychroot/proc\n" > ./mounts - MOUNTS_FILE=./mounts - CHROOTDIR=/home/mychroot assertCommandSuccess chroot_teardown - assertEquals "$(echo -e "umount(/home/mychroot/proc/fs1) -umount(/home/mychroot/proc) -umount(/home/mychroot/dev/shm) -umount(/home/mychroot/dev) -umount(/home/mychroot)")" "$(cat $STDOUTF)" -} - -function test_groot_with_rbind(){ - assertCommandSuccess main -r -b /tmp chrootdir - [[ -d chrootdir/tmp ]] - assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/tmp)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} - -function test_groot_with_avoid_bind_proc(){ - assertCommandSuccess main -i -b /proc chrootdir - [[ -d chrootdir/proc ]] - assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(proc chrootdir/proc -t proc)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} - -function test_groot_with_avoid_bind_dev(){ - assertCommandSuccess main -i -b /dev chrootdir - [[ -d chrootdir/dev ]] - assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(udev chrootdir/dev -t devtmpfs)\nmount(devpts /dev/pts -t devpts)\nmount(shm /dev/shm -t tmpfs)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} - -function test_groot_with_avoid_bind_sys(){ - assertCommandSuccess main -i -b /sys chrootdir - [[ -d chrootdir/sys ]] - assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(sys chrootdir/sys -t sysfs)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} - -function test_groot_with_avoid_bind_run(){ - assertCommandSuccess main -i -b /run chrootdir - [[ -d chrootdir/run ]] - assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(run chrootdir/run -t tmpfs)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} - -function test_groot_with_avoid_bind_tmp(){ - assertCommandSuccess main -i -b /tmp chrootdir - [[ -d chrootdir/tmp ]] - assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(tmp chrootdir/tmp -t tmpfs)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} - -function test_groot_with_avoid_bind_combined(){ - assertCommandSuccess main -i -b /tmp -b /usr chrootdir - cat $STDERRF - [[ -d chrootdir/tmp ]] - assertEquals 0 $? - [[ -d chrootdir/usr ]] - assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(tmp chrootdir/tmp -t tmpfs)\nmount(--bind /usr chrootdir/usr)\nchroot(chrootdir)")" "$(cat $STDOUTF)" -} - -source $(dirname $0)/../utils/shunit2 diff --git a/tests/unit-tests/test-junest.sh b/tests/unit-tests/test-junest.sh index 2176cea..07a92b1 100755 --- a/tests/unit-tests/test-junest.sh +++ b/tests/unit-tests/test-junest.sh @@ -1,8 +1,10 @@ #!/bin/bash -source "$(dirname $0)/../utils/utils.sh" +# shellcheck disable=SC1091 -JUNEST_BASE="$(dirname $0)/../.." -source $JUNEST_BASE/bin/junest -h &> /dev/null +source "$(dirname "$0")/../utils/utils.sh" + +JUNEST_BASE="$(dirname "$0")/../.." +source "$JUNEST_BASE"/bin/junest -h &> /dev/null # Disable the exiterr set +e @@ -12,178 +14,370 @@ function oneTimeSetUp(){ } function setUp(){ + ## Mock functions ## + # shellcheck disable=SC2317 + function usage(){ + echo "usage" + } + # shellcheck disable=SC2317 + function version(){ + echo "version" + } + # shellcheck disable=SC2317 + function build_image_env(){ + local disable_check=$1 + echo "build_image_env($disable_check)" + } + # shellcheck disable=SC2317 + function delete_env(){ + echo "delete_env" + } + # shellcheck disable=SC2317 + function setup_env_from_file(){ + echo "setup_env_from_file($1)" + } + # shellcheck disable=SC2317 + function setup_env(){ + echo "setup_env($1)" + } + # shellcheck disable=SC2317 + function run_env_as_proot_fakeroot(){ + local backend_command="$1" + local backend_args="$2" + local no_copy_files="$3" + shift 3 + echo "run_env_as_proot_fakeroot($backend_command,$backend_args,$no_copy_files,$*)" + } + # shellcheck disable=SC2317 + function run_env_as_groot(){ + local backend_command="$1" + local backend_args="$2" + local no_copy_files="$3" + shift 3 + echo "run_env_as_groot($backend_command,$backend_args,$no_copy_files,$*)" + } + # shellcheck disable=SC2317 + function run_env_as_chroot(){ + local backend_command="$1" + local backend_args="$2" + local no_copy_files="$3" + shift 3 + echo "run_env_as_chroot($backend_command,$backend_args,$no_copy_files,$*)" + } + # shellcheck disable=SC2317 + function run_env_as_proot_user(){ + local backend_command="$1" + local backend_args="$2" + local no_copy_files="$3" + shift 3 + echo "run_env_as_proot_user($backend_command,$backend_args,$no_copy_files,$*)" + } + # shellcheck disable=SC2317 + function run_env_as_bwrap_fakeroot(){ + local backend_command="$1" + local backend_args="$2" + local no_copy_files="$3" + shift 3 + echo "run_env_as_bwrap_fakeroot($backend_command,$backend_args,$no_copy_files,$*)" + } + # shellcheck disable=SC2317 + function run_env_as_bwrap_user(){ + local backend_command="$1" + local backend_args="$2" + local no_copy_files="$3" + shift 3 + echo "run_env_as_bwrap_user($backend_command,$backend_args,$no_copy_files,$*)" + } + # shellcheck disable=SC2317 function is_env_installed(){ return 0 } -} - -## Mock functions ## -function usage(){ - echo "usage" -} -function version(){ - echo "version" -} -function build_image_env(){ - local disable_validation=$1 - echo "build_image_env($disable_validation)" -} -function delete_env(){ - echo "delete_env" -} -function setup_env_from_file(){ - echo "setup_env_from_file($1)" -} -function setup_env(){ - echo "setup_env($1)" -} -function run_env_as_fakeroot(){ - local backend_args="$1" - shift - echo "run_env_as_fakeroot($backend_args,$@)" -} -function run_env_as_groot(){ - echo "run_env_as_groot $@" -} -function run_env_as_chroot(){ - echo "run_env_as_chroot $@" -} -function run_env_as_user(){ - local backend_args="$1" - shift - echo "run_env_as_user($backend_args,$@)" -} -function run_env_with_namespace(){ - local backend_args="$1" - shift - echo "run_env_with_namespace($backend_args,$@)" + # shellcheck disable=SC2317 + function create_wrappers(){ + : + } } function test_help(){ assertCommandSuccess main -h - assertEquals "usage" "$(cat $STDOUTF)" + assertEquals "usage" "$(cat "$STDOUTF")" assertCommandSuccess main --help - assertEquals "usage" "$(cat $STDOUTF)" + assertEquals "usage" "$(cat "$STDOUTF")" } function test_version(){ assertCommandSuccess main -V - assertEquals "version" "$(cat $STDOUTF)" + assertEquals "version" "$(cat "$STDOUTF")" assertCommandSuccess main --version - assertEquals "version" "$(cat $STDOUTF)" + assertEquals "version" "$(cat "$STDOUTF")" } function test_build_image_env(){ - assertCommandSuccess main -b - assertEquals "build_image_env(false)" "$(cat $STDOUTF)" - assertCommandSuccess main --build-image - assertEquals "build_image_env(false)" "$(cat $STDOUTF)" - assertCommandSuccess main -b -n - assertEquals "build_image_env(true)" "$(cat $STDOUTF)" - assertCommandSuccess main --build-image --disable-validation - assertEquals "build_image_env(true)" "$(cat $STDOUTF)" + assertCommandSuccess main b + assertEquals "build_image_env(false)" "$(cat "$STDOUTF")" + assertCommandSuccess main build + assertEquals "build_image_env(false)" "$(cat "$STDOUTF")" + assertCommandSuccess main b -n + assertEquals "build_image_env(true)" "$(cat "$STDOUTF")" + assertCommandSuccess main build --disable-check + assertEquals "build_image_env(true)" "$(cat "$STDOUTF")" } + +function test_create_wrappers(){ + # shellcheck disable=SC2317 + function create_wrappers(){ + local force=$1 + echo "create_wrappers($force)" + } + assertCommandSuccess main create-bin-wrappers + assertEquals "create_wrappers(false)" "$(cat "$STDOUTF")" + + assertCommandSuccess main create-bin-wrappers --force + assertEquals "create_wrappers(true)" "$(cat "$STDOUTF")" +} + function test_delete_env(){ - assertCommandSuccess main -d - assertEquals "delete_env" "$(cat $STDOUTF)" - assertCommandSuccess main --delete - assertEquals "delete_env" "$(cat $STDOUTF)" + assertCommandSuccess main s -d + assertEquals "delete_env" "$(cat "$STDOUTF")" + assertCommandSuccess main setup --delete + assertEquals "delete_env" "$(cat "$STDOUTF")" } function test_setup_env_from_file(){ + # shellcheck disable=SC2317 is_env_installed(){ return 1 } - assertCommandSuccess main -i myimage - assertEquals "$(echo -e "setup_env_from_file(myimage)\nrun_env_as_user(,)")" "$(cat $STDOUTF)" - assertCommandSuccess main --setup-from-file myimage - assertEquals "$(echo -e "setup_env_from_file(myimage)\nrun_env_as_user(,)")" "$(cat $STDOUTF)" + assertCommandSuccess main s -i myimage + assertEquals "setup_env_from_file(myimage)" "$(cat "$STDOUTF")" + assertCommandSuccess main setup --from-file myimage + assertEquals "setup_env_from_file(myimage)" "$(cat "$STDOUTF")" + # shellcheck disable=SC2317 is_env_installed(){ return 0 } - assertCommandFail main -i myimage + assertCommandFail main setup -i myimage } function test_setup_env(){ + # shellcheck disable=SC2317 is_env_installed(){ return 1 } - assertCommandSuccess main -a arm - assertEquals "$(echo -e "setup_env(arm)\nrun_env_as_user(,)")" "$(cat $STDOUTF)" - assertCommandSuccess main --arch arm - assertEquals "$(echo -e "setup_env(arm)\nrun_env_as_user(,)")" "$(cat $STDOUTF)" - assertCommandSuccess main - assertEquals "$(echo -e "setup_env()\nrun_env_as_user(,)")" "$(cat $STDOUTF)" + assertCommandSuccess main s + assertEquals "setup_env()" "$(cat "$STDOUTF")" + assertCommandSuccess main setup + assertEquals "setup_env()" "$(cat "$STDOUTF")" + assertCommandSuccess main s -a arm + assertEquals "setup_env(arm)" "$(cat "$STDOUTF")" + assertCommandSuccess main setup --arch arm + assertEquals "setup_env(arm)" "$(cat "$STDOUTF")" + # shellcheck disable=SC2317 is_env_installed(){ return 0 } - assertCommandFail main -a arm + assertCommandFail main setup -a arm } -function test_run_env_as_fakeroot(){ - assertCommandSuccess main -f - assertEquals "run_env_as_fakeroot(,)" "$(cat $STDOUTF)" - assertCommandSuccess main --fakeroot - assertEquals "run_env_as_fakeroot(,)" "$(cat $STDOUTF)" - assertCommandSuccess main -f -p "-b arg" - assertEquals "run_env_as_fakeroot(-b arg,)" "$(cat $STDOUTF)" - assertCommandSuccess main -f -p "-b arg" -- command -kv - assertEquals "run_env_as_fakeroot(-b arg,command -kv)" "$(cat $STDOUTF)" - assertCommandSuccess main -f command --as - assertEquals "run_env_as_fakeroot(,command --as)" "$(cat $STDOUTF)" - assertCommandFail main -a "myarch" -f command --as +function test_run_env_as_proot_fakeroot(){ + assertCommandSuccess main p -f + assertEquals "run_env_as_proot_fakeroot(,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main proot --fakeroot + assertEquals "run_env_as_proot_fakeroot(,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main p -f -n + assertEquals "run_env_as_proot_fakeroot(,,true,)" "$(cat "$STDOUTF")" + + assertCommandSuccess main p -f --backend-command blah + assertEquals "run_env_as_proot_fakeroot(blah,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main proot -f --backend-command blah + assertEquals "run_env_as_proot_fakeroot(blah,,false,)" "$(cat "$STDOUTF")" + + assertCommandSuccess main proot -f -b "-b arg" + assertEquals "run_env_as_proot_fakeroot(,-b arg,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main proot -f -b "-b arg" -- command -kv + assertEquals "run_env_as_proot_fakeroot(,-b arg,false,command -kv)" "$(cat "$STDOUTF")" + assertCommandSuccess main proot -f command --as + assertEquals "run_env_as_proot_fakeroot(,,false,command --as)" "$(cat "$STDOUTF")" + assertCommandSuccess main proot -f -- command --as + assertEquals "run_env_as_proot_fakeroot(,,false,command --as)" "$(cat "$STDOUTF")" + + # shellcheck disable=SC2317 + is_env_installed(){ + return 1 + } + assertCommandFail main proot -f } + function test_run_env_as_user(){ - assertCommandSuccess main - assertEquals "run_env_as_user(,)" "$(cat $STDOUTF)" + assertCommandSuccess main proot + assertEquals "run_env_as_proot_user(,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main p -n + assertEquals "run_env_as_proot_user(,,true,)" "$(cat "$STDOUTF")" - assertCommandSuccess main -p "-b arg" - assertEquals "run_env_as_user(-b arg,)" "$(cat $STDOUTF)" - assertCommandSuccess main -p "-b arg" -- command -ll - assertEquals "run_env_as_user(-b arg,command -ll)" "$(cat $STDOUTF)" - assertCommandSuccess main command -ls - assertEquals "run_env_as_user(,command -ls)" "$(cat $STDOUTF)" + assertCommandSuccess main p --backend-command blah + assertEquals "run_env_as_proot_user(blah,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main proot --backend-command blah + assertEquals "run_env_as_proot_user(blah,,false,)" "$(cat "$STDOUTF")" - assertCommandFail main -a "myarch" -- command -ls + assertCommandSuccess main proot -b "-b arg" + assertEquals "run_env_as_proot_user(,-b arg,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main proot -b "-b arg" -- command -ll + assertEquals "run_env_as_proot_user(,-b arg,false,command -ll)" "$(cat "$STDOUTF")" + assertCommandSuccess main proot command -ls + assertEquals "run_env_as_proot_user(,,false,command -ls)" "$(cat "$STDOUTF")" + assertCommandSuccess main proot -- command -ls + assertEquals "run_env_as_proot_user(,,false,command -ls)" "$(cat "$STDOUTF")" + + # shellcheck disable=SC2317 + is_env_installed(){ + return 1 + } + assertCommandFail main proot } + function test_run_env_as_groot(){ - assertCommandSuccess main -g - assertEquals "run_env_as_groot " "$(cat $STDOUTF)" - assertCommandSuccess main -g command - assertEquals "run_env_as_groot command" "$(cat $STDOUTF)" + assertCommandSuccess main g + assertEquals "run_env_as_groot(,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main g -n + assertEquals "run_env_as_groot(,,true,)" "$(cat "$STDOUTF")" + assertCommandSuccess main g -b "-b arg" + assertEquals "run_env_as_groot(,-b arg,false,)" "$(cat "$STDOUTF")" + + assertCommandSuccess main g --backend-command blah + assertEquals "run_env_as_groot(blah,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main groot --backend-command blah + assertEquals "run_env_as_groot(blah,,false,)" "$(cat "$STDOUTF")" + + assertCommandSuccess main groot command + assertEquals "run_env_as_groot(,,false,command)" "$(cat "$STDOUTF")" + assertCommandSuccess main groot -- command + assertEquals "run_env_as_groot(,,false,command)" "$(cat "$STDOUTF")" + + # shellcheck disable=SC2317 + is_env_installed(){ + return 1 + } + assertCommandFail main groot } + function test_run_env_as_chroot(){ - assertCommandSuccess main -r - assertEquals "run_env_as_chroot " "$(cat $STDOUTF)" - assertCommandSuccess main -r command - assertEquals "run_env_as_chroot command" "$(cat $STDOUTF)" + assertCommandSuccess main r + assertEquals "run_env_as_chroot(,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main r -b "-b arg" + assertEquals "run_env_as_chroot(,-b arg,false,)" "$(cat "$STDOUTF")" + + assertCommandSuccess main r --backend-command blah + assertEquals "run_env_as_chroot(blah,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main root --backend-command blah + assertEquals "run_env_as_chroot(blah,,false,)" "$(cat "$STDOUTF")" + + assertCommandSuccess main root command + assertEquals "run_env_as_chroot(,,false,command)" "$(cat "$STDOUTF")" + assertCommandSuccess main root -- command + assertEquals "run_env_as_chroot(,,false,command)" "$(cat "$STDOUTF")" + + # shellcheck disable=SC2317 + is_env_installed(){ + return 1 + } + assertCommandFail main root -f } -function test_run_env_with_namespace(){ - assertCommandSuccess main -u -f - assertEquals "run_env_with_namespace(,)" "$(cat $STDOUTF)" - assertCommandSuccess main --namespace --fakeroot - assertEquals "run_env_with_namespace(,)" "$(cat $STDOUTF)" +function test_run_env_as_bwrap_fakeroot(){ + assertCommandSuccess main n -f + assertEquals "run_env_as_bwrap_fakeroot(,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main ns -f + assertEquals "run_env_as_bwrap_fakeroot(,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main ns -n -f + assertEquals "run_env_as_bwrap_fakeroot(,,true,)" "$(cat "$STDOUTF")" - assertCommandSuccess main -u -f -p "-b arg" - assertEquals "run_env_with_namespace(-b arg,)" "$(cat $STDOUTF)" - assertCommandSuccess main -u -f -p "-b arg" -- command -kv - assertEquals "run_env_with_namespace(-b arg,command -kv)" "$(cat $STDOUTF)" - assertCommandSuccess main -u -f command --as - assertEquals "run_env_with_namespace(,command --as)" "$(cat $STDOUTF)" + assertCommandSuccess main ns -f -b "-b arg" + assertEquals "run_env_as_bwrap_fakeroot(,-b arg,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main ns -f -b "-b arg" -- command -kv + assertEquals "run_env_as_bwrap_fakeroot(,-b arg,false,command -kv)" "$(cat "$STDOUTF")" + assertCommandSuccess main ns -f command --as + assertEquals "run_env_as_bwrap_fakeroot(,,false,command --as)" "$(cat "$STDOUTF")" + assertCommandSuccess main ns -f -- command --as + assertEquals "run_env_as_bwrap_fakeroot(,,false,command --as)" "$(cat "$STDOUTF")" + + assertCommandSuccess main ns -f --backend-command blah + assertEquals "run_env_as_bwrap_fakeroot(blah,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main -f --backend-command blah + assertEquals "run_env_as_bwrap_fakeroot(blah,,false,)" "$(cat "$STDOUTF")" + + assertCommandSuccess main -f + assertEquals "run_env_as_bwrap_fakeroot(,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main -f + assertEquals "run_env_as_bwrap_fakeroot(,,false,)" "$(cat "$STDOUTF")" + + assertCommandSuccess main -f -b "-b arg" + assertEquals "run_env_as_bwrap_fakeroot(,-b arg,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main -f -b "-b arg" -- command -kv + assertEquals "run_env_as_bwrap_fakeroot(,-b arg,false,command -kv)" "$(cat "$STDOUTF")" + assertCommandSuccess main -f command --as + assertEquals "run_env_as_bwrap_fakeroot(,,false,command --as)" "$(cat "$STDOUTF")" + assertCommandSuccess main -f -- command --as + assertEquals "run_env_as_bwrap_fakeroot(,,false,command --as)" "$(cat "$STDOUTF")" + + # shellcheck disable=SC2317 + is_env_installed(){ + return 1 + } + assertCommandFail main ns -f } -function test_check_cli(){ - assertCommandFail main -b -h - assertCommandFail main -b -c - assertCommandFail main -d -s - assertCommandFail main -n -v - assertCommandFail main -d -r - assertCommandFail main -h -f - assertCommandFail main -v -i fsd - assertCommandFail main -f -r - assertCommandFail main -p args -v - assertCommandFail main -a arch -v - assertCommandFail main -d args +function test_run_env_as_bwrap_user(){ + assertCommandSuccess main n + assertEquals "run_env_as_bwrap_user(,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main ns + assertEquals "run_env_as_bwrap_user(,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main ns -n + assertEquals "run_env_as_bwrap_user(,,true,)" "$(cat "$STDOUTF")" + + assertCommandSuccess main ns -b "-b arg" + assertEquals "run_env_as_bwrap_user(,-b arg,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main ns -b "-b arg" -- command -kv + assertEquals "run_env_as_bwrap_user(,-b arg,false,command -kv)" "$(cat "$STDOUTF")" + assertCommandSuccess main ns command --as + assertEquals "run_env_as_bwrap_user(,,false,command --as)" "$(cat "$STDOUTF")" + assertCommandSuccess main ns -- command --as + assertEquals "run_env_as_bwrap_user(,,false,command --as)" "$(cat "$STDOUTF")" + + assertCommandSuccess main ns --backend-command blah + assertEquals "run_env_as_bwrap_user(blah,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main --backend-command blah + assertEquals "run_env_as_bwrap_user(blah,,false,)" "$(cat "$STDOUTF")" + + assertCommandSuccess main + assertEquals "run_env_as_bwrap_user(,,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main + assertEquals "run_env_as_bwrap_user(,,false,)" "$(cat "$STDOUTF")" + + assertCommandSuccess main -b "-b arg" + assertEquals "run_env_as_bwrap_user(,-b arg,false,)" "$(cat "$STDOUTF")" + assertCommandSuccess main -b "-b arg" -- command -kv + assertEquals "run_env_as_bwrap_user(,-b arg,false,command -kv)" "$(cat "$STDOUTF")" + assertCommandSuccess main command --as + assertEquals "run_env_as_bwrap_user(,,false,command --as)" "$(cat "$STDOUTF")" + assertCommandSuccess main -- command --as + assertEquals "run_env_as_bwrap_user(,,false,command --as)" "$(cat "$STDOUTF")" + + # shellcheck disable=SC2317 + is_env_installed(){ + return 1 + } + assertCommandFail main ns } -source $(dirname $0)/../utils/shunit2 +function test_invalid_option(){ + assertCommandFail main --no-option + assertCommandFail main n --no-option + assertCommandFail main g --no-option + assertCommandFail main r --no-option + + assertCommandFail main p --no-option + + assertCommandFail main b --no-option + assertCommandFail main s --no-option +} + +source "$(dirname "$0")"/../utils/shunit2 diff --git a/tests/unit-tests/test-namespace.sh b/tests/unit-tests/test-namespace.sh index 811cfef..7a845aa 100755 --- a/tests/unit-tests/test-namespace.sh +++ b/tests/unit-tests/test-namespace.sh @@ -1,6 +1,7 @@ #!/bin/bash +# shellcheck disable=SC1091 -JUNEST_ROOT=$(readlink -f $(dirname $0)/../..) +JUNEST_ROOT=$(readlink -f "$(dirname "$0")"/../..) source "$JUNEST_ROOT/tests/utils/utils.sh" @@ -15,8 +16,9 @@ function oneTimeSetUp(){ ## Mock functions ## function init_mocks() { - function unshare_cmd(){ - echo "unshare $@" + # shellcheck disable=SC2317 + function bwrap_cmd(){ + echo "$BWRAP $*" } } @@ -39,16 +41,16 @@ function tearDown(){ } function _test_copy_common_files() { - [[ -e /etc/hosts ]] && assertEquals "$(cat /etc/hosts)" "$(cat ${JUNEST_HOME}/etc/hosts)" - [[ -e /etc/host.conf ]] && assertEquals "$(cat /etc/host.conf)" "$(cat ${JUNEST_HOME}/etc/host.conf)" - [[ -e /etc/nsswitch.conf ]] && assertEquals "$(cat /etc/nsswitch.conf)" "$(cat ${JUNEST_HOME}/etc/nsswitch.conf)" - [[ -e /etc/resolv.conf ]] && assertEquals "$(cat /etc/resolv.conf)" "$(cat ${JUNEST_HOME}/etc/resolv.conf)" + [[ -e /etc/hosts ]] && assertEquals "$(cat /etc/hosts)" "$(cat "${JUNEST_HOME}"/etc/hosts)" + [[ -e /etc/host.conf ]] && assertEquals "$(cat /etc/host.conf)" "$(cat "${JUNEST_HOME}"/etc/host.conf)" + [[ -e /etc/nsswitch.conf ]] && assertEquals "$(cat /etc/nsswitch.conf)" "$(cat "${JUNEST_HOME}"/etc/nsswitch.conf)" + [[ -e /etc/resolv.conf ]] && assertEquals "$(cat /etc/resolv.conf)" "$(cat "${JUNEST_HOME}"/etc/resolv.conf)" } function _test_copy_remaining_files() { - [[ -e /etc/hosts.equiv ]] && assertEquals "$(cat /etc/hosts.equiv)" "$(cat ${JUNEST_HOME}/etc/hosts.equiv)" - [[ -e /etc/netgroup ]] && assertEquals "$(cat /etc/netgroup)" "$(cat ${JUNEST_HOME}/etc/netgroup)" - [[ -e /etc/networks ]] && assertEquals "$(cat /etc/networks)" "$(cat ${JUNEST_HOME}/etc/networks)" + [[ -e /etc/hosts.equiv ]] && assertEquals "$(cat /etc/hosts.equiv)" "$(cat "${JUNEST_HOME}"/etc/hosts.equiv)" + [[ -e /etc/netgroup ]] && assertEquals "$(cat /etc/netgroup)" "$(cat "${JUNEST_HOME}"/etc/netgroup)" + [[ -e /etc/networks ]] && assertEquals "$(cat /etc/networks)" "$(cat "${JUNEST_HOME}"/etc/networks)" [[ -e ${JUNEST_HOME}/etc/passwd ]] assertEquals 0 $? @@ -57,57 +59,183 @@ function _test_copy_remaining_files() { } function test_is_user_namespace_enabled_no_config_file(){ + PROC_USERNS_FILE="blah" + PROC_USERNS_CLONE_FILE="blah" CONFIG_PROC_FILE="blah" CONFIG_BOOT_FILE="blah" - assertCommandFailOnStatus $NOT_EXISTING_FILE _is_user_namespace_enabled + assertCommandFailOnStatus "$NOT_EXISTING_FILE" _is_user_namespace_enabled } function test_is_user_namespace_enabled_no_config(){ + PROC_USERNS_FILE="blah" + PROC_USERNS_CLONE_FILE="blah" touch config gzip config + # shellcheck disable=SC2034 CONFIG_PROC_FILE="config.gz" + # shellcheck disable=SC2034 CONFIG_BOOT_FILE="blah" - assertCommandFailOnStatus $NO_CONFIG_FOUND _is_user_namespace_enabled + assertCommandFailOnStatus "$NO_CONFIG_FOUND" _is_user_namespace_enabled } -function test_is_user_namespace_enabled_with_config(){ - echo "CONFIG_USER_NS=y" > config - gzip config - CONFIG_PROC_FILE="config.gz" - CONFIG_BOOT_FILE="blah" +function test_is_user_namespace_enabled_with_userns_clone_file_disabled(){ + PROC_USERNS_FILE="blah" + PROC_USERNS_CLONE_FILE="unprivileged_userns_clone" + echo "0" > $PROC_USERNS_CLONE_FILE + assertCommandFailOnStatus "$UNPRIVILEGED_USERNS_DISABLED" _is_user_namespace_enabled +} + +function test_is_user_namespace_enabled_with_userns_clone_file_enabled(){ + PROC_USERNS_CLONE_FILE="unprivileged_userns_clone" + echo "1" > $PROC_USERNS_CLONE_FILE assertCommandSuccess _is_user_namespace_enabled } -function test_run_env_with_namespace() { - assertCommandSuccess run_env_with_namespace "" "" - assertEquals "unshare --mount --user --map-root-user $GROOT --no-umount --recursive -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" +function test_is_user_namespace_enabled_with_proc_userns_file_existing(){ + PROC_USERNS_FILE="user" + ln -s . $PROC_USERNS_FILE + PROC_USERNS_CLONE_FILE="blah" + assertCommandSuccess _is_user_namespace_enabled +} + +function test_run_env_as_bwrap_fakeroot() { + assertCommandSuccess run_env_as_bwrap_fakeroot "" "" "false" + assertEquals "$BWRAP $COMMON_BWRAP_OPTION --cap-add ALL --uid 0 --gid 0 sudo /bin/sh --login" "$(cat "$STDOUTF")" + + _test_copy_common_files +} + +function test_run_env_as_bwrap_fakeroot_with_backend_command() { + assertCommandSuccess run_env_as_bwrap_fakeroot "mybwrap" "" "false" + assertEquals "mybwrap $COMMON_BWRAP_OPTION --cap-add ALL --uid 0 --gid 0 sudo /bin/sh --login" "$(cat "$STDOUTF")" + + _test_copy_common_files +} + +function test_run_env_as_bwrap_user() { + assertCommandSuccess run_env_as_bwrap_user "" "" "false" + assertEquals "$BWRAP $COMMON_BWRAP_OPTION /bin/sh --login" "$(cat "$STDOUTF")" _test_copy_common_files _test_copy_remaining_files } -function test_run_env_with_namespace_with_bindings() { - assertCommandSuccess run_env_with_namespace "-b /usr -b /lib:/tmp/lib" "" - assertEquals "unshare --mount --user --map-root-user $GROOT --no-umount --recursive -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" +function test_run_env_as_bwrap_user_with_backend_command() { + assertCommandSuccess run_env_as_bwrap_user "mybwrap" "" "false" + assertEquals "mybwrap $COMMON_BWRAP_OPTION /bin/sh --login" "$(cat "$STDOUTF")" _test_copy_common_files _test_copy_remaining_files } -function test_run_env_with_namespace_with_command() { - assertCommandSuccess run_env_with_namespace "" "ls -la" - assertEquals "unshare --mount --user --map-root-user $GROOT --no-umount --recursive -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" +function test_run_env_as_bwrap_fakeroot_no_copy() { + assertCommandSuccess run_env_as_bwrap_fakeroot "" "" "true" "" + assertEquals "$BWRAP $COMMON_BWRAP_OPTION --cap-add ALL --uid 0 --gid 0 sudo /bin/sh --login" "$(cat "$STDOUTF")" + + [[ ! -e ${JUNEST_HOME}/etc/hosts ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/host.conf ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/nsswitch.conf ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/resolv.conf ]] + assertEquals 0 $? + + [[ ! -e ${JUNEST_HOME}/etc/hosts.equiv ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/netgroup ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/networks ]] + assertEquals 0 $? + + [[ ! -e ${JUNEST_HOME}/etc/passwd ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/group ]] + assertEquals 0 $? +} + +function test_run_env_as_bwrap_user_no_copy() { + assertCommandSuccess run_env_as_bwrap_user "" "" "true" "" + assertEquals "$BWRAP $COMMON_BWRAP_OPTION /bin/sh --login" "$(cat "$STDOUTF")" + + [[ ! -e ${JUNEST_HOME}/etc/hosts ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/host.conf ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/nsswitch.conf ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/resolv.conf ]] + assertEquals 0 $? + + [[ ! -e ${JUNEST_HOME}/etc/hosts.equiv ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/netgroup ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/networks ]] + assertEquals 0 $? + + [[ ! -e ${JUNEST_HOME}/etc/passwd ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/group ]] + assertEquals 0 $? +} + +function test_run_env_as_bwrap_fakeroot_with_backend_args() { + assertCommandSuccess run_env_as_bwrap_fakeroot "" "--bind /usr /usr" "false" + assertEquals "$BWRAP $COMMON_BWRAP_OPTION --cap-add ALL --uid 0 --gid 0 --bind /usr /usr sudo /bin/sh --login" "$(cat "$STDOUTF")" + + _test_copy_common_files +} + +function test_run_env_as_bwrap_user_with_backend_args() { + assertCommandSuccess run_env_as_bwrap_user "" "--bind /usr /usr" "false" + assertEquals "$BWRAP $COMMON_BWRAP_OPTION --bind /usr /usr /bin/sh --login" "$(cat "$STDOUTF")" _test_copy_common_files _test_copy_remaining_files } -function test_run_env_with_namespace_with_bindings_and_command() { - assertCommandSuccess run_env_with_namespace "-b /usr -b /lib:/tmp/lib" "ls -la" - assertEquals "unshare --mount --user --map-root-user $GROOT --no-umount --recursive -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" +function test_run_env_as_bwrap_fakeroot_with_command() { + assertCommandSuccess run_env_as_bwrap_fakeroot "" "" "false" "ls -la" + assertEquals "$BWRAP $COMMON_BWRAP_OPTION --cap-add ALL --uid 0 --gid 0 sudo /bin/sh --login -c \"ls -la\"" "$(cat "$STDOUTF")" + + _test_copy_common_files +} + +function test_run_env_as_bwrap_user_with_command() { + assertCommandSuccess run_env_as_bwrap_user "" "" "false" "ls -la" + assertEquals "$BWRAP $COMMON_BWRAP_OPTION /bin/sh --login -c \"ls -la\"" "$(cat "$STDOUTF")" _test_copy_common_files _test_copy_remaining_files } -source $JUNEST_ROOT/tests/utils/shunit2 +function test_run_env_as_bwrap_fakeroot_with_backend_args_and_command() { + assertCommandSuccess run_env_as_bwrap_fakeroot "" "--bind /usr /usr" "false" "ls -la" + assertEquals "$BWRAP $COMMON_BWRAP_OPTION --cap-add ALL --uid 0 --gid 0 --bind /usr /usr sudo /bin/sh --login -c \"ls -la\"" "$(cat "$STDOUTF")" + + _test_copy_common_files +} + +function test_run_env_as_bwrap_user_with_backend_args_and_command() { + assertCommandSuccess run_env_as_bwrap_user "" "--bind /usr /usr" "false" "ls -la" + assertEquals "$BWRAP $COMMON_BWRAP_OPTION --bind /usr /usr /bin/sh --login -c \"ls -la\"" "$(cat "$STDOUTF")" + + _test_copy_common_files + _test_copy_remaining_files +} + +function test_run_env_as_bwrap_fakeroot_nested_env(){ + JUNEST_ENV=1 + assertCommandFailOnStatus 106 run_env_as_bwrap_fakeroot "" "" "false" "" + unset JUNEST_ENV +} + +function test_run_env_as_bwrap_user_nested_env(){ + # shellcheck disable=SC2034 + JUNEST_ENV=1 + assertCommandFailOnStatus 106 run_env_as_bwrap_user "" "" "false" "" + unset JUNEST_ENV +} + +source "$JUNEST_ROOT"/tests/utils/shunit2 diff --git a/tests/unit-tests/test-proot.sh b/tests/unit-tests/test-proot.sh index 6640035..0f4f11a 100755 --- a/tests/unit-tests/test-proot.sh +++ b/tests/unit-tests/test-proot.sh @@ -1,6 +1,7 @@ #!/bin/bash +# shellcheck disable=SC1091 -JUNEST_ROOT=$(readlink -f $(dirname $0)/../..) +JUNEST_ROOT=$(readlink -f "$(dirname "$0")"/../..) source "$JUNEST_ROOT/tests/utils/utils.sh" @@ -30,16 +31,16 @@ function tearDown(){ } function _test_copy_common_files() { - [[ -e /etc/hosts ]] && assertEquals "$(cat /etc/hosts)" "$(cat ${JUNEST_HOME}/etc/hosts)" - [[ -e /etc/host.conf ]] && assertEquals "$(cat /etc/host.conf)" "$(cat ${JUNEST_HOME}/etc/host.conf)" - [[ -e /etc/nsswitch.conf ]] && assertEquals "$(cat /etc/nsswitch.conf)" "$(cat ${JUNEST_HOME}/etc/nsswitch.conf)" - [[ -e /etc/resolv.conf ]] && assertEquals "$(cat /etc/resolv.conf)" "$(cat ${JUNEST_HOME}/etc/resolv.conf)" + [[ -e /etc/hosts ]] && assertEquals "$(cat /etc/hosts)" "$(cat "${JUNEST_HOME}"/etc/hosts)" + [[ -e /etc/host.conf ]] && assertEquals "$(cat /etc/host.conf)" "$(cat "${JUNEST_HOME}"/etc/host.conf)" + [[ -e /etc/nsswitch.conf ]] && assertEquals "$(cat /etc/nsswitch.conf)" "$(cat "${JUNEST_HOME}"/etc/nsswitch.conf)" + [[ -e /etc/resolv.conf ]] && assertEquals "$(cat /etc/resolv.conf)" "$(cat "${JUNEST_HOME}"/etc/resolv.conf)" } function _test_copy_remaining_files() { - [[ -e /etc/hosts.equiv ]] && assertEquals "$(cat /etc/hosts.equiv)" "$(cat ${JUNEST_HOME}/etc/hosts.equiv)" - [[ -e /etc/netgroup ]] && assertEquals "$(cat /etc/netgroup)" "$(cat ${JUNEST_HOME}/etc/netgroup)" - [[ -e /etc/networks ]] && assertEquals "$(cat /etc/networks)" "$(cat ${JUNEST_HOME}/etc/networks)" + [[ -e /etc/hosts.equiv ]] && assertEquals "$(cat /etc/hosts.equiv)" "$(cat "${JUNEST_HOME}"/etc/hosts.equiv)" + [[ -e /etc/netgroup ]] && assertEquals "$(cat /etc/netgroup)" "$(cat "${JUNEST_HOME}"/etc/netgroup)" + [[ -e /etc/networks ]] && assertEquals "$(cat /etc/networks)" "$(cat "${JUNEST_HOME}"/etc/networks)" [[ -e ${JUNEST_HOME}/etc/passwd ]] assertEquals 0 $? @@ -47,72 +48,173 @@ function _test_copy_remaining_files() { assertEquals 0 $? } -function test_run_env_as_user(){ +function test_run_env_as_proot_user(){ + # shellcheck disable=SC2317 _run_env_with_qemu() { - echo $@ + # shellcheck disable=SC2086 + # shellcheck disable=SC2048 + echo $* } - assertCommandSuccess run_env_as_user "-k 3.10" "/usr/bin/mkdir" "-v" "/newdir2" - assertEquals "-b $HOME -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10 /usr/bin/mkdir -v /newdir2" "$(cat $STDOUTF)" + assertCommandSuccess run_env_as_proot_user "" "-k 3.10" "false" "/usr/bin/mkdir" "-v" "/newdir2" + assertEquals "-b /run/user/$(id -u) -b $HOME -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10 /usr/bin/mkdir -v /newdir2" "$(cat "$STDOUTF")" SH=("/usr/bin/echo") - assertCommandSuccess run_env_as_user "-k 3.10" - assertEquals "-b $HOME -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10" "$(cat $STDOUTF)" + assertCommandSuccess run_env_as_proot_user "" "-k 3.10" "false" + assertEquals "-b /run/user/$(id -u) -b $HOME -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10" "$(cat "$STDOUTF")" _test_copy_common_files _test_copy_remaining_files } -function test_run_env_as_fakeroot(){ +function test_run_env_as_proot_user_with_backend_command(){ + # shellcheck disable=SC2317 _run_env_with_qemu() { - echo $@ + # shellcheck disable=SC2086 + # shellcheck disable=SC2048 + echo $* } - assertCommandSuccess run_env_as_fakeroot "-k 3.10" "/usr/bin/mkdir" "-v" "/newdir2" - assertEquals "-0 -b ${HOME} -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10 /usr/bin/mkdir -v /newdir2" "$(cat $STDOUTF)" + assertCommandSuccess run_env_as_proot_user "myproot" "-k 3.10" "false" "/usr/bin/mkdir" "-v" "/newdir2" + assertEquals "myproot -b /run/user/$(id -u) -b $HOME -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10 /usr/bin/mkdir -v /newdir2" "$(cat "$STDOUTF")" SH=("/usr/bin/echo") - assertCommandSuccess run_env_as_fakeroot "-k 3.10" - assertEquals "-0 -b ${HOME} -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10" "$(cat $STDOUTF)" + assertCommandSuccess run_env_as_proot_user "myproot" "-k 3.10" "false" + assertEquals "myproot -b /run/user/$(id -u) -b $HOME -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10" "$(cat "$STDOUTF")" + + _test_copy_common_files + _test_copy_remaining_files +} + +function test_run_env_as_proot_user_no_copy(){ + # shellcheck disable=SC2317 + _run_env_with_qemu() { + # shellcheck disable=SC2086 + # shellcheck disable=SC2048 + echo $* + } + assertCommandSuccess run_env_as_proot_user "" "-k 3.10" "true" "/usr/bin/mkdir" "-v" "/newdir2" + assertEquals "-b /run/user/$(id -u) -b $HOME -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10 /usr/bin/mkdir -v /newdir2" "$(cat "$STDOUTF")" + + [[ ! -e ${JUNEST_HOME}/etc/hosts ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/host.conf ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/nsswitch.conf ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/resolv.conf ]] + assertEquals 0 $? + + [[ ! -e ${JUNEST_HOME}/etc/hosts.equiv ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/netgroup ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/networks ]] + assertEquals 0 $? + + [[ ! -e ${JUNEST_HOME}/etc/passwd ]] + assertEquals 0 $? + [[ ! -e ${JUNEST_HOME}/etc/group ]] + assertEquals 0 $? +} + +function test_run_env_as_proot_user_nested_env(){ + JUNEST_ENV=1 + assertCommandFailOnStatus 106 run_env_as_proot_user "" "" "false" + unset JUNEST_ENV +} + +function test_run_env_as_proot_fakeroot(){ + # shellcheck disable=SC2317 + _run_env_with_qemu() { + # shellcheck disable=SC2086 + # shellcheck disable=SC2048 + echo $* + } + assertCommandSuccess run_env_as_proot_fakeroot "" "-k 3.10" "false" "/usr/bin/mkdir" "-v" "/newdir2" + assertEquals "-0 -b /run/user/$(id -u) -b ${HOME} -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10 /usr/bin/mkdir -v /newdir2" "$(cat "$STDOUTF")" + + SH=("/usr/bin/echo") + assertCommandSuccess run_env_as_proot_fakeroot "" "-k 3.10" "false" + assertEquals "-0 -b /run/user/$(id -u) -b ${HOME} -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10" "$(cat "$STDOUTF")" _test_copy_common_files } -function test_run_env_with_quotes(){ +function test_run_env_as_proot_fakeroot_with_backend_command(){ + # shellcheck disable=SC2317 _run_env_with_qemu() { - echo $@ + # shellcheck disable=SC2086 + # shellcheck disable=SC2048 + echo $* } - assertCommandSuccess run_env_as_user "-k 3.10" "bash" "-c" "/usr/bin/mkdir -v /newdir2" - assertEquals "-b ${HOME} -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10 bash -c /usr/bin/mkdir -v /newdir2" "$(cat $STDOUTF)" + assertCommandSuccess run_env_as_proot_fakeroot "myproot" "-k 3.10" "false" "/usr/bin/mkdir" "-v" "/newdir2" + assertEquals "myproot -0 -b /run/user/$(id -u) -b ${HOME} -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10 /usr/bin/mkdir -v /newdir2" "$(cat "$STDOUTF")" + + # shellcheck disable=SC2034 + SH=("/usr/bin/echo") + assertCommandSuccess run_env_as_proot_fakeroot "myproot" "-k 3.10" "false" + assertEquals "myproot -0 -b /run/user/$(id -u) -b ${HOME} -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10" "$(cat "$STDOUTF")" + + _test_copy_common_files +} + +function test_run_env_as_proot_fakeroot_nested_env(){ + JUNEST_ENV=1 + assertCommandFailOnStatus 106 run_env_as_proot_fakeroot "" "" "false" "" + unset JUNEST_ENV +} + +function test_run_env_with_quotes(){ + # shellcheck disable=SC2317 + _run_env_with_qemu() { + # shellcheck disable=SC2086 + # shellcheck disable=SC2048 + echo $* + } + assertCommandSuccess run_env_as_proot_user "" "-k 3.10" "false" "bash" "-c" "/usr/bin/mkdir -v /newdir2" + assertEquals "-b /run/user/$(id -u) -b ${HOME} -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10 bash -c /usr/bin/mkdir -v /newdir2" "$(cat "$STDOUTF")" } function test_run_env_with_proot_args(){ + # shellcheck disable=SC2317 proot_cmd() { [ "$JUNEST_ENV" != "1" ] && return 1 - echo $@ + # shellcheck disable=SC2086 + # shellcheck disable=SC2048 + echo $* } - assertCommandSuccess _run_env_with_proot --help - assertEquals "--help /bin/sh --login" "$(cat $STDOUTF)" + assertCommandSuccess _run_env_with_proot "" "--help" + assertEquals "--help /bin/sh --login" "$(cat "$STDOUTF")" - assertCommandSuccess _run_env_with_proot --help mycommand - assertEquals "--help /bin/sh --login -c mycommand" "$(cat $STDOUTF)" + assertCommandSuccess _run_env_with_proot "" "--help" mycommand + assertEquals "--help /bin/sh --login -c mycommand" "$(cat "$STDOUTF")" assertCommandFail _run_env_with_proot } function test_qemu() { - echo "JUNEST_ARCH=arm" > ${JUNEST_HOME}/etc/junest/info + echo "JUNEST_ARCH=arm" > "${JUNEST_HOME}"/etc/junest/info + # shellcheck disable=SC2317 rm_cmd() { - echo $@ + # shellcheck disable=SC2086 + # shellcheck disable=SC2048 + echo $* } + # shellcheck disable=SC2317 ln_cmd() { - echo $@ + # shellcheck disable=SC2086 + # shellcheck disable=SC2048 + echo $* } + # shellcheck disable=SC2317 _run_env_with_proot() { - echo $@ + # shellcheck disable=SC2086 + # shellcheck disable=SC2048 + echo $* } - RANDOM=100 ARCH=x86_64 assertCommandSuccess _run_env_with_qemu "" - assertEquals "$(echo -e "-s $JUNEST_HOME/opt/qemu/qemu-arm-static-x86_64 /tmp/qemu-arm-static-x86_64-100\n-q /tmp/qemu-arm-static-x86_64-100")" "$(cat $STDOUTF)" + RANDOM=100 ARCH=x86_64 assertCommandSuccess _run_env_with_qemu "" "" + assertEquals "$(echo -e "-s $JUNEST_HOME/bin/qemu-arm-static-x86_64 /tmp/qemu-arm-static-x86_64-100\n-q /tmp/qemu-arm-static-x86_64-100")" "$(cat "$STDOUTF")" } -source $JUNEST_ROOT/tests/utils/shunit2 +source "$JUNEST_ROOT"/tests/utils/shunit2 diff --git a/tests/unit-tests/test-setup.sh b/tests/unit-tests/test-setup.sh index a15dbca..de2df75 100755 --- a/tests/unit-tests/test-setup.sh +++ b/tests/unit-tests/test-setup.sh @@ -1,6 +1,7 @@ #!/bin/bash +# shellcheck disable=SC1091 -JUNEST_ROOT=$(readlink -f $(dirname $0)/../..) +JUNEST_ROOT=$(readlink -f "$(dirname "$0")"/../..) source "$JUNEST_ROOT/tests/utils/utils.sh" @@ -26,24 +27,27 @@ function tearDown(){ } function test_is_env_installed(){ - rm -rf $JUNEST_HOME/* + rm -rf "${JUNEST_HOME:?}"/* assertCommandFail is_env_installed - touch $JUNEST_HOME/just_file + touch "$JUNEST_HOME"/just_file assertCommandSuccess is_env_installed } function test_setup_env(){ - rm -rf $JUNEST_HOME/* + rm -rf "${JUNEST_HOME:?}"/* + # shellcheck disable=SC2317 wget_mock(){ # Proof that the setup is happening # inside $JUNEST_TEMPDIR - local cwd=${PWD#${JUNEST_TEMPDIR}} - local parent_dir=${PWD%${cwd}} + local cwd=${PWD#"${JUNEST_TEMPDIR}"} + local parent_dir=${PWD%"${cwd}"} assertEquals "$JUNEST_TEMPDIR" "${parent_dir}" touch file - tar -czvf ${CMD}-${ARCH}.tar.gz file + tar -czvf "${CMD}-${ARCH}".tar.gz file } + # shellcheck disable=SC2034 WGET=wget_mock + # shellcheck disable=SC2119 setup_env 1> /dev/null assertTrue "[ -e $JUNEST_HOME/file ]" @@ -52,10 +56,10 @@ function test_setup_env(){ function test_setup_env_from_file(){ - rm -rf $JUNEST_HOME/* + rm -rf "${JUNEST_HOME:?}"/* touch file - tar -czvf ${CMD}-${ARCH}.tar.gz file 1> /dev/null - assertCommandSuccess setup_env_from_file ${CMD}-${ARCH}.tar.gz + tar -czvf "${CMD}-${ARCH}".tar.gz file 1> /dev/null + assertCommandSuccess setup_env_from_file "${CMD}-${ARCH}.tar.gz" assertTrue "[ -e $JUNEST_HOME/file ]" } @@ -64,10 +68,10 @@ function test_setup_env_from_file_not_existing_file(){ } function test_setup_env_from_file_with_absolute_path(){ - rm -rf $JUNEST_HOME/* + rm -rf "${JUNEST_HOME:?}"/* touch file - tar -czf ${CMD}-${ARCH}.tar.gz file - assertCommandSuccess setup_env_from_file ${CMD}-${ARCH}.tar.gz + tar -czf "${CMD}-${ARCH}".tar.gz file + assertCommandSuccess setup_env_from_file "${CMD}-${ARCH}.tar.gz" assertTrue "[ -e $JUNEST_HOME/file ]" } @@ -78,4 +82,4 @@ function test_delete_env(){ assertCommandFail is_env_installed } -source $JUNEST_ROOT/tests/utils/shunit2 +source "$JUNEST_ROOT"/tests/utils/shunit2 diff --git a/tests/unit-tests/test-utils.sh b/tests/unit-tests/test-utils.sh index c0b1ebf..03e602a 100755 --- a/tests/unit-tests/test-utils.sh +++ b/tests/unit-tests/test-utils.sh @@ -1,10 +1,13 @@ #!/bin/bash -source "$(dirname $0)/../utils/utils.sh" +# shellcheck disable=SC1091 + +source "$(dirname "$0")/../utils/utils.sh" unset HOME -export HOME=$(TMPDIR=/tmp mktemp -d -t pearl-user-home.XXXXXXX) +export HOME +HOME=$(TMPDIR=/tmp mktemp -d -t pearl-user-home.XXXXXXX) -source "$(dirname $0)/../../lib/utils/utils.sh" +source "$(dirname "$0")/../../lib/utils/utils.sh" # Disable the exiterr set +e @@ -20,37 +23,42 @@ function test_check_not_null(){ function test_echoerr(){ assertCommandSuccess echoerr "Test" - assertEquals "Test" "$(cat $STDERRF)" + assertEquals "Test" "$(cat "$STDERRF")" } function test_error(){ assertCommandSuccess error "Test" - local expected=$(echo -e "\033[1;31mTest\033[0m") - assertEquals "$expected" "$(cat $STDERRF)" + local expected + expected=$(echo -e "\033[1;31mTest\033[0m") + assertEquals "$expected" "$(cat "$STDERRF")" } function test_warn(){ assertCommandSuccess warn "Test" - local expected=$(echo -e "\033[1;33mTest\033[0m") - assertEquals "$expected" "$(cat $STDERRF)" + local expected + expected=$(echo -e "\033[1;33mTest\033[0m") + assertEquals "$expected" "$(cat "$STDERRF")" } function test_info(){ assertCommandSuccess info "Test" - local expected=$(echo -e "\033[1;36mTest\033[0m") - assertEquals "$expected" "$(cat $STDOUTF)" + local expected + expected=$(echo -e "\033[1;36mTest\033[0m") + assertEquals "$expected" "$(cat "$STDOUTF")" } function test_die(){ assertCommandFail die "Test" - local expected=$(echo -e "\033[1;31mTest\033[0m") - assertEquals "$expected" "$(cat $STDERRF)" + local expected + expected=$(echo -e "\033[1;31mTest\033[0m") + assertEquals "$expected" "$(cat "$STDERRF")" } function test_die_on_status(){ assertCommandFailOnStatus 222 die_on_status 222 "Test" - local expected=$(echo -e "\033[1;31mTest\033[0m") - assertEquals "$expected" "$(cat $STDERRF)" + local expected + expected=$(echo -e "\033[1;31mTest\033[0m") + assertEquals "$expected" "$(cat "$STDERRF")" } function test_ask_null_question(){ @@ -79,34 +87,12 @@ function test_ask_wrong_default_answer() { assertEquals 33 $? } -function test_check_and_trap_fail() { - trap echo EXIT - trap ls QUIT - assertCommandFailOnStatus 1 check_and_trap 'pwd' EXIT QUIT -} - -function test_check_and_trap() { - trap - EXIT QUIT - assertCommandSuccess check_and_trap 'echo' EXIT QUIT -} - -function test_check_and_force_trap_fail() { - trap echo EXIT - trap ls QUIT - assertCommandSuccess check_and_force_trap 'echo' EXIT QUIT -} - -function test_check_and_force_trap() { - trap - EXIT QUIT - assertCommandSuccess check_and_force_trap 'echo' EXIT QUIT -} - function test_insert_quotes_on_spaces(){ assertCommandSuccess insert_quotes_on_spaces this is "a test" - assertEquals "this is \"a test\"" "$(cat $STDOUTF)" + assertEquals "this is \"a test\"" "$(cat "$STDOUTF")" assertCommandSuccess insert_quotes_on_spaces this is 'a test' - assertEquals "this is \"a test\"" "$(cat $STDOUTF)" + assertEquals "this is \"a test\"" "$(cat "$STDOUTF")" } function test_contains_element(){ @@ -116,4 +102,4 @@ function test_contains_element(){ assertCommandFailOnStatus 1 contains_element "blabla" "${array[@]}" } -source $(dirname $0)/../utils/shunit2 +source "$(dirname "$0")"/../utils/shunit2 diff --git a/tests/unit-tests/test-wrappers.sh b/tests/unit-tests/test-wrappers.sh new file mode 100755 index 0000000..ee9776f --- /dev/null +++ b/tests/unit-tests/test-wrappers.sh @@ -0,0 +1,139 @@ +#!/bin/bash +# shellcheck disable=SC1091 + +source "$(dirname "$0")/../utils/utils.sh" + +source "$(dirname "$0")/../../lib/core/wrappers.sh" + +# Disable the exiterr +set +e + +function oneTimeSetUp(){ + setUpUnitTests +} + +function setUp(){ + junestSetUp +} + +function tearDown(){ + junestTearDown +} + +function test_create_wrappers_empty_bin(){ + assertCommandSuccess create_wrappers + assertEquals "" "$(cat "$STDOUTF")" + assertTrue "bin_wrappers does not exist" "[ -e $JUNEST_HOME/usr/bin_wrappers ]" +} + +function test_create_wrappers_not_executable_file(){ + touch "$JUNEST_HOME"/usr/bin/myfile + assertCommandSuccess create_wrappers + assertEquals "" "$(cat "$STDOUTF")" + assertTrue "bin_wrappers should exist" "[ -e $JUNEST_HOME/usr/bin_wrappers ]" + assertTrue "myfile wrapper should not exist" "[ ! -x $JUNEST_HOME/usr/bin_wrappers/myfile ]" +} + +function test_create_wrappers_directory(){ + mkdir -p "$JUNEST_HOME"/usr/bin/mydir + assertCommandSuccess create_wrappers + assertEquals "" "$(cat "$STDOUTF")" + assertTrue "bin_wrappers should exist" "[ -e $JUNEST_HOME/usr/bin_wrappers ]" + assertTrue "mydir wrapper should not exist" "[ ! -e $JUNEST_HOME/usr/bin_wrappers/mydir ]" +} + +function test_create_wrappers_broken_link(){ + ln -s /opt/myapp/bin/cmd "$JUNEST_HOME"/usr/bin/cmd + assertCommandSuccess create_wrappers + assertEquals "" "$(cat "$STDOUTF")" + assertTrue "bin_wrappers should exist" "[ -e $JUNEST_HOME/usr/bin_wrappers ]" + assertTrue "cmd wrapper should exist" "[ -x $JUNEST_HOME/usr/bin_wrappers/cmd ]" +} + +function test_create_wrappers_executable_file(){ + touch "$JUNEST_HOME"/usr/bin/myfile + chmod +x "$JUNEST_HOME"/usr/bin/myfile + assertCommandSuccess create_wrappers + assertEquals "" "$(cat "$STDOUTF")" + assertTrue "bin_wrappers should exist" "[ -e $JUNEST_HOME/usr/bin_wrappers ]" + assertTrue "myfile wrapper should exist" "[ -x $JUNEST_HOME/usr/bin_wrappers/myfile ]" +} + +function test_create_wrappers_verify_content(){ + # Test for: + # https://github.com/fsquillace/junest/issues/262 + # https://github.com/fsquillace/junest/issues/292 + touch "$JUNEST_HOME"/usr/bin/myfile + chmod +x "$JUNEST_HOME"/usr/bin/myfile + export JUNEST_ARGS="ns --fakeroot -b '--bind /run /run2'" + assertCommandSuccess create_wrappers + assertEquals "" "$(cat "$STDOUTF")" + + # Mock junest command to capture the actual output generated from myfile script + # shellcheck disable=SC2317 + junest(){ + for arg in "$@" + do + echo "$arg" + done + } + assertEquals "ns +--fakeroot +-b +--bind /run /run2 +-- +test-wrappers.sh +pacman +-Rsn +neovim +new package" "$(source "$JUNEST_HOME"/usr/bin_wrappers/myfile pacman -Rsn neovim 'new package')" +} + +function test_create_wrappers_already_exist(){ + touch "$JUNEST_HOME"/usr/bin/myfile + chmod +x "$JUNEST_HOME"/usr/bin/myfile + mkdir -p "$JUNEST_HOME"/usr/bin_wrappers + echo "original" > "$JUNEST_HOME"/usr/bin_wrappers/myfile + chmod +x "$JUNEST_HOME"/usr/bin_wrappers/myfile + assertCommandSuccess create_wrappers false + assertEquals "" "$(cat "$STDOUTF")" + assertTrue "bin_wrappers should exist" "[ -e $JUNEST_HOME/usr/bin_wrappers ]" + assertTrue "myfile wrapper should exist" "[ -x $JUNEST_HOME/usr/bin_wrappers/myfile ]" + assertEquals "original" "$(cat "$JUNEST_HOME"/usr/bin_wrappers/myfile)" +} + +function test_create_wrappers_forced_already_exist(){ + echo "new" > "$JUNEST_HOME"/usr/bin/myfile + chmod +x "$JUNEST_HOME"/usr/bin/myfile + mkdir -p "$JUNEST_HOME"/usr/bin_wrappers + echo "original" > "$JUNEST_HOME"/usr/bin_wrappers/myfile + chmod +x "$JUNEST_HOME"/usr/bin_wrappers/myfile + assertCommandSuccess create_wrappers true + assertEquals "" "$(cat "$STDOUTF")" + assertTrue "bin_wrappers should exist" "[ -e $JUNEST_HOME/usr/bin_wrappers ]" + assertTrue "myfile wrapper should exist" "[ -x $JUNEST_HOME/usr/bin_wrappers/myfile ]" + assertNotEquals "original" "$(cat "$JUNEST_HOME"/usr/bin_wrappers/myfile)" +} + +function test_create_wrappers_executable_no_longer_exist(){ + mkdir -p "$JUNEST_HOME"/usr/bin_wrappers + touch "$JUNEST_HOME"/usr/bin_wrappers/myfile + chmod +x "$JUNEST_HOME"/usr/bin_wrappers/myfile + assertCommandSuccess create_wrappers + assertEquals "" "$(cat "$STDOUTF")" + assertTrue "bin_wrappers should exist" "[ -e $JUNEST_HOME/usr/bin_wrappers ]" + assertTrue "myfile wrapper should not exist" "[ ! -x $JUNEST_HOME/usr/bin_wrappers/myfile ]" +} + +function test_create_wrappers_custom_bin_path(){ + mkdir -p "$JUNEST_HOME"/usr/mybindir + touch "$JUNEST_HOME"/usr/mybindir/myfile + chmod +x "$JUNEST_HOME"/usr/mybindir/myfile + assertCommandSuccess create_wrappers false /usr/mybindir/ + assertEquals "" "$(cat "$STDOUTF")" + assertTrue "bin_wrappers should exist" "[ -e $JUNEST_HOME/usr/mybindir_wrappers ]" + assertTrue "myfile wrapper should exist" "[ -x $JUNEST_HOME/usr/mybindir_wrappers/myfile ]" +} + + +source "$(dirname "$0")"/../utils/shunit2 diff --git a/tests/unit-tests/unit-tests.sh b/tests/unit-tests/unit-tests.sh index acf1bba..e90ed22 100755 --- a/tests/unit-tests/unit-tests.sh +++ b/tests/unit-tests/unit-tests.sh @@ -1,6 +1,7 @@ #!/bin/bash tests_succeded=true -for tst in $(ls $(dirname $0)/test* | grep -v $(basename $0)) +# shellcheck disable=SC2010 +for tst in $(ls "$(dirname "$0")"/test* | grep -v "$(basename "$0")") do $tst || tests_succeded=false done diff --git a/tests/utils/shunit2 b/tests/utils/shunit2 index d6e7503..e4c719c 100644 --- a/tests/utils/shunit2 +++ b/tests/utils/shunit2 @@ -987,7 +987,7 @@ _shunit_extractTestFunctions() # extract the lines with test function names, strip of anything besides the # function name, and output everything on a single line. _shunit_regex_='^[ ]*(function )*test[A-Za-z0-9_]* *\(\)' - egrep "${_shunit_regex_}" "${_shunit_script_}" \ + grep -E "${_shunit_regex_}" "${_shunit_script_}" \ |sed 's/^[^A-Za-z0-9_]*//;s/^function //;s/\([A-Za-z0-9_]*\).*/\1/g' \ |xargs diff --git a/tests/utils/utils.sh b/tests/utils/utils.sh index ed7cb8c..ab37bd9 100644 --- a/tests/utils/utils.sh +++ b/tests/utils/utils.sh @@ -1,25 +1,28 @@ +#!/usr/bin/env bash + OLD_CWD=${PWD} function cwdSetUp(){ ORIGIN_CWD=$(TMPDIR=/tmp mktemp -d -t junest-cwd.XXXXXXXXXX) - cd $ORIGIN_CWD + cd "$ORIGIN_CWD" || return 1 } function cwdTearDown(){ - rm -rf $ORIGIN_CWD - cd $OLD_CWD + rm -rf "$ORIGIN_CWD" + cd "$OLD_CWD" || return 1 } function junestSetUp(){ JUNEST_HOME=$(TMPDIR=/tmp mktemp -d -t junest-home.XXXXXXXXXX) - mkdir -p ${JUNEST_HOME}/etc/junest - echo "JUNEST_ARCH=x86_64" > ${JUNEST_HOME}/etc/junest/info - mkdir -p ${JUNEST_HOME}/etc/ca-certificates + mkdir -p "${JUNEST_HOME}/usr/bin" + mkdir -p "${JUNEST_HOME}/etc/junest" + echo "JUNEST_ARCH=x86_64" > "${JUNEST_HOME}/etc/junest/info" + mkdir -p "${JUNEST_HOME}/etc/ca-certificates" } function junestTearDown(){ # the CA directories are read only and can be deleted only by changing the mod - [ -d ${JUNEST_HOME}/etc/ca-certificates ] && chmod -R +w ${JUNEST_HOME}/etc/ca-certificates - rm -rf $JUNEST_HOME + [ -d "${JUNEST_HOME}/etc/ca-certificates" ] && chmod -R +w "${JUNEST_HOME}/etc/ca-certificates" + rm -rf "$JUNEST_HOME" unset JUNEST_HOME } @@ -31,15 +34,17 @@ function setUpUnitTests(){ } function assertCommandSuccess(){ + # shellcheck disable=SC2091 $(set -e - "$@" > $STDOUTF 2> $STDERRF + "$@" > "$STDOUTF" 2> "$STDERRF" ) assertTrue "The command $1 did not return 0 exit status" $? } function assertCommandFail(){ + # shellcheck disable=SC2091 $(set -e - "$@" > $STDOUTF 2> $STDERRF + "$@" > "$STDOUTF" 2> "$STDERRF" ) assertFalse "The command $1 returned 0 exit status" $? } @@ -49,8 +54,9 @@ function assertCommandFail(){ function assertCommandFailOnStatus(){ local status=$1 shift + # shellcheck disable=SC2091 $(set -e - "$@" > $STDOUTF 2> $STDERRF + "$@" > "$STDOUTF" 2> "$STDERRF" ) - assertEquals $status $? + assertEquals "$status" $? }