Skip to main content

Python implementation of an enhanced chroot functionality with minimal dependencies

Project description

chorut

A Python library that provides chroot functionality inspired by arch-chroot with minimal dependencies, using only Python standard library modules.

Features

  • Complete chroot setup: Automatically mounts proc, sys, dev, devpts, shm, run, and tmp filesystems
  • Custom mounts: Support for user-defined bind mounts and filesystem mounts
  • Unshare mode: Support for running as non-root user using Linux namespaces
  • Context manager: Clean automatic setup and teardown
  • resolv.conf handling: Proper DNS configuration in chroot
  • String and list commands: Execute commands using either list format or string format
  • Output capture: Capture stdout and stderr from executed commands
  • Error handling: Comprehensive error reporting and cleanup
  • Zero external dependencies: Uses only Python standard library

Installation

pip install chorut

Usage

As a Library

from chorut import ChrootManager

# Basic usage as root
with ChrootManager('/path/to/chroot') as chroot:
    result = chroot.execute(['ls', '-la'])

# String commands (parsed with shlex.split)
with ChrootManager('/path/to/chroot') as chroot:
    result = chroot.execute('ls -la /etc')

# Output capture
with ChrootManager('/path/to/chroot') as chroot:
    result = chroot.execute('cat /etc/hostname', capture_output=True)
    if result.returncode == 0:
        hostname = result.stdout.strip()
        print(f"Hostname: {hostname}")

# Non-root usage with unshare mode (requires complete chroot environment)
with ChrootManager('/path/to/complete/chroot', unshare_mode=True) as chroot:
    result = chroot.execute(['whoami'])

# Manual setup/teardown
chroot = ChrootManager('/path/to/chroot')
chroot.setup()
try:
    result = chroot.execute(['bash', '-c', 'echo "Hello from chroot"'])
finally:
    chroot.teardown()

# With custom mounts
custom_mounts = [
    {
        "source": "/home",
        "target": "home",
        "bind": True,
        "options": "ro"  # Read-only bind mount
    },
    {
        "source": "tmpfs",
        "target": "workspace",
        "fstype": "tmpfs",
        "options": "size=1G"
    }
]

with ChrootManager('/path/to/chroot', custom_mounts=custom_mounts) as chroot:
    result = chroot.execute(['df', '-h'])

String Commands and Shell Features

The execute method accepts both list and string commands:

# List format (recommended for complex commands)
result = chroot.execute(['ls', '-la', '/etc'])

# String format (parsed with shlex.split or auto-wrapped with bash -c)
result = chroot.execute('ls -la /etc')

# Shell features now work automatically (auto_shell=True by default)
result = chroot.execute('ls | wc -l')           # Pipes
result = chroot.execute('echo hello && echo world')  # Logical operators
result = chroot.execute('echo `date`')          # Command substitution
result = chroot.execute('ls *.txt')             # Glob patterns
result = chroot.execute('echo $HOME')           # Variable expansion

# Manual shell invocation still works
result = chroot.execute("bash -c 'ls | wc -l'")

# Disable auto-detection by setting auto_shell=False
chroot_manual = ChrootManager('/path/to/chroot', auto_shell=False)
result = chroot_manual.execute("bash -c 'ls | wc -l'")  # Explicit bash -c needed

Auto-Detection: By default (auto_shell=True), string commands are automatically analyzed for shell metacharacters (pipes |, logical operators &&/||, redirects <>/>, command substitution `cmd`/$(cmd), glob patterns */?, variable expansion $VAR, etc.). When detected, the command is automatically wrapped with bash -c. Simple commands are still parsed with shlex.split() for security.

Output Capture

Capture command output using the capture_output parameter:

# Capture both stdout and stderr
result = chroot.execute('cat /etc/hostname', capture_output=True)
if result.returncode == 0:
    hostname = result.stdout.strip()

# Capture with error handling
result = chroot.execute('ls /nonexistent', capture_output=True)
if result.returncode != 0:
    error_msg = result.stderr.strip()

