mirror of
https://github.com/termux/proot-distro.git
synced 2024-09-22 03:50:56 +00:00
2954 lines
101 KiB
Bash
Executable File
2954 lines
101 KiB
Bash
Executable File
#!@TERMUX_PREFIX@/bin/bash
|
|
# shellcheck disable=SC2239
|
|
##
|
|
## Script for managing PRoot containers with Linux distributions.
|
|
##
|
|
## Originally created by Leonid Pliushch <sylirre@termux.dev> for Termux
|
|
## project.
|
|
##
|
|
## This program is free software: you can redistribute it and/or modify
|
|
## it under the terms of the GNU General Public License as published by
|
|
## the Free Software Foundation, either version 3 of the License, or
|
|
## (at your option) any later version.
|
|
##
|
|
## This program is distributed in the hope that it will be useful,
|
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
## GNU General Public License for more details.
|
|
##
|
|
## You should have received a copy of the GNU General Public License
|
|
## along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
##
|
|
|
|
PROGRAM_VERSION="4.16.0"
|
|
|
|
#############################################################################
|
|
##
|
|
## >>> CONTRIBUTOR'S NOTICE <<<
|
|
##
|
|
## By contributing to the PRoot-Distro project you are obligated to follow
|
|
## the next mandatory rules:
|
|
##
|
|
## 1. The PRoot-Distro project is built around PRoot and is intended to be
|
|
## developed as a wrapper around PRoot. Supporting classical Linux chroot
|
|
## is outside of the project scope.
|
|
##
|
|
## 2. The PRoot-Distro is designed to work only in Termux. Supporting other
|
|
## distributions where PRoot is available is out of scope.
|
|
##
|
|
## 3. The PRoot-Distro is intended to be used by a non-root user. Using it
|
|
## as superuser (root) is possible but has a potential of messing up
|
|
## ownership of Termux environment directories and in some cases it also
|
|
## may wipe Android device data completely.
|
|
##
|
|
## The measures such as checking current user id must be used to prevent
|
|
## using PRoot-Distro by the root user.
|
|
##
|
|
## 4. PRoot-Distro is not a generic tool. Its idea is to provide user a
|
|
## high level intrument for managing Linux distributions for PRoot and
|
|
## hide the all underlying routines and automate everything that is
|
|
## possible. It is not all-in-one swiss-knife framework for PRoot.
|
|
## Therefore PRoot-Distro does not provide a way to modify its behavior.
|
|
##
|
|
## ***
|
|
##
|
|
## 5. Follow the general design.
|
|
##
|
|
## Proot Distro has a specific, well defined structure which makes it
|
|
## unique and separates from analogous projects. This structure expected
|
|
## to be followed during whole project lifetime. If you have ideas
|
|
## how to restructure the PRoot-Distro, then please keep them in your
|
|
## own fork! Source customization is not welcome.
|
|
##
|
|
## 5.1 PRoot-Distro is written in Bash script language with using features
|
|
## which are not compatible with POSIX shell. The script itself is
|
|
## monolithic and is not supposed to be split on multiple parts.
|
|
##
|
|
## 5.2 The tabs must be used for indenting code blocks everywhere.
|
|
##
|
|
## 5.3 All features are split on functions. There are 3 types of functions:
|
|
##
|
|
## * Commands: implement specific features of PRoot-Distro such as
|
|
## install, backup, login the distribution. These functions have
|
|
## following name scheme: 'command_name()'.
|
|
## * Command help: supplementary functions implementing informational
|
|
## output describing usage of PRoot-Distro commands. These functions
|
|
## are named as 'command_name_help()' and are defined AFTER the
|
|
## commands.
|
|
## * Utility functions: reusable or very long pieces of code.
|
|
##
|
|
## 5.4 Each command function definition should begin with comment section.
|
|
##
|
|
## Comment sections logically separate functions on groups and provide
|
|
## a brief overview which part of PRoot-Distro is implemented here. The
|
|
## comment must provide a description of how the function works.
|
|
##
|
|
## Utility functions are allowed to have a very brief description and
|
|
## command help functions.
|
|
##
|
|
## Command help functions should not have comments. They are self
|
|
## descriptive.
|
|
##
|
|
## 5.5 Code pieces that do non-obvious actions must be commented.
|
|
##
|
|
## 5.6 Functions are not allowed to define global variables. Use 'local'
|
|
## keyword to define the local variables instead.
|
|
##
|
|
## 5.7 Global variables must be defined either at beginning of PRoot-Distro
|
|
## script or at the entry point.
|
|
##
|
|
## 5.8 Use of getopt for options handling is considered as non-flexible
|
|
## and thus not allowed.
|
|
##
|
|
## 5.9 The checks for error conditions must be implemented. If error has
|
|
## been encountered, a message should be printed on stderr and further
|
|
## execution should be terminated.
|
|
##
|
|
## 5.10 If error was encountered inside function, exiting should be done
|
|
## using the command 'return 1' to pass a status code to the caller.
|
|
## Use of exit command is not allowed unless used in script entry point.
|
|
##
|
|
## 5.11 User input in command functions must be validated during the
|
|
## command line arguments handling.
|
|
##
|
|
## 5.12 Use heredocs to write files, especially if their content is long.
|
|
##
|
|
## 5.13 Certain configuration such as plug-in directory path may be set only
|
|
## during installation of the PRoot-Distro. Such configuration is
|
|
## treated as persistent and should not be changed by user.
|
|
##
|
|
## 5.14 PRoot-Distro uses string place holders to define certain common
|
|
## values for some variables:
|
|
##
|
|
## * @TERMUX_APP_PACKAGE@ - sets Termux app package (com.termux).
|
|
## * @TERMUX_PREFIX@ - sets the installation prefix path.
|
|
## * @TERMUX_HOME@ - sets the Termux home directory path.
|
|
##
|
|
## It is not allowed to hardcode the mentioned values instead of using
|
|
## the place holder.
|
|
##
|
|
## ***
|
|
##
|
|
## 6. All informational messages are printed in specific format.
|
|
##
|
|
## 6.1 Use function 'msg' to print messages.
|
|
##
|
|
## 6.2 Messages printed by help functions must fit in 76 columns. If the
|
|
## message is too long, split in on multiple lines. This is not relevant
|
|
## to errors and other informational messages. Pay extra attention to
|
|
## keep the message properly indented.
|
|
##
|
|
## 6.3 All messages printable by PRoot-Distro script must support colored
|
|
## text through the escape sequences defined below by variables such as
|
|
## RED (red), BRED (bold red text), YELLOW, BYELLOW, etc.
|
|
##
|
|
## 6.4 PRoot-Distro operation errors must have bold red color (${BRED}).
|
|
## The key information such as arguments caused an error should be
|
|
## encloded in quotes and highlighted by yellow color (${YELLOW}).
|
|
##
|
|
## 6.5 Messages should be terminated by ${RST} which resets the text colors
|
|
## and other attributes.
|
|
##
|
|
## 6.7 Informational messages produced by PRoot-Distro command steps must
|
|
## have the following format:
|
|
##
|
|
## ${BLUE}[${GREEN}*${BLUE}] ${CYAN}Message...${RST}"
|
|
##
|
|
## 6.8 If the step encountered a condition where an error should be printed,
|
|
## the following message format should be used instead:
|
|
##
|
|
## ${BLUE}[${RED}!${BLUE}] ${CYAN}Error or warning message.${RST}
|
|
##
|
|
## 6.9 Warnings about issues happened due to unexpected circumstances rather
|
|
## than due to failure of PRoot-Distro actions should be printed in
|
|
## this format:
|
|
##
|
|
## ${BRED}Warning: message.${RST}
|
|
##
|
|
## Similarly to warnings, the error messages should have this format:
|
|
##
|
|
## ${BRED}Error: message.${RST}
|
|
##
|
|
## ***
|
|
##
|
|
## 7. Handling distributions.
|
|
##
|
|
## 7.1 PRoot-Distro script is intended to be distribution-agnostic. That
|
|
## means it treats all distributions equally and handles them in the
|
|
## same way despite the possible differences at level of distribition
|
|
## root file system package.
|
|
##
|
|
## 7.2 Support of specific distribution is enabled by using a plug-in. The
|
|
## plug-in is a Bash script that is sourced by PRoot-Distro and has the
|
|
## following format:
|
|
##
|
|
## DISTRO_NAME="Example"
|
|
## DISTRO_COMMENT="Example distribution."
|
|
##
|
|
## TARBALL_STRIP_OPT=1
|
|
##
|
|
## TARBALL_URL['aarch64']="https://example.com/archive-aarch64.tar.gz"
|
|
## TARBALL_URL['arm']="https://example.com/archive-armv7.tar.gz"
|
|
## TARBALL_URL['i686']="https://example.com/archive-i386.tar.gz"
|
|
## TARBALL_URL['riscv64']="https://example.com/archive-riscv64.tar.gz"
|
|
## TARBALL_URL['x86_64']="https://example.com/archive-amd64.tar.gz"
|
|
##
|
|
## TARBALL_SHA256['aarch64']="0000000000000000000000000000000000000000000000000000000000000000"
|
|
## TARBALL_SHA256['arm']="0000000000000000000000000000000000000000000000000000000000000000"
|
|
## TARBALL_SHA256['i686']="0000000000000000000000000000000000000000000000000000000000000000"
|
|
## TARBALL_SHA256['riscv64']="0000000000000000000000000000000000000000000000000000000000000000"
|
|
## TARBALL_SHA256['x86_64']="0000000000000000000000000000000000000000000000000000000000000000"
|
|
##
|
|
## distro_setup() {
|
|
## run_proot_cmd touch /etc/hello-world
|
|
## run_proot_cmd bash -c "echo '127.0.0.1 hello-world' >> /etc/hosts"
|
|
## }
|
|
##
|
|
## 7.4 The variable DISTRO_NAME specifies a full name of distribution such
|
|
## as "Ubuntu" or "Debian (stable)". This variable is mandatory.
|
|
##
|
|
## 7.3 The variable TARBALL_URL is a Bash associative array which contains
|
|
## URLs to distribution rootfs tarball for given CPU architectures.
|
|
## This variable is mandatory. The URL should start with proper protocol
|
|
## scheme. For example, https://, file://, ftp:// etc. to access local
|
|
## or remote tarball file.
|
|
##
|
|
## 7.4 The variable TARBALL_SHA256 is a Bash associative array which
|
|
## contains SHA-256 checksums of rootfs tarballs for given CPU
|
|
## architectures. This variable is mandatory.
|
|
##
|
|
## 7.5 Post-installation steps that may be required for some distributions
|
|
## are defined in 'distro_setup()' function (optional). This function
|
|
## has access to all variables defined by PRoot-Distro during the
|
|
## execution of 'command_install()'.
|
|
##
|
|
## 7.6 Commands inside 'distro_setup()' that are intended to be executed in
|
|
## distribution environment must be defined as arguments of the
|
|
## command 'run_proot_cmd'.
|
|
##
|
|
## 7.7 Distributions must be adressed by their alias which in fact is a
|
|
## file name of plug-in except the extension part.
|
|
##
|
|
## 7.8 The alias files are located in $PREFIX/etc/proot-distro and have
|
|
## extensions .sh or .override.sh.
|
|
##
|
|
## * dist.sh name format is used for standard plug-ins.
|
|
## * dist2.override.sh name is used to indicate that this is a
|
|
## renamed distribution created by command 'rename' or by the
|
|
## option '--override-alias' of command 'install'.
|
|
##
|
|
## 7.9 The alias for distribution must be unique and PRoot-Distro should
|
|
## take care of that. User should not end with having two plug-ins
|
|
## for same alias, for example ubuntu.sh and ubuntu.override.sh.
|
|
##
|
|
## 7.10 The rootfs for distribution may be extracted only under PRoot
|
|
## session with active link2symlink extension.
|
|
##
|
|
## ***
|
|
##
|
|
## 8. Project versioning: major.minor.patch
|
|
##
|
|
## 8.1 Major version should be incremented when breaking changes were
|
|
## released. Examples are deprecated features, changed locations of
|
|
## files, command line format changes.
|
|
##
|
|
## 8.2 Minor version is incremented for significant but non-breaking
|
|
## changes such as added new features or upgraded distributions. The
|
|
## minor version is set to 0 when major version was incremented.
|
|
##
|
|
## 8.3 Patch version is incremented for small changes such as improvements
|
|
## to existing features and bug fixes. It is set to 0 when major or
|
|
## minor versions were incremented.
|
|
##
|
|
## ***
|
|
##
|
|
## 9. Warranty disclaimer.
|
|
##
|
|
## 9.1 PRoot-Distro has been created with aim to solve a specific range
|
|
## of tasks and its functionality may not align with your needs.
|
|
##
|
|
## 9.2 PRoot-Distro is a Bash script wrapper for PRoot. If PRoot does not
|
|
## work on your device, the PRoot-Distro will not work here as well.
|
|
## You shall not beg the authors of PRoot-Distro to fix the PRoot. Such
|
|
## requests will be ignored.
|
|
##
|
|
## 9.3 Certain functionality of PRoot-Distro relies on the Internet and
|
|
## services like GitHub. If you do not have the Internet connection or
|
|
## the GitHub is blocked by your ISP, then you may not be able to
|
|
## install distributions. Please do not beg for setting up a censorship
|
|
## resistant distribution tarball hosts.
|
|
##
|
|
## 9.4 The distributions are provided as-is, possibly with small changes
|
|
## to set of preinstalled packages and their configuration. Bugs that
|
|
## may exist in a specific distribution will not be fixed by authors
|
|
## of the PRoot-Distro.
|
|
##
|
|
## 9.5 PRoot-Distro authors make the final decision whether to accept
|
|
## submitted patches (pull requests) or not.
|
|
##
|
|
## 9.6 PRoot-Distro authors do not provide the time frames for fullfilling
|
|
## a specific request (new feature, etc).
|
|
##
|
|
## 9.7 PRoot-Distro does not have upgrade schedule. Authors work on the
|
|
## project development whenever have opportunity and desire to do so.
|
|
## Releases are submitted when ready. There no alpha or beta builds.
|
|
##
|
|
#############################################################################
|
|
|
|
#############################################################################
|
|
#
|
|
# GLOBAL ENVIRONMENT AND INSTALLATION-SPECIFIC CONFIGURATION
|
|
#
|
|
|
|
set -e -u
|
|
|
|
# Override user-defined PATH.
|
|
export PATH="@TERMUX_PREFIX@/bin"
|
|
|
|
# Reference this where need to retrieve program name.
|
|
PROGRAM_NAME=$(basename "$(realpath "$0")")
|
|
|
|
# Where distribution plug-ins are stored.
|
|
DISTRO_PLUGINS_DIR="@TERMUX_PREFIX@/etc/proot-distro"
|
|
|
|
# Base directory where script keeps runtime data.
|
|
RUNTIME_DIR="@TERMUX_PREFIX@/var/lib/proot-distro"
|
|
|
|
# Where rootfs tarballs are downloaded.
|
|
DOWNLOAD_CACHE_DIR="${RUNTIME_DIR}/dlcache"
|
|
|
|
# Where extracted rootfs are stored.
|
|
INSTALLED_ROOTFS_DIR="${RUNTIME_DIR}/installed-rootfs"
|
|
|
|
# Default name servers.
|
|
DEFAULT_PRIMARY_NAMESERVER="8.8.8.8"
|
|
DEFAULT_SECONDARY_NAMESERVER="8.8.4.4"
|
|
|
|
# PATH environment variable for distributions.
|
|
DEFAULT_PATH_ENV="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games:@TERMUX_PREFIX@/bin:/system/bin:/system/xbin"
|
|
|
|
# Default fake kernel version.
|
|
# Note: faking kernel version is required when using PRoot-Distro on
|
|
# old devices that are not compatible with up-to-date versions of GNU libc.
|
|
DEFAULT_FAKE_KERNEL_VERSION="6.2.1-PRoot-Distro"
|
|
|
|
# Emulator type for x86_64 systems.
|
|
# Can be either BLINK or QEMU.
|
|
: "${PROOT_DISTRO_X64_EMULATOR:=QEMU}"
|
|
|
|
# Colors.
|
|
if [ -n "$(command -v tput)" ] && [ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ] && [ -z "${PROOT_DISTRO_FORCE_NO_COLORS-}" ]; then
|
|
RST="$(tput sgr0)"
|
|
RED="${RST}$(tput setaf 1)"
|
|
BRED="${RST}$(tput bold)$(tput setaf 1)"
|
|
GREEN="${RST}$(tput setaf 2)"
|
|
YELLOW="${RST}$(tput setaf 3)"
|
|
BYELLOW="${RST}$(tput bold)$(tput setaf 3)"
|
|
IYELLOW="${RST}$(tput sitm)$(tput setaf 3)"
|
|
BLUE="${RST}$(tput setaf 4)"
|
|
MAGENTA="${RST}$(tput setaf 5)"
|
|
CYAN="${RST}$(tput setaf 6)"
|
|
BCYAN="${RST}$(tput bold)$(tput setaf 6)"
|
|
ICYAN="${RST}$(tput sitm)$(tput setaf 6)"
|
|
else
|
|
RED=""
|
|
BRED=""
|
|
GREEN=""
|
|
YELLOW=""
|
|
BYELLOW=""
|
|
IYELLOW=""
|
|
BLUE=""
|
|
MAGENTA=""
|
|
CYAN=""
|
|
BCYAN=""
|
|
ICYAN=""
|
|
RST=""
|
|
fi
|
|
|
|
# Disable termux-exec or other things which may interfere with proot.
|
|
# It is expected that all dependencies have fixed hardcoded paths according
|
|
# to Termux file system layout.
|
|
unset LD_PRELOAD
|
|
|
|
# Override umask
|
|
umask 0022
|
|
|
|
#############################################################################
|
|
#
|
|
# FUNCTION TO PRINT A MESSAGE TO CONSOLE
|
|
#
|
|
# Prints a given text string to stderr. Supports escape sequences.
|
|
#
|
|
#############################################################################
|
|
|
|
msg() {
|
|
echo -e "$@" >&2
|
|
}
|
|
|
|
#############################################################################
|
|
#
|
|
# DEPENDENCY CHECK
|
|
#
|
|
# Make sure all needed utilities are available in PATH before continuing.
|
|
#
|
|
#############################################################################
|
|
|
|
for i in awk basename bzip2 cat chmod cp curl cut du find grep gzip \
|
|
head id lscpu mkdir proot rm sed tar xargs xz; do
|
|
if [ -z "$(command -v "$i")" ]; then
|
|
msg
|
|
msg "${BRED}Utility '${i}' is not installed. Cannot continue.${RST}"
|
|
msg
|
|
exit 1
|
|
fi
|
|
done
|
|
unset i
|
|
|
|
# Notify user if bin/bash is not a GNU Bash.
|
|
if ! grep -q '^GNU bash' <(bash --version 2>/dev/null | head -n 1); then
|
|
msg
|
|
msg "${BRED}Warning: bash binary that is available in PATH appears to be not a GNU bash. You may experience issues during installation, backup and restore operations.${RST}"
|
|
msg
|
|
fi
|
|
|
|
# Notify user if tar available in PATH is not GNU tar.
|
|
if ! grep -q '^tar (GNU tar)' <(tar --version 2>/dev/null | head -n 1); then
|
|
msg
|
|
msg "${BRED}Warning: tar binary that is available in PATH appears to be not a GNU tar. You may experience issues during installation, backup and restore operations.${RST}"
|
|
msg
|
|
fi
|
|
|
|
#############################################################################
|
|
#
|
|
# ANTI ROOT FUSE
|
|
#
|
|
# This script should never be executed as root as can mess up the ownership,
|
|
# and SELinux labels in $PREFIX.
|
|
#
|
|
#############################################################################
|
|
|
|
if [ "$(id -u)" = "0" ]; then
|
|
msg
|
|
msg "${BRED}Error: ${PROGRAM_NAME} should not be executed as root user.${RST}"
|
|
msg
|
|
exit 1
|
|
fi
|
|
|
|
#############################################################################
|
|
#
|
|
# ANTI NESTED PROOT FUSE
|
|
#
|
|
# Nested PRoot usage leads to performance degradation and other issues.
|
|
#
|
|
#############################################################################
|
|
|
|
TRACER_PID=$(grep TracerPid "/proc/$$/status" | cut -d $'\t' -f 2)
|
|
if [ "$TRACER_PID" != 0 ]; then
|
|
TRACER_NAME=$(grep Name "/proc/${TRACER_PID}/status" | cut -d $'\t' -f 2)
|
|
if [ "$TRACER_NAME" = "proot" ]; then
|
|
msg
|
|
msg "${BRED}Error: ${PROGRAM_NAME} should not be executed under PRoot.${RST}"
|
|
msg
|
|
exit 1
|
|
fi
|
|
unset TRACER_NAME
|
|
fi
|
|
unset TRACER_PID
|
|
|
|
#############################################################################
|
|
#
|
|
# FUNCTION TO INSTALL THE SPECIFIED DISTRIBUTION
|
|
#
|
|
# Brief algorithm how it works:
|
|
#
|
|
# 1. Process arguments supplied to 'install' command.
|
|
# 2. Ensure that requested distribution is supported and is not installed.
|
|
# 3. Source the distribution configuration plug-in.
|
|
# 4. Download the tarball of rootfs for requested distribution unless found
|
|
# in cache.
|
|
# 5. Verify SHA-256 checksum of the rootfs tarball.
|
|
# 6. Extract the rootfs under PRoot with link2symlink extension enabled.
|
|
# 7. Perform post-installation actions on distribution to make it ready.
|
|
#
|
|
#############################################################################
|
|
|
|
command_install() {
|
|
local distro_name
|
|
local override_alias
|
|
local distro_plugin_script
|
|
|
|
while (($# >= 1)); do
|
|
case "$1" in
|
|
--)
|
|
shift 1
|
|
break
|
|
;;
|
|
-h|--help)
|
|
command_install_help
|
|
return 0
|
|
;;
|
|
--override-alias)
|
|
if [ $# -ge 2 ]; then
|
|
shift 1
|
|
|
|
if [ -z "$1" ]; then
|
|
msg
|
|
msg "${BRED}Error: argument to option '${YELLOW}--override-alias${BRED}' should not be empty.${RST}"
|
|
command_install_help
|
|
return 1
|
|
fi
|
|
|
|
if ! grep -qP '^[a-z0-9][a-z0-9_.+\-]*$' <<< "$1"; then
|
|
msg
|
|
msg "${BRED}Error: argument to option '${YELLOW}--override-alias${BRED}' should start only with an alphanumeric character and consist of alphanumeric characters including symbols '_.+-'."
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
if grep -qP '^.*\.sh$' <<< "$1"; then
|
|
msg
|
|
msg "${BRED}Error: argument to option '${YELLOW}--override-alias${BRED}' should not end with '.sh'.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
override_alias="$1"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: option '${YELLOW}--override-alias${BRED}' requires an argument.${RST}"
|
|
command_install_help
|
|
return 1
|
|
fi
|
|
;;
|
|
-*)
|
|
msg
|
|
msg "${BRED}Error: got unknown option '${YELLOW}${1}${BRED}'.${RST}"
|
|
command_install_help
|
|
return 1
|
|
;;
|
|
*)
|
|
if [ -z "${distro_name-}" ]; then
|
|
if [ -z "$1" ]; then
|
|
msg
|
|
msg "${BRED}Error: distribution alias argument should not be empty.${RST}"
|
|
command_install_help
|
|
return 1
|
|
fi
|
|
distro_name="$1"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: got excessive positional argument '${YELLOW}${1}${BRED}'. Note that distribution can be specified only once.${RST}"
|
|
command_install_help
|
|
return 1
|
|
fi
|
|
;;
|
|
esac
|
|
shift 1
|
|
done
|
|
|
|
if [ -z "${distro_name-}" ]; then
|
|
msg
|
|
msg "${BRED}Error: distribution alias is not specified.${RST}"
|
|
command_install_help
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "${SUPPORTED_DISTRIBUTIONS["$distro_name"]+x}" ]; then
|
|
msg
|
|
msg "${BRED}Error: unknown distribution '${YELLOW}${distro_name}${BRED}' was requested to be installed.${RST}"
|
|
msg
|
|
msg "${CYAN}View supported distributions by: ${GREEN}${PROGRAM_NAME} list${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
if grep -qiP '(kali|parrot|nethunter|blackarch)' <<< "$distro_name" || grep -qiP '^nh$' <<< "$distro_name"; then
|
|
msg
|
|
show_donate
|
|
msg
|
|
msg "${MAGENTA}No, you won't get a Kali Linux for this :)${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
if [ -n "${override_alias-}" ]; then
|
|
if [ ! -e "${DISTRO_PLUGINS_DIR}/${override_alias}.sh" ] && [ ! -e "${DISTRO_PLUGINS_DIR}/${override_alias}.override.sh" ]; then
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Creating file '${DISTRO_PLUGINS_DIR}/${override_alias}.override.sh'...${RST}"
|
|
distro_plugin_script="${DISTRO_PLUGINS_DIR}/${override_alias}.override.sh"
|
|
cp "${DISTRO_PLUGINS_DIR}/${distro_name}.sh" "${distro_plugin_script}"
|
|
sed -i "s/^\(DISTRO_NAME=\)\(.*\)\$/\1\"${SUPPORTED_DISTRIBUTIONS["$distro_name"]} - ${override_alias}\"/g" "${distro_plugin_script}"
|
|
SUPPORTED_DISTRIBUTIONS["${override_alias}"]="${SUPPORTED_DISTRIBUTIONS["$distro_name"]}"
|
|
distro_name="${override_alias}"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: distribution with alias '${YELLOW}${override_alias}${BRED}' already exists.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
else
|
|
distro_plugin_script="${DISTRO_PLUGINS_DIR}/${distro_name}.sh"
|
|
|
|
# Try an alternate distribution name.
|
|
if [ ! -f "${distro_plugin_script}" ]; then
|
|
distro_plugin_script="${DISTRO_PLUGINS_DIR}/${distro_name}.override.sh"
|
|
fi
|
|
fi
|
|
|
|
if [ -d "${INSTALLED_ROOTFS_DIR}/${distro_name}" ]; then
|
|
msg
|
|
msg "${BRED}Error: distribution '${YELLOW}${distro_name}${BRED}' is already installed.${RST}"
|
|
msg
|
|
msg "${CYAN}Log in: ${GREEN}${PROGRAM_NAME} login ${distro_name}${RST}"
|
|
msg "${CYAN}Reinstall: ${GREEN}${PROGRAM_NAME} reset ${distro_name}${RST}"
|
|
msg "${CYAN}Uninstall: ${GREEN}${PROGRAM_NAME} remove ${distro_name}${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
if [ -f "${distro_plugin_script}" ]; then
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Installing ${YELLOW}${SUPPORTED_DISTRIBUTIONS["$distro_name"]}${CYAN}...${RST}"
|
|
|
|
# Make sure things are cleared up on failure or user requested exit.
|
|
# shellcheck disable=SC2064 # variables must expand here
|
|
trap "echo -e \"\\r\\e[2K${BLUE}[${RED}!${BLUE}] ${CYAN}Exiting due to failure.${RST}\"; chmod -R u+rwx \"${INSTALLED_ROOTFS_DIR:?}/${distro_name:?}\"; rm -rf \"${INSTALLED_ROOTFS_DIR:?}/${distro_name:?}\"; [ -e \"${DISTRO_PLUGINS_DIR}/${distro_name}.override.sh\" ] && rm -f \"${DISTRO_PLUGINS_DIR}/${distro_name}.override.sh\"; exit 1;" EXIT
|
|
# shellcheck disable=SC2064 # variables must expand here
|
|
trap "trap - EXIT; echo -e \"\\r\\e[2K${BLUE}[${RED}!${BLUE}] ${CYAN}Exiting immediately as requested.${RST}\"; chmod -R u+rwx \"${INSTALLED_ROOTFS_DIR:?}/${distro_name:?}\"; rm -rf \"${INSTALLED_ROOTFS_DIR:?}/${distro_name:?}\"; [ -e \"${DISTRO_PLUGINS_DIR}/${distro_name}.override.sh\" ] && rm -f \"${DISTRO_PLUGINS_DIR}/${distro_name}.override.sh\"; exit 1;" HUP INT TERM
|
|
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Creating directory '${INSTALLED_ROOTFS_DIR}/${distro_name}'...${RST}"
|
|
mkdir -p "${INSTALLED_ROOTFS_DIR}/${distro_name}"
|
|
|
|
export PROOT_L2S_DIR="${INSTALLED_ROOTFS_DIR}/${distro_name}/.l2s"
|
|
if [ ! -d "${INSTALLED_ROOTFS_DIR}/${distro_name}/.l2s" ]; then
|
|
echo -e "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Creating directory '${PROOT_L2S_DIR}'...${RST}"
|
|
mkdir -p "$PROOT_L2S_DIR"
|
|
fi
|
|
|
|
# This should be overridden in distro plug-in with valid URL for
|
|
# each architecture where possible.
|
|
TARBALL_URL["aarch64"]=""
|
|
TARBALL_URL["arm"]=""
|
|
TARBALL_URL["i686"]=""
|
|
TARBALL_URL["riscv64"]=""
|
|
TARBALL_URL["x86_64"]=""
|
|
|
|
# This should be overridden in distro plug-in with valid SHA-256
|
|
# for corresponding tarballs.
|
|
TARBALL_SHA256["aarch64"]=""
|
|
TARBALL_SHA256["arm"]=""
|
|
TARBALL_SHA256["i686"]=""
|
|
TARBALL_SHA256["riscv64"]=""
|
|
TARBALL_SHA256["x86_64"]=""
|
|
|
|
# If your content inside tarball isn't stored in subdirectory,
|
|
# you can override this variable in distro plug-in with 0.
|
|
TARBALL_STRIP_OPT=1
|
|
|
|
# Distribution plug-in contains steps on how to get download URL
|
|
# and further post-installation configuration.
|
|
# shellcheck disable=SC1090
|
|
source "${distro_plugin_script}"
|
|
|
|
# Cannot proceed without URL and SHA-256.
|
|
if [ -z "${TARBALL_URL["$DISTRO_ARCH"]}" ]; then
|
|
msg "${BLUE}[${RED}!${BLUE}] ${CYAN}The distribution download URL is not defined for CPU architecture '${DISTRO_ARCH}'.${RST}"
|
|
return 1
|
|
fi
|
|
if ! grep -qP '^[0-9a-fA-F]{64}$' <<< "${TARBALL_SHA256["$DISTRO_ARCH"]}"; then
|
|
msg
|
|
msg "${BRED}Error: got malformed SHA-256 from plug-in script '${distro_plugin_script}'.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -d "$DOWNLOAD_CACHE_DIR" ]; then
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Creating directory '$DOWNLOAD_CACHE_DIR'...${RST}"
|
|
mkdir -p "$DOWNLOAD_CACHE_DIR"
|
|
fi
|
|
|
|
local tarball_name
|
|
tarball_name=$(basename "${TARBALL_URL["$DISTRO_ARCH"]}")
|
|
|
|
if [ ! -f "${DOWNLOAD_CACHE_DIR}/${tarball_name}" ]; then
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Downloading rootfs tarball...${RST}"
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}URL: ${TARBALL_URL["$DISTRO_ARCH"]}${RST}"
|
|
|
|
# Using temporary file as script can't distinguish the partially
|
|
# downloaded file from the complete. Useful in case if curl will
|
|
# fail for some reason.
|
|
msg
|
|
rm -f "${DOWNLOAD_CACHE_DIR}/${tarball_name}.tmp"
|
|
if ! curl --disable --fail --retry 5 --retry-connrefused --retry-delay 5 --location \
|
|
--output "${DOWNLOAD_CACHE_DIR}/${tarball_name}.tmp" "${TARBALL_URL["$DISTRO_ARCH"]}"; then
|
|
msg
|
|
msg "${BLUE}[${RED}!${BLUE}] ${CYAN}Download failure, please check your network connection.${RST}"
|
|
rm -f "${DOWNLOAD_CACHE_DIR}/${tarball_name}.tmp"
|
|
return 1
|
|
fi
|
|
msg
|
|
|
|
# If curl finished successfully, rename file to original.
|
|
mv -f "${DOWNLOAD_CACHE_DIR}/${tarball_name}.tmp" "${DOWNLOAD_CACHE_DIR}/${tarball_name}"
|
|
else
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Using cached rootfs tarball...${RST}"
|
|
fi
|
|
|
|
if [ -n "${TARBALL_SHA256["$DISTRO_ARCH"]}" ]; then
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Checking integrity, please wait...${RST}"
|
|
local actual_sha256
|
|
actual_sha256=$(sha256sum "${DOWNLOAD_CACHE_DIR}/${tarball_name}" | awk '{ print $1}')
|
|
|
|
if [ "${TARBALL_SHA256["$DISTRO_ARCH"]}" != "${actual_sha256}" ]; then
|
|
msg "${BLUE}[${RED}!${BLUE}] ${CYAN}Integrity checking failed. Try to redo installation again.${RST}"
|
|
rm -f "${DOWNLOAD_CACHE_DIR}/${tarball_name}"
|
|
return 1
|
|
fi
|
|
else
|
|
msg "${BLUE}[${RED}!${BLUE}] ${CYAN}Integrity checking of downloaded rootfs has been disabled.${RST}"
|
|
fi
|
|
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Extracting rootfs, please wait...${RST}"
|
|
# --exclude='dev' - need to exclude /dev directory which may contain device files.
|
|
# --delay-directory-restore - set directory permissions only when files were extracted
|
|
# to avoid issues with Arch Linux bootstrap archives.
|
|
set +e
|
|
proot --link2symlink \
|
|
tar -C "${INSTALLED_ROOTFS_DIR}/${distro_name}" --warning=no-unknown-keyword \
|
|
--delay-directory-restore --preserve-permissions --strip="${TARBALL_STRIP_OPT}" \
|
|
-xf "${DOWNLOAD_CACHE_DIR}/${tarball_name}" --exclude='dev' |& grep -v "/linkerconfig/" >&2
|
|
set -e
|
|
|
|
# If no /etc in rootfs, terminate installation.
|
|
# This usually indicates that downloaded distribution tarball doesn't contain
|
|
# actual rootfs, wrong tar strip option was specified or the distribution has
|
|
# high grade of customization and doesn't respect FHS standard.
|
|
if [ ! -e "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc" ]; then
|
|
msg
|
|
msg "${BRED}Error: the rootfs of distribution '${YELLOW}${distro_name}${BRED}' has unexpected structure (no /etc directory). Make sure that variable TARBALL_STRIP_OPT specified in distribution plug-in is correct.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
# Write important environment variables to /etc/environment.
|
|
chmod u+rw "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/environment" >/dev/null 2>&1 || true
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Writing file '${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/environment'...${RST}"
|
|
for var in ANDROID_ART_ROOT ANDROID_DATA ANDROID_I18N_ROOT ANDROID_ROOT \
|
|
ANDROID_RUNTIME_ROOT ANDROID_TZDATA_ROOT BOOTCLASSPATH COLORTERM \
|
|
DEX2OATBOOTCLASSPATH EXTERNAL_STORAGE; do
|
|
set +u
|
|
if [ -n "${!var}" ]; then
|
|
echo "${var}=${!var}" >> "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/environment"
|
|
fi
|
|
set -u
|
|
done
|
|
unset var
|
|
# Don't touch these variables.
|
|
# TERM is being inherited from currect environment. Otherwise it is being
|
|
# set to xterm-256color (Termux app default).
|
|
cat <<- EOF >> "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/environment"
|
|
LANG=en_US.UTF-8
|
|
MOZ_FAKE_NO_SANDBOX=1
|
|
PATH=${DEFAULT_PATH_ENV}
|
|
PULSE_SERVER=127.0.0.1
|
|
TERM=${TERM-xterm-256color}
|
|
TMPDIR=/tmp
|
|
EOF
|
|
|
|
# Fix PATH in some configuration files.
|
|
for f in /etc/bash.bashrc /etc/profile /etc/login.defs; do
|
|
[ ! -e "${INSTALLED_ROOTFS_DIR}/${distro_name}${f}" ] && continue
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Updating PATH in '${INSTALLED_ROOTFS_DIR}/${distro_name}${f}' if needed...${RST}"
|
|
sed -i -E "s@\<(PATH=)(\"?[^\"[:space:]]+(\"|\$|\>))@\1\"${DEFAULT_PATH_ENV}\"@g" \
|
|
"${INSTALLED_ROOTFS_DIR}/${distro_name}${f}"
|
|
done
|
|
unset f
|
|
|
|
# Default /etc/resolv.conf may be empty or unsuitable for use.
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Creating file '${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/resolv.conf'...${RST}"
|
|
rm -f "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/resolv.conf"
|
|
cat <<- EOF > "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/resolv.conf"
|
|
nameserver ${DEFAULT_PRIMARY_NAMESERVER}
|
|
nameserver ${DEFAULT_SECONDARY_NAMESERVER}
|
|
EOF
|
|
|
|
# Default /etc/hosts may be empty or incomplete.
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Creating file '${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/hosts'...${RST}"
|
|
chmod u+rw "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/hosts" >/dev/null 2>&1 || true
|
|
cat <<- EOF > "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/hosts"
|
|
# IPv4.
|
|
127.0.0.1 localhost.localdomain localhost
|
|
|
|
# IPv6.
|
|
::1 localhost.localdomain localhost ip6-localhost ip6-loopback
|
|
fe00::0 ip6-localnet
|
|
ff00::0 ip6-mcastprefix
|
|
ff02::1 ip6-allnodes
|
|
ff02::2 ip6-allrouters
|
|
ff02::3 ip6-allhosts
|
|
EOF
|
|
|
|
# Add Android-specific UIDs/GIDs to /etc/group and /etc/gshadow.
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Registering Android-specific UIDs and GIDs...${RST}"
|
|
chmod u+rw "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/passwd" \
|
|
"${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/shadow" \
|
|
"${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/group" \
|
|
"${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/gshadow" >/dev/null 2>&1 || true
|
|
echo "aid_$(id -un):x:$(id -u):$(id -g):Termux:/:/sbin/nologin" >> \
|
|
"${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/passwd"
|
|
echo "aid_$(id -un):*:18446:0:99999:7:::" >> \
|
|
"${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/shadow"
|
|
local group_name group_id
|
|
while read -r group_name group_id; do
|
|
echo "aid_${group_name}:x:${group_id}:root,aid_$(id -un)" \
|
|
>> "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/group"
|
|
if [ -f "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/gshadow" ]; then
|
|
echo "aid_${group_name}:*::root,aid_$(id -un)" \
|
|
>> "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/gshadow"
|
|
fi
|
|
done < <(paste <(id -Gn | tr ' ' '\n') <(id -G | tr ' ' '\n'))
|
|
|
|
# Ensure that proot will be able to bind fake /proc entries.
|
|
setup_fake_proc
|
|
|
|
# Run optional distro-specific hook.
|
|
if declare -f -F distro_setup >/dev/null 2>&1; then
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Running distribution-specific configuration steps...${RST}"
|
|
(cd "${INSTALLED_ROOTFS_DIR}/${distro_name}"
|
|
distro_setup
|
|
)
|
|
fi
|
|
|
|
# Reset trap for HUP/INT/TERM.
|
|
trap - EXIT
|
|
trap 'echo -e "\\r\\e[2K${BLUE}[${RED}!${BLUE}] ${CYAN}Exiting immediately as requested.${RST}"; exit 1;' HUP INT TERM
|
|
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Finished.${RST}"
|
|
msg
|
|
msg "${CYAN}Log in with: ${GREEN}${PROGRAM_NAME} login ${distro_name}${CYAN}${RST}"
|
|
msg
|
|
return 0
|
|
else
|
|
# Reset trap for HUP/INT/TERM.
|
|
trap - EXIT
|
|
trap 'echo -e "\\r\\e[2K${BLUE}[${RED}!${BLUE}] ${CYAN}Exiting immediately as requested.${RST}"; exit 1;' HUP INT TERM
|
|
|
|
msg "${BLUE}[${RED}!${BLUE}] ${CYAN}Cannot find '${distro_plugin_script}' which is used to define a distribution properties.${RST}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Special function for executing a command inside rootfs.
|
|
# Intended to be used inside plug-in distro_setup() function.
|
|
# shellcheck disable=SC2317 # run_proot_cmd called indirectly
|
|
run_proot_cmd() {
|
|
if [ -z "${distro_name-}" ]; then
|
|
msg
|
|
msg "${BRED}Error: called run_proot_cmd() but \${distro_name} is not set. Make sure that run_proot_cmd() is used inside distro_setup() function.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "${DISTRO_ARCH-}" ]; then
|
|
msg
|
|
msg "${BRED}Error: called run_proot_cmd() but \${DISTRO_ARCH} is not set.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
local cpu_emulator_arg=""
|
|
if [ "$DISTRO_ARCH" != "$DEVICE_CPU_ARCH" ]; then
|
|
local cpu_emulator_path=""
|
|
|
|
# If CPU and host OS are 64bit, we can run 32bit guest OS without emulation.
|
|
# Everything else requires emulator (QEMU).
|
|
case "$DISTRO_ARCH" in
|
|
aarch64) cpu_emulator_path="@TERMUX_PREFIX@/bin/qemu-aarch64";;
|
|
arm)
|
|
if [ "$DEVICE_CPU_ARCH" != "aarch64" ] || ! $SUPPORT_32BIT; then
|
|
cpu_emulator_path="@TERMUX_PREFIX@/bin/qemu-arm"
|
|
fi
|
|
;;
|
|
i686)
|
|
if [ "$DEVICE_CPU_ARCH" != "x86_64" ]; then
|
|
cpu_emulator_path="@TERMUX_PREFIX@/bin/qemu-i386"
|
|
fi
|
|
;;
|
|
riscv64) cpu_emulator_path="@TERMUX_PREFIX@/bin/qemu-riscv64";;
|
|
x86_64)
|
|
if [ "$PROOT_DISTRO_X64_EMULATOR" = "QEMU" ]; then
|
|
cpu_emulator_path="@TERMUX_PREFIX@/bin/qemu-x86_64"
|
|
elif [ "$PROOT_DISTRO_X64_EMULATOR" = "BLINK" ]; then
|
|
cpu_emulator_path="@TERMUX_PREFIX@/bin/blink"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: PROOT_DISTRO_X64_EMULATOR has unknown value '$PROOT_DISTRO_X64_EMULATOR'. Valid values are: BLINK, QEMU."
|
|
msg
|
|
fi
|
|
;;
|
|
*)
|
|
msg
|
|
msg "${BRED}Error: DISTRO_ARCH has unknown value '$DISTRO_ARCH'. Valid values are: aarch64, arm, i686, riscv64, x86_64."
|
|
msg
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
if [ -n "$cpu_emulator_path" ]; then
|
|
if [ -x "$cpu_emulator_path" ]; then
|
|
cpu_emulator_arg="-q ${cpu_emulator_path}"
|
|
else
|
|
local cpu_emulator_pkg=""
|
|
case "$DISTRO_ARCH" in
|
|
aarch64) cpu_emulator_pkg="qemu-user-aarch64";;
|
|
arm) cpu_emulator_pkg="qemu-user-arm";;
|
|
i686) cpu_emulator_pkg="qemu-user-i386";;
|
|
riscv64) cpu_emulator_pkg="qemu-user-riscv64";;
|
|
x86_64)
|
|
if [ "$PROOT_DISTRO_X64_EMULATOR" = "QEMU" ]; then
|
|
cpu_emulator_pkg="qemu-user-x86-64"
|
|
elif [ "$PROOT_DISTRO_X64_EMULATOR" = "BLINK" ]; then
|
|
cpu_emulator_pkg="blink"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: PROOT_DISTRO_X64_EMULATOR has unknown value '$PROOT_DISTRO_X64_EMULATOR'. Valid values are: BLINK, QEMU."
|
|
msg
|
|
fi
|
|
;;
|
|
*) cpu_emulator_pkg="qemu-user-${DISTRO_ARCH}";;
|
|
esac
|
|
msg
|
|
msg "${BRED}Error: package '${cpu_emulator_pkg}' is not installed.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
fi
|
|
else
|
|
# Warn about CPU not supporting 32-bit instructions
|
|
if ! $SUPPORT_32BIT; then
|
|
msg "${BRED}Warning: CPU doesn't support 32-bit instructions, some software may not work.${RST}"
|
|
fi
|
|
fi
|
|
|
|
if [ -n "$cpu_emulator_arg" ]; then
|
|
if [ -d "/apex" ]; then
|
|
cpu_emulator_arg="${cpu_emulator_arg} --bind=/apex"
|
|
fi
|
|
if [ -e "/linkerconfig/ld.config.txt" ]; then
|
|
cpu_emulator_arg="${cpu_emulator_arg} --bind=/linkerconfig/ld.config.txt"
|
|
fi
|
|
cpu_emulator_arg="${cpu_emulator_arg} --bind=@TERMUX_PREFIX@"
|
|
cpu_emulator_arg="${cpu_emulator_arg} --bind=/system"
|
|
cpu_emulator_arg="${cpu_emulator_arg} --bind=/vendor"
|
|
if [ -f "/plat_property_contexts" ]; then
|
|
cpu_emulator_arg="${cpu_emulator_arg} --bind=/plat_property_contexts"
|
|
fi
|
|
if [ -f "/property_contexts" ]; then
|
|
cpu_emulator_arg="${cpu_emulator_arg} --bind=/property_contexts"
|
|
fi
|
|
fi
|
|
|
|
# shellcheck disable=SC2086 # ${cpu_emulator_arg} should expand into nothing rather than into ''.
|
|
proot ${cpu_emulator_arg} \
|
|
-L \
|
|
--kernel-release="${DEFAULT_FAKE_KERNEL_VERSION}" \
|
|
--link2symlink \
|
|
--kill-on-exit \
|
|
--rootfs="${INSTALLED_ROOTFS_DIR}/${distro_name}" \
|
|
--root-id \
|
|
--cwd=/root \
|
|
--bind=/dev \
|
|
--bind="/dev/urandom:/dev/random" \
|
|
--bind=/proc \
|
|
--bind="/proc/self/fd:/dev/fd" \
|
|
--bind="/proc/self/fd/0:/dev/stdin" \
|
|
--bind="/proc/self/fd/1:/dev/stdout" \
|
|
--bind="/proc/self/fd/2:/dev/stderr" \
|
|
--bind=/sys \
|
|
--bind="${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.loadavg:/proc/loadavg" \
|
|
--bind="${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.stat:/proc/stat" \
|
|
--bind="${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.uptime:/proc/uptime" \
|
|
--bind="${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.version:/proc/version" \
|
|
--bind="${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.vmstat:/proc/vmstat" \
|
|
--bind="${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.sysctl_entry_cap_last_cap:/proc/sys/kernel/cap_last_cap" \
|
|
/usr/bin/env -i \
|
|
"HOME=/root" \
|
|
"LANG=C.UTF-8" \
|
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \
|
|
"TERM=${TERM-xterm-256color}" \
|
|
"TMPDIR=/tmp" \
|
|
"$@"
|
|
}
|
|
|
|
# A function for preparing fake content for certain /proc entries which are
|
|
# known to have restricted access on Android OS. All entries are based on
|
|
# values retrieved from Arch Linux (x86_64) running on a VM with 8 CPUs and 8
|
|
# GiB of memory. Date 2023.03.28, Linux 6.2.1. Some values edited to fit
|
|
# the PRoot-Distro.
|
|
setup_fake_proc() {
|
|
mkdir -p "${INSTALLED_ROOTFS_DIR}/${distro_name}/proc"
|
|
chmod 700 "${INSTALLED_ROOTFS_DIR}/${distro_name}/proc"
|
|
|
|
if [ ! -f "${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.loadavg" ]; then
|
|
cat <<- EOF > "${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.loadavg"
|
|
0.12 0.07 0.02 2/165 765
|
|
EOF
|
|
fi
|
|
|
|
if [ ! -f "${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.stat" ]; then
|
|
cat <<- EOF > "${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.stat"
|
|
cpu 1957 0 2877 93280 262 342 254 87 0 0
|
|
cpu0 31 0 226 12027 82 10 4 9 0 0
|
|
cpu1 45 0 664 11144 21 263 233 12 0 0
|
|
cpu2 494 0 537 11283 27 10 3 8 0 0
|
|
cpu3 359 0 234 11723 24 26 5 7 0 0
|
|
cpu4 295 0 268 11772 10 12 2 12 0 0
|
|
cpu5 270 0 251 11833 15 3 1 10 0 0
|
|
cpu6 430 0 520 11386 30 8 1 12 0 0
|
|
cpu7 30 0 172 12108 50 8 1 13 0 0
|
|
intr 127541 38 290 0 0 0 0 4 0 1 0 0 25329 258 0 5777 277 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
|
ctxt 140223
|
|
btime 1680020856
|
|
processes 772
|
|
procs_running 2
|
|
procs_blocked 0
|
|
softirq 75663 0 5903 6 25375 10774 0 243 11685 0 21677
|
|
EOF
|
|
fi
|
|
|
|
if [ ! -f "${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.uptime" ]; then
|
|
cat <<- EOF > "${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.uptime"
|
|
124.08 932.80
|
|
EOF
|
|
fi
|
|
|
|
if [ ! -f "${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.version" ]; then
|
|
cat <<- EOF > "${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.version"
|
|
Linux version ${DEFAULT_FAKE_KERNEL_VERSION} (proot@termux) (gcc (GCC) 12.2.1 20230201, GNU ld (GNU Binutils) 2.40) #1 SMP PREEMPT_DYNAMIC Wed, 01 Mar 2023 00:00:00 +0000
|
|
EOF
|
|
fi
|
|
|
|
if [ ! -f "${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.vmstat" ]; then
|
|
cat <<- EOF > "${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.vmstat"
|
|
nr_free_pages 1743136
|
|
nr_zone_inactive_anon 179281
|
|
nr_zone_active_anon 7183
|
|
nr_zone_inactive_file 22858
|
|
nr_zone_active_file 51328
|
|
nr_zone_unevictable 642
|
|
nr_zone_write_pending 0
|
|
nr_mlock 0
|
|
nr_bounce 0
|
|
nr_zspages 0
|
|
nr_free_cma 0
|
|
numa_hit 1259626
|
|
numa_miss 0
|
|
numa_foreign 0
|
|
numa_interleave 720
|
|
numa_local 1259626
|
|
numa_other 0
|
|
nr_inactive_anon 179281
|
|
nr_active_anon 7183
|
|
nr_inactive_file 22858
|
|
nr_active_file 51328
|
|
nr_unevictable 642
|
|
nr_slab_reclaimable 8091
|
|
nr_slab_unreclaimable 7804
|
|
nr_isolated_anon 0
|
|
nr_isolated_file 0
|
|
workingset_nodes 0
|
|
workingset_refault_anon 0
|
|
workingset_refault_file 0
|
|
workingset_activate_anon 0
|
|
workingset_activate_file 0
|
|
workingset_restore_anon 0
|
|
workingset_restore_file 0
|
|
workingset_nodereclaim 0
|
|
nr_anon_pages 7723
|
|
nr_mapped 8905
|
|
nr_file_pages 253569
|
|
nr_dirty 0
|
|
nr_writeback 0
|
|
nr_writeback_temp 0
|
|
nr_shmem 178741
|
|
nr_shmem_hugepages 0
|
|
nr_shmem_pmdmapped 0
|
|
nr_file_hugepages 0
|
|
nr_file_pmdmapped 0
|
|
nr_anon_transparent_hugepages 1
|
|
nr_vmscan_write 0
|
|
nr_vmscan_immediate_reclaim 0
|
|
nr_dirtied 0
|
|
nr_written 0
|
|
nr_throttled_written 0
|
|
nr_kernel_misc_reclaimable 0
|
|
nr_foll_pin_acquired 0
|
|
nr_foll_pin_released 0
|
|
nr_kernel_stack 2780
|
|
nr_page_table_pages 344
|
|
nr_sec_page_table_pages 0
|
|
nr_swapcached 0
|
|
pgpromote_success 0
|
|
pgpromote_candidate 0
|
|
nr_dirty_threshold 356564
|
|
nr_dirty_background_threshold 178064
|
|
pgpgin 890508
|
|
pgpgout 0
|
|
pswpin 0
|
|
pswpout 0
|
|
pgalloc_dma 272
|
|
pgalloc_dma32 261
|
|
pgalloc_normal 1328079
|
|
pgalloc_movable 0
|
|
pgalloc_device 0
|
|
allocstall_dma 0
|
|
allocstall_dma32 0
|
|
allocstall_normal 0
|
|
allocstall_movable 0
|
|
allocstall_device 0
|
|
pgskip_dma 0
|
|
pgskip_dma32 0
|
|
pgskip_normal 0
|
|
pgskip_movable 0
|
|
pgskip_device 0
|
|
pgfree 3077011
|
|
pgactivate 0
|
|
pgdeactivate 0
|
|
pglazyfree 0
|
|
pgfault 176973
|
|
pgmajfault 488
|
|
pglazyfreed 0
|
|
pgrefill 0
|
|
pgreuse 19230
|
|
pgsteal_kswapd 0
|
|
pgsteal_direct 0
|
|
pgsteal_khugepaged 0
|
|
pgdemote_kswapd 0
|
|
pgdemote_direct 0
|
|
pgdemote_khugepaged 0
|
|
pgscan_kswapd 0
|
|
pgscan_direct 0
|
|
pgscan_khugepaged 0
|
|
pgscan_direct_throttle 0
|
|
pgscan_anon 0
|
|
pgscan_file 0
|
|
pgsteal_anon 0
|
|
pgsteal_file 0
|
|
zone_reclaim_failed 0
|
|
pginodesteal 0
|
|
slabs_scanned 0
|
|
kswapd_inodesteal 0
|
|
kswapd_low_wmark_hit_quickly 0
|
|
kswapd_high_wmark_hit_quickly 0
|
|
pageoutrun 0
|
|
pgrotated 0
|
|
drop_pagecache 0
|
|
drop_slab 0
|
|
oom_kill 0
|
|
numa_pte_updates 0
|
|
numa_huge_pte_updates 0
|
|
numa_hint_faults 0
|
|
numa_hint_faults_local 0
|
|
numa_pages_migrated 0
|
|
pgmigrate_success 0
|
|
pgmigrate_fail 0
|
|
thp_migration_success 0
|
|
thp_migration_fail 0
|
|
thp_migration_split 0
|
|
compact_migrate_scanned 0
|
|
compact_free_scanned 0
|
|
compact_isolated 0
|
|
compact_stall 0
|
|
compact_fail 0
|
|
compact_success 0
|
|
compact_daemon_wake 0
|
|
compact_daemon_migrate_scanned 0
|
|
compact_daemon_free_scanned 0
|
|
htlb_buddy_alloc_success 0
|
|
htlb_buddy_alloc_fail 0
|
|
cma_alloc_success 0
|
|
cma_alloc_fail 0
|
|
unevictable_pgs_culled 27002
|
|
unevictable_pgs_scanned 0
|
|
unevictable_pgs_rescued 744
|
|
unevictable_pgs_mlocked 744
|
|
unevictable_pgs_munlocked 744
|
|
unevictable_pgs_cleared 0
|
|
unevictable_pgs_stranded 0
|
|
thp_fault_alloc 13
|
|
thp_fault_fallback 0
|
|
thp_fault_fallback_charge 0
|
|
thp_collapse_alloc 4
|
|
thp_collapse_alloc_failed 0
|
|
thp_file_alloc 0
|
|
thp_file_fallback 0
|
|
thp_file_fallback_charge 0
|
|
thp_file_mapped 0
|
|
thp_split_page 0
|
|
thp_split_page_failed 0
|
|
thp_deferred_split_page 1
|
|
thp_split_pmd 1
|
|
thp_scan_exceed_none_pte 0
|
|
thp_scan_exceed_swap_pte 0
|
|
thp_scan_exceed_share_pte 0
|
|
thp_split_pud 0
|
|
thp_zero_page_alloc 0
|
|
thp_zero_page_alloc_failed 0
|
|
thp_swpout 0
|
|
thp_swpout_fallback 0
|
|
balloon_inflate 0
|
|
balloon_deflate 0
|
|
balloon_migrate 0
|
|
swap_ra 0
|
|
swap_ra_hit 0
|
|
ksm_swpin_copy 0
|
|
cow_ksm 0
|
|
zswpin 0
|
|
zswpout 0
|
|
direct_map_level2_splits 29
|
|
direct_map_level3_splits 0
|
|
nr_unstable 0
|
|
EOF
|
|
fi
|
|
|
|
if [ ! -f "${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.sysctl_entry_cap_last_cap" ]; then
|
|
cat <<- EOF > "${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.sysctl_entry_cap_last_cap"
|
|
40
|
|
EOF
|
|
fi
|
|
}
|
|
|
|
command_install_help() {
|
|
msg
|
|
msg "${BYELLOW}Usage: ${BCYAN}${PROGRAM_NAME} ${GREEN}install ${CYAN}[${GREEN}OPTIONS${CYAN}] [${GREEN}DISTRIBUTION ALIAS${CYAN}]${RST}"
|
|
msg
|
|
msg "${CYAN}Command aliases: ${GREEN}add${CYAN}, ${GREEN}i${CYAN}, ${GREEN}in${CYAN}, ${GREEN}ins${RST}"
|
|
msg
|
|
msg "${CYAN}Install a specified Linux distribution.${RST}"
|
|
msg
|
|
msg "${CYAN}Options:${RST}"
|
|
msg
|
|
msg " ${GREEN}--help ${CYAN}- Show this help information.${RST}"
|
|
msg
|
|
msg " ${GREEN}--override-alias [new alias] ${CYAN}- Set a custom alias for installed${RST}"
|
|
msg " ${CYAN}distribution.${RST}"
|
|
msg
|
|
msg "${CYAN}Selected distribution should be referenced by alias which can be${RST}"
|
|
msg "${CYAN}obtained by this command: ${GREEN}${PROGRAM_NAME} list${RST}"
|
|
msg
|
|
show_version
|
|
msg
|
|
}
|
|
|
|
#############################################################################
|
|
#
|
|
# FUNCTION TO UNINSTALL SPECIFIED DISTRIBUTION
|
|
#
|
|
# Delete the rootfs of given distribution. If the associated plug-in has
|
|
# extension '.override.sh', it will be deleted as well.
|
|
#
|
|
#############################################################################
|
|
|
|
command_remove() {
|
|
local distro_name
|
|
|
|
while (($# >= 1)); do
|
|
case "$1" in
|
|
-h|--help)
|
|
command_remove_help
|
|
return 0
|
|
;;
|
|
-*)
|
|
msg
|
|
msg "${BRED}Error: got unknown option '${YELLOW}${1}${BRED}'.${RST}"
|
|
command_remove_help
|
|
return 1
|
|
;;
|
|
*)
|
|
if [ -z "${distro_name-}" ]; then
|
|
if [ -z "$1" ]; then
|
|
msg
|
|
msg "${BRED}Error: distribution alias argument should not be empty.${RST}"
|
|
command_remove_help
|
|
return 1
|
|
fi
|
|
distro_name="$1"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: got excessive positional argument '${YELLOW}${1}${BRED}'. Note that distribution can be specified only once.${RST}"
|
|
command_remove_help
|
|
return 1
|
|
fi
|
|
;;
|
|
esac
|
|
shift 1
|
|
done
|
|
|
|
if [ -z "${distro_name-}" ]; then
|
|
msg
|
|
msg "${BRED}Error: distribution alias is not specified.${RST}"
|
|
command_remove_help
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "${SUPPORTED_DISTRIBUTIONS["$distro_name"]+x}" ]; then
|
|
msg
|
|
msg "${BRED}Error: unknown distribution '${YELLOW}${distro_name}${BRED}' was requested to be removed.${RST}"
|
|
msg
|
|
msg "${CYAN}View supported distributions by: ${GREEN}${PROGRAM_NAME} list${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -d "${INSTALLED_ROOTFS_DIR}/${distro_name}" ]; then
|
|
msg
|
|
msg "${BRED}Error: distribution '${YELLOW}${distro_name}${BRED}' is not installed.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
# The plug-ins created during renaming the distribution are considered
|
|
# as generated content and should be deleted with rootfs.
|
|
if [ "${CMD_REMOVE_REQUESTED_RESET-false}" = "false" ] && [ -e "${DISTRO_PLUGINS_DIR}/${distro_name}.override.sh" ]; then
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Deleting file '${DISTRO_PLUGINS_DIR}/${distro_name}.override.sh'...${RST}"
|
|
rm -f "${DISTRO_PLUGINS_DIR}/${distro_name}.override.sh"
|
|
fi
|
|
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Wiping the rootfs of ${YELLOW}${SUPPORTED_DISTRIBUTIONS["$distro_name"]}${CYAN}...${RST}"
|
|
# Attempt to restore permissions so directory can be removed without issues.
|
|
chmod u+rwx -R "${INSTALLED_ROOTFS_DIR}/${distro_name}" > /dev/null 2>&1 || true
|
|
# There is still chance for failure.
|
|
if rm -rf "${INSTALLED_ROOTFS_DIR:?}/${distro_name:?}"; then
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Finished.${RST}"
|
|
else
|
|
msg "${BLUE}[${RED}!${BLUE}] ${CYAN}Finished with errors. Some files probably were not deleted.${RST}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
command_remove_help() {
|
|
msg
|
|
msg "${BYELLOW}Usage: ${BCYAN}${PROGRAM_NAME} ${GREEN}remove ${CYAN}[${GREEN}DISTRIBUTION ALIAS${CYAN}]${RST}"
|
|
msg
|
|
msg "${CYAN}Command aliases: ${GREEN}rm${RST}"
|
|
msg
|
|
msg "${CYAN}Remove a specified Linux distribution.${RST}"
|
|
msg
|
|
msg "${CYAN}Options:${RST}"
|
|
msg
|
|
msg " ${GREEN}--help ${CYAN}- Show this help information.${RST}"
|
|
msg
|
|
msg "${CYAN}Be careful when using it because you will not be prompted for${RST}"
|
|
msg "${CYAN}confirmation and all data saved within the distribution will${RST}"
|
|
msg "${CYAN}instantly gone.${RST}"
|
|
msg
|
|
msg "${CYAN}Selected distribution should be referenced by alias which can be${RST}"
|
|
msg "${CYAN}obtained by this command: ${GREEN}${PROGRAM_NAME} list${RST}"
|
|
msg
|
|
show_version
|
|
msg
|
|
}
|
|
|
|
#############################################################################
|
|
#
|
|
# FUNCTION TO RENAME A DISTRIBUTION
|
|
#
|
|
# Change the name of installed distribution by moving its rootfs directory
|
|
# and creating copy of original distrubution plug-in. The new plug-in will
|
|
# have an extension '.override.sh'.
|
|
#
|
|
#############################################################################
|
|
|
|
command_rename() {
|
|
local orig_distro_name
|
|
local new_distro_name
|
|
|
|
while (($# >= 1)); do
|
|
case "$1" in
|
|
-h|--help)
|
|
command_rename_help
|
|
return 0
|
|
;;
|
|
-*)
|
|
msg
|
|
msg "${BRED}Error: got unknown option '${YELLOW}${1}${BRED}'.${RST}"
|
|
command_rename_help
|
|
return 1
|
|
;;
|
|
*)
|
|
if [ -z "${orig_distro_name-}" ]; then
|
|
if [ -z "$1" ]; then
|
|
msg
|
|
msg "${BRED}Error: original distribution alias argument should not be empty.${RST}"
|
|
command_rename_help
|
|
return 1
|
|
fi
|
|
orig_distro_name="$1"
|
|
elif [ -z "${new_distro_name-}" ]; then
|
|
if [ -z "$1" ]; then
|
|
msg
|
|
msg "${BRED}Error: new distribution alias argument should not be empty.${RST}"
|
|
command_rename_help
|
|
return 1
|
|
fi
|
|
new_distro_name="$1"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: got excessive positional argument '${YELLOW}${1}${BRED}'.${RST}"
|
|
command_rename_help
|
|
return 1
|
|
fi
|
|
;;
|
|
esac
|
|
shift 1
|
|
done
|
|
|
|
if [ -z "${orig_distro_name-}" ]; then
|
|
msg
|
|
msg "${BRED}Error: the original alias of distribution is not specified.${RST}"
|
|
command_rename_help
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "${new_distro_name-}" ]; then
|
|
msg
|
|
msg "${BRED}Error: the new alias of distribution is not specified.${RST}"
|
|
command_rename_help
|
|
return 1
|
|
fi
|
|
|
|
if [ "${orig_distro_name}" = "${new_distro_name}" ]; then
|
|
msg
|
|
msg "${BRED}Error: the original and new distribution aliases should not be same.${RST}"
|
|
command_rename_help
|
|
return 1
|
|
fi
|
|
|
|
# Put a restriction on characters in distribution name.
|
|
# Same as for --override-alias option of command_install().
|
|
if ! grep -qP '^[a-z0-9][a-z0-9_.+\-]*$' <<< "${new_distro_name}"; then
|
|
msg
|
|
msg "${BRED}Error: the new alias of distribution should start only with an alphanumeric character and consist of alphanumeric characters including symbols '_.+-'.${RST}"
|
|
command_rename_help
|
|
return 1
|
|
fi
|
|
|
|
if grep -qP '^.*\.sh$' <<< "${new_distro_name}"; then
|
|
msg
|
|
msg "${BRED}Error: the new alias of distribution should not end with '.sh'.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "${SUPPORTED_DISTRIBUTIONS["$orig_distro_name"]+x}" ]; then
|
|
msg
|
|
msg "${BRED}Error: unknown distribution '${YELLOW}${orig_distro_name}${BRED}' was requested to be renamed.${RST}"
|
|
msg
|
|
msg "${CYAN}View supported distributions by: ${GREEN}${PROGRAM_NAME} list${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -d "${INSTALLED_ROOTFS_DIR}/${orig_distro_name}" ]; then
|
|
msg
|
|
msg "${BRED}Error: cannot rename because the distribution '${YELLOW}${orig_distro_name}${BRED}' is not installed.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
if [ -d "${INSTALLED_ROOTFS_DIR}/${new_distro_name}" ]; then
|
|
msg
|
|
msg "${BRED}Error: cannot rename because the rootfs directory for distribution '${YELLOW}${new_distro_name}${BRED}' already exists.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
if [ -e "${DISTRO_PLUGINS_DIR}/${new_distro_name}.sh" ] || [ -e "${DISTRO_PLUGINS_DIR}/${new_distro_name}.override.sh" ]; then
|
|
msg
|
|
msg "${BRED}Error: distribution with alias '${YELLOW}${new_distro_name}${BRED}' already exists.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
if [ -e "${DISTRO_PLUGINS_DIR}/${orig_distro_name}.override.sh" ]; then
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Renaming '${DISTRO_PLUGINS_DIR}/${orig_distro_name}.override.sh' to '${DISTRO_PLUGINS_DIR}/${new_distro_name}.override.sh'...${RST}"
|
|
mv "${DISTRO_PLUGINS_DIR}/${orig_distro_name}.override.sh" "${DISTRO_PLUGINS_DIR}/${new_distro_name}.override.sh"
|
|
sed -i "s/^\(DISTRO_NAME=\)\"\(.*\) - ${orig_distro_name}\"\$/\1\"\2 - ${new_distro_name}\"/g" "${DISTRO_PLUGINS_DIR}/${new_distro_name}.override.sh"
|
|
elif [ -e "${DISTRO_PLUGINS_DIR}/${orig_distro_name}.sh" ]; then
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Creating file '${DISTRO_PLUGINS_DIR}/${new_distro_name}.override.sh'...${RST}"
|
|
cp "${DISTRO_PLUGINS_DIR}/${orig_distro_name}.sh" "${DISTRO_PLUGINS_DIR}/${new_distro_name}.override.sh"
|
|
sed -i "s/^\(DISTRO_NAME=\)\(.*\)\$/\1\"${SUPPORTED_DISTRIBUTIONS["$orig_distro_name"]} - ${new_distro_name}\"/g" "${DISTRO_PLUGINS_DIR}/${new_distro_name}.override.sh"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: could not find a plug-in for distribution '${YELLOW}${orig_distro_name}${BRED}'.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Renaming '${INSTALLED_ROOTFS_DIR}/${orig_distro_name}' to '${INSTALLED_ROOTFS_DIR}/${new_distro_name}'...${RST}"
|
|
mv "${INSTALLED_ROOTFS_DIR}/${orig_distro_name}" "${INSTALLED_ROOTFS_DIR}/${new_distro_name}"
|
|
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Updating PRoot link2symlink extension files (may take long time)...${RST}"
|
|
local symlink_file_name
|
|
find "${INSTALLED_ROOTFS_DIR}/${new_distro_name}" -type l | while read -r symlink_file_name; do
|
|
local symlink_current_target
|
|
symlink_current_target=$(readlink "$symlink_file_name")
|
|
if [ "${symlink_current_target:0:${#INSTALLED_ROOTFS_DIR}}" != "${INSTALLED_ROOTFS_DIR}" ]; then
|
|
# Skip non-l2s symlinks.
|
|
continue
|
|
fi
|
|
local symlink_new_target
|
|
symlink_new_target=$(sed -E "s@(${INSTALLED_ROOTFS_DIR})/([^/]+)/(.*)@\1/${new_distro_name}/\3@g" <<< "$symlink_current_target")
|
|
ln -sf "$symlink_new_target" "$symlink_file_name"
|
|
done
|
|
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Finished.${RST}"
|
|
}
|
|
|
|
command_rename_help() {
|
|
msg
|
|
msg "${BYELLOW}Usage: ${BCYAN}${PROGRAM_NAME} ${GREEN}rename ${CYAN}[${GREEN}ORIG ALIAS${CYAN}] [${GREEN}NEW ALIAS${CYAN}]${RST}"
|
|
msg
|
|
msg "${CYAN}Command aliases: ${GREEN}mv${RST}"
|
|
msg
|
|
msg "${CYAN}Rename a specified Linux distribution.${RST}"
|
|
msg
|
|
msg "${CYAN}Note that renaming default distribution will take a while${RST}"
|
|
msg "${CYAN}as PRoot-Distro has to update symlinks. If user renames a${RST}"
|
|
msg "${CYAN}default distribution, the plug-in copy will be created.${RST}"
|
|
msg
|
|
msg "${CYAN}Options:${RST}"
|
|
msg
|
|
msg " ${GREEN}--help ${CYAN}- Show this help information.${RST}"
|
|
msg
|
|
msg "${CYAN}Selected distribution should be referenced by alias which can be${RST}"
|
|
msg "${CYAN}obtained by this command: ${GREEN}${PROGRAM_NAME} list${RST}"
|
|
msg
|
|
show_version
|
|
msg
|
|
}
|
|
|
|
#############################################################################
|
|
#
|
|
# FUNCTION TO REINSTALL THE GIVEN DISTRIBUTION
|
|
#
|
|
# A wrapper unifying functions command_remove && command_install.
|
|
#
|
|
#############################################################################
|
|
|
|
command_reset() {
|
|
local distro_name
|
|
|
|
while (($# >= 1)); do
|
|
case "$1" in
|
|
-h|--help)
|
|
command_reset_help
|
|
return 0
|
|
;;
|
|
-*)
|
|
msg
|
|
msg "${BRED}Error: got unknown option '${YELLOW}${1}${BRED}'.${RST}"
|
|
command_reset_help
|
|
return 1
|
|
;;
|
|
*)
|
|
if [ -z "${distro_name-}" ]; then
|
|
if [ -z "$1" ]; then
|
|
msg
|
|
msg "${BRED}Error: distribution alias argument should not be empty.${RST}"
|
|
command_reset_help
|
|
return 1
|
|
fi
|
|
distro_name="$1"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: got excessive positional argument '${YELLOW}${1}${BRED}'. Note that distribution can be specified only once.${RST}"
|
|
command_reset_help
|
|
return 1
|
|
fi
|
|
;;
|
|
esac
|
|
shift 1
|
|
done
|
|
|
|
if [ -z "${distro_name-}" ]; then
|
|
msg
|
|
msg "${BRED}Error: distribution alias is not specified.${RST}"
|
|
command_reset_help
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "${SUPPORTED_DISTRIBUTIONS["$distro_name"]+x}" ]; then
|
|
msg
|
|
msg "${BRED}Error: unknown distribution '${YELLOW}${distro_name}${BRED}' was requested to be reset.${RST}"
|
|
msg
|
|
msg "${CYAN}View supported distributions by: ${GREEN}${PROGRAM_NAME} list${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -d "${INSTALLED_ROOTFS_DIR}/${distro_name}" ]; then
|
|
msg
|
|
msg "${BRED}Error: distribution '${YELLOW}${distro_name}${BRED}' is not installed.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
CMD_REMOVE_REQUESTED_RESET="true" command_remove "$distro_name"
|
|
command_install "$distro_name"
|
|
}
|
|
|
|
command_reset_help() {
|
|
msg
|
|
msg "${BYELLOW}Usage: ${BCYAN}${PROGRAM_NAME} ${GREEN}reset ${CYAN}[${GREEN}DISTRIBUTION ALIAS${CYAN}]${RST}"
|
|
msg
|
|
msg "${CYAN}Reinstall the specified Linux distribution.${RST}"
|
|
msg
|
|
msg "${CYAN}Options:${RST}"
|
|
msg
|
|
msg " ${GREEN}--help ${CYAN}- Show this help information.${RST}"
|
|
msg
|
|
msg "${CYAN}Be careful when using it because you will not be prompted for${RST}"
|
|
msg "${CYAN}confirmation and all data saved within the distribution will${RST}"
|
|
msg "${CYAN}instantly gone.${RST}"
|
|
msg
|
|
msg "${CYAN}Selected distribution should be referenced by alias which can be${RST}"
|
|
msg "${CYAN}obtained by this command: ${GREEN}${PROGRAM_NAME} list${RST}"
|
|
msg
|
|
show_version
|
|
msg
|
|
}
|
|
|
|
#############################################################################
|
|
#
|
|
# FUNCTION TO START SHELL OR EXECUTE COMMAND
|
|
#
|
|
# Starts root shell inside the rootfs of specified Linux distribution.
|
|
#
|
|
# If '--' with further arguments was specified, then execute command line
|
|
# given after '--' as root user without starting interactive shell.
|
|
#
|
|
#############################################################################
|
|
|
|
command_login() {
|
|
local fix_low_ports=false
|
|
local isolated_environment=false
|
|
local use_termux_home=false
|
|
local make_host_tmp_shared=false
|
|
local -a custom_fs_bindings
|
|
local no_link2symlink=false
|
|
local no_sysvipc=false
|
|
local no_kill_on_exit=false
|
|
local login_user="root"
|
|
local login_wd=""
|
|
local -a login_env_vars
|
|
login_env_vars=("PATH=${DEFAULT_PATH_ENV}")
|
|
local kernel_release="${DEFAULT_FAKE_KERNEL_VERSION}"
|
|
local distro_name
|
|
|
|
while (($# >= 1)); do
|
|
case "$1" in
|
|
--)
|
|
shift 1
|
|
break
|
|
;;
|
|
--help)
|
|
command_login_help
|
|
return 0
|
|
;;
|
|
--fix-low-ports)
|
|
fix_low_ports=true
|
|
;;
|
|
--isolated)
|
|
isolated_environment=true
|
|
;;
|
|
--termux-home)
|
|
use_termux_home=true
|
|
;;
|
|
--shared-tmp)
|
|
make_host_tmp_shared=true
|
|
;;
|
|
--bind)
|
|
if [ $# -ge 2 ]; then
|
|
shift 1
|
|
|
|
if [ -z "$1" ]; then
|
|
msg
|
|
msg "${BRED}Error: argument to option '${YELLOW}--bind${BRED}' should not be empty.${RST}"
|
|
command_login_help
|
|
return 1
|
|
fi
|
|
|
|
custom_fs_bindings+=("$1")
|
|
else
|
|
msg
|
|
msg "${BRED}Error: option '${YELLOW}--bind${BRED}' requires an argument.${RST}"
|
|
command_login_help
|
|
return 1
|
|
fi
|
|
;;
|
|
--no-link2symlink)
|
|
no_link2symlink=true
|
|
;;
|
|
--no-sysvipc)
|
|
no_sysvipc=true
|
|
;;
|
|
--no-kill-on-exit)
|
|
no_kill_on_exit=true
|
|
;;
|
|
--user)
|
|
if [ $# -ge 2 ]; then
|
|
shift 1
|
|
|
|
if [ -z "$1" ]; then
|
|
msg
|
|
msg "${BRED}Error: argument to option '${YELLOW}--user${BRED}' should not be empty.${RST}"
|
|
command_login_help
|
|
return 1
|
|
fi
|
|
|
|
login_user="$1"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: option '${YELLOW}--user${BRED}' requires an argument.${RST}"
|
|
command_login_help
|
|
return 1
|
|
fi
|
|
;;
|
|
--kernel)
|
|
if [ $# -ge 2 ]; then
|
|
shift 1
|
|
|
|
if [ -z "$1" ]; then
|
|
msg
|
|
msg "${BRED}Error: argument to option '${YELLOW}--kernel${BRED}' should not be empty.${RST}"
|
|
command_login_help
|
|
return 1
|
|
fi
|
|
|
|
kernel_release="$1"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: option '${YELLOW}$1${BRED}' requires an argument.${RST}"
|
|
command_login_help
|
|
return 1
|
|
fi
|
|
;;
|
|
--work-dir)
|
|
if [ $# -ge 2 ]; then
|
|
shift 1
|
|
|
|
if [ -z "$1" ]; then
|
|
msg
|
|
msg "${BRED}Error: argument to option '${YELLOW}--work-dir${BRED}' should not be empty.${RST}"
|
|
command_login_help
|
|
return 1
|
|
fi
|
|
|
|
login_wd="$1"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: option '${YELLOW}--work-dir${BRED}' requires an argument.${RST}"
|
|
command_login_help
|
|
return 1
|
|
fi
|
|
;;
|
|
--env)
|
|
if [ $# -ge 2 ]; then
|
|
shift 1
|
|
|
|
if [ -z "$1" ]; then
|
|
msg
|
|
msg "${BRED}Error: argument to option '${YELLOW}--env${BRED}' should not be empty.${RST}"
|
|
command_login_help
|
|
return 1
|
|
fi
|
|
|
|
login_env_vars+=("$1")
|
|
else
|
|
msg
|
|
msg "${BRED}Error: option '${YELLOW}--env${BRED}' requires an argument.${RST}"
|
|
command_login_help
|
|
return 1
|
|
fi
|
|
;;
|
|
-*)
|
|
msg
|
|
msg "${BRED}Error: got unknown option '${YELLOW}${1}${BRED}'.${RST}"
|
|
command_login_help
|
|
return 1
|
|
;;
|
|
*)
|
|
if [ -z "${distro_name-}" ]; then
|
|
if [ -z "$1" ]; then
|
|
msg
|
|
msg "${BRED}Error: distribution alias argument should not be empty.${RST}"
|
|
command_login_help
|
|
return 1
|
|
fi
|
|
distro_name="$1"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: got excessive positional argument '${YELLOW}${1}${BRED}'. Note that distribution can be specified only once.${RST}"
|
|
command_login_help
|
|
return 1
|
|
fi
|
|
;;
|
|
esac
|
|
shift 1
|
|
done
|
|
|
|
if [ -z "${distro_name-}" ]; then
|
|
msg
|
|
msg "${BRED}Error: distribution alias is not specified.${RST}"
|
|
command_login_help
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "${SUPPORTED_DISTRIBUTIONS["$distro_name"]+x}" ]; then
|
|
msg
|
|
msg "${BRED}Error: unknown distribution '${YELLOW}${distro_name}${BRED}' was requested for logging in.${RST}"
|
|
msg
|
|
msg "${CYAN}View supported distributions by: ${GREEN}${PROGRAM_NAME} list${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
if grep -qiP '(kali|parrot|nethunter|blackarch)' <<< "$distro_name" || grep -qiP '^nh$' <<< "$distro_name"; then
|
|
# Redirect user to a safe distrubution.
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Distribution '${YELLOW}${distro_name}${CYAN}' known to be used by hax and therefore is not safe. Redirecting to '${YELLOW}ubuntu${CYAN}'...${RST}"
|
|
distro_name="ubuntu"
|
|
fi
|
|
|
|
if [ ! -d "${INSTALLED_ROOTFS_DIR}/${distro_name}" ]; then
|
|
msg
|
|
msg "${BRED}Error: distribution '${YELLOW}${distro_name}${BRED}' is not installed.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
if [ -d "${INSTALLED_ROOTFS_DIR}/${distro_name}/.l2s" ]; then
|
|
export PROOT_L2S_DIR="${INSTALLED_ROOTFS_DIR}/${distro_name}/.l2s"
|
|
fi
|
|
|
|
# It's hard to work without /etc/passwd.
|
|
if [ ! -e "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/passwd" ]; then
|
|
msg "${BRED}Error: the selected distribution doesn't have /etc/passwd.${RST}"
|
|
return 1
|
|
fi
|
|
|
|
# Catch invalid specified user before login command will be executed.
|
|
if ! grep -q "${login_user}:" "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/passwd" >/dev/null 2>&1; then
|
|
msg "${BRED}Error: no user '${YELLOW}${login_user}${BRED}' defined in /etc/passwd of distribution.${RST}"
|
|
return 1
|
|
fi
|
|
|
|
local login_uid login_gid login_home login_shell
|
|
login_uid=$(grep "^${login_user}:" "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/passwd" | cut -d ':' -f 3)
|
|
if [ -z "${login_uid}" ]; then
|
|
msg "${BRED}Error: failed to retrieve the id of user '${YELLOW}${login_user}${BRED}' from /etc/passwd of distribution.${RST}"
|
|
return 1
|
|
fi
|
|
login_gid=$(grep "^${login_user}:" "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/passwd" | cut -d ':' -f 4)
|
|
if [ -z "${login_gid}" ]; then
|
|
msg "${BRED}Error: failed to retrieve the primary group id of user '${YELLOW}${login_user}${BRED}' from /etc/passwd of distribution.${RST}"
|
|
return 1
|
|
fi
|
|
login_home=$(grep "^${login_user}:" "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/passwd" | cut -d ':' -f 6)
|
|
if [ -z "${login_home}" ]; then
|
|
msg "${BRED}Error: failed to retrieve the home of user '${YELLOW}${login_user}${BRED}' from /etc/passwd of distribution.${RST}"
|
|
return 1
|
|
fi
|
|
if [ -z "${login_wd}" ]; then
|
|
login_wd="${login_home}"
|
|
fi
|
|
#if [ ! -d "$(realpath "${INSTALLED_ROOTFS_DIR}/${distro_name}/${login_wd}")" ]; then
|
|
# msg "${BRED}Warning: cannot use path '${YELLOW}${login_wd}${BRED}' as working directory.${RST}"
|
|
#fi
|
|
login_shell=$(grep "^${login_user}:" "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/passwd" | cut -d ':' -f 7)
|
|
if [ -z "${login_shell}" ]; then
|
|
msg "${BRED}Error: failed to retrieve the shell of user '${YELLOW}${login_user}${BRED}' from /etc/passwd of distribution.${RST}"
|
|
return 1
|
|
fi
|
|
|
|
# Update Android-specific variables in /etc/environment.
|
|
# Needed to handle changes after Android OS was upgraded.
|
|
chmod u+rw "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/environment" >/dev/null 2>&1 || true
|
|
for var in ANDROID_ART_ROOT ANDROID_DATA ANDROID_I18N_ROOT ANDROID_ROOT \
|
|
ANDROID_RUNTIME_ROOT ANDROID_TZDATA_ROOT BOOTCLASSPATH \
|
|
DEX2OATBOOTCLASSPATH; do
|
|
set +u
|
|
if [ -n "${!var}" ]; then
|
|
# Create new variable entry instead of editing as variable may
|
|
# not exist in the file.
|
|
sed -i "/^${var}=/d" "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/environment"
|
|
echo "${var}=${!var}" >> "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/environment"
|
|
fi
|
|
set -u
|
|
done
|
|
unset var
|
|
|
|
local -a login_shell_args
|
|
if [ $# -ge 1 ]; then
|
|
# Wrap in quotes each argument to prevent word splitting.
|
|
local -a login_shell_args
|
|
for i in "$@"; do
|
|
login_shell_args+=("'$i'")
|
|
done
|
|
set -- "-c" "${login_shell_args[*]}"
|
|
else
|
|
set --
|
|
fi
|
|
|
|
for var in ANDROID_ART_ROOT ANDROID_DATA ANDROID_I18N_ROOT ANDROID_ROOT \
|
|
ANDROID_RUNTIME_ROOT ANDROID_TZDATA_ROOT BOOTCLASSPATH \
|
|
DEX2OATBOOTCLASSPATH EXTERNAL_STORAGE; do
|
|
set +u
|
|
if [ -n "${!var}" ]; then
|
|
login_env_vars+=("${var}=${!var}")
|
|
fi
|
|
set -u
|
|
done
|
|
unset var
|
|
|
|
# Handle /etc/environment.
|
|
if [ -e "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/environment" ]; then
|
|
mapfile -t -O "${#login_env_vars[@]}" login_env_vars < <(
|
|
grep -P '^[A-Za-z_][A-Za-z0-9_]+=.+' "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/environment" | \
|
|
sed -E \
|
|
-e "s/^([^=]+=)['\"]/\1/g" \
|
|
-e "s/['\"]\$//g" \
|
|
-e "/^[^=]+\$/d"
|
|
)
|
|
fi
|
|
|
|
# Using '-i' to ensure that we can fully control which
|
|
# environment variables will be inherited by shell.
|
|
set -- "/usr/bin/env" "-i" \
|
|
"${login_env_vars[@]}" \
|
|
"COLORTERM=${COLORTERM-}" \
|
|
"HOME=${login_home}" \
|
|
"USER=${login_user}" \
|
|
"TERM=${TERM-xterm-256color}" \
|
|
"${login_shell}" \
|
|
"-l" \
|
|
"$@"
|
|
|
|
set -- "--rootfs=${INSTALLED_ROOTFS_DIR}/${distro_name}" "$@"
|
|
set -- "--change-id=${login_uid}:${login_gid}" "$@"
|
|
set -- "--cwd=${login_wd}" "$@"
|
|
|
|
# Setup QEMU when CPU architecture do not match the one of device.
|
|
local target_arch
|
|
if [ -f "${DISTRO_PLUGINS_DIR}/${distro_name}.sh" ]; then
|
|
# shellcheck disable=SC1090
|
|
target_arch=$(. "${DISTRO_PLUGINS_DIR}/${distro_name}.sh"; echo "${DISTRO_ARCH}")
|
|
elif [ -f "${DISTRO_PLUGINS_DIR}/${distro_name}.override.sh" ]; then
|
|
# shellcheck disable=SC1090
|
|
target_arch=$(. "${DISTRO_PLUGINS_DIR}/${distro_name}.override.sh"; echo "${DISTRO_ARCH}")
|
|
else
|
|
# This should never happen.
|
|
msg
|
|
msg "${BRED}Error: missing plugin for distribution '${YELLOW}${distro_name}${BRED}'.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
local need_cpu_emulator=false
|
|
if [ "$target_arch" != "$DEVICE_CPU_ARCH" ]; then
|
|
local cpu_emulator_path=""
|
|
need_cpu_emulator=true
|
|
|
|
# If CPU and host OS are 64bit, we can run 32bit guest OS without emulation.
|
|
# Everything else requires emulator (QEMU).
|
|
case "$target_arch" in
|
|
aarch64) cpu_emulator_path="@TERMUX_PREFIX@/bin/qemu-aarch64";;
|
|
arm)
|
|
if [ "$DEVICE_CPU_ARCH" != "aarch64" ] || ! $SUPPORT_32BIT; then
|
|
cpu_emulator_path="@TERMUX_PREFIX@/bin/qemu-arm"
|
|
else
|
|
need_cpu_emulator=false
|
|
fi
|
|
;;
|
|
i686)
|
|
if [ "$DEVICE_CPU_ARCH" != "x86_64" ]; then
|
|
cpu_emulator_path="@TERMUX_PREFIX@/bin/qemu-i386"
|
|
else
|
|
need_cpu_emulator=false
|
|
fi
|
|
;;
|
|
riscv64) cpu_emulator_path="@TERMUX_PREFIX@/bin/qemu-riscv64";;
|
|
x86_64)
|
|
if [ "$PROOT_DISTRO_X64_EMULATOR" = "QEMU" ]; then
|
|
cpu_emulator_path="@TERMUX_PREFIX@/bin/qemu-x86_64"
|
|
elif [ "$PROOT_DISTRO_X64_EMULATOR" = "BLINK" ]; then
|
|
cpu_emulator_path="@TERMUX_PREFIX@/bin/blink"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: PROOT_DISTRO_X64_EMULATOR has unknown value '${YELLOW}${PROOT_DISTRO_X64_EMULATOR}${BRED}'. Valid values are: BLINK, QEMU."
|
|
msg
|
|
fi
|
|
;;
|
|
*)
|
|
msg
|
|
msg "${BRED}Error: DISTRO_ARCH has unknown value '${YELLOW}${target_arch}${BRED}'. Valid values are: aarch64, arm, i686, riscv64, x86_64."
|
|
msg
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
if [ -n "$cpu_emulator_path" ]; then
|
|
if [ -x "$cpu_emulator_path" ]; then
|
|
set -- "-q" "$cpu_emulator_path" "$@"
|
|
else
|
|
local cpu_emulator_pkg=""
|
|
case "$target_arch" in
|
|
aarch64) cpu_emulator_pkg="qemu-user-aarch64";;
|
|
arm) cpu_emulator_pkg="qemu-user-arm";;
|
|
i686) cpu_emulator_pkg="qemu-user-i386";;
|
|
riscv64) cpu_emulator_pkg="qemu-user-riscv64";;
|
|
x86_64)
|
|
if [ "$PROOT_DISTRO_X64_EMULATOR" = "QEMU" ]; then
|
|
cpu_emulator_pkg="qemu-user-x86-64"
|
|
elif [ "$PROOT_DISTRO_X64_EMULATOR" = "BLINK" ]; then
|
|
cpu_emulator_pkg="blink"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: PROOT_DISTRO_X64_EMULATOR has unknown value '${YELLOW}${PROOT_DISTRO_X64_EMULATOR}${BRED}'. Valid values are: BLINK, QEMU."
|
|
msg
|
|
fi
|
|
;;
|
|
*) cpu_emulator_pkg="qemu-user-$target_arch";;
|
|
esac
|
|
|
|
msg
|
|
msg "${BRED}Error: package '${YELLOW}${cpu_emulator_pkg}${BRED}' is not installed.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
fi
|
|
else
|
|
# Warn about CPU not supporting 32-bit instructions
|
|
if ! $SUPPORT_32BIT; then
|
|
msg "${BRED}Warning: CPU doesn't support 32-bit instructions, some software may not work.${RST}"
|
|
fi
|
|
fi
|
|
|
|
if ! $no_kill_on_exit; then
|
|
# This option terminates all background processes on exit, so
|
|
# proot can terminate freely.
|
|
set -- "--kill-on-exit" "$@"
|
|
else
|
|
msg "${BRED}Warning: option '${YELLOW}--no-kill-on-exit${BRED}' is enabled. When exiting, your session will be blocked until all processes are terminated.${RST}"
|
|
fi
|
|
|
|
if ! $no_link2symlink; then
|
|
# Support hardlinks.
|
|
set -- "--link2symlink" "$@"
|
|
fi
|
|
|
|
if ! $no_sysvipc; then
|
|
# Support System V IPC.
|
|
set -- "--sysvipc" "$@"
|
|
fi
|
|
|
|
# Some devices have old kernels and GNU libc refuses to work on them.
|
|
# Fix this behavior by reporting a fake up-to-date kernel version.
|
|
set -- "--kernel-release=$kernel_release" "$@"
|
|
|
|
# Fix lstat to prevent dpkg symlink size warnings
|
|
set -- "-L" "$@"
|
|
|
|
# Core file systems that should always be present.
|
|
set -- "--bind=/dev" "$@"
|
|
set -- "--bind=/dev/urandom:/dev/random" "$@"
|
|
set -- "--bind=/proc" "$@"
|
|
set -- "--bind=/proc/self/fd:/dev/fd" "$@"
|
|
set -- "--bind=/proc/self/fd/0:/dev/stdin" "$@"
|
|
set -- "--bind=/proc/self/fd/1:/dev/stdout" "$@"
|
|
set -- "--bind=/proc/self/fd/2:/dev/stderr" "$@"
|
|
set -- "--bind=/sys" "$@"
|
|
|
|
# Ensure that we can bind fake /proc entries.
|
|
setup_fake_proc
|
|
|
|
# Fake /proc/loadavg if necessary.
|
|
if ! cat /proc/loadavg > /dev/null 2>&1; then
|
|
set -- "--bind=${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.loadavg:/proc/loadavg" "$@"
|
|
fi
|
|
|
|
# Fake /proc/stat if necessary.
|
|
if ! cat /proc/stat > /dev/null 2>&1; then
|
|
set -- "--bind=${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.stat:/proc/stat" "$@"
|
|
fi
|
|
|
|
# Fake /proc/uptime if necessary.
|
|
if ! cat /proc/uptime > /dev/null 2>&1; then
|
|
set -- "--bind=${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.uptime:/proc/uptime" "$@"
|
|
fi
|
|
|
|
# Fake /proc/version if necessary.
|
|
if ! cat /proc/version > /dev/null 2>&1; then
|
|
set -- "--bind=${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.version:/proc/version" "$@"
|
|
fi
|
|
|
|
# Fake /proc/vmstat if necessary.
|
|
if ! cat /proc/vmstat > /dev/null 2>&1; then
|
|
set -- "--bind=${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.vmstat:/proc/vmstat" "$@"
|
|
fi
|
|
|
|
# Fake /proc/sys/kernel/cap_last_cap if necessary.
|
|
if ! cat /proc/sys/kernel/cap_last_cap > /dev/null 2>&1; then
|
|
set -- "--bind=${INSTALLED_ROOTFS_DIR}/${distro_name}/proc/.sysctl_entry_cap_last_cap:/proc/sys/kernel/cap_last_cap" "$@"
|
|
fi
|
|
|
|
# Bind /tmp to /dev/shm.
|
|
if [ ! -d "${INSTALLED_ROOTFS_DIR}/${distro_name}/tmp" ]; then
|
|
mkdir -p "${INSTALLED_ROOTFS_DIR}/${distro_name}/tmp"
|
|
chmod 1777 "${INSTALLED_ROOTFS_DIR}/${distro_name}/tmp"
|
|
fi
|
|
set -- "--bind=${INSTALLED_ROOTFS_DIR}/${distro_name}/tmp:/dev/shm" "$@"
|
|
|
|
# When running in non-isolated mode, provide some bindings specific
|
|
# to Android and Termux so user can interact with host file system.
|
|
if ! $isolated_environment; then
|
|
for data_dir in /data/app /data/dalvik-cache \
|
|
/data/misc/apexdata/com.android.art/dalvik-cache; do
|
|
[ ! -d "$data_dir" ] && continue
|
|
local dir_mode
|
|
dir_mode=$(stat --format='%a' "$data_dir")
|
|
if [[ ${dir_mode:2} =~ ^[157]$ ]]; then
|
|
set -- "--bind=${data_dir}" "$@"
|
|
fi
|
|
done
|
|
unset data_dir
|
|
|
|
set -- "--bind=/data/data/@TERMUX_APP_PACKAGE@/cache" "$@"
|
|
if [ -d "/data/data/@TERMUX_APP_PACKAGE@/files/apps" ]; then
|
|
set -- "--bind=/data/data/@TERMUX_APP_PACKAGE@/files/apps" "$@"
|
|
fi
|
|
set -- "--bind=@TERMUX_HOME@" "$@"
|
|
|
|
# Bind whole /storage directory when it is readable. This gives
|
|
# access to shared storage and on some Android versions to external
|
|
# disks such as SD cards. On failure try binding only shared
|
|
# storage.
|
|
if ls -1U /storage > /dev/null 2>&1; then
|
|
set -- "--bind=/storage" "$@"
|
|
set -- "--bind=/storage/emulated/0:/sdcard" "$@"
|
|
else
|
|
# We want to use the primary shared storage mount point
|
|
# there with avoiding secondary and legacy mount points. As
|
|
# Android OS versions are different, some directories may
|
|
#be unavailable and we need to try them all.
|
|
local storage_path
|
|
if ls -1U /storage/self/primary/ > /dev/null 2>&1; then
|
|
storage_path="/storage/self/primary"
|
|
elif ls -1U /storage/emulated/0/ > /dev/null 2>&1; then
|
|
storage_path="/storage/emulated/0"
|
|
elif ls -1U /sdcard/ > /dev/null 2>&1; then
|
|
storage_path="/sdcard"
|
|
else
|
|
# Shared storage is not accessible.
|
|
storage_path=""
|
|
fi
|
|
|
|
if [ -n "$storage_path" ]; then
|
|
set -- "--bind=${storage_path}:/sdcard" "$@"
|
|
set -- "--bind=${storage_path}:/storage/emulated/0" "$@"
|
|
set -- "--bind=${storage_path}:/storage/self/primary" "$@"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# When using QEMU, we need some host files even in isolated mode.
|
|
if ! $isolated_environment || $need_cpu_emulator; then
|
|
local system_mnt
|
|
for system_mnt in /apex /odm /product /system /system_ext /vendor \
|
|
/linkerconfig/ld.config.txt \
|
|
/linkerconfig/com.android.art/ld.config.txt \
|
|
/plat_property_contexts /property_contexts; do
|
|
|
|
if [ -e "$system_mnt" ]; then
|
|
system_mnt=$(realpath "$system_mnt")
|
|
else
|
|
continue
|
|
fi
|
|
|
|
if [ -d "$system_mnt" ]; then
|
|
local dir_mode
|
|
dir_mode=$(stat --format='%a' "$system_mnt")
|
|
if [[ ${dir_mode:2} =~ ^[157]$ ]]; then
|
|
set -- "--bind=${system_mnt}" "$@"
|
|
fi
|
|
elif [ -f "$system_mnt" ]; then
|
|
if head -c 1 "$system_mnt" >/dev/null 2>&1; then
|
|
set -- "--bind=${system_mnt}" "$@"
|
|
fi
|
|
else
|
|
continue
|
|
fi
|
|
done
|
|
set -- "--bind=@TERMUX_PREFIX@" "$@"
|
|
fi
|
|
|
|
# Use Termux home directory if requested.
|
|
# Ignores --isolated.
|
|
if $use_termux_home; then
|
|
if [ "$login_user" = "root" ]; then
|
|
set -- "--bind=@TERMUX_HOME@:/root" "$@"
|
|
else
|
|
if [ -f "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/passwd" ]; then
|
|
local user_home
|
|
user_home=$(grep -P "^${login_user}:" "${INSTALLED_ROOTFS_DIR}/${distro_name}/etc/passwd" | cut -d: -f 6)
|
|
|
|
if [ -z "$user_home" ]; then
|
|
user_home="/home/${login_user}"
|
|
fi
|
|
|
|
set -- "--bind=@TERMUX_HOME@:${user_home}" "$@"
|
|
else
|
|
set -- "--bind=@TERMUX_HOME@:/home/${login_user}" "$@"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Bind the tmp folder from the host system to the guest system
|
|
# Ignores --isolated.
|
|
if $make_host_tmp_shared; then
|
|
set -- "--bind=@TERMUX_PREFIX@/tmp:/tmp" "$@"
|
|
fi
|
|
|
|
# Bind custom file systems.
|
|
local bnd
|
|
for bnd in "${custom_fs_bindings[@]}"; do
|
|
set -- "--bind=${bnd}" "$@"
|
|
done
|
|
|
|
# Modify bindings to protected ports to use a higher port number.
|
|
if $fix_low_ports; then
|
|
set -- "-p" "$@"
|
|
fi
|
|
|
|
exec proot "$@"
|
|
}
|
|
|
|
command_login_help() {
|
|
msg
|
|
msg "${BYELLOW}Usage: ${BCYAN}${PROGRAM_NAME} ${GREEN}login ${CYAN}[${GREEN}OPTIONS${CYAN}] [${GREEN}DISTRO ALIAS${CYAN}] [${GREEN}-- ${CYAN}[${GREEN}COMMAND${CYAN}]]${RST}"
|
|
msg
|
|
msg "${CYAN}Command aliases: ${GREEN}sh${RST}"
|
|
msg
|
|
msg "${CYAN}Launch a login shell for the specified distribution if no${RST}"
|
|
msg "${CYAN}additional arguments were given. Otherwise execute the${RST}"
|
|
msg "${CYAN}given command and exit.${RST}"
|
|
msg
|
|
msg "${CYAN}Options:${RST}"
|
|
msg
|
|
msg " ${GREEN}--help ${CYAN}- Show this help information.${RST}"
|
|
msg
|
|
msg " ${GREEN}--user [user] ${CYAN}- Login as specified user instead of 'root'.${RST}"
|
|
msg
|
|
msg " ${GREEN}--fix-low-ports ${CYAN}- Modify bindings to protected ports to use${RST}"
|
|
msg " ${CYAN}a higher port number.${RST}"
|
|
msg
|
|
msg " ${GREEN}--isolated ${CYAN}- Run isolated environment without access${RST}"
|
|
msg " ${CYAN}to host file system.${RST}"
|
|
msg
|
|
msg " ${GREEN}--termux-home ${CYAN}- Mount Termux home directory to /root.${RST}"
|
|
msg " ${CYAN}Takes priority over '${GREEN}--isolated${CYAN}' option.${RST}"
|
|
msg
|
|
msg " ${GREEN}--shared-tmp ${CYAN}- Mount Termux temp directory to /tmp.${RST}"
|
|
msg " ${CYAN}Takes priority over '${GREEN}--isolated${CYAN}' option.${RST}"
|
|
msg
|
|
msg " ${GREEN}--bind [path:path] ${CYAN}- Custom file system binding. Can be specified${RST}"
|
|
msg " ${CYAN}multiple times.${RST}"
|
|
msg " ${CYAN}Takes priority over '${GREEN}--isolated${CYAN}' option.${RST}"
|
|
msg
|
|
msg " ${GREEN}--no-link2symlink ${CYAN}- Disable hardlink emulation by proot.${RST}"
|
|
msg " ${CYAN}Adviseable only on devices with SELinux${RST}"
|
|
msg " ${CYAN}in permissive mode.${RST}"
|
|
msg
|
|
msg " ${GREEN}--no-sysvipc ${CYAN}- Disable System V IPC emulation by proot.${RST}"
|
|
msg
|
|
msg " ${GREEN}--no-kill-on-exit ${CYAN}- Wait until all running processes will finish${RST}"
|
|
msg " ${CYAN}before exiting. This will cause proot to${RST}"
|
|
msg " ${CYAN}freeze if you are running daemons.${RST}"
|
|
msg
|
|
msg " ${GREEN}--kernel [string] ${CYAN}- Set the kernel release and compatibility${RST}"
|
|
msg " ${CYAN}level to string.${RST}"
|
|
msg
|
|
msg " ${GREEN}--work-dir [path] ${CYAN}- Set the working directory.${RST}"
|
|
msg
|
|
msg " ${GREEN}--env ENV=val ${CYAN}- Set environment variable. Can be specified${RST}"
|
|
msg " ${CYAN}multiple times.${RST}"
|
|
msg
|
|
msg "${CYAN}Put '${GREEN}--${CYAN}' if you wish to stop command line processing and pass${RST}"
|
|
msg "${CYAN}options as shell arguments.${RST}"
|
|
msg
|
|
msg "${CYAN}If no '${GREEN}--isolated${CYAN}' option given, the following host directories${RST}"
|
|
msg "${CYAN}will be available:${RST}"
|
|
msg
|
|
msg " ${CYAN}* ${YELLOW}/apex ${CYAN}(only Android 10+)${RST}"
|
|
msg " ${CYAN}* ${YELLOW}/data/dalvik-cache${RST}"
|
|
msg " ${CYAN}* ${YELLOW}/data/data/@TERMUX_APP_PACKAGE@${RST}"
|
|
msg " ${CYAN}* ${YELLOW}/sdcard${RST}"
|
|
msg " ${CYAN}* ${YELLOW}/storage${RST}"
|
|
msg " ${CYAN}* ${YELLOW}/system${RST}"
|
|
msg " ${CYAN}* ${YELLOW}/vendor${RST}"
|
|
msg
|
|
msg "${CYAN}This should be enough to get Termux utilities like termux-api or${RST}"
|
|
msg "${CYAN}termux-open get working. If they do not work for some reason,${RST}"
|
|
msg "${CYAN}make sure they are properly set in ${YELLOW}/etc/environment${CYAN}.${RST}"
|
|
msg
|
|
msg "${CYAN}Also check whether they define variables like ANDROID_DATA,${RST}"
|
|
msg "${CYAN}ANDROID_ROOT, BOOTCLASSPATH and others which are usually set${RST}"
|
|
msg "${CYAN}in Termux sessions.${RST}"
|
|
msg
|
|
msg "${CYAN}If issue occurs only after su/sudo use, then likely your PAM${RST}"
|
|
msg "${CYAN}configuration doesn't load ${YELLOW}/etc/environment${CYAN} and you need to fix${RST}"
|
|
msg "${CYAN}it by enabling pam_env.so in /etc/pam.d configuration.${RST}"
|
|
msg
|
|
msg "${CYAN}Example PAM configuration line:${RST}"
|
|
msg
|
|
msg " ${GREEN}session required pam_env.so readenv=1${RST}"
|
|
msg
|
|
msg "${CYAN}You need to append it to ${YELLOW}/etc/pam.d/su${CYAN}, ${YELLOW}/etc/pam.d/sudo${CYAN} or other${RST}"
|
|
msg "${CYAN}file depending on distribution.${RST}"
|
|
msg
|
|
msg "${CYAN}Selected distribution should be referenced by alias which can be${RST}"
|
|
msg "${CYAN}obtained by this command: ${GREEN}${PROGRAM_NAME} list${RST}"
|
|
msg
|
|
show_version
|
|
msg
|
|
}
|
|
|
|
#############################################################################
|
|
#
|
|
# FUNCTION TO LIST THE SUPPORTED DISTRIBUTIONS
|
|
#
|
|
# Print the summary of available distributions and their installation
|
|
# status. The information about distributions is read from plug-in files.
|
|
#
|
|
#############################################################################
|
|
|
|
command_list() {
|
|
msg
|
|
if [ -z "${!SUPPORTED_DISTRIBUTIONS[*]}" ]; then
|
|
msg "${YELLOW}No distribution plug-ins found.${RST}"
|
|
msg
|
|
msg "${YELLOW}Please check the directory '${DISTRO_PLUGINS_DIR}' and create at least one distribution plug-in.${RST}"
|
|
else
|
|
msg "${CYAN}Supported distributions:${RST}"
|
|
|
|
local i
|
|
for i in $(echo "${!SUPPORTED_DISTRIBUTIONS[@]}" | tr ' ' '\n' | sort -d); do
|
|
msg
|
|
msg " ${CYAN}* ${YELLOW}${SUPPORTED_DISTRIBUTIONS[$i]}${RST}"
|
|
msg
|
|
msg " ${CYAN}Alias: ${YELLOW}${i}${RST}"
|
|
if [ -d "${INSTALLED_ROOTFS_DIR}/${i}" ]; then
|
|
msg " ${CYAN}Installed: ${GREEN}yes${RST}"
|
|
else
|
|
msg " ${CYAN}Installed: ${RED}no${RST}"
|
|
fi
|
|
if [ -n "${SUPPORTED_DISTRIBUTIONS_COMMENTS["${i}"]+x}" ]; then
|
|
msg " ${CYAN}Comment: ${SUPPORTED_DISTRIBUTIONS_COMMENTS["${i}"]}${RST}"
|
|
fi
|
|
done
|
|
|
|
msg
|
|
msg "${CYAN}Install selected one with: ${GREEN}${PROGRAM_NAME} install <alias>${RST}"
|
|
fi
|
|
msg
|
|
}
|
|
|
|
#############################################################################
|
|
#
|
|
# FUNCTION TO BACKUP A SPECIFIED DISTRIBUTION
|
|
#
|
|
# Backup a specified distribution installation by making a tarball that
|
|
# contains distribution rootfs and a corresponding plug-in file.
|
|
#
|
|
#############################################################################
|
|
|
|
command_backup() {
|
|
local distro_name
|
|
local tarball_file_path
|
|
|
|
while (($# >= 1)); do
|
|
case "$1" in
|
|
-h|--help)
|
|
command_backup_help
|
|
return 0
|
|
;;
|
|
--output)
|
|
if [ $# -ge 2 ]; then
|
|
shift 1
|
|
|
|
if [ -z "$1" ]; then
|
|
msg
|
|
msg "${BRED}Error: argument to option '${YELLOW}--output${BRED}' should not be empty.${RST}"
|
|
command_backup_help
|
|
return 1
|
|
fi
|
|
|
|
tarball_file_path="$1"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: option '${YELLOW}--output${BRED}' requires an argument.${RST}"
|
|
command_backup_help
|
|
return 1
|
|
fi
|
|
;;
|
|
-*)
|
|
msg
|
|
msg "${BRED}Error: got unknown option '${YELLOW}${1}${BRED}'.${RST}"
|
|
command_backup_help
|
|
return 1
|
|
;;
|
|
*)
|
|
if [ -z "${distro_name-}" ]; then
|
|
if [ -z "$1" ]; then
|
|
msg
|
|
msg "${BRED}Error: distribution alias argument should not be empty.${RST}"
|
|
command_backup_help
|
|
return 1
|
|
fi
|
|
distro_name="$1"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: got excessive positional argument '${YELLOW}${1}${BRED}'. Note that distribution can be specified only once.${RST}"
|
|
command_backup_help
|
|
return 1
|
|
fi
|
|
;;
|
|
esac
|
|
shift 1
|
|
done
|
|
|
|
if [ -z "${distro_name-}" ]; then
|
|
msg
|
|
msg "${BRED}Error: distribution alias is not specified.${RST}"
|
|
command_backup_help
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "${SUPPORTED_DISTRIBUTIONS["$distro_name"]+x}" ]; then
|
|
msg
|
|
msg "${BRED}Error: unknown distribution '${YELLOW}${distro_name}${BRED}' was requested for backup.${RST}"
|
|
msg
|
|
msg "${CYAN}View supported distributions by: ${GREEN}${PROGRAM_NAME} list${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
if grep -qiP '(kali|parrot|nethunter|blackarch)' <<< "$distro_name" || grep -qiP '^nh$' <<< "$distro_name"; then
|
|
# Redirect user to a safe distrubution.
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Distribution '${YELLOW}${distro_name}${CYAN}' known to be used by hax and therefore is not safe. Redirecting to '${YELLOW}ubuntu${CYAN}'...${RST}"
|
|
distro_name="ubuntu"
|
|
fi
|
|
|
|
if [ ! -d "${INSTALLED_ROOTFS_DIR}/${distro_name}" ]; then
|
|
msg
|
|
msg "${BRED}Error: distribution '${YELLOW}${distro_name}${BRED}' is not installed.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
|
|
# Notify user if tar available in PATH is not GNU tar.
|
|
if ! grep -q 'tar (GNU tar)' <(tar --version 2>/dev/null | head -n 1); then
|
|
msg
|
|
msg "${BRED}Warning: tar binary that is available in PATH appears to be not a GNU tar. You may experience issues during installation, backup and restore operations.${RST}"
|
|
msg
|
|
fi
|
|
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Backing up ${YELLOW}${SUPPORTED_DISTRIBUTIONS["$distro_name"]}${CYAN}...${RST}"
|
|
|
|
if [ -z "${tarball_file_path-}" ]; then
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Tarball will be written to stdout.${RST}"
|
|
|
|
if [ -t 1 ]; then
|
|
msg
|
|
msg "${BRED}Error: tarball cannot be printed to console. Please use option '${YELLOW}--output${BRED}' to specify a file or use pipe for sending the output to another program.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
else
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Tarball will be written to '${tarball_file_path}'.${RST}"
|
|
|
|
if [ -d "$tarball_file_path" ]; then
|
|
msg
|
|
msg "${BRED}Error: cannot write to '${YELLOW}${tarball_file_path}${YELLOW}' because this path is a directory.${RST}"
|
|
command_backup_help
|
|
return 1
|
|
fi
|
|
|
|
if [ -f "$tarball_file_path" ]; then
|
|
msg
|
|
msg "${BRED}Error: file '${YELLOW}${tarball_file_path}${YELLOW}' already exists. Please specify a different name.${RST}"
|
|
command_backup_help
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Fixing file permissions in rootfs...${RST}"
|
|
# Ensure we can read all files.
|
|
find "${INSTALLED_ROOTFS_DIR}/${distro_name}" -type d -print0 | xargs -0 -r chmod u+rx
|
|
find "${INSTALLED_ROOTFS_DIR}/${distro_name}" -type f -executable -print0 | xargs -0 -r chmod u+rx
|
|
find "${INSTALLED_ROOTFS_DIR}/${distro_name}" -type f ! -executable -print0 | xargs -0 -r chmod u+r
|
|
|
|
local distro_plugin_script="${distro_name}.sh"
|
|
if [ ! -f "${DISTRO_PLUGINS_DIR}/${distro_plugin_script}" ]; then
|
|
# Alt name.
|
|
distro_plugin_script="${distro_name}.override.sh"
|
|
|
|
# We already passed check for supported distributions but doing
|
|
# this check anyway. Above step with fixing permissions takes enough
|
|
# time to let significant changes on file system happen.
|
|
if [ ! -f "${DISTRO_PLUGINS_DIR}/${distro_plugin_script}" ]; then
|
|
msg
|
|
msg "${BRED}Error: neither '${distro_name}.sh' nor '${distro_name}.override.sh' are available in directory '${DISTRO_PLUGINS_DIR}'.${RST}"
|
|
msg
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Archiving the rootfs and plug-in...${RST}"
|
|
if [ -n "${tarball_file_path-}" ]; then
|
|
# shellcheck disable=SC2064 # variables must expand here
|
|
trap "echo -e \"\\r\\e[2K${BLUE}[${RED}!${BLUE}] ${CYAN}Exiting due to failure.${RST}\"; rm -f \"${tarball_file_path:?}\"; exit 1;" EXIT
|
|
# shellcheck disable=SC2064 # variables must expand here
|
|
trap "trap - EXIT; echo -e \"\\r\\e[2K${BLUE}[${RED}!${BLUE}] ${CYAN}Exiting immediately as requested.${RST}\"; rm -f \"${tarball_file_path:?}\"; exit 1;" HUP INT TERM
|
|
tar -c --auto-compress \
|
|
--warning=no-file-ignored \
|
|
-f "$tarball_file_path" \
|
|
-C "${DISTRO_PLUGINS_DIR}/../" "$(basename "$DISTRO_PLUGINS_DIR")/${distro_plugin_script}" \
|
|
-C "${INSTALLED_ROOTFS_DIR}/../" "$(basename "$INSTALLED_ROOTFS_DIR")/${distro_name}"
|
|
trap - EXIT
|
|
trap 'echo -e "\\r\\e[2K${BLUE}[${RED}!${BLUE}] ${CYAN}Exiting immediately as requested.${RST}"; exit 1;' HUP INT TERM
|
|
else
|
|
tar -c \
|
|
--warning=no-file-ignored \
|
|
-C "${DISTRO_PLUGINS_DIR}/../" "$(basename "$DISTRO_PLUGINS_DIR")/${distro_plugin_script}" \
|
|
-C "${INSTALLED_ROOTFS_DIR}/../" "$(basename "$INSTALLED_ROOTFS_DIR")/${distro_name}"
|
|
fi
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Finished.${RST}"
|
|
}
|
|
|
|
command_backup_help() {
|
|
msg
|
|
msg "${BYELLOW}Usage: ${BCYAN}${PROGRAM_NAME} ${GREEN}backup ${CYAN}[${GREEN}DISTRIBUTION ALIAS${CYAN}]${RST}"
|
|
msg
|
|
msg "${CYAN}Command aliases: ${GREEN}bak${CYAN}, ${GREEN}bkp${RST}"
|
|
msg
|
|
msg "${CYAN}Back up a specified distribution installation into tarball.${RST}"
|
|
msg
|
|
msg "${CYAN}Options:${RST}"
|
|
msg
|
|
msg " ${GREEN}--help ${CYAN}- Show this help information.${RST}"
|
|
msg
|
|
msg " ${GREEN}--output [path] ${CYAN}- Write tarball to specified file.${RST}"
|
|
msg " ${CYAN}If not specified, the tarball will be${RST}"
|
|
msg " ${CYAN}printed to stdout. File extension affects${RST}"
|
|
msg " ${CYAN}used compression (e.g. gz, bz2, xz).${RST}"
|
|
msg " ${CYAN}Backup sent to stdout is not compressed.${RST}"
|
|
msg
|
|
msg "${CYAN}Selected distribution should be referenced by alias which can be${RST}"
|
|
msg "${CYAN}obtained by this command: ${GREEN}${PROGRAM_NAME} list${RST}"
|
|
msg
|
|
show_version
|
|
msg
|
|
}
|
|
|
|
#############################################################################
|
|
#
|
|
# FUNCTION TO RESTORE A SPECIFIED DISTRIBUTION
|
|
#
|
|
# Restore a specified distribution installation from the backup (tarball).
|
|
# The supplied tarball should be one made by PRoot-Distro as it has a proper
|
|
# structure. Regular rootfs tarball will not work here.
|
|
#
|
|
#############################################################################
|
|
|
|
command_restore() {
|
|
local tarball_file_path
|
|
|
|
while (($# >= 1)); do
|
|
case "$1" in
|
|
-h|--help)
|
|
command_restore_help
|
|
return 0
|
|
;;
|
|
-*)
|
|
msg
|
|
msg "${BRED}Error: got unknown option '${YELLOW}${1}${BRED}'.${RST}"
|
|
command_restore_help
|
|
return 1
|
|
;;
|
|
*)
|
|
if [ -z "${tarball_file_path-}" ]; then
|
|
if [ -z "$1" ]; then
|
|
msg
|
|
msg "${BRED}Error: tarball file path argument should not be empty.${RST}"
|
|
command_restore_help
|
|
return 1
|
|
fi
|
|
tarball_file_path="$1"
|
|
else
|
|
msg
|
|
msg "${BRED}Error: got excessive positional argument '${YELLOW}${1}${BRED}'. Note that tarball file path can be specified only once.${RST}"
|
|
command_restore_help
|
|
return 1
|
|
fi
|
|
;;
|
|
esac
|
|
shift 1
|
|
done
|
|
|
|
if [ -n "${tarball_file_path-}" ]; then
|
|
if [ ! -e "$tarball_file_path" ]; then
|
|
msg
|
|
msg "${BRED}Error: file '${YELLOW}${tarball_file_path}${YELLOW}' does not exist.${RST}"
|
|
command_restore_help
|
|
return 1
|
|
fi
|
|
|
|
if [ -d "$tarball_file_path" ]; then
|
|
msg
|
|
msg "${BRED}Error: path '${YELLOW}${tarball_file_path}${YELLOW}' is a directory.${RST}"
|
|
command_restore_help
|
|
return 1
|
|
fi
|
|
else
|
|
if [ -t 0 ]; then
|
|
msg
|
|
msg "${BRED}Error: tarball file path is not specified and it looks like nothing is being piped via stdin either.${RST}"
|
|
command_restore_help
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Notify user if tar available in PATH is not GNU tar.
|
|
if ! grep -q 'tar (GNU tar)' <(tar --version 2>/dev/null | head -n 1); then
|
|
msg
|
|
msg "${BRED}Warning: tar binary that is available in PATH appears to be not a GNU tar. You may experience issues during installation, backup and restore operations.${RST}"
|
|
msg
|
|
fi
|
|
|
|
local success
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Extracting distribution plug-in and rootfs from the tarball...${RST}"
|
|
if [ -n "${tarball_file_path-}" ]; then
|
|
if mkdir -p "${INSTALLED_ROOTFS_DIR}" && tar -x --auto-compress -f "$tarball_file_path" \
|
|
--recursive-unlink --preserve-permissions \
|
|
-C "${DISTRO_PLUGINS_DIR}/../" "$(basename "${DISTRO_PLUGINS_DIR}")/" \
|
|
-C "${INSTALLED_ROOTFS_DIR}/../" "$(basename "${INSTALLED_ROOTFS_DIR}")/"; then
|
|
success=true
|
|
else
|
|
success=false
|
|
fi
|
|
else
|
|
if mkdir -p "${INSTALLED_ROOTFS_DIR}" && tar -x --recursive-unlink --preserve-permissions \
|
|
-C "${DISTRO_PLUGINS_DIR}/../" "$(basename "${DISTRO_PLUGINS_DIR}")/" \
|
|
-C "${INSTALLED_ROOTFS_DIR}/../" "$(basename "${INSTALLED_ROOTFS_DIR}")/"; then
|
|
success=true
|
|
else
|
|
success=false
|
|
fi
|
|
fi
|
|
|
|
if $success; then
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Finished.${RST}"
|
|
else
|
|
msg "${BLUE}[${RED}!${BLUE}] ${CYAN}Failure.${RST}"
|
|
msg
|
|
msg "${BRED}Failed to restore distribution from the given tarball.${RST}"
|
|
msg
|
|
msg "${BRED}Possibly that tarball was corrupted or not made by PRoot-Distro. Note that tarball piped to stdin must be decompressed.${RST}"
|
|
msg
|
|
fi
|
|
}
|
|
|
|
command_restore_help() {
|
|
msg
|
|
msg "${BYELLOW}Usage: ${BCYAN}${PROGRAM_NAME} ${GREEN}restore ${CYAN}[${GREEN}FILENAME.TAR${CYAN}]${RST}"
|
|
msg
|
|
msg "${CYAN}Restore distribution installation from a specified tarball. If${RST}"
|
|
msg "${CYAN}file name is not specified, it will be assumed that tarball is${RST}"
|
|
msg "${CYAN}being piped from stdin.${RST}"
|
|
msg
|
|
msg "${CYAN}Options:${RST}"
|
|
msg
|
|
msg " ${GREEN}--help ${CYAN}- Show this help information.${RST}"
|
|
msg
|
|
msg "${CYAN}Archive compression is determined automatically from the file${RST}"
|
|
msg "${CYAN}extension. When archive content is piped it is expected that${RST}"
|
|
msg "${CYAN}data is not compressed.${RST}"
|
|
msg
|
|
msg "${CYAN}Important note: there are no any sanity check being performed${RST}"
|
|
msg "${CYAN}on the supplied tarballs. Be careful when using this command as${RST}"
|
|
msg "${CYAN}data loss may happen when the wrong tarball was used.${RST}"
|
|
msg
|
|
show_version
|
|
msg
|
|
}
|
|
|
|
#############################################################################
|
|
#
|
|
# FUNCTION TO CLEAR DOWNLOAD CACHE
|
|
#
|
|
# Delete all cached rootfs tarballs.
|
|
#
|
|
#############################################################################
|
|
|
|
command_clear_cache() {
|
|
while (($# >= 1)); do
|
|
case "$1" in
|
|
-h|--help)
|
|
command_clear_cache_help
|
|
return 0
|
|
;;
|
|
-*)
|
|
msg
|
|
msg "${BRED}Error: got unknown option '${YELLOW}${1}${BRED}'.${RST}"
|
|
command_clear_cache_help
|
|
return 1
|
|
;;
|
|
*)
|
|
msg
|
|
msg "${BRED}Error: got excessive positional argument '${YELLOW}${1}${BRED}'. Note that tarball file path can be specified only once.${RST}"
|
|
command_clear_cache_help
|
|
return 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if ! ls -la "${DOWNLOAD_CACHE_DIR}"/* > /dev/null 2>&1; then
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Download cache is empty.${RST}"
|
|
else
|
|
local size_of_cache
|
|
size_of_cache="$(du -d 0 -h -a ${DOWNLOAD_CACHE_DIR} | awk '{$2=$2};1' | cut -d " " -f 1)"
|
|
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Clearing cache files...${RST}"
|
|
|
|
local filename
|
|
while read -r filename; do
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Deleting ${CYAN}'${filename}'${RST}"
|
|
rm -f "${filename}"
|
|
done < <(find "${DOWNLOAD_CACHE_DIR}" -type f)
|
|
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Reclaimed ${size_of_cache} of disk space.${RST}"
|
|
fi
|
|
|
|
msg "${BLUE}[${GREEN}*${BLUE}] ${CYAN}Finished.${RST}"
|
|
}
|
|
|
|
command_clear_cache_help() {
|
|
msg
|
|
msg "${BYELLOW}Usage: ${BCYAN}${PROGRAM_NAME} ${GREEN}clear-cache${RST}"
|
|
msg
|
|
msg "${CYAN}Command aliases: ${GREEN}clear${CYAN}, ${GREEN}cl${RST}"
|
|
msg
|
|
msg "${CYAN}Remove all cached rootfs tarballs to reclaim disk space.${RST}"
|
|
msg
|
|
show_version
|
|
msg
|
|
}
|
|
|
|
#############################################################################
|
|
#
|
|
# FUNCTION TO PRINT UTILITY USAGE INFORMATION
|
|
#
|
|
# Prints a description of PRoot-Distro utility and list of the available
|
|
# commands.
|
|
#
|
|
#############################################################################
|
|
|
|
command_help() {
|
|
msg
|
|
msg "${BYELLOW}Usage: ${BCYAN}${PROGRAM_NAME}${CYAN} [${GREEN}COMMAND${CYAN}] [${GREEN}ARGUMENTS${CYAN}]${RST}"
|
|
msg
|
|
msg "${CYAN}PRoot-Distro is a Bash script wrapper for PRoot. It provides${RST}"
|
|
msg "${CYAN}a set of functions with standardized command line interface${RST}"
|
|
msg "${CYAN}to let user easily manage Linux PRoot containers. By default${RST}"
|
|
msg "${CYAN}it supports a number of well known Linux distributions such${RST}"
|
|
msg "${CYAN}Alpine Linux, Debian or OpenSUSE. However it is possible to${RST}"
|
|
msg "${CYAN}add others with a help of plug-ins.${RST}"
|
|
msg
|
|
msg "${CYAN}List of the available commands:${RST}"
|
|
msg
|
|
msg " ${GREEN}help ${CYAN}- Show this help information.${RST}"
|
|
msg
|
|
msg " ${GREEN}backup ${CYAN}- Backup a specified distribution.${RST}"
|
|
msg
|
|
msg " ${GREEN}install ${CYAN}- Install a specified distribution.${RST}"
|
|
msg
|
|
msg " ${GREEN}list ${CYAN}- List supported distributions and their${RST}"
|
|
msg " ${CYAN}installation status.${RST}"
|
|
msg
|
|
msg " ${GREEN}login ${CYAN}- Start login shell for the specified distribution.${RST}"
|
|
msg
|
|
msg " ${GREEN}remove ${CYAN}- Delete a specified distribution.${RST}"
|
|
msg " ${RED}WARNING: this command destroys data!${RST}"
|
|
msg
|
|
msg " ${GREEN}rename ${CYAN}- Rename installed distribution.${RST}"
|
|
msg
|
|
msg " ${GREEN}reset ${CYAN}- Reinstall from scratch a specified distribution.${RST}"
|
|
msg " ${RED}WARNING: this command destroys data!${RST}"
|
|
msg
|
|
msg " ${GREEN}restore ${CYAN}- Restore a specified distribution.${RST}"
|
|
msg " ${RED}WARNING: this command destroys data!${RST}"
|
|
msg
|
|
msg " ${GREEN}clear-cache ${CYAN}- Clear cache of downloaded files. ${RST}"
|
|
msg
|
|
msg "${CYAN}Each of commands has its own help information. To view it, just${RST}"
|
|
msg "${CYAN}supply a '${GREEN}--help${CYAN}' argument to chosen command.${RST}"
|
|
msg
|
|
msg "${CYAN}Hint: type command '${GREEN}${PROGRAM_NAME} list${CYAN}' to get a list of the${RST}"
|
|
msg "${CYAN}supported distributions. Pick a distro alias and run the next${RST}"
|
|
msg "${CYAN}command to install it: ${GREEN}${PROGRAM_NAME} install <alias>${RST}"
|
|
msg
|
|
msg "${CYAN}Runtime data is stored at this location:${RST}"
|
|
msg
|
|
msg "${YELLOW} ${RUNTIME_DIR}${RST}"
|
|
msg
|
|
msg "${CYAN}If you have issues with proot during installation or login, try${RST}"
|
|
msg "${CYAN}to set '${GREEN}PROOT_NO_SECCOMP=1${CYAN}' environment variable.${RST}"
|
|
msg
|
|
show_donate
|
|
msg
|
|
show_version
|
|
msg
|
|
}
|
|
|
|
#############################################################################
|
|
#
|
|
# FUNCTION TO PRINT VERSION STRING
|
|
#
|
|
# Prints version & author information. Used in functions for displaying
|
|
# usage info.
|
|
#
|
|
#############################################################################
|
|
|
|
show_version() {
|
|
msg "${ICYAN}Proot-Distro v${PROGRAM_VERSION} by Termux (@sylirre).${RST}"
|
|
}
|
|
|
|
#############################################################################
|
|
#
|
|
# FUNCTION TO PRINT VERSION STRING
|
|
#
|
|
# Prints version & author information. Used in functions for displaying
|
|
# usage info.
|
|
#
|
|
#############################################################################
|
|
|
|
show_donate() {
|
|
msg "${MAGENTA}Support development by donation and motivate us to bring new${RST}"
|
|
msg "${MAGENTA}features. We are accepting cryptocurrency:${RST}"
|
|
msg
|
|
msg "${MAGENTA}LTC: ltc1q2yne7e2p5ypf2ky0j3tg3vd6yktd5u57rmlly9${RST}"
|
|
msg
|
|
msg "${MAGENTA}TRX: TUP941DmHfrBNxvbcYkThx9hHrskU7FyTa${RST}"
|
|
}
|
|
|
|
#############################################################################
|
|
#
|
|
# ENTRY POINT
|
|
#
|
|
# 1. Determine device properties such as CPU architecture.
|
|
# 2. Check all available distribution plug-ins.
|
|
# 3. Handle the requested commands or show help when '-h/--help/help' were
|
|
# given. Further command line processing is offloaded to requested command.
|
|
#
|
|
#############################################################################
|
|
|
|
# This will be executed when signal HUP/INT/TERM is received.
|
|
trap 'echo -e "\\r${BLUE}[${RED}!${BLUE}] ${CYAN}Exiting immediately as requested.${RST}"; exit 1;' HUP INT TERM
|
|
|
|
# Determine a CPU architecture of device.
|
|
case "$(uname -m)" in
|
|
# Note: armv8l means that device is running 32bit OS on 64bit CPU.
|
|
armv7l|armv8l) DEVICE_CPU_ARCH="arm";;
|
|
*) DEVICE_CPU_ARCH=$(uname -m);;
|
|
esac
|
|
DISTRO_ARCH=${DISTRO_ARCH:-}
|
|
if [ -z "$DISTRO_ARCH" ]; then DISTRO_ARCH="${DEVICE_CPU_ARCH}"; fi
|
|
|
|
# Verify architecture if possible - avoid running under linux32 or similar.
|
|
if [ -x "@TERMUX_PREFIX@/bin/dpkg" ]; then
|
|
if [ "$DEVICE_CPU_ARCH" != "$("@TERMUX_PREFIX@"/bin/dpkg --print-architecture)" ]; then
|
|
msg
|
|
msg "${BRED}Error: the CPU architecture reported by system does not match the architecture of Termux packages. Do not attempt to hijack system properties by using 'linux32' or similar utilities.${RST}"
|
|
msg
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Check if architecture supports 32-bit instructions.
|
|
SUPPORT_32BIT=true
|
|
if grep -q "CPU op-mode" <(lscpu) 2>/dev/null && ! grep -qE 'CPU op-mode\(s\):.*32-bit' <(lscpu) 2>/dev/null; then
|
|
SUPPORT_32BIT=false
|
|
fi
|
|
|
|
declare -A TARBALL_URL TARBALL_SHA256
|
|
declare -A SUPPORTED_DISTRIBUTIONS
|
|
declare -A SUPPORTED_DISTRIBUTIONS_COMMENTS
|
|
while read -r filename; do
|
|
# shellcheck disable=SC1090
|
|
distro_name=$(. "$filename"; echo "${DISTRO_NAME-}")
|
|
# shellcheck disable=SC1090
|
|
distro_comment=$(. "$filename"; echo "${DISTRO_COMMENT-}")
|
|
# May have 2 name formats:
|
|
# * alias.override.sh
|
|
# * alias.sh
|
|
# but we need to treat both as 'alias'.
|
|
distro_alias=${filename%%.override.sh}
|
|
distro_alias=${distro_alias%%.sh}
|
|
distro_alias=$(basename "$distro_alias")
|
|
|
|
# We getting distribution name from $DISTRO_NAME which
|
|
# should be set in plug-in.
|
|
if [ -z "$distro_name" ]; then
|
|
msg
|
|
msg "${BRED}Error: no DISTRO_NAME defined in '${YELLOW}${filename}${BRED}'.${RST}"
|
|
msg
|
|
exit 1
|
|
fi
|
|
|
|
SUPPORTED_DISTRIBUTIONS["$distro_alias"]="$distro_name"
|
|
[ -n "$distro_comment" ] && SUPPORTED_DISTRIBUTIONS_COMMENTS["$distro_alias"]="$distro_comment"
|
|
done < <(find "$DISTRO_PLUGINS_DIR" -maxdepth 1 \( -type f -o -type l \) -iname "*.sh" 2>/dev/null)
|
|
unset distro_name distro_alias
|
|
|
|
if [ $# -ge 1 ]; then
|
|
case "$1" in
|
|
-h|--help|help|hel|he|h) shift 1; command_help;;
|
|
backup|bak|bkp) shift 1; command_backup "$@";;
|
|
install|i|in|ins|add) shift 1; command_install "$@";;
|
|
list|li|ls) shift 1; command_list;;
|
|
login|sh) shift 1; command_login "$@";;
|
|
remove|rm) shift 1; CMD_REMOVE_REQUESTED_RESET="false" command_remove "$@";;
|
|
rename|mv) shift 1; command_rename "$@";;
|
|
clear-cache|clear|cl) shift 1; command_clear_cache "$@";;
|
|
|
|
# Not implementing aliases as they could be confusing.
|
|
# We don't have many choices for these two commands: r, re, res, rst.
|
|
reset) shift 1; command_reset "$@";;
|
|
restore) shift 1; command_restore "$@";;
|
|
|
|
*)
|
|
msg
|
|
msg "${BRED}Error: unknown command '${YELLOW}${1}${BRED}'.${RST}"
|
|
msg
|
|
msg "${CYAN}View supported commands by: ${GREEN}${PROGRAM_NAME} help${CYAN}${RST}"
|
|
msg
|
|
exit 1
|
|
;;
|
|
esac
|
|
else
|
|
msg
|
|
msg "${BRED}Error: no command provided.${RST}"
|
|
command_help
|
|
fi
|
|
|
|
exit 0
|