6
\$\begingroup\$
#!/usr/bin/env bash
# ==========================
# Termux Sandbox Script
# ==========================
# This script creates a localized, Termux-like sandbox using proot, designed to
# provide an isolated, ephemeral Termux environment within the ~/sandbox-termux
# directory. It mimics a clean Termux installation and is ideal for testing.
#
# Features:
# - Start, reset, and destroy the sandbox.
# - Bind /sdcard for minimal external storage access (optional).
# - Pre-install default Termux packages to mimic a clean Termux environment.
# - Sets up all necessary Termux-specific environment variables.
#
# Dependencies: proot
#
# Usage:
# ./sandbox-termux.sh [options]
# Options:
# start Start the sandbox and drop into a shell
# destroy Destroy the sandbox environment
# reset Reset the sandbox, re-initializing both usr and home
# -s, --sdcard Bind /sdcard for external storage access (optional)
# -h, --help Show this help message and exit
# ==========================
# Global variables
# ==========================
SANDBOX_DIR="$HOME/sandbox-termux"
SANDBOX_HOME="${SANDBOX_DIR}/home"
BIND_SDCARD=false
TERMUX_USR="/data/data/com.termux/files/usr" # Shared root directory
# ==========================
# Functions
# ==========================
# Show usage information
function print_usage() {
 cat <<EOF
Usage: $(basename "0ドル") [start|destroy|reset] [options]
Options:
 start Start the Termux sandbox and drop into a shell
 destroy Destroy the sandbox environment
 reset Reset the sandbox (delete and recreate both usr and home)
 -s, --sdcard Bind /sdcard to access external storage within the sandbox
 -h, --help Show this help message and exit
Examples:
 ./sandbox-termux.sh start -s
 ./sandbox-termux.sh destroy
 ./sandbox-termux.sh reset
EOF
}
# Initialize the standard shell configuration files
function initialize_shell_config() {
 # Ensure .bashrc, .profile, and .bash_logout exist
 cat > "${SANDBOX_HOME}/.bashrc" << 'EOF'
# .bashrc for Termux sandbox
export PATH="$PREFIX/bin:$PATH"
export LD_LIBRARY_PATH="$PREFIX/lib"
export TERM=xterm-256color
alias ls='ls --color=auto'
alias ll='ls -la'
EOF
 cat > "${SANDBOX_HOME}/.profile" << 'EOF'
# .profile for Termux sandbox
if [ -n "$BASH" ]; then
 # Source .bashrc if present
 if [ -f "$HOME/.bashrc" ]; then
 . "$HOME/.bashrc"
 fi
fi
export HOME="$HOME"
export PREFIX="$HOME/../usr"
export PATH="$PREFIX/bin:$PATH"
export TMPDIR="$PREFIX/tmp"
EOF
 cat > "${SANDBOX_HOME}/.bash_logout" << 'EOF'
# .bash_logout for Termux sandbox
# Clean up temporary files on logout
EOF
 # Ensure tmp directory exists inside the sandbox (in this case, shared with host Termux)
 mkdir -p "${TERMUX_USR}/tmp"
}
# Create the initial sandbox directory structure
function initialize_sandbox() {
 echo "Initializing sandbox directory structure..."
 # Create sandbox home directory
 mkdir -p "${SANDBOX_HOME}"
 # Initialize the standard shell configuration files
 initialize_shell_config
 echo "Sandbox initialization complete."
}
# Start the sandbox environment
function start_sandbox() {
 if [[ ! -d "${SANDBOX_HOME}" ]]; then
 echo "Sandbox home directory not found. Initializing sandbox..."
 initialize_sandbox
 fi
 # Prepare proot command with appropriate bindings
 PROOT_CMD="proot --link2symlink -r ${TERMUX_USR} -b ${SANDBOX_HOME}:/data/data/com.termux/files/home -b /dev/ -b /proc/ -b /sys/"
 # Bind external storage (/sdcard) if requested
 if [[ "${BIND_SDCARD}" == true ]]; then
 PROOT_CMD="${PROOT_CMD} -b /sdcard"
 echo "Binding /sdcard to sandbox environment."
 fi
 echo "Starting Termux sandbox..."
 ${PROOT_CMD} /usr/bin/env -i HOME="/data/data/com.termux/files/home" TERM="$TERM" \
 PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin \
 PREFIX="${TERMUX_USR}" \
 LD_LIBRARY_PATH="${TERMUX_USR}/lib" \
 TMPDIR="${TERMUX_USR}/tmp" \
 /bin/bash --login
}
# Destroy the sandbox environment
function destroy_sandbox() {
 if [[ -d "${SANDBOX_DIR}" ]]; then
 echo "Destroying Termux sandbox..."
 rm -rf "${SANDBOX_DIR}"
 echo "Sandbox destroyed."
 else
 echo "No sandbox directory found. Nothing to destroy."
 fi
}
# Reset the sandbox environment by destroying and recreating it
function reset_sandbox() {
 echo "Resetting Termux sandbox..."
 destroy_sandbox
 start_sandbox
}
# ==========================
# Argument Parsing
# ==========================
# Parse command line options and arguments
POSITIONAL_ARGS=()
while [[ $# -gt 0 ]]; do
 case 1ドル in
 start|destroy|reset)
 ACTION="1ドル"
 shift
 ;;
 -s|--sdcard)
 BIND_SDCARD=true
 shift
 ;;
 -h|--help)
 print_usage
 exit 0
 ;;
 *)
 echo "Unknown option: 1ドル"
 print_usage
 exit 1
 ;;
 esac
