chorut


Namechorut JSON
Version 0.1.4 PyPI version JSON
download
home_pageNone
SummaryPython implementation of an enhanced chroot functionality with minimal dependencies
upload_time2025-10-12 18:56:40
maintainerNone
docs_urlNone
authorAntal Buss
requires_python>=3.12
licenseMIT
keywords chroot containers linux namespaces
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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

```bash
pip install chorut
```

## Usage

### As a Library

```python
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:

```python
# 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:

```python
# 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:

```python
# 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

```bash
# 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

```python
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

```python
# List command
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 require explicit bash
result = chroot.execute("bash -c 'ls | wc -l'", capture_output=True)
line_count = int(result.stdout.strip())

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

### Exceptions

- `ChrootError`: Raised for chroot-related errors
- `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

This project is in the public domain.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "chorut",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.12",
    "maintainer_email": null,
    "keywords": "chroot, containers, linux, namespaces",
    "author": "Antal Buss",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/a9/92/bedb88617c3dd5db2f1f91dd461626f1a1c74a652a4934580579713d638a/chorut-0.1.4.tar.gz",
    "platform": null,
    "description": "# chorut\n\nA Python library that provides chroot functionality inspired by arch-chroot with minimal dependencies, using only Python standard library modules.\n\n## Features\n\n- **Complete chroot setup**: Automatically mounts proc, sys, dev, devpts, shm, run, and tmp filesystems\n- **Custom mounts**: Support for user-defined bind mounts and filesystem mounts\n- **Unshare mode**: Support for running as non-root user using Linux namespaces\n- **Context manager**: Clean automatic setup and teardown\n- **resolv.conf handling**: Proper DNS configuration in chroot\n- **String and list commands**: Execute commands using either list format or string format\n- **Output capture**: Capture stdout and stderr from executed commands\n- **Error handling**: Comprehensive error reporting and cleanup\n- **Zero external dependencies**: Uses only Python standard library\n\n## Installation\n\n```bash\npip install chorut\n```\n\n## Usage\n\n### As a Library\n\n```python\nfrom chorut import ChrootManager\n\n# Basic usage as root\nwith ChrootManager('/path/to/chroot') as chroot:\n    result = chroot.execute(['ls', '-la'])\n\n# String commands (parsed with shlex.split)\nwith ChrootManager('/path/to/chroot') as chroot:\n    result = chroot.execute('ls -la /etc')\n\n# Output capture\nwith ChrootManager('/path/to/chroot') as chroot:\n    result = chroot.execute('cat /etc/hostname', capture_output=True)\n    if result.returncode == 0:\n        hostname = result.stdout.strip()\n        print(f\"Hostname: {hostname}\")\n\n# Non-root usage with unshare mode (requires complete chroot environment)\nwith ChrootManager('/path/to/complete/chroot', unshare_mode=True) as chroot:\n    result = chroot.execute(['whoami'])\n\n# Manual setup/teardown\nchroot = ChrootManager('/path/to/chroot')\nchroot.setup()\ntry:\n    result = chroot.execute(['bash', '-c', 'echo \"Hello from chroot\"'])\nfinally:\n    chroot.teardown()\n\n# With custom mounts\ncustom_mounts = [\n    {\n        \"source\": \"/home\",\n        \"target\": \"home\",\n        \"bind\": True,\n        \"options\": \"ro\"  # Read-only bind mount\n    },\n    {\n        \"source\": \"tmpfs\",\n        \"target\": \"workspace\",\n        \"fstype\": \"tmpfs\",\n        \"options\": \"size=1G\"\n    }\n]\n\nwith ChrootManager('/path/to/chroot', custom_mounts=custom_mounts) as chroot:\n    result = chroot.execute(['df', '-h'])\n```\n\n### String Commands and Shell Features\n\nThe `execute` method accepts both list and string commands:\n\n```python\n# List format (recommended for complex commands)\nresult = chroot.execute(['ls', '-la', '/etc'])\n\n# String format (parsed with shlex.split or auto-wrapped with bash -c)\nresult = chroot.execute('ls -la /etc')\n\n# Shell features now work automatically (auto_shell=True by default)\nresult = chroot.execute('ls | wc -l')           # Pipes\nresult = chroot.execute('echo hello && echo world')  # Logical operators\nresult = chroot.execute('echo `date`')          # Command substitution\nresult = chroot.execute('ls *.txt')             # Glob patterns\nresult = chroot.execute('echo $HOME')           # Variable expansion\n\n# Manual shell invocation still works\nresult = chroot.execute(\"bash -c 'ls | wc -l'\")\n\n# Disable auto-detection by setting auto_shell=False\nchroot_manual = ChrootManager('/path/to/chroot', auto_shell=False)\nresult = chroot_manual.execute(\"bash -c 'ls | wc -l'\")  # Explicit bash -c needed\n```\n\n**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.\n\n### Output Capture\n\nCapture command output using the `capture_output` parameter:\n\n```python\n# Capture both stdout and stderr\nresult = chroot.execute('cat /etc/hostname', capture_output=True)\nif result.returncode == 0:\n    hostname = result.stdout.strip()\n\n# Capture with error handling\nresult = chroot.execute('ls /nonexistent', capture_output=True)\nif result.returncode != 0:\n    error_msg = result.stderr.strip()\n\n# Get raw bytes instead of text\nresult = chroot.execute('cat binary_file', capture_output=True, text=False)\nbinary_data = result.stdout\n```\n\n### Custom Mounts\n\nYou can specify additional mounts to be set up in the chroot environment. Each mount specification is a dictionary with the following keys:\n\n- `source` (required): Source path, device, or filesystem type\n- `target` (required): Target path relative to chroot root\n- `fstype` (optional): Filesystem type (e.g., \"tmpfs\", \"ext4\")\n- `options` (optional): Mount options (e.g., \"ro\", \"size=1G\")\n- `bind` (optional): Whether this is a bind mount (default: False)\n- `mkdir` (optional): Whether to create target directory (default: True)\n\n#### Examples:\n\n```python\n# Bind mount home directory as read-only\n{\n    \"source\": \"/home\",\n    \"target\": \"home\",\n    \"bind\": True,\n    \"options\": \"ro\"\n}\n\n# Create a tmpfs workspace\n{\n    \"source\": \"tmpfs\",\n    \"target\": \"tmp/workspace\",\n    \"fstype\": \"tmpfs\",\n    \"options\": \"size=512M,mode=1777\"\n}\n\n# Bind mount a specific directory\n{\n    \"source\": \"/var/cache/pacman\",\n    \"target\": \"var/cache/pacman\",\n    \"bind\": True\n}\n```\n\n### Command Line\n\n```bash\n# Basic chroot (requires root)\nsudo chorut /path/to/chroot\n\n# Run specific command\nsudo chorut /path/to/chroot ls -la\n\n# Non-root mode (requires proper chroot environment)\nchorut -N /path/to/complete/chroot\n\n# Specify user\nsudo chorut -u user:group /path/to/chroot\n\n# Verbose output\nchorut -v -N /path/to/chroot\n\n# Custom mounts\nchorut -m \"/home:home:bind,ro\" -m \"tmpfs:workspace:size=1G\" /path/to/chroot\n\n# Multiple custom mounts\nchorut -N \\\n  -m \"/var/cache:var/cache:bind\" \\\n  -m \"tmpfs:tmp/build:size=2G\" \\\n  /path/to/chroot make -j4\n```\n\n#### Command Line Mount Format\n\nThe `-m/--mount` option accepts mount specifications in the format:\n\n```\nSOURCE:TARGET[:OPTIONS]\n```\n\n- **SOURCE**: Source path, device, or filesystem type\n- **TARGET**: Target path relative to chroot (without leading slash)\n- **OPTIONS**: Comma-separated mount options (optional)\n\nSpecial options:\n- `bind` - Creates a bind mount\n- Other options are passed to the mount command\n\nExamples:\n- `-m \"/home:home:bind,ro\"` - Read-only bind mount of /home\n- `-m \"tmpfs:workspace:size=1G\"` - 1GB tmpfs at /workspace\n- `-m \"/dev/sdb1:mnt/data:rw\"` - Mount device with read-write access\n\n### Command Line Options\n\n- `-h, --help`: Show help message\n- `-N, --unshare`: Run in unshare mode as regular user\n- `-u USER[:GROUP], --userspec USER[:GROUP]`: Specify user/group to run as\n- `-v, --verbose`: Enable verbose logging\n- `-m SOURCE:TARGET[:OPTIONS], --mount SOURCE:TARGET[:OPTIONS]`: Add custom mount (can be used multiple times)\n\n## API Reference\n\n### ChrootManager\n\nThe main class for managing chroot environments.\n\n#### Constructor\n\n```python\nChrootManager(chroot_dir, unshare_mode=False, custom_mounts=None, auto_shell=True)\n```\n\n- `chroot_dir`: Path to the chroot directory\n- `unshare_mode`: Whether to use unshare mode for non-root operation\n- `custom_mounts`: Optional list of custom mount specifications\n- `auto_shell`: Whether to automatically detect shell features in string commands and wrap them with 'bash -c' (default: True)\n\n#### Methods\n\n- `setup()`: Set up the chroot environment\n- `teardown()`: Clean up the chroot environment\n- `execute(command=None, userspec=None, capture_output=False, text=True)`: Execute a command in the chroot\n\n##### execute() Parameters\n\n- `command`: Command to execute. Can be:\n  - `list[str]`: List of command and arguments (e.g., `['ls', '-la']`)\n  - `str`: String command parsed with `shlex.split()` (e.g., `'ls -la'`)\n  - `None`: Start interactive shell\n- `userspec`: User specification in format \"user\" or \"user:group\"\n- `capture_output`: If `True`, capture stdout and stderr (default: `False`)\n- `text`: If `True`, decode output as text; if `False`, return bytes (default: `True`)\n\n##### execute() Return Value\n\nReturns a `subprocess.CompletedProcess` object with:\n- `returncode`: Exit code of the command\n- `stdout`: Command output (if `capture_output=True`)\n- `stderr`: Command error output (if `capture_output=True`)\n\n##### execute() Examples\n\n```python\n# List command\nresult = chroot.execute(['ls', '-la'])\n\n# String command\nresult = chroot.execute('ls -la')\n\n# With output capture\nresult = chroot.execute('cat /etc/hostname', capture_output=True)\nhostname = result.stdout.strip()\n\n# Shell features require explicit bash\nresult = chroot.execute(\"bash -c 'ls | wc -l'\", capture_output=True)\nline_count = int(result.stdout.strip())\n\n# Interactive shell (command=None)\nchroot.execute()  # Starts bash shell\n```\n\n### Exceptions\n\n- `ChrootError`: Raised for chroot-related errors\n- `MountError`: Raised for mount-related errors\n\n## Requirements\n\n- Python 3.12+\n- Linux system with mount/umount utilities\n- Root privileges (unless using unshare mode)\n\n### Unshare Mode Requirements\n\nWhen using unshare mode (`-N` flag), the following additional requirements apply:\n\n- `unshare` command must be available\n- The chroot directory must contain a complete filesystem with:\n  - Essential binaries in `/bin`, `/usr/bin`, etc.\n  - Required libraries in `/lib`, `/lib64`, `/usr/lib`, etc.\n  - Proper directory structure (`/etc`, `/proc`, `/sys`, `/dev`, etc.)\n\n**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.\n\nFor 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).\n\n## License\n\nThis project is in the public domain.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Python implementation of an enhanced chroot functionality with minimal dependencies",
    "version": "0.1.4",
    "project_urls": {
        "Documentation": "https://github.com/abuss/chorut#readme",
        "Homepage": "https://github.com/abuss/chorut",
        "Issues": "https://github.com/abuss/chorut/issues",
        "Repository": "https://github.com/abuss/chorut.git"
    },
    "split_keywords": [
        "chroot",
        " containers",
        " linux",
        " namespaces"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "dfe90db8d1cb4da6f84f0b4525451ccab1e5aa52d954dc9c50acff46423216f4",
                "md5": "6c338f2952895593fddb405ee85d5f71",
                "sha256": "213272e4e483ec7a46eb14493b8b8323290f27362637ced25f9c2f8d8156d145"
            },
            "downloads": -1,
            "filename": "chorut-0.1.4-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "6c338f2952895593fddb405ee85d5f71",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.12",
            "size": 13205,
            "upload_time": "2025-10-12T18:56:38",
            "upload_time_iso_8601": "2025-10-12T18:56:38.982110Z",
            "url": "https://files.pythonhosted.org/packages/df/e9/0db8d1cb4da6f84f0b4525451ccab1e5aa52d954dc9c50acff46423216f4/chorut-0.1.4-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "a992bedb88617c3dd5db2f1f91dd461626f1a1c74a652a4934580579713d638a",
                "md5": "a2ff2a02a545770760fdba06fc341dda",
                "sha256": "81c7525025b5905a55706389a364711e88472c5584b40e4f1be5a1bb9512a69c"
            },
            "downloads": -1,
            "filename": "chorut-0.1.4.tar.gz",
            "has_sig": false,
            "md5_digest": "a2ff2a02a545770760fdba06fc341dda",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.12",
            "size": 13055,
            "upload_time": "2025-10-12T18:56:40",
            "upload_time_iso_8601": "2025-10-12T18:56:40.006309Z",
            "url": "https://files.pythonhosted.org/packages/a9/92/bedb88617c3dd5db2f1f91dd461626f1a1c74a652a4934580579713d638a/chorut-0.1.4.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-12 18:56:40",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "abuss",
    "github_project": "chorut#readme",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "chorut"
}
        
Elapsed time: 1.12821s