Experiment with JuNest

Moderator: BarryK

Post Reply
Caramel
Posts: 532
Joined: Sun Oct 02, 2022 6:25 pm
Location: France
Has thanked: 106 times
Been thanked: 95 times

Experiment with JuNest

Post by Caramel »

In the past, I have tested the Nix package manager (viewtopic.php?p=91286)

It was a disappointment, too big and too much files installed.

This time, the test is with JuNest.
https://github.com/fsquillace/junest (Positive review : https://distrowatch.com/weekly.php?issue=20211213)

Here It is only about intallation and quick test with Dolphin.

To install, the indicated method uses the command git that is available in the devx sfs.
Alternative : install this pet https://www.swisstransfer.com/d/5beacd2 ... 8e9426a64f (618 Ko)

The github page says to add this line to /root/.bashrc:

Code: Select all

export PATH=~/.local/share/junest/bin:$PATH

Alternative : create symlink to the junest executables in /usr/bin:

Code: Select all

ln -s /root/.local/share/junest/bin/junest /usr/bin/junest
ln-s /root/.local/share/junest/bin/sudoj /usr/bin/sudoj

The command sudoj is for using sudo in the junest environment (Probably useless in Easy if you run as root)

Then you enter "junest setup" to install all the junest environment (a "filesystem" in the directory /root/.junest)

To enter in the junest environment type enter (and type exit in the environment to go out)

The environment is a minimal Arch Linux distribution.
It is necessary to know a little pacman
https://wiki.archlinux.org/title/Pacman

There is only one mirror (to download packages) listed in the installation
To choose other mirrors we can download a package :

Code: Select all

pacman -S pacman-mirrorlist

(see https://wiki.archlinux.org/title/Mirrors)

Then in the text file /root/.junest/etc/pacman.d/mirrorlist.pacnew unomment the mirror(s) you want to use and enter:

Code: Select all

cp /root/.junest/etc/pacman.d/mirrorlist.pacnew /root/.junest/etc/pacman.d/mirrorlist

After that, essential update of the packages database

Code: Select all

pacman -Syyu

installation of Dolphin:

Code: Select all

pacman -S dolphin

(More than 2.5 GB installed at this point)

The first try was not brilliant. Files appeared to be missing I needed to upadted glibc (I had not done the complete update) (Edit : typo fixed)
No font was installed (No result from the command "fc-list")

I was not satisfied by the result, dolphin was limited to the junest environment.

So new try : launch dolphin outside his jail. Continued in a next message.

Last edited by Caramel on Mon Aug 05, 2024 8:18 am, edited 1 time in total.
Caramel
Posts: 532
Joined: Sun Oct 02, 2022 6:25 pm
Location: France
Has thanked: 106 times
Been thanked: 95 times

Re: Experiment with JuNest

Post by Caramel »

Caramel wrote: Sat Aug 03, 2024 7:57 pm

So new try : launch dolphin outside his jail. Continued in a next message.

For the rest, it is not necessary to have completely installed dolphin in junest (that's to say in the junest environment).
To reproduce the instructions you can resume after "pacman -S dolphin".

The idea is to use a command like

Code: Select all

/root/.junest/usr/lib/ld-linux-x86-64.so.2 --library-path /root/.junest/usr/lib/ /root/.junest/usr/bin/dolphin

to launch dolphin in EasyOS with access to the library of the junest environment.

First (potential)l problem, the content of a suddirectory of a directory listed in the option --library-path (here equivalent of LD_LIBRAY_PATH) is not taken into account.
And there are many subdirectories in /root/.junest/usr/lib/

But the true problem is dolphin (and kioworker, launched by dolphin) sometimes ignores the option --library-path.
(dolphin searches kioworker in a predefined list of directories and obviously /root/.;unest/usr/lib is not part of it)
(kioworker apparently searches the .so files it needs, only in "/usr/lib" )

The addition of symlinks in /usr/lib to some files in /root/.junest/usr/lib fix the problem.

Script to launch dolphin :

Code: Select all

#!/bin/sh

export PATH=/root/.junest/usr/bin:$PATH

export LD_PRELOAD=/root/.junest/usr/lib/pulseaudio/libpulsecommon-17.0.so:/root/.junest/usr/lib/libstdc++.so.6

/root/.junest/usr/lib/ld-linux-x86-64.so.2 --library-path /root/.junest/usr/lib/ /root/.junest/usr/bin/dolphin

Pet with all the symlinks:

dolphin-junest-extra-1.0.0.pet
(1.08 KiB) Downloaded 37 times

Normally, if the pet is installed, the script launches Dolphin that can mount and access other partitions. (But sometimes you have to click twice) (udisks2 must be installed (by PKGget))

The trouble is dolphin mount in /tmp/run/media/root and not in /mnt like ROX

To use Dolphin in other language than English, you have to copy the files dolphin.mo kio6.mo and kxmlgui6.mo from junest to the main filesystem
Example for French:

Code: Select all

cd /root/.junest/usr/share/locale/fr/LC_MESSAGES
cp dolphin.mo kio6.mo kxmlgui6.mo /usr/share/locale/fr/LC_MESSAGES
User avatar
BarryK
Posts: 2871
Joined: Tue Dec 24, 2019 1:04 pm
Has thanked: 149 times
Been thanked: 788 times

Re: Experiment with JuNest

Post by BarryK »

Very interesting.

I was thinking of putting git builtin to easy.sfs, instead of being in the devx*sfs

Reason for doing that is was considering putting Gittyup git client builtin; currently it is in the devx.

Caramel
Posts: 532
Joined: Sun Oct 02, 2022 6:25 pm
Location: France
Has thanked: 106 times
Been thanked: 95 times

Re: Experiment with JuNest

Post by Caramel »

Several remarks if someone is interested by a try of JuNest

-In EasyOS it's better to change the default directory of junest (the nested sytem) so that it is not included in the snapshots
In the script common.sh (in junest/lib/core where junest is the directory with the executable junest) replace

Code: Select all

JUNEST_HOME=${JUNEST_HOME:-~/.${CMD}}

by, for example

Code: Select all

JUNEST_HOME=${JUNEST_HOME:-/mnt/wkg/.${CMD}}

or, so that the directory is not hidden

Code: Select all

JUNEST_HOME=${JUNEST_HOME:-/mnt/wkg/${CMD}

(In fact ${CMD}=junest )

- Line 301 of common.sh, the option -f for cp is insufficient. The option --remove-destination is neccessary.

- In common.sh, the lists of users and groups from EasyOS is copied to the nested Arch Linux. It's risky as they are big differences between the users and groups of the two systems and useless if junest is executed only as root

- In namespace.sh (in junest/lib/core), it is written "No need for localtime as it is setup during the image build" but it seems only true if we use a build image we have made.
The image available on internet is probably unique. It can not know the local time of the downloader.
So it's better that the line 151 is uncommented for synchronize time between EasyOS and junest.

-The executable yay installed in the nested system refuses to start without systemd.
(yay is a AUR helper, it downloads sources of softwares in the AUR repositoryand compiles them)
(AUR is a complementary repository for Arch Linux with no binaries)

Caramel
Posts: 532
Joined: Sun Oct 02, 2022 6:25 pm
Location: France
Has thanked: 106 times
Been thanked: 95 times

Re: Experiment with JuNest

Post by Caramel »

In my last try, the file junest-x86_64.tar.gz (hat contains the nested system) downloaded for internet was problematic. The user and group proprietaries of all the contained files in the archive was "runner" and "docker" instead of root and root. The setup was impossible.
So it is neccessary to add in the function _setup_env() (in the file setup.sh)

Code: Select all

chown -R root:root "${JUNEST_HOME}"

To simplify I have made a script that contains all the useful code (No need to clone the git repo anymore)
The code of all the scripts (except sudoj, the scripts git, the scripts for test for developers and the scripts for build (in Arch Linux) a local image ) was grouped in a uniq file.

I removed the useless parts (for example the test to know if the usernames are available on the host. It is available in recent EasyOS. Or for other example, the remaining parts for build on Arch Linux a local image)
I changed the default directory for the nested sytem in /mnt/wkg to excule it from snapshots

I made some fixes (I hope without errors)

It remains probably still bugs.
It was only tested for the usage based on Linux usernames (the default usage) and launched as root.

This is the result. A big junest script (to make executable and to place in /usr/bin)

Code: Select all

#!/usr/bin/bash
#
#JuNest (https://github.com/fsquillace/junest) simplified and modified version usable in EasyOS
#


# To keep time in sync between the host and JuNest while traveling to another time zone, uncomment the next line
#copy_file /etc/localtime

#The default JuNest directory here is at the root of the working partition (/mnt/wkg/junest). So it is not included in the snapshots in EasyOS.
#To change this default ,modify this line :
JUNEST_HOME=${JUNEST_HOME:-/mnt/wkg/junest}


###################################
### Used scripts          ###
###################################

# The used scripts in the directory where the executable is installed(=$JUNEST_BASE) by the real JuNest have been copied here, after simplification and small changes. 
# The parts are "utils, common, setup, chroot, namespace, proot and wrappers".

###  source "${JUNEST_BASE}/lib/utils/utils.sh"

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"
}

###  source "${JUNEST_BASE}/lib/core/common.sh"
# This module contains all common functionalities for JuNest.


NAME='JuNest'
CMD='junest'
DESCRIPTION='The Arch Linux based distro that runs upon any Linux distros without root access'

ROOT_ACCESS_ERROR=105


JUNEST_TEMPDIR=${JUNEST_TEMPDIR:-/tmp}

ARCH="x86_64"
LD_LIB="${JUNEST_HOME}/lib64/ld-linux-x86-64.so.2"


MAIN_REPO=https://link.storjshare.io/s/jvb5tgarnjtt565fffa44spvyuga/junest-repo
MAIN_REPO=https://pub-a2af2344e8554f6c807bc3db355ae622.r2.dev
ENV_REPO=${MAIN_REPO}/${CMD}

DEFAULT_MIRROR='https://mirror.rackspace.com/archlinux/$repo/os/$arch'

ORIGIN_WD=$(pwd)

################## EXECUTABLES ################

# 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"

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 download_cmd(){
    $WGET "$@" || $CURL "$@"
}

function chroot(){
    $CLASSIC_CHROOT "$@" || $LD_EXEC "${JUNEST_HOME}"/usr/bin/$CLASSIC_CHROOT "$@"
}

function bwrap_cmd(){
    if $LD_EXEC "$BWRAP" --dev-bind / / "${DEFAULT_SH[0]}" "-c" ":"
    then
        $LD_EXEC "$BWRAP" "${@}"
    else
        die "Error: Something went wrong while executing bwrap command. Exiting"
    fi
}

function proot_cmd(){
    local proot_args="$1"
    shift
    
    if ${PROOT} ${proot_args} "${DEFAULT_SH[@]}" "-c" ":"
    then
        
        ${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 ###############

#######################################
# 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(){
	## Important modification here. The users and groups of EasyOS and those of Arch Linux are too different.
	
    # Code from JuNest :
    # 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 passwd > "${JUNEST_HOME}"/etc/passwd || \
        #! getent 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 group > "${JUNEST_HOME}"/etc/group
    #then
        #warn "getent command failed or does not exist. Binding directly from /etc/group."
        #copy_file /etc/group
    #fi
    
    # New code
    getent passwd spot >> "${NEST_HOME}"/etc/passwd
    getent group  spot >> "${NEST_HOME}"/etc/group
    #if [ ! "${USER}" == "root" ] && [ ! "${USER}" == "spot" ]
    #then
    #getent passwd "${USER}" >> "${NEST_HOME}"/etc/passwd
    #getent group  "${USER}" >> "${NEST_HOME}"/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 -f --remove-destination "$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
}

###  source "${JUNEST_BASE}/lib/core/setup.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 -fr "$maindir"
}


function _prepare_build_directory(){
    local maindir=$1
    check_not_null "$maindir"
    trap - QUIT EXIT ABRT KILL TERM INT
    
    trap "rm -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 -p "${JUNEST_HOME}"
    tar -zxpf "${imagepath}" -C "${JUNEST_HOME}"
    # precaution (New code)
    chown -R root:root "${JUNEST_HOME}"
    # New code
    mkdir -p /home/spot
    chown 502:502 /home/spot
    
    copy_file /etc/localtime
    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.
#
# Output:
#   None
#######################################
function setup_env(){
    local arch=x86_64
    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}"
}

#######################################
# 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 -rf "${JUNEST_HOME}"
    then
        info "${NAME} deleted in ${JUNEST_HOME}"
    else
        error "Error: Cannot delete ${NAME} in ${JUNEST_HOME}"
    fi
}

###  source "${JUNEST_BASE}/lib/core/chroot.sh"


function _run_env_as_xroot(){
    local cmd=$1
    local backend_args="$2"
    local no_copy_files="$3"
    shift 3

    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
    
    trap "[ -z $uid ] || chown -R ${uid} ${JUNEST_HOME};" EXIT QUIT ABRT TERM INT

    if ! $no_copy_files
    then
        copy_common_files
    fi

    
    $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.
# Output:
#   -                        : The command output.
#######################################
function run_env_as_groot(){


    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.
# Output:
#   -                        : The command output.
#######################################
function run_env_as_chroot(){


    local backend_command="${1:-chroot}"
    local backend_args="$2"
    local no_copy_files="$3"
    shift 3

    _run_env_as_xroot "$backend_command" "$backend_args" "$no_copy_files" "$@"
}

###  source "${JUNEST_BASE}/lib/core/namespace.sh"


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"


#######################################
# 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:
#   $ROOT_ACCESS_ERROR        : If the user is the real root.
# Output:
#   -                         : The command output.
#######################################
function run_env_as_bwrap_fakeroot(){

    local backend_command="${1:-$BWRAP}"
    local backend_args="$2"
    local no_copy_files="$3"
    shift 3


    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
    
    PATH="/usr/bin" BWRAP="${backend_command}" 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.
# Output:
#   -                        : The command output.
#######################################
function run_env_as_bwrap_user() {

    local backend_command="${1:-$BWRAP}"
    local backend_args="$2"
    local no_copy_files="$3"
    shift 3


    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 after the JuNest directory creation
        #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
    
    PATH='' BWRAP="${backend_command}" bwrap_cmd $COMMON_BWRAP_OPTION $backend_args "${DEFAULT_SH[@]}" "${args[@]}"
}

### source "${JUNEST_BASE}/lib/core/proot.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}" proot_cmd "${backend_args}" "${DEFAULT_SH[@]}" "${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."

    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

    _run_env_with_proot "$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."

    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 after the JuNest directory creation
        #copy_file /etc/localtime
        copy_passwd_and_group
    fi

    provide_common_bindings
    local bindings=${RESULT}
    unset RESULT

    _run_env_with_proot "$backend_command" "${bindings} -r ${JUNEST_HOME} $backend_args" "$@"
}

### source "${JUNEST_BASE}/lib/core/wrappers.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 <<EOF > "${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

}



###################################
### General functions           ###
###################################
usage() {
    echo -e "$NAME (v$(cat "$JUNEST_BASE"/VERSION)): $DESCRIPTION"
    echo
    echo -e "Usage: $CMD [action] [options] [--] [command]"
    echo
    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} (if not modified by the environment variable JUNEST_HOME or by the option -l)"
    echo -e "            -l, --location <directory>     Setup $NAME in the chosen directory "
    echo -e "            -d, --delete                   Delete $NAME from ${JUNEST_HOME} (or from the chosen directory if the option -l precede)"
    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 <cmd>        Bwrap command to use"
    echo -e "            -b, --backend-args <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 <cmd>        PRoot command to use"
    echo -e "            -b, --backend-args <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 <cmd>        GRoot command to use"
    echo -e "            -b, --backend-args <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 <cmd>        Chroot command to use"
    echo -e "            -b, --backend-args <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 "  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
}

version() {
    echo -e "$NAME $(cat "$JUNEST_BASE"/VERSION)"
}

function parse_arguments(){
    # Actions
    ACT_SETUP=false
    ACT_CREATE_WRAPPERS=false
    ACT_NAMESPACE=false
    ACT_PROOT=false
    ACT_GROOT=false
    ACT_ROOT=false
    ACT_HELP=false
    ACT_VERSION=false

    case "$1" in
        s|setup) ACT_SETUP=true ; shift ;;
        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_CREATE_WRAPPERS
    then
        _parse_create_wrappers_opts "$@"
    elif $ACT_NAMESPACE
    then
        _parse_ns_opts "$@"
    elif $ACT_PROOT
    then
        _parse_proot_opts "$@"
    elif $ACT_GROOT
    then
        _parse_root_opts "$@"
    elif $ACT_ROOT
    then
        _parse_root_opts "$@"
    fi
}

