initial commit

This commit is contained in:
klein panic
2025-02-03 19:46:21 -05:00
commit 0ea58b195d
4 changed files with 567 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
backups/
chroot_daemon_logs/

50
chroot_manager.1 Normal file
View 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.

View 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
View 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 programs 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