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. + +

+ JuNest +

|Project Status|Donation|Communication| |:------------:|:------:|:-----------:| -| [![Build status](https://api.travis-ci.org/fsquillace/junest.png?branch=master)](https://travis-ci.org/fsquillace/junest) [![OpenHub](https://www.openhub.net/p/junest/widgets/project_thin_badge.gif)](https://www.openhub.net/p/junest) | [![PayPal](https://img.shields.io/badge/PayPal-Donate%20a%20beer-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8LEHQKBCYTACY) | [![Join the gitter chat at https://gitter.im/fsquillace/junest](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/fsquillace/junest?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the IRC chat at https://webchat.freenode.net/?channels=junest](https://img.shields.io/badge/IRC-JuNest-yellow.svg)](https://webchat.freenode.net/?channels=junest) [![Join the group at https://groups.google.com/d/forum/junest](https://img.shields.io/badge/Google Groups-JuNest-red.svg)](https://groups.google.com/d/forum/junest) [![RSS](https://img.shields.io/badge/RSS-News-orange.svg)](http://fsquillace.github.io/junest-site/feed.xml) | +| [![Build status](https://api.travis-ci.com/fsquillace/junest.png?branch=master)](https://app.travis-ci.com/github/fsquillace/junest) [![OpenHub](https://www.openhub.net/p/junest/widgets/project_thin_badge.gif)](https://www.openhub.net/p/junest) | [![Github Sponsors](https://img.shields.io/badge/GitHub-Sponsors-orange.svg)](https://github.com/sponsors/fsquillace) [![PayPal](https://img.shields.io/badge/PayPal-Donation-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8LEHQKBCYTACY) [![Buy me a coffee](https://www.buymeacoffee.com/assets/img/custom_images/yellow_img.png)](https://www.buymeacoffee.com/fsquillace) | [![Join the Discord server at https://discord.gg/ttfBT7MKve](https://img.shields.io/badge/Discord-Server-blueviolet.svg)](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/images/0)](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/0)[![](https://sourcerer.io/fame/fsquillace/fsquillace/junest/images/1)](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/1)[![](https://sourcerer.io/fame/fsquillace/fsquillace/junest/images/2)](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/2)[![](https://sourcerer.io/fame/fsquillace/fsquillace/junest/images/3)](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/3)[![](https://sourcerer.io/fame/fsquillace/fsquillace/junest/images/4)](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/4)[![](https://sourcerer.io/fame/fsquillace/fsquillace/junest/images/5)](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/5)[![](https://sourcerer.io/fame/fsquillace/fsquillace/junest/images/6)](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/6)[![](https://sourcerer.io/fame/fsquillace/fsquillace/junest/images/7)](https://sourcerer.io/fame/fsquillace/fsquillace/junest/links/7) diff --git a/VERSION b/VERSION 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" $? +}