function _parse_root_opts() {
    # Options:
    BACKEND_ARGS=""
    OPT_NO_COPY_FILES=false
    BACKEND_COMMAND=""

    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

    ARGS=()
    for arg in "$@"
    do
        ARGS+=("$arg")
    done
}

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_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() {
    IMAGE_FILE=""
    OPT_DELETE=false
    while [[ -n "$1" ]]
    do
        case "$1" in
            -l|--location) shift ; JUNEST_HOME=$1 ; echo -e "\nJUNEST_HOME=$1" >> $HOME/.bashrc ; 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_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

            setup_env
            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
        
        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
    
    trap "PATH=$PATH create_wrappers" EXIT QUIT TERM
    
    $run_env "$BACKEND_COMMAND" "${BACKEND_ARGS}" $OPT_NO_COPY_FILES "${ARGS[@]}"
}

function main() {
    parse_arguments "$@"
    execute_operation
}

main "$@"

#!/bin/sh

At frst launch type

Code: Select all

junest setup

The nested system will install in /mnt/wkg/junest .

Caramel
Posts: 532
Joined: Sun Oct 02, 2022 6:25 pm
Location: France
Has thanked: 106 times
Been thanked: 95 times

Devuan as a "nested system" on EasyOS

Post by Caramel »

JuNest is a script able to install a minimal Arch Linux system on a partition and also able to use a "nested system" on the main OS (EasyOS in the present case)

So the idea is to use this script with a other system than Arch Linux.

I tested with Devuan (a fork of Debian without systemd as init)

According to https://wiki.debian.org/Debootstrap :

debootstrap is a tool which will install a Debian base system into a subdirectory of another, already installed system. It doesn't require an installation CD, just access to a Debian repository.

Debootstraps works also with Devuan and Ubuntu.

Debootstrap is available as a .deb package from a Debian repository or from a Devuan repository (and probably from many other sources)

It is impossible to install Devuan with the debootstrap from Debian (In fact if it is launched with a Devuan release as option, it installs Debian!)
The debootstrap from Devuan allows to install Devuan and also Debian.

Debootstrap .deb from Devuan converted in pet :

debootstrap_devuan1_all-1.0.137.pet
(53.03 KiB) Downloaded 27 times

Below here is a big script, named nest and derived from junest, that installs and uses the release daedalus of Devuan in /mnt/wkg/daeadalus
(By default xorg and synaptic are also installed)

nest (to make executable and to place in /usr/bin) :

Code: Select all

#!/usr/bin/bash
#
#Nest is derived from JuNest (https://github.com/fsquillace/junest).
#JuNest was simplified and modified to use Devuan/Debian/Ubuntu instead of Arch Linux (as nested system) and to be usable in EasyOS.
#By default, a minimal Devuan distro is installed.
#
#This script requires the Devuan debootstrap package to work with Devuan, Debian or Ubuntu. 
#The Debian debootsrap package is suitable to work with Debian and Ubuntu but not with Devuan
 
TEXTDOMAIN=nest
export TEXTDOMAIN
. /usr/bin/gettext.sh

# To keep time in sync between the host and the Nest system while traveling to another time zone, uncomment the next line
#copy_file /etc/localtime

# To change the default values, modify these lines :

#Release that will be used. By default, it's daedalus, the stable release of Devuan at time of the creation of this script. To change to Debian, change both RELEASE and MIRROR.
RELEASE=${RELEASE:-daedalus}

# The default mirror for Devuan is http://dev.devuan.org/merged. It had problems when the scipt was tested.
# See https://sledjhamr.org/apt-panopticon/results/Report-web.html for a list of mirrors for Devuan with current status.
# For DEBIAN you can use MIRROR=https://deb.debian.org/debian 
MIRROR=https://devuan.packet-gain.de/merged

#Options to pass to debootstrap. For experts. See possible options on the man page https://linux.die.net/man/8/debootstrap
OPTIONS=

# The default Nest directory is at the root of the working partition(/mnt/wkg). So it is not included in the snapshots in EasyOS
NEST_HOME=${NEST_HOME:-/mnt/wkg/$RELEASE}
NEST_TEMPDIR=${NEST_TEMPDIR:-/tmp}


# To not install xorg in the Nest system during the setup change true to false(In this case, Synaptic will not be installed even if INSTALLSYNAPTIC=true)
INSTALLXORG=true

# To not install synaptic in the Nest system during the setup change true to false
INSTALLSYNAPTIC=true

###################################
### Used scripts          ###
###################################

# The used scripts in the directory where the executable is installed(=$JUNEST_BASE) by the real JuNest have been copied here, after simplification and small changes. 
# The parts are "utils, common, setup, chroot, namespace, proot and wrappers".
# The setup part was completely rewritten because here the nested system is Debian (or a variant) and not Arch Linux.

###  source "${JUNEST_BASE}/lib/utils/utils.sh"

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"
}

###  source "${JUNEST_BASE}/lib/core/common.sh"
# This module contains all common functionalities for Nest.


NAME='Nest'
CMD='nest'
DESCRIPTION=$(gettext 'A minimal Devuan (or Debian or variant) distro that runs upon other Linux distros')

ROOT_ACCESS_ERROR=105




ARCH="x86_64"
LD_LIB="${NEST_HOME}/lib64/ld-linux-x86-64.so.2"

ORIGIN_WD=$(pwd)

################## EXECUTABLES ################


# List of executables that are run inside Nest:
DEFAULT_SH=("/bin/bash" "--login")

# List of executables that are run in the host OS:
BWRAP="${NEST_HOME}/usr/bin/bwrap"
PROOT="${NEST_HOME}/usr/bin/proot-${ARCH}"

LD_EXEC="$LD_LIB --library-path ${NEST_HOME}/usr/lib:${NEST_HOME}/lib"
#LD_EXEC="$LD_LIB --library-path ${NEST_HOME}/usr/lib:${NEST_HOME}/lib:${NEST_HOME}/lib/x86_64-linux-gnu"