done
# ==========================
# Main Logic
# ==========================
if [[ -z "${ACTION}" ]]; then
 echo "Error: No action specified."
 print_usage
 exit 1
fi
case "${ACTION}" in
 start)
 start_sandbox
 ;;
 destroy)
 destroy_sandbox
 ;;
 reset)
 reset_sandbox
 ;;
 *)
 echo "Error: Invalid action '${ACTION}'."
 print_usage
 exit 1
 ;;
esac

This is my fifth script iteraton. I am not yet a proficient shell scripter, but I am aspiring to be a better scripter and become adept, and wish to contribute to the Termux community in future. I am still learning how Termux works. This project is to help make a testing environment for my own scripts, modules and packages, as well as an environment for testing post-patching compilation and builds of third-party projects for possible inclusion in the Termux repository. This sandbox script is endeavouring to be an addition and companion to Android Studio, which I am running in a proot chroot alias of Debian GNU/Linux.

toolic
14.3k5 gold badges29 silver badges200 bronze badges
asked Oct 20, 2024 at 9:14
\$\endgroup\$
1
  • 3
    \$\begingroup\$ Here's the comment I left on your S.O. posting : If your code passes the tests at shellcheck,net then you know more about shell scripting that 95% of general shell scripting population (-; . As you're doing this for a particular "community" (Termux users), it would be better to spend your time getting feedback from them. There is almost certainly a specialty website with a forums area where you can get feedback on a) is this a good thing at all, b) is it heading in the right direction c) are there other things "they" would prioritize more highly? All excellent observations below. Good luck. \$\endgroup\$ Commented Oct 20, 2024 at 15:54

2 Answers 2

7
\$\begingroup\$