# Get raw bytes instead of text
result = chroot.execute('cat binary_file', capture_output=True, text=False)
binary_data = result.stdout

Custom Mounts

You can specify additional mounts to be set up in the chroot environment. Each mount specification is a dictionary with the following keys:

  • source (required): Source path, device, or filesystem type
  • target (required): Target path relative to chroot root
  • fstype (optional): Filesystem type (e.g., "tmpfs", "ext4")
  • options (optional): Mount options (e.g., "ro", "size=1G")
  • bind (optional): Whether this is a bind mount (default: False)
  • mkdir (optional): Whether to create target directory (default: True)

Examples:

# Bind mount home directory as read-only
{
    "source": "/home",
    "target": "home",
    "bind": True,
    "options": "ro"
}

# Create a tmpfs workspace
{
    "source": "tmpfs",
    "target": "tmp/workspace",
    "fstype": "tmpfs",
    "options": "size=512M,mode=1777"
}

# Bind mount a specific directory
{
    "source": "/var/cache/pacman",
    "target": "var/cache/pacman",
    "bind": True
}

Command Line

# Basic chroot (requires root)
sudo chorut /path/to/chroot

# Run specific command
sudo chorut /path/to/chroot ls -la

# Non-root mode (requires proper chroot environment)
chorut -N /path/to/complete/chroot

# Specify user
sudo chorut -u user:group /path/to/chroot

# Verbose output
chorut -v -N /path/to/chroot

# Custom mounts
chorut -m "/home:home:bind,ro" -m "tmpfs:workspace:size=1G" /path/to/chroot

# Multiple custom mounts
chorut -N \
  -m "/var/cache:var/cache:bind" \
  -m "tmpfs:tmp/build:size=2G" \
  /path/to/chroot make -j4

Command Line Mount Format

The -m/--mount option accepts mount specifications in the format:

SOURCE:TARGET[:OPTIONS]
  • SOURCE: Source path, device, or filesystem type
  • TARGET: Target path relative to chroot (without leading slash)
  • OPTIONS: Comma-separated mount options (optional)

Special options:

  • bind - Creates a bind mount
  • Other options are passed to the mount command

Examples:

  • -m "/home:home:bind,ro" - Read-only bind mount of /home
  • -m "tmpfs:workspace:size=1G" - 1GB tmpfs at /workspace
  • -m "/dev/sdb1:mnt/data:rw" - Mount device with read-write access

Command Line Options

  • -h, --help: Show help message
  • -N, --unshare: Run in unshare mode as regular user
  • -u USER[:GROUP], --userspec USER[:GROUP]: Specify user/group to run as
  • -v, --verbose: Enable verbose logging
  • -m SOURCE:TARGET[:OPTIONS], --mount SOURCE:TARGET[:OPTIONS]: Add custom mount (can be used multiple times)

API Reference

ChrootManager

The main class for managing chroot environments.

Constructor

ChrootManager(chroot_dir, unshare_mode=False, custom_mounts=None, auto_shell=True)
  • chroot_dir: Path to the chroot directory
  • unshare_mode: Whether to use unshare mode for non-root operation
  • custom_mounts: Optional list of custom mount specifications
  • auto_shell: Whether to automatically detect shell features in string commands and wrap them with 'bash -c' (default: True)

Methods

  • setup(): Set up the chroot environment
  • teardown(): Clean up the chroot environment
  • execute(command=None, userspec=None, capture_output=False, text=True): Execute a command in the chroot
execute() Parameters
  • command: Command to execute. Can be:
    • list[str]: List of command and arguments (e.g., ['ls', '-la'])
    • str: String command parsed with shlex.split() (e.g., 'ls -la')
    • None: Start interactive shell
  • userspec: User specification in format "user" or "user:group"
  • capture_output: If True, capture stdout and stderr (default: False)
  • text: If True, decode output as text; if False, return bytes (default: True)
execute() Return Value

Returns a subprocess.CompletedProcess object with:

  • returncode: Exit code of the command
  • stdout: Command output (if capture_output=True)
  • stderr: Command error output (if capture_output=True)
