initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
backups/
|
||||
chroot_daemon_logs/
|
||||
50
chroot_manager.1
Normal file
50
chroot_manager.1
Normal file
@@ -0,0 +1,50 @@
|
||||
.\" Manpage for chroot_manager
|
||||
.TH CHROOT_MANAGER 1 "February 2025" "chroot_manager 1.0" "User Commands"
|
||||
.SH NAME
|
||||
chroot_manager \- chroot management tool with daemon logging support
|
||||
.SH SYNOPSIS
|
||||
.B chroot_manager
|
||||
[--verbose] [--daemon] <command>
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B chroot_manager
|
||||
script provides commands to create, connect to, disconnect from, and manage a chroot jail.
|
||||
.PP
|
||||
It also supports a daemon mode (using --daemon) where the chroot session is run under
|
||||
strace to log all system calls from the chroot environment and its child processes.
|
||||
.PP
|
||||
Available commands:
|
||||
.TP
|
||||
.B create
|
||||
Creates a chroot jail using debootstrap.
|
||||
.TP
|
||||
.B connect
|
||||
Mounts required filesystems, sets up X access, and enters the chroot.
|
||||
With --daemon, the session is traced via strace.
|
||||
.TP
|
||||
.B disconnect
|
||||
Unmounts the filesystems from the chroot.
|
||||
.TP
|
||||
.B status
|
||||
Shows the mount status for the chroot jail.
|
||||
.TP
|
||||
.B install
|
||||
Installs chroot_manager to /usr/local/bin along with its man page and shell completions.
|
||||
.TP
|
||||
.B uninstall
|
||||
Removes the installed chroot_manager, its man page, and shell completions.
|
||||
.TP
|
||||
.B help
|
||||
Displays this help message.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B --verbose
|
||||
Enables verbose output.
|
||||
.TP
|
||||
.B --daemon
|
||||
Enables daemon mode for the connect command.
|
||||
.SH AUTHOR
|
||||
Written by Your Name.
|
||||
.SH COPYRIGHT
|
||||
This is free software; see the source for copying conditions. There is NO warranty.
|
||||
|
||||
21
chroot_manager.bash_completion
Normal file
21
chroot_manager.bash_completion
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
# Bash completion for chroot_manager
|
||||
|
||||
_chroot_manager_completion() {
|
||||
local cur prev opts cmds
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
|
||||
cmds="create connect disconnect status install uninstall help"
|
||||
opts="--verbose --daemon --help -h"
|
||||
|
||||
if [[ ${COMP_CWORD} == 1 ]]; then
|
||||
COMPREPLY=( $(compgen -W "${cmds} ${opts}" -- ${cur}) )
|
||||
else
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
fi
|
||||
}
|
||||
|
||||
complete -F _chroot_manager_completion chroot_manager
|
||||
|
||||
494
chroot_manager.sh
Executable file
494
chroot_manager.sh
Executable file
@@ -0,0 +1,494 @@
|
||||
#!/bin/bash
|
||||
# chroot_manager.sh
|
||||
#
|
||||
# A fully featured chroot management tool with:
|
||||
# - A command-line interface (create, connect, disconnect, status, help, install, uninstall)
|
||||
# - Improved error handling and logging
|
||||
# - A verbose/debug mode (--verbose)
|
||||
# - A daemon mode (--daemon) which, when used with connect, will run
|
||||
# the chroot session under strace to log all activity (system calls) in separate files.
|
||||
#
|
||||
# Requirements:
|
||||
# - Some commands (create, connect, disconnect, install, uninstall) must be run as root.
|
||||
#
|
||||
# Usage:
|
||||
# sudo ./chroot_manager.sh [--verbose] [--daemon] <command>
|
||||
#
|
||||
# Commands:
|
||||
# create Create the chroot jail using debootstrap.
|
||||
# connect Mount necessary filesystems, set up X access, and enter the chroot.
|
||||
# With --daemon, the session is run under strace to log all activity.
|
||||
# disconnect Unmount the filesystems from the chroot (if needed).
|
||||
# status Display the current mount status for the chroot jail.
|
||||
# help Display a detailed help message.
|
||||
# install Install chroot_manager to /usr/local/bin (and install man page and shell completions).
|
||||
# uninstall Uninstall chroot_manager and remove its man page and shell completions.
|
||||
#
|
||||
# Note:
|
||||
# By default, the script assumes the chroot jail is located at /var/chroot.
|
||||
# When using --daemon, the log files will be stored in a directory created in the
|
||||
# current working directory (default: "chroot_daemon_logs"). After the chroot session ends,
|
||||
# the script will attempt to rename each log file based on the traced program’s name (if available)
|
||||
# and ignore logs for trivial commands. Finally, the script will adjust ownership and permissions
|
||||
# on that directory and its contents so they are accessible to the invoking user.
|
||||
#
|
||||
|
||||
# --- Global Variables and Defaults ---
|
||||
CHROOT_DIR="/var/chroot"
|
||||
DEBIAN_MIRROR="http://deb.debian.org/debian"
|
||||
LOGFILE="/var/log/chroot_manager.log"
|
||||
VERBOSE=0
|
||||
DAEMON=0
|
||||
# Default directory to store daemon logs
|
||||
DAEMON_LOG_DIR="$(pwd)/chroot_daemon_logs"
|
||||
|
||||
# List of trivial commands to ignore in daemon logs (basename only)
|
||||
IGNORE_LIST=(bash sh ls cat echo grep mount umount)
|
||||
|
||||
# --- Logging Functions ---
|
||||
function log_info() {
|
||||
echo "[INFO] $(date +'%F %T') $*" | tee -a "$LOGFILE"
|
||||
}
|
||||
|
||||
function log_debug() {
|
||||
if [ "$VERBOSE" -eq 1 ]; then
|
||||
echo "[DEBUG] $(date +'%F %T') $*" | tee -a "$LOGFILE"
|
||||
else
|
||||
echo "[DEBUG] $(date +'%F %T') $*" >> "$LOGFILE"
|
||||
fi
|
||||
}
|
||||
|
||||
function log_error() {
|
||||
echo "[ERROR] $(date +'%F %T') $*" | tee -a "$LOGFILE" >&2
|
||||
}
|
||||
|
||||
# --- Extended Help Message ---
|
||||
function help_message() {
|
||||
cat <<EOF
|
||||
chroot_manager.sh - A chroot management tool
|
||||
|
||||
Usage:
|
||||
sudo $0 [--verbose] [--daemon] <command>
|
||||
|
||||
Commands:
|
||||
create
|
||||
Create the chroot jail using debootstrap.
|
||||
Sets up a minimal Debian environment in \$CHROOT_DIR.
|
||||
|
||||
connect
|
||||
Mount necessary filesystems (dev, proc, sys, tmp) into the chroot,
|
||||
set up X access, and then enter the chroot environment.
|
||||
When the chroot session ends, the bind mounts are automatically cleaned up.
|
||||
With the --daemon flag, the session is launched under strace so that every system call
|
||||
from the chroot process and its children is logged. The logs are stored in a directory:
|
||||
\$DAEMON_LOG_DIR
|
||||
Each log file is post-processed to try to include the traced program's name (if found)
|
||||
and trivial commands are ignored. Finally, the log directory and its files will have their
|
||||
ownership and permissions changed so the invoking user (from \$SUDO_USER) can read and write them.
|
||||
|
||||
disconnect
|
||||
Unmount the filesystems from the chroot.
|
||||
Checks if bind mounts exist for \$CHROOT_DIR and, if so, unmounts them.
|
||||
|
||||
status
|
||||
Displays the current mount status for the chroot jail (\$CHROOT_DIR).
|
||||
|
||||
install
|
||||
Installs chroot_manager to /usr/local/bin, and copies the man page and bash completion file
|
||||
(if found in the current directory) to the appropriate locations.
|
||||
|
||||
uninstall
|
||||
Uninstalls chroot_manager from /usr/local/bin and removes the installed man page and bash completion file.
|
||||
|
||||
help
|
||||
Display this detailed help message.
|
||||
|
||||
Options:
|
||||
--verbose
|
||||
Enable verbose/debug mode (more detailed logging to console and log file).
|
||||
|
||||
--daemon
|
||||
When used with the 'connect' command, run the chroot session under strace in daemon mode,
|
||||
logging all system call activity in separate log files.
|
||||
|
||||
Notes:
|
||||
- Commands that modify the chroot environment (create, connect, disconnect, install, uninstall)
|
||||
must be run as root.
|
||||
- By default, the chroot jail is located at: \$CHROOT_DIR.
|
||||
EOF
|
||||
}
|
||||
|
||||
# --- Usage (Short) ---
|
||||
function usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 [--verbose] [--daemon] <command>
|
||||
Try '$0 help' for more information.
|
||||
EOF
|
||||
}
|
||||
|
||||
# --- Dependency Check ---
|
||||
REQUIRED_CMDS=(debootstrap chroot mount xhost xauth sudo strace)
|
||||
function check_dependencies() {
|
||||
local missing=0
|
||||
for cmd in "${REQUIRED_CMDS[@]}"; do
|
||||
if ! command -v "$cmd" &>/dev/null; then
|
||||
log_error "Required command '$cmd' is not installed."
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
if [ $missing -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Check for Chroot Jail Existence ---
|
||||
function check_jail() {
|
||||
if [ ! -d "$CHROOT_DIR" ]; then
|
||||
log_error "Chroot jail '$CHROOT_DIR' does not exist."
|
||||
echo "Please create the chroot environment first using the 'create' command."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Mount / Unmount Filesystems ---
|
||||
function mount_filesystems() {
|
||||
local dirs=(dev proc sys tmp)
|
||||
for d in "${dirs[@]}"; do
|
||||
log_info "Mounting /$d to ${CHROOT_DIR}/$d..."
|
||||
if ! mountpoint -q "${CHROOT_DIR}/$d"; then
|
||||
if ! sudo mount --bind "/$d" "${CHROOT_DIR}/$d"; then
|
||||
log_error "Error mounting /$d"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_debug "${CHROOT_DIR}/$d is already mounted."
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function unmount_filesystems() {
|
||||
local dirs=(dev proc sys tmp)
|
||||
local any_mounted=0
|
||||
for d in "${dirs[@]}"; do
|
||||
if mountpoint -q "${CHROOT_DIR}/$d"; then
|
||||
any_mounted=1
|
||||
log_info "Unmounting ${CHROOT_DIR}/$d..."
|
||||
if ! sudo umount "${CHROOT_DIR}/$d"; then
|
||||
log_error "Error unmounting ${CHROOT_DIR}/$d"
|
||||
fi
|
||||
else
|
||||
log_debug "${CHROOT_DIR}/$d is not mounted."
|
||||
fi
|
||||
done
|
||||
return $any_mounted
|
||||
}
|
||||
|
||||
# --- Post-Process Daemon Logs ---
|
||||
function post_process_daemon_logs() {
|
||||
log_info "Post-processing daemon logs in directory: $DAEMON_LOG_DIR"
|
||||
# Iterate over log files matching the pattern (they are created with suffix .<pid>)
|
||||
for logfile in "$DAEMON_LOG_DIR"/chroot_daemon.log.*; do
|
||||
[ -e "$logfile" ] || continue
|
||||
# Extract the PID from the filename: assume filename ends with .<pid>
|
||||
pid="${logfile##*.}"
|
||||
# Try to extract the first execve call to determine the program name.
|
||||
prog_full=$(grep -m 1 'execve(' "$logfile" | sed -E 's/.*execve\("([^"]+)".*/\1/')
|
||||
if [ -n "$prog_full" ]; then
|
||||
prog_name=$(basename "$prog_full")
|
||||
else
|
||||
prog_name="pid${pid}"
|
||||
fi
|
||||
|
||||
# Check if the program is in the ignore list (compare only the basename)
|
||||
ignore=0
|
||||
for trivial in "${IGNORE_LIST[@]}"; do
|
||||
if [ "$prog_name" == "$trivial" ]; then
|
||||
ignore=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$ignore" -eq 1 ]; then
|
||||
log_debug "Ignoring trivial log for program '$prog_name' (file: $logfile). Removing."
|
||||
rm -f "$logfile"
|
||||
else
|
||||
newname="$DAEMON_LOG_DIR/${prog_name}_${pid}.log"
|
||||
log_info "Renaming log file '$logfile' to '$newname'"
|
||||
mv "$logfile" "$newname"
|
||||
fi
|
||||
done
|
||||
|
||||
# Adjust ownership and permissions of the daemon log directory and its contents.
|
||||
if [ -n "$SUDO_USER" ]; then
|
||||
log_info "Changing ownership of $DAEMON_LOG_DIR to user $SUDO_USER"
|
||||
sudo chown -R "$SUDO_USER":"$SUDO_USER" "$DAEMON_LOG_DIR"
|
||||
fi
|
||||
log_info "Setting permissions on $DAEMON_LOG_DIR and its files."
|
||||
# Directories: rwxr-xr-x; Files: rw-r--r--
|
||||
find "$DAEMON_LOG_DIR" -type d -exec chmod 0755 {} \;
|
||||
find "$DAEMON_LOG_DIR" -type f -exec chmod 0644 {} \;
|
||||
}
|
||||
|
||||
# --- Cleanup Routine (for connect) ---
|
||||
function cleanup() {
|
||||
log_info "Cleaning up: Unmounting filesystems..."
|
||||
unmount_filesystems
|
||||
log_info "Cleanup complete."
|
||||
}
|
||||
|
||||
# --- Command: create ---
|
||||
function create() {
|
||||
# Require root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Error: The 'create' command must be run as root (use sudo)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
check_dependencies
|
||||
if [ -d "$CHROOT_DIR" ]; then
|
||||
log_error "Chroot jail already exists at $CHROOT_DIR."
|
||||
echo "Use 'connect' to enter it, or 'disconnect' if mounts remain."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Creating chroot jail at $CHROOT_DIR using debootstrap..."
|
||||
if ! sudo mkdir -p "$CHROOT_DIR"; then
|
||||
log_error "Failed to create directory $CHROOT_DIR."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if sudo debootstrap stable "$CHROOT_DIR" "$DEBIAN_MIRROR"; then
|
||||
log_info "Chroot jail successfully created."
|
||||
else
|
||||
log_error "debootstrap failed. Check your network and settings."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Command: connect ---
|
||||
function connect() {
|
||||
# Require root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Error: The 'connect' command must be run as root (use sudo)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
check_dependencies
|
||||
check_jail
|
||||
|
||||
# Set trap for cleanup when connect ends.
|
||||
trap cleanup EXIT SIGINT SIGTERM SIGHUP
|
||||
|
||||
# Mount the required filesystems
|
||||
mount_filesystems
|
||||
|
||||
# Allow all connections to the X server
|
||||
log_info "Running 'xhost +' to allow X connections..."
|
||||
if ! xhost +; then
|
||||
log_error "Error running 'xhost +'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the current X authentication keys
|
||||
log_info "Retrieving X authentication keys with 'xauth list'..."
|
||||
local xauth_keys
|
||||
xauth_keys=$(xauth list)
|
||||
if [ -z "$xauth_keys" ]; then
|
||||
log_error "Warning: 'xauth list' returned no output."
|
||||
else
|
||||
if command -v xclip &>/dev/null; then
|
||||
echo "$xauth_keys" | xclip -selection clipboard
|
||||
log_info "X authentication keys have been copied to your clipboard."
|
||||
else
|
||||
log_info "xclip not found. Here are your X authentication keys:"
|
||||
echo "$xauth_keys"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "------------------------------"
|
||||
echo "Now entering the chroot environment."
|
||||
echo "Inside the chroot, add the X authentication key by running:"
|
||||
echo " xauth add <paste-from-clipboard>"
|
||||
echo "For example, if your clipboard contains:"
|
||||
echo " $(echo "$xauth_keys" | head -n 1)"
|
||||
echo "then run:"
|
||||
echo " xauth add $(echo "$xauth_keys" | head -n 1)"
|
||||
echo "------------------------------"
|
||||
echo "Press Enter to continue..."
|
||||
read -r
|
||||
|
||||
log_info "Entering chroot at $CHROOT_DIR..."
|
||||
if [ "$DAEMON" -eq 1 ]; then
|
||||
# Create the daemon log directory if it doesn't exist.
|
||||
if [ ! -d "$DAEMON_LOG_DIR" ]; then
|
||||
mkdir -p "$DAEMON_LOG_DIR" || {
|
||||
log_error "Failed to create daemon log directory: $DAEMON_LOG_DIR"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
log_info "Daemon mode enabled. Monitoring chroot session with strace."
|
||||
# Run chroot under strace so that all system calls (and those of forked children)
|
||||
# are logged to files with prefix "$DAEMON_LOG_DIR/chroot_daemon.log"
|
||||
strace -ff -tt -o "$DAEMON_LOG_DIR/chroot_daemon.log" sudo chroot "$CHROOT_DIR"
|
||||
# Post-process the generated logs
|
||||
post_process_daemon_logs
|
||||
else
|
||||
sudo chroot "$CHROOT_DIR"
|
||||
fi
|
||||
log_info "Chroot session ended."
|
||||
# When the chroot session ends, cleanup is automatically triggered by trap.
|
||||
}
|
||||
|
||||
# --- Command: disconnect ---
|
||||
function disconnect() {
|
||||
# Require root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Error: The 'disconnect' command must be run as root (use sudo)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
check_jail
|
||||
|
||||
# Check if any bind mounts exist.
|
||||
if ! mount | grep -q "$CHROOT_DIR"; then
|
||||
log_info "Chroot environment appears to be clean; no mounts found at $CHROOT_DIR."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log_info "Running disconnect: unmounting chroot filesystems..."
|
||||
unmount_filesystems
|
||||
|
||||
# Revoke X server permissions.
|
||||
if xhost -; then
|
||||
log_info "X server access has been revoked."
|
||||
else
|
||||
log_error "Warning: failed to revoke X server permissions with 'xhost -'."
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Command: status ---
|
||||
function status() {
|
||||
echo "Mount status for chroot jail ($CHROOT_DIR):"
|
||||
mount | grep "$CHROOT_DIR" || echo "No mounts found for $CHROOT_DIR."
|
||||
}
|
||||
|
||||
# --- Command: install ---
|
||||
function install_program() {
|
||||
# Require root.
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Error: The 'install' command must be run as root (use sudo)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Resolve the full path of the current script.
|
||||
script_path=$(readlink -f "$0")
|
||||
log_info "Installing chroot_manager from $script_path to /usr/local/bin/chroot_manager..."
|
||||
cp "$script_path" /usr/local/bin/chroot_manager || { log_error "Failed to copy script."; exit 1; }
|
||||
chmod 0755 /usr/local/bin/chroot_manager
|
||||
|
||||
# Install the man page if available.
|
||||
if [ -e "chroot_manager.1" ]; then
|
||||
log_info "Installing man page..."
|
||||
mkdir -p /usr/local/share/man/man1
|
||||
cp chroot_manager.1 /usr/local/share/man/man1/chroot_manager.1 || { log_error "Failed to copy man page."; }
|
||||
gzip -f /usr/local/share/man/man1/chroot_manager.1
|
||||
else
|
||||
log_debug "No man page (chroot_manager.1) found in the current directory."
|
||||
fi
|
||||
|
||||
# Install bash completions if available.
|
||||
if [ -e "chroot_manager.bash_completion" ]; then
|
||||
log_info "Installing bash completion..."
|
||||
cp chroot_manager.bash_completion /etc/bash_completion.d/chroot_manager || { log_error "Failed to install bash completion."; }
|
||||
else
|
||||
log_debug "No bash completion file found in the current directory."
|
||||
fi
|
||||
|
||||
log_info "Installation complete."
|
||||
}
|
||||
|
||||
# --- Command: uninstall ---
|
||||
function uninstall_program() {
|
||||
# Require root.
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Error: The 'uninstall' command must be run as root (use sudo)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Uninstalling chroot_manager from /usr/local/bin..."
|
||||
rm -f /usr/local/bin/chroot_manager
|
||||
|
||||
log_info "Removing man page..."
|
||||
rm -f /usr/local/share/man/man1/chroot_manager.1.gz
|
||||
|
||||
log_info "Removing bash completion..."
|
||||
rm -f /etc/bash_completion.d/chroot_manager
|
||||
|
||||
log_info "Uninstallation complete."
|
||||
}
|
||||
|
||||
# --- Main CLI Processing ---
|
||||
if [ $# -eq 0 ]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Process options.
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
help_message
|
||||
exit 0
|
||||
;;
|
||||
--verbose)
|
||||
VERBOSE=1
|
||||
shift
|
||||
;;
|
||||
--daemon)
|
||||
DAEMON=1
|
||||
shift
|
||||
;;
|
||||
create|connect|disconnect|status|install|uninstall|help)
|
||||
COMMAND="$1"
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option or command: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Dispatch command.
|
||||
case "$COMMAND" in
|
||||
create)
|
||||
create
|
||||
;;
|
||||
connect)
|
||||
connect
|
||||
;;
|
||||
disconnect)
|
||||
disconnect
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
install)
|
||||
install_program
|
||||
;;
|
||||
uninstall)
|
||||
uninstall_program
|
||||
;;
|
||||
help)
|
||||
help_message
|
||||
;;
|
||||
*)
|
||||
echo "Invalid command."
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user