function bwrap_cmd(){
    #$LD_EXEC "$BWRAP" --dev-bind / / "${DEFAULT_SH[0]}" "-c" ":"
 # LD_PRELOAD=$NEST_HOME/lib/x86_64-linux-gnu/libselinux.so.1 $LD_EXEC "$BWRAP" --dev-bind / / "${DEFAULT_SH[0]}" "-c" ":"
  # echo "$BWRAP" "${@}"
  LD_PRELOAD=$NEST_HOME/lib/x86_64-linux-gnu/libselinux.so.1 $LD_EXEC "$BWRAP" "${@}"
 #   LD_PRELOAD=/daedalus/lib/x86_64-linux-gnu/libselinux.so.1 /daedalus/lib64/ld-linux-x86-64.so.2 --library-path /daedalus/usr/lib:/daedalus/lib /daedalus/usr/bin/bwrap  --bind /daedalus / --bind /root /root --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 /bin/bash --login
#    LD_PRELOAD=/daedalus/lib/x86_64-linux-gnu/libselinux.so.1 /daedalus/lib64/ld-linux-x86-64.so.2 --library-path /daedalus/usr/lib:/daedalus/lib /daedalus/usr/bin/bwrap  --bind /daedalus / --bind /root /root --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 /bin/bash --login
#LD_PRELOAD=/daedalus/lib/x86_64-linux-gnu/libselinux.so.1 /daedalus/lib64/ld-linux-x86-64.so.2 --library-path /daedalus/usr/lib:/daedalus/lib /daedalus/usr/bin/bwrap  --bind /daedalus / --bind /root /root --bind /tmp /tmp --bind /sys /sys --bind /proc /proc --dev-bind-try /dev /dev --bind-try /run/user/0 /run/user/0 --unshare-user-try /bin/bash --login


  #  $LD_EXEC "$BWRAP" "${@}"
}

function proot_cmd(){
    local proot_args="$1"
    shift
    
    if ${PROOT} ${proot_args} "${DEFAULT_SH[@]}" "-c" ":"
    then
        
        ${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 ###############

#######################################
# 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 $NEST_HOME/etc/junest.
#
# Globals:
#  NEST_HOME (RO)      : The JuNest home directory.
# Arguments:
#  None
# Returns:
#  None
# Output:
#  None
#######################################
# In EasyOS, getent exists so a test in junest was removed here. 
# The change of the lists of users and groups in the Nest system by the lists from EasyOS could cause problems
# because some users or groups system could exist or have differents ids in the Nest system and not in the host.
# So this change was removed
# E.g. video is the user 44 in Debian (see https://salsa.debian.org/debian/base-passwd/blob/master/group.master)
function copy_passwd_and_group(){
 
    getent passwd root >> "${NEST_HOME}"/etc/passwd
    getent group root >> "${NEST_HOME}"/etc/group
    getent passwd spot >> "${NEST_HOME}"/etc/passwd
    getent group  spot >> "${NEST_HOME}"/etc/group
    if [ ! "${USER}" == "root" ] && [ ! "${USER}" == "spot" ]
    then
    getent passwd "${USER}" >> "${NEST_HOME}"/etc/passwd
    getent group  "${USER}" >> "${NEST_HOME}"/etc/group
    fi

}

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
    # In this script the option --remove-destination was added
    [[ -r "$file" ]] && cp -f --remove-destination "$file" "${NEST_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
}

###  source "${JUNEST_BASE}/lib/core/setup.sh"


# This function tests if $NEST_HOME exists and is not empty. It is not used in this script. So we can resume an infinished installation.
# To use it , uncomment line 1004 and following
function is_env_installed(){
    [[ -d "$NEST_HOME" ]] && [[ "$(ls -A "$NEST_HOME")" ]] && return 0
    return 1
}

#######################################
# Setup the Nest system.
#######################################

function setup_env(){
    command -v debootstrap
    if [ $? ]; then 
     echo "$(gettext 'debootstrap will download a minimal system. It could take a while')"
     debootstrap --arch amd64 $OPTIONS $RELEASE $NEST_HOME $MIRROR
     if [ ! $? ]
     then
      echo
      echo "$(gettext 'Something went wrong. You shoud try again')"
     else
      echo
      echo "$(gettext 'Download of the debootstrap image finished')"
     fi
     ## The next lines are probably useless if debootstrap worked without error.
     #rmdir $NEST_HOME/var/cache/apt/archives/partial
     #rmdir $NEST_HOME/var/lib/apt/lists/auxfiles
     # The two next lines are a trick to prevent annoying error message.
     mkdir -p $NEST_HOME$NEST_HOME/lib/x86_64-linux-gnu/
     cp $NEST_HOME/lib/x86_64-linux-gnu/libselinux.so.1 $NEST_HOME$NEST_HOME/lib/x86_64-linux-gnu/libselinux.so.1
     copy_file /etc/localtime
     mkdir $NEST_HOME/home/spot
     mount -t proc /proc $NEST_HOME/proc/
     mount -t sysfs /sys $NEST_HOME/sys/
     if $INSTALLXORG
     then
      echo
      echo "$(gettext 'Download and installation of xorg')"
      chroot $NEST_HOME apt-get -y install xorg
        if $INSTALLSYNAPTIC
        then
         echo
         echo "$(gettext 'Download and installation of Synaptic')"
         chroot $NEST_HOME apt-get -y install synaptic
        fi  
     fi
     echo
     echo "$(gettext 'Download and installation of bubblewrap and proot')"
     chroot $NEST_HOME apt-get -y install bubblewrap
     chroot $NEST_HOME apt-get -y install proot
     echo
     echo "$(gettext 'Installation and generation of locales')"
     chroot $NEST_HOME apt-get -y install locales-all
     chroot $NEST_HOME apt-get -y install locales
     umount $NEST_HOME/sys
     umount $NEST_HOME/proc
     touch /etc/profile.locale
     grep "$NEST_HOME/usr/bin_wrappers" /etc/profile.locale || ( echo n && echo "PATH=\$PATH:$NEST_HOME/usr/bin_wrappers" >> /etc/profile.locale )
      mkdir -p "${NEST_HOME}/usr/bin_wrappers"
    else
     echo "$(gettext 'debootstrap must be installed to setup the Nest image')"
     exit
    fi 
}

#######################################
# Remove an existing Nest system.
#######################################
function delete_env(){
    ! ask "Are you sure to delete ${NAME} located in ${NEST_HOME}" "N" && return
    if mountpoint -q "${NEST_HOME}"
    then
        info "There are mounted directories inside ${NEST_HOME}"
        if ! umount --force "${NEST_HOME}"
        then
            error "Cannot umount directories in ${NEST_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 "${NEST_HOME}"/etc/ca-certificates
    if rm -rf "${NEST_HOME}"
    then
        info "${NAME} deleted in ${NEST_HOME}"
    else
        error "Error: Cannot delete ${NAME} in ${NEST_HOME}"
    fi
}

###  source "${JUNEST_BASE}/lib/core/chroot.sh"


function _run_env_as_xroot(){
    local cmd=$1
    local backend_args="$2"
    local no_copy_files="$3"
    shift 3

    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
    
    trap "[ -z $uid ] || chown -R ${uid} ${NEST_HOME};" EXIT QUIT ABRT TERM INT

    if ! $no_copy_files
    then
        copy_common_files
    fi

    
    $cmd $backend_args "$NEST_HOME" "${DEFAULT_SH[@]}" "${args[@]}"
}

#######################################
# Run Nest as real root via chroot command.
#
# Globals:
#   NEST_HOME (RO)         : The Nest 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 Nest.
# 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 Nest environment.
#   cmd ($3-?)               : The command to run inside Nest environment.
#                              Default command is defined by DEFAULT_SH variable.
# Output:
#   -                        : The command output.
#######################################
function run_env_as_chroot(){


    local backend_command="${1:-chroot}"
    local backend_args="$2"
    local no_copy_files="$3"
    shift 3

    _run_env_as_xroot "$backend_command" "$backend_args" "$no_copy_files" "$@"
}

###  source "${JUNEST_BASE}/lib/core/namespace.sh"

ID="id"
COMMON_BWRAP_OPTION="--bind "$NEST_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"


#######################################
# Run JuNest as fakeroot via bwrap
#
# Globals:
#   NEST_HOME (RO)          : The JuNest home directory.
#   DEFAULT_SH (RO)           : Contains the default command to run in Nest.
#   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 Nest environment.
#   cmd ($3-?)                : The command to run inside Nest 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_bwrap_fakeroot(){

    local backend_command="${1:-$BWRAP}"
    local backend_args="$2"
    local no_copy_files="$3"
    shift 3


    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
    
    PATH="/usr/bin" BWRAP="${backend_command}" 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:
#   NEST_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.
# Output:
#   -                        : The command output.
#######################################
function run_env_as_bwrap_user() {

    local backend_command="${1:-$BWRAP}"
    local backend_args="$2"
    local no_copy_files="$3"
    shift 3

    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 after the Nest directory creation
        #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 /  
    # In this script PATH=/usr/bin to avoid weird error messages
    PATH='/usr/bin' BWRAP="${backend_command}" bwrap_cmd $COMMON_BWRAP_OPTION $backend_args "${DEFAULT_SH[@]}" "${args[@]}"
}

### source "${JUNEST_BASE}/lib/core/proot.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}" proot_cmd "${backend_args}" "${DEFAULT_SH[@]}" "${args[@]}"
}

#######################################
# Run JuNest as fakeroot.
#
# Globals:
#   NEST_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."

    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

    _run_env_with_proot "$backend_command" "-0 ${bindings} -r ${NEST_HOME} $backend_args" "$@"
}

#######################################
# Run Nest as normal user.
#
# Globals:
#   NEST_HOME (RO)         : The Nest 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 "$(gettext '$ROOT_ACCESS_ERROR" "You cannot access with root privileges.')"

    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 after the Nest directory creation
        #copy_file /etc/localtime
        copy_passwd_and_group
    fi

    provide_common_bindings
    local bindings=${RESULT}
    unset RESULT

    _run_env_with_proot "$backend_command" "${bindings} -r ${NEST_HOME} $backend_args" "$@"
}

### source "${JUNEST_BASE}/lib/core/wrappers.sh"

#######################################
# Create bin wrappers
#
# Globals:
#   NEST_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%/}
    # Arguments inside a variable (i.e. `NEST_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 <<EOF > "${NEST_HOME}/usr/bin/nest_wrapper"
#!/usr/bin/env bash

eval "nest_args_array=(\${NEST_ARGS:-ns})"
nest "\${nest_args_array[@]}" -- \$(basename \${0}) "\$@"
EOF
    chmod +x "${NEST_HOME}/usr/bin/nest_wrapper"
    cd "${NEST_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 ${NEST_HOME}${bin_path}_wrappers/$file ]] && ! $force
        then
            continue
        fi
        rm -f "${NEST_HOME}${bin_path}_wrappers/$file"
        ln -s "${NEST_HOME}/usr/bin/nest_wrapper" "${NEST_HOME}${bin_path}_wrappers/$file"
    done
    # Remove wrappers no longer needed
    cd "${NEST_HOME}${bin_path}_wrappers" || return 1
    for file in *
    do
        [[ -e ${NEST_HOME}${bin_path}/$file || -L ${NEST_HOME}${bin_path}/$file ]] || rm -f "$file"
    done
    grep "$NEST_HOME${bin_path}_wrappers" /etc/profile.locale > /dev/null ||  echo "PATH=\$PATH:$NEST_HOME${bin_path}_wrappers" >> /etc/profile.locale
    # The following loop allows applications that have an entry in the nested system menu to also have an entry in the host system menu
    # if there is not already an entry for a such application in this menu. (Cancelled!)
    # Problem: The categories in the menu differ between Devuan/Debian and EasyOS
    #cd "${NEST_HOME}/usr/share/applications"
    #for file in *
    #do
     #cp -n $file /usr/share/applications/
    #done
}