execute() Examples
# List command (recommended for security)
result = chroot.execute(['ls', '-la'])

# String command
result = chroot.execute('ls -la')

# With output capture
result = chroot.execute('cat /etc/hostname', capture_output=True)
hostname = result.stdout.strip()

# Shell features work automatically with auto_shell=True (default)
result = chroot.execute('ls | wc -l', capture_output=True)  # Pipes work!
result = chroot.execute('echo hello && echo world')  # Logical operators
result = chroot.execute('ls *.txt')  # Glob patterns

# List commands with special characters are handled safely
result = chroot.execute(['echo', 'hello world', 'foo;bar'])

# Interactive shell (command=None)
chroot.execute()  # Starts bash shell

Command List Validation

When using list-based commands, all arguments are validated:

# Valid - all arguments are strings
result = chroot.execute(['echo', 'hello', 'world'])

# Raises ChrootError - non-string arguments
result = chroot.execute(['echo', 123])  # Error: All command arguments must be strings

# Raises ChrootError - empty list
result = chroot.execute([])  # Error: Command list cannot be empty

Security

All user-provided values (mount sources, targets, command arguments) are properly escaped using shlex.quote() to prevent command injection attacks. List-based commands are recommended over string commands when handling untrusted input.

Exceptions

  • ChrootError: Raised for chroot-related errors, invalid command arguments, or empty command lists
  • MountError: Raised for mount-related errors

Requirements

  • Python 3.12+
  • Linux system with mount/umount utilities
  • Root privileges (unless using unshare mode)

Unshare Mode Requirements

When using unshare mode (-N flag), the following additional requirements apply:

  • unshare command must be available
  • The chroot directory must contain a complete filesystem with:
    • Essential binaries in /bin, /usr/bin, etc.
    • Required libraries in /lib, /lib64, /usr/lib, etc.
    • Proper directory structure (/etc, /proc, /sys, /dev, etc.)

Note: Unshare mode performs all mount operations within an unshared mount namespace, allowing non-root users to create chroot environments. However, the target directory must still contain a complete, functional filesystem for the chroot to work properly.

For example, trying to chroot into /tmp will fail because it lacks the necessary binaries and libraries. You need a proper root filesystem (like those created by debootstrap, pacstrap, or similar tools).

License

Licensed under the MIT License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

chorut-0.1.8.tar.gz (13.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

chorut-0.1.8-py3-none-any.whl (14.0 kB view details)

Uploaded Python 3

File details

Details for the file chorut-0.1.8.tar.gz.

File metadata

  • Download URL: chorut-0.1.8.tar.gz
  • Upload date:
  • Size: 13.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for chorut-0.1.8.tar.gz
Algorithm Hash digest
SHA256 d27392e2b33d7e718d34144cef2ff4c7d92fc0523988f28a16f9336ad6710af4
MD5 d3d71c394df9b10b6883591b34591ef3
BLAKE2b-256 0b6751862c993fb974f091722f157e4ff86917ab6c0684073a572c280321419e

See more details on using hashes here.

Provenance

The following attestation bundles were made for chorut-0.1.8.tar.gz:

Publisher: release.yml on abuss/chorut

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file chorut-0.1.8-py3-none-any.whl.

File metadata

  • Download URL: chorut-0.1.8-py3-none-any.whl
  • Upload date:
  • Size: 14.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for chorut-0.1.8-py3-none-any.whl
Algorithm Hash digest
SHA256 edaba9cc99fd6076c8c713b926765addb8016dd3ed23df3d2d1195713d9a50e8
MD5 f12fbd6a4ee306c123d8ed3e5ed674f7
BLAKE2b-256 374b8b31d2163891cf6266c184535efa6dd4927c87935975be6bd853778d8a1b

See more details on using hashes here.

Provenance

The following attestation bundles were made for chorut-0.1.8-py3-none-any.whl:

Publisher: release.yml on abuss/chorut

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page