| Name | chorut JSON |
| Version |
0.1.4
JSON |
| download |
| home_page | None |
| Summary | Python implementation of an enhanced chroot functionality with minimal dependencies |
| upload_time | 2025-10-12 18:56:40 |
| maintainer | None |
| docs_url | None |
| author | Antal Buss |
| requires_python | >=3.12 |
| license | MIT |
| 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"
}