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 570a311..fab23ba 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,17 +1,62 @@
-language: bash
-
sudo: required
+os: linux
+
+cache:
+ directories:
+ - ~/.ccache
+ - ~/.pkg-cache
+
+services:
+- docker
+
+env:
+ matrix:
+ - TRAVIS_BASH_VERSION="4.0"
+
+before_install:
+ - ./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))"
+ - PATH=$PWD/bin:$PATH
script:
- - ./tests/test_all.sh
- - junest --check ./bin/junest
- - yes | junest --delete
- - JUNEST_HOME=~/.junest-arm junest --check ./bin/junest --skip-root-tests
- - yes | JUNEST_HOME=~/.junest-arm junest --delete
+ #######################
+ # 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
+
+ # 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
+
+ #######################
+ # 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/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..9ea8193
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,260 @@
+Contributing to JuNest
+=====================
+
+First off, thanks for taking the time to contribute!
+
+The following is a set of guidelines for contributing to JuNest.
+These are just guidelines, not rules, use your best judgment and
+feel free to propose changes to this document in a pull request.
+
+**Table of Contents**
+
+- [How Can I Contribute?](#how-can-i-contribute)
+ - [Reporting Bugs](#reporting-bugs)
+ - [Suggesting Enhancements](#suggesting-enhancements)
+ - [Your First Code Contribution](#your-first-code-contribution)
+
+- [Styleguides](#styleguides)
+ - [Git Commit Messages](#git-commit-messages)
+ - [Documentation Styleguide](#documentation-styleguide)
+ - [Shell Styleguide](#shell-styleguide)
+
+- [Versioning](#versioning)
+
+## How Can I Contribute? ##
+
+### Reporting Bugs ###
+
+This section guides you through submitting a bug report for JuNest.
+
+#### Before submitting a bug report ####
+
+You might be able to find the cause of the problem and fix things yourself.
+
+- **Check the [troubleshooting section](https://github.com/fsquillace/junest#troubleshooting)**
+- **Check if you can reproduce the problem with the latest version of JuNest**
+- **Check for [existing open/closed issues](https://github.com/fsquillace/junest/issues?utf8=%E2%9C%93&q=is%3Aissue)**
+ - If the bug has already been suggested, add a comment to the existing issue instead of opening a new one.
+
+#### How Do I Submit A (Good) Bug Report? ####
+
+Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/) in the [JuNest issues page](https://github.com/fsquillace/junest/issues).
+Explain the problem and include additional details to help maintainers reproduce the problem:
+
+* **Use a clear and descriptive title** for the issue to identify the problem.
+* **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining how you started JuNest, e.g. which command exactly you used in the terminal. When listing steps, **don't just say what you did, but explain how you did it**. For example.
+* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
+* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
+* **Explain which behavior you expected to see instead and why.**
+* **Put the bug label to the issue.**
+
+Include details about your configuration and environment:
+
+* **Which version of JuNest are you using?**
+* **What's the name and version of the OS you're using**?
+* **Are you running JuNest in a virtual machine?** If so, which VM software are you using and which operating systems and versions are used for the host and the guest?
+* **Which packages do you have installed?** You can get that list by running `pacman -Qq`.
+
+#### Template For Submitting Bug Reports ####
+
+ [Short description of problem here]
+
+ **Reproduction Steps:**
+
+ 1. [First Step]
+ 2. [Second Step]
+ 3. [Other Steps...]
+
+ **Expected behavior:**
+
+ [Describe expected behavior here]
+
+ **Observed behavior:**
+
+ [Describe observed behavior here]
+
+ **JuNest version:** [Enter JuNest version here]
+ **OS and version:** [Enter OS name and version here]
+
+ **Installed packages:**
+
+ [List of installed packages here]
+
+ **Additional information:**
+
+ * Problem started happening recently, didn't happen in an older version of JuNest: [Yes/No]
+ * Problem can be reliably reproduced, doesn't happen randomly: [Yes/No]
+
+### Suggesting Enhancements ###
+
+This section guides you through submitting an enhancement suggestion for JuNest, including completely new features and minor improvements to existing functionality.
+
+#### Before Submitting An Enhancement Suggestion ####
+
+* **Check if you're using the latest version of JuNest**
+- **Check for [existing open/closed issues](https://github.com/fsquillace/junest/issues?utf8=%E2%9C%93&q=is%3Aissue)**
+ - If enhancement has already been suggested, add a comment to the existing issue instead of opening a new one.
+
+#### How Do I Submit A (Good) Enhancement Suggestion? ####
+
+Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/) in the [JuNest issues page](https://github.com/fsquillace/junest/issues).
+
+Create an issue on that repository and provide the following information:
+
+* **Use a clear and descriptive title** for the issue to identify the suggestion.
+* **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
+* **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
+* **Describe the current behavior** and **explain which behavior you expected to see instead** and why.
+* **Specify which version of JuNest you're using.**
+* **Specify the name and version of the OS you're using.**
+* **Put the enanchement label to the issue.**
+
+#### Template For Submitting Enhancement Suggestions ####
+
+ [Short description of suggestion]
+
+ **Steps which explain the enhancement**
+
+ 1. [First Step]
+ 2. [Second Step]
+ 3. [Other Steps...]
+
+ **Current and suggested behavior**
+
+ [Describe current and suggested behavior here]
+
+ **Why would the enhancement be useful to most users**
+
+ [Explain why the enhancement would be useful to most users]
+
+ [List some other text editors or applications where this enhancement exists]
+
+ **JuNest Version:** [Enter JuNest version here]
+ **OS and Version:** [Enter OS name and version here]
+
+### Your First Code Contribution ###
+
+All JuNest issues are tracked as [GitHub issues](https://guides.github.com/features/issues/) in the [JuNest issues page](https://github.com/fsquillace/junest/issues).
+
+#### Pull Requests ####
+
+* Fork the repo and create your feature branch from ***dev***.
+* If you make significant changes, please add tests too.
+ Get familiar with [shunit](https://github.com/kward/shunit2).
+* If you've changed APIs, please update the documentation
+* Follow the [Shell styleguide](#shell-styleguide).
+* Document new code based on the
+ [Documentation Styleguide](#documentation-styleguide).
+* End files with a newline.
+* Follow the [Git commit messages](#git-commit-messages).
+* Send a [GitHub Pull Request to JuNest](https://github.com/fsquillace/junest/compare/dev...) with a clear list of what you've done (read more about [pull requests](http://help.github.com/pull-requests/)).
+* Put **dev as the base branch** and NOT the master one.
+
+#### Unit Tests ####
+To run unit tests:
+```sh
+./tests/unit-tests/unit-tests.sh
+```
+
+#### Integration Tests ####
+Generally, there is no need to run integration tests locally
+since [Travis](https://travis-ci.org/fsquillace/junest) will run as
+soon as the pull request gets created.
+
+## Styleguides ##
+
+### Git Commit Messages ###
+
+* Follow the [seven rules](http://chris.beams.io/posts/git-commit/#seven-rules) of a great Git commit message
+* Reference issues and pull requests liberally
+* Consider starting the commit message with an applicable emoji:
+ * :art: `:art:` when improving the format/structure of the code
+ * :racehorse: `:racehorse:` when improving performance
+ * :non-potable_water: `:non-potable_water:` when plugging memory leaks
+ * :memo: `:memo:` when writing docs
+ * :penguin: `:penguin:` when fixing something on Linux
+ * :apple: `:apple:` when fixing something on Mac OS
+ * :checkered_flag: `:checkered_flag:` when fixing something on Windows
+ * :bug: `:bug:` when fixing a bug
+ * :fire: `:fire:` when removing code or files
+ * :green_heart: `:green_heart:` when fixing the CI build
+ * :white_check_mark: `:white_check_mark:` when adding tests
+ * :lock: `:lock:` when dealing with security
+ * :arrow_up: `:arrow_up:` when upgrading dependencies
+ * :arrow_down: `:arrow_down:` when downgrading dependencies
+ * :shirt: `:shirt:` when removing linter warnings
+ * :package: `:package:` when bumping the version
+
+### Documentation Styleguide ###
+
+* Use [Markdown](https://daringfireball.net/projects/markdown).
+
+### Shell Styleguide ###
+
+* Use [google shell styleguide](https://google.github.io/styleguide/shell.xml)
+
+#### Function documentation ####
+For function documentation follows the example below:
+
+ #######################################
+ # Cleanup files from the backup dir.
+ #
+ # Globals:
+ # VAR1 (RO,bool) : `my_func` access to VAR1.
+ # VAR2 (WO) : `my_func` change the value of VAR2.
+ # VAR3 (RW) : `my_func` read and write to VAR3.
+ # Arguments:
+ # arg1 ($1,int) : Directory to cleanup.
+ # arg2 ($2-) : Command to execute for the cleanup.
+ # Returns:
+ # 0 : Cleanup completed successfully.
+ # 101 : Backup directory is not readable.
+ # $NOT_DIR_ERROR : Backup directory is not a directory.
+ # Output:
+ # None
+ #######################################
+ my_func() {
+ local arg1=$1
+ shift
+ local arg2=$@
+ ...
+ }
+
+The documentation is divided by a description of the function, a `Globals`,
+`Arguments`, `Returns` and `Output` sections. If a section does not need details
+use the word `None` inside of it.
+
+`Globals` section provides all global variables that interact with the function.
+
+`Arguments` section provides the list of arguments to pass to the function. Use
+the parenthesis to indicate the position of the arguments:
+
+- `$1` : Argument is in position one.
+- `$2-` : Argument takes all args from position two up to the end.
+- `$@` : Argument takes all args.
+- `$3?` : Argument is optional.
+
+Variables defined in `Globals` and `Arguments` sections can have the following
+types:
+
+- `int` : Integer variable.
+- `str` : String variable (default).
+- `bool` : Bool variable.
+
+`Returns` section contains the exit status of the function.
+
+`Output` section describe the string printed to stdout.
+
+## Versioning ##
+
+* JuNest uses the following [semantic versioning](http://semver.org/)
+
+### Public API ###
+
+The public API refers to the following parts of JuNest system:
+
+- JuNest script CLI
+
+Any potential code change that cause backwards incompatible changes to the
+public API requires the major version to be incremented.
+
diff --git a/LICENSE b/LICENSE
index 6b156fe..610ce97 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,6 @@
-GNU GENERAL PUBLIC LICENSE
+
+
+ GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
@@ -631,8 +633,8 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
- {one line to give the program's name and a brief idea of what it does.}
- Copyright (C) {year} {name of author}
+ JuNest: The Arch Linux based distro that runs upon any Linux distros without root access
+ Copyright (C) 2014 Filippo Squillace
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -652,7 +654,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
- {project} Copyright (C) {year} {fullname}
+ JuNest Copyright (C) 2014 Filippo Squillace
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
@@ -673,3 +675,4 @@ the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
+
diff --git a/README.md b/README.md
index 0c6505d..1268f8b 100644
--- a/README.md
+++ b/README.md
@@ -1,120 +1,320 @@
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.
+
+
+
+
|Project Status|Donation|Communication|
|:------------:|:------:|:-----------:|
-| [](https://travis-ci.org/fsquillace/junest) [](https://www.openhub.net/p/junest) | [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8LEHQKBCYTACY) | [](https://gitter.im/fsquillace/junest?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://webchat.freenode.net/?channels=junest) [](https://groups.google.com/d/forum/junest) [](http://fsquillace.github.io/junest-site/feed.xml) |
+| [](https://app.travis-ci.com/github/fsquillace/junest) [](https://www.openhub.net/p/junest) | [](https://github.com/sponsors/fsquillace) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8LEHQKBCYTACY) [](https://www.buymeacoffee.com/fsquillace) | [](https://discord.gg/ttfBT7MKve) |
**Table of Contents**
- [Description](#description)
- [Quickstart](#quickstart)
- [Installation](#installation)
-- [Dependencies](#dependencies)
+- [Usage](#usage)
- [Advanced usage](#advanced-usage)
- [Internals](#internals)
- [Troubleshooting](#troubleshooting)
-- [More documentation](#more documentation)
-- [License](#license)
-- [Author](#author)
-- [WWW](#www)
+- [More documentation](#more-documentation)
+- [Contributing](#contributing)
+- [Donating](#donating)
+- [Authors](#authors)
Description
===========
-**JuNest** (Jailed User NEST) is a lightweight Arch Linux based distribution that allows to have
-an isolated GNU/Linux environment inside any generic host GNU/Linux OS
-and without the need to have root privileges for installing packages.
+**JuNest** (Jailed User Nest) is a lightweight Arch Linux based distribution
+that allows the creation of disposable and partially isolated GNU/Linux environments
+within any generic GNU/Linux host OS and without requiring root
+privileges to install packages.
-JuNest contains mainly the package managers (called [pacman](https://wiki.archlinux.org/index.php/Pacman) and [yaourt](https://wiki.archlinux.org/index.php/Yaourt)) that allows to access
+JuNest is built around [pacman](https://wiki.archlinux.org/index.php/Pacman),
+the Arch Linux package manager, which allows access
to a wide range of packages from the Arch Linux repositories.
-The main advantages on using JuNest are:
+The main advantages of using JuNest include:
- Install packages without root privileges.
-- Isolated environment in which you can install packages without affecting a production system.
-- Access to a wide range of packages in particular on GNU/Linux distros that may contain limited repositories (such as CentOS and RedHat).
-- Available for x86\_64, x86 and ARM architectures but you can build your own image from scratch too!
-- Run on a different architecture from the host OS via QEMU
-- All Arch Linux lovers can have their favourite distro everywhere!
+- Create partially isolated environments in which you can install packages without risking mishaps on production systems.
+- Access a wider range of packages, particularly on GNU/Linux distros with comparatively limited repositories (such as CentOS and Red Hat).
+- Run on a different architecture from the host OS via QEMU.
+- Available for `x86_64` and `arm` architectures but you can build your own image from scratch too!
+- All Arch Linux lovers can enjoy their favourite distro everywhere!
JuNest follows the [Arch Linux philosophy](https://wiki.archlinux.org/index.php/The_Arch_Way).
-Quickstart
-==========
-There are three different ways you can run JuNest:
+How different is JuNest from Docker and Vagrant?
+------------------------------------------------
+Although JuNest sounds similar to a virtualisation/Linux container-like system,
+JuNest is quite different from solutions like Docker or Vagrant.
+In fact, the purpose of JuNest is **not** to
+build a completely isolated environment but, conversely, to provide the ability to run
+programs as if they were running natively from the host OS. Almost everything is shared
+between the host OS and the JuNest sandbox (kernel, process subtree, network, mounting, etc)
+and only the root filesystem gets isolated
+(since the programs installed in JuNest need to reside elsewhere).
-- As normal user - Allow to make basic operations: ```junest```
-
-- As fakeroot - Allow to install/remove packages: ```junest -f```
-
-- As root - Allow to have fully root privileges inside JuNest environment (you need to be root for executing this): ```junest -r```
-
-If the JuNest image has not been downloaded yet, the script will download
-the image and will place it to the default directory ~/.junest.
-You can change the default directory by changing the environment variable *JUNEST\_HOME*.
-
-If you are new on Archlinux and you are not familiar with *pacman* package manager
-visit the [pacman rosetta page](https://wiki.archlinux.org/index.php/Pacman_Rosetta).
+This allows interaction between processes belonging to both host OS and JuNest.
+For example, you can install the `top` command in JuNest and use it to monitor
+processes belonging to the host OS.
Installation
============
-JuNest can works on GNU/Linux OS with kernel version greater or equal
-2.6.0 (JuNest was not tested on kernel versions older than this) on 64 bit, 32 bit and ARM architectures.
-## Method one (Recommended) ##
-Just clone the JuNest repo somewhere (for example in ~/junest):
+## Dependencies ##
+JuNest comes with a very short list of dependencies in order to be installed in most
+of GNU/Linux distributions.
+Before installing JuNest be sure that all dependencies are properly installed in your system:
- git clone git://github.com/fsquillace/junest ~/junest
- export PATH=~/junest/bin:$PATH
+- [bash (>=4.0)](https://www.gnu.org/software/bash/)
+- [GNU coreutils](https://www.gnu.org/software/coreutils/)
+
+## Installation from git repository ##
+Just clone the JuNest repo somewhere (for example in ~/.local/share/junest):
+
+```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://dl.dropboxusercontent.com/u/42449030/junest/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`:
-Dependencies
-============
-JuNest comes with a very short list of dependencies in order to be installed in most
-of GNU/Linux distributions. The needed executables in the host OS are:
+```sh
+junest setup
+```
-- bash
-- chown (for root access only)
-- ln
-- mkdir
-- rm
-- tar
-- uname
-- wget or curl
+The script will download the image from the repository and will place it to the default directory `~/.junest`.
-The minimum recommended linux kernel is 2.6.0+
+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.
+
+Linux namespaces based
+----------------------
+The [Linux namespaces](http://man7.org/linux/man-pages/man7/namespaces.7.html)
+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 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`, 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 groot`
+
+- 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
+----------------
+The following table shows the capabilities that each backend program is able to perform:
+
+| | QEMU | Root privileges required | Manage Official Packages | Manage AUR Packages | Portability | Support | User modes |
+| --- | ---- | ------------------------ | ------------------------ | ------------------- | ----------- | ------- | ---------- |
+| **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
==============
-
## 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, yaourt 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.
-To change the build directory just use the *JUNEST_TEMPDIR* (by default /tmp).
+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)
@@ -127,22 +327,36 @@ 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"
+```
-Check out the proot options with:
+Or using proot arguments:
- junest -p "--help"
+```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:
+
+```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
virtualize the process tree thanks to the [systemd container](https://wiki.archlinux.org/index.php/Systemd-nspawn).
The JuNest containter allows to run services inside the container that can be
@@ -152,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:
@@ -161,32 +377,19 @@ 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
-jchroot, 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 jchroot 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).
+## Automatic fallback for all the dependent host OS executables ##
+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.
+## Automatic building of the JuNest images ##
+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##
+## Static QEMU binaries ##
There are static QEMU binaries included in JuNest image that allows to run JuNest
in a different architecture from the host system. They are located in `/opt/qemu`
directory.
@@ -194,85 +397,40 @@ directory.
Troubleshooting
===============
-##Cannot use AUR repository##
+For Arch Linux related FAQs take a look at the [General troubleshooting page](https://wiki.archlinux.org/index.php/General_troubleshooting).
-> **Q**: Why do I get the following error when I try to install a package with yaourt?
+## Cannot use AUR repository ##
+
+> **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 yaourt 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
+ #> pacman -S base-devel
-##Kernel too old##
+> Remember to not install `core/sudo` as it conflicts with `junest/sudo-fake` package.
-> **Q**: Why do I get the error: "FATAL: kernel too old"?
+## Can't set user and group as root
-> **A**: This is because the executable from the precompiled package cannot
-> properly run if the kernel is old.
-> You may need to specify the PRoot *-k* option if the guest rootfs
-> requires a newer kernel version:
+> **Q**: In ns mode when installing package I get the following error:
- junest -p "-k 3.10"
+ warning: warning given when extracting /usr/file... (Can't set user=0/group=0 for
+ /usr/file...)
-> In order to check if an executable inside JuNest environment can be compatible
-> with the kernel of the host OS just use the *file* command, for instance:
+> **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.
- file ~/.junest/usr/bin/bash
- ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked
- (uses shared libs), for GNU/Linux 2.6.32,
- BuildID[sha1]=ec37e49e7188ff4030052783e61b859113e18ca6, stripped
+## Could not change the root directory in pacman
-> From the output you can see what is the minimum recommended Linux kernel version.
-
-##SUID permissions##
-> **Q**: Why I do not have permissions for ping?
-
- ping www.google.com
- ping: icmp open socket: Operation not permitted
-
-> **A**: The ping command uses *suid* permissions that allow to execute the command using
-> root privileges. The fakeroot mode is not able to execute a command set with suid,
-> and you may need to use root privileges. There are other few commands that
-> have *suid* permission, you can list the commands from your JuNest environment
-> with the following command:
-
- find /usr/bin -perm +4000
-
-##No characters are visible on a graphic application##
-
-> **Q**: Why I do not see any characters in the application I have installed?
-
-> **A**: This is probably because there are no
-> [fonts](https://wiki.archlinux.org/index.php/Font_Configuration) installed in
-> the system.
-
-> To quick fix this, you can just install a fonts package:
-
- pacman -S gnu-free-fonts
-
-##Differences between filesystem and package ownership##
-
-> **Q**: Why do I get warning when I install a package using root privileges?
-
- pacman -S systat
- ...
- warning: directory ownership differs on /usr/
- filesystem: 1000:100 package: 0:0
- ...
-
-> **A**: In these cases the package installation went smoothly anyway.
-> This should happen every time you install package with root privileges
-> since JuNest will try to preserve the JuNest environment by assigning ownership
-> of the files to the real user.
-
-##No servers configured for repository##
+## No servers configured for repository ##
> **Q**: Why I cannot install packages?
- pacman -S lsof
+ #> pacman -S lsof
Packages (1): lsof-4.88-2
Total Download Size: 0.09 MiB
@@ -286,35 +444,173 @@ Troubleshooting
> **A**: You need simply to update the mirrorlist file according to your location:
# Uncomment the repository line according to your location
- nano /etc/pacman.d/mirrorlist
- pacman -Syy
+ #> nano /etc/pacman.d/mirrorlist
+ #> pacman -Syy
+
+## Locate the package for a given file ##
+
+> **Q**: How do I find which package a certain file belongs to?
+
+> **A**: JuNest is a really small distro, therefore you frequently need to find
+> the package name for a certain file. `pkgfile` is an extremely useful package
+> that allows you to detect the package of a given file.
+> For instance, if you want to find the package name for the command `getopt`:
+
+ #> pacman -S pkgfile
+ #> pkgfile --update
+ $> 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"?
+
+> **A**: This is because the binaries from the precompiled package are
+> compiled for Linux kernel 2.6.32. When JuNest is started without further
+> options, it tries to run a shell from the JuNest chroot. The system sees that
+> the host OS kernel is too old and refuses to start the shell.
+
+> The solution is to present a higher "fake" kernel version to the JuNest
+> chroot. PRoot offers the *-k* option for this, and JuNest passes this option
+> on to PRoot when *-p* is prepended. For example, to fake a kernel version of
+> 3.10, issue the following command:
+
+ $> 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
+> trying to run binaries that were later installed in the JuNest chroot with
+> the `pacman` command.
+
+> In order to check if an executable inside JuNest chroot is compatible with
+> the kernel of the host OS just use the `file` command, for instance:
+
+ $> file ~/.junest/usr/bin/bash
+ ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked
+ (uses shared libs), for GNU/Linux 2.6.32,
+ BuildID[sha1]=ec37e49e7188ff4030052783e61b859113e18ca6, stripped
+
+> The output shows the minimum recommended Linux kernel version.
+
+## Kernel doesn't support private futexes ##
+
+> **Q**: Why do I get the warning: "kompat: this kernel doesn't support private
+> futexes and PRoot can't emulate them."?
+
+> **A**: This happens on older host OS kernels when the trick of showing a fake
+> kernel version to the JuNest chroot is applied (see above:
+> [Kernel too old](#kernel-too-old)).
+
+> The consequence of showing a fake kernel version to the JuNest chroot is that
+> in the background, PRoot needs to translate requests from applications in the
+> chroot to the old kernel of the host OS. Some of the newer kernel
+> functionality can be emulated, but private futexes cannot be translated.
+
+> Private Futexes were introduced in Linux kernel 2.6.22. Therefore, the above
+> problem likely appears on old Linux systems, for example RHEL5 systems, which
+> are based on Linux kernel 2.6.18. Many of the core tools like `which`, `man`,
+> or `vim` run without problems while others, especially XOrg-based programs,
+> are more likely to show the warning. These are also more likely to crash
+> unexpectedly.
+
+> Currently, there is no (easy) workaround for this. In order to be fully
+> compatible with kernels below 2.6.22, both the precompiled package from
+> JuNest and all software that is installed later needs to be compiled for this
+> kernel. Most likely this can only be achieved by building the needed software
+> packages from source, which kind of contradicts JuNest's distro-in-a-distro
+> philosophy.
+
+## SUID permissions ##
+> **Q**: Why I do not have permissions for ping?
+
+ $> ping www.google.com
+ ping: icmp open socket: Operation not permitted
+
+> **A**: The ping command uses *suid* permissions that allow to execute the command using
+> root privileges. The fakeroot mode is not able to execute a command set with suid,
+> and you may need to use root privileges. There are other few commands that
+> have *suid* permission, you can list the commands from your JuNest environment
+> with the following command:
+
+ $> find /usr/bin -perm /4000
+
+## No characters are visible on a graphic application ##
+
+> **Q**: Why I do not see any characters in the application I have installed?
+
+> **A**: This is probably because there are no
+> [fonts](https://wiki.archlinux.org/index.php/Font_Configuration) installed in
+> the system.
+
+> To quick fix this, you can just install a fonts package:
+
+ #> pacman -S gnu-free-fonts
+
+## Differences between filesystem and package ownership ##
+
+> **Q**: Why do I get warning when I install a package using root privileges?
+
+ #> pacman -S systat
+ ...
+ warning: directory ownership differs on /usr/
+ filesystem: 1000:100 package: 0:0
+ ...
+
+> **A**: In these cases the package installation went smoothly anyway.
+> This should happen every time you install package with root privileges
+> since JuNest will try to preserve the JuNest environment by assigning ownership
+> of the files to the real user.
+
+## Unprivileged user namespace disable at kernel compile time or kernel too old ##
+
+> **Q**: Why do I get this warning when I run JuNest via Linux namespaces?
+
+ $> 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. If you have root permissions try to enable it, otherwise try to use
+> Proot as backend program.
More documentation
==================
There are additional tutorials in the
[JuNest wiki page](https://github.com/fsquillace/junest/wiki).
-License
+Contributing
+============
+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)
+
+Donating
+========
+To sustain the project please consider funding by donations through
+the [GitHub Sponsors page](https://github.com/sponsors/fsquillace/).
+
+Authors
=======
-Copyright (c) 2012-2015
+JuNest was originally created in late 2014 by [Filippo Squillace (feel.sqoox@gmail.com)](https://github.com/fsquillace).
-This program is free software; you can redistribute it and/or modify it
-under the terms of the GNU Library General Public License as published
-by the Free Software Foundation; either version 2, or (at your option)
-any later version.
+Here is a list of [**really appreciated contributors**](https://github.com/fsquillace/junest/graphs/contributors)!
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-
-Author
-======
-Filippo Squillace
-
-WWW
-===
-https://github.com/fsquillace/junest
+[](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/0)[](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/1)[](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/2)[](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/3)[](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/4)[](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/5)[](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/6)[](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/7)
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..ef13716
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+7.4.10
diff --git a/bin/jchroot b/bin/jchroot
deleted file mode 100755
index 2ed810f..0000000
--- a/bin/jchroot
+++ /dev/null
@@ -1,112 +0,0 @@
-#!/bin/bash
-#
-# Copyright (c) 2012-2015
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of the GNU Library General Public License as published
-# by the Free Software Foundation; either version 2, or (at your option)
-# any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-#
-
-# This script is the simplified and portable version of arch-chroot
-# (https://wiki.archlinux.org/index.php/Change_root#Using_arch-chroot)
-
-set -e
-
-################################ IMPORTS ##################################
-source "$(dirname $0)/../lib/util.sh"
-
-################################ MAIN FUNCTIONS ###########################
-
-chroot_add_mount() {
- mount "$@" && CHROOT_ACTIVE_MOUNTS=("$2" "${CHROOT_ACTIVE_MOUNTS[@]}")
-}
-
-chroot_maybe_add_mount() {
- local cond=$1; shift
- if eval "$cond"; then
- chroot_add_mount "$@"
- fi
-}
-
-chroot_setup() {
- CHROOT_ACTIVE_MOUNTS=()
- [[ $(trap -p EXIT) ]] && die '(BUG): attempting to overwrite existing EXIT trap'
- trap 'chroot_teardown' EXIT
-
- chroot_maybe_add_mount "! mountpoint -q '$1'" "$1" "$1" --bind &&
- chroot_add_mount proc "$1/proc" -t proc -o nosuid,noexec,nodev &&
- chroot_add_mount sys "$1/sys" -t sysfs -o nosuid,noexec,nodev,ro &&
- chroot_add_mount udev "$1/dev" -t devtmpfs -o mode=0755,nosuid &&
- chroot_add_mount devpts "$1/dev/pts" -t devpts -o mode=0620,gid=5,nosuid,noexec &&
- chroot_add_mount shm "$1/dev/shm" -t tmpfs -o mode=1777,nosuid,nodev &&
- chroot_add_mount run "$1/run" -t tmpfs -o nosuid,nodev,mode=0755 &&
- chroot_add_mount tmp "$1/tmp" -t tmpfs -o mode=1777,atime,nodev,nosuid &&
- mkdir -p "$1/$HOME" &&
- chroot_add_mount $HOME "$1/$HOME" --bind
-
- mkdir -p "$1/run/lock"
-}
-
-chroot_teardown() {
- umount "${CHROOT_ACTIVE_MOUNTS[@]}"
- unset CHROOT_ACTIVE_MOUNTS
-}
-
-usage() {
- cat <.
-#
-JUNEST_BASE="$(dirname $0)/.."
+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")")"/..)}"
+
+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/wrappers.sh"
-source "${JUNEST_BASE}/lib/core.sh"
###################################
### General functions ###
###################################
-
usage() {
- echo -e "$NAME: $DESCRIPTION"
- echo -e "Usage: $CMD [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 "$NAME (v$(cat "$JUNEST_BASE"/VERSION)): $DESCRIPTION"
echo
- echo -e "Access options:"
- echo -e "-f, --fakeroot Run $NAME with fakeroot privileges"
- echo -e "-r, --root Run $NAME with root privileges"
- echo -e "-p, --proot-args Proot arguments"
+ echo -e "Usage: $CMD [action] [options] [--] [command]"
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 "-c, --check <${CMD}_script> Validate the env located in ${JUNEST_HOME}"
- echo -e " using ${CMD}_script. This will alterate the environment."
- echo -e "-s, --skip-root-tests Skip the root tests during the validation process."
+ echo -e "General:"
+ echo -e "-h, --help Show this help message"
+ echo -e "-V, --version Show the $NAME version"
+ echo
+ 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 " 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 $VERSION ($CODE_NAME): $DESCRIPTION"
- echo -e "Copyright (c) $COPYRIGHT $AUTHOR"
- echo -e "Homepage: $HOMEPAGE"
+ 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 || $OPT_CHECK
- then
- die "The build image option must be used exclusively"
- fi
- fi
- if $OPT_SKIP_ROOT_TEST
- then
- if $OPT_DELETE || $OPT_HELP || $OPT_VERSION || $OPT_SETUP_FROM_FILE || \
- $OPT_FAKEROOT || $OPT_ROOT
- then
- die "The skip root tests option must be used with either build image or check options"
- fi
- fi
- if $OPT_CHECK
- then
- if $OPT_DELETE || $OPT_HELP || $OPT_VERSION || $OPT_SETUP_FROM_FILE || \
- $OPT_FAKEROOT || $OPT_ROOT || $OPT_BUILD_IMAGE
- then
- die "The validation 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 || $OPT_CHECK
- 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 || $OPT_CHECK
- 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 || $OPT_CHECK
- 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 || $OPT_CHECK
- 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_PROOT_ARGS || $OPT_ARCH
- then
- if $OPT_BUILD_IMAGE || $OPT_DELETE || $OPT_HELP || \
- $OPT_ROOT || $OPT_VERSION || $OPT_DISABLE_VALIDATION || $OPT_CHECK
- 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 || $OPT_CHECK
- 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_PROOT_ARGS=false
- PROOT_ARGS=""
- OPT_ARCH=false
- ARCH_ARG=""
- OPT_BUILD_IMAGE=false
- OPT_DISABLE_VALIDATION=false
- OPT_CHECK=false
- CHECK_ARG=""
- OPT_SKIP_ROOT_TEST=false
- OPT_DELETE=false
- OPT_HELP=false
- OPT_VERSION=false
- for opt in "$@"
- 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 ;;
- -p|--proot-args) OPT_PROOT_ARGS=true ; shift ; PROOT_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 ;;
- -c|--check) OPT_CHECK=true ; shift ; CHECK_ARG=$1; shift ;;
- -s|--skip-root-tests) OPT_SKIP_ROOT_TEST=true ; shift ;;
- -d|--delete) OPT_DELETE=true ; shift ;;
- -h|--help) OPT_HELP=true ; shift ;;
- -v|--version) OPT_VERSION=true ; shift ;;
- --) shift ; break ;;
- -*) die "Invalid option $1" ;;
- *) break ;;
- esac
- done
+ # 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
- ARGS=()
- for arg in "$@"
- do
- ARGS+=("$arg")
- done
+ 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 execute_operation(){
- $OPT_HELP && usage && return
- $OPT_VERSION && version && return
+function _parse_root_opts() {
+ # Options:
+ BACKEND_ARGS=""
+ OPT_NO_COPY_FILES=false
+ BACKEND_COMMAND=""
- if $OPT_BUILD_IMAGE; then
- build_image_env $OPT_DISABLE_VALIDATION $OPT_SKIP_ROOT_TEST
- return
- elif $OPT_DELETE; then
- delete_env
- return
- elif $OPT_CHECK; then
- check_env "${JUNEST_HOME}" "${CHECK_ARG}" $OPT_SKIP_ROOT_TEST
- return
- fi
+ while [[ -n "$1" ]]
+ do
+ case "$1" in
+ -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
- 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."
- fi
-
- [ -z "${ARCH_ARG}" ] || \
- die "The option --arch cannot be specified since JuNest has already been downloaded in $JUNEST_HOME"
-
- if $OPT_FAKEROOT; then
- run_env_as_fakeroot "${PROOT_ARGS}" "${ARGS[@]}"
- elif $OPT_ROOT; then
- run_env_as_root "${ARGS[@]}"
- else
- run_env_as_user "${PROOT_ARGS}" "${ARGS[@]}"
- fi
+ ARGS=()
+ for arg in "$@"
+ do
+ ARGS+=("$arg")
+ done
}
-parse_arguments "$@"
-check_cli
-execute_operation
+function _parse_ns_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=()
+ 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
+ die "Error: The image is still not installed in $JUNEST_HOME. Run this first: $CMD setup"
+ fi
+
+ if $ACT_CREATE_WRAPPERS; then
+ # shellcheck disable=SC2086
+ create_wrappers $OPT_FORCE "$OPT_BIN_PATH"
+ exit
+ fi
+
+ local run_env
+ 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
+ elif $ACT_ROOT; then
+ run_env=run_env_as_chroot
+ fi
+
+ # 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 "$@"
+ execute_operation
+}
+
+main "$@"
# vim: set ts=4 sw=4 noet:
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
new file mode 100755
index 0000000..789206e
--- /dev/null
+++ b/lib/checks/check.sh
@@ -0,0 +1,102 @@
+#!/usr/bin/env bash
+# shellcheck disable=SC1091
+#
+# This modules is used for:
+# - Running checks against the building JuNest image
+# - Integration tests on JuNest script against different execution modes (i.e. -f, -u, -r modes)
+#
+# Dependencies:
+# - None
+#
+# vim: ft=sh
+
+set -ex
+
+
+RUN_ROOT_TESTS=false
+SKIP_AUR_TESTS=false
+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")")"/../..)}"
+
+source "${JUNEST_BASE}/lib/utils/utils.sh"
+source "${JUNEST_BASE}/lib/core/common.sh"
+
+info "Validating JuNest located in ${JUNEST_HOME}..."
+
+info "Initial JuNest setup..."
+# 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
+
+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..."
+# shellcheck disable=SC2086
+$SUDO pacman $PACMAN_OPTIONS -S ${repo_package1}
+tree -L 1
+# shellcheck disable=SC2086
+$SUDO pacman $PACMAN_OPTIONS -Rsn ${repo_package1}
+
+repo_package2=iftop
+info "Checking ${repo_package2} package from official repo..."
+# 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..."
+ 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.sh b/lib/core.sh
deleted file mode 100644
index bb95c80..0000000
--- a/lib/core.sh
+++ /dev/null
@@ -1,469 +0,0 @@
-#!/usr/bin/env bash
-#
-# Copyright (c) 2012-2015
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of the GNU Library General Public License as published
-# by the Free Software Foundation; either version 2, or (at your option)
-# any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-#
-
-# References:
-# https://wiki.archlinux.org/index.php/PKGBUILD
-# https://wiki.archlinux.org/index.php/Creating_Packages
-
-set -e
-
-################################ IMPORTS #################################
-source "$(dirname ${BASH_ARGV[0]})/util.sh"
-
-################################# VARIABLES ##############################
-
-NAME='JuNest'
-CMD='junest'
-VERSION='5.6.7'
-CODE_NAME='Nitida'
-DESCRIPTION='The Arch Linux based distro that runs upon any Linux distros without root access'
-AUTHOR='Filippo Squillace '
-HOMEPAGE="https://github.com/fsquillace/${CMD}"
-COPYRIGHT='2012-2015'
-
-
-if [ "$JUNEST_ENV" == "1" ]
-then
- die "Error: Nested ${NAME} environments are not allowed"
-elif [ ! -z $JUNEST_ENV ] && [ "$JUNEST_ENV" != "0" ]
-then
- die "The variable JUNEST_ENV is not properly set"
-fi
-
-[ -z ${JUNEST_HOME} ] && JUNEST_HOME=~/.${CMD}
-[ -z ${JUNEST_BASE} ] && JUNEST_BASE=${JUNEST_HOME}/opt/junest
-if [ -z ${JUNEST_TEMPDIR} ] || [ ! -d ${JUNEST_TEMPDIR} ]
-then
- JUNEST_TEMPDIR=/tmp
-fi
-
-# The update of the variable PATH ensures that the executables are
-# found on different locations
-PATH=/usr/bin:/bin:/usr/sbin:/sbin:$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
-
-ARCH_LIST=('x86_64' 'x86' 'arm')
-HOST_ARCH=$($UNAME -m)
-if [ $HOST_ARCH == "i686" ] || [ $HOST_ARCH == "i386" ]
-then
- ARCH="x86"
- LD_LIB="${JUNEST_HOME}/lib/ld-linux.so.2"
-elif [ $HOST_ARCH == "x86_64" ]
-then
- ARCH="x86_64"
- LD_LIB="${JUNEST_HOME}/lib64/ld-linux-x86-64.so.2"
-elif [[ $HOST_ARCH =~ .*(arm).* ]]
-then
- ARCH="arm"
- LD_LIB="${JUNEST_HOME}/lib/ld-linux-armhf.so.3"
-else
- die "Unknown architecture ${ARCH}"
-fi
-
-PROOT_LINK=http://static.proot.me/
-MAIN_REPO=https://dl.dropboxusercontent.com/u/42449030
-ENV_REPO=${MAIN_REPO}/${CMD}
-DEFAULT_MIRROR='https://mirrors.kernel.org/archlinux/$repo/os/$arch'
-
-ORIGIN_WD=$(pwd)
-
-################################ EXECUTABLES ##################################
-# This section contains all the executables needed for JuNest to run properly.
-# They are based on a fallback mechanism that tries to use the executable in
-# different locations in the host OS.
-
-# List of executables that are run inside JuNest:
-SH=("/bin/sh" "--login")
-
-# List of executables that are run in the host OS:
-PROOT_COMPAT="${JUNEST_HOME}/opt/proot/proot-${ARCH}"
-CHROOT=${JUNEST_BASE}/bin/jchroot
-CLASSIC_CHROOT="chroot"
-WGET="wget --no-check-certificate"
-CURL="curl -L -J -O -k"
-TAR=tar
-CHOWN="chown"
-
-LD_EXEC="$LD_LIB --library-path ${JUNEST_HOME}/usr/lib:${JUNEST_HOME}/lib"
-
-# The following functions attempt first to run the executable in the host OS.
-# As a last hope they try to run the same executable available in the JuNest
-# image.
-
-function ln_cmd(){
- ln $@ || $LD_EXEC ${JUNEST_HOME}/usr/bin/ln $@
-}
-
-function rm_cmd(){
- rm $@ || $LD_EXEC ${JUNEST_HOME}/usr/bin/rm $@
-}
-
-function chown_cmd(){
- $CHOWN $@ || $LD_EXEC ${JUNEST_HOME}/usr/bin/chown $@
-}
-
-function mkdir_cmd(){
- mkdir $@ || $LD_EXEC ${JUNEST_HOME}/usr/bin/mkdir $@
-}
-
-function proot_cmd(){
- local proot_args="$1"
- shift
- if ${PROOT_COMPAT} ${proot_args} "${SH[@]}" "-c" ":"
- then
- ${PROOT_COMPAT} ${proot_args} "${@}"
- elif PROOT_NO_SECCOMP=1 ${PROOT_COMPAT} ${proot_args} "${SH[@]}" "-c" ":"
- then
- PROOT_NO_SECCOMP=1 ${PROOT_COMPAT} ${proot_args} "${@}"
- else
- die "Error: Check if the ${CMD} arguments are correct and if the kernel is too old use the option ${CMD} -p \"-k 3.10\""
- fi
-}
-
-function download_cmd(){
- $WGET $@ || $CURL $@
-}
-
-function chroot_cmd(){
- $CHROOT "$@" || $CLASSIC_CHROOT "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/chroot "$@"
-}
-
-################################# MAIN FUNCTIONS ##############################
-
-function is_env_installed(){
- [ -d "$JUNEST_HOME" ] && [ "$(ls -A $JUNEST_HOME)" ] && return 0
- return 1
-}
-
-
-function _cleanup_build_directory(){
-# $1: maindir (optional) - str: build directory to get rid
- local maindir=$1
- builtin cd $ORIGIN_WD
- trap - QUIT EXIT ABRT KILL TERM INT
- rm_cmd -fr "$maindir"
-}
-
-
-function _prepare_build_directory(){
- trap - QUIT EXIT ABRT KILL TERM INT
- trap "rm_cmd -rf ${maindir}; die \"Error occurred when installing ${NAME}\"" EXIT QUIT ABRT KILL TERM INT
-}
-
-
-function _setup_env(){
- is_env_installed && die "Error: ${NAME} has been already installed in $JUNEST_HOME"
- mkdir_cmd -p "${JUNEST_HOME}"
- imagepath=$1
- $TAR -zxpf ${imagepath} -C ${JUNEST_HOME}
- mkdir_cmd -p ${JUNEST_HOME}/run/lock
- info "The default mirror URL is ${DEFAULT_MIRROR}."
- info "Remember to refresh the package databases from the server:"
- info " pacman -Syy"
- info "${NAME} installed successfully"
-}
-
-
-function setup_env(){
- local arch=$ARCH
- [ -z $1 ] || arch="$1"
- contains_element $arch "${ARCH_LIST[@]}" || \
- die "The architecture is not one of: ${ARCH_LIST[@]}"
-
- local maindir=$(TMPDIR=$JUNEST_TEMPDIR mktemp -d -t ${CMD}.XXXXXXXXXX)
- _prepare_build_directory
-
- info "Downloading ${NAME}..."
- builtin cd ${maindir}
- local imagefile=${CMD}-${arch}.tar.gz
- download_cmd ${ENV_REPO}/${imagefile}
-
- info "Installing ${NAME}..."
- _setup_env ${maindir}/${imagefile}
-
- _cleanup_build_directory ${maindir}
-}
-
-
-function setup_env_from_file(){
- local imagefile=$1
- [ ! -e ${imagefile} ] && die "Error: The ${NAME} image file ${imagefile} does not exist"
-
- info "Installing ${NAME} from ${imagefile}..."
- _setup_env ${imagefile}
-
- builtin cd $ORIGIN_WD
-}
-
-function run_env_as_root(){
- source ${JUNEST_HOME}/etc/junest/info
- [ "$JUNEST_ARCH" != "$ARCH" ] && \
- die "The host system architecture is not correct: $ARCH != $JUNEST_ARCH"
-
- local uid=$UID
- # SUDO_USER is more reliable compared to SUDO_UID
- [ -z $SUDO_USER ] || uid=$SUDO_USER:$SUDO_GID
-
- local main_cmd="${SH[@]}"
- [ "$1" != "" ] && main_cmd="$(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}; rm_cmd -f ${JUNEST_HOME}/etc/mtab" EXIT QUIT ABRT KILL TERM INT
-
- [ ! -e ${JUNEST_HOME}/etc/mtab ] && ln_cmd -s /proc/self/mounts ${JUNEST_HOME}/etc/mtab
-
- JUNEST_ENV=1 chroot_cmd "$JUNEST_HOME" "${SH[@]}" "-c" "${main_cmd}"
-}
-
-function _run_env_with_proot(){
- local proot_args="$1"
- shift
-
- 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
-}
-
-function _run_env_with_qemu(){
- local proot_args="$1"
- 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
-
- 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"
- fi
- shift
- _run_env_with_proot "$proot_args" "${@}"
-}
-
-function run_env_as_fakeroot(){
- (( EUID == 0 )) && \
- die "You cannot access with root privileges. Use --root option instead."
- [ ! -e ${JUNEST_HOME}/etc/mtab ] && ln_cmd -s /proc/self/mounts ${JUNEST_HOME}/etc/mtab
- _run_env_with_qemu "-S ${JUNEST_HOME} $1" "${@:2}"
-}
-
-function run_env_as_user(){
- (( EUID == 0 )) && \
- die "You cannot access with root privileges. Use --root option instead."
- [ -e ${JUNEST_HOME}/etc/mtab ] && rm_cmd -f ${JUNEST_HOME}/etc/mtab
- _run_env_with_qemu "-R ${JUNEST_HOME} $1" "${@:2}"
-}
-
-
-function delete_env(){
- ! ask "Are you sure to delete ${NAME} located in ${JUNEST_HOME}" "N" && return
- if mountpoint -q ${JUNEST_HOME}
- then
- info "There are mounted directories inside ${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}/*
- then
- info "${NAME} deleted in ${JUNEST_HOME}"
- else
- error "Error: Cannot delete ${NAME} in ${JUNEST_HOME}"
- fi
-}
-
-
-function _check_package(){
- if ! pacman -Qq $1 > /dev/null
- then
- die "Package $1 must be installed"
- fi
-}
-
-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 build_image_env(){
- umask 022
-
- # The function must runs on ArchLinux with non-root privileges.
- (( EUID == 0 )) && \
- die "You cannot build with root privileges."
-
- _check_package arch-install-scripts
- _check_package gcc
- _check_package package-query
- _check_package git
-
- local disable_validation=$1
- local skip_root_tests=$2
-
- 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
- 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
- sudo pacstrap -G -M -d ${maindir}/root pacman coreutils libunistring archlinux-keyring sed
- sudo bash -c "echo 'Server = $DEFAULT_MIRROR' >> ${maindir}/root/etc/pacman.d/mirrorlist"
-
- 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
-
- info "Generating the locales..."
- # sed command is required for locale-gen
- 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/jchroot ${maindir}/root locale-gen
- sudo bash -c "echo 'LANG = \"en_US.UTF-8\"' >> ${maindir}/root/etc/locale.conf"
-
- info "Generating the metadata info..."
- sudo mkdir ${maindir}/root/etc/${CMD}
- sudo bash -c "echo 'JUNEST_ARCH=$ARCH' > ${maindir}/root/etc/${CMD}/info"
-
- info "Installing compatibility binaries proot"
- sudo mkdir -p ${maindir}/root/opt/proot
- builtin cd ${maindir}/root/opt/proot
- for arch in ${ARCH_LIST[@]}
- do
- info "Downloading $PROOT_LINK/proot-$arch ..."
- sudo $CURL $PROOT_LINK/proot-$arch
- sudo chmod +x proot-$arch
- done
-
- info "Installing qemu static binaries"
- sudo mkdir -p ${maindir}/root/opt/qemu
- builtin cd ${maindir}/root/opt/qemu
- for arch in ${ARCH_LIST[@]}
- do
- if [ "$arch" != "$ARCH" ]
- then
- info "Downloading qemu-$ARCH-static-$arch ..."
- sudo $CURL ${MAIN_REPO}/qemu/$ARCH/qemu-$ARCH-static-$arch
- sudo chmod +x qemu-$ARCH-static-$arch
- fi
- done
-
- # 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"
- # Apply patches for yaourt and makepkg
- sudo mkdir -p ${maindir}/root/opt/yaourt/bin/
- sudo cp ${maindir}/root/usr/bin/yaourt ${maindir}/root/opt/yaourt/bin/
- sudo sed -i -e 's/"--asroot"//' ${maindir}/root/opt/yaourt/bin/yaourt
- sudo cp ${maindir}/root/usr/bin/makepkg ${maindir}/root/opt/yaourt/bin/
- sudo sed -i -e 's/EUID\s==\s0/false/' ${maindir}/root/opt/yaourt/bin/makepkg
- sudo bash -c "echo 'export PATH=/opt/yaourt/bin:\$PATH' > ${maindir}/root/etc/profile.d/${CMD}.sh"
- sudo chmod +x ${maindir}/root/etc/profile.d/${CMD}.sh
-
- info "Setting up the pacman keyring (this might take a while!)..."
- sudo ${maindir}/root/opt/junest/bin/jchroot ${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"
-
- sudo rm ${maindir}/root/var/cache/pacman/pkg/*
-
- mkdir -p ${maindir}/output
- builtin cd ${maindir}/output
- local imagefile="${CMD}-${ARCH}.tar.gz"
- info "Compressing image to ${imagefile}..."
- sudo $TAR -zcpf ${imagefile} -C ${maindir}/root .
-
- if ! $disable_validation
- then
- mkdir -p ${maindir}/root_test
- $TAR -zxpf ${imagefile} -C "${maindir}/root_test"
- check_env "${maindir}/root_test" "${maindir}/root_test/opt/${CMD}/bin/${CMD}" $skip_root_tests
- fi
-
- sudo cp ${maindir}/output/${imagefile} ${ORIGIN_WD}
-
- builtin cd ${ORIGIN_WD}
- trap - QUIT EXIT ABRT KILL TERM INT
- sudo rm -fr "$maindir"
-}
-
-function check_env(){
- local testdir=$1
- local cmd=$2
- local skip_root_tests=$3
- info "Validating ${NAME} located in ${testdir} using the ${cmd} script..."
- echo "Server = ${DEFAULT_MIRROR}" >> ${testdir}/etc/pacman.d/mirrorlist
- JUNEST_HOME=${testdir} ${cmd} -f pacman --noconfirm -Syy
-
- # Check most basic executables work
- $skip_root_tests || JUNEST_HOME=${testdir} sudo -E ${cmd} -r pacman -Qi pacman 1> /dev/null
- JUNEST_HOME=${testdir} ${cmd} -- pacman -Qi pacman 1> /dev/null
- JUNEST_HOME=${testdir} ${cmd} -f -- pacman -Qi pacman 1> /dev/null
- $skip_root_tests || JUNEST_HOME=${testdir} sudo -E ${cmd} -r yaourt -V 1> /dev/null
- JUNEST_HOME=${testdir} ${cmd} -- yaourt -V 1> /dev/null
- JUNEST_HOME=${testdir} ${cmd} -f -- yaourt -V 1> /dev/null
- $skip_root_tests || JUNEST_HOME=${testdir} sudo -E ${cmd} -r /opt/proot/proot-$ARCH --help 1> /dev/null
- JUNEST_HOME=${testdir} ${cmd} -- /opt/proot/proot-$ARCH --help 1> /dev/null
- JUNEST_HOME=${testdir} ${cmd} -f -- /opt/proot/proot-$ARCH --help 1> /dev/null
-
- local repo_package=tree
- info "Installing ${repo_package} package from official repo using proot..."
- JUNEST_HOME=${testdir} ${cmd} -f pacman --noconfirm -S ${repo_package}
- JUNEST_HOME=${testdir} ${cmd} tree
- JUNEST_HOME=${testdir} ${cmd} -f tree
-
- local repo_package=iftop
- info "Installing ${repo_package} package from official repo using root..."
- JUNEST_HOME=${testdir} ${cmd} -f pacman --noconfirm -S ${repo_package}
- $skip_root_tests || JUNEST_HOME=${testdir} sudo -E ${cmd} -r iftop -t -s 5
-
- JUNEST_HOME=${testdir} ${cmd} -f pacman --noconfirm -S base-devel
- local yaourt_package=tcptraceroute
- info "Installing ${yaourt_package} package from AUR repo using proot..."
- JUNEST_HOME=${testdir} ${cmd} -f -- yaourt -A --noconfirm -S ${yaourt_package}
- $skip_root_tests || JUNEST_HOME=${testdir} sudo -E ${cmd} -r tcptraceroute localhost
-
- info "Removing the previous packages..."
- JUNEST_HOME=${testdir} ${cmd} -f pacman --noconfirm -Rsn tcptraceroute tree iftop
-
-}
diff --git a/lib/core/build.sh b/lib/core/build.sh
new file mode 100644
index 0000000..65a9474
--- /dev/null
+++ b/lib/core/build.sh
@@ -0,0 +1,141 @@
+#!/usr/bin/env bash
+#
+# This module contains all build functionalities for JuNest.
+#
+# Dependencies:
+# - lib/utils/utils.sh
+# - lib/core/common.sh
+#
+# vim: ft=sh
+
+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 _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."
+
+ _prepare
+
+ local disable_validation=$1
+
+ 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..."
+ # All the essential executables (ln, mkdir, chown, etc) are in coreutils
+ # bwrap command belongs to bubblewrap
+ sudo pacstrap -G -M "${maindir}"/root pacman coreutils bubblewrap
+
+ 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
+
+ # 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 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/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!)..."
+ 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/*
+ # 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 || return 1
+ local imagefile="${CMD}-${ARCH}.tar.gz"
+ info "Compressing image to ${imagefile}..."
+ 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" "${JUNEST_BASE}"/lib/checks/check_all.sh
+ fi
+
+ sudo cp "${maindir}"/output/"${imagefile}" "${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
new file mode 100644
index 0000000..c2237a9
--- /dev/null
+++ b/lib/core/chroot.sh
@@ -0,0 +1,104 @@
+#!/usr/bin/env bash
+#
+# This module contains all chroot functionalities for JuNest.
+#
+# Dependencies:
+# - lib/utils/utils.sh
+# - lib/core/common.sh
+#
+# vim: ft=sh
+
+function _run_env_as_xroot(){
+ local cmd=$1
+ local backend_args="$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
+
+ 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
+ # shellcheck disable=SC2064
+ trap "[ -z $uid ] || chown_cmd -R ${uid} ${JUNEST_HOME};" EXIT QUIT ABRT TERM INT
+
+ if ! $no_copy_files
+ then
+ copy_common_files
+ fi
+
+ # shellcheck disable=SC2086
+ JUNEST_ENV=1 $cmd $backend_args "$JUNEST_HOME" "${DEFAULT_SH[@]}" "${args[@]}"
+}
+
+#######################################
+# Run JuNest as real root via GRoot command.
+#
+# Globals:
+# JUNEST_HOME (RO) : The JuNest home directory.
+# UID (RO) : The user ID.
+# SUDO_USER (RO) : The sudo user ID.
+# SUDO_GID (RO) : The sudo group ID.
+# DEFAULT_SH (RO) : Contains the default command to run in JuNest.
+# Arguments:
+# 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(){
+ 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 "$backend_command $bindings" "$backend_args" "$no_copy_files" "$@"
+}
+
+#######################################
+# Run JuNest as real root via chroot command.
+#
+# Globals:
+# JUNEST_HOME (RO) : The JuNest home directory.
+# UID (RO) : The user ID.
+# SUDO_USER (RO) : The sudo user ID.
+# SUDO_GID (RO) : The sudo group ID.
+# DEFAULT_SH (RO) : Contains the default command to run in JuNest.
+# Arguments:
+# 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(){
+ check_nested_env
+
+ 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
new file mode 100644
index 0000000..df79bec
--- /dev/null
+++ b/lib/core/common.sh
@@ -0,0 +1,335 @@
+#!/usr/bin/env bash
+# shellcheck disable=SC2034
+# shellcheck disable=SC1091
+#
+# This module contains all common functionalities for JuNest.
+#
+# Dependencies:
+# - lib/utils/utils.sh
+#
+# vim: ft=sh
+
+NAME='JuNest'
+CMD='junest'
+DESCRIPTION='The Arch Linux based distro that runs upon any Linux distros without root access'
+
+NOT_AVAILABLE_ARCH=102
+NOT_EXISTING_FILE=103
+ARCHITECTURE_MISMATCH=104
+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_TEMPDIR=${JUNEST_TEMPDIR:-/tmp}
+
+# The update of the variable PATH ensures that the executables are
+# found on different locations
+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"
+
+ARCH_LIST=('x86_64' 'x86' 'arm')
+HOST_ARCH=$($UNAME -m)
+# 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" ]]
+then
+ ARCH="x86_64"
+ LD_LIB="${JUNEST_HOME}/lib64/ld-linux-x86-64.so.2"
+elif [[ $HOST_ARCH =~ .*(arm).* ]] || [[ $HOST_ARCH == "aarch64" ]]
+then
+ ARCH="arm"
+ LD_LIB="${JUNEST_HOME}/lib/ld-linux-armhf.so.3"
+else
+ die "Unknown architecture ${HOST_ARCH}"
+fi
+
+MAIN_REPO=https://link.storjshare.io/s/jvb5tgarnjtt565fffa44spvyuga/junest-repo
+MAIN_REPO=https://pub-a2af2344e8554f6c807bc3db355ae622.r2.dev
+ENV_REPO=${MAIN_REPO}/${CMD}
+# shellcheck disable=SC2016
+DEFAULT_MIRROR='https://mirror.rackspace.com/archlinux/$repo/os/$arch'
+
+ORIGIN_WD=$(pwd)
+
+################## EXECUTABLES ################
+
+# This section contains all the executables needed for JuNest to run properly.
+# They are based on a fallback mechanism that tries to use the executable in
+# different locations in the host OS.
+
+# List of executables that are run inside JuNest:
+DEFAULT_SH=("/bin/sh" "--login")
+
+# List of executables that are run in the host OS:
+BWRAP="${JUNEST_HOME}/usr/bin/bwrap"
+PROOT="${JUNEST_HOME}/usr/bin/proot-${ARCH}"
+GROOT="${JUNEST_HOME}/usr/bin/groot"
+CLASSIC_CHROOT=chroot
+WGET="wget --content-disposition --no-check-certificate"
+CURL="curl -L -J -O -k"
+TAR="tar"
+CHOWN="chown"
+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"
+
+LD_EXEC="$LD_LIB --library-path ${JUNEST_HOME}/usr/lib:${JUNEST_HOME}/lib"
+
+# The following functions attempt first to run the executable in the host OS.
+# As a last hope they try to run the same executable available in the JuNest
+# image.
+
+function ln_cmd(){
+ $LN "$@" || $LD_EXEC "${JUNEST_HOME}"/usr/bin/$LN "$@"
+}
+
+function getent_cmd(){
+ $GETENT "$@" || $LD_EXEC "${JUNEST_HOME}"/usr/bin/$GETENT "$@"
+}
+
+function cp_cmd(){
+ $CP "$@" || $LD_EXEC "${JUNEST_HOME}"/usr/bin/$CP "$@"
+}
+
+function rm_cmd(){
+ $RM "$@" || $LD_EXEC "${JUNEST_HOME}"/usr/bin/$RM "$@"
+}
+
+function chown_cmd(){
+ $CHOWN "$@" || $LD_EXEC "${JUNEST_HOME}"/usr/bin/$CHOWN "$@"
+}
+
+function mkdir_cmd(){
+ $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 "$@"
+}
+
+function download_cmd(){
+ $WGET "$@" || $CURL "$@"
+}
+
+function chroot_cmd(){
+ $CLASSIC_CHROOT "$@" || $LD_EXEC "${JUNEST_HOME}"/usr/bin/$CLASSIC_CHROOT "$@"
+}
+
+function unshare_cmd(){
+ # Most of the distros do not have the `unshare` command updated
+ # 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 DEFAULT_SH arguments (i.e. --login) as
+ # they are not supported by dash.
+ if $LD_EXEC "${JUNEST_HOME}"/usr/bin/$UNSHARE --user "${DEFAULT_SH[0]}" "-c" ":"
+ then
+ $LD_EXEC "${JUNEST_HOME}"/usr/bin/$UNSHARE "${@}"
+ elif $UNSHARE --user "${DEFAULT_SH[0]}" "-c" ":"
+ then
+ $UNSHARE "$@"
+ else
+ 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
+ # 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} "${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"
+ fi
+}
+
+############## COMMON FUNCTIONS ###############
+
+#######################################
+# Check if the executable is being running inside a JuNest environment.
+#
+# Globals:
+# JUNEST_ENV (RO) : The boolean junest env check
+# NESTED_ENVIRONMENT (RO) : The nest env exception
+# VARIABLE_NOT_SET (RO) : The var not set exception
+# NAME (RO) : The JuNest name
+# Arguments:
+# None
+# Returns:
+# VARIABLE_NOT_SET : If no JUNEST_ENV is not properly set
+# NESTED_ENVIRONMENT : If the script is executed inside JuNest env
+# Output:
+# None
+#######################################
+function check_nested_env() {
+ if [[ $JUNEST_ENV == "1" ]]
+ then
+ die_on_status $NESTED_ENVIRONMENT "Error: Nested ${NAME} environments are not allowed"
+ elif [[ -n $JUNEST_ENV ]] && [[ $JUNEST_ENV != "0" ]]
+ then
+ die_on_status $VARIABLE_NOT_SET "The variable JUNEST_ENV is not properly set"
+ fi
+}
+
+#######################################
+# Check if the architecture between Host OS and Guest OS is the same.
+#
+# Globals:
+# JUNEST_HOME (RO) : The JuNest home path.
+# ARCHITECTURE_MISMATCH (RO) : The arch mismatch exception
+# ARCH (RO) : The host OS arch
+# JUNEST_ARCH (RO) : The JuNest arch
+# Arguments:
+# None
+# Returns:
+# ARCHITECTURE_MISMATCH : If arch between host and guest is not the same
+# Output:
+# None
+#######################################
+function check_same_arch() {
+ 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
+}
+
+#######################################
+# Provide the proot common binding options for both normal user and fakeroot.
+# The list of bindings can be found in `proot --help`. This function excludes
+# /etc/mtab file so that it will not give conflicts with the related
+# symlink in the image.
+#
+# Globals:
+# HOME (RO) : The home directory.
+# RESULT (WO) : Contains the binding options.
+# Arguments:
+# None
+# Returns:
+# None
+# Output:
+# None
+#######################################
+function provide_common_bindings(){
+ RESULT=""
+ local re='(.*):.*'
+ for bind in "/dev" "/sys" "/proc" "/tmp" "$HOME" "/run/user/$($ID -u)"
+ do
+ if [[ $bind =~ $re ]]
+ then
+ [ -e "${BASH_REMATCH[1]}" ] && RESULT="-b $bind $RESULT"
+ else
+ [ -e "$bind" ] && RESULT="-b $bind $RESULT"
+ fi
+ done
+ return 0
+}
+
+#######################################
+# Build passwd and group files using getent command.
+# If getent fails the function fallbacks by copying the content from /etc/passwd
+# and /etc/group.
+#
+# The generated passwd and group will be stored in $JUNEST_HOME/etc/junest.
+#
+# Globals:
+# JUNEST_HOME (RO) : The JuNest home directory.
+# Arguments:
+# None
+# Returns:
+# None
+# Output:
+# None
+#######################################
+function copy_passwd_and_group(){
+ # Enumeration of users/groups is disabled/limited depending on how nsswitch.conf
+ # 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
+ then
+ warn "getent command failed or does not exist. Binding directly from /etc/passwd."
+ copy_file /etc/passwd
+ fi
+
+ 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
+ fi
+ return 0
+}
+
+function copy_file() {
+ local file="${1}"
+ # -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
+}
+
+function copy_common_files() {
+ copy_file /etc/host.conf
+ copy_file /etc/hosts
+ copy_file /etc/nsswitch.conf
+ 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
new file mode 100644
index 0000000..70763bd
--- /dev/null
+++ b/lib/core/namespace.sh
@@ -0,0 +1,165 @@
+#!/usr/bin/env bash
+#
+# This module contains functionalities for accessing to JuNest via bubblewrap.
+#
+# https://github.com/containers/bubblewrap
+#
+# Dependencies:
+# - lib/utils/utils.sh
+# - lib/core/common.sh
+#
+# 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
+ config_file=$CONFIG_PROC_FILE
+ elif [[ -e $CONFIG_BOOT_FILE ]]
+ then
+ config_file=$CONFIG_BOOT_FILE
+ else
+ return "$NOT_EXISTING_FILE"
+ fi
+
+ # `-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"
+ 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 "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
+}
+
+
+#######################################
+# Run JuNest as fakeroot 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.
+# $ROOT_ACCESS_ERROR : If the user is the real root.
+# Output:
+# - : The command output.
+#######################################
+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
+
+ if ! $no_copy_files
+ then
+ copy_common_files
+ fi
+
+ 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
new file mode 100644
index 0000000..b6c1c8f
--- /dev/null
+++ b/lib/core/proot.sh
@@ -0,0 +1,138 @@
+#!/usr/bin/env bash
+# shellcheck disable=SC1091
+#
+# This module contains all proot functionalities for JuNest.
+#
+# Dependencies:
+# - lib/utils/utils.sh
+# - lib/core/common.sh
+#
+# vim: ft=sh
+
+function _run_env_with_proot(){
+ local backend_command="${1:-$PROOT}"
+ local backend_args="$2"
+ shift 2
+
+ 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 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
+ # 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}/bin/${qemu_bin}" "${qemu_symlink}"
+ backend_args="-q ${qemu_symlink} $backend_args"
+ fi
+
+ _run_env_with_proot "${backend_command}" "$backend_args" "${@}"
+}
+
+#######################################
+# Run JuNest as fakeroot.
+#
+# Globals:
+# JUNEST_HOME (RO) : The JuNest home directory.
+# EUID (RO) : The user ID.
+# DEFAULT_SH (RO) : Contains the default command to run in JuNest.
+# Arguments:
+# backend_args ($1) : The arguments to pass to proot
+# 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_proot_fakeroot(){
+ (( EUID == 0 )) && \
+ die_on_status "$ROOT_ACCESS_ERROR" "You cannot access with root privileges. Use --groot option instead."
+ check_nested_env
+
+ 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}
+ unset RESULT
+
+ # An alternative is via -S option:
+ #_run_env_with_qemu "-S ${JUNEST_HOME} $1" "${@:2}"
+ _run_env_with_qemu "$backend_command" "-0 ${bindings} -r ${JUNEST_HOME} $backend_args" "$@"
+}
+
+#######################################
+# Run JuNest as normal user.
+#
+# Globals:
+# JUNEST_HOME (RO) : The JuNest home directory.
+# EUID (RO) : The user ID.
+# DEFAULT_SH (RO) : Contains the default command to run in JuNest.
+# Arguments:
+# backend_args ($1) : The arguments to pass to proot
+# 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_proot_user(){
+ (( EUID == 0 )) && \
+ die_on_status "$ROOT_ACCESS_ERROR" "You cannot access with root privileges. Use --groot option instead."
+ check_nested_env
+
+ 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 "$backend_command" "${bindings} -r ${JUNEST_HOME} $backend_args" "$@"
+}
diff --git a/lib/core/setup.sh b/lib/core/setup.sh
new file mode 100644
index 0000000..58c6122
--- /dev/null
+++ b/lib/core/setup.sh
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+#
+# This module contains all setup functionalities for JuNest.
+#
+# Dependencies:
+# - lib/utils/utils.sh
+# - lib/core/common.sh
+#
+# vim: ft=sh
+
+#######################################
+# Check if the JuNest system is installed in JUNEST_HOME.
+#
+# Globals:
+# JUNEST_HOME (RO) : Contains the JuNest home directory.
+# Arguments:
+# None
+# Returns:
+# 0 : If JuNest is installed
+# 1 : If JuNest is not installed
+# Output:
+# None
+#######################################
+function is_env_installed(){
+ [[ -d "$JUNEST_HOME" ]] && [[ "$(ls -A "$JUNEST_HOME")" ]] && return 0
+ return 1
+}
+
+
+function _cleanup_build_directory(){
+ local maindir=$1
+ check_not_null "$maindir"
+ builtin cd "$ORIGIN_WD" || return 1
+ trap - QUIT EXIT ABRT KILL TERM INT
+ rm_cmd -fr "$maindir"
+}
+
+
+function _prepare_build_directory(){
+ local maindir=$1
+ check_not_null "$maindir"
+ trap - QUIT EXIT ABRT KILL TERM INT
+ # shellcheck disable=SC2064
+ trap "rm_cmd -rf ${maindir}; die \"Error occurred when installing ${NAME}\"" EXIT QUIT ABRT TERM INT
+}
+
+
+function _setup_env(){
+ local imagepath=$1
+ check_not_null "$imagepath"
+
+ 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 "${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"
+ echo
+ info "To install packages from AUR follow the wiki here:"
+ info "https://github.com/fsquillace/junest#install-packages-from-aur"
+}
+
+
+#######################################
+# Setup JuNest.
+#
+# Globals:
+# JUNEST_HOME (RO) : The JuNest home directory in which JuNest needs
+# to be installed.
+# ARCH (RO) : The host architecture.
+# JUNEST_TEMPDIR (RO) : The JuNest temporary directory for building
+# the JuNest system from the image.
+# ENV_REPO (RO) : URL of the site containing JuNest images.
+# NAME (RO) : The JuNest name.
+# Arguments:
+# arch ($1?) : The JuNest architecture image to download.
+# Defaults to the host architecture
+# Returns:
+# $NOT_AVAILABLE_ARCH : If the architecture is not one of the available ones.
+# Output:
+# None
+#######################################
+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[*]}"
+
+ local maindir
+ maindir=$(TMPDIR=$JUNEST_TEMPDIR mktemp -d -t "${CMD}".XXXXXXXXXX)
+ _prepare_build_directory "$maindir"
+
+ info "Downloading ${NAME}..."
+ builtin cd "${maindir}" || return 1
+ local imagefile=${CMD}-${arch}.tar.gz
+ download_cmd "${ENV_REPO}/${imagefile}"
+
+ info "Installing ${NAME}..."
+ _setup_env "${maindir}/${imagefile}"
+
+ _cleanup_build_directory "${maindir}"
+}
+
+#######################################
+# Setup JuNest from file.
+#
+# Globals:
+# JUNEST_HOME (RO) : The JuNest home directory in which JuNest needs
+# to be installed.
+# NAME (RO) : The JuNest name.
+# Arguments:
+# imagefile ($1) : The JuNest image file.
+# Returns:
+# $NOT_EXISTING_FILE : If the image file does not exist.
+# Output:
+# None
+#######################################
+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"
+
+ info "Installing ${NAME} from ${imagefile}..."
+ _setup_env "${imagefile}"
+}
+
+#######################################
+# Remove an existing JuNest system.
+#
+# Globals:
+# JUNEST_HOME (RO) : The JuNest home directory to remove.
+# Arguments:
+# None
+# Returns:
+# None
+# Output:
+# None
+#######################################
+function delete_env(){
+ ! ask "Are you sure to delete ${NAME} located in ${JUNEST_HOME}" "N" && return
+ if mountpoint -q "${JUNEST_HOME}"
+ then
+ info "There are mounted directories inside ${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}"
+ then
+ info "${NAME} deleted in ${JUNEST_HOME}"
+ else
+ error "Error: Cannot delete ${NAME} in ${JUNEST_HOME}"
+ fi
+}
+
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/util.sh b/lib/util.sh
deleted file mode 100644
index 7f45102..0000000
--- a/lib/util.sh
+++ /dev/null
@@ -1,93 +0,0 @@
-#!/usr/bin/env bash
-#
-# This file is part of JuNest (https://github.com/fsquillace/junest)
-#
-# Copyright (c) 2012-2015
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of the GNU Library General Public License as published
-# by the Free Software Foundation; either version 2, or (at your option)
-# any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-function echoerr(){
- echo "$@" 1>&2
-}
-function die(){
-# $@: msg (mandatory) - str: Message to print
- error $@
- exit 1
-}
-function error(){
-# $@: msg (mandatory) - str: Message to print
- echoerr -e "\033[1;31m$@\033[0m"
-}
-function warn(){
-# $@: msg (mandatory) - str: Message to print
- echoerr -e "\033[1;33m$@\033[0m"
-}
-function info(){
-# $@: msg (mandatory) - str: Message to print
- echo -e "\033[1;37m$@\033[0m"
-}
-
-function ask(){
- # $1: question string
- # $2: default value - can be either Y, y, N, n (by default Y)
-
- local default="Y"
- [ -z $2 ] || default=$(echo "$2" | tr '[:lower:]' '[:upper:]')
-
- local other="n"
- [ "$default" == "N" ] && other="y"
-
- local prompt="$1 (${default}/${other})> "
-
- local res="none"
- while [ "$res" != "Y" ] && [ "$res" != "N" ] && [ "$res" != "" ];
- do
- read -p "$prompt" res
- res=$(echo "$res" | tr '[:lower:]' '[:upper:]')
- done
-
- [ "$res" == "" ] && res="$default"
-
- if [ "$res" == "Y" ]
- then
- return 0
- else
- return 1
- fi
-
-}
-
-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=''
- whitespace="[[:space:]]"
- for i in "$@"
- do
- if [[ $i =~ $whitespace ]]
- then
- C="$C \"$i\""
- else
- C="$C $i"
- fi
- done
- echo $C
-}
-
-contains_element () {
- local e
- for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
- return 1
-}
diff --git a/lib/utils/utils.sh b/lib/utils/utils.sh
new file mode 100644
index 0000000..5659568
--- /dev/null
+++ b/lib/utils/utils.sh
@@ -0,0 +1,205 @@
+#!/usr/bin/env bash
+
+NULL_EXCEPTION=11
+WRONG_ANSWER=33
+
+#######################################
+# Check if the argument is null.
+#
+# Globals:
+# None
+# Arguments:
+# argument ($1) : Argument to check.
+# Returns:
+# 0 : If argument is not null.
+# NULL_EXCEPTION : If argument is null.
+# Output:
+# None
+#######################################
+function check_not_null() {
+ [ -z "$1" ] && { error "Error: null argument $1"; return $NULL_EXCEPTION; }
+ return 0
+}
+
+#######################################
+# Redirect message to stderr.
+#
+# Globals:
+# None
+# Arguments:
+# msg ($@): Message to print.
+# Returns:
+# None
+# Output:
+# Message printed to stderr.
+#######################################
+function echoerr() {
+ echo "$@" 1>&2;
+}
+
+#######################################
+# Print an error message to stderr and exit program.
+#
+# Globals:
+# None
+# Arguments:
+# msg ($@) : Message to print.
+# Returns:
+# 1 : The unique exit status printed.
+# Output:
+# Message printed to stderr.
+#######################################
+function die() {
+ error "$@"
+ exit 1
+}
+
+#######################################
+# Print an error message to stderr and exit program with a given status.
+#
+# Globals:
+# None
+# Arguments:
+# status ($1) : The exit status to use.
+# msg ($2-) : Message to print.
+# Returns:
+# $? : The $status exit status.
+# Output:
+# Message printed to stderr.
+#######################################
+function die_on_status() {
+ status=$1
+ shift
+ error "$@"
+ exit "$status"
+}
+
+#######################################
+# Print an error message to stderr.
+#
+# Globals:
+# None
+# Arguments:
+# msg ($@): Message to print.
+# Returns:
+# None
+# Output:
+# Message printed to stderr.
+#######################################
+function error() {
+ echoerr -e "\033[1;31m$*\033[0m"
+}
+
+#######################################
+# Print a warn message to stderr.
+#
+# Globals:
+# None
+# Arguments:
+# msg ($@): Message to print.
+# Returns:
+# None
+# Output:
+# Message printed to stderr.
+#######################################
+function warn() {
+ # $@: msg (mandatory) - str: Message to print
+ echoerr -e "\033[1;33m$*\033[0m"
+}
+
+#######################################
+# Print an info message to stdout.
+#
+# Globals:
+# None
+# Arguments:
+# msg ($@): Message to print.
+# Returns:
+# None
+# Output:
+# Message printed to stdout.
+#######################################
+function info(){
+ echo -e "\033[1;36m$*\033[0m"
+}
+
+#######################################
+# Ask a question and wait to receive an answer from stdin.
+# It returns $default_answer if no answer has be received from stdin.
+#
+# Globals:
+# None
+# Arguments:
+# question ($1) : The question to ask.
+# default_answer ($2) : Possible values: 'Y', 'y', 'N', 'n' (default: 'Y')
+# Returns:
+# 0 : If user replied with either 'Y' or 'y'.
+# 1 : If user replied with either 'N' or 'n'.
+# WRONG_ANSWER : If `default_answer` is not one of the possible values.
+# Output:
+# Print the question to ask.
+#######################################
+function ask(){
+ local question=$1
+ local default_answer=$2
+ check_not_null "$question"
+
+ 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; }
+ fi
+
+ local default="Y"
+ [ -z "$default_answer" ] || default=$(echo "$default_answer" | tr '[:lower:]' '[:upper:]')
+
+ local other="n"
+ [ "$default" == "N" ] && other="y"
+
+ local prompt
+ prompt=$(info "$question (${default}/${other})> ")
+
+ local res="none"
+ while [ "$res" != "Y" ] && [ "$res" != "N" ] && [ "$res" != "" ];
+ do
+ read -r -p "$prompt" res
+ res=$(echo "$res" | tr '[:lower:]' '[:upper:]')
+ done
+
+ [ "$res" == "" ] && res="$default"
+
+ [ "$res" == "Y" ]
+}
+
+function insert_quotes_on_spaces(){
+# It inserts quotes between arguments.
+# Useful to preserve quotes on command
+# to be used inside sh -c/bash -c
+ local C=""
+ whitespace="[[:space:]]"
+ for i in "$@"
+ do
+ if [[ $i =~ $whitespace ]]
+ then
+ temp_C="\"$i\""
+ else
+ 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"
+}
+
+contains_element () {
+ local e
+ for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
+ return 1
+}
diff --git a/tests/checkstyle/checkstyle.sh b/tests/checkstyle/checkstyle.sh
new file mode 100755
index 0000000..4f71965
--- /dev/null
+++ b/tests/checkstyle/checkstyle.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+# shellcheck disable=SC1091
+
+source "$(dirname "$0")/../utils/utils.sh"
+
+# Disable the exiterr
+set +e
+
+function oneTimeSetUp(){
+ setUpUnitTests
+}
+
+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")"
+}
+
+source "$(dirname "$0")"/../utils/shunit2
diff --git a/tests/test_all.sh b/tests/test_all.sh
deleted file mode 100755
index 48f9262..0000000
--- a/tests/test_all.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-tests_succeded=true
-for tst in $(ls $(dirname $0)/test_* | grep -v $(basename $0))
-do
- $tst || tests_succeded=false
-done
-
-$tests_succeded
diff --git a/tests/test_cli.sh b/tests/test_cli.sh
deleted file mode 100755
index 46d38d1..0000000
--- a/tests/test_cli.sh
+++ /dev/null
@@ -1,202 +0,0 @@
-#!/bin/bash
-source $(dirname $0)/../bin/junest -h &> /dev/null
-
-# Disable the exiterr
-set +e
-
-function setUp(){
- function is_env_installed(){
- return 0
- }
-}
-
-## Mock functions ##
-function usage(){
- echo "usage"
-}
-function version(){
- echo "version"
-}
-function build_image_env(){
- local disable_validation=$1
- local skip_root_tests=$2
- echo "build_image_env($disable_validation,$skip_root_tests)"
-}
-function check_env(){
- local env_home=$1
- local cmd_script=$2
- local skip_root_tests=$3
- echo "check_env($env_home,$cmd_script,$skip_root_tests)"
-}
-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 proot_args="$1"
- shift
- echo "run_env_as_fakeroot($proot_args,$@)"
-}
-function run_env_as_root(){
- echo "run_env_as_root $@"
-}
-function run_env_as_user(){
- local proot_args="$1"
- shift
- echo "run_env_as_user($proot_args,$@)"
-}
-
-function wrap_env(){
- parse_arguments "$@"
- check_cli
- execute_operation
-}
-
-function test_help(){
- local output=$(wrap_env -h)
- assertEquals $output "usage"
- local output=$(wrap_env --help)
- assertEquals $output "usage"
-}
-function test_version(){
- local output=$(wrap_env -v)
- assertEquals $output "version"
- local output=$(wrap_env --version)
- assertEquals $output "version"
-}
-function test_build_image_env(){
- local output=$(wrap_env -b)
- assertEquals $output "build_image_env(false,false)"
- local output=$(wrap_env --build-image)
- assertEquals $output "build_image_env(false,false)"
- local output=$(wrap_env -b -s)
- assertEquals $output "build_image_env(false,true)"
- local output=$(wrap_env -b -n)
- assertEquals $output "build_image_env(true,false)"
- local output=$(wrap_env -b -n -s)
- assertEquals $output "build_image_env(true,true)"
- local output=$(wrap_env --build-image --disable-validation --skip-root-tests)
- assertEquals $output "build_image_env(true,true)"
-}
-function test_check_env(){
- local output=$(wrap_env -c myscript)
- assertEquals $output "check_env(${JUNEST_HOME},myscript,false)"
- local output=$(wrap_env --check myscript)
- assertEquals $output "check_env(${JUNEST_HOME},myscript,false)"
- local output=$(wrap_env -c myscript -s)
- assertEquals $output "check_env(${JUNEST_HOME},myscript,true)"
- local output=$(wrap_env --check myscript --skip-root-tests)
- assertEquals $output "check_env(${JUNEST_HOME},myscript,true)"
-}
-function test_delete_env(){
- local output=$(wrap_env -d)
- assertEquals $output "delete_env"
- local output=$(wrap_env --delete)
- assertEquals $output "delete_env"
-}
-#function test_setup_env_from_file(){
- #local output=$(wrap_env -i myimage)
- #assertEquals $output "setup_env_from_file(myimage)"
- #local output=$(wrap_env --setup-from-file myimage)
- #assertEquals $output "setup_env_from_file(myimage)"
-#}
-function test_setup_env_from_file(){
- is_env_installed(){
- return 1
- }
- local output=$(wrap_env -i myimage)
- assertEquals "$output" "$(echo -e "setup_env_from_file(myimage)\nrun_env_as_user(,)")"
- local output=$(wrap_env --setup-from-file myimage)
- assertEquals "$output" "$(echo -e "setup_env_from_file(myimage)\nrun_env_as_user(,)")"
-
- is_env_installed(){
- return 0
- }
- $(wrap_env -i myimage 2> /dev/null)
- assertEquals 1 $?
-}
-
-function test_setup_env(){
- is_env_installed(){
- return 1
- }
- local output=$(wrap_env -a arm)
- assertEquals "$output" "$(echo -e "setup_env(arm)\nrun_env_as_user(,)")"
- local output=$(wrap_env --arch arm)
- assertEquals "$output" "$(echo -e "setup_env(arm)\nrun_env_as_user(,)")"
- local output=$(wrap_env)
- assertEquals "$output" "$(echo -e "setup_env()\nrun_env_as_user(,)")"
-
- is_env_installed(){
- return 0
- }
- $(wrap_env -a arm 2> /dev/null)
- assertEquals 1 $?
-}
-function test_run_env_as_fakeroot(){
- local output=$(wrap_env -f)
- assertEquals $output "run_env_as_fakeroot(,)"
- local output=$(wrap_env --fakeroot)
- assertEquals $output "run_env_as_fakeroot(,)"
-
- local output=$(wrap_env -f -p "-b arg")
- assertEquals "${output[@]}" "run_env_as_fakeroot(-b arg,)"
- local output=$(wrap_env -f -p "-b arg" -- command -kv)
- assertEquals "${output[@]}" "run_env_as_fakeroot(-b arg,command -kv)"
- local output=$(wrap_env -f command --as)
- assertEquals "${output[@]}" "run_env_as_fakeroot(,command --as)"
- $(wrap_env -a "myarch" -f command --as 2> /dev/null)
- assertEquals 1 $?
-}
-function test_run_env_as_user(){
- local output=$(wrap_env)
- assertEquals $output "run_env_as_user(,)"
-
- local output=$(wrap_env -p "-b arg")
- assertEquals "$output" "run_env_as_user(-b arg,)"
- local output=$(wrap_env -p "-b arg" -- command -ll)
- assertEquals "$output" "run_env_as_user(-b arg,command -ll)"
- local output=$(wrap_env command -ls)
- assertEquals "$output" "run_env_as_user(,command -ls)"
- $(wrap_env -a "myarch" -- command -ls 2> /dev/null)
- assertEquals 1 $?
-}
-function test_run_env_as_root(){
- local output=$(wrap_env -r)
- assertEquals $output "run_env_as_root"
-
- local output=$(wrap_env -r command)
- assertEquals "${output[@]}" "run_env_as_root command"
-}
-
-function test_check_cli(){
- $(wrap_env -b -h 2> /dev/null)
- assertEquals $? 1
- $(wrap_env -b -c 2> /dev/null)
- assertEquals $? 1
- $(wrap_env -d -s 2> /dev/null)
- assertEquals $? 1
- $(wrap_env -n -v 2> /dev/null)
- assertEquals $? 1
- $(wrap_env -d -r 2> /dev/null)
- assertEquals $? 1
- $(wrap_env -h -f 2> /dev/null)
- assertEquals $? 1
- $(wrap_env -v -i fsd 2> /dev/null)
- assertEquals $? 1
- $(wrap_env -f -r 2> /dev/null)
- assertEquals $? 1
- $(wrap_env -p args -v 2> /dev/null)
- assertEquals $? 1
- $(wrap_env -a arch -v 2> /dev/null)
- assertEquals $? 1
- $(wrap_env -d args 2> /dev/null)
- assertEquals $? 1
-}
-
-source $(dirname $0)/shunit2
diff --git a/tests/test_core.sh b/tests/test_core.sh
deleted file mode 100755
index bc23b88..0000000
--- a/tests/test_core.sh
+++ /dev/null
@@ -1,362 +0,0 @@
-#!/bin/bash
-
-function oneTimeSetUp(){
- [ -z "$SKIP_ROOT_TESTS" ] && SKIP_ROOT_TESTS=0
-
- CURRPWD=$PWD
- ENV_MAIN_HOME=/tmp/envtesthome
- [ -e $ENV_MAIN_HOME ] || JUNEST_HOME=$ENV_MAIN_HOME bash --rcfile "$(dirname $0)/../lib/core.sh" -ic "setup_env"
- JUNEST_HOME=""
-}
-
-function install_mini_env(){
- cp -rfa $ENV_MAIN_HOME/* $JUNEST_HOME
-}
-
-function setUp(){
- cd $CURRPWD
- JUNEST_HOME=$(TMPDIR=/tmp mktemp -d -t envhome.XXXXXXXXXX)
- JUNEST_BASE="$CURRPWD/$(dirname $0)/.."
- source "${JUNEST_BASE}/lib/core.sh"
- ORIGIN_WD=$(TMPDIR=/tmp mktemp -d -t envowd.XXXXXXXXXX)
- cd $ORIGIN_WD
- JUNEST_TEMPDIR=$(TMPDIR=/tmp mktemp -d -t envtemp.XXXXXXXXXX)
-
- set +e
-
- trap - QUIT EXIT ABRT KILL TERM INT
- trap "rm -rf ${JUNEST_HOME}; rm -rf ${ORIGIN_WD}; rm -rf ${JUNEST_TEMPDIR}" EXIT QUIT ABRT KILL TERM INT
-}
-
-
-function tearDown(){
- # 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
- rm -rf $ORIGIN_WD
- rm -rf $JUNEST_TEMPDIR
- trap - QUIT EXIT ABRT KILL TERM INT
-}
-
-
-function test_is_env_installed(){
- is_env_installed
- assertEquals $? 1
- touch $JUNEST_HOME/just_file
- is_env_installed
- assertEquals $? 0
-}
-
-
-function test_download(){
- WGET=/bin/true
- CURL=/bin/false
- download_cmd
- assertEquals $? 0
-
- WGET=/bin/false
- CURL=/bin/true
- download_cmd
- assertEquals $? 0
-
- $(WGET=/bin/false CURL=/bin/false download_cmd something 2> /dev/null)
- assertEquals $? 1
-}
-
-function test_ln(){
- install_mini_env
-
- touch ln_file
- ln_cmd -s ln_file new_file
- assertEquals $? 0
- assertTrue "[ -e new_file ]"
- rm new_file
-
- touch ln_file
- OLDPATH="$PATH"
- PATH=""
- ln_cmd -s ln_file new_file 2> /dev/null
- local ret=$?
- PATH="$OLDPATH"
- assertEquals $ret 0
- assertTrue "[ -e new_file ]"
-}
-
-function test_rm(){
- install_mini_env
-
- touch rm_file
- rm_cmd rm_file
- assertEquals $? 0
- assertTrue "[ ! -e rm_file ]"
-
- touch rm_file
- OLDPATH="$PATH"
- PATH=""
- rm_cmd rm_file 2> /dev/null
- local ret=$?
- PATH="$OLDPATH"
- assertEquals $ret 0
- assertTrue "[ ! -e rm_file ]"
-}
-
-function test_chown(){
- install_mini_env
-
- local id=$(id -u)
-
- touch chown_file
- chown_cmd $id chown_file
- assertEquals $? 0
-
- touch chown_file
- OLDPATH="$PATH"
- PATH=""
- chown_cmd $id chown_file 2> /dev/null
- local ret=$?
- PATH="$OLDPATH"
- assertEquals $ret 0
-}
-
-function test_mkdir(){
- install_mini_env
-
- mkdir_cmd -p new_dir/new_dir
- assertEquals $? 0
- assertTrue "[ -d new_dir/new_dir ]"
- rm -rf new_dir
-
- OLDPATH="$PATH"
- PATH=""
- mkdir_cmd -p new_dir/new_dir 2> /dev/null
- local ret=$?
- PATH="$OLDPATH"
- assertEquals $ret 0
- assertTrue "[ -d new_dir/new_dir ]"
-}
-
-function test_setup_env(){
- wget_mock(){
- # Proof that the setup is happening
- # inside $JUNEST_TEMPDIR
- local cwd=${PWD#${JUNEST_TEMPDIR}}
- local parent_dir=${PWD%${cwd}}
- assertEquals "$JUNEST_TEMPDIR" "${parent_dir}"
- touch file
- tar -czvf ${CMD}-${ARCH}.tar.gz file
- }
- WGET=wget_mock
- setup_env 1> /dev/null
- assertTrue "[ -e $JUNEST_HOME/file ]"
- assertTrue "[ -e $JUNEST_HOME/run/lock ]"
-
- $(setup_env "noarch" 2> /dev/null)
- assertEquals 1 $?
-}
-
-
-function test_setup_env_from_file(){
- touch file
- tar -czvf ${CMD}-${ARCH}.tar.gz file 1> /dev/null
- setup_env_from_file ${CMD}-${ARCH}.tar.gz &> /dev/null
- assertTrue "[ -e $JUNEST_HOME/file ]"
- assertTrue "[ -e $JUNEST_HOME/run/lock ]"
-
- $(setup_env_from_file noexist.tar.gz 2> /dev/null)
- assertEquals $? 1
-}
-
-function test_setup_env_from_file_with_absolute_path(){
- touch file
- tar -czvf ${CMD}-${ARCH}.tar.gz file 1> /dev/null
- setup_env_from_file ${ORIGIN_WD}/${CMD}-${ARCH}.tar.gz &> /dev/null
- assertTrue "[ -e $JUNEST_HOME/file ]"
- assertTrue "[ -e $JUNEST_HOME/run/lock ]"
-}
-
-function test_run_env_as_root(){
- [ $SKIP_ROOT_TESTS -eq 1 ] && return
-
- install_mini_env
- CHROOT="sudo $CHROOT"
- CLASSIC_CHROOT="sudo $CLASSIC_CHROOT"
- CHOWN="sudo $CHOWN"
-
- local output=$(run_env_as_root pwd)
- assertEquals "/" "$output"
- run_env_as_root [ -e /run/lock ]
- assertEquals 0 $?
- run_env_as_root [ -e $HOME ]
- assertEquals 0 $?
-
- # test that normal user has ownership of the files created by root
- run_env_as_root touch /a_root_file
- # This ensure that the trap will be executed
- kill -TERM $$
- local output=$(run_env_as_root stat -c '%u' /a_root_file)
- assertEquals "$UID" "$output"
-
- SH=("sh" "--login" "-c" "type -t type")
- local output=$(run_env_as_root)
- assertEquals "builtin" "$output"
- SH=("sh" "--login" "-c" "[ -e /run/lock ]")
- run_env_as_root
- assertEquals 0 $?
- SH=("sh" "--login" "-c" "[ -e $HOME ]")
- run_env_as_root
- assertEquals 0 $?
-}
-
-function test_run_env_as_root_different_arch(){
- [ $SKIP_ROOT_TESTS -eq 1 ] && return
-
- install_mini_env
- echo "JUNEST_ARCH=XXX" > ${JUNEST_HOME}/etc/junest/info
- $(run_env_as_root pwd 2> /dev/null)
- assertEquals 1 $?
-}
-
-function test_run_env_as_classic_root(){
- [ $SKIP_ROOT_TESTS -eq 1 ] && return
-
- install_mini_env
- CHROOT="sudo unknowncommand"
- CLASSIC_CHROOT="sudo $CLASSIC_CHROOT"
- CHOWN="sudo $CHOWN"
-
- local output=$(run_env_as_root pwd 2> /dev/null)
- assertEquals "/" "$output"
- run_env_as_root [ -e /run/lock ] 2> /dev/null
- assertEquals 0 $?
-}
-
-function test_run_env_as_junest_root(){
- [ $SKIP_ROOT_TESTS -eq 1 ] && return
-
- install_mini_env
- CHROOT="sudo unknowncommand"
- CLASSIC_CHROOT="sudo unknowncommand"
- LD_EXEC="sudo $LD_EXEC"
- CHOWN="sudo $CHOWN"
-
- local output=$(run_env_as_root pwd 2> /dev/null)
- assertEquals "/" "$output"
- run_env_as_root [ -e /run/lock ] 2> /dev/null
- assertEquals 0 $?
- run_env_as_root [ -e $HOME ] 2> /dev/null
- assertEquals 1 $?
-}
-
-function test_run_env_as_user(){
- install_mini_env
- local output=$(run_env_as_user "-k 3.10" "/usr/bin/mkdir" "-v" "/newdir2" | awk -F: '{print $1}')
- assertEquals "$output" "/usr/bin/mkdir"
- assertTrue "[ -e $JUNEST_HOME/newdir2 ]"
-
- SH=("/usr/bin/echo")
- local output=$(run_env_as_user "-k 3.10")
- assertEquals "-c :" "$output"
-}
-
-function test_run_env_as_proot_mtab(){
- install_mini_env
- $(run_env_as_fakeroot "-k 3.10" "echo")
- assertTrue "[ -e $JUNEST_HOME/etc/mtab ]"
- $(run_env_as_user "-k 3.10" "echo")
- assertTrue "[ ! -e $JUNEST_HOME/etc/mtab ]"
-}
-
-function test_run_env_as_root_mtab(){
- [ $SKIP_ROOT_TESTS -eq 1 ] && return
-
- install_mini_env
- CHROOT="sudo $CHROOT"
- CLASSIC_CHROOT="sudo $CLASSIC_CHROOT"
- CHOWN="sudo $CHOWN"
- $(run_env_as_root "echo")
- assertTrue "[ ! -e $JUNEST_HOME/etc/mtab ]"
-}
-
-function test_run_env_with_quotes(){
- install_mini_env
- local output=$(run_env_as_user "-k 3.10" "bash" "-c" "/usr/bin/mkdir -v /newdir2" | awk -F: '{print $1}')
- assertEquals "/usr/bin/mkdir" "$output"
- assertTrue "[ -e $JUNEST_HOME/newdir2 ]"
-}
-
-function test_run_env_as_user_proot_args(){
- install_mini_env
- run_env_as_user "--help" "" &> /dev/null
- assertEquals 0 $?
-
- mkdir $JUNEST_TEMPDIR/newdir
- touch $JUNEST_TEMPDIR/newdir/newfile
- run_env_as_user "-b $JUNEST_TEMPDIR/newdir:/newdir -k 3.10" "ls" "-l" "/newdir/newfile" &> /dev/null
- assertEquals 0 $?
-
- $(_run_env_with_proot --helps 2> /dev/null)
- assertEquals 1 $?
-}
-
-function test_run_env_with_proot_compat(){
- PROOT_COMPAT="/bin/true"
- _run_env_with_proot "" "" &> /dev/null
- assertEquals 0 $?
-
- $(PROOT_COMPAT="/bin/false" _run_env_with_proot --helps 2> /dev/null)
- assertEquals 1 $?
-}
-
-function test_run_env_with_proot_as_root(){
- [ $SKIP_ROOT_TESTS -eq 1 ] && return
-
- install_mini_env
-
- $(sudo run_env_as_user 2> /dev/null)
- assertEquals 1 $?
- $(sudo run_env_as_fakeroot 2> /dev/null)
- assertEquals 1 $?
-}
-
-function test_run_proot_seccomp(){
- envv(){
- env
- }
- PROOT_COMPAT=envv
- local output=$(proot_cmd | grep "^PROOT_NO_SECCOMP")
- assertEquals "" "$output"
-
- envv(){
- env | grep "^PROOT_NO_SECCOMP"
- }
- PROOT_COMPAT=envv
- local output=$(proot_cmd | grep "^PROOT_NO_SECCOMP")
- # The variable PROOT_NO_SECCOMP will be produced
- # twice due to the fallback mechanism
- assertEquals "PROOT_NO_SECCOMP=1
-PROOT_NO_SECCOMP=1" "$output"
-}
-
-function test_run_env_as_fakeroot(){
- install_mini_env
- local output=$(run_env_as_fakeroot "-k 3.10" "id" | awk '{print $1}')
- assertEquals "uid=0(root)" "$output"
-}
-
-function test_delete_env(){
- install_mini_env
- echo "N" | delete_env 1> /dev/null
- is_env_installed
- assertEquals 0 $?
- echo "Y" | delete_env 1> /dev/null
- is_env_installed
- assertEquals 1 $?
-}
-
-function test_nested_env(){
- install_mini_env
- JUNEST_ENV=1 bash -ic "source $CURRPWD/$(dirname $0)/../lib/core.sh" &> /dev/null
- assertEquals 1 $?
-}
-
-source $(dirname $0)/shunit2
diff --git a/tests/test_util.sh b/tests/test_util.sh
deleted file mode 100755
index 80fd660..0000000
--- a/tests/test_util.sh
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/bin/bash
-source "$(dirname $0)/../lib/util.sh"
-
-# Disable the exiterr
-set +e
-
-function test_echoerr(){
- local actual=$(echoerr "Test" 2>&1)
- assertEquals "$actual" "Test"
-}
-
-function test_error(){
- local actual=$(error "Test" 2>&1)
- local expected=$(echo -e "\033[1;31mTest\033[0m")
- assertEquals "$actual" "$expected"
-}
-
-function test_warn(){
- local actual=$(warn "Test" 2>&1)
- local expected=$(echo -e "\033[1;33mTest\033[0m")
- assertEquals "$actual" "$expected"
-}
-
-function test_info(){
- local actual=$(info "Test")
- local expected=$(echo -e "\033[1;37mTest\033[0m")
- assertEquals "$actual" "$expected"
-}
-
-function test_die(){
- local actual=$(die "Test" 2>&1)
- local expected=$(echo -e "\033[1;31mTest\033[0m")
- assertEquals "$actual" "$expected"
- $(die Dying 2> /dev/null)
- assertEquals $? 1
-}
-
-function test_ask(){
- echo "Y" | ask "Test" &> /dev/null
- assertEquals $? 0
- echo "y" | ask "Test" &> /dev/null
- assertEquals $? 0
- echo "N" | ask "Test" &> /dev/null
- assertEquals $? 1
- echo "n" | ask "Test" &> /dev/null
- assertEquals $? 1
- echo -e "\n" | ask "Test" &> /dev/null
- assertEquals $? 0
- echo -e "\n" | ask "Test" "N" &> /dev/null
- assertEquals $? 1
- echo -e "asdf\n\n" | ask "Test" "N" &> /dev/null
- assertEquals $? 1
-}
-
-function test_insert_quotes_on_spaces(){
- local actual=$(insert_quotes_on_spaces this is "a test")
- assertEquals "this is \"a test\"" "$actual"
-
- local actual=$(insert_quotes_on_spaces this is 'a test')
- assertEquals "this is \"a test\"" "$actual"
-}
-
-function test_contains_element(){
- array=("something to search for" "a string" "test2000")
- contains_element "a string" "${array[@]}"
- assertEquals "$?" "0"
-
- contains_element "blabla" "${array[@]}"
- assertEquals "$?" "1"
-}
-
-source $(dirname $0)/shunit2
diff --git a/tests/unit-tests/test-chroot.sh b/tests/unit-tests/test-chroot.sh
new file mode 100755
index 0000000..3739c58
--- /dev/null
+++ b/tests/unit-tests/test-chroot.sh
@@ -0,0 +1,125 @@
+#!/bin/bash
+# shellcheck disable=SC1091
+
+JUNEST_ROOT=$(readlink -f "$(dirname "$0")"/../..)
+
+source "$JUNEST_ROOT/tests/utils/utils.sh"
+
+source "$JUNEST_ROOT/lib/utils/utils.sh"
+source "$JUNEST_ROOT/lib/core/common.sh"
+source "$JUNEST_ROOT/lib/core/chroot.sh"
+
+# Disable the exiterr
+set +e
+
+function oneTimeSetUp(){
+ setUpUnitTests
+}
+
+function setUp(){
+ cwdSetUp
+ junestSetUp
+ init_mocks
+}
+
+function tearDown(){
+ junestTearDown
+ cwdTearDown
+}
+
+function init_mocks() {
+ chroot_cmd() {
+ # shellcheck disable=SC2317
+ [ "$JUNEST_ENV" != "1" ] && return 1
+ # 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 "" "" "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 "" "" "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" "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 "" "" "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 "" "" "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" "false" pwd
+ assertEquals "chroot_cmd -n -b /home/blah $JUNEST_HOME /bin/sh --login -c pwd" "$(cat "$STDOUTF")"
+}
+
+source "$JUNEST_ROOT"/tests/utils/shunit2
diff --git a/tests/unit-tests/test-common.sh b/tests/unit-tests/test-common.sh
new file mode 100755
index 0000000..89c1a1e
--- /dev/null
+++ b/tests/unit-tests/test-common.sh
@@ -0,0 +1,242 @@
+#!/bin/bash
+# shellcheck disable=SC1091
+
+JUNEST_ROOT=$(readlink -f "$(dirname "$0")"/../..)
+
+source "$JUNEST_ROOT/tests/utils/utils.sh"
+
+source "$JUNEST_ROOT/lib/utils/utils.sh"
+source "$JUNEST_ROOT/lib/core/common.sh"
+
+# Disable the exiterr
+set +e
+
+function oneTimeSetUp(){
+ setUpUnitTests
+ junestSetUp
+}
+
+function oneTimeTearDown(){
+ junestTearDown
+}
+
+function setUp(){
+ ld_exec_mock() {
+ # shellcheck disable=SC2317
+ echo "ld_exec $*"
+ }
+ # shellcheck disable=SC2317
+ ld_exec_mock_false() {
+ echo "ld_exec $*"
+ return 1
+ }
+ # shellcheck disable=SC2034
+ LD_EXEC=ld_exec_mock
+
+ unshare_mock() {
+ # 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=false assertCommandSuccess ln_cmd -s ln_file new_file
+ 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="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=false assertCommandSuccess cp_cmd passwd
+ assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false passwd" "$(cat "$STDOUTF")"
+
+ CP=false LD_EXEC=false assertCommandFail cp_cmd
+}
+
+function test_download(){
+ WGET=/bin/true
+ CURL=/bin/false
+ assertCommandSuccess download_cmd
+
+ # shellcheck disable=SC2034
+ WGET=/bin/false
+ # shellcheck disable=SC2034
+ CURL=/bin/true
+ assertCommandSuccess download_cmd
+
+ WGET=/bin/false CURL=/bin/false assertCommandFail download_cmd
+}
+
+function test_rm(){
+ 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 LD_EXEC=false assertCommandFail rm_cmd rm_file
+}
+
+function test_chown(){
+ local id
+ id=$(id -u)
+
+ 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 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=false assertCommandSuccess mkdir_cmd -p new_dir/new_dir
+ 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")"
+
+ 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
+ ZGREP=false assertCommandSuccess zgrep_cmd new_file
+ assertEquals "zgrep" "$(cat "$STDOUTF")"
+
+ 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")"
+
+ 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")"
+
+ 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=false assertCommandSuccess chroot_cmd root
+ assertEquals "ld_exec $JUNEST_HOME/usr/bin/false root" "$(cat "$STDOUTF")"
+
+ CLASSIC_CHROOT=false LD_EXEC=false assertCommandFail chroot_cmd root
+}
+
+function test_proot_cmd_compat(){
+ PROOT="/bin/true" assertCommandSuccess proot_cmd "" ""
+
+ PROOT="/bin/false" assertCommandFail proot_cmd --helps
+}
+
+function test_proot_cmd_seccomp(){
+ envv(){
+ # shellcheck disable=SC2317
+ env
+ }
+ PROOT=envv
+ assertCommandSuccess proot_cmd cmd
+ assertEquals "" "$(grep "^PROOT_NO_SECCOMP" "$STDOUTF")"
+
+ envv(){
+ # shellcheck disable=SC2317
+ env | grep "^PROOT_NO_SECCOMP"
+ }
+ # shellcheck disable=SC2034
+ PROOT=envv
+ 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" "$(grep "^PROOT_NO_SECCOMP" "$STDOUTF")"
+}
+
+function test_copy_passwd_and_group(){
+ getent_cmd_mock() {
+ # 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)"
+}
+
+function test_copy_passwd_and_group_fallback(){
+ cp_cmd_mock() {
+ # shellcheck disable=SC2317
+ echo "$*"
+ }
+ CP=cp_cmd_mock GETENT=false LD_EXEC=false assertCommandSuccess copy_passwd_and_group
+ 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(){
+ CP=false GETENT=false LD_EXEC=false assertCommandFailOnStatus 1 copy_passwd_and_group
+}
+
+function test_nested_env(){
+ JUNEST_ENV=1 assertCommandFailOnStatus 106 check_nested_env
+}
+
+function test_nested_env_not_set_variable(){
+ JUNEST_ENV=aaa assertCommandFailOnStatus 107 check_nested_env
+}
+
+function test_check_same_arch_not_same(){
+ 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
+ assertCommandSuccess check_same_arch
+}
+
+
+source "$JUNEST_ROOT"/tests/utils/shunit2
diff --git a/tests/unit-tests/test-junest.sh b/tests/unit-tests/test-junest.sh
new file mode 100755
index 0000000..07a92b1
--- /dev/null
+++ b/tests/unit-tests/test-junest.sh
@@ -0,0 +1,383 @@
+#!/bin/bash
+# shellcheck disable=SC1091
+
+source "$(dirname "$0")/../utils/utils.sh"
+
+JUNEST_BASE="$(dirname "$0")/../.."
+source "$JUNEST_BASE"/bin/junest -h &> /dev/null
+
+# Disable the exiterr
+set +e
+
+function oneTimeSetUp(){
+ setUpUnitTests
+}
+
+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
+ }
+ # shellcheck disable=SC2317
+ function create_wrappers(){
+ :
+ }
+}
+
+function test_help(){
+ assertCommandSuccess main -h
+ assertEquals "usage" "$(cat "$STDOUTF")"
+ assertCommandSuccess main --help
+ assertEquals "usage" "$(cat "$STDOUTF")"
+}
+function test_version(){
+ assertCommandSuccess main -V
+ assertEquals "version" "$(cat "$STDOUTF")"
+ assertCommandSuccess main --version
+ assertEquals "version" "$(cat "$STDOUTF")"
+}
+function test_build_image_env(){
+ 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 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 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 setup -i myimage
+}
+
+function test_setup_env(){
+ # shellcheck disable=SC2317
+ is_env_installed(){
+ return 1
+ }
+ 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 setup -a arm
+}
+
+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 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 --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")"
+
+ 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(,,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(,,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_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 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_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
+}
+
+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
new file mode 100755
index 0000000..7a845aa
--- /dev/null
+++ b/tests/unit-tests/test-namespace.sh
@@ -0,0 +1,241 @@
+#!/bin/bash
+# shellcheck disable=SC1091
+
+JUNEST_ROOT=$(readlink -f "$(dirname "$0")"/../..)
+
+source "$JUNEST_ROOT/tests/utils/utils.sh"
+
+source "$JUNEST_ROOT/lib/utils/utils.sh"
+
+# Disable the exiterr
+set +e
+
+function oneTimeSetUp(){
+ setUpUnitTests
+}
+
+## Mock functions ##
+function init_mocks() {
+ # shellcheck disable=SC2317
+ function bwrap_cmd(){
+ echo "$BWRAP $*"
+ }
+}
+
+function setUp(){
+ cwdSetUp
+ junestSetUp
+
+ # Attempt to source the files under test to revert variable
+ # overrides (i.e. SH variable)
+ source "$JUNEST_ROOT/lib/core/common.sh"
+ source "$JUNEST_ROOT/lib/core/namespace.sh"
+ set +e
+
+ init_mocks
+}
+
+function tearDown(){
+ junestTearDown
+ cwdTearDown
+}
+
+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)"
+}
+
+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 ${JUNEST_HOME}/etc/passwd ]]
+ assertEquals 0 $?
+ [[ -e ${JUNEST_HOME}/etc/group ]]
+ assertEquals 0 $?
+}
+
+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
+}
+
+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
+}
+
+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_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_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_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_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
+}
+
+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
new file mode 100755
index 0000000..0f4f11a
--- /dev/null
+++ b/tests/unit-tests/test-proot.sh
@@ -0,0 +1,220 @@
+#!/bin/bash
+# shellcheck disable=SC1091
+
+JUNEST_ROOT=$(readlink -f "$(dirname "$0")"/../..)
+
+source "$JUNEST_ROOT/tests/utils/utils.sh"
+
+source "$JUNEST_ROOT/lib/utils/utils.sh"
+
+# Disable the exiterr
+set +e
+
+function oneTimeSetUp(){
+ setUpUnitTests
+}
+
+function setUp(){
+ cwdSetUp
+ junestSetUp
+
+ # Attempt to source the files under test to revert variable
+ # overrides (i.e. SH variable)
+ source "$JUNEST_ROOT/lib/core/common.sh"
+ source "$JUNEST_ROOT/lib/core/proot.sh"
+ set +e
+}
+
+function tearDown(){
+ junestTearDown
+ cwdTearDown
+}
+
+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)"
+}
+
+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 ${JUNEST_HOME}/etc/passwd ]]
+ assertEquals 0 $?
+ [[ -e ${JUNEST_HOME}/etc/group ]]
+ assertEquals 0 $?
+}
+
+function test_run_env_as_proot_user(){
+ # shellcheck disable=SC2317
+ _run_env_with_qemu() {
+ # shellcheck disable=SC2086
+ # shellcheck disable=SC2048
+ echo $*
+ }
+ 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_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_proot_user_with_backend_command(){
+ # shellcheck disable=SC2317
+ _run_env_with_qemu() {
+ # shellcheck disable=SC2086
+ # shellcheck disable=SC2048
+ echo $*
+ }
+ 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_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_as_proot_fakeroot_with_backend_command(){
+ # shellcheck disable=SC2317
+ _run_env_with_qemu() {
+ # shellcheck disable=SC2086
+ # shellcheck disable=SC2048
+ echo $*
+ }
+ 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
+ # 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" 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
+ # shellcheck disable=SC2317
+ rm_cmd() {
+ # shellcheck disable=SC2086
+ # shellcheck disable=SC2048
+ echo $*
+ }
+ # shellcheck disable=SC2317
+ ln_cmd() {
+ # shellcheck disable=SC2086
+ # shellcheck disable=SC2048
+ echo $*
+ }
+ # shellcheck disable=SC2317
+ _run_env_with_proot() {
+ # shellcheck disable=SC2086
+ # shellcheck disable=SC2048
+ echo $*
+ }
+
+ 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
diff --git a/tests/unit-tests/test-setup.sh b/tests/unit-tests/test-setup.sh
new file mode 100755
index 0000000..de2df75
--- /dev/null
+++ b/tests/unit-tests/test-setup.sh
@@ -0,0 +1,85 @@
+#!/bin/bash
+# shellcheck disable=SC1091
+
+JUNEST_ROOT=$(readlink -f "$(dirname "$0")"/../..)
+
+source "$JUNEST_ROOT/tests/utils/utils.sh"
+
+source "$JUNEST_ROOT/lib/utils/utils.sh"
+source "$JUNEST_ROOT/lib/core/common.sh"
+source "$JUNEST_ROOT/lib/core/setup.sh"
+
+# Disable the exiterr
+set +e
+
+function oneTimeSetUp(){
+ setUpUnitTests
+}
+
+function setUp(){
+ cwdSetUp
+ junestSetUp
+}
+
+function tearDown(){
+ junestTearDown
+ cwdTearDown
+}
+
+function test_is_env_installed(){
+ rm -rf "${JUNEST_HOME:?}"/*
+ assertCommandFail is_env_installed
+ touch "$JUNEST_HOME"/just_file
+ assertCommandSuccess is_env_installed
+}
+
+function test_setup_env(){
+ 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}"}
+ assertEquals "$JUNEST_TEMPDIR" "${parent_dir}"
+ touch 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 ]"
+
+ assertCommandFailOnStatus 102 setup_env "noarch"
+}
+
+
+function test_setup_env_from_file(){
+ 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"
+ assertTrue "[ -e $JUNEST_HOME/file ]"
+}
+
+function test_setup_env_from_file_not_existing_file(){
+ assertCommandFailOnStatus 103 setup_env_from_file noexist.tar.gz
+}
+
+function test_setup_env_from_file_with_absolute_path(){
+ rm -rf "${JUNEST_HOME:?}"/*
+ touch file
+ tar -czf "${CMD}-${ARCH}".tar.gz file
+ assertCommandSuccess setup_env_from_file "${CMD}-${ARCH}.tar.gz"
+ assertTrue "[ -e $JUNEST_HOME/file ]"
+}
+
+function test_delete_env(){
+ echo "N" | delete_env 1> /dev/null
+ assertCommandSuccess is_env_installed
+ echo "Y" | delete_env 1> /dev/null
+ assertCommandFail is_env_installed
+}
+
+source "$JUNEST_ROOT"/tests/utils/shunit2
diff --git a/tests/unit-tests/test-utils.sh b/tests/unit-tests/test-utils.sh
new file mode 100755
index 0000000..03e602a
--- /dev/null
+++ b/tests/unit-tests/test-utils.sh
@@ -0,0 +1,105 @@
+#!/bin/bash
+# shellcheck disable=SC1091
+
+source "$(dirname "$0")/../utils/utils.sh"
+
+unset HOME
+export HOME
+HOME=$(TMPDIR=/tmp mktemp -d -t pearl-user-home.XXXXXXX)
+
+source "$(dirname "$0")/../../lib/utils/utils.sh"
+
+# Disable the exiterr
+set +e
+
+function oneTimeSetUp(){
+ setUpUnitTests
+}
+
+function test_check_not_null(){
+ assertCommandFailOnStatus 11 check_not_null "" ""
+ assertCommandSuccess check_not_null "bla" ""
+}
+
+function test_echoerr(){
+ assertCommandSuccess echoerr "Test"
+ assertEquals "Test" "$(cat "$STDERRF")"
+}
+
+function test_error(){
+ assertCommandSuccess error "Test"
+ local expected
+ expected=$(echo -e "\033[1;31mTest\033[0m")
+ assertEquals "$expected" "$(cat "$STDERRF")"
+}
+
+function test_warn(){
+ assertCommandSuccess warn "Test"
+ local expected
+ expected=$(echo -e "\033[1;33mTest\033[0m")
+ assertEquals "$expected" "$(cat "$STDERRF")"
+}
+
+function test_info(){
+ assertCommandSuccess info "Test"
+ local expected
+ expected=$(echo -e "\033[1;36mTest\033[0m")
+ assertEquals "$expected" "$(cat "$STDOUTF")"
+}
+
+function test_die(){
+ assertCommandFail die "Test"
+ 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
+ expected=$(echo -e "\033[1;31mTest\033[0m")
+ assertEquals "$expected" "$(cat "$STDERRF")"
+}
+
+function test_ask_null_question(){
+ assertCommandFailOnStatus 11 ask "" "Y"
+}
+
+function test_ask(){
+ echo "Y" | ask "Test" &> /dev/null
+ assertEquals 0 $?
+ echo "y" | ask "Test" &> /dev/null
+ assertEquals 0 $?
+ echo "N" | ask "Test" &> /dev/null
+ assertEquals 1 $?
+ echo "n" | ask "Test" &> /dev/null
+ assertEquals 1 $?
+ echo -e "\n" | ask "Test" &> /dev/null
+ assertEquals 0 $?
+ echo -e "\n" | ask "Test" "N" &> /dev/null
+ assertEquals 1 $?
+ echo -e "asdf\n\n" | ask "Test" "N" &> /dev/null
+ assertEquals 1 $?
+}
+
+function test_ask_wrong_default_answer() {
+ echo "Y" | ask "Test" G &> /dev/null
+ assertEquals 33 $?
+}
+
+function test_insert_quotes_on_spaces(){
+ assertCommandSuccess insert_quotes_on_spaces this is "a test"
+ assertEquals "this is \"a test\"" "$(cat "$STDOUTF")"
+
+ assertCommandSuccess insert_quotes_on_spaces this is 'a test'
+ assertEquals "this is \"a test\"" "$(cat "$STDOUTF")"
+}
+
+function test_contains_element(){
+ array=("something to search for" "a string" "test2000")
+ assertCommandSuccess contains_element "a string" "${array[@]}"
+
+ assertCommandFailOnStatus 1 contains_element "blabla" "${array[@]}"
+}
+
+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
new file mode 100755
index 0000000..e90ed22
--- /dev/null
+++ b/tests/unit-tests/unit-tests.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+tests_succeded=true
+# shellcheck disable=SC2010
+for tst in $(ls "$(dirname "$0")"/test* | grep -v "$(basename "$0")")
+do
+ $tst || tests_succeded=false
+done
+
+$tests_succeded
diff --git a/tests/shunit2 b/tests/utils/shunit2
old mode 100755
new mode 100644
similarity index 96%
rename from tests/shunit2
rename to tests/utils/shunit2
index 8862ffd..e4c719c
--- a/tests/shunit2
+++ b/tests/utils/shunit2
@@ -1,5 +1,5 @@
#! /bin/sh
-# $Id: shunit2 335 2011-05-01 20:10:33Z kate.ward@forestent.com $
+# $Id$
# vim:et:ft=sh:sts=2:sw=2
#
# Copyright 2008 Kate Ward. All Rights Reserved.
@@ -15,20 +15,31 @@
# return if shunit already loaded
[ -n "${SHUNIT_VERSION:-}" ] && exit 0
+SHUNIT_VERSION='2.1.7pre'
-SHUNIT_VERSION='2.1.6'
-
+# return values that scripts can use
SHUNIT_TRUE=0
SHUNIT_FALSE=1
SHUNIT_ERROR=2
-# enable strict mode by default
-SHUNIT_STRICT=${SHUNIT_STRICT:-${SHUNIT_TRUE}}
-
+# logging functions
_shunit_warn() { echo "shunit2:WARN $@" >&2; }
_shunit_error() { echo "shunit2:ERROR $@" >&2; }
_shunit_fatal() { echo "shunit2:FATAL $@" >&2; exit ${SHUNIT_ERROR}; }
+# determine some reasonable command defaults
+__SHUNIT_UNAME_S=`uname -s`
+case "${__SHUNIT_UNAME_S}" in
+ BSD) __SHUNIT_EXPR_CMD='gexpr' ;;
+ *) __SHUNIT_EXPR_CMD='expr' ;;
+esac
+
+# commands a user can override if needed
+SHUNIT_EXPR_CMD=${SHUNIT_EXPR_CMD:-${__SHUNIT_EXPR_CMD}}
+
+# enable strict mode by default
+SHUNIT_STRICT=${SHUNIT_STRICT:-${SHUNIT_TRUE}}
+
# specific shell checks
if [ -n "${ZSH_VERSION:-}" ]; then
setopt |grep "^shwordsplit$" >/dev/null
@@ -51,19 +62,24 @@ __SHUNIT_MODE_STANDALONE='standalone'
__SHUNIT_PARENT=${SHUNIT_PARENT:-$0}
# set the constants readonly
-shunit_constants_=`set |grep '^__SHUNIT_' |cut -d= -f1`
-echo "${shunit_constants_}" |grep '^Binary file' >/dev/null && \
- shunit_constants_=`set |grep -a '^__SHUNIT_' |cut -d= -f1`
-for shunit_constant_ in ${shunit_constants_}; do
- shunit_ro_opts_=''
- case ${ZSH_VERSION:-} in
- '') ;; # this isn't zsh
- [123].*) ;; # early versions (1.x, 2.x, 3.x)
- *) shunit_ro_opts_='-g' ;; # all later versions. declare readonly globally
- esac
- readonly ${shunit_ro_opts_} ${shunit_constant_}
+__shunit_constants=`set |grep '^__SHUNIT_' |cut -d= -f1`
+echo "${__shunit_constants}" |grep '^Binary file' >/dev/null && \
+ __shunit_constants=`set |grep -a '^__SHUNIT_' |cut -d= -f1`
+for __shunit_const in ${__shunit_constants}; do
+ if [ -z "${ZSH_VERSION:-}" ]; then
+ readonly ${__shunit_const}
+ else
+ case ${ZSH_VERSION} in
+ [123].*) readonly ${__shunit_const} ;;
+ *) readonly -g ${__shunit_const} # declare readonly constants globally
+ esac
+ fi
done
-unset shunit_constant_ shunit_constants_ shunit_ro_opts_
+unset __shunit_const __shunit_constants
+
+#
+# internal variables
+#
# variables
__shunit_lineno='' # line number of executed test
@@ -88,6 +104,9 @@ __shunit_assertsSkipped=0
# macros
_SHUNIT_LINENO_='eval __shunit_lineno=""; if [ "${1:-}" = "--lineno" ]; then [ -n "$2" ] && __shunit_lineno="[$2] "; shift 2; fi'
+#-----------------------------------------------------------------------------
+# private functions
+
#-----------------------------------------------------------------------------
# assert functions
#
@@ -313,8 +332,8 @@ _ASSERT_NOT_SAME_='eval assertNotSame --lineno "${LINENO:-}"'
assertTrue()
{
${_SHUNIT_LINENO_}
- if [ $# -gt 2 ]; then
- _shunit_error "assertTrue() takes one two arguments; $# given"
+ if [ $# -lt 1 -o $# -gt 2 ]; then
+ _shunit_error "assertTrue() takes one or two arguments; $# given"
return ${SHUNIT_ERROR}
fi
_shunit_shouldSkip && return ${SHUNIT_TRUE}
@@ -968,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
new file mode 100644
index 0000000..ab37bd9
--- /dev/null
+++ b/tests/utils/utils.sh
@@ -0,0 +1,62 @@
+#!/usr/bin/env bash
+
+OLD_CWD=${PWD}
+function cwdSetUp(){
+ ORIGIN_CWD=$(TMPDIR=/tmp mktemp -d -t junest-cwd.XXXXXXXXXX)
+ cd "$ORIGIN_CWD" || return 1
+}
+
+function cwdTearDown(){
+ 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}/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"
+ unset JUNEST_HOME
+}
+
+function setUpUnitTests(){
+ OUTPUT_DIR="${SHUNIT_TMPDIR}/output"
+ mkdir "${OUTPUT_DIR}"
+ STDOUTF="${OUTPUT_DIR}/stdout"
+ STDERRF="${OUTPUT_DIR}/stderr"
+}
+
+function assertCommandSuccess(){
+ # shellcheck disable=SC2091
+ $(set -e
+ "$@" > "$STDOUTF" 2> "$STDERRF"
+ )
+ assertTrue "The command $1 did not return 0 exit status" $?
+}
+
+function assertCommandFail(){
+ # shellcheck disable=SC2091
+ $(set -e
+ "$@" > "$STDOUTF" 2> "$STDERRF"
+ )
+ assertFalse "The command $1 returned 0 exit status" $?
+}
+
+# $1: expected exit status
+# $2-: The command under test
+function assertCommandFailOnStatus(){
+ local status=$1
+ shift
+ # shellcheck disable=SC2091
+ $(set -e
+ "$@" > "$STDOUTF" 2> "$STDERRF"
+ )
+ assertEquals "$status" $?
+}