Good job on your script, especially as you mention you're just starting to learn shell scripting. A few suggestions:

  1. Copy/paste your script into http://shellcheck.net and fix the issues it tells you about.

  2. Don't use all-upper-case for non-exported variable names, see correct-bash-and-shell-script-variable-capitalization.

  3. Indent code inside functions such as initialize_shell_config().

  4. Don't store code in variables, e.g. PROOT_CMD="${PROOT_CMD} -b /sdcard"; see https://mywiki.wooledge.org/BashFAQ/050.

  5. Always quote your variables unless you need to not quote them and then seriously question yourself about why that'd be the case (not quoting them is almost always a bug and there is a better way to do what you want with them quoted), https://mywiki.wooledge.org/Quotes. For example if you think you need ${PROOT_CMD} /usr/bin/env... instead of "${PROOT_CMD}" /usr/bin/env... then there is something wrong with how you are using PROOT_CMD (e.g. as a scalar holding code per my previous point).

  6. Use single quotes unless you need double quotes, and then double quotes unless you need no quotes. e.g. HOME="/data/data/com.termux/files/home" should be HOME='/data/data/com.termux/files/home' since there is nothing in that string that requires it to be parsed by the shell, e.g. to expand variables.

  7. { and } are to separate variables in mid-string from the rest of the text, e.g. echo "foo${HOME}bar", otherwise they do nothing (unless used in a context where you're doing some form of parameter substitution) so ${PROOT_CMD} /usr/bin/env... is no different from $PROOT_CMD /usr/bin/env..., it doesn't add any protection to $PROOT_CMD, unlike quotes "$PROOT_CMD".

  8. Don't use the Bash-specific function keyword (e.g. function initialize_shell_config() should just be initialize_shell_config()); it's not needed for Bash and just makes your code less portable to other shells if/when you decide to copy/move it.

  9. Always add || exit or similar after a call to mkdir or cd so you exit if it fails rather than trash some other directory.

  10. If you might add more options then consider using getopt or getopts or similar rather than writing a loop on arguments so your script can parse options like -ab to mean -a -b and -x27 to mean -x 27 as other Unix commands do (google how to handle long options - there are lots of suggestions/opinions for you to choose from).

  11. If you write your shell scripts as below with a - after << to allow leading tabs in the content and blanks inside the delimiter ' EOF' then you can indent the terminating EOF by that number of blanks and indent each line by a tab so the here-doc is indented and so makes the control flow of your code clearer. The space before echo and this is a tab, the space before cat and EOF is 4 blanks (I personally find 4 blanks clearer than 2 blanks for indenting steps; use what you like though):

    foo() {
     cat <<- ' EOF'
     echo "whatever"
     this=that
     EOF
    }
    
  12. You should print your progress messages to stderr instead of stdout as that's typical for Unix commands, avoids the 2 forms of output getting blended undesirably, and would ensure that any messages you print from your code appear in the correct sequence with any messages to stderr from the commands you call, e.g. echo "Sandbox home directory not found. Initializing sandbox..." >&2.

  13. Use arithmetic language constructs (( ... )) for arithmetic operations, e.g. instead of while [[ $# -gt 0 ]]; do you'd write while (( $# > 0 )); do.

  14. Consider using printf instead of echo when outputting anything other than just a literal string, e.g. echo "Error: Invalid action '${ACTION}'." would do different things in different situations depending on the value of ACTION while printf "Error: Invalid action '%s'.\n" "${ACTION}" would be consistent and portable, see why-is-printf-better-than-echo.

Toby Speight
87k14 gold badges104 silver badges322 bronze badges
answered Oct 20, 2024 at 10:49
\$\endgroup\$
5
\$\begingroup\$

Overview

You did a good job with:

  • Partitioning code into functions
  • Adding documentation as comments to describe the purpose of the code as well as the options
  • Using meaningful names for functions and variables

Consider the following suggestions.

Indentation

It is great that you used consistent indentation of the code, but I find 2 spaces per level to be to few, in any language. I think the code structure is more evident with a little more: I prefer 4 spaces per level.

Command line

The documentation always shows command lines like the following:

./sandbox-termux.sh

The leading ./ implies that the user must always run the code from the directory that contains the script. Typically on Linux, we can omit the ./ path when running a script in the current working directory.

Also, adding the path is somewhat restrictive since we often add scripts to a directory which is added to the PATH environment variable.

DRY

You have nearly duplicate copies of the description of the command line options in the header comments and in the help function:

# Options:
# start Start the sandbox and drop into a shell
# destroy Destroy the sandbox environment

Etc.

This means that you need to maintain 2 copies of the same information, and you have no automated way to know that they remain consistent with each other. To lessen your burden, I recommend keeping the print_usage function as-is, and replacing the details comments with a simple comment like:

# For more details on usage, run:
# sandbox-termux.sh -h

The following path appears twice in the code:

/data/data/com.termux/files/home

I recommend using a variable.

Unused code

The following line seems to be unused and can be removed:

POSITIONAL_ARGS=()
answered Oct 20, 2024 at 10:49
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.