###################################
### General functions           ###
###################################
usage() { 
    echo -e "$NAME : $DESCRIPTION"
    echo
    echo -e "$(gettext 'Usage'): $CMD [action] [options] [--] [command]"
    echo
    echo -e "$(gettext 'General'):"
    echo -e "-h, --help                                 $(gettext 'Show this help message')"
    echo
    echo -e "$(gettext 'Actions and options'):"
    echo -e "  s[etup]                                  $(eval_gettext 'Setup the Nest system in ${NEST_HOME}') (if not modified by the environment variable NEST_HOME or by the option -l)"
    echo -e "            -o, --option                   $(gettext 'Option for the debootstrap command. Each option must be prceded by a -o. See possible options on the man page https://linux.die.net/man/8/debootstrap'.) "
    echo -e "            -r, --release                  $(gettext 'Release code name. See /usr/share/debootstrap for the list of possible releases.The symbolic names (eg, unstable, testing,) corresponds to the distribution '.) "
    echo -e "                         -m, --mirror      $(gettext 'address of a repository that will provide the downloaded packages') "  
    echo -e "            -l, --location <directory>     $(gettext 'Setup the Nest system in the chosen directory'.) "
    echo
    echo -e "  d[elete]                                 $(eval_gettext 'Delete the Nest system from ${NEST_HOME} (or from the chosen directory if the option -l precedes)')"
    echo -e "            -l, --location <directory>     $(gettext 'Delete the Nest system from the chosen directory'.) "
    echo
    echo -e "  n[s]                                     $(gettext 'Access via Linux Namespaces using BubbleWrap (Default action)')"
    echo -e "            -f, --fakeroot                 $(eval_gettext 'Run $NAME with fakeroot privileges')"
    echo -e "            --backend-command <cmd>        $(gettext 'Bwrap command to use')"
    echo -e "            -b, --backend-args <args>      $(gettext 'Arguments for bwrap backend program')"
    echo -e "                                           $(eval_gettext '($CMD ns -b \"--help\" to check out the bwrap options)')"
    echo -e "            -n, --no-copy-files             $(eval_gettext '(Do not copy common etc files into $NAME environment')"
    echo
    echo -e "  p[root]                                  $(gettext 'Access via PRoot')"
    echo -e "            -f, --fakeroot                 $(eval_gettext 'Run $NAME with fakeroot privileges')"
    echo -e "            --backend-command <cmd>        $(gettext 'PRoot command to use')"
    echo -e "            -b, --backend-args <args>      $(gettext 'Arguments for PRoot backend program')"
    echo -e "                                           $(eval_gettext '($CMD proot -b \"--help\" to check out the PRoot options)')"
    echo -e "            -n, --no-copy-files             $(eval_gettext '(Do not copy common etc files into $NAME environment')"
    echo
    echo -e "  r[oot]                                   $(gettext 'Access with root privileges via classic chroot')"
    echo -e "            --backend-command <cmd>        $(gettext 'Chroot command to use')"
    echo -e "            -b, --backend-args <args>      $(gettext 'Arguments for chroot backend program')"
    echo -e "                                           $(eval_gettext '($CMD root -b \"--help\" to check out the chroot options)')"
    echo -e "            -n, --no-copy-files            $(eval_gettext 'Do not copy common etc files into $NAME environment')"
    echo
    echo -e "  create-bin-wrappers                      $(gettext 'Create a bin wrappers directory according to --bin-path option')"
    echo -e "                                           $(eval_gettext 'Default path is $NEST_HOME/usr/bin_wrappers')"
    echo -e "            -f, --force                    $(gettext 'Create the wrapper files even if they already exist')"
    echo -e "            -p, --bin-path                 $(gettext 'The source directory where executable are located in Nest')"
    echo -e "                                           $(gettext 'Default value is: /usr/bin')"
    echo 
}

function parse_arguments(){
    # Actions
    ACT_SETUP=false
    ACT_DELETE=false
    ACT_CREATE_WRAPPERS=false
    ACT_NAMESPACE=false
    ACT_PROOT=false
    ACT_ROOT=false
    ACT_HELP=false

    case "$1" in
        s|setup) ACT_SETUP=true ; shift ;;
        d|delete) ACT_DELETE=true ; shift ;;
        create-bin-wrappers) ACT_CREATE_WRAPPERS=true ; shift ;;
        n|ns) ACT_NAMESPACE=true ; shift ;;
        p|proot) ACT_PROOT=true ; shift ;;
        r|root) ACT_ROOT=true ; shift ;;
        -h|--help) ACT_HELP=true ; shift ;;
        *) ACT_NAMESPACE=true ;;
    esac

    if $ACT_SETUP
    then
        _parse_setup_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_ROOT
    then
        _parse_root_opts "$@"
    fi
}

function _parse_root_opts() {
    # Options:
    BACKEND_ARGS=""
    OPT_NO_COPY_FILES=false
    BACKEND_COMMAND=""

    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

    ARGS=()
    for arg in "$@"
    do
        ARGS+=("$arg")
    done
}

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_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() {

    while [[ -n "$1" ]]
    do
        case "$1" in
            -r|--release) shift ; RELEASE=$1 ; NEST_HOME=/$1 ; shift ;;
            -o|option) shift ; OPTIONS="$OPTIONS $1" ; shift ;;
            -m|--mirror) shift ; MIRROR=$1 ; shift ;;
            -l|--location) shift ; NEST_HOME=$1 ; shift ;;
            *) die "Invalid option $1" ;;
        esac
    done
}

function _parse_delete_opts() {

    while [[ -n "$1" ]]
    do
        case "$1" in
            -l|--location) shift ; NEST_HOME=$1 ; shift ;;
            *) die "Invalid option $1" ;;
        esac
    done
}

function execute_operation() {
    $ACT_HELP && usage && return

    if $ACT_SETUP; then
        #if is_env_installed
        #then
            #die "Error: The image cannot be installed since $NEST_HOME is not empty."
        #fi

        setup_env
        create_wrappers

        return
    fi

    if $ACT_DELETE; then
       delete_env
       return
    fi

    if ! is_env_installed
    then
        die "Error: The image is still not installed in $NEST_HOME. Run this first: $CMD setup"
    fi

    if $ACT_CREATE_WRAPPERS; then
        
        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_ROOT; then
        run_env=run_env_as_chroot
    fi

    # Call create_wrappers in case new bin files have been created
    if [ "${USER}" == "root" ]
    then
    trap "PATH=$PATH create_wrappers" EXIT QUIT TERM
    fi
    $run_env "$BACKEND_COMMAND" "${BACKEND_ARGS}" $OPT_NO_COPY_FILES "${ARGS[@]}"
}

function main() {
    parse_arguments "$@"
    execute_operation
}

main "$@"

.
It is possible to choose another release (of Devuan/Debian/Ubuntu) and another install directory by modifying the script (It's at the beginning of the script) or by using environment variable (Eg the install directory is NEST_HOME)

The script creates the file profile.locale) (if it doesn't exist) in /etc. It writes a line to it that adds the directory $NEST_HOME/usr/bin_wrappers to the PATH, so it will be possible to launch an executable in the nested Devuan as a command in EasyOS.
As the categories in the desktop menu differs betwwen EasyOs and Devuan, it is not possible to integrate automatically the (graphical) applications of Devuan in the menu of EasyOS.
Unfortunately this minimal system is big (more than 1500 MB)

There are other options for debootstrap and even variants of debootstrap : cdebootsrap, mmdebootstrap grml-debootstrap with other options
But for now I will not pursue my tests in this direction further.

Edit : fix in the script : There was missing dots line 456
Edit2: the line 786 was lightly modified
EDIT3: In EasyOS the file /root/.bash_profile is not used. It was replaced by /etc/profile.local in the script

Last edited by Caramel on Wed Oct 02, 2024 3:22 pm, edited 1 time in total.
Caramel
Posts: 532
Joined: Sun Oct 02, 2022 6:25 pm
Location: France
Has thanked: 106 times
Been thanked: 95 times

Void (glibc version) as nested system

Post by Caramel »

Same script with Devuan replaced by Void (and renamed vnest)

Code: Select all

#!/usr/bin/bash
#
#VNest is derived from JuNest (https://github.com/fsquillace/junest).
#JuNest was simplified and modified to use Void Linux instead of Arch Linux (as nested system) and to be usable in EasyOS.
#By default, a minimal Void distro is installed.
#
 
TEXTDOMAIN=vnest
export TEXTDOMAIN
. /usr/bin/gettext.sh

# To keep time in sync between the host and the Nest system while traveling to another time zone, uncomment the next line
#copy_file /etc/localtime

# To change the default values, modify these lines :

# Date is the date of the last archive (see https://repo-default.voidlinux.org/live/)
DATE=20240314

# The default VNest directory is at the root of the working partition(/mnt/wkg). So it is not included in the snapshots in EasyOS
VNEST_HOME=${VNEST_HOME:-/mnt/wkg/void}
VNEST_TEMPDIR=${VNEST_TEMPDIR:-/tmp}



###################################
### Used scripts          ###
###################################

# The used scripts in the directory where the executable is installed(=$JUNEST_BASE) by the real JuNest have been copied here, after simplification and small changes. 
# The parts are "utils, common, setup, chroot, namespace, proot and wrappers".
# The setup part was completely rewritten because here the nested system is Void and not Arch Linux.

###  source "${JUNEST_BASE}/lib/utils/utils.sh"

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"
}

###  source "${JUNEST_BASE}/lib/core/common.sh"
# This module contains all common functionalities for Nest.


NAME='Nest'
CMD='vnest'
DESCRIPTION=$(gettext 'A minimal Void distro that runs upon other Linux distros')

ROOT_ACCESS_ERROR=105




ARCH="x86_64"
LD_LIB="${VNEST_HOME}/lib64/ld-linux-x86-64.so.2"

ORIGIN_WD=$(pwd)

################## EXECUTABLES ################


# List of executables that are run inside Nest:
DEFAULT_SH=("/bin/bash" "--login")

# List of executables that are run in the host OS:
BWRAP="${VNEST_HOME}/usr/bin/bwrap"
PROOT="${VNEST_HOME}/usr/bin/proot-${ARCH}"

LD_EXEC="$LD_LIB --library-path ${VNEST_HOME}/usr/lib:${VNEST_HOME}/lib"

function bwrap_cmd(){
   $LD_EXEC "$BWRAP" "${@}"
}

