No description
Find a file
Marcos Méndez 92e3f7a861
All checks were successful
tests / tests (push) Successful in 31s
security / security (push) Successful in 1m13s
Merge pull request 'ci: Forgejo workflows for git.pop.coop' (#5) from ci/forgejo-workflows into master
2026-10-14 19:54:16 -03:00
.forgejo/workflows ci: Forgejo workflows for git.pop.coop 2026-10-14 22:32:57 +00:00
.github/workflows ci: security scanning, with the findings read and justified 2026-10-07 18:17:15 +00:00
chroot Unmount with plain umount, fall back to --lazy, instead of --force 2026-09-22 16:02:56 +00:00
debian chore: Keel Linux maintainers in debian/control 2026-10-07 14:15:37 +00:00
release-notes Add release notes ready for stable release 2026-06-11 09:53:41 +10:00
tests Cover mount, umount and the chroot API in the tests 2026-09-25 17:36:30 +00:00
.gitignore Update/extend gitignore 2026-05-31 10:42:15 +10:00
COVERAGE.md COVERAGE.md: record the measured baseline on master after the merge 2026-09-26 08:56:25 +00:00
pyproject.toml Commit the 95 percent coverage threshold and document the commands 2026-09-25 17:36:31 +00:00
README.md Commit the 95 percent coverage threshold and document the commands 2026-09-25 17:36:31 +00:00

turnkey-chroot

A library to interact with and/or run commands in a chroot.

turnkey-chroot (Python module chroot) is a small wrapper around the system chroot command. It sets up a chroot by mounting the required system paths (/proc, /sys, /dev, /dev/pts & /run, or a custom profile), runs the given command(s) inside the chroot, then unmounts everything again.

Requirements

  • Python 3.13+
  • System utilities (invoked via subprocess):
    • chroot - coreutils
    • mount / umount - mount

Mounting and chrooting require root privileges.

Common usage

The mount context manager handles set up and tear down of the system mounts for you and yields a Chroot object:

import chroot

with chroot.mount("/path/to/chroot") as mnt:
    # run a command, capturing its output (forwards to subprocess.run)
    proc = mnt.run(["ls", "-la", "/proc"], capture_output=True, text=True)
    print(proc.stdout)

    # run via a shell, returning only the exit code
    rc = mnt.system("apt-get update && apt-get -y upgrade")

Pass environment variables into the chroot via the environ argument:

with chroot.mount("/path/to/chroot", environ={"DEBIAN_FRONTEND": "noninteractive"}) as mnt:
    mnt.system("apt-get -y install nginx")

The Chroot class can also be used directly (it mounts on instantiation):

from chroot import Chroot

cr = Chroot("/path/to/chroot", environ={"ENVVAR": "bar"})
assert "ENVVAR=bar" in cr.run(["env"], capture_output=True, text=True).stdout

Mount profiles

By default a --type based profile is used (MNT_DEFAULT). A bind-mount profile (MNT_FULL, which also binds /run from the host) is also provided, and a custom profile dict may be supplied via the mnt_profile argument.

Debugging

Set the TKL_CHROOT_DEBUG environment variable (to any non-empty value) to print the commands being run.

Architecture

chroot/
  __init__.py     Module: mount context manager, Chroot + MagicMounts classes
  py.typed        PEP 561 marker (package ships inline type hints)
tests/
  test_chroot.py  Unit tests of the Chroot API with the mount layer stood in
  test_mount.py   Unit tests of mount() and MagicMounts with subprocess patched

Tests

Tests use pytest (Debian: python3-pytest, PyPI: pytest). Run from the project root:

python3 -m pytest

The suite mocks subprocess and the mount layer, so it needs neither root nor a real chroot.

Coverage

The package must keep at least 95 percent line and branch coverage. The threshold and the branch setting live in pyproject.toml ([tool.coverage.run] and [tool.coverage.report]), so the last command exits non zero when the package falls under the bar:

python3 -m coverage run -m pytest
python3 -m coverage report
python3 -m coverage report --fail-under=95

Lint and type checks

ruff check
ruff format --check
mypy chroot