Minimal Python library to programmatically construct Debian .deb packages
Project description
debx
Pronounced "deb-ex", debx is a minimal Python library for creating, reading, and manipulating Debian package files (.deb). It includes a powerful command-line tool for packing, unpacking, inspecting, and signing packages.
Table of Contents
- Features
- Installation
- Quick Start
- User Guide (CLI)
- Developer Guide (Python API)
- Tutorials
- API Reference
- License
- Contributing
Features
- Cross-platform - Create .deb packages on Linux, macOS, and Windows
- Zero dependencies - Uses only Python standard library
- Full package lifecycle - Read, create, modify, and sign packages
- CLI and API - Use from command line or integrate into Python applications
- Type hints - Full type annotation support for modern Python development
- Multiple output formats - Inspect packages as JSON, CSV, or ls-style output
Installation
pip install debx
Or with uv:
uv pip install debx
Requires Python 3.10 or later.
Quick Start
30-Second CLI Example
# Inspect a package
debx inspect mypackage.deb
# Unpack a package to a directory
debx unpack mypackage.deb -d ./unpacked
# Create a package from files
debx pack \
--control control:/control \
--data myapp:/usr/bin/myapp:mode=0755 \
-o mypackage.deb
30-Second Python Example
import os
import tempfile
from debx import DebBuilder, Deb822
# Create a new package
builder = DebBuilder()
# Add control metadata
control = Deb822({
"Package": "hello-world",
"Version": "1.0.0",
"Architecture": "all",
"Maintainer": "You <you@example.com>",
"Description": "A hello world package",
})
builder.add_control_entry("control", control.dump())
# Add an executable
builder.add_data_entry(
b"#!/bin/sh\necho 'Hello, World!'\n",
"/usr/bin/hello-world",
mode=0o755
)
# Write the package
with tempfile.TemporaryDirectory() as tmp:
path = os.path.join(tmp, "hello-world_1.0.0_all.deb")
with open(path, "wb") as f:
f.write(builder.pack())
assert os.path.exists(path)
User Guide (CLI)
The debx command-line tool provides four main commands for working with Debian packages.
Inspecting Packages
The inspect command displays the contents of a .deb package.
Basic Usage
debx inspect package.deb
This displays an ls -lah style listing of all files in the package:
total 42
-rw-r--r-- 0 0 4 06 May 15:30 debian-binary
-rw-r--r-- 0 0 512 06 May 15:30 control.tar.gz
-rw-r--r-- 0 0 512 06 May 15:30 control.tar.gz/control
-rw-r--r-- 0 0 128 06 May 15:30 control.tar.gz/md5sums
-rw-r--r-- 0 0 1024 06 May 15:30 data.tar.bz2
drwxr-xr-x 0 0 0 06 May 15:30 data.tar.bz2/usr/
drwxr-xr-x 0 0 0 06 May 15:30 data.tar.bz2/usr/bin/
-rwxr-xr-x 0 0 256 06 May 15:30 data.tar.bz2/usr/bin/myapp
Output Formats
Use the --format option to change the output format:
| Format | Description | Use Case |
|---|---|---|
ls |
ls -lah style (default) | Human-readable inspection |
json |
Structured JSON | Programmatic processing |
csv |
Comma-separated values | Spreadsheet import |
find |
File paths only | Piping to other tools |
JSON Format
debx inspect --format=json package.deb
[
{
"file": "debian-binary",
"size": 4,
"type": "regular",
"mode": 33188,
"uid": 0,
"gid": 0,
"mtime": 1715006234,
"md5": "a1b2c3d4e5f6...",
"path": null
}
]
CSV Format
debx inspect --format=csv package.deb > contents.csv
Find Format
debx inspect --format=find package.deb | grep usr/bin
Logging
Enable debug logging to see detailed processing information:
debx --log-level=debug inspect package.deb
Unpacking Packages
The unpack command extracts the contents of a .deb package.
Basic Usage
debx unpack package.deb
This creates a directory named after the package (without .deb extension):
package/
├── control/
│ ├── control
│ ├── md5sums
│ ├── preinst
│ └── postinst
├── data/
│ └── usr/
│ └── bin/
│ └── myapp
└── debian-binary
Specify Output Directory
debx unpack package.deb -d /tmp/extracted
Keep Original Archives
By default, the tar archives are extracted and removed. To keep them:
debx unpack package.deb --keep-archives
This preserves control.tar.gz and data.tar.bz2 alongside the extracted directories.
Packing Packages
The pack command creates a .deb package from files and directories.
Basic Usage
debx pack \
--control path/to/control:/control \
--data path/to/binary:/usr/bin/myapp:mode=0755 \
-o mypackage.deb
File Format Specification
Files are specified in the format:
source_path:destination_path[:modifiers]
| Component | Description |
|---|---|
source_path |
Local path to the file or directory |
destination_path |
Absolute path inside the package |
modifiers |
Optional comma-separated key=value pairs |
Available Modifiers
| Modifier | Description | Example |
|---|---|---|
mode |
File permissions (octal) | mode=0755 |
uid |
Owner user ID | uid=1000 |
gid |
Owner group ID | gid=1000 |
mtime |
Modification time (Unix timestamp) | mtime=1715006234 |
Control Files
Control files are added with the -c or --control option:
debx pack \
--control control:/control \
--control preinst:/preinst:mode=0755 \
--control postinst:/postinst:mode=0755 \
--control conffiles:/conffiles \
--data ...
Common control files:
| File | Description |
|---|---|
control |
Package metadata (required) |
preinst |
Script run before installation |
postinst |
Script run after installation |
prerm |
Script run before removal |
postrm |
Script run after removal |
conffiles |
List of configuration files |
triggers |
Trigger definitions |
md5sums |
File checksums (auto-generated) |
For detailed control file specifications, see the Debian Policy Manual.
Adding Directories
Specify a directory as source to include all its contents recursively:
debx pack \
--control control:/control \
--data ./build/:/opt/myapp \
-o mypackage.deb
The directory structure is preserved within the package.
Complete Example
# Create control file
cat > control << 'EOF'
Package: myapp
Version: 1.0.0
Architecture: amd64
Maintainer: Developer <dev@example.com>
Description: My Application
A longer description of my application
spanning multiple lines.
Section: utils
Priority: optional
EOF
# Create postinst script
cat > postinst << 'EOF'
#!/bin/sh
echo "Installation complete!"
EOF
# Build the package
debx pack \
--control control:/control \
--control postinst:/postinst:mode=0755 \
--data ./bin/myapp:/usr/bin/myapp:mode=0755 \
--data ./lib/:/usr/lib/myapp \
--data ./etc/config:/etc/myapp/config \
-o myapp_1.0.0_amd64.deb
Signing Packages
The sign command adds GPG signatures to .deb packages.
How It Works
Signing is a two-step process:
- Extract the payload from the package
- Sign with GPG and update the package with the signature
Complete Signing Workflow
debx sign --extract mypackage.deb | \
gpg --armor --detach-sign --output - | \
debx sign --update mypackage.deb -o mypackage.signed.deb
This pipeline:
- Extracts the
control.taranddata.tarfrom the package - Pipes them to GPG for signing
- Embeds the signature as
_gpgoriginin the new package
Step-by-Step Signing
If you prefer separate steps:
# Extract payload to a file
debx sign --extract mypackage.deb > payload.bin
# Sign the payload
gpg --armor --detach-sign --output signature.asc payload.bin
# Update the package with signature
cat signature.asc | debx sign --update mypackage.deb -o mypackage.signed.deb
Custom Output Path
By default, signed packages are named <original>.signed.deb. Specify a custom path:
debx sign --extract pkg.deb | gpg --armor --detach-sign | \
debx sign --update pkg.deb -o /path/to/signed-pkg.deb
Developer Guide (Python API)
Creating Packages with DebBuilder
DebBuilder is the main class for programmatically creating .deb packages.
Basic Usage
import os
import tempfile
from debx import DebBuilder, Deb822
builder = DebBuilder()
# Add control file (required)
control = Deb822({
"Package": "mypackage",
"Version": "1.0.0",
"Architecture": "all",
"Maintainer": "Developer <dev@example.com>",
"Description": "Package description",
})
builder.add_control_entry("control", control.dump())
# Add data files
builder.add_data_entry(b"file content", "/path/in/package")
# Generate the package
deb_content = builder.pack()
# Write to file
with tempfile.TemporaryDirectory() as tmp:
path = os.path.join(tmp, "mypackage.deb")
with open(path, "wb") as f:
f.write(deb_content)
assert os.path.exists(path)
assert os.path.getsize(path) > 0
Adding Control Entries
from debx import DebBuilder
builder = DebBuilder()
builder.add_control_entry(
name="control", # Filename in control.tar.gz
content="Package: test\nVersion: 1.0\nArchitecture: all\nMaintainer: Test <test@test.com>\nDescription: Test",
mode=0o644, # File permissions (default: 0o644)
mtime=-1, # Modification time (-1 = current time)
)
assert "control" in builder.control_files
Common control entries:
from debx import DebBuilder, Deb822
builder = DebBuilder()
control = Deb822({
"Package": "test",
"Version": "1.0",
"Architecture": "all",
"Maintainer": "Test <test@test.com>",
"Description": "Test package",
})
# Main control file
builder.add_control_entry("control", control.dump())
# Pre/post installation scripts
builder.add_control_entry("preinst", "#!/bin/sh\necho 'Pre-install'", mode=0o755)
builder.add_control_entry("postinst", "#!/bin/sh\necho 'Post-install'", mode=0o755)
# Pre/post removal scripts
builder.add_control_entry("prerm", "#!/bin/sh\necho 'Pre-remove'", mode=0o755)
builder.add_control_entry("postrm", "#!/bin/sh\necho 'Post-remove'", mode=0o755)
# Configuration files list
builder.add_control_entry("conffiles", "/etc/myapp/config\n")
assert len(builder.control_files) == 6
Adding Data Entries
from debx import DebBuilder
builder = DebBuilder()
builder.add_data_entry(
content=b"binary content", # File content as bytes
name="/usr/bin/myapp", # Absolute path in package
uid=0, # Owner user ID (default: 0)
gid=0, # Owner group ID (default: 0)
mode=0o755, # File permissions (default: 0o644)
mtime=-1, # Modification time (-1 = current time)
symlink_to=None, # Target path for symlinks
)
assert "usr/bin/myapp" in builder.data_files
Creating Symlinks
from debx import DebBuilder
builder = DebBuilder()
# Create the target file
builder.add_data_entry(
b"#!/bin/sh\necho 'Hello'\n",
"/usr/bin/myapp",
mode=0o755
)
# Create a symlink to it
builder.add_data_entry(
b"", # Empty content for symlinks
"/usr/bin/myapp-link",
symlink_to="/usr/bin/myapp"
)
assert "usr/bin/myapp" in builder.data_files
assert "usr/bin/myapp-link" in builder.data_files
Reading Files from Disk
import os
import tempfile
from pathlib import Path
from debx import DebBuilder
builder = DebBuilder()
with tempfile.TemporaryDirectory() as tmp:
# Create test files
build_dir = Path(tmp) / "build"
build_dir.mkdir()
myapp = build_dir / "myapp"
myapp.write_bytes(b"#!/bin/sh\necho hello")
config_dir = Path(tmp) / "config"
config_dir.mkdir()
config_file = config_dir / "myapp.conf"
config_file.write_bytes(b"key=value")
# Read a file and add it to the package
binary = myapp.read_bytes()
builder.add_data_entry(binary, "/usr/bin/myapp", mode=0o755)
# Add configuration file
config = config_file.read_bytes()
builder.add_data_entry(config, "/etc/myapp/myapp.conf", mode=0o644)
assert "usr/bin/myapp" in builder.data_files
assert "etc/myapp/myapp.conf" in builder.data_files
Directory Handling
Directories are created automatically based on file paths:
from debx import DebBuilder
builder = DebBuilder()
# This automatically creates /usr, /usr/share, and /usr/share/myapp directories
builder.add_data_entry(b"content", "/usr/share/myapp/data.txt")
assert len(builder.directories) == 3
MD5 Checksums
MD5 checksums are automatically calculated and included in control.tar.gz/md5sums:
from pathlib import PurePosixPath
from debx import DebBuilder
builder = DebBuilder()
builder.add_data_entry(b"content", "/usr/bin/myapp")
# Access checksums before packing
assert PurePosixPath("/usr/bin/myapp") in builder.md5sums
assert builder.md5sums[PurePosixPath("/usr/bin/myapp")] == "9a0364b9e99bb480dd25e1f0284c8555"
Reading Packages with DebReader
DebReader opens and reads existing .deb packages.
Basic Usage
import io
from debx import DebBuilder, DebReader, Deb822
# First create a package to read
builder = DebBuilder()
control = Deb822({
"Package": "test-pkg",
"Version": "1.0.0",
"Architecture": "all",
"Maintainer": "Test <test@example.com>",
"Description": "Test package",
})
builder.add_control_entry("control", control.dump())
builder.add_data_entry(b"test content", "/usr/bin/myapp")
builder.add_data_entry(b"config data", "/etc/myapp/config")
deb_content = builder.pack()
# Now read it
reader = DebReader(io.BytesIO(deb_content))
# Access control archive (tarfile.TarFile)
control_names = reader.control.getnames()
assert "control" in control_names
assert "md5sums" in control_names
# Access data archive (tarfile.TarFile)
data_names = reader.data.getnames()
assert "usr/bin/myapp" in data_names
assert "etc/myapp/config" in data_names
Reading the Control File
import io
from debx import DebBuilder, DebReader, Deb822
# Create a test package
builder = DebBuilder()
control = Deb822({
"Package": "test-pkg",
"Version": "2.0.0",
"Architecture": "all",
"Maintainer": "Test <test@example.com>",
"Description": "A test package for reading",
})
builder.add_control_entry("control", control.dump())
builder.add_data_entry(b"data", "/usr/bin/test")
deb_content = builder.pack()
# Read and parse control file
reader = DebReader(io.BytesIO(deb_content))
control_member = reader.control.extractfile("control")
control_content = control_member.read().decode("utf-8")
parsed = Deb822.parse(control_content)
assert parsed["Package"] == "test-pkg"
assert parsed["Version"] == "2.0.0"
assert "test package" in parsed["Description"]
Extracting Data Files
import io
import tempfile
from debx import DebBuilder, DebReader, Deb822
# Create a test package
builder = DebBuilder()
control = Deb822({
"Package": "test-pkg",
"Version": "1.0.0",
"Architecture": "all",
"Maintainer": "Test <test@example.com>",
"Description": "Test",
})
builder.add_control_entry("control", control.dump())
builder.add_data_entry(b"binary content here", "/usr/bin/myapp")
deb_content = builder.pack()
reader = DebReader(io.BytesIO(deb_content))
# List all files
files = []
for member in reader.data.getmembers():
files.append((member.name, member.size))
assert any("usr/bin/myapp" in f[0] for f in files)
# Extract a specific file
content = reader.data.extractfile("usr/bin/myapp").read()
assert content == b"binary content here"
# Extract to directory
with tempfile.TemporaryDirectory() as tmp:
reader.data.extractall(tmp)
Reading MD5 Checksums
import io
from debx import DebBuilder, DebReader, Deb822
# Create a test package
builder = DebBuilder()
control = Deb822({
"Package": "test-pkg",
"Version": "1.0.0",
"Architecture": "all",
"Maintainer": "Test <test@example.com>",
"Description": "Test",
})
builder.add_control_entry("control", control.dump())
builder.add_data_entry(b"file content", "/usr/share/test/file.txt")
deb_content = builder.pack()
reader = DebReader(io.BytesIO(deb_content))
md5sums = reader.control.extractfile("md5sums")
checksums = {}
for line in md5sums.read().decode().splitlines():
checksum, filepath = line.split(maxsplit=1)
checksums[filepath.strip()] = checksum.strip()
assert "usr/share/test/file.txt" in checksums
Working with Control Files (Deb822)
The Deb822 class parses and generates Debian control file format (RFC 822 style).
Parsing Control Files
from debx import Deb822
# Parse from string
control = Deb822.parse("""
Package: example
Version: 1.0.0
Architecture: all
Description: Short description
This is a longer description that
spans multiple lines.
""")
assert control["Package"] == "example"
assert control["Version"] == "1.0.0"
assert "Short description" in control["Description"]
assert "longer description" in control["Description"]
Creating Control Files
from debx import Deb822
# From dictionary
control = Deb822({
"Package": "mypackage",
"Version": "1.0.0",
"Architecture": "amd64",
"Maintainer": "Name <email@example.com>",
"Depends": "libc6 (>= 2.17), libssl3",
"Description": "Short description\n Long description line 1\n Long description line 2",
})
# Generate control file content
content = control.dump()
assert "Package: mypackage" in content
assert "Version: 1.0.0" in content
assert "Architecture: amd64" in content
Modifying Control Files
from debx import Deb822
existing_content = """
Package: mypackage
Version: 1.0.0
Depends: python3
Suggests: vim
"""
control = Deb822.parse(existing_content)
# Modify fields
control["Version"] = "2.0.0"
# Add new fields
control["Recommends"] = "nginx"
# Remove fields
del control["Suggests"]
# Check field existence
assert "Depends" in control
assert control["Depends"] == "python3"
# Iterate over fields
keys = list(control)
assert "Package" in keys
assert "Version" in keys
# Convert to dict
data = control.to_dict()
assert data["Version"] == "2.0.0"
assert "Suggests" not in data
Reading from File
import tempfile
from pathlib import Path
from debx import Deb822
with tempfile.TemporaryDirectory() as tmp:
control_path = Path(tmp) / "control"
control_path.write_text("""Package: test
Version: 1.0
Architecture: all
Maintainer: Test <test@test.com>
Description: Test package
""")
control = Deb822.from_file(control_path)
assert control["Package"] == "test"
assert control["Version"] == "1.0"
Multi-line Fields
Multi-line values use continuation lines (starting with space):
from debx import Deb822
control = Deb822({
"Description": "Short description\n"
"This is line 2 of the long description\n"
"This is line 3 of the long description",
})
dumped = control.dump()
assert "Description: Short description" in dumped
assert " This is line 2" in dumped
assert " This is line 3" in dumped
Low-Level AR Archive Operations
For advanced use cases, you can work directly with AR archives.
Reading AR Archives
import io
from debx import DebBuilder, Deb822, unpack_ar_archive
# Create a package to read
builder = DebBuilder()
control = Deb822({
"Package": "test",
"Version": "1.0",
"Architecture": "all",
"Maintainer": "Test <test@test.com>",
"Description": "Test",
})
builder.add_control_entry("control", control.dump())
builder.add_data_entry(b"data", "/usr/bin/test")
deb_content = builder.pack()
# Read the AR archive
files = []
for ar_file in unpack_ar_archive(io.BytesIO(deb_content)):
files.append({
"name": ar_file.name,
"size": ar_file.size,
"mode": oct(ar_file.mode),
"uid": ar_file.uid,
"gid": ar_file.gid,
})
assert any(f["name"] == "debian-binary" for f in files)
assert any(f["name"] == "control.tar.gz" for f in files)
assert any(f["name"] == "data.tar.bz2" for f in files)
Creating AR Archives
import io
import tempfile
from pathlib import Path
from debx import ArFile, pack_ar_archive, unpack_ar_archive
# Create from bytes
file1 = ArFile.from_bytes(b"content", "filename.txt")
with tempfile.TemporaryDirectory() as tmp:
# Create a test file
test_file = Path(tmp) / "test.txt"
test_file.write_bytes(b"test file content")
# Create from file on disk
file2 = ArFile.from_file(test_file, arcname="renamed.txt")
# Create from file object
data_file = Path(tmp) / "data.bin"
data_file.write_bytes(b"binary data")
with open(data_file, "rb") as f:
file3 = ArFile.from_fp(f, "data.bin")
# Pack into AR archive
archive_content = pack_ar_archive(file1, file2, file3)
# Verify
unpacked = list(unpack_ar_archive(io.BytesIO(archive_content)))
assert len(unpacked) == 3
assert unpacked[0].name == "filename.txt"
assert unpacked[1].name == "renamed.txt"
assert unpacked[2].name == "data.bin"
ArFile Properties
from debx import ArFile
ar_file = ArFile.from_bytes(b"content", "test.txt")
assert ar_file.name == "test.txt" # Filename (max 16 chars)
assert ar_file.size == 7 # Content size in bytes
assert ar_file.content == b"content" # Raw bytes content
assert ar_file.uid == 0 # Owner user ID
assert ar_file.gid == 0 # Owner group ID
assert ar_file.mode == 0o100644 # File mode (permissions)
assert ar_file.mtime > 0 # Modification time (Unix timestamp)
assert ar_file.fp.read() == b"content" # BytesIO file object for content
Tutorials
Tutorial 1: Creating a Simple Package
Create a minimal "Hello World" package from scratch.
import os
import tempfile
from debx import DebBuilder, Deb822
# 1. Initialize builder
builder = DebBuilder()
# 2. Create control metadata
control = Deb822({
"Package": "hello-debx",
"Version": "1.0.0",
"Architecture": "all",
"Maintainer": "Tutorial <tutorial@example.com>",
"Description": "Hello World from debx\n"
"A simple example package created with the debx library.",
"Section": "misc",
"Priority": "optional",
})
builder.add_control_entry("control", control.dump())
# 3. Add executable script
script = b"""#!/bin/sh
echo "Hello from debx!"
echo "This package was created with Python."
"""
builder.add_data_entry(script, "/usr/bin/hello-debx", mode=0o755)
# 4. Add documentation
readme = b"""Hello Debx Package
==================
This is a demonstration package created with the debx Python library.
Usage: hello-debx
"""
builder.add_data_entry(readme, "/usr/share/doc/hello-debx/README")
# 5. Build and save
with tempfile.TemporaryDirectory() as tmp:
path = os.path.join(tmp, "hello-debx_1.0.0_all.deb")
with open(path, "wb") as f:
f.write(builder.pack())
assert os.path.exists(path)
assert os.path.getsize(path) > 0
print("Package created: hello-debx_1.0.0_all.deb")
Install and test:
sudo dpkg -i hello-debx_1.0.0_all.deb
hello-debx
# Output: Hello from debx!
Tutorial 2: Extracting and Modifying a Package
Read an existing package, modify it, and create a new version.
import io
import os
import tempfile
from debx import DebReader, DebBuilder, Deb822
# First, create an "original" package to modify
original_builder = DebBuilder()
original_control = Deb822({
"Package": "test-package",
"Version": "1.0.0",
"Architecture": "all",
"Maintainer": "Test <test@example.com>",
"Description": "Original package",
})
original_builder.add_control_entry("control", original_control.dump())
original_builder.add_data_entry(b"original binary", "/usr/bin/testapp", mode=0o755)
original_builder.add_data_entry(b"config data", "/etc/testapp/config", mode=0o644)
original_deb = original_builder.pack()
# 1. Read the original package
reader = DebReader(io.BytesIO(original_deb))
# Parse control file
control_content = reader.control.extractfile("control").read().decode()
control = Deb822.parse(control_content)
# Store all data files
data_files = {}
for member in reader.data.getmembers():
if member.isfile():
content = reader.data.extractfile(member).read()
data_files[member.name] = {
"content": content,
"mode": member.mode,
"uid": member.uid,
"gid": member.gid,
}
# 2. Modify the package
control["Version"] = "1.0.1" # Bump version
control["Description"] = control["Description"] + "\n Modified with debx."
# 3. Rebuild the package
builder = DebBuilder()
# Add modified control
builder.add_control_entry("control", control.dump())
# Re-add all data files
for path, info in data_files.items():
builder.add_data_entry(
info["content"],
f"/{path}", # Add leading slash
mode=info["mode"],
uid=info["uid"],
gid=info["gid"],
)
# 4. Save the modified package
with tempfile.TemporaryDirectory() as tmp:
path = os.path.join(tmp, "modified.deb")
with open(path, "wb") as f:
f.write(builder.pack())
assert os.path.exists(path)
# Verify the modification
with open(path, "rb") as f:
verify_reader = DebReader(f)
verify_control = Deb822.parse(
verify_reader.control.extractfile("control").read().decode()
)
assert verify_control["Version"] == "1.0.1"
assert "Modified with debx" in verify_control["Description"]
print(f"Modified package: {control['Package']}_{control['Version']}")
Tutorial 3: Building a Python Application Package
Package a Python application with configuration and systemd service.
import os
import tempfile
from debx import DebBuilder, Deb822
def build_python_app_package(output_dir):
builder = DebBuilder()
# Control file
control = Deb822({
"Package": "myapp",
"Version": "2.0.0",
"Architecture": "all",
"Maintainer": "DevTeam <dev@company.com>",
"Depends": "python3 (>= 3.10)",
"Description": "My Python Application\n"
"A production-ready Python application\n"
"with systemd service integration.",
"Section": "python",
"Priority": "optional",
"Homepage": "https://github.com/company/myapp",
})
builder.add_control_entry("control", control.dump())
# Post-installation script
postinst = """#!/bin/sh
set -e
echo "MyApp installed successfully!"
"""
builder.add_control_entry("postinst", postinst, mode=0o755)
# Pre-removal script
prerm = """#!/bin/sh
set -e
echo "Removing MyApp..."
"""
builder.add_control_entry("prerm", prerm, mode=0o755)
# Configuration files list
builder.add_control_entry("conffiles", "/etc/myapp/config.yaml\n")
# Main application script
app_script = b"""#!/usr/bin/env python3
import logging
from pathlib import Path
CONFIG_PATH = Path("/etc/myapp/config.yaml")
def main():
print("MyApp is running...")
if __name__ == "__main__":
main()
"""
builder.add_data_entry(app_script, "/opt/myapp/app.py", mode=0o755)
# Wrapper script
wrapper = b"""#!/bin/sh
exec /usr/bin/python3 /opt/myapp/app.py "$@"
"""
builder.add_data_entry(wrapper, "/usr/bin/myapp", mode=0o755)
# Default configuration
config = b"""# MyApp Configuration
server:
host: 0.0.0.0
port: 8080
logging:
level: INFO
"""
builder.add_data_entry(config, "/etc/myapp/config.yaml", mode=0o644)
# Systemd service file
service = b"""[Unit]
Description=MyApp Python Application
After=network.target
[Service]
Type=simple
User=root
ExecStart=/usr/bin/myapp
Restart=on-failure
[Install]
WantedBy=multi-user.target
"""
builder.add_data_entry(service, "/lib/systemd/system/myapp.service", mode=0o644)
# Build package
package_name = f"{control['Package']}_{control['Version']}_all.deb"
package_path = os.path.join(output_dir, package_name)
with open(package_path, "wb") as f:
f.write(builder.pack())
print(f"Built: {package_name}")
return package_path
# Test the function
with tempfile.TemporaryDirectory() as tmp:
path = build_python_app_package(tmp)
assert os.path.exists(path)
assert os.path.getsize(path) > 0
API Reference
DebBuilder
| Method | Description |
|---|---|
add_control_entry(name, content, mode=0o644, mtime=-1) |
Add a file to control.tar.gz |
add_data_entry(content, name, uid=0, gid=0, mode=0o644, mtime=-1, symlink_to=None) |
Add a file to data.tar.bz2 |
pack() |
Build and return the .deb package as bytes |
create_control_tar() |
Generate control.tar.gz content |
create_data_tar() |
Generate data.tar.bz2 content |
Properties:
md5sums: dict[PurePosixPath, str]- MD5 checksums of data filesdata_files: dict- Data entries to be packedcontrol_files: dict- Control entries to be packeddirectories: set- Directories that will be created
DebReader
| Attribute | Type | Description |
|---|---|---|
control |
tarfile.TarFile |
Control archive (control.tar.gz) |
data |
tarfile.TarFile |
Data archive (data.tar.*) |
Deb822
| Method | Description |
|---|---|
parse(text) |
Parse Deb822 format string |
from_file(path) |
Parse from file path |
dump() |
Generate Deb822 format string |
to_dict() |
Convert to dictionary |
Implements MutableMapping[str, Any] - supports [], in, del, len(), iteration.
AR Archive Functions
| Function | Description |
|---|---|
pack_ar_archive(*files) |
Create AR archive from ArFile objects |
unpack_ar_archive(fp) |
Iterate ArFile objects from archive |
ArFile
| Method | Description |
|---|---|
from_bytes(data, name, **kwargs) |
Create from bytes |
from_file(path, arcname="") |
Create from file path |
from_fp(fp, name, **kwargs) |
Create from file object |
dump() |
Serialize to AR format |
Attributes: name, size, content, uid, gid, mode, mtime, fp
Exceptions
| Exception | Description |
|---|---|
ARFileError |
Base exception for AR operations |
EmptyHeaderError |
AR header is empty |
TruncatedHeaderError |
AR header is incomplete |
TruncatedDataError |
AR data is incomplete |
License
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file debx-0.2.11.tar.gz.
File metadata
- Download URL: debx-0.2.11.tar.gz
- Upload date:
- Size: 79.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd6ebd722473261496b51c86972f0a04b5147fbab2c9a41d34e08ed36b1513a9
|
|
| MD5 |
37c4e5bc55a6e384cfc9e1f4b7924613
|
|
| BLAKE2b-256 |
28a5fd65a2c794760c90898c7d6de3f871edf4d179db6692ee313c4a6a1d4e26
|
File details
Details for the file debx-0.2.11-py3-none-any.whl.
File metadata
- Download URL: debx-0.2.11-py3-none-any.whl
- Upload date:
- Size: 24.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e71715dc334f5accf6076bb4088ce0ea26f28aeee392b608d1b72381e3819a7
|
|
| MD5 |
5b94164ea9ee46d6ad4a2d9356c86771
|
|
| BLAKE2b-256 |
61e0c455f77a1bfe44e28371d6fa2e73ea4d3056eacc6e16830e177b787469a1
|