function proot_cmd(){
    local proot_args="$1"
    shift
    
    if ${PROOT} ${proot_args} "${DEFAULT_SH[@]}" "-c" ":"
    then
        
        ${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 ###############

#######################################
# 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 $VNEST_HOME/etc/junest.
#
# Globals:
#  VNEST_HOME (RO)      : The JuNest home directory.
# Arguments:
#  None
# Returns:
#  None
# Output:
#  None
#######################################
# In EasyOS, getent exists so a test in junest was removed here. 
# The change of the lists of users and groups in the Nest system by the lists from EasyOS could cause problems
# because some users or groups system could exist or have differents ids in the Nest system and not in the host.
# So this change was removed
# E.g. video is the user 44 in Debian (see https://salsa.debian.org/debian/base-passwd/blob/master/group.master)
function copy_passwd_and_group(){
 
    getent passwd root >> "${VNEST_HOME}"/etc/passwd
    getent group root >> "${VNEST_HOME}"/etc/group
    getent passwd spot >> "${VNEST_HOME}"/etc/passwd
    getent group  spot >> "${VNEST_HOME}"/etc/group
    if [ ! "${USER}" == "root" ] && [ ! "${USER}" == "spot" ]
    then
    getent passwd "${USER}" >> "${VNEST_HOME}"/etc/passwd
    getent group  "${USER}" >> "${VNEST_HOME}"/etc/group
    fi

}

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
    # In this script the option --remove-destination was added
    [[ -r "$file" ]] && cp -f --remove-destination "$file" "${VNEST_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
}

###  source "${JUNEST_BASE}/lib/core/setup.sh"


# This function tests if $VNEST_HOME exists and is not empty. 
function is_env_installed(){
    [[ -d "$VNEST_HOME" ]] && [[ "$(ls -A "$VNEST_HOME")" ]] && return 0
    return 1
}

#######################################
# Setup the VNest system.
#######################################

function setup_env(){
	mkdir -p $VNEST_HOME
	wget https://repo-default.voidlinux.org/live/$DATE/void-x86_64-ROOTFS-$DATE.tar.xz --show-progress -qO $VNEST_HOME/void-x86_64-ROOTFS-$DATE.tar.xz
    tar xfp "$VNEST_HOME/void-x86_64-ROOTFS-$DATE.tar.xz" -C "$VNEST_HOME"
    copy_file /etc/localtime
    copy_common_files
    mkdir $VNEST_HOME/home/spot
    locale > $VNEST_HOME/etc/locale.conf # maybe useless
    sed -i s/#$LANG/$LANG/g $VNEST_HOME/etc/default/libc-locales
    mount -t proc /proc $VNEST_HOME/proc/
    mount -t sysfs /sys $VNEST_HOME/sys/
    chroot $VNEST_HOME xbps-install -Suy
    chroot $VNEST_HOME xbps-reconfigure --force glibc-locales
    chroot $VNEST_HOME xbps-install -y bubblewrap
    chroot $VNEST_HOME xbps-install -y proot
    umount $VNEST_HOME/sys
    umount $VNEST_HOME/proc
    touch /etc/profile.local
    grep "$VNEST_HOME/usr/bin_wrappers" /etc/profile.locale || ( echo n && echo "PATH=\$PATH:$VNEST_HOME/usr/bin_wrappers" >> /etc/profile.locale )
    mkdir -p "${VNEST_HOME}/usr/bin_wrappers"
}

#######################################
# Remove an existing Nest system.
#######################################
function delete_env(){
    ! ask "Are you sure to delete ${NAME} located in ${VNEST_HOME}" "N" && return
    if mountpoint -q "${VNEST_HOME}"
    then
        info "There are mounted directories inside ${VNEST_HOME}"
        if ! umount --force "${VNEST_HOME}"
        then
            error "Cannot umount directories in ${VNEST_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 "${VNEST_HOME}"/etc/ca-certificates
    if rm -rf "${VNEST_HOME}"
    then
        info "${NAME} deleted in ${VNEST_HOME}"
    else
        error "Error: Cannot delete ${NAME} in ${VNEST_HOME}"
    fi
}

###  source "${JUNEST_BASE}/lib/core/chroot.sh"


function _run_env_as_xroot(){
    local cmd=$1
    local backend_args="$2"
    local no_copy_files="$3"
    shift 3

    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
    
    trap "[ -z $uid ] || chown -R ${uid} ${VNEST_HOME};" EXIT QUIT ABRT TERM INT

    if ! $no_copy_files
    then
        copy_common_files
    fi

    
    $cmd $backend_args "$VNEST_HOME" "${DEFAULT_SH[@]}" "${args[@]}"
}

#######################################
# Run Nest as real root via chroot command.
#
# Globals:
#   VNEST_HOME (RO)         : The Nest 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 Nest.
# 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 Nest environment.
#   cmd ($3-?)               : The command to run inside Nest environment.
#                              Default command is defined by DEFAULT_SH variable.
# Output:
#   -                        : The command output.
#######################################
function run_env_as_chroot(){


    local backend_command="${1:-chroot}"
    local backend_args="$2"
    local no_copy_files="$3"
    shift 3

    _run_env_as_xroot "$backend_command" "$backend_args" "$no_copy_files" "$@"
}

###  source "${JUNEST_BASE}/lib/core/namespace.sh"

ID="id"
COMMON_BWRAP_OPTION="--bind "$VNEST_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"


#######################################
# Run JuNest as fakeroot via bwrap
#
# Globals:
#   VNEST_HOME (RO)          : The JuNest home directory.
#   DEFAULT_SH (RO)           : Contains the default command to run in Nest.
#   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 Nest environment.
#   cmd ($3-?)                : The command to run inside Nest 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_bwrap_fakeroot(){

    local backend_command="${1:-$BWRAP}"
    local backend_args="$2"
    local no_copy_files="$3"
    shift 3


    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
    
    PATH="/usr/bin" BWRAP="${backend_command}" 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:
#   VNEST_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.
# Output:
#   -                        : The command output.
#######################################
function run_env_as_bwrap_user() {

    local backend_command="${1:-$BWRAP}"
    local backend_args="$2"
    local no_copy_files="$3"
    shift 3

    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 after the Nest directory creation
        #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 /  
    # In this script PATH=/usr/bin to avoid weird error messages
    PATH='/usr/bin' BWRAP="${backend_command}" bwrap_cmd $COMMON_BWRAP_OPTION $backend_args "${DEFAULT_SH[@]}" "${args[@]}"
}

### source "${JUNEST_BASE}/lib/core/proot.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}" proot_cmd "${backend_args}" "${DEFAULT_SH[@]}" "${args[@]}"
}

#######################################
# Run JuNest as fakeroot.
#
# Globals:
#   VNEST_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."

    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

    _run_env_with_proot "$backend_command" "-0 ${bindings} -r ${VNEST_HOME} $backend_args" "$@"
}

#######################################
# Run Nest as normal user.
#
# Globals:
#   VNEST_HOME (RO)         : The Nest 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 "$(gettext '$ROOT_ACCESS_ERROR" "You cannot access with root privileges.')"

    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 after the Nest directory creation
        #copy_file /etc/localtime
        copy_passwd_and_group
    fi

    provide_common_bindings
    local bindings=${RESULT}
    unset RESULT

    _run_env_with_proot "$backend_command" "${bindings} -r ${VNEST_HOME} $backend_args" "$@"
}

### source "${JUNEST_BASE}/lib/core/wrappers.sh"

#######################################
# Create bin wrappers
#
# Globals:
#   VNEST_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%/}
    # Arguments inside a variable (i.e. `VNEST_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 <<EOF > "${VNEST_HOME}/usr/bin/vnest_wrapper"
#!/usr/bin/env bash

eval "vnest_args_array=(\${VNEST_ARGS:-ns})"
vnest "\${vnest_args_array[@]}" -- \$(basename \${0}) "\$@"
EOF
    chmod +x "${VNEST_HOME}/usr/bin/vnest_wrapper"
    cd "${VNEST_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 ${VNEST_HOME}${bin_path}_wrappers/$file ]] && ! $force
        then
            continue
        fi
        rm -f "${VNEST_HOME}${bin_path}_wrappers/$file"
        ln -s "${VNEST_HOME}/usr/bin/vnest_wrapper" "${VNEST_HOME}${bin_path}_wrappers/$file"
    done
    # Remove wrappers no longer needed
    cd "${VNEST_HOME}${bin_path}_wrappers" || return 1
    for file in *
    do
        [[ -e ${VNEST_HOME}${bin_path}/$file || -L ${VNEST_HOME}${bin_path}/$file ]] || rm -f "$file"
    done
   grep "$VNEST_HOME${bin_path}_wrappers" /etc/profile.locale > /dev/null ||  echo "PATH=\$PATH:$VNEST_HOME${bin_path}_wrappers" >> /etc/profile.locale

}



###################################
### General functions           ###
###################################
usage() { 
    echo -e "$NAME : $DESCRIPTION"
    echo
    echo -e "$(gettext 'Usage'): $CMD [action] [options] [--] [command]"
    echo
    echo -e "$(gettext 'General'):"
    echo -e "-h, --help                                 $(gettext 'Show this help message')"
    echo
    echo -e "$(gettext 'Actions and options'):"
    echo -e "  s[etup]                                  $(eval_gettext 'Setup the Nest system in ${VNEST_HOME}') (if not modified by the environment variable VNEST_HOME or by the option -l)"
    echo -e "            -o, --option                   $(gettext 'Option for the debootstrap command. Each option must be prceded by a -o. See possible options on the man page https://linux.die.net/man/8/debootstrap'.) "
    echo -e "            -r, --release                  $(gettext 'Release code name. See /usr/share/debootstrap for the list of possible releases.The symbolic names (eg, unstable, testing,) corresponds to the distribution '.) "
    echo -e "                         -m, --mirror      $(gettext 'address of a repository that will provide the downloaded packages') "  
    echo -e "            -l, --location <directory>     $(gettext 'Setup the Nest system in the chosen directory'.) "
    echo
    echo -e "  d[elete]                                 $(eval_gettext 'Delete the Nest system from ${VNEST_HOME} (or from the chosen directory if the option -l precedes)')"
    echo -e "            -l, --location <directory>     $(gettext 'Delete the Nest system from the chosen directory'.) "
    echo
    echo -e "  n[s]                                     $(gettext 'Access via Linux Namespaces using BubbleWrap (Default action)')"
    echo -e "            -f, --fakeroot                 $(eval_gettext 'Run $NAME with fakeroot privileges')"
    echo -e "            --backend-command <cmd>        $(gettext 'Bwrap command to use')"
    echo -e "            -b, --backend-args <args>      $(gettext 'Arguments for bwrap backend program')"
    echo -e "                                           $(eval_gettext '($CMD ns -b \"--help\" to check out the bwrap options)')"
    echo -e "            -n, --no-copy-files             $(eval_gettext '(Do not copy common etc files into $NAME environment')"
    echo
    echo -e "  p[root]                                  $(gettext 'Access via PRoot')"
    echo -e "            -f, --fakeroot                 $(eval_gettext 'Run $NAME with fakeroot privileges')"
    echo -e "            --backend-command <cmd>        $(gettext 'PRoot command to use')"
    echo -e "            -b, --backend-args <args>      $(gettext 'Arguments for PRoot backend program')"
    echo -e "                                           $(eval_gettext '($CMD proot -b \"--help\" to check out the PRoot options)')"
    echo -e "            -n, --no-copy-files             $(eval_gettext '(Do not copy common etc files into $NAME environment')"
    echo
    echo -e "  r[oot]                                   $(gettext 'Access with root privileges via classic chroot')"
    echo -e "            --backend-command <cmd>        $(gettext 'Chroot command to use')"
    echo -e "            -b, --backend-args <args>      $(gettext 'Arguments for chroot backend program')"
    echo -e "                                           $(eval_gettext '($CMD root -b \"--help\" to check out the chroot options)')"
    echo -e "            -n, --no-copy-files            $(eval_gettext 'Do not copy common etc files into $NAME environment')"
    echo
    echo -e "  create-bin-wrappers                      $(gettext 'Create a bin wrappers directory according to --bin-path option')"
    echo -e "                                           $(eval_gettext 'Default path is $VNEST_HOME/usr/bin_wrappers')"
    echo -e "            -f, --force                    $(gettext 'Create the wrapper files even if they already exist')"
    echo -e "            -p, --bin-path                 $(gettext 'The source directory where executable are located in Nest')"
    echo -e "                                           $(gettext 'Default value is: /usr/bin')"
    echo 
}

