#!/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.
-
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\$shellter– shellter2024年10月20日 15:54:40 +00:00Commented Oct 20, 2024 at 15:54
2 Answers 2
Good job on your script, especially as you mention you're just starting to learn shell scripting. A few suggestions:
Copy/paste your script into http://shellcheck.net and fix the issues it tells you about.
Don't use all-upper-case for non-exported variable names, see correct-bash-and-shell-script-variable-capitalization.
Indent code inside functions such as
initialize_shell_config()
.Don't store code in variables, e.g.
PROOT_CMD="${PROOT_CMD} -b /sdcard"
; see https://mywiki.wooledge.org/BashFAQ/050.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 usingPROOT_CMD
(e.g. as a scalar holding code per my previous point).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 beHOME='/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.{
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"
.Don't use the Bash-specific
function
keyword (e.g.function initialize_shell_config()
should just beinitialize_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.Always add
|| exit
or similar after a call tomkdir
orcd
so you exit if it fails rather than trash some other directory.If you might add more options then consider using
getopt
orgetopts
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).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 terminatingEOF
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 beforeecho
andthis
is a tab, the space beforecat
andEOF
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 }
You should print your progress messages to
stderr
instead ofstdout
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
.Use arithmetic language constructs
(( ... ))
for arithmetic operations, e.g. instead ofwhile [[ $# -gt 0 ]]; do
you'd writewhile (( $# > 0 )); do
.Consider using
printf
instead ofecho
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 ofACTION
whileprintf "Error: Invalid action '%s'.\n" "${ACTION}"
would be consistent and portable, see why-is-printf-better-than-echo.
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=()