function parse_arguments(){
    # Actions
    ACT_SETUP=false
    ACT_DELETE=false
    ACT_CREATE_WRAPPERS=false
    ACT_NAMESPACE=false
    ACT_PROOT=false
    ACT_ROOT=false
    ACT_HELP=false

    case "$1" in
        s|setup) ACT_SETUP=true ; shift ;;
        d|delete) ACT_DELETE=true ; shift ;;
        create-bin-wrappers) ACT_CREATE_WRAPPERS=true ; shift ;;
        n|ns) ACT_NAMESPACE=true ; shift ;;
        p|proot) ACT_PROOT=true ; shift ;;
        r|root) ACT_ROOT=true ; shift ;;
        -h|--help) ACT_HELP=true ; shift ;;
        *) ACT_NAMESPACE=true ;;
    esac

    if $ACT_SETUP
    then
        _parse_setup_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_ROOT
    then
        _parse_root_opts "$@"
    fi
}

function _parse_root_opts() {
    # Options:
    BACKEND_ARGS=""
    OPT_NO_COPY_FILES=false
    BACKEND_COMMAND=""

    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

    ARGS=()
    for arg in "$@"
    do
        ARGS+=("$arg")
    done
}

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_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() {

    while [[ -n "$1" ]]
    do
        case "$1" in
            -r|--release) shift ; RELEASE=$1 ; VNEST_HOME=/$1 ; shift ;;
            -o|option) shift ; OPTIONS="$OPTIONS $1" ; shift ;;
            -m|--mirror) shift ; MIRROR=$1 ; shift ;;
            -l|--location) shift ; VNEST_HOME=$1 ; shift ;;
            *) die "Invalid option $1" ;;
        esac
    done
}

function _parse_delete_opts() {

    while [[ -n "$1" ]]
    do
        case "$1" in
            -l|--location) shift ; VNEST_HOME=$1 ; shift ;;
            *) die "Invalid option $1" ;;
        esac
    done
}

function execute_operation() {
    $ACT_HELP && usage && return

    if $ACT_SETUP; then
        #if is_env_installed
        #then
            #die "Error: The image cannot be installed since $VNEST_HOME is not empty."
        #fi

        setup_env
        create_wrappers

        return
    fi

    if $ACT_DELETE; then
       delete_env
       return
    fi

    if ! is_env_installed
    then
        die "Error: The image is still not installed in $VNEST_HOME. Run this first: $CMD setup"
    fi

    if $ACT_CREATE_WRAPPERS; then
        
        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_ROOT; then
        run_env=run_env_as_chroot
    fi

    # Call create_wrappers in case new bin files have been created
    if [ "${USER}" == "root" ]
    then
    trap "PATH=$PATH create_wrappers" EXIT QUIT TERM
    fi
    $run_env "$BACKEND_COMMAND" "${BACKEND_ARGS}" $OPT_NO_COPY_FILES "${ARGS[@]}"
}

function main() {
    parse_arguments "$@"
    execute_operation
}

main "$@"

After the installation of the base, the size of the Void directory is 311MB. It's acceptable but xbps-install says that the installation of okular required 1121MB on disk !
It's still a dead end.

EDIT: In EasyOS the file /root/.bash_profile is not used. It was replaced by /etc/profile.local in the script

Caramel
Posts: 532
Joined: Sun Oct 02, 2022 6:25 pm
Location: France
Has thanked: 106 times
Been thanked: 95 times

Slackware64/Salix64 as nested system.

Post by Caramel »

I conclude this series of tests with Slackware 15.0. I hoped the system was less big . In fact it's huge.
Maybe the installation without following scrupulously the dependencies would be better. But it's long and difficult to find and install only the files really necessary.

Here is the script (named slacknest)

Code: Select all

#!/usr/bin/bash
#
#SlackNest is derived from JuNest (https://github.com/fsquillace/junest).
#JuNest was simplified and modified to use Slackware instead of Arch Linux (as nested system) and to be usable in EasyOS.
#
 
TEXTDOMAIN=slacknest
export TEXTDOMAIN
. /usr/bin/gettext.sh

# To keep time in sync between the host and the Nest system while traveling to another time zone, uncomment the next line
#copy_file /etc/localtime

# To change the default values, modify these lines :

# Mirror used by this script  (see https://mirrors.slackware.com/slackware/slackware64-current/isolinux/initrd.img.mirrorlist for a list of mirrors)
MIRROR=https://mirrors.slackware.com

# The stable version at the time of the creation of the script is 15.0.
VERSION=15.0

# The default SlackNest directory is at the root of the working partition(/mnt/wkg). So it is not included in the snapshots in EasyOS
SLACKNEST_HOME=${SLACKNEST_HOME:-/mnt/wkg/slackware}
SLACKNEST_TEMPDIR=${SLACKNEST_TEMPDIR:-/tmp}

# To not install glibc-i18n(for languages other than English) in the SlackNest system during the setup change true to false
INSTALLGLIBCI18N=true

###################################
### Used scripts          ###
###################################

# The used scripts in the directory where the executable is installed(=$JUNEST_BASE) by the real JuNest have been copied here, after simplification and small changes. 
# The parts are "utils, common, setup, chroot, namespace and wrappers".
# The setup part was completely rewritten because here the nested system is Slackware and not Arch Linux.

###  source "${JUNEST_BASE}/lib/utils/utils.sh"

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"
}

###  source "${JUNEST_BASE}/lib/core/common.sh"
# This module contains all common functionalities for Nest.


NAME='Nest'
CMD='slacknest'
DESCRIPTION=$(gettext 'A minimal Void distro that runs upon other Linux distros')

ROOT_ACCESS_ERROR=105




ARCH="x86_64"
LD_LIB="${SLACKNEST_HOME}/lib64/ld-linux-x86-64.so.2"

ORIGIN_WD=$(pwd)

################## EXECUTABLES ################


# List of executables that are run inside SlackNest:
DEFAULT_SH=("/bin/bash" "--login")

# List of executables that are run in the host OS:
BWRAP="${SLACKNEST_HOME}/usr/bin/bwrap"

LD_EXEC="$LD_LIB --library-path ${SLACKNEST_HOME}/usr/lib:${SLACKNEST_HOME}/lib:${SLACKNEST_HOME}/usr/lib64:${SLACKNEST_HOME}/lib64"

function bwrap_cmd(){
   $LD_EXEC "$BWRAP" "${@}"
}

############## COMMON FUNCTIONS ###############

#######################################
# 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 $SLACKNEST_HOME/etc/junest.
#
# Globals:
#  SLACKNEST_HOME (RO)      : The JuNest home directory.
# Arguments:
#  None
# Returns:
#  None
# Output:
#  None
#######################################
# In EasyOS, getent exists so a test in junest was removed here. 
# The change of the lists of users and groups in the Nest system by the lists from EasyOS could cause problems
# because some users or groups system could exist or have differents ids in the Nest system and not in the host.
# So this change was removed
# E.g. video is the user 44 in Debian (see https://salsa.debian.org/debian/base-passwd/blob/master/group.master)

function copy_passwd_and_group(){
 
    getent passwd root >> "${SLACKNEST_HOME}"/etc/passwd
    getent group root >> "${SLACKNEST_HOME}"/etc/group
    getent passwd spot >> "${SLACKNEST_HOME}"/etc/passwd
    getent group  spot >> "${SLACKNEST_HOME}"/etc/group
    if [ ! "${USER}" == "root" ] && [ ! "${USER}" == "spot" ]
    then
    getent passwd "${USER}" >> "${SLACKNEST_HOME}"/etc/passwd
    getent group  "${USER}" >> "${SLACKNEST_HOME}"/etc/group
    fi

}

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
    # In this script the option --remove-destination was added
    [[ -r "$file" ]] && cp -f --remove-destination "$file" "${SLACKNEST_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
}

###  source "${JUNEST_BASE}/lib/core/setup.sh"


# This function tests if $SLACKNEST_HOME exists and is not empty. 
function is_env_installed(){
    [[ -d "$SLACKNEST_HOME" ]] && [[ "$(ls -A "$SLACKNEST_HOME")" ]] && return 0
    return 1
}

#######################################
# Setup the SlackNest system.
#######################################

function setup_env(){
	mkdir -p $SLACKNEST_HOME
	# Installation of a minimal system (The initrd.img comes from an iso, some modifications will be necessary)
	wget $MIRROR/slackware/slackware64-$VERSION/isolinux/initrd.img --show-progress -qO $SLACKNEST_HOME/initrd.img
	7z e $SLACKNEST_HOME/initrd.img -o$SLACKNEST_HOME
	rm $SLACKNEST_HOME/initrd.img
    cpio -iD $SLACKNEST_HOME < $SLACKNEST_HOME/initrd
	rm $SLACKNEST_HOME/initrd
    # the next line is optional   
    rm $SLACKNEST_HOME/{boot,cdrom} #{boot,cdrom,init,nfs}
    touch $SLACKNEST_HOME/etc/slackware-version
    touch /etc/profile.d/lang.sh
    echo '#!/bin/sh' > $SLACKNEST_HOME/etc/profile.d/lang.sh
    echo "export LANG=$LANG" >> $SLACKNEST_HOME/etc/profile.d/lang.sh
    sed -i 's|/mnt||g' $SLACKNEST_HOME/etc/profile
    mkdir $SLACKNEST_HOME/var/lock
    echo "Slackware 15.0" > $SLACKNEST_HOME/etc/slackware-version
    rm $SLACKNEST_HOME/var/log/packages 2>/dev/null
    mkdir -p $SLACKNEST_HOME/var/lib/pkgtools/packages
    cd $SLACKNEST_HOME/var/log
    ln -s ../lib/pkgtools/packages packages
    rm $SLACKNEST_HOME/var/log/scripts
    ln -s ../lib/pkgtools/scripts scripts
    mkdir -p $SLACKNEST_HOME/var/lib/pkgtools/scripts
    copy_file /etc/localtime
    copy_common_files
    # Download and installation of fundamental packages
    # Some packages have been modified since the release of Slackware 15.0. The new versions are available in the Slackware mirrors in a separate directory named patches.
    # The list of patches is fetched
    wget $MIRROR/slackware/slackware64-$VERSION/patches/PACKAGES.TXT -qO $SLACKNEST_HOME/patches-packages.txt
    # The installation commands must be executed in the chrooted Slackware system
    mount -t proc /proc $SLACKNEST_HOME/proc/
    mount -t sysfs /sys $SLACKNEST_HOME/sys
    # Update of pkgtools
    PKGTOOLS=$(grep "pkgtools.*txz" $SLACKNEST_HOME/patches-packages.txt | awk '{print $3}')
    wget $MIRROR/slackware/slackware64-$VERSION/patches/packages/$PKGTOOLS --show-progress -qO $SLACKNEST_HOME/$PKGTOOLS
    chroot $SLACKNEST_HOME installpkg $PKGTOOLS
    rm $SLACKNEST_HOME/$PKGTOOLS
    #Installation of aaa_base
    wget $MIRROR/slackware/slackware64-$VERSION/slackware64/a/aaa_base-15.0-x86_64-3.txz -qO $SLACKNEST_HOME/aaa_base-15.0-x86_64-3.txz
    chroot $SLACKNEST_HOME installpkg aaa_base-15.0-x86_64-3.txz
    rm $SLACKNEST_HOME/aaa_base-15.0-x86_64-3.txz
    # Installation of aaa_libraries
    wget $MIRROR/slackware/slackware64-$VERSION/slackware64/a/aaa_libraries-15.0-x86_64-19.txz 	--show-progress -qO $SLACKNEST_HOME/aaa_libraries-15.0-x86_64-19.txz
    chroot $SLACKNEST_HOME installpkg aaa_libraries-15.0-x86_64-19.txz
    rm $SLACKNEST_HOME/aaa_libraries-15.0-x86_64-19.txz
    # Installation of bin
    wget $MIRROR/slackware/slackware64-$VERSION/slackware64/a/bin-11.1-x86_64-5.txz --show-progress -qO $SLACKNEST_HOME/bin-11.1-x86_64-5.txz 
    chroot $SLACKNEST_HOME installpkg bin-11.1-x86_64-5.txz
    rm $SLACKNEST_HOME/bin-11.1-x86_64-5.txz     
    # Installation of findutils
    wget $MIRROR/slackware/slackware64-$VERSION/slackware64/a/findutils-4.8.0-x86_64-3.txz --show-progress -qO $SLACKNEST_HOME/findutils-4.8.0-x86_64-3.txz
    chroot $SLACKNEST_HOME installpkg findutils-4.8.0-x86_64-3.txz
    rm $SLACKNEST_HOME/findutils-4.8.0-x86_64-3.txz
    # Installation of grep
    wget $MIRROR/slackware/slackware64-$VERSION/slackware64/a/grep-3.7-x86_64-1.txz --show-progress -qO $SLACKNEST_HOME/grep-3.7-x86_64-1.txz
    chroot $SLACKNEST_HOME installpkg grep-3.7-x86_64-1.txz
    rm $SLACKNEST_HOME/grep-3.7-x86_64-1.txz
    # Installation of utils-linux
    UTILLINUX=$(grep "util-linux.*txz" $SLACKNEST_HOME/patches-packages.txt | awk '{print $3}')
    wget $MIRROR/slackware/slackware64-$VERSION/patches/packages/$UTILLINUX --show-progress -qO $SLACKNEST_HOME/$UTILLINUX
    chroot $SLACKNEST_HOME installpkg $UTILLINUX
    rm $SLACKNEST_HOME/$UTILLINUX
    # Installation of gzip
    GZIP=$(grep "gzip.*txz" $SLACKNEST_HOME/patches-packages.txt | awk '{print $3}')
    wget $MIRROR/slackware/slackware64-$VERSION/patches/packages/$GZIP --show-progress -qO $SLACKNEST_HOME/$GZIP       
    chroot $SLACKNEST_HOME installpkg $GZIP
    rm $SLACKNEST_HOME/$GZIP
    # Installation of glibc
    GLIBC=$(grep "glibc-2.*txz" $SLACKNEST_HOME/patches-packages.txt | awk '{print $3}')
    wget $MIRROR/slackware/slackware64-$VERSION/patches/packages/$GLIBC --show-progress -qO $SLACKNEST_HOME/$GLIBC
    chroot $SLACKNEST_HOME installpkg $GLIBC
    rm $SLACKNEST_HOME/$GLIBC
    # Installation of glibc-i18n (optional)
    if $INSTALLGLIBCI18N
     then
      GLIBCI18N=$(grep "glibc-i18n.*txz" $SLACKNEST_HOME/patches-packages.txt | awk '{print $3}')
      wget $MIRROR/slackware/slackware64-$VERSION/patches/packages/$GLIBCI18N --show-progress -qO $SLACKNEST_HOME/$GLIBCI18N
      chroot $SLACKNEST_HOME installpkg $GLIBCI18N
      rm $SLACKNEST_HOME/$GLIBCI18N
    fi 
    # Installation of gnupg
    wget $MIRROR/slackware/slackware64-$VERSION/slackware64/n/gnupg-1.4.23-x86_64-4.txz --show-progress -qO $SLACKNEST_HOME/gnupg-1.4.23-x86_64-4.txz
    chroot $SLACKNEST_HOME installpkg gnupg-1.4.23-x86_64-4.txz
    rm $SLACKNEST_HOME/gnupg-1.4.23-x86_64-4.txz
    # Installation of gnupg2
    GNUPG2=$(grep "gnupg2.*txz" $SLACKNEST_HOME/patches-packages.txt | awk '{print $3}')
    wget $MIRROR/slackware/slackware64-$VERSION/patches/packages/$GNUPG2 --show-progress -qO $SLACKNEST_HOME/$GNUPG2
    chroot $SLACKNEST_HOME installpkg $GNUPG2
    rm $SLACKNEST_HOME/$GNUPG2
    # Installation of ca-certificates
    CACERT=$(grep "ca-certificates.*txz" $SLACKNEST_HOME/patches-packages.txt | awk '{print $3}')
    wget $MIRROR/slackware/slackware64-$VERSION/patches/packages/$CACERT --show-progress -qO $SLACKNEST_HOME/$CACERT
    chroot $SLACKNEST_HOME installpkg $CACERT
    rm $SLACKNEST_HOME/$CACERT 
    # Download and installation of slap-get with  dependencies
    # Installation ofcyrus-sasl
    CYRUSSASL=$(grep "cyrus-sasl.*txz" $SLACKNEST_HOME/patches-packages.txt | awk '{print $3}')
    wget $MIRROR/slackware/slackware64-$VERSION/patches/packages/$CYRUSSASL --show-progress -qO $SLACKNEST_HOME/$CYRUSSASL
    chroot $SLACKNEST_HOME installpkg $CYRUSSASL
    rm $SLACKNEST_HOME/$CYRUSSASL
    # Installation of xz
    XZ=$(grep "xz-.*txz" $SLACKNEST_HOME/patches-packages.txt | awk '{print $3}')
    wget $MIRROR/slackware/slackware64-$VERSION/patches/packages/$XZ --show-progress -qO $SLACKNEST_HOME/$XZ
    chroot $SLACKNEST_HOME installpkg $XZ
    rm $SLACKNEST_HOME/$XZ
    # Installation of gpgme
    wget $MIRROR/slackware/slackware64-$VERSION/slackware64/n/gpgme-1.16.0-x86_64-3.txz --show-progress -qO $SLACKNEST_HOME/gpgme-1.16.0-x86_64-3.txz
    chroot $SLACKNEST_HOME installpkg gpgme-1.16.0-x86_64-3.txz
    rm $SLACKNEST_HOME/gpgme-1.16.0-x86_64-3.txz
    # Installation of nghttp2
    NGHTTP2=$(grep "nghttp2.*txz" $SLACKNEST_HOME/patches-packages.txt | awk '{print $3}')
    wget $MIRROR/slackware/slackware64-$VERSION/patches/packages/$NGHTTP2 --show-progress -qO $SLACKNEST_HOME/$NGHTTP2
    chroot $SLACKNEST_HOME installpkg $NGHTTP2
    rm $SLACKNEST_HOME/$NGHTTP2
    # Installation of libasssuan
    wget $MIRROR/slackware/slackware64-$VERSION/slackware64/n/libassuan-2.5.5-x86_64-1.txz --show-progress -qO $SLACKNEST_HOME/libassuan-2.5.5-x86_64-1.txz
    chroot $SLACKNEST_HOME installpkg libassuan-2.5.5-x86_64-1.txz
    rm $SLACKNEST_HOME/libassuan-2.5.5-x86_64-1.txz
    # Installation of brotli
    wget $MIRROR/slackware/slackware64-$VERSION/slackware64/l/brotli-1.0.9-x86_64-7.txz --show-progress -qO $SLACKNEST_HOME/brotli-1.0.9-x86_64-7.txz
    chroot $SLACKNEST_HOME installpkg brotli-1.0.9-x86_64-7.txz
    rm $SLACKNEST_HOME/brotli-1.0.9-x86_64-7.txz
    # slapt-get and its dependency spkg are provided by Salix
    # Installation of spkg
    wget https://download.salixos.org/x86_64/15.0/PACKAGES.TXT --show-progress -qO $SLACKNEST_HOME/salix-packages.txt
    SPKG=$(grep "PACKAGE NAME.*spkg" $SLACKNEST_HOME/salix-packages.txt | awk '{print $3}')
    wget https://download.salixos.org/x86_64/15.0/salix/a/$SPKG --show-progress -qO $SLACKNEST_HOME/$SPKG
    chroot $SLACKNEST_HOME installpkg $SPKG
    rm $SLACKNEST_HOME/$SPKG
    # Installation of slapt-get
    SLAPTGET=$(grep "PACKAGE NAME.*slapt-get" $SLACKNEST_HOME/salix-packages.txt | awk '{print $3}')
    wget https://download.salixos.org/x86_64/15.0/salix/ap/$SLAPTGET --show-progress -qO $SLACKNEST_HOME/$SLAPTGET
    chroot $SLACKNEST_HOME installpkg $SLAPTGET
    rm $SLACKNEST_HOME/$SLAPTGET
    # Initialization of files used by slapt-get
    chroot $SLACKNEST_HOME slapt-get --update
    # Now we can use slapt-get to download and install packages more simply
    # Installation of packages useful for gslapt
    chroot $SLACKNEST_HOME slapt-get --install libpng #maybe useless
    chroot $SLACKNEST_HOME slapt-get --install shared-mime-info
    chroot $SLACKNEST_HOME slapt-get --install dejavu-fonts-ttf
    chroot $SLACKNEST_HOME slapt-get --install adwaita-icon-theme
    chroot $SLACKNEST_HOME slapt-get --install hicolor-icon-theme
    # Installation of gslapt, an fronend for slapt-get inspired by synaptic from Debian
    chroot $SLACKNEST_HOME slapt-get --install gslapt
    # Installation of bubblewrap (used by slacknest)
    chroot $SLACKNEST_HOME slapt-get --install bubblewrap
    # No need for the chrooted environment
    umount $SLACKNEST_HOME/sys
    umount $SLACKNEST_HOME/proc
    touch /etc/profile.local
    mkdir -p "${SLACKNEST_HOME}/usr/bin_wrappers"
    mkdir -p "${SLACKNEST_HOME}/usr/sbin_wrappers"
    grep "$SLACKNEST_HOME/usr/bin_wrappers" /etc/profile.local || echo "PATH=\$PATH:$SLACKNEST_HOME/usr/bin_wrappers" >> /etc/profile.locale
    grep "$SLACKNEST_HOME/usr/sbin_wrappers" /etc/profile.local || echo "PATH=\$PATH:$SLACKNEST_HOME/usr/sbin_wrappers" >> /etc/profile.locale
}

#######################################
# Remove an existing Nest system.
#######################################
function delete_env(){
    ! ask "Are you sure to delete ${NAME} located in ${SLACKNEST_HOME}" "N" && return
    if mountpoint -q "${SLACKNEST_HOME}"
    then
        info "There are mounted directories inside ${SLACKNEST_HOME}"
        if ! umount --force "${SLACKNEST_HOME}"
        then
            error "Cannot umount directories in ${SLACKNEST_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 "${SLACKNEST_HOME}"/etc/ca-certificates
    if rm -rf "${SLACKNEST_HOME}"
    then
        info "${NAME} deleted in ${SLACKNEST_HOME}"
    else
        error "Error: Cannot delete ${NAME} in ${SLACKNEST_HOME}"
    fi
}

###  source "${JUNEST_BASE}/lib/core/chroot.sh"


function _run_env_as_xroot(){
    local cmd=$1
    local backend_args="$2"
    local no_copy_files="$3"
    shift 3

    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
    
    trap "[ -z $uid ] || chown -R ${uid} ${SLACKNEST_HOME};" EXIT QUIT ABRT TERM INT

    if ! $no_copy_files
    then
        copy_common_files
    fi

    
    $cmd $backend_args "$SLACKNEST_HOME" "${DEFAULT_SH[@]}" "${args[@]}"
}

#######################################
# Run Nest as real root via chroot command.
#
# Globals:
#   SLACKNEST_HOME (RO)         : The Nest 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 Nest.
# 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 Nest environment.
#   cmd ($3-?)               : The command to run inside Nest environment.
#                              Default command is defined by DEFAULT_SH variable.
# Output:
#   -                        : The command output.
#######################################
function run_env_as_chroot(){


    local backend_command="${1:-chroot}"
    local backend_args="$2"
    local no_copy_files="$3"
    shift 3

    _run_env_as_xroot "$backend_command" "$backend_args" "$no_copy_files" "$@"
}

###  source "${JUNEST_BASE}/lib/core/namespace.sh"

ID="id"
COMMON_BWRAP_OPTION="--bind "$SLACKNEST_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"


#######################################
# Run JuNest as fakeroot via bwrap
#
# Globals:
#   SLACKNEST_HOME (RO)          : The JuNest home directory.
#   DEFAULT_SH (RO)           : Contains the default command to run in Nest.
#   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 Nest environment.
#   cmd ($3-?)                : The command to run inside Nest 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_bwrap_fakeroot(){

    local backend_command="${1:-$BWRAP}"
    local backend_args="$2"
    local no_copy_files="$3"
    shift 3


    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
    
    PATH="/usr/bin" BWRAP="${backend_command}" 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:
#   SLACKNEST_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.
# Output:
#   -                        : The command output.
#######################################
function run_env_as_bwrap_user() {

    local backend_command="${1:-$BWRAP}"
    local backend_args="$2"
    local no_copy_files="$3"
    shift 3

    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 after the Nest directory creation
        #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 /  
    # In this script PATH=/usr/bin to avoid weird error messages
   PATH='/usr/bin' BWRAP="${backend_command}" bwrap_cmd $COMMON_BWRAP_OPTION $backend_args "${DEFAULT_SH[@]}" "${args[@]}"
}




### source "${JUNEST_BASE}/lib/core/wrappers.sh"

#######################################
# Create bin wrappers
#
# Globals:
#   SLACKNEST_HOME (RO)         : The SlackNest 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%/}
    # Arguments inside a variable (i.e. `SLACKNEST_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 <<EOF > "${SLACKNEST_HOME}/usr/bin/slacknest_wrapper"
#!/usr/bin/env bash

eval "slacknest_args_array=(\${SLACKNEST_ARGS:-ns})"
slacknest "\${slacknest_args_array[@]}" -- \$(basename \${0}) "\$@"
EOF
    chmod +x "${SLACKNEST_HOME}/usr/bin/slacknest_wrapper"
    cd "${SLACKNEST_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 ${SLACKNEST_HOME}${bin_path}_wrappers/$file ]] && ! $force
        then
            continue
        fi
        rm -f "${SLACKNEST_HOME}${bin_path}_wrappers/$file"
        ln -s "${SLACKNEST_HOME}/usr/bin/slacknest_wrapper" "${SLACKNEST_HOME}${bin_path}_wrappers/$file"
    done
    # Remove wrappers no longer needed
    cd "${SLACKNEST_HOME}${bin_path}_wrappers" || return 1
    for file in *
    do
        [[ -e ${SLACKNEST_HOME}${bin_path}/$file || -L ${SLACKNEST_HOME}${bin_path}/$file ]] || rm -f "$file"
    done
 #  grep "$SLACKNEST_HOME${bin_path}_wrappers" /root/.bash_profile > /dev/null ||  echo "PATH=\$PATH:$SLACKNEST_HOME${bin_path}_wrappers" >> /root/.bash_profile

    grep "$SLACKNEST_HOME${bin_path}_wrappers" /etc/profile.locale > /dev/null ||  echo "PATH=\$PATH:$SLACKNEST_HOME${bin_path}_wrappers" >> /etc/profile.locale


}

###################################
### General functions           ###
###################################
usage() { 
    echo -e "$NAME : $DESCRIPTION"
    echo
    echo -e "$(gettext 'Usage'): $CMD [action] [options] [--] [command]"
    echo
    echo -e "$(gettext 'General'):"
    echo -e "-h, --help                                 $(gettext 'Show this help message')"
    echo
    echo -e "$(gettext 'Actions and options'):"
    echo -e "  s[etup]                                  $(eval_gettext 'Setup the Nest system in ${SLACKNEST_HOME}') (if not modified by the environment variable SLACKNEST_HOME or by the option -l)"
    echo -e "            -o, --option                   $(gettext 'Option for the debootstrap command. Each option must be prceded by a -o. See possible options on the man page https://linux.die.net/man/8/debootstrap'.) "
    echo -e "            -r, --release                  $(gettext 'Release code name. See /usr/share/debootstrap for the list of possible releases.The symbolic names (eg, unstable, testing,) corresponds to the distribution '.) "
    echo -e "                         -m, --mirror      $(gettext 'address of a repository that will provide the downloaded packages') "  
    echo -e "            -l, --location <directory>     $(gettext 'Setup the Nest system in the chosen directory'.) "
    echo
    echo -e "  d[elete]                                 $(eval_gettext 'Delete the Nest system from ${SLACKNEST_HOME} (or from the chosen directory if the option -l precedes)')"
    echo -e "            -l, --location <directory>     $(gettext 'Delete the Nest system from the chosen directory'.) "
    echo
    echo -e "  n[s]                                     $(gettext 'Access via Linux Namespaces using BubbleWrap (Default action)')"
    echo -e "            -f, --fakeroot                 $(eval_gettext 'Run $NAME with fakeroot privileges')"
    echo -e "            --backend-command <cmd>        $(gettext 'Bwrap command to use')"
    echo -e "            -b, --backend-args <args>      $(gettext 'Arguments for bwrap backend program')"
    echo -e "                                           $(eval_gettext '($CMD ns -b \"--help\" to check out the bwrap options)')"
    echo -e "            -n, --no-copy-files             $(eval_gettext '(Do not copy common etc files into $NAME environment')"
    echo
    echo -e "  r[oot]                                   $(gettext 'Access with root privileges via classic chroot')"
    echo -e "            --backend-command <cmd>        $(gettext 'Chroot command to use')"
    echo -e "            -b, --backend-args <args>      $(gettext 'Arguments for chroot backend program')"
    echo -e "                                           $(eval_gettext '($CMD root -b \"--help\" to check out the chroot options)')"
    echo -e "            -n, --no-copy-files            $(eval_gettext 'Do not copy common etc files into $NAME environment')"
    echo
    echo -e "  create-bin-wrappers                      $(gettext 'Create a bin wrappers directory according to --bin-path option')"
    echo -e "                                           $(eval_gettext 'Default path is $SLACKNEST_HOME/usr/bin_wrappers')"
    echo -e "            -f, --force                    $(gettext 'Create the wrapper files even if they already exist')"
    echo -e "            -p, --bin-path                 $(gettext 'The source directory where executable are located in Nest')"
    echo -e "                                           $(gettext 'Default value is: /usr/bin')"
    echo 
}

function parse_arguments(){
    # Actions
    ACT_SETUP=false
    ACT_DELETE=false
    ACT_CREATE_WRAPPERS=false
    ACT_NAMESPACE=false
    ACT_ROOT=false
    ACT_HELP=false

    case "$1" in
        s|setup) ACT_SETUP=true ; shift ;;
        d|delete) ACT_DELETE=true ; shift ;;
        create-bin-wrappers) ACT_CREATE_WRAPPERS=true ; shift ;;
        n|ns) ACT_NAMESPACE=true ; shift ;;
        r|root) ACT_ROOT=true ; shift ;;
        -h|--help) ACT_HELP=true ; shift ;;
        *) ACT_NAMESPACE=true ;;
    esac

    if $ACT_SETUP
    then
        _parse_setup_opts "$@"
    elif $ACT_CREATE_WRAPPERS
    then
        _parse_create_wrappers_opts "$@"
    elif $ACT_NAMESPACE
    then
        _parse_ns_opts "$@"
    elif $ACT_ROOT
    then
        _parse_root_opts "$@"
    fi
}

function _parse_root_opts() {
    # Options:
    BACKEND_ARGS=""
    OPT_NO_COPY_FILES=false
    BACKEND_COMMAND=""

    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

    ARGS=()
    for arg in "$@"
    do
        ARGS+=("$arg")
    done
}

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_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() {

    while [[ -n "$1" ]]
    do
        case "$1" in
            -r|--release) shift ; RELEASE=$1 ; SLACKNEST_HOME=/$1 ; shift ;;
            -o|option) shift ; OPTIONS="$OPTIONS $1" ; shift ;;
            -m|--mirror) shift ; MIRROR=$1 ; shift ;;
            -l|--location) shift ; SLACKNEST_HOME=$1 ; shift ;;
            *) die "Invalid option $1" ;;
        esac
    done
}

function _parse_delete_opts() {

    while [[ -n "$1" ]]
    do
        case "$1" in
            -l|--location) shift ; SLACKNEST_HOME=$1 ; shift ;;
            *) die "Invalid option $1" ;;
        esac
    done
}

function execute_operation() {
    $ACT_HELP && usage && return

    if $ACT_SETUP; then
        #if is_env_installed
        #then
            #die "Error: The image cannot be installed since $SLACKNEST_HOME is not empty."
        #fi

      ##  setup_env
        create_wrappers
        # create wrappers for the executable in the directory /usr/sbin of the nested system
        create_wrappers false /usr/sbin

        return
    fi

    if $ACT_DELETE; then
       delete_env
       return
    fi

    if ! is_env_installed
    then
        die "Error: The image is still not installed in $SLACKNEST_HOME. Run this first: $CMD setup"
    fi

    if $ACT_CREATE_WRAPPERS; then
        
        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_ROOT; then
        run_env=run_env_as_chroot
    fi

    # Call create_wrappers in case new bin files have been created
    if [ "${USER}" == "root" ]
    then
    trap "PATH=$PATH create_wrappers && PATH=$PATH create_wrappers false /usr/sbin" EXIT QUIT TERM
    fi
    $run_env "$BACKEND_COMMAND" "${BACKEND_ARGS}" $OPT_NO_COPY_FILES "${ARGS[@]}"
}

function main() {
    parse_arguments "$@"
    execute_operation
}

main "$@"

In the first try, the command slackpkg (with slacpkg+, a plugin that allows to add third-party repositories to slackpkg) was used to download and install package.
But it's slow and there is no dependencies management.

The second try was with slpkg (https://github.com/unixbhaskar/slpkg). But the only available binary (from https://slackers.it/) is for Slackware current and I did not succeed to use it.

In the last try ( the shared script), slapt-get and gslapt(a graphical application) are used to manage the packages.
These utils are inspired by apt-get and synaptic from Debian.
They use spkg (https://spkg.megous.com/ (do not confuse with slpkg) an alternative to the pkgtools used in Slackware.

spkg, slapt-get and gslapt are available in Salix (a derived of Slackware)

The repository for Salix are automatically used by slapt-get and gslapt (in addition of the slackware repository)

PS : The size of the locale directory(/usr/lib64/locale) is more than 900 MB!. We can remove the useless locales or not install the locales by replacing INSTALLGLIBCI18N=true by INSTALLGLIBCI18N=false at the beginning of the script.

Unless someone is really interested, I will stop with the nested systems.

Stogie
Posts: 66
Joined: Thu Oct 07, 2021 8:10 pm
Has thanked: 10 times
Been thanked: 10 times

Re: Experiment with JuNest

Post by Stogie »

Someone REALLY needs to make a universal package manager that can access, and install, ALL kinds of Linux packages on ANY distro, and WITHOUT downloading hundreds of megabytes or gigabytes of crap onto the system.

Oh, and it needs to WORK EASILY AND SIMPLY without requiring a PhD degree in Linux internals and a bachelor's degree in computer science and 20+ years of experience with mucking around with deep OS details.

That would be incredibly awesome, and would propel the market share of Linux-on-the-desktop to new heights VERY quickly.

It'd also make Easy OS tremendously better (along with every other Linux distro in existence too).

Post Reply

Return to “